[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:multi-line-gap-control":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 重复连接线的运行时多线间距控制\n\n## 这个示例构建了什么\n这个示例构建了一个用于重复边间距调节的全屏图谱工作台。画布中展示了三个头像节点，它们之间通过多条重复关系相连，因此同一对节点可以同时显示多条看起来彼此平行的连线。一个悬浮面板允许用户在直线和曲线之间切换所有连线，切换标签是否沿路径显示，并放大或缩小重复连接线之间的间距。\n\n图谱内容刻意保持得很小。它的主要价值在于：加载完成后的同一张图会持续留在屏幕上，同时边的几何表现可以实时变化，因此 `multiLineDistance`、连线形状和文本位置的效果都很容易直接比较。\n\n## 数据是如何组织的\n数据来自 `mock-data-api.ts` 中本地的 `RGJsonData` 对象。它定义了三个节点，每个节点都带有颜色以及位于 `node.data.icon` 中的头像 URL，同时还包含十条连线记录。其中有多条连线重复使用相同的 `from` 和 `to` 节点对，这让该示例可以稳定地用于测试并发连线之间的间距。\n\n在布局执行前几乎没有任何预处理。`fetchJsonData()` 只是把静态数据集包装进一个 200 毫秒的 Promise 中，而 `initializeGraph()` 会把结果直接传给 `setJsonData()`。在那之后，图结构保持固定，只有连线的可视化属性会在运行时被修改。在真实应用中，相同的结构可以表示具有多种关系类型的人物、通过多个依赖通道连接的服务，或在同一组端点之间存在多条流向的系统。\n\n## relation-graph 是如何使用的\n`RGProvider` 包裹整个示例，而 `RGHooks.useGraphInstance()` 则是 `MyGraph` 内部的主要控制入口。图配置使用树形布局，并将 `treeNodeGapH` 和 `treeNodeGapV` 都设置为 `200`，这样这个三节点场景就有足够空间清晰展示彼此分离的重复连线。\n\n关键的连线配置是 `defaultJunctionPoint: RGJunctionPoint.border` 和 `multiLineDistance: multiLineGap`。边界连接点设置使得同一对节点之间的重复连线会呈现为彼此分开的平行路径，而不是塌缩为一条视觉路径。该示例还在配置中把 `defaultLineShape` 设为直线，但在图加载后会立刻根据 React state 重写这些连线，因此初始渲染最终会变成带有 `50` 像素间距的曲线。\n\n运行时更新路径是这里的核心技术。第一个 effect 负责加载 JSON，并通过 `loading()`、`setJsonData()`、`clearLoading()`、`moveToCenter()` 和 `zoomToFit()` 来居中图谱。第二个 effect 监听 `lineShape`、`textOnPath` 和 `multiLineGap`，然后通过 `updateOptions({ multiLineDistance })` 以及一次覆盖全图的 `getLines().forEach(updateLine(...))` 遍历将这些变更应用下去。这意味着当用户调整展示设置时，不需要重新构建图数据。\n\n节点外观通过 `RGSlotOnNode` 自定义。示例没有使用默认的节点渲染器，而是把每个节点渲染成圆形头像，并将标签放在下方，文字颜色与节点颜色一致。本地 SCSS 还添加了平铺的画布背景、连线标签样式，以及节点进入 checked 状态时显示的蓝色高亮光晕。\n\n这个示例也复用了共享辅助组件。`DraggableWindow` 提供悬浮面板外壳，它内部的 `CanvasSettingsPanel` 使用 `RGHooks.useGraphStore()` 和 `graphInstance.setOptions()` 在运行时切换滚轮和拖拽行为。同一个面板还可以通过 `prepareForImageGeneration()`、`domToImageByModernScreenshot()` 和 `restoreAfterImageGeneration()` 将当前图谱导出为图片。这些工具支撑了整个工作台，但并不是本示例最核心的教学点。\n\n## 关键交互\n- 连线形状选择器会在 `RGLineShape.StandardStraight` 和 `RGLineShape.StandardCurve` 之间切换所有已加载连线。\n- 连线文本选择器会为当前所有连线切换 `useTextOnPath`，从而让标签在普通标签块和 SVG 路径上显示之间切换。\n- 范围滑块会修改 `multiLineGap`，这会立即更新实时图谱上的 `multiLineDistance`，并改变重复连接线之间的间距。\n- 点击空白画布会调用 `clearChecked()`，移除节点或连线上的任何 checked 高亮状态。\n- 悬浮面板可以被拖动、最小化，并切换到设置覆盖层，用来修改滚轮模式、拖拽模式以及导出图片。\n\n## 关键代码片段\n下面这段配置展示了：重复连线间距是图谱的一级配置项，而边界连接点则是进行这种可视对比的必要条件。\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: false,\n    defaultLineShape: RGLineShape.StandardStraight,\n    defaultNodeShape: RGNodeShape.circle,\n    defaultJunctionPoint: RGJunctionPoint.border,\n    multiLineDistance: multiLineGap,\n    layout: {\n        layoutName: 'tree',\n        treeNodeGapH: 200,\n        treeNodeGapV: 200\n    },\n```\n\n下面这段初始化流程证明：该示例只加载一次固定 JSON，然后整理视口，而不是在每次 UI 变化时重新计算数据。\n\n```tsx\nconst initializeGraph = async () => {\n    const myJsonData: RGJsonData = await fetchJsonData();\n\n    graphInstance.loading();\n    await graphInstance.setJsonData(myJsonData);\n    await updateMyGraphData();\n    graphInstance.clearLoading();\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n};\n```\n\n这个更新函数是最核心的实现细节：它先通过配置更新全图间距，然后重写当前每一条连线的形状和标签模式。\n\n```tsx\nconst updateMyGraphData = async () => {\n    graphInstance.updateOptions({\n        multiLineDistance: multiLineGap\n    });\n    graphInstance.getLines().forEach((line) => {\n        graphInstance.updateLine(line, {\n            lineShape,\n            useTextOnPath: textOnPath\n        });\n    });\n};\n```\n\n这段控制代码说明，连线表现形式的 UI 是由 state 驱动的，而不是在 JSON 数据中写死的。\n\n```tsx\n\u003CSimpleUISelect\n    data={[\n        { value: RGLineShape.StandardStraight, text: 'Straight' },\n        { value: RGLineShape.StandardCurve, text: 'Curve' }\n    ]}\n    onChange={(newValue: number) => setLineShape(newValue)}\n    currentValue={lineShape}\n/>\n```\n\n这个节点插槽证明：示例没有使用默认节点主体，而是改为渲染头像，并把标签放在下方。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }: RGNodeSlotProps) => (\n        \u003Cdiv className=\"w-20 h-20 flex place-items-center justify-center\">\n            \u003Cdiv className=\"my-node-avatar\" style={{ backgroundImage: `url(${node.data?.icon})` }} />\n            \u003Cdiv className=\"absolute transform translate-y-[55px]\" style={{ color: node.color }}>{node.text}\u003C/div>\n        \u003C/div>\n    )}\n\u003C/RGSlotOnNode>\n```\n\n这段共享设置代码展示了：工作台也可以在不触碰图数据的前提下切换画布交互模式。\n\n```tsx\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={wheelMode}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\n## 这个示例的独特之处\n对比记录表明，这个示例与 `line-shape-and-label`、`gap-of-line-and-node` 和 `custom-line-style` 最接近，但它的侧重点明显不同。这里的核心教学点是在一张已经包含同一节点对重复连线的图上，实时控制 `multiLineDistance`。形状切换和路径文本切换之所以重要，是因为它们帮助用户在同一张已加载图谱上，以不同连线表现形式判断间距效果。\n\n与 `line-shape-and-label` 相比，这个示例更关注重复边之间的间距，而不是连接锚点的对比。与 `gap-of-line-and-node` 相比，这里的间距问题发生在同一对节点之间的并发连线，而不是连线端点与其接触的节点边界之间。与 `custom-line-style`、`line-style1` 和 `line-style2` 相比，它的独特价值在于内建的几何调节能力，而不是由 CSS 驱动的皮肤化样式或 checked 连线强调。\n\n对比文件也限制了可宣称的范围。这并不是唯一一个使用 `getLines()` 和 `updateLine(...)` 批量更新已渲染连线的示例，拖拽式设置窗口、画布交互控制以及图片导出流程也都是其他示例中共享的工具。这里真正独特的组合是：重复边树形图、边界连接点、头像节点、平铺展示背景，以及一个在保持同一图状态可见的前提下动态改变连接线几何表现的实时间距滑块。\n\n## 这种模式还能应用在哪里\n- 人物或组织关系图中，两类实体之间可能同时存在多种关系类型，而这些连线必须保持可读性。\n- 服务依赖图中，一个系统可能通过 API 流量、事件、归属关系和告警等多个通道连接到另一个系统。\n- 知识图谱查看器中，在为更大的产品界面确定最终边展示规则之前，需要一个紧凑的样式调节面板。\n- 演示或 QA 工作台中，团队需要在固定图状态下比较连线间距、路径走向和标签位置，而不必重新构建数据集。\n",false,500,1782615374851]