JavaScript is required

展开/收缩后是否重新布局(纵向树)

这是一个 relation-graph 示例,聚焦演示自上而下树图中的 `reLayoutWhenExpandedOrCollapsed`。它加载固定内联层级数据,允许用户在可拖拽悬浮面板中切换“展开/收缩后是否重布局”,并继承共享辅助窗中的画布设置和图片导出控制。

自上而下树形图中的实时展开/收起重布局

这个示例构建了什么

这个示例构建了一个全高的自上而下树形图,用于对比用户点击 relation-graph 内置的展开或收起控件后会发生什么。画布中展示的是一个狭长的青色层级结构,带有弯曲的青绿色连线、位于底部的展开控制器,以及一个悬浮在图谱上方的白色辅助窗口。

用户可以直接在图上展开或收起分支,在 Re-layoutDo not re-layout 之间切换展开后的行为,把辅助窗口拖到屏幕上的其他位置,将其最小化,并打开一个二级设置浮层。该浮层继承自本地共享辅助组件,增加了画布滚轮模式、画布拖拽模式和图片下载控制,但这个示例的核心教学点是运行时切换 reLayoutWhenExpandedOrCollapsed,而不是自定义过渡时序。

数据是如何组织的

数据直接在 initializeGraph() 中以内联方式声明为一个 RGJsonData 对象。它使用 rootId: 'a',定义了 16 个硬编码节点,并通过 15 条显式连线把它们连接起来。这个结构是一个简单的固定层级,而不是带有额外元数据的领域模型,因此注意力会集中在展开或收起行为上,而不是内容语义上。

在调用 setJsonData(...) 之前没有预处理步骤。组件不会拉取远程数据、规范化记录,也不会派生出第二套用于布局的结构。在真实应用中,同样的数据形态可以表示组织子树、产品分类、文件夹树、依赖拆解,或任何其他层级结构,而产品团队需要决定展开某个分支时,是否应该触发整棵可见树重新布局。

如何使用 relation-graph

index.tsx 使用 RGProvider 包裹该示例,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 使用实例 API 驱动图谱。图谱选项配置了一个从顶部向下生长的 tree 布局,使用 treeNodeGapH: 40treeNodeGapV: 200,渲染狭窄的矩形节点,并通过 RGLineShape.StandardCurveRGJunctionPoint.tb 连接。最终的视觉效果是一个高耸的单向层级结构,因此在节点展开或收起时,分支移动会非常明显。

组件通过 setJsonData(...) 加载数据,然后立即调用 moveToCenter()zoomToFit(),使整棵树在初始状态下居中显示。第二个 effect 监听 React 的 relayout 状态,并通过 updateOptions({ reLayoutWhenExpandedOrCollapsed }) 把它同步到已挂载的图谱实例中。这就是本示例中的核心 relation-graph 技术点:布局规则可以在运行时改变,而无需重建数据集,也无需重新挂载图谱组件。

这个示例中没有自定义节点、连线、画布或视口插槽,示例专属代码里也没有显式的图谱事件处理器。展开或收起行为来自 relation-graph 内置的展开控制器,该控件通过 defaultExpandHolderPosition: 'bottom' 被放置在每个节点底部。该示例同样没有实现编辑、创作或手动布局逻辑。

悬浮控制界面来自本地的 DraggableWindow 辅助组件。这个辅助组件并不是此示例独有的,但在这里很重要,因为它让重布局选择器可以被拖动,同时还提供了一个共享的设置浮层。在该浮层内部,CanvasSettingsPanel 使用 RGHooks.useGraphStore() 读取当前的 wheelEventActiondragEventAction,然后调用 graphInstance.setOptions(...) 在实时图谱上更新这些值。它还使用 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 来支持下载操作。本地 SCSS 文件只定义了空的包装选择器,因此几乎所有可见样式都来自图谱选项和共享悬浮窗口,而不是来自自定义的 relation-graph CSS 覆盖。

