JavaScript is required

系统架构图编辑器

这个示例基于硬编码层级数据渲染分层系统架构看板,并隐藏结构性的父子连线。它结合固定布局图加载、自定义后处理分组排布、节点文本内联编辑、缩放与吸附覆盖层、分组工具栏动作、深度预设、配色控制和图片导出,形成一个轻量架构编辑器。

支持分组重排的系统架构图编辑器

这个示例构建了什么

这个示例构建了一个分层的系统架构画板,用户可以直接在画布上进行调整。可见效果是一组层层嵌套的分区容器,带有左侧标题栏、基于层级深度的背景颜色,以及白色叶子卡片,而结构性的父子连线保持隐藏。

用户可以切换可见深度预设、开关 minimap、显示或隐藏展开按钮、修改层级配色方案、打开共享画布设置面板、导出图片、选择单个节点、选择整个子树、内联重命名节点、调整所选分组大小、重新贴合分组边框、重新布局子项,以及追加新的子项。

这个实现的主要亮点在于,它并不只依赖默认的自动布局。它会把固定位置的层级数据加载到 relation-graph 中,等待 node.lot.levelnode.lot.childs 这类层级元数据生成完成,然后执行自定义的打包与重新贴合流程,从而让图看起来更像架构工作台,而不是普通的节点连线图。

数据是如何组织的

内置的数据源是在 ai-json-data.ts 中硬编码的嵌套树,名称为 aiOutputJson。每一项都包含 textxy 和可选的 children,因此源数据本身已经是层级结构,也已经包含了大致的位置提示。

parseJsonDataByAI() 会递归地把这棵树转换为 RGJsonData,将每一项扁平化为一个节点,生成 id,保留原始坐标,在 node.data 中存储 hasChildrendeep,并为每个非根节点关系创建一条父子连线。图依然会加载这些连线,因为层级逻辑依赖它们,但该示例会在加载前后将它们隐藏,以保持展示效果以盒状分组为主。

setJsonData() 之后,还有两个重要的预处理结果。第一,relation-graph 会生成后续逻辑所依赖的 node.lot 层级元数据。第二,自定义 actions 层可以把图重新打开到指定深度,重新打包每个分组的子节点,并根据后代边界重新调整祖先容器的尺寸。

在真实产品中,这种数据结构同样可以表示平台架构、能力地图、分层产品模块、企业系统版图、服务分类体系,或任何其他已经预先整理好的层级结构,在这些场景中,分组区块比可见连接线更重要。

relation-graph 是如何使用的

入口组件使用 RGProvider 包裹整个示例,MyGraph 则通过 RGHooks.useGraphInstance() 作为主要的命令式控制入口。初始 RGOptions 配置了 layout.layoutName = 'fixed'、矩形节点、默认节点零边框、曲线连线、边框连接点、左侧展开控制、展开或收起时重新布局、滚轮滚动、画布拖拽,以及调试模式。

初始化时,relation-graph 被当作一个分阶段加载器来使用。示例会调用 loading()setJsonData()moveToCenter()zoomToFit()clearLoading(),同时 MyGraphActions 会调用 doLayout()updateNodesVisibleProperty()getNodesRectBox()updateNodeData()updateNode()addNodes()addLines()generateNewNodeId()getDescendantNodes()updateEditingControllerView(),以保持这个可编辑画板的一致性。

插槽承担了大部分视觉适配工作。RGSlotOnNode 会用叶子卡片或分组容器替换默认图节点,这两种形式都通过 MyEditableNode 渲染。RGSlotOnView 则挂载 RGMiniViewRGEditingReferenceLineRGEditingNodeControllerRGEditingResize 和自定义的 MyNodeToolbar,因此编辑交互能力位于图的覆盖层中,而不是侧边表单中。

共享的 DraggableWindow 组件则在图周围提供展示控制。它通过 setOptions(...) 提供深度预设、minimap 与展开按钮开关、颜色方案切换、滚轮模式与拖拽模式切换,并通过 prepareForImageGeneration()restoreAfterImageGeneration() 支持图片导出。

样式也是 relation-graph 使用方式的一部分。SCSS 用 var(--rg-node-color) 重绘展开按钮,为叶子节点赋予带边框的卡片外观,让分组节点看起来像带内边距的容器,并把顶层和一级标签移成左侧标题栏样式,其中包括一个较大的纵向根标签。

关键交互

  • 单击节点会通过 setEditingNodes([node]) 将其设为当前编辑选中项。
  • 双击叶子包装器或分组包装器,会通过 getDescendantNodes(...) 选择该节点及其所有后代。
  • 双击 MyEditableNode 中的文本会进入内联编辑;失焦或按 Enter 会通过 updateNode(...) 保存,按 Escape 会取消。
  • 点击画布会同时清除勾选状态和当前编辑选中状态。
  • 深度预设选择器会重新执行 openByLevel(...)、自定义布局流程和 zoomToFit(),从而可以在不同层级深度下查看同一张画板。
  • 浮动控件可以显示或隐藏 RGMiniView,切换展开控制的可见性,并通过修改层级配色来改变画板外观。
  • 共享设置面板可以切换滚轮模式、切换画布拖拽模式,并将当前图下载为图片。
  • 当某个已选节点完成尺寸调整时,onResizeEnd 会触发该节点的子项重新布局。
  • 当某个已选节点拥有子节点时,覆盖层工具栏会直接提供三个操作:重新布局子项、让容器贴合后代内容,以及追加新的子项。

关键代码片段

