[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:root-side-branch-visibility-toggles":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 以根节点为中心并可分别切换左右分支的树形图\n\n## 这个示例构建了什么\n\n这个示例构建了一个以单个根节点为中心的双向树。用户可以在左侧看到传入分支，在右侧看到传出分支，并且在根节点外侧放置了两个粉色操作按钮，因此每一侧都可以独立显示或隐藏。\n\n这个示例的重点并不是通用的展开或折叠行为，而是一个使用 `RGSlotOnNode` 构建的自定义根节点控制界面，其中一个按钮作用于层级结构的左侧，另一个按钮作用于右侧。\n\n## 数据是如何组织的\n\n图数据以内联方式声明为一个 `RGJsonData` 对象，其中包含 `rootId: 'a'`、13 个节点和 12 条连线。组件在布局之前不会对数据集做预处理。相反，它会把 `nodes` 和 `lines` 数组添加到图实例中，执行 `doLayout()`，然后再把两个运行时标记 `leftExpanded` 和 `rightExpanded` 写到根节点上。\n\n这种结构很适合真实场景中的数据，即一个焦点实体需要同时展示两个方向上的邻域，例如上游和下游血缘、父子关系、供应商与客户树，或者围绕某个事件的原因与结果分支。\n\n## relation-graph 的使用方式\n\n这个示例包裹在 `RGProvider` 中，并通过 `RGHooks.useGraphInstance()` 驱动初始化以及后续更新。图使用树形布局，配置了 `treeNodeGapH: 150`、`RGLineShape.StandardCurve`、`RGJunctionPoint.lr`、灰色默认连线颜色以及沿路径跟随的连线标签。内置工具栏保持启用，并水平放置在右下角。\n\n最重要的自定义点是 `RGSlotOnNode`。只有当 `node.lot.level === 0` 时，它才会替换默认节点主体，这使根节点成为一个特殊的交互目标，而非根节点则继续保持为简单的文本块。随后，示例依赖实例 API，例如 `addNodes`、`addLines`、`doLayout`、`getRootNode`、`getNodeRelatedNodes`、`updateNodeData`、`updateNode`、`moveToCenter` 和 `zoomToFit`。\n\n浮动说明面板和设置覆盖层来自共享的 `DraggableWindow` 辅助组件，而不是示例专属的图逻辑。该辅助组件使用 `RGHooks.useGraphStore()` 和 `setOptions(...)` 来切换滚轮与拖拽行为，并且通过 `prepareForImageGeneration()` 和 `restoreAfterImageGeneration()` 支持导出图片。本地 SCSS 文件实际上更像是占位符，因此可见样式主要由自定义节点插槽标记和共享窗口组件提供。\n\n## 关键交互\n\n- 点击左侧根节点按钮，会切换那些计算后 `lot.level` 为负值的关联节点的可见性。\n- 点击右侧根节点按钮，会切换那些计算后 `lot.level` 为正值的关联节点的可见性。\n- 根节点按钮会根据根节点上保存的 `leftExpanded` 和 `rightExpanded` 标记，在加号与减号图标之间切换。\n- 只有根节点会获得这些额外交互控件；其他所有节点都保持为只读文本。\n- 浮动辅助窗口可以被拖动、最小化、重新打开，并切换为设置覆盖层。\n- 设置覆盖层可以修改滚轮行为、修改画布拖拽行为，并将当前图下载为图片。\n\n## 关键代码片段\n\n这个片段表明，该示例从静态内联树数据开始，而不是按需加载或重建数据。\n\n```tsx\nconst treeJsonData: RGJsonData = {\n    rootId: 'a',\n    nodes: [\n        { id: 'a', text: 'Root Node a', width: 120, height: 80 },\n        { id: 'R-b', text: 'R-b' },\n        { id: 'R-c', text: 'R-c' },\n        { id: 'R-c-1', text: 'R-c-1' },\n        { id: 'R-c-2', text: 'R-c-2' },\n        { id: 'R-d', text: 'R-d' },\n        { id: 'b', text: 'b' },\n```\n\n这个片段展示了初始化流程：加载节点和连线、执行布局、添加根节点元数据，然后让视口适配图内容。\n\n```tsx\nconst initializeGraph = async () => {\n    graphInstance.addNodes(treeJsonData.nodes);\n    graphInstance.addLines(treeJsonData.lines);\n    await graphInstance.doLayout();\n    const rootNode = graphInstance.getRootNode();\n    graphInstance.updateNodeData(rootNode, {\n        leftExpanded: true,\n        rightExpanded: true,\n    });\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n};\n```\n\n这个片段证明，左侧可见性并不是由内置展开控制器来控制的。它是根据关联节点以及 `lot.level` 中的布局侧别值计算出来的。\n\n```tsx\nconst toggleRootNodeLeft = async () => {\n    const rootNode = graphInstance.getRootNode();\n    if (rootNode) {\n        graphInstance.updateNodeData(rootNode, {\n            leftExpanded: !rootNode.data.leftExpanded\n        });\n        const relatedNodes = graphInstance.getNodeRelatedNodes(rootNode);\n        const leftNodes = relatedNodes.filter(n => n.lot.level \u003C 0);\n        leftNodes.forEach(node => {\n            graphInstance.updateNode(node, {\n                hidden: !rootNode.data.leftExpanded\n            });\n        });\n    }\n};\n```\n\n这个片段展示了根节点如何获得一个自定义交互界面，而所有其他节点都回退为纯文本渲染。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }) => {\n        return node.lot.level === 0 ? (\n            \u003Cdiv className=\"px-6 py-1 w-full h-full flex place-items-center justify-center text-xs\">\n                \u003Cdiv className=\"px-3 py-0.5 bg-gray-100 bg-opacity-30 rounded text-black text-sm\">\n                    {node.text}\n                \u003C/div>\n                {/* left and right buttons omitted */}\n            \u003C/div>\n        ) : (\n            \u003Cdiv className=\"px-6 py-1 w-full h-full flex place-items-center justify-center text-xs\">\n                {node.text}\n            \u003C/div>\n        );\n    }}\n\u003C/RGSlotOnNode>\n```\n\n这个片段表明，画布设置和导出属于共享的演示工具，而不是这个示例最核心的专属图行为。\n\n```tsx\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={wheelMode}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\n## 这个示例的独特之处\n\n根据对比数据，这个示例在与其他与展开相关的演示一起阅读时最具区别性。与 `expand-animation` 和 `expand-gradually` 相比，它并不关注 relation-graph 内置的展开控制器、初始折叠状态，或展开后的重新布局策略。它的核心启发是一个自定义根节点 UI，把同一层级结构相反两侧拆分成两个独立操作。\n\n与 `open-all-close-all` 相比，它的控制范围要窄得多，也更有针对性。后者会编排整个图的递归展开与折叠，而这个示例保持数据集静态，只对与根节点相关的节点立即应用可见性变化。\n\n与其他 `RGSlotOnNode` 示例（例如 `element-connect-to-node`）相比，这里的自定义插槽较少被用作通用视觉组合技巧，而更多被用作只服务于根节点的行为界面。真正突出的组合在于：一个居中的双向树、根节点元数据标记、通过 `getNodeRelatedNodes(...)` 加上 `lot.level` 的方向过滤，以及对现有节点直接执行 `hidden` 更新。\n\n## 这种模式还能用在哪里\n\n这种模式非常适合用于以一个中心实体为核心、并且需要分别控制相反关系方向的查看器。示例包括上游与下游数据血缘、组织视图中的管理者与下属分支、供应商与客户依赖关系，以及事故分析中的原因与结果图。\n\n当产品团队希望使用一套在外观和行为上都不同于 relation-graph 默认展开控件的自定义控制时，这种方式也很有价值。相同的方法还可以扩展到根节点范围过滤、方向强调，或一键分支摘要，而无需改变底层图数据集格式。\n",false,500,1782615427667]