JavaScript is required

整图展开/折叠回放

这是一个 relation-graph 示例,用于在固定居中层级图上回放整图展开/收缩序列。它加载一份内联 39 节点数据,自动适配视图并在挂载后自动播放展开过程,在可拖拽辅助窗中提供全局 `Expand All` 与 `Collapse All`,并继承共享画布设置与图片导出能力。

居中层级中的整图展开/折叠回放

这个示例构建了什么

这个示例构建了一个全高度的居中层级查看器,它会对整张图的分支可见性进行回放,而不是把展开操作留给单个节点点击。画布展示的是一个以根节点为中心、类似子系统结构图的关系图,包含橙色圆形节点、灰色直线标签连线,以及悬浮在图上方的白色辅助窗口。

用户可以回放 Expand AllCollapse All,拖动辅助窗口、将其最小化,并打开一个次级设置浮层。该浮层来自共享的本地辅助组件,提供滚轮模式、拖动画布模式和图片下载控制,但这个示例的重点是脚本化的整图回放:它会重置层级结构、按自上而下的顺序执行展开动画,并且还能基于同一份数据执行从叶子节点开始的折叠。

数据是如何组织的

数据在 initializeGraph() 内以内联方式声明为一个 RGJsonData 对象。它使用 rootId: '2',定义了 39 个硬编码节点,并通过 38 条显式连线将它们连接起来。节点记录只需要 idtext,而连线记录只需要 idfromto 以及重复出现的标签 Subsystem

在调用 setJsonData(...) 之前没有任何预处理步骤。组件不会拉取远程数据、不会对源记录做标准化,也不会为了回放再派生出第二套结构。在真实应用里,这种同样的数据形状可以表示子系统树、产品架构图、设备分解结构、制造能力层级,或任何其他固定根节点居中的拆解结构,只要团队希望通过一个控制项按顺序展示或收起整张图。

relation-graph 是如何使用的

index.tsxRGProvider 包裹整个示例,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 使用实例 API 控制运行中的图。图配置中设置了 layoutName: 'center',将展开控制点放在右侧,在分支变化时保持重新布局启用,使用紧凑的 50x50 圆形节点,并通过 RGLineShape.StandardStraight 渲染边。随后,SCSS 文件覆盖了节点和连线样式,以生成白色画布、橙色节点、选中态发光效果、灰色描边,以及小型灰色连线标签。

组件加载内联层级数据时会调用 setJsonData(...),随后立即用 moveToCenter()zoomToFit() 将图重新居中并适配显示,然后在挂载时调用 openAll()。这条启动路径很关键,因为这个示例并不只是一个手动演示。它会在数据准备好后主动把图重置到折叠状态,并立即回放整套展开序列。

这个示例特有的逻辑是围绕 relation-graph 的实例 API 构建的,而不是依赖自定义插槽或事件处理器。openAll() 会启用画布动画、折叠所有分支节点、获取根节点,并执行一次深度优先的 deepExpandNode(...) 遍历。在这个遍历过程中,每个子节点会先通过 updateNode(...) 被移动到父节点坐标位置,然后通过 expandNode(...) 展开父分支,接着固定暂停 sleep(400),再递归进入布局计算出来的子节点。closeAll() 则执行相反的流程:它先将分支节点标记为展开状态,获取根节点,然后运行 deepCloseNode(...),该函数会先向下递归,再以相同的节奏暂停并自底向上调用 collapseNode(...),同时反复执行 zoomToFit()

这个示例没有自定义节点、连线、画布或视口插槽,也没有编辑工作流。悬浮控制界面来自共享的 DraggableWindow 辅助组件。这个辅助组件并不是该示例独有的,但它很重要,因为它提供了可拖动、可最小化的外壳以及设置浮层。在这个浮层内部,CanvasSettingsPanel 使用 RGHooks.useGraphStore() 读取当前的 wheelEventActiondragEventAction,通过 graphInstance.setOptions(...) 更新它们,并借助 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 支持图片导出。

关键交互

  • 点击 Expand All 会从根节点开始,以整图自上而下的方式回放显示过程,而不是只展开单个节点。
  • 点击 Collapse All 会对整个层级结构回放一套从叶子节点开始的折叠序列。
  • playing 状态会在序列运行期间禁用两个回放按钮,从而防止展开和折叠命令相互重叠。
  • 辅助窗口可以通过标题栏拖动,因此操作面板不会占用固定的页面区域。
  • 辅助窗口可以最小化,这让用户能够在更少界面装饰的情况下查看图。
  • 设置按钮会打开一个共享浮层,可切换滚轮行为、切换画布拖动行为,并将当前图下载为图片。这些控制项是继承来的辅助能力,而不是该示例的核心要点。

关键代码片段

这段代码说明,该图被有意配置为一个居中层级结构,使用紧凑的圆形节点、右侧展开控制点,并在可见性变化期间启用重新布局:

const graphOptions: RGOptions = {
    defaultExpandHolderPosition: "right",
    reLayoutWhenExpandedOrCollapsed: true,
    defaultNodeWidth: 50,
    defaultNodeHeight: 50,
    defaultNodeShape: RGNodeShape.circle,
    debug: false,
    layout: {
        layoutName: 'center',
    },
    defaultLineShape: RGLineShape.StandardStraight
};

