JavaScript is required

根节点左右分支显隐切换

这个示例渲染以根为中心的双向树,根节点提供左右两侧独立切换按钮。每个按钮通过筛选 `lot.level` 相关节点来隐藏或显示一侧层级,并复用共享示例工具提供画布设置和图片导出。

以根节点为中心并可分别切换左右分支的树形图

这个示例构建了什么

这个示例构建了一个以单个根节点为中心的双向树。用户可以在左侧看到传入分支,在右侧看到传出分支,并且在根节点外侧放置了两个粉色操作按钮,因此每一侧都可以独立显示或隐藏。

这个示例的重点并不是通用的展开或折叠行为,而是一个使用 RGSlotOnNode 构建的自定义根节点控制界面,其中一个按钮作用于层级结构的左侧,另一个按钮作用于右侧。

数据是如何组织的

图数据以内联方式声明为一个 RGJsonData 对象,其中包含 rootId: 'a'、13 个节点和 12 条连线。组件在布局之前不会对数据集做预处理。相反,它会把 nodeslines 数组添加到图实例中,执行 doLayout(),然后再把两个运行时标记 leftExpandedrightExpanded 写到根节点上。

这种结构很适合真实场景中的数据,即一个焦点实体需要同时展示两个方向上的邻域,例如上游和下游血缘、父子关系、供应商与客户树,或者围绕某个事件的原因与结果分支。

relation-graph 的使用方式

这个示例包裹在 RGProvider 中,并通过 RGHooks.useGraphInstance() 驱动初始化以及后续更新。图使用树形布局,配置了 treeNodeGapH: 150RGLineShape.StandardCurveRGJunctionPoint.lr、灰色默认连线颜色以及沿路径跟随的连线标签。内置工具栏保持启用,并水平放置在右下角。

最重要的自定义点是 RGSlotOnNode。只有当 node.lot.level === 0 时,它才会替换默认节点主体,这使根节点成为一个特殊的交互目标,而非根节点则继续保持为简单的文本块。随后,示例依赖实例 API,例如 addNodesaddLinesdoLayoutgetRootNodegetNodeRelatedNodesupdateNodeDataupdateNodemoveToCenterzoomToFit

浮动说明面板和设置覆盖层来自共享的 DraggableWindow 辅助组件,而不是示例专属的图逻辑。该辅助组件使用 RGHooks.useGraphStore()setOptions(...) 来切换滚轮与拖拽行为,并且通过 prepareForImageGeneration()restoreAfterImageGeneration() 支持导出图片。本地 SCSS 文件实际上更像是占位符,因此可见样式主要由自定义节点插槽标记和共享窗口组件提供。

关键交互

  • 点击左侧根节点按钮,会切换那些计算后 lot.level 为负值的关联节点的可见性。
  • 点击右侧根节点按钮,会切换那些计算后 lot.level 为正值的关联节点的可见性。
  • 根节点按钮会根据根节点上保存的 leftExpandedrightExpanded 标记,在加号与减号图标之间切换。
  • 只有根节点会获得这些额外交互控件;其他所有节点都保持为只读文本。
  • 浮动辅助窗口可以被拖动、最小化、重新打开,并切换为设置覆盖层。
  • 设置覆盖层可以修改滚轮行为、修改画布拖拽行为,并将当前图下载为图片。

关键代码片段

这个片段表明,该示例从静态内联树数据开始,而不是按需加载或重建数据。

const treeJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'Root Node a', width: 120, height: 80 },
        { id: 'R-b', text: 'R-b' },
        { id: 'R-c', text: 'R-c' },
        { id: 'R-c-1', text: 'R-c-1' },
        { id: 'R-c-2', text: 'R-c-2' },
        { id: 'R-d', text: 'R-d' },
        { id: 'b', text: 'b' },

这个片段展示了初始化流程:加载节点和连线、执行布局、添加根节点元数据,然后让视口适配图内容。

const initializeGraph = async () => {
    graphInstance.addNodes(treeJsonData.nodes);
    graphInstance.addLines(treeJsonData.lines);
    await graphInstance.doLayout();
    const rootNode = graphInstance.getRootNode();
    graphInstance.updateNodeData(rootNode, {
        leftExpanded: true,
        rightExpanded: true,
    });
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

这个片段证明,左侧可见性并不是由内置展开控制器来控制的。它是根据关联节点以及 lot.level 中的布局侧别值计算出来的。

const toggleRootNodeLeft = async () => {
    const rootNode = graphInstance.getRootNode();
    if (rootNode) {
        graphInstance.updateNodeData(rootNode, {
            leftExpanded: !rootNode.data.leftExpanded
        });
        const relatedNodes = graphInstance.getNodeRelatedNodes(rootNode);
        const leftNodes = relatedNodes.filter(n => n.lot.level < 0);
        leftNodes.forEach(node => {
            graphInstance.updateNode(node, {
                hidden: !rootNode.data.leftExpanded
            });
        });
    }
};

这个片段展示了根节点如何获得一个自定义交互界面,而所有其他节点都回退为纯文本渲染。

<RGSlotOnNode>
    {({ node }) => {
        return node.lot.level === 0 ? (
            <div className="px-6 py-1 w-full h-full flex place-items-center justify-center text-xs">
                <div className="px-3 py-0.5 bg-gray-100 bg-opacity-30 rounded text-black text-sm">
                    {node.text}
                </div>
                {/* left and right buttons omitted */}
            </div>
        ) : (
            <div className="px-6 py-1 w-full h-full flex place-items-center justify-center text-xs">
                {node.text}
            </div>
        );
    }}
</RGSlotOnNode>

这个片段表明,画布设置和导出属于共享的演示工具,而不是这个示例最核心的专属图行为。

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

这个示例的独特之处

根据对比数据,这个示例在与其他与展开相关的演示一起阅读时最具区别性。与 expand-animationexpand-gradually 相比,它并不关注 relation-graph 内置的展开控制器、初始折叠状态,或展开后的重新布局策略。它的核心启发是一个自定义根节点 UI,把同一层级结构相反两侧拆分成两个独立操作。

open-all-close-all 相比,它的控制范围要窄得多,也更有针对性。后者会编排整个图的递归展开与折叠,而这个示例保持数据集静态,只对与根节点相关的节点立即应用可见性变化。

与其他 RGSlotOnNode 示例(例如 element-connect-to-node)相比,这里的自定义插槽较少被用作通用视觉组合技巧,而更多被用作只服务于根节点的行为界面。真正突出的组合在于:一个居中的双向树、根节点元数据标记、通过 getNodeRelatedNodes(...) 加上 lot.level 的方向过滤,以及对现有节点直接执行 hidden 更新。

这种模式还能用在哪里

这种模式非常适合用于以一个中心实体为核心、并且需要分别控制相反关系方向的查看器。示例包括上游与下游数据血缘、组织视图中的管理者与下属分支、供应商与客户依赖关系,以及事故分析中的原因与结果图。

当产品团队希望使用一套在外观和行为上都不同于 relation-graph 默认展开控件的自定义控制时,这种方式也很有价值。相同的方法还可以扩展到根节点范围过滤、方向强调,或一键分支摘要,而无需改变底层图数据集格式。