[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:animated-shortest-path-highlighting":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 带可选线条效果的动画最短路径高亮\n\n## 这个示例构建了什么\n\n这个示例构建了一个只读的关系查看器，用于在一张紧凑图中持续强调两个节点之间的最短路径。画布使用基于图标的圆形节点、带自定义箭头标记的曲线连线，以及暗色霓虹视觉主题。用户可以点击两个节点手动计算一条路径，让示例自动回放随机路径，在 `tree` 和 `center` 布局之间切换，并修改当前活动路径使用的视觉皮肤。\n\n这里的重点不是通用的线条主题化。选中的效果类专门保留给当前最短路径，而无关的节点和连线都会被调暗，以便将注意力引导到正在检查的路径上。\n\n## 数据是如何组织的\n\n图数据以内联方式声明为一个 `RGJsonData` 对象，其中包含 `rootId`、扁平的 `nodes` 数组和扁平的 `lines` 数组。该示例在同一个枢纽节点周围混合了出向和入向连线，因此高亮路径可以跨越拓扑中的不同区域。\n\n在调用 `setJsonData` 之前，代码会重写每一条连线，使其使用自定义 SVG marker id，启用起始箭头，并关闭默认的结束箭头。路径查找数据并没有作为单独的静态结构保存。相反，示例会基于 `graphInstance.getNodes()` 和 `graphInstance.getLinks()` 重建一个辅助图，然后沿这些连接的任意方向还原出一条最短路径。在生产系统中，这种结构同样可以表示服务依赖、流程交接、仓储路径、基础设施追踪或升级路径。\n\n## relation-graph 是如何使用的\n\n`RGProvider` 提供图实例，而 `RGHooks.useGraphInstance()` 驱动了几乎所有运行时行为。图的初始配置为 `layout.layoutName = 'tree'`、`from = 'left'`、`treeNodeGapH = 100`、`treeNodeGapV = 20`、`defaultJunctionPoint = RGJunctionPoint.ltrb`、`defaultNodeShape = RGNodeShape.circle`，以及 `defaultLineShape = RGLineShape.StandardCurve`。\n\n该示例保留 relation-graph 的内置渲染器，并通过 slots、defs 和 CSS 对其进行定制。`RGSlotOnNode` 用图标加文本的节点替换默认节点主体。`MySvgDefs` 注入自定义箭头标记，以及供电流路线样式使用的 glitch 过滤器。SCSS 文件会针对 `.rg-node-peel`、`.rg-line-peel`、`.rg-line-bg` 和 `.rg-line` 编写样式，因此示例无需提供自定义线条 slot，也能重设内置节点和连线的样式。\n\n在运行时，`setJsonData` 负责加载准备好的图，`moveToCenter()` 和 `zoomToFit()` 负责归一化视口，`updateOptions()` 会在另一种布局下重新启动同一份数据集，而 `getNodes()`、`getLinks()`、`getLines()`、`updateNode()` 和 `updateLine()` 则用于计算并渲染当前活动路径。共享浮动窗口还会使用 `prepareForImageGeneration()`、`getOptions()`、`setOptions()` 和 `restoreAfterImageGeneration()`，围绕图形暴露画布设置和图片导出能力。\n\n## 关键交互\n\n- 点击一个节点会记录一个起始端点。随后点击另一个不同的节点时，会计算最短路径并高亮匹配的节点与连线。\n- 连续点击同一个节点两次会被忽略。点击一条连线会清除任何待定的首个节点选择。\n- 一个 `800ms` 定时器会把随机节点送入同一套节点点击流程，因此自动播放和手动选路共用同一套路径高亮机制。\n- 浮动面板可以在 `tree` 和 `center` 布局之间切换图形、重新加载数据集，并让用户在 `Electric Current` 与 `Data Flow` 两种路径效果之间切换。\n- 共享窗口还可以被拖动、最小化、作为画布设置覆盖层打开，并用于将当前图导出为图片。\n\n## 关键代码片段\n\n这段初始化过程说明，线条标记是在数据准备阶段分配的，也就是图被加载之前。\n\n```tsx\nmyJsonData.lines.forEach(line => {\n    line.endMarkerId = 'my-arrow-001';\n    line.startMarkerId = 'my-arrow-001-start';\n    line.showStartArrow = true;\n});\nmyJsonData.lines.forEach((line) => {\n    line.showEndArrow = false;\n});\nawait graphInstance.setJsonData(myJsonData);\n```\n\n这个定时器会持续回放同一套路径选择流程，而不是另外创建一套自动播放渲染器。\n\n```tsx\nconst restartRandomPathTask = () => {\n    clearInterval(playTimerRef.current);\n    playTimerRef.current = setInterval(() => {\n        const nodes = graphInstance.getNodes();\n        const randomNode = nodes[Math.floor(Math.random() * nodes.length)];\n        onNodeClick(randomNode);\n    }, 800);\n};\n```\n\n这个点击处理器会把两个端点选择转成一次最短路径查询，并在之后清除待定的起始节点。\n\n```tsx\nconst onNodeClick = (node: RGNode, $event?: RGUserEvent) => {\n    if (checkedNodeIdRef.current) {\n        if (checkedNodeIdRef.current === node.id) {\n            return;\n        }\n        calcShortestPath(checkedNodeIdRef.current, node.id, graphInstance, itemsOnPathClassName);\n        checkedNodeIdRef.current = '';\n    } else {\n        checkedNodeIdRef.current = node.id;\n    }\n};\n```\n\n这个辅助方法表明，路径查找层是基于运行中的 relation-graph 实例重建的，而不是来自第二份硬编码邻接表。\n\n```ts\nloadDataFromRelationGraph(graphInstance: RelationGraphInstance) {\n    this.nodes = graphInstance.getNodes().map(n => {\n        return { id: n.id, childs: [], indexed: false, parentNode: null };\n    });\n    this.edges = graphInstance.getLinks().map(link => {\n        return { from: link.fromNode.id, to: link.toNode.id };\n    });\n}\n```\n\n这段变更过程只会把所选效果类应用到路径连线上，同时淡化图中的其余上下文。\n\n```ts\ngraphInstance.getLines().forEach((line: RGLine) => {\n    if (lineIdsOnPath.includes(line.id)) {\n        graphInstance.updateLine(line, { className: flagClassName });\n    } else {\n        graphInstance.updateLine(line, { opacity: 0.2 });\n    }\n});\n```\n\n这个 SCSS 预设表明，动画路径皮肤只是 relation-graph 内置线条层上的普通类名。\n\n```scss\n.rg-line-peel.my-line-class-12 {\n    .rg-line-bg {\n        stroke: #334455;\n        stroke-width: 6px;\n        stroke-dasharray: 9, 9, 9;\n        animation: traffic-slow 4s linear infinite;\n    }\n\n    .rg-line {\n        stroke: #00d2ff;\n        stroke-dasharray: 20, 80;\n        animation: traffic-fast 1.5s linear infinite;\n    }\n}\n```\n\n## 这个示例的独特之处\n\n对比数据将这个示例放在了线条样式示例与路径分析示例之间。与 `custom-line-animation` 和 `custom-line-style` 相比，它不会重新主题化每一条已渲染的连线。相反，样式选择器只会改变计算后路径的呈现方式，而非路径元素会被调暗。与 `find-min-path` 相比，它采用了更轻量的交互模型：两次节点点击加上一个定时器驱动的回放循环，会复用同一套最短路径例程，而不是依赖表单式查询流程。\n\n正是这种组合让该示例脱颖而出。稀缺之处不在于某一个单独成分本身，而在于把实时最短路径计算、仅作用于路径的动画皮肤、自定义 SVG 箭头标记、运行时布局切换以及自动播放驱动的路径回放整合进同一个查看器。因此，与通用的全图线条样式展示相比，它更适合作为路径检查类界面的起点。\n\n## 这种模式还适用于哪里\n\n这种模式非常适合基础设施与运维图示场景，因为团队需要一次只检查一条路径，而不必编辑图形。例如服务依赖追踪、物流交接地图、告警升级链路、数据血缘查看器以及网络路径演示。\n\n它也适合那些既需要分析能力又需要展示氛围的演示型工具。团队可以保留同一套底层拓扑，为了可读性切换布局，并为演示、调查或导出快照应用不同的路径皮肤，而无需替换 relation-graph 的内置线条渲染器。\n",false,500,1782615377215]