这段代码说明,层级数据以内联方式组装,并在没有任何预处理步骤的情况下直接传给 relation-graph:

const myJsonData: RGJsonData = {
    "rootId": "2",
    "nodes": [
        { "id": "2", "text": "ALTXX" }, { "id": "3", "text": "CH2 TTN" },
        { "id": "4", "text": "CH1 AlCu" }, { "id": "5", "text": "MainFrame" },
        // ... more nodes omitted
    ],
    "lines": [
        { "id": "l1", "from": "2", "to": "5", "text": "Subsystem" },
        { "id": "l2", "from": "2", "to": "6", "text": "Subsystem" }
        // ... more lines omitted
    ]
};

这段代码说明了挂载时的数据加载顺序,包括图完成居中和适配显示后自动触发回放:

await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
await openAll();

这段代码展示了展开侧的回放技巧:先把子节点预放置到父节点位置,展开分支,等待,然后再递归处理计算出的子节点:

const deepExpandNode = async (node: RGNode) => {
    if (node.rgChildrenSize === 0) return;
    for (const cnode of node.lot.childs) {
        graphInstance.updateNode(cnode, { x: node.x, y: node.y});
    }
    graphInstance.expandNode(node);
    await graphInstance.sleep(400);
    for (const cnode of node.lot.childs) {
        await deepExpandNode(cnode);
    }
    graphInstance.zoomToFit();
};

这段代码说明,openAll() 会在回放前先重置分支可见性,然后在启用画布动画的情况下,从根节点驱动整套展开序列:

const openAll = async () => {
    setPlaying(true);
    graphInstance.enableCanvasAnimation();
    graphInstance.getNodes().forEach(n => {
        if (n.rgChildrenSize > 0) {
            graphInstance.collapseNode(n);
        }
    });
    const rootNode = graphInstance.getRootNode();
    if (rootNode) await deepExpandNode(rootNode);
    graphInstance.disableCanvasAnimation();
    setPlaying(false);
};

这段代码展示了折叠回放时相反的遍历顺序:先向下递归,再在回溯过程中执行折叠:

const deepCloseNode = async (node: RGNode) => {
    if (node.rgChildrenSize === 0) return;
    for (const cnode of node.lot.childs) {
        await deepCloseNode(cnode);
    }
    await graphInstance.sleep(400);
    graphInstance.collapseNode(node);
    graphInstance.zoomToFit();
};

这段代码说明,面向用户的控制项是全局回放按钮,并且在序列运行期间会被锁定:

<DraggableWindow>
    <div className="py-2 text-sm">Actions:</div>
    <div className="flex gap-3">
        <SimpleUIButton disabled={playing} onClick={openAll}>Expand All</SimpleUIButton>
        <SimpleUIButton disabled={playing} onClick={closeAll}>Collapse All</SimpleUIButton>
    </div>
</DraggableWindow>

这段代码说明,共享浮层仍然可以通过当前的图实例修改画布交互模式,并导出当前图像:

const { options } = RGHooks.useGraphStore();
const dragMode = options.dragEventAction;
const wheelMode = options.wheelEventAction;

<SettingRow
    label="Wheel Event:"
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

这个示例的独特之处

对比数据清楚地表明了它最主要的区别:这并不主要是一个像 expand-animation2expand-animation 那样的重新布局切换示例。它更适合作为按时间编排整图回放的参考。它不寻常的地方不只是图可以展开或折叠,而是这两个方向都被设计成可以从同一个控制界面反复回放的动作。

与附近的其他示例相比,这个示例在同一个居中层级中组合了多项少见特性:启动时自动重置为折叠状态、挂载后立即自动播放、专门的 Expand AllCollapse All 按钮、自顶向下的递归展开、从叶子节点开始的递归折叠、通过 updateNode(...) 预放置子节点位置、固定的 sleep(400) 节奏,以及包裹整套流程的画布动画。对比产物也明确划出了一条重要边界:悬浮辅助窗口、滚轮模式切换、拖动模式切换以及图片导出都只是共享脚手架,而不是这个示例真正独特的点。

正因为有这种组合,当需求是重置并回放整棵层级的可见性状态,而不是只控制根节点某一侧时,这个示例比 multiple-expand-buttons 更适合作为起点。当目标是异步分支编排,而不是画布交互设置时,它也比 drag-and-wheel-event 更适合作为起点。

这种模式还适用于哪些场景

这种模式很适合迁移到引导式产品演示、架构讲解、设备分解查看器、子系统图、依赖关系浏览器,以及面向展示的知识图谱场景。在这些场景中,用户需要通过一个命令按可控顺序展开或收起整棵层级结构。尤其是在瞬间切换状态显得过于突兀,而产品需要一个更易读的展示过程时,这种模式会很有用。

同样的方法也可以扩展到重置流程、引导序列、演示 kiosks、QA 回放工具,或在“完全展开”和“完全折叠基线”之间切换的分析工作台中。需要强调的是,这些只是可迁移的应用场景,并不是该示例中已经实现的功能。