JavaScript is required

自定义连线(#line

连线插槽用于替换一条线的 SVG/文本渲染。它接收的不是原始 JsonLine,而是已经由图谱实例计算过的 lineConfiglineConfig 中包含当前线对象、起点、终点、多线偏移信息和默认配置,适合直接生成 path、箭头和标签。

如果只是修改线条颜色、宽度、形状、虚线、箭头、文本位置,优先使用 连线数据模型 中的字段;如果需要自定义 path 结构、添加多个标签、渲染状态图标、把连线文本做成复杂 HTML,再使用 #line

1. 各平台写法

平台 写法 说明
Vue 3 / Vue 2 <template #line="{ lineConfig, checked, graphInstanceId, defaultLineTextOnPath }"> 推荐写法
React <RGSlotOnLine>{props => ...}</RGSlotOnLine> children 必须是函数
React lineSlot={({ lineConfig }) => ...} 属性式写法,不能和 <RGSlotOnLine> 同时使用
Svelte <g slot="line" let:lineConfig let:checked let:graphInstanceId> 内容位于 SVG <g> 中,根元素建议使用 SVG 元素

2. 插槽参数

源码中的类型定义:

export type RGLineSlotProps = {
  lineConfig: RGGenerateLineConfig;
  checked?: boolean;
  defaultLineTextOnPath?: boolean;
  graphInstanceId?: string;
};
参数 类型 作用
lineConfig RGGenerateLineConfig 当前线的渲染配置。普通线来自 generateLineConfig(line),假线来自 generateFakeLineConfig(fakeLine)
checked boolean | undefined 当前线是否被内部控制器标记为 checked
defaultLineTextOnPath boolean | undefined 全局默认是否启用沿路径文本,来源于 options.defaultLineTextOnPath
graphInstanceId string | undefined 当前图谱实例 ID,用于生成不冲突的 SVG path id

RGGenerateLineConfig

export type RGGenerateLineConfig = {
  line: RGLine | RGFakeLine;
  from?: RGLineTarget;
  to?: RGLineTarget;
  totalLinesBetweenNodes?: number;
  currentLineIndex?: number;
  defaultOptions?: any;
};
字段 类型 说明
line RGLine | RGFakeLine 当前渲染的线对象。普通线含 from/to/text 等数据字段;假线含拖拽中连线的临时目标
from RGLineTarget | undefined 起点目标。普通线通常是节点边界或指定连接点
to RGLineTarget | undefined 终点目标
totalLinesBetweenNodes number | undefined 两个节点之间的总线数,计算多线间距时使用
currentLineIndex number | undefined 当前线在多线组中的序号
defaultOptions any 当前连线计算所需默认值,例如默认线形、连接点规则等

lineConfig 可能因为节点隐藏、端点无法解析、EasyView 等内部条件无法生成。源码中 RGLinePeel 只有在 config 存在时才渲染插槽,因此进入 #line 时通常已经是可渲染状态。

3. line 对象中常用字段

完整字段请参考 连线数据模型。在连线插槽中最常使用:

字段 类型 用途
line.id string 线唯一 ID。自定义 SVG path id 通常会拼接 graphInstanceIdline.id
line.text string | undefined 默认线条文本
line.type string | undefined 线类型。可按业务类型分发模板
line.data Record<string, any> | undefined 业务数据,例如状态、权重、起止标签
line.color string | undefined 线条颜色,会写入 --rg-line-color
line.lineWidth number | undefined 线宽,单位 px,会写入 --rg-line-width
line.opacity number | undefined 透明度
line.lineShape RGLineShape | undefined 线形,影响 generateLinePath 计算结果
line.dashType number | string | undefined 虚线样式 class 的后缀
line.animation number | string | undefined 线条动画 class 的后缀
line.useTextOnPath boolean | undefined 是否让文本沿路径显示
line.showStartArrow / line.showEndArrow boolean | undefined 是否显示起点/终点箭头
line.startMarkerId / line.endMarkerId string | undefined 自定义 marker id
line.cssVars Record<string, string> 附加到线外层的 CSS 变量
line.isFakeLine boolean | undefined 是否是假线。拖拽连线、编辑连线时可能为真

4. 可复用的内置组件与实例 API

Vue3/React/Svelte 包中当前都公开了 RGLinePathRGLineText;Vue3/React 也在默认连线实现中使用它们。

名称 类型 作用
RGLinePath 组件 根据 lineConfiglinePathInfo 渲染 SVG path、箭头 marker、沿路径文本
RGLineText 组件 将 HTML 线条文本渲染到内部线条文本容器中
graphInstance.generateLinePath(lineConfig) 实例 API 生成 RGLinePathInfo,包含 path 数据、文本位置、关键点等
graphInstance.generateLineTextStyle(lineConfig, linePathInfo) 实例 API 生成普通 HTML 线条文本的文本内容和 CSS style
graphInstance.generateLineTextStyle4TextOnPath(lineConfig) 实例 API 生成 SVG <textPath> 所需偏移、锚点、文本等信息
graphInstance.getArrowMarkerId(line, isStartArrow) 实例 API 获取起点/终点箭头 marker 引用
graphInstance.onLineClick(line, event) 实例 API 触发内置线条点击逻辑和外部事件

RGLinePathInfo

export type RGLinePathInfo = {
  pathData: string;
  pathCommands: any[];
  textPosition: RGCoordinate;
  points: RGCoordinate[];
  startDirection?: string;
  endDirection?: string;
};
字段 类型 说明
pathData string 可直接用于 <path d="..."> 的 SVG path 字符串
pathCommands any[] 生成 path 的命令集合,适合高级自定义时参考
textPosition { x, y } 默认文本位置
points RGCoordinate[] 当前线计算出的关键点
startDirection / endDirection string | undefined 起止方向,主要给箭头、折线等高级逻辑使用

5. 复用默认 path,只改线条内容

Vue3 示例:

<script setup lang="ts">
import { computed } from 'vue';
import {
  RGLinePath,
  RGLineText,
  RGHooks,
  type RGLineSlotProps
} from '@relation-graph/vue';

const props = defineProps<RGLineSlotProps>();
const graphInstance = RGHooks.useGraphInstance();

const linePathInfo = computed(() => graphInstance.generateLinePath(props.lineConfig));
const textStyle = computed(() => {
  return graphInstance.generateLineTextStyle(props.lineConfig, linePathInfo.value);
});

function onLineClick(event: MouseEvent | TouchEvent) {
  graphInstance.onLineClick(props.lineConfig.line, event);
}
</script>

<template>
  <RGLinePath
    :line-config="lineConfig"
    :line-path-info="linePathInfo"
    :checked="checked"
    :graph-instance-id="graphInstanceId"
    :use-text-on-path="false"
    @onLineClick="onLineClick"
  />

  <RGLineText
    v-if="lineConfig.line.text"
    :line-config="lineConfig"
    :line-path-info="linePathInfo"
    :checked="checked"
  >
    <div
      class="my-line-label"
      :style="textStyle.cssStyles"
      @click="onLineClick"
    >
      {{ textStyle.text }}
      <span v-if="lineConfig.line.data?.status" class="status">
        {{ lineConfig.line.data.status }}
      </span>
    </div>
  </RGLineText>
</template>

在页面中使用:

<RelationGraph :options="graphOptions" :initial-data="graphData">
  <template #line="lineSlotProps">
    <MyLineContent v-bind="lineSlotProps" />
  </template>
</RelationGraph>

6. React 示例

import {
  RelationGraph,
  RGSlotOnLine,
  RGLinePath,
  RGLineText,
  RGHooks,
  RGLineShape,
  type RGLineSlotProps
} from '@relation-graph/react';

function MyLineContent({
  lineConfig,
  checked,
  graphInstanceId,
  defaultLineTextOnPath
}: RGLineSlotProps) {
  const graphInstance = RGHooks.useGraphInstance();
  const linePathInfo = graphInstance.generateLinePath(lineConfig);
  const textStyle = graphInstance.generateLineTextStyle(lineConfig, linePathInfo);
  const useTextOnPath = !!(lineConfig.line.useTextOnPath || defaultLineTextOnPath);
  const useSvgTextPath = useTextOnPath && lineConfig.line.lineShape !== RGLineShape.StandardStraight;

  const onLineClick = (event: React.MouseEvent | React.TouchEvent) => {
    graphInstance.onLineClick(lineConfig.line, event.nativeEvent);
  };

  return (
    <>
      <RGLinePath
        lineConfig={lineConfig}
        linePathInfo={linePathInfo}
        checked={checked}
        graphInstanceId={graphInstanceId}
        useTextOnPath={useSvgTextPath}
        onLineClick={onLineClick}
      />

      {lineConfig.line.text && !useSvgTextPath && (
        <RGLineText
          lineConfig={lineConfig}
          linePathInfo={linePathInfo}
          checked={checked}
        >
          <div
            className="my-line-label"
            style={textStyle.cssStyles}
            onClick={onLineClick}
          >
            {textStyle.text}
            {lineConfig.line.data?.status && (
              <span className="status">{lineConfig.line.data.status}</span>
            )}
          </div>
        </RGLineText>
      )}
    </>
  );
}

<RelationGraph options={graphOptions} initialData={graphData}>
  <RGSlotOnLine>
    {(props) => <MyLineContent {...props} />}
  </RGSlotOnLine>
</RelationGraph>

7. 完全自定义 SVG path

如果你不复用 RGLinePath,可以自己渲染 SVG。此时建议保留以下能力:

能力 为什么重要
使用 generateLinePath(lineConfig) 保持内置线形、多线偏移、连接点规则一致
保留一条较粗的透明点击 path 细线不容易点击,默认实现使用 .rg-line-bg 承接点击
给外层 <g> 添加 rg-line-peeldata-id 方便内部方法 isLine(el)、CSS 状态和调试
使用 getArrowMarkerId 保持默认箭头 marker、自定义 marker 与配置一致
处理 disablePointEvent / opacity === 0 禁用事件时不应继续响应点击

示例:

<template #line="{ lineConfig, checked, graphInstanceId }">
  <CustomSvgLine
    :line-config="lineConfig"
    :checked="checked"
    :graph-instance-id="graphInstanceId"
  />
</template>

CustomSvgLine.vue

<script setup lang="ts">
import { computed } from 'vue';
import { RGHooks, type RGLineSlotProps } from '@relation-graph/vue';

const props = defineProps<RGLineSlotProps>();
const graphInstance = RGHooks.useGraphInstance();

const pathInfo = computed(() => graphInstance.generateLinePath(props.lineConfig));
const line = computed(() => props.lineConfig.line);
const startMarker = computed(() => graphInstance.getArrowMarkerId(line.value, true));
const endMarker = computed(() => graphInstance.getArrowMarkerId(line.value, false));
const pathId = computed(() => `${props.graphInstanceId}-${line.value.id}`);

function onLineClick(event: MouseEvent | TouchEvent) {
  graphInstance.onLineClick(line.value, event);
}
</script>

<template>
  <g
    class="rg-line-peel my-svg-line"
    :class="[
      line.className,
      line.selected && 'rg-line-selected',
      checked && 'rg-line-checked',
      (line.disablePointEvent || line.opacity === 0) && 'rg-line-disable-events'
    ]"
    :data-id="line.id"
    :style="{
      '--rg-line-color': line.color,
      '--rg-line-width': line.lineWidth ? line.lineWidth + 'px' : undefined,
      '--rg-line-opacity': line.opacity,
      '--rg-line-marker-start': startMarker,
      '--rg-line-marker-end': endMarker,
      ...(line.cssVars || {})
    }"
  >
    <path
      :d="pathInfo.pathData"
      class="rg-line-bg"
      fill="none"
      stroke="transparent"
      stroke-width="12"
      @click="onLineClick"
    />
    <path
      :id="pathId"
      :d="pathInfo.pathData"
      class="rg-line"
      fill="none"
      :marker-start="startMarker"
      :marker-end="endMarker"
    />
  </g>