这个递归辅助函数说明,内置源数据起点是一棵嵌套树,在加载 relation-graph 之前,它会先被转换为扁平的图节点和连线。

const nodeJson = {
    id: 'n-' + nodes_collect.length,
    text: item.text,
    x: item.x,
    y: item.y,
    data: { hasChildren, deep }
};
nodes_collect.push(nodeJson);
if (parentNode) {
    links_collect.push({ id: `${parentNode.id}-to-${nodeJson.id}`, from: parentNode.id, to: nodeJson.id });
}

这个初始化流程会隐藏结构连接线、加载准备好的图,然后应用深度展开、自定义布局和视口自适应。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = await parseJsonDataByAI();
    myJsonData.lines.forEach(line => {
        line.hidden = true;
    });
    graphInstance.loading();
    await graphInstance.setJsonData(myJsonData);
    updateGraphStyles();
    await myGraphActions.current.openByLevel(8);
    await myGraphActions.current.doMyLayout();
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

这个自定义布局流程会重新打包每个非叶子分组,然后统一一级分区的宽度。

for (const node of allNodes) {
    if (node.rgChildrenSize > 0) {
        const childrenNodes = node.lot.childs;
        const nodesWithNewXy = adjustNodeLayout({
            nodes: childrenNodes,
            width: node.lot.level < 2 ? 1600 : 200,
            gap: 20
        });
        nodesWithNewXy.forEach(newXy => {
            const cnode = childrenNodes.find(n => n.id === newXy.nodeId);
            if (cnode) this.moveNodeTo(cnode, newXy.newX, newXy.newY);
        });
        this.updateNodeSizeByChildrenSize(node);
    }
}

这个重新贴合步骤展示了如何根据后代边界重新计算分组的包围框,并将结果向上传递到祖先容器。

const childrenNodesSize = graphInstance.getNodesRectBox(childrenNodes);
const padding = 10;
const leftTitleWidth = node.lot.level <= 1 ? 250 : 0;
const titleHeight = leftTitleWidth > 0 ? 0 : 30;
graphInstance.updateNode(node, {
    x: childrenNodesSize.minX - padding - leftTitleWidth,
    y: childrenNodesSize.minY - padding - titleHeight,
    width: Math.max(groupMinWidth, childrenNodesSize.width + padding * 2) + leftTitleWidth,
    height: childrenNodesSize.height + padding * 2 + titleHeight
});

这个编辑处理器说明,内联重命名是实际实现的行为,而不只是视觉上的输入框演示。

const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
        finishEditing();
    } else if (e.key === 'Escape') {
        setEditingText(node.text || '');
        setIsEditing(false);
    }
};
const finishEditing = () => {
    if (editingText !== node.text) {
        onNodeTextChange(node, editingText);
    }
    setIsEditing(false);
};

这个视图插槽就是示例挂载 minimap、吸附参考线、尺寸调整手柄和分组操作工具栏的位置。

<RGSlotOnView>
    {showMiniView && <RGMiniView />}
    <RGEditingReferenceLine adsorption={true} />
    <RGEditingNodeController>
        <MyNodeToolbar
            onLayoutItemsButtonClick={onLayoutItemsButtonClick}
            onFitContentButtonClick={onFitContentButtonClick}
            onAddChildrenButtonClick={onAddChildrenButtonClick}
        />
        <RGEditingResize />
    </RGEditingNodeController>
</RGSlotOnView>

这个示例的独特之处

根据对比数据,最接近的偏查看器风格示例是 system-architecture-diagram。这两个示例都共享同样的隐藏连线架构画板基础、固定布局层级加载、基于深度的配色、左侧标题栏,以及布局后容器打包。不同之处在于,这个示例加入了编辑覆盖层:在同样的分组视觉语言之上,增加了内联重命名、尺寸调整手柄、吸附参考线、子树选择、贴合内容重算,以及新增子项操作。

undo-redo-examplefreely-draw-lines-on-canvas 这类偏编辑器的相邻示例相比,这个示例更窄、更聚焦具体场景。它不会扩展到历史管理、通用连线绘制或自由创建,而是保持一套预先准备好的分层结构,并聚焦于架构感知的修改动作,例如重新打包后代节点、重新贴合祖先容器边框,以及给选中分区新增一个子项。

industry-chain 相比,可复用的经验也不同。两个示例都支持基于深度的查看方式和浮动工作台外壳,但 industry-chain 主要是一个只读层级查看器。这个示例则更进一步,把深度预设与分组容器上的直接操作结合起来,同时仍通过隐藏连接线来保持画面的整洁有序。

因此,真正有辨识度的并不是某一个孤立功能,而是这一整组能力的组合:固定位置层级扁平化、隐藏连线的分组呈现、自定义布局后重排、递归分组重新贴合、内联文本编辑、子树多选、尺寸驱动的重新布局,以及同一画布上的轻量运行时控制。

这种模式还适用于哪里

这种模式很适合用于半可编辑的能力地图、平台架构画板、分层产品清单、企业应用版图,以及运营模型图这类场景。在这些场景中,层级结构通常预先已知,但团队仍需要有限的画布内维护能力。

它也适用于 AI 流水线、服务域、模块归属图、合规控制栈,以及技术工作台界面的内部规划工具。在这些场景中,分组区块需要保持可读,而不适合始终显示大量连接线。最关键的可复用思路是,逻辑和编辑层面保留真实的图结构数据,而展示层则把结果呈现为嵌套的架构面板,而不是传统的重边连线图。