关键交互

  • 点击内置的展开控制器可以展开或收起树中的某个分支。
  • 点击 Re-layoutDo not re-layout 会更新已挂载图谱上的 reLayoutWhenExpandedOrCollapsed,因此下一次展开或收起操作会重新打包整棵树,或保留当前分支位置。
  • 辅助窗口可以通过其标题栏拖动,因此控制界面可以移动,而不是固定占据画布的一部分。
  • 辅助窗口可以最小化,因此该示例可以在显示控件的教学模式和更简洁的查看模式之间切换。
  • 设置按钮会打开一个二级浮层,可用于切换滚轮行为、切换画布拖拽行为,并将当前图谱下载为图片。这些控件来自共享辅助组件,而不是来自该示例自身的树逻辑。

关键代码片段

这段代码展示了该示例被有意配置为一个自上而下的树形布局,具有较大的垂直间距和底部展开控件,因此分支重排更容易观察:

const graphOptions: RGOptions = {
    layout: {
        layoutName: 'tree',
        from: 'top',
        treeNodeGapH: 40,
        treeNodeGapV: 200
    },
    // ... visual defaults omitted
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.tb,
    defaultExpandHolderPosition: 'bottom'
};

这段代码展示了该层级结构是以内联方式组装的,而不是从其他来源拉取或转换而来:

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a' },
        { id: 'b', text: 'b' },
        { id: 'b1', text: 'b1' },
        // ... more nodes
    ],
    lines: [
        { id: 'l1', from: 'a', to: 'b' },
        // ... more lines
    ]
};

这段代码展示了挂载时的数据加载模式:先加载一次数据,再让结果居中并适配视图:

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

这段代码展示了使该示例具备区分度的运行时选项同步模式:

const syncOptionsToGraph = () => {
    graphInstance.updateOptions({
        reLayoutWhenExpandedOrCollapsed: relayout
    });
};

useEffect(() => {
    syncOptionsToGraph();
}, [relayout]);

这段代码展示了 UI 将重布局选择直接暴露为布尔开关,而不是作为重建或重置操作:

<SimpleUISelect
    data={[
        { value: true, text: 'Re-layout' },
        { value: false, text: 'Do not re-layout' }
    ]}
    currentValue={relayout}
    onChange={(newValue: boolean) => {
        setRelayout(newValue);
    }}
/>

这段代码展示了共享悬浮窗口如何在主选择器之上显示一个二级设置界面:

{
    showSettingPanel &&
    <div className="absolute w-full h-full left-0 top-[35px]">
        <div className="absolute z-10 w-full h-full left-0 bg-white/90" onClick={() => { setShowSettingPanel(false); }}></div>
        <div className="absolute z-20 w-full bg-gray-50 border-b border-gray-200">
            <CanvasSettingsPanel />
        </div>
    </div>
}

这段代码展示了共享浮层如何通过实时实例更新图谱交互模式,而不是通过页面重载完成:

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-animation2open-all-close-allexpand-graduallylayout-tree 最接近,但它的范围比它们都更收敛。相比 expand-animation2,它把相同的重布局决策应用在单向的自上而下树形布局中,而不是根节点居中的布局。相比 open-all-close-all,它不会编排定时的整图播放,也不讲解动画编排。相比 expand-gradually,它不关注初始收起规则。相比 layout-tree,它不会比较多个布局预设,也不会在布局前重写节点属性。

它的突出之处在于把运行时 reLayoutWhenExpandedOrCollapsed 开关、自上而下树形结构、底部展开控件、上下方向连接点、较大的层级间距,以及一个极简的二选一控制面板结合在一起。这样的组合使它成为一个很强的起点,适用于产品团队已经确定要使用纵向层级结构,只需决定展开某个分支时应该保持当前位置,还是触发一次新的重排。

对比产物还清楚划定了一个边界:这个示例并不能说明自定义动画时序、缓动或过渡代码。该示例关注的是 relation-graph 中展开后的重布局行为,而不是编写定制动画。

这种模式还适用于哪里

这种模式很适合迁移到任何需要有意识地选择披露行为,而不是直接接受默认行为的层级式 UI 中。典型场景包括组织架构图、分类树、工作流拆解、策略树、故障排查树,以及依赖视图,在这些场景里,用户会反复展开和收起分支,同时保持空间方位感。

它同样是面向配置型图谱产品的实用参考。这种做法可以扩展为面向用户的行为开关、层级视图的管理端设置、披露规则的 A/B 对比,或用于验证启用与禁用重布局时树形体验差异的 QA 工具,而无需改变底层数据集。