</template>

8. 沿路径文本与普通文本

relation-graph 当前默认逻辑:

情况 默认行为
line.useTextOnPath 为真 尝试使用路径文本
options.defaultLineTextOnPath 为真 未单独配置的线也尝试使用路径文本
线形为 RGLineShape.StandardStraight 默认实现不会使用 SVG <textPath>,而是用普通 HTML 文本
其他线形且启用路径文本 默认实现使用 SVG <textPath>
未启用路径文本 默认实现用 RGLineText 渲染 HTML 标签

如果自定义文本:

  • 普通 HTML 标签可复用 RGLineText,并使用 generateLineTextStyle 提供的位置和样式。
  • SVG 路径文本可以复用 RGLinePathuseTextOnPath,或自己调用 generateLineTextStyle4TextOnPath
  • 多标签场景建议自己基于 linePathInfo.pointslinePathInfo.textPosition 计算位置。

9. 假线的处理

拖拽创建连线、编辑连线过程中可能出现 RGFakeLine。源码中 RGLinePeel 会根据 line.isFakeLine 选择:

line.isFakeLine
  ? graphInstance.generateFakeLineConfig(line)
  : graphInstance.generateLineConfig(line)

因此你的 #line 可能收到普通线,也可能收到假线。判断方式:

const isFakeLine = !!lineConfig.line.isFakeLine;

