[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:multi-line-gap-control":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Runtime Multi-Line Gap Control for Repeated Connectors\n\n## What This Example Builds\nThis example builds a full-screen graph workbench for repeated-edge spacing. The canvas shows three avatar nodes connected by several duplicate relationships, so the same node pair can display multiple parallel-looking links at once. A floating panel lets the user switch all lines between straight and curved shapes, toggle whether labels follow the path, and widen or tighten the spacing between repeated connectors.\n\nThe graph content is intentionally small. The main value is that one loaded graph stays on screen while edge geometry changes live, which makes the effect of `multiLineDistance`, line shape, and text placement easy to compare.\n\n## How the Data Is Organized\nThe data comes from a local `RGJsonData` object in `mock-data-api.ts`. It defines three nodes, each with a color and an avatar URL in `node.data.icon`, plus ten line records. Several lines repeat the same `from` and `to` pairs, which gives the example a reliable surface for testing spacing between concurrent links.\n\nThere is almost no preprocessing before layout runs. `fetchJsonData()` only wraps the static dataset in a 200 millisecond Promise, and `initializeGraph()` passes the result directly into `setJsonData()`. After that, the structure stays fixed and only visual line properties are changed at runtime. In a real application, the same shape can represent people with several relationship types, services with multiple dependency channels, or systems with several flows between the same endpoints.\n\n## How relation-graph Is Used\n`RGProvider` wraps the example, and `RGHooks.useGraphInstance()` is the main control surface inside `MyGraph`. The graph options use a tree layout with `treeNodeGapH` and `treeNodeGapV` both set to `200`, so the three-node scene has enough room to show separated duplicate links clearly.\n\nThe important line options are `defaultJunctionPoint: RGJunctionPoint.border` and `multiLineDistance: multiLineGap`. The border junction setting is what makes repeated links between the same nodes appear as separated parallel routes instead of collapsing into one visual path. The example also sets `defaultLineShape` to straight in the options, but immediately rewrites loaded lines from React state so the initial render becomes curved with a `50` pixel gap.\n\nThe runtime update path is the core technique. One effect loads the JSON and centers the graph with `loading()`, `setJsonData()`, `clearLoading()`, `moveToCenter()`, and `zoomToFit()`. A second effect watches `lineShape`, `textOnPath`, and `multiLineGap`, then applies them through `updateOptions({ multiLineDistance })` and a graph-wide `getLines().forEach(updateLine(...))` pass. That means the graph data does not have to be rebuilt when users change presentation settings.\n\nThe node appearance is customized with `RGSlotOnNode`. Instead of the default node renderer, each node becomes a circular avatar with the label placed below it in the node color. Local SCSS adds the tiled canvas background, styles the line labels, and shows a blue halo when a node enters the checked state.\n\nThe example also reuses shared helper components. `DraggableWindow` provides the floating panel shell, and its `CanvasSettingsPanel` uses `RGHooks.useGraphStore()` plus `graphInstance.setOptions()` to switch wheel and drag behavior at runtime. The same panel can export the current graph as an image through `prepareForImageGeneration()`, `domToImageByModernScreenshot()`, and `restoreAfterImageGeneration()`. These utilities support the workbench, but they are not the main lesson of this example.\n\n## Key Interactions\n- The line shape selector switches every loaded line between `RGLineShape.StandardStraight` and `RGLineShape.StandardCurve`.\n- The line text selector toggles `useTextOnPath` for all current lines, so labels can move from normal label blocks onto the SVG path.\n- The range slider changes `multiLineGap`, which immediately updates `multiLineDistance` on the live graph and changes the spacing between repeated connectors.\n- Clicking empty canvas space calls `clearChecked()`, which removes any checked highlight from nodes or lines.\n- The floating panel can be dragged, minimized, and switched into a settings overlay that changes wheel mode, drag mode, and supports image export.\n\n## Key Code Fragments\nThis options block shows that repeated-line spacing is a first-class graph option and that border junction points are required for the visual comparison.\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\nThis initialization flow proves that the example loads fixed JSON once and then prepares the viewport instead of recalculating data for each UI change.\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\nThis update function is the central implementation detail: it changes graph-wide spacing through options and then rewrites every current line's shape and label mode.\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\nThis control fragment shows that the line presentation UI is state-driven rather than hard-coded in the JSON data.\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\nThis node slot proves that the example is not using the default node body and instead renders avatar portraits with labels below.\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\nThis shared settings fragment shows how the workbench can also change canvas interaction mode without touching the graph data.\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## What Makes This Example Distinct\nThe comparison records place this example closest to `line-shape-and-label`, `gap-of-line-and-node`, and `custom-line-style`, but they also show a clear difference in emphasis. Here the main lesson is live `multiLineDistance` control on a graph that already contains repeated links between the same node pairs. The shape switch and text-on-path switch matter because they help users judge the spacing under different line presentations on the same loaded graph.\n\nCompared with `line-shape-and-label`, this example focuses more on spacing among repeated edges than on junction-anchor comparison. Compared with `gap-of-line-and-node`, the spacing lesson is between concurrent links on one node pair, not between a line endpoint and the node border it touches. Compared with `custom-line-style`, `line-style1`, and `line-style2`, the distinctive value is built-in geometric tuning rather than CSS-driven skinning or checked-line emphasis.\n\nThe comparison file also limits what should be claimed. This is not the only example that batch-updates rendered lines with `getLines()` and `updateLine(...)`, and the draggable settings window, canvas interaction controls, and image export flow are shared utilities used elsewhere. The distinctive combination here is a repeated-edge tree, border junction points, avatar nodes, a tiled showcase background, and a live gap slider that keeps the same graph state visible while connector geometry changes.\n\n## Where Else This Pattern Applies\n- People or organization graphs where two entities can have several simultaneous relationship types and those lines must remain readable.\n- Service dependency maps where one system can connect to another through multiple channels such as API traffic, events, ownership, and alerts.\n- Knowledge graph viewers that need a compact style-tuning board before a final edge presentation rule is chosen for a larger product surface.\n- Demo or QA workbenches where teams need to compare line spacing, routing, and label placement on a fixed graph state without rebuilding the dataset.\n",false,500,1782615374849]