自定义连线(#line)
连线插槽用于替换一条线的 SVG/文本渲染。它接收的不是原始 JsonLine,而是已经由图谱实例计算过的 lineConfig。lineConfig 中包含当前线对象、起点、终点、多线偏移信息和默认配置,适合直接生成 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 通常会拼接 graphInstanceId 与 line.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 包中当前都公开了 RGLinePath 与 RGLineText;Vue3/React 也在默认连线实现中使用它们。
| 名称 | 类型 | 作用 |
|---|---|---|
RGLinePath |
组件 | 根据 lineConfig 和 linePathInfo 渲染 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-peel 和 data-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 路径文本可以复用
RGLinePath的useTextOnPath,或自己调用generateLineTextStyle4TextOnPath。 - 多标签场景建议自己基于
linePathInfo.points或linePathInfo.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 中硬编码 stroke 和 stroke-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.textOffsetX、line.textOffsetY、line.placeText、line.textAnchor 等字段。
为什么 lineConfig.from 或 lineConfig.to 没有值?
当节点隐藏、端点目标无法解析、临时连线目标不存在时,配置可能无法生成。当前组件只在 config 存在时渲染 #line,所以常规 slot 内较少遇到。但如果你自己调用 generateLineConfig,要处理返回 false 的情况。
能否在 #line 中渲染 HTML?
可以,但连线插槽本身位于 SVG <g> 内。直接写 HTML 不一定合法或表现一致。推荐使用内置 RGLineText,它会把 HTML 标签渲染到内部文本容器;或使用 SVG <foreignObject>,但要自行处理浏览器兼容和尺寸。