假线常见特点:

特点 说明
line.isFakeLine 为真 表示它不是数据中的持久化连线
起点/终点可能是节点、节点点位、画布点、HTML 元素等 取决于编辑/拖拽中的目标类型
通常不应保存到业务数据 它用于临时视觉反馈
插槽最好兼容它 否则拖拽连线时可能看不到临时线

如果你的业务自定义线条不需要特殊处理假线,可以仍然使用 generateLinePath(lineConfig),让实例处理端点计算。

10. 线条样式建议

推荐使用 CSS 变量承接数据字段:

.my-svg-line .rg-line {
  stroke: var(--rg-line-color);
  stroke-width: var(--rg-line-width);
  opacity: var(--rg-line-opacity);
  marker-start: var(--rg-line-marker-start);
  marker-end: var(--rg-line-marker-end);
}

.my-svg-line.rg-line-selected .rg-line {
  filter: drop-shadow(0 0 3px #3b82f6);
}

.my-line-label {
  padding: 2px 6px;
  border-radius: 4px;
  background: #ffffff;
  color: var(--rg-line-fontcolor);
  font-size: var(--rg-line-fontsize);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
  pointer-events: auto;
}

不要只在自定义 path 中硬编码 strokestroke-width。更推荐:

  • line.color 表达线条语义色。
  • line.lineWidth 表达线宽。
  • line.cssVars 补充复杂主题变量。
  • 在 CSS 中读取 --rg-line-color--rg-line-width 等变量。

11. 常见问题

为什么自定义连线后点击不到线?

SVG 细线本身很难点击。默认实现会渲染一条 .rg-line-bg path,使用较粗的透明 stroke 接收点击。自定义线条也建议保留:

<path class="rg-line-bg" d="..." stroke="transparent" stroke-width="12" fill="none" />

为什么箭头不显示?

检查:

  • 是否使用了 graphInstance.getArrowMarkerId(line, true/false)
  • 是否把结果设置到了 marker-start / marker-end
  • 是否生成了唯一 path id,避免多实例冲突。
  • line.showStartArrow / line.showEndArrow 或默认 marker 配置是否允许显示。

为什么文本位置不对?

普通文本建议使用 generateLineTextStyle(lineConfig, linePathInfo),它会考虑线形、多线偏移和文本位置配置。完全自定义文本时,需要自己处理 line.textOffsetXline.textOffsetYline.placeTextline.textAnchor 等字段。

为什么 lineConfig.fromlineConfig.to 没有值?

当节点隐藏、端点目标无法解析、临时连线目标不存在时,配置可能无法生成。当前组件只在 config 存在时渲染 #line,所以常规 slot 内较少遇到。但如果你自己调用 generateLineConfig,要处理返回 false 的情况。

能否在 #line 中渲染 HTML?

可以,但连线插槽本身位于 SVG <g> 内。直接写 HTML 不一定合法或表现一致。推荐使用内置 RGLineText,它会把 HTML 标签渲染到内部文本容器;或使用 SVG <foreignObject>,但要自行处理浏览器兼容和尺寸。

12. 下一步阅读