[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:system-architecture-diagram":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 使用自定义布局后打包的系统架构图\n\n## 这个示例构建了什么\n\n这个示例将一份分层的平台描述转换为一个分层的系统架构看板。最终呈现的视图并不是传统的节点连线图。相反，它更像是堆叠的架构分区：高层级使用大型彩色分组容器表示，叶子节点则显示为带白色边框的卡片。\n\n用户可以展开或折叠分组，切换可见深度预设，切换 minimap，显示或隐藏展开标签，修改层级配色方案，双击任意节点以选中它的完整后代子树，并打开一个浮动设置面板来修改画布行为或导出图片。\n\n实现上的主要亮点是两步式布局策略。RelationGraph 先构建固定布局的层级元数据，然后该示例再执行自己的打包过程，重新排列每个容器内的子节点，并根据测量结果调整父盒子的尺寸。再结合隐藏连线后，整个图看起来就更像技术架构看板，而不是可见关系网络。\n\n## 数据是如何组织的\n\n随附的 demo 从 `ai-json-data.ts` 中内嵌的一棵嵌套树开始。每一项都包含 `text`、近似的 `x` 和 `y` 坐标，以及可选的 `children`。运行时，`parseJsonDataByAI()` 会把这棵树拍平成 `RGJsonData`，生成节点 id，保留源坐标，在 `node.data` 中保存深度元数据，并为每一条非根节点关系创建一条父子连线。\n\n在最终视图显示之前，有两个重要的预处理步骤。第一，在执行 `setJsonData()` 之前，每条生成的连线都会被标记为隐藏；之后在图加载完成后的样式处理阶段，又会再次隐藏它们。第二，加载完成后，示例会按层级给节点重新着色，将层级展开到一个初始阈值，然后执行自己的布局后打包过程，最后再将视口居中并适配到合适大小。\n\n在真实系统中，同样的结构可以表示能力地图、产品模块、技术栈、平台分层、业务域，或者任何其他已经存在近似初始坐标、且可见连接线只会增加噪声而不会带来价值的层级结构。\n\n## relation-graph 是如何使用的\n\n入口组件使用 `RGProvider` 包裹整个 demo，随后 `MyGraph` 通过 `RGHooks.useGraphInstance()` 作为主要控制入口。初始化流程使用 `loading()`、`setJsonData()`、`moveToCenter()`、`zoomToFit()` 和 `clearLoading()` 来加载并展示准备好的图。之后的运行时行为则依赖于 `getNodes()`、`updateNodeData()`、`updateNode()`、`getNodesRectBox()`、`updateNodesVisibleProperty()`、`doLayout()`、`getDescendantNodes()`、`setEditingNodes()` 和 `clearChecked()` 等实例方法。\n\n图配置刻意从一个固定的基础开始。该示例启用了 `layoutName: 'fixed'`、矩形节点、曲线连线、边框连接点、左侧展开控制、展开或折叠时重新布局、滚轮滚动、画布拖拽以及调试模式。但这个固定布局并不是最终的视觉排列方式。它只是一个中间阶段，让代码可以访问 `node.lot.level` 和 `node.lot.childs`，随后自定义布局过程再利用这些信息重新打包子节点并调整每个分组容器的尺寸。\n\n大部分视觉转换都通过 slots 完成。`RGSlotOnNode` 用两个分支替换了默认节点：叶子节点会变成白色卡片，非叶子节点则会变成带标题区域的边框容器。`RGSlotOnView` 添加了一个可选的 `RGMiniView`，以及一个始终开启的 `RGEditingReferenceLine`。共享的浮动辅助窗口还通过 `RGHooks.useGraphStore()` 读取实时图配置，从而让它的滚轮和拖拽控制与当前图选项保持同步，并通过 `prepareForImageGeneration()` 加上 `restoreAfterImageGeneration()` 导出当前画布。\n\n样式的重要性与布局代码同样高。SCSS 会把展开控制移动到左侧边缘，通过 `var(--rg-node-color)` 将它们渲染为彩色标签，把 0 级和 1 级标题竖向显示在左侧轨道上，并让叶子卡片在视觉上与分组盒子清晰区分。由于该示例还隐藏了所有连线，这些 slot 和样式覆盖实际上就成了主要的展示层。\n\n## 关键交互\n\n- 深度选择器显示标签 `2` 到 `6`，并将它们映射为阈值 `1` 到 `5`，随后重新执行展开、重新应用自定义布局，并再次适配视口。\n- 无论双击的是叶子卡片还是分组容器，都会通过 `getDescendantNodes()` 和 `setEditingNodes()` 选中被点击节点及其全部后代。\n- 一个 minimap 复选框会根据状态挂载或移除 `RGMiniView`。\n- 第二个复选框会在运行时更新 `defaultExpandHolderPosition`，从而让展开或折叠标签保持显示在左侧边缘，或者完全隐藏。\n- 调色板编辑器可以应用预设配色方案，也可以修改各个层级的颜色，并且每次更新都会按 `node.lot.level` 重新为图着色。\n- 浮动设置覆盖层可以切换滚轮行为、切换画布拖拽行为，并将当前图导出为图片。\n- 点击画布会清除选中状态，并清除当前编辑节点选择。\n\n## 关键代码片段\n\n这个递归分支展示了嵌套源树如何被转换成图中的连线，并继续拍平更深层级。\n\n```ts\nif (parentNode) {\n    links_collect.push({\n        id: `${parentNode.id}-to-${nodeJson.id}`,\n        from: parentNode.id,\n        to: nodeJson.id\n    });\n}\nif (hasChildren) {\n    flatTreeData(_childs, nodeJson, nodes_collect, links_collect, deep + 1);\n}\n```\n\n这段初始化流程会先隐藏连接线，再加载生成好的图数据，应用展开状态和自定义布局，最后将结果居中并适配到合适大小。\n\n```ts\nconst initializeGraph = async () => {\n    const myJsonData: RGJsonData = await parseJsonDataByAI();\n    myJsonData.lines.forEach(line => {\n        line.hidden = true;\n    });\n    graphInstance.loading();\n    await graphInstance.setJsonData(myJsonData);\n    updateGraphStyles();\n    await openByLevel(8);\n    await doMyLayout();\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n    graphInstance.clearLoading();\n};\n```\n\n自定义布局后处理中的这一部分会测量每个子节点簇，并根据打包后的子节点范围调整父分组的尺寸。\n\n```ts\nconst childrenNodesSize = graphInstance.getNodesRectBox(childrenNodes);\nconst padding = 10;\nconst leftTitleWidth = node.lot.level \u003C= 1 ? 150 : 0;\nconst titleHeight = leftTitleWidth > 0 ? 0 : 30;\nconst groupMinWidth = 160;\ngraphInstance.updateNode(node, {\n    x: childrenNodesSize.minX - padding - leftTitleWidth,\n    y: childrenNodesSize.minY - padding - titleHeight,\n    width: Math.max(groupMinWidth, childrenNodesSize.width + padding * 2) + leftTitleWidth,\n    height: childrenNodesSize.height + padding * 2 + titleHeight\n});\n```\n\n这个 slot 渲染器会在叶子卡片和分组容器之间切换，而且两个分支都绑定了双击选中整棵子树的行为。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }: RGNodeSlotProps) => {\n        const level = node.lot?.level || 0;\n        const isLeaf = node.rgChildrenSize === 0;\n\n        return (\n            isLeaf ?\u003Cdiv\n                className={`my-leaf-node min-w-[150px] px-4 py-1 bg-white text-sm`}\n                onDoubleClick={() => {selectAllDescendantNodes(node);}}\n            >\n                    {node.text}\n            \u003C/div>\n```\n\n这个视图 slot 增加了面向查看场景的辅助能力，但并没有把这个示例变成完整的编辑工作台。\n\n```tsx\n\u003CRGSlotOnView>\n    {showMiniView && \u003CRGMiniView />}\n    \u003CRGEditingReferenceLine adsorption={true} />\n\u003C/RGSlotOnView>\n```\n\n## 这个示例的独特之处\n\n与 `system-architecture-diagram-designer` 相比，这个示例保留了相同的架构看板基础，但仍然停留在展示模式这一侧。它没有加入行内文本编辑、尺寸调整手柄或运行时节点创建，因此当目标是渲染一个架构看板而不是去制作它时，这个示例会是更纯粹的参考。\n\n与 `use-d3-layout` 相比，这里的主要经验并不是替换外部算法。它的独特技术点在于：先让 RelationGraph 产出固定层级元数据，再利用这些元数据执行类似货架式的子节点打包过程，根据后代范围调整分组容器尺寸，并始终隐藏所有连接线。\n\n与 `node-slot-list`、`node` 这类以 slot 为核心的查看器示例相比，这里的 slots 和样式并不是一个通用样式展示集。它们都服务于一个非常聚焦的视觉目标：带有左侧轨道标题、按深度着色、分组容器以及基于层级显示控制的分层架构看板。\n\n这里罕见的组合才是真正重要的部分：内嵌层级 JSON 拍平、隐藏父子连线、按深度编码的分组、竖向分区标题、运行时调色、预设展开层级，以及在同一张已加载图上执行自定义重新布局。正是这种组合，使它成为一个很强的起点，适用于那些“分组比可见边更重要”的架构查看器。\n\n## 这种模式还适用于哪里\n\n这种模式也很适合迁移到技术能力地图、平台架构总览、分层产品或服务清单、AI 流水线拆解，以及企业系统版图摘要中。尤其是在需要保持层级分组可读性、适合按层级逐步展开、并且显式连线会让界面更难阅读的场景下，它会非常有用。\n\n它也可以被改造成合规控制地图、运营模型图、职责分层，或模块依赖摘要等场景。在这些场景里，底层关系在结构上依然重要，但不需要始终保持可见。可复用的核心思想是：将真实层级结构加载进 relation-graph，为逻辑保留结构连线，再通过自定义打包和 slot 渲染，把结果呈现为嵌套面板。\n",false,500,1782616037942]