[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:custom-line-slot-rendering":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 带端点标签和详情操作的自定义连线插槽渲染\n\n## 这个示例构建了什么\n\n这个示例构建了一个从左到右的树形图，并用自定义插槽组件替换默认的连线渲染器。最终可见的效果是一组弯曲的蓝色连线，线上有一个移动的白点，每条连接都有标签，并且还有直接锚定在计算后路径上的额外 `Start Text` 和 `End Text` 标记。节点本身则尽量保持轻量视觉风格，使用透明框体和选中发光效果，而不是厚重的装饰外观。\n\n用户可以点击连线、触发连线专属的 `Detail` 操作、打开浮动设置窗口、切换画布交互模式，并将当前图导出为图片。这个示例的重点在于，整套连线表现都是自定义的，但 relation-graph 仍然负责提供路径几何、标签定位以及常规的连线点击行为。\n\n## 数据是如何组织的\n\n图数据在 `initializeGraph` 中以内联 `RGJsonData` 的形式组装。它声明了一个名为 `base` 的根节点、若干一级入边和出边，以及节点 `1` 和 `4` 下的一些二级子连接。每条连接仍然使用 relation-graph 的标准结构来表示：`from`、`to` 和 `text`。\n\n在调用 `setJsonData` 之前，只有一个预处理步骤：给每条连线都设置 `showEndArrow = false`。这样可以避免自定义的管道式样和端点标签与默认箭头头部相互干扰。数据加载完成后，图会居中并适配到视口，但不会再做额外的数据转换。\n\n在真实应用里，同样的数据结构可以表示网络路由、系统依赖、设备连接、仓库路径，或组织层级中的交接关系。这个示例刻意让数据载荷保持简单，这样文档重点就能放在连线渲染，而不是特定领域的元数据上。\n\n## relation-graph 是如何使用的\n\n`RGProvider` 包裹了整个演示，这样 relation-graph 的 hooks 就能解析到当前激活的图实例。在 `MyGraph` 内部，`RelationGraph` 被配置为一个从左侧生长的树形布局，使用 `[500, 400]` 的 `levelGaps`、`RGJunctionPoint.lr` 以及 `RGLineShape.StandardCurve`。这些设置让路径足够可预测，从而便于稳定地附加装饰。\n\n主要的扩展点是 `RGSlotOnLine`。示例没有直接接受 relation-graph 内建的边输出，而是把每条连线的 slot props 传给 `MyLineContent`，并同时传入父级回调，用于独立的详情操作。在这个插槽组件内部，通过 `RGHooks.useGraphInstance()` 调用 `generateLinePath` 和 `generateLineTextStyle`，因此这个自定义渲染器依然复用了 relation-graph 自身的几何计算和文本定位逻辑。\n\n接着，`MyLineContent` 继续让 relation-graph 的基础能力参与渲染。`RGLinePath` 负责绘制实际的 SVG 边，并处理转发过来的连线点击事件。这个插槽会在路径渲染器内部注入一个移动的 `\u003Ccircle>`，并添加起点和终点文字分组，它们通过 CSS `offset-path` 跟随同一条计算后的路径移动。当连线使用基于 div 的文本分支时，`RGLineText` 会渲染 HTML 标签容器，并在计算出的连线文字后面追加一个 `Detail` 链接。\n\n在插槽之外，图实例 API 被用于初始化和查看器工具。挂载时，`setJsonData`、`moveToCenter` 和 `zoomToFit` 会完成图的初始化。浮动的 `DraggableWindow` 中包含一个设置面板，它通过 `RGHooks.useGraphStore()` 读取当前图配置，使用 `setOptions` 更新滚轮和拖拽行为，并通过 `prepareForImageGeneration` 和 `restoreAfterImageGeneration` 导出画布。截图辅助逻辑使用 `modern-screenshot` 把准备好的图 DOM 转成可下载图片。\n\n视觉定制最终在 `my-relation-graph.scss` 中完成。这个样式文件移除了默认节点框样式，增加了选中节点的发光效果，加粗了连线背景和前景描边，设置了标签文字颜色，并通过读取插槽中定义的自定义 `--my-line-path` 变量，让圆点和端点标签沿着 SVG 路径运动。\n\n## 关键交互\n\n- 点击自定义 SVG 连线后，仍会进入 relation-graph 正常的 `onLineClick` 流程，因为这个插槽通过 `graphInstance.onLineClick(...)` 转发了事件。\n- 基于 div 的标签分支同样可点击，并会转发相同的连线事件，因此替换连线渲染器并不会移除标准的连线交互。\n- 追加的 `Detail` 操作会回调到 `MyGraph`，并针对当前连线文本弹出 alert。\n- 浮动的 `DraggableWindow` 可以拖动、最小化，并切换为设置面板。\n- 设置面板可以修改滚轮行为、修改拖拽行为，并下载当前图画布的图片。\n\n## 关键代码片段\n\n下面这段代码展示了自定义渲染器所依赖的布局配置和默认连线设置。\n\n```tsx\nconst graphOptions: RGOptions = {\n    defaultJunctionPoint: RGJunctionPoint.lr,\n    defaultLineShape: RGLineShape.StandardCurve,\n    layout: {\n        layoutName: 'tree',\n        from: 'left',\n        levelGaps: [500, 400]\n    }\n};\n```\n\n下面这段代码展示了内联数据集，以及在加载前关闭默认终点箭头的预处理步骤。\n\n```tsx\nconst myJsonData: RGJsonData = {\n    rootId: 'base',\n    nodes: [\n        { id: 'base', text: '🏢 Base', },\n        // ...\n    ],\n    lines: [\n        { from: 'base', to: '1', text: 'Line X01' },\n        // ...\n    ]\n};\nmyJsonData.lines.forEach((line) => {\n    line.showEndArrow = false;\n});\n```\n\n下面这段代码展示了图实例如何加载数据，并立刻把视图调整到适合展示的状态。\n\n```tsx\nawait graphInstance.setJsonData(myJsonData);\ngraphInstance.moveToCenter();\ngraphInstance.zoomToFit();\n```\n\n下面这段代码展示了这个示例如何用自定义插槽组件替换默认连线渲染器。\n\n```tsx\n\u003CRelationGraph\n    options={graphOptions}\n    onLineClick={onLineClick}\n>\n    \u003CRGSlotOnLine>\n        {(lineSlotProps: RGLineSlotProps) => {\n            return (\n                \u003CMyLineContent\n                    {...lineSlotProps}\n                    onMyLineDetailClick={onMyLineDetailClick}\n                />\n            );\n        }}\n    \u003C/RGSlotOnLine>\n\u003C/RelationGraph>\n```\n\n下面这段代码展示了自定义插槽如何获取路径几何、决定文本渲染方式，并把点击事件转发回 relation-graph。\n\n```tsx\nconst linePathInfo = useMemo\u003CRGLinePathInfo>(() => graphInstance.generateLinePath(lineConfig), [lineConfig]);\nconst onLineClick = (e: React.MouseEvent | React.TouchEvent) => {\n    graphInstance.onLineClick(lineConfig.line, e.nativeEvent);\n};\nconst useTextOnPath = (lineConfig.line.useTextOnPath || defaultLineTextOnPath);\nconst useSvgTextPath = useTextOnPath && (lineConfig.line.lineShape !== RGLineShape.StandardStraight);\nconst useDivLineText = !useSvgTextPath && lineConfig.line.text;\nconst textStyle = graphInstance.generateLineTextStyle(lineConfig, linePathInfo);\n```\n\n下面这段代码展示了插槽如何继续使用 `RGLinePath`，同时在其上叠加额外的路径绑定视觉元素。\n\n```tsx\n\u003CRGLinePath\n    lineConfig={lineConfig}\n    linePathInfo={linePathInfo}\n    useTextOnPath={useSvgTextPath}\n    checked={checked}\n    graphInstanceId={graphInstanceId}\n    onLineClick={onLineClick}\n>\n    \u003Ccircle className=\"my-dot\" r=\"5\">\u003C/circle>\n    \u003Cg className=\"my-text-start\" style={{ transform: 'translate(0px, -3px)' }}>\n        \u003Ctext fontSize={10} textAnchor={'start'}>Start Text\u003C/text>\n    \u003C/g>\n    \u003Cg className=\"my-text-end\" style={{ transform: 'translate(0px, -3px)' }}>\n        \u003Ctext fontSize={10} textAnchor={'end'}>End Text\u003C/text>\n    \u003C/g>\n\u003C/RGLinePath>\n```\n\n下面这段代码展示了连线标签分支如何在计算出的文本后面追加一个独立的详情入口。\n\n```tsx\n\u003Cdiv\n    className={`rg-line-label ${useTextOnPath ? 'rg-line-label-on-path' : ''}`}\n    style={{\n        ...textStyle.cssStyles\n    }}\n    onTouchStart={onLineClick}\n    onClick={onLineClick}\n>\n    {textStyle.text}\n    \u003Ca\n        className=\"text-green-300 cursor-pointer hover:underline\"\n        onClick={() => { onMyLineDetailClick(lineConfig.line); }}\n    >\n        Detail\n    \u003C/a>\n\u003C/div>\n```\n\n下面这段代码展示了共享工具面板如何把准备好的图画布导出为图片。\n\n```tsx\nconst downloadImage = async () => {\n    const canvasDom = await graphInstance.prepareForImageGeneration();\n    let graphBackgroundColor = graphInstance.getOptions().backgroundColor;\n    if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n        graphBackgroundColor = '#ffffff';\n    }\n    const imageBlob = await domToImageByModernScreenshot(canvasDom, {\n        backgroundColor: graphBackgroundColor\n    });\n    if (imageBlob) {\n        downloadBlob(imageBlob, 'my-image-name');\n    }\n    await graphInstance.restoreAfterImageGeneration();\n};\n```\n\n## 这个示例的独特之处\n\n对比数据将这个示例定位为一个聚焦参考，用于在完全替换 relation-graph 连线渲染器的同时，依然保留 relation-graph 的基础能力和事件链路。它最突出的区分点，是在同一个渲染器中同时组合使用 `RGSlotOnLine`、`RGLinePath`、`RGLineText`、`generateLinePath` 和 `generateLineTextStyle`，而不是只停留在描边颜色修改或丝带式重绘上。带动画的圆点，以及明确的起点和终点文字标记，也被视为一种少见的视觉组合。\n\n与 `adv-line-slot-1` 相比，这个示例更强调端点注释和更简单的插槽接线方式，而不是基于路径长度的动画。与 `adv-line-slot-2` 相比，它的范围更收敛：它不会加入路线指标或自定义节点插槽，而是持续把重点放在连线渲染本身。与 `div-on-line` 相比，它通过 `RGSlotOnLine` 重绘每一条边，而不是保留原生渲染器不动，只给某一条选中连线附加单独的覆盖层。与 `adv-line-slot2` 和 `customer-line1` 相比，它更接近 relation-graph 原生的连线基础能力，而不是把连接转换为填充的丝带区域或主题驱动的带状效果。\n\n因此，当需求不只是给连线换一种样式，而是要用一个理解几何路径的渲染器替换整层连线，同时又保持它像普通 relation-graph 连线一样工作时，这个示例就是一个很强的起点。\n\n## 这种模式还适用于哪里\n\n这种模式可以复用在路线监控、基础设施地图、设备布线图、依赖关系可视化、运输交接视图，或任何连线所表达含义不应只是普通描边的有向网络中。尤其是在每条连接都需要动画流向提示、路径绑定的端点标签、独立的连线级操作，以及可导出的查看器工具，但又不希望把整个图做成完整编辑器时，它会特别有用。\n",false,500,1782615378636]