依次展开图谱
这是一个 relation-graph 示例:加载内联系统树后先折叠所有可展开分支,再从根节点自动回放展开。它结合递归 `expandNode(...)`、定时停顿、画布动画和重复视口适配,使呈现更像引导式播放而非手动披露。
深度优先树展开回放
这个示例构建了什么
这个示例构建了一个全高树形查看器,它会从根节点向外回放整个层级结构,而不是等待用户点击展开控件。屏幕上展示的是一个带有矩形节点和曲线标签连线的树图,并且图会在组件挂载后立即自动展开。
这个示例的核心在于回放行为。代码会先加载完整的层级结构,折叠所有仍然有子节点的分支,然后通过短暂停顿让树一步步展开。在这个过程中,视口会持续重新居中并适配当前可见图形,以保证展开过程始终清晰可读。
数据是如何组织的
数据直接在 initializeGraph() 内以内联方式声明,采用标准的 RGJsonData 风格树形载荷结构,包含 rootId、nodes 和 lines。每个节点都是简单的 { id, text } 对象,每条边都是 { from, to, text } 关系,并用 Subsystem 作为连接标签。
在把数据传给 setJsonData() 之前,示例会先遍历 lines 数组,并为每条连线补充一个合成 id,例如 line_0 或 line_1。这里没有远程获取、懒加载或服务端转换。在生产系统中,同样的数据结构可以表示设备分解树、子系统地图、组织架构图,或者从 API 导入的产品结构。
relation-graph 是如何使用的
这个演示包裹在 RGProvider 中,MyGraph 通过 RGHooks.useGraphInstance() 获取当前激活的图实例。这个 hook 是整个示例的中心:它负责加载数据、重新居中视口、切换画布动画、在重置阶段遍历节点、查找根节点,并通过命令式 API 调用驱动展开过程。
图配置使用树布局,并设置了 treeNodeGapV: 20 和 treeNodeGapH: 120。它还开启了 reLayoutWhenExpandedOrCollapsed,把展开占位器放在右侧,使用矩形节点,绘制曲线连接线,通过 RGJunctionPoint.lr 让连线从左到右锚定,并把线文字直接放在线路径上。
这个示例中没有自定义插槽、没有事件处理器,也没有编辑器功能。样式刻意保持极简:导入的 SCSS 文件虽然包含了节点、连线和选中状态的选择器骨架,但没有加入具体声明,因此大多数视觉效果都来自 relation-graph 默认渲染以及 100vh 的包裹层。
关键交互
主要交互是脚本驱动的,而不是点击驱动的。useEffect() 只运行一次,会调用 initializeGraph(),并在图数据加载完成后立即开始回放。
整个序列有三个关键步骤。首先,openAll() 会折叠所有 rgChildrenSize 大于零的节点,这样即使完整层级已经被加载,也能保证重置后的初始状态一致。其次,deepExpandNode() 会按照深度优先顺序逐个展开分支,并在每次视口更新前后分别插入两次 sleep(300) 暂停。最后,每一步都会调用 moveToCenter() 和 zoomToFit(),因此镜头会跟随当前的展开进度,而不是停留在初始边界上不动。
关键代码片段
下面这个选项片段展示了,该示例是一个具备重新布局能力的树图,使用矩形节点和带标签的曲线连线。
defaultExpandHolderPosition: 'right',
reLayoutWhenExpandedOrCollapsed: true,
defaultNodeShape: RGNodeShape.rect,
defaultNodeBorderWidth: 1,
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.lr,
defaultLineTextOnPath: true,
layout:
{
layoutName: 'tree',
treeNodeGapV: 20,
treeNodeGapH: 120
}
下面这个预处理步骤说明,该演示会把内联原始层级转换为 RGJsonData,并在加载前为每条连线分配唯一 id。
const myJsonData: RGJsonData = {
...rawData,
lines: rawData.lines.map((line, index) => ({
...line,
id: `line_${index}`
}))
};
await graphInstance.setJsonData(myJsonData);
下面这个递归片段是回放算法的核心:跳过叶子节点,展开当前节点,暂停,重新适配视口,再暂停一次,然后递归处理子节点。
if (node.rgChildrenSize === 0) {
return;
}
graphInstance.expandNode(node);
await graphInstance.sleep(300);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
await graphInstance.sleep(300);
for (const cnode of node.lot.childs) {
await deepExpandNode(cnode);
}
下面这个重置步骤表明,回放总是从一棵已折叠的树重新开始,然后再从根节点启动展开。
graphInstance.getNodes().forEach(n => {
if (n.rgChildrenSize > 0) {
graphInstance.collapseNode(n);
}
});
await deepExpandNode(graphInstance.getRootNode());
这个示例的独特之处
根据对比数据,这个示例的特别之处在于,它会在组件挂载时自动回放展开过程,而不是额外提供回放按钮,也不是等待用户点击某个已折叠分支。因此,与那些聚焦手动展开的示例相比,它更适合作为引导式展示行为的参考。
它也比一般的图实例 API 示例更具体。与 graph-instance-api 相比,这里使用了相同的 provider hook 和命令式 API 风格,但目的是编排整个层级结构,而不是从外部控件切换某一个已知节点。与 expand-gradually 相比,这里的重点从用户触发的分支打开,转向按时间推进的深度优先展示。与 open-by-level 相比,这里的展示顺序来自递归遍历,而不是某个指定的层级阈值。
对比文件还强调了镜头跟随模式也是这个示例身份的一部分。许多示例会在初始加载后对图进行居中或自适应;而这个示例会在每一次展开步骤中重复调用 moveToCenter() 和 zoomToFit(),因此视口管理本身也成为了回放算法的一部分。
这种模式还能应用到哪里
这种模式非常适合用于新手引导演示、架构讲解、子系统拆解演示,以及需要让层级按受控顺序逐步展开的培训材料。它同样适用于需要从已知根状态进行确定性回放,而不是开放式浏览的展示模式。
同样的方法还可以适配依赖树、物料清单结构、基础设施归属图或课程地图。真正可复用的关键并不是示例数据本身,而是“启动前先折叠”的初始化方式、递归 expandNode() 回放,以及每一步之后都进行的视口同步这三者的组合。