[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:corporate-group-relationship-tree":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 支持懒加载分支展开的企业关系树\n\n## 这个示例构建了什么\n这个示例围绕一家核心公司构建了一棵偏重阅读展示的企业关系树，并渲染在全高白色画布上。根公司显示为一条宽横幅，下游公司显示为狭长的纵向徽标，大多数连线都带有来自本地大型数据集的出资比例标签。用户可以查看层级结构，展开一个预先设置的占位分支以追加更多子节点，打开一个悬浮工具窗口，切换画布交互模式，并将当前关系图导出为图片。最值得关注的实现细节是，这棵树通过 relation-graph 的 append API 渐进增长，而不是重新加载整份数据集。\n\n## 数据如何组织\n数据来自 `my-data.ts`，它以静态内联 `RGJsonData` 对象的形式由 `getMyJsonData()` 返回。它包含一个 `rootId`、一个 `nodes` 数组和一个 `lines` 数组。大多数关系都从腾讯根节点流向各个子公司，并带有 `出资` 比例标签；与此同时，少量上游人员节点通过空标签连入根节点。因此，这个场景读起来更像是一棵企业所有权或关联关系树，而不是经典的员工组织架构图。\n\n在调用 `setJsonData(...)` 之前，`initializeGraph()` 会原地预处理这份数据集。它会扫描节点列表，通过强制设置 `width` 和 `height` 为 `0` 来让腾讯根节点自动测量尺寸，并把一个橙色节点改写为懒加载占位节点，添加 `data.asyncChild`、`loaded: false`、`expandHolderPosition: 'bottom'` 和 `expanded: false`。在生产系统中，同样的结构也可以表示所有权层级、关联公司树、控制方到子公司的视图，或尽调关系总览。\n\n## relation-graph 的使用方式\n入口文件使用 `RGProvider` 包裹整个示例，因此主图组件和共享辅助窗口都可以通过 relation-graph hooks 获取当前活动图实例。`MyGraph.tsx` 使用 `RGHooks.useGraphInstance()` 来加载准备好的数据集、将视口居中、让整棵树适配屏幕、在懒加载展开期间显示 loading 状态、追加新节点和新连线，并在插入新分支后重新执行布局。\n\n这张图本身使用内置的自上而下 `tree` 布局，并设置 `treeNodeGapV: 100` 与 `treeNodeGapH: 10`。它的选项将 `RGLineShape.SimpleOrthogonal` 与 `RGJunctionPoint.tb` 配对使用，因此连接线在向下层级结构中表现为规整的折线连接。节点几何尺寸保持为 slot 测量模式，因为 `defaultNodeWidth` 和 `defaultNodeHeight` 都是 `0`，内置边框被移除，且位于底部的展开控件仍然能与自动重新布局保持兼容。\n\n`RGSlotOnNode` 是主要的渲染技术。它为腾讯根节点提供了一个专用的横向横幅，而其他所有节点都会渲染为紧凑的纵向徽标，并复用 `var(--rg-node-color)` 作为填充色和边框色。本地 SCSS 让画布背景保持白色，并为已选中节点添加选中态光晕。悬浮的 `DraggableWindow` 及其 `CanvasSettingsPanel` 使用 `RGHooks.useGraphStore()` 配合 `setOptions(...)` 来切换当前图的滚轮与拖拽行为，而同一个共享面板也通过 `prepareForImageGeneration()`、`modern-screenshot` 和 `restoreAfterImageGeneration()` 驱动图片导出。\n\n## 关键交互\n展开那个唯一预先准备好的占位节点时，会触发一次性的延迟分支加载。经过一次模拟异步等待后，示例会追加三个子节点和三条带标签的连线，然后重新执行布局。\n\n悬浮辅助窗口可以被拖动、最小化，并通过齿轮按钮切换到设置叠层。\n\n在设置叠层内部，滚轮行为可以在 `scroll`、`zoom` 和 `none` 之间切换，而画布拖拽行为可以在 `selection`、`move` 和 `none` 之间切换。\n\nDownload Image 操作会先准备图的 DOM，将当前画布捕获为 blob，下载该图片，然后在事后恢复图状态。\n\n## 关键代码片段\n这个片段展示了核心图配置：自上而下的树布局、正交连接线、底部展开控件，以及依赖 slot 测量的自动尺寸节点。\n\n```tsx\nconst graphOptions: RGOptions = {\n    defaultNodeShape: RGNodeShape.rect,\n    defaultLineShape: RGLineShape.SimpleOrthogonal,\n    defaultLineColor: '#666666',\n    defaultJunctionPoint: RGJunctionPoint.tb,\n    defaultNodeBorderWidth: 0,\n    defaultExpandHolderPosition: 'bottom',\n    reLayoutWhenExpandedOrCollapsed: true,\n    defaultNodeWidth: 0,\n    defaultNodeHeight: 0,\n    defaultNodeColor: '#3B82F6',\n    layout: {\n```\n\n这个片段说明，该示例在把数据集加载进 relation-graph 之前，会先改写两个特定节点。\n\n```tsx\nconst myJsonData: RGJsonData = await getMyJsonData();\nmyJsonData.nodes.forEach(thisNode => {\n    if (thisNode.text === '深圳市腾讯计算机系统有限公司') {\n        thisNode.width = 0;\n        thisNode.height = 0;\n    }\n    if (thisNode.text === '这个节点原本是没有子节点的') {\n        thisNode.data = { asyncChild: true, loaded: false };\n        thisNode.expandHolderPosition = 'bottom';\n        thisNode.expanded = false;\n    }\n});\n```\n\n这个片段展示了分支增长是在展开之后通过 append API 完成的，而不是重建整份图 JSON。\n\n```tsx\nif (nodeObject.data && nodeObject.data.asyncChild === true && nodeObject.data.loaded === false) {\n    nodeObject.data.loaded = true;\n    graphInstance.loading('Loading data from remote server...')\n    setTimeout(async () => {\n        const newJsonData: RGJsonData = {\n            nodes: [\n                { id: nodeObject.id + '-child-1', text: nodeObject.id + '-Child Node 1' },\n                { id: nodeObject.id + '-child-2', text: nodeObject.id + '-Child Node 2' },\n                { id: nodeObject.id + '-child-3', text: nodeObject.id + '-Child Node 3' }\n            ],\n```\n\n这个片段展示了 `RGSlotOnNode` 如何把根节点横幅与纵向子节点徽标区分开来。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }: RGNodeSlotProps) => {\n        return (\n            node.text === '深圳市腾讯计算机系统有限公司' ?\n                \u003Cdiv>\n                    \u003Cdiv className=\"rounded bg-blue-500 text-white flex items-center justify-center gap-2 px-4 py-4\">\n                        \u003CBuilding2Icon size={40} />\n                        \u003Cdiv className=\"w-fit text-2xl\">{node.text}\u003C/div>\n                    \u003C/div>\n                \u003C/div>\n                :\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```tsx\nconst canvasDom = await graphInstance.prepareForImageGeneration();\nlet graphBackgroundColor = graphInstance.getOptions().backgroundColor;\nif (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n    graphBackgroundColor = '#ffffff';\n}\nconst imageBlob = await domToImageByModernScreenshot(canvasDom, {\n    backgroundColor: graphBackgroundColor\n});\nif (imageBlob) {\n    downloadBlob(imageBlob, 'my-image-name');\n}\n```\n\n## 这个示例的独特之处\n与附近的业务类示例（如 `scene-group` 和 `investment`）相比，这个示例刻意保持更轻量。它让一个核心公司位于向下展开的树顶端，避免了密集交叉连线和布局后清理逻辑，并采用更简单的白色画布展示方式，以及一种固定的节点皮肤模式，而不是多种运行时视觉模式。\n\n与 `show-more-nodes-by-page`、`investment-penetration` 和 `show-more-nodes-front` 这类渐进加载示例相比，这里的懒加载行为被刻意收窄。它只使用一个会加载一次的占位节点，而不是带类型的分支导航、分页、展开后重新居中流程，或隐藏节点揭示策略。\n\n对比数据还表明，这种组合本身并不常见：一个准备好的、带有大量比例标签的所有权风格数据集，自上而下的正交布局，根节点与子节点不同的 slot 样式，通过 `addNodes(...)` 和 `addLines(...)` 一次性异步插入分支，以及同页共享的悬浮设置与导出窗口。这使得该示例非常适合作为只读型企业层级查看器的起点，用于在不演变成完整探索工具的前提下提供适度的渐进披露能力。\n\n## 这一模式还适用于哪里\n这一模式同样适用于股权树、关联公司图、子公司所有权视图，以及需要一个主企业层级并带少量延迟展开能力的合规看板。\n\n它也适合偏展示型的关系图查看器，在这些场景里，根实体需要比下游节点更强的视觉识别度，但实现层面仍应依赖 relation-graph 的内置树布局。\n\n这种 append 后再重新布局的方法适用于任何需要按需揭示更多图细节、又不想重建整份数据集的产品，尤其是在页面还需要轻量级查看器工具（如模式切换和截图导出）时。\n",false,500,1782615399437]