产业链关系组布局编辑器
这个示例构建了一个可编辑的产业价值链看板:HTML 阶段卡片在同一固定画布上锚定多个 relation-graph 树分组。用户可以整组选中、调整节点/连线/树布局参数、在指定行插入新的随机树,并在结构变化时触发带依赖感知的重布局。
产业价值链看板上的可编辑树形分组
这个示例构建了什么
本示例构建了一个三阶段产业价值链看板,其中可见卡片里的每一行都锚定一棵独立的 relation-graph 树。用户看到的是上游、中游和下游业务画布,而实际的图节点则从这些卡片行向外扩展,而不是取代它们。
这个体验兼具查看器和轻量编辑器两种角色。用户可以点击任意可见图节点来选中其所属的整个分组,借助对齐辅助线拖动该选中分组,在浮动面板中调整分组级别的节点、连线和树布局选项,删除当前选中的分组,或者在特定阶段卡片行之前或之后插入一棵新生成的树。这里最重要的理念是:普通 HTML 业务卡片仍然是页面可见的骨架,而 relation-graph 则围绕这些卡片渲染可编辑的树形簇。
数据如何组织
源模型起始于 my-data.ts 中三列嵌套的 JsonNode 树。getMyAllColumnsData() 返回示例自带的三列场景 c1、c2 和 c3,每一列都同时携带标题、副标题、图标、主题色等展示元数据,以及该列对应的树根节点。
在任何布局开始之前,示例会先把这些嵌套树预处理为分组记录。MyMixLayout.ts 中的 buildMyGroup() 和 flatNodeData() 会将每棵树拍平成图节点和连线,分配共享的 myGroupId,为每个节点补充 hasChildren 和 deep 标记,并把真实根节点转换成一个很小的隐藏锚点节点。这个预处理很关键,因为后续交互操作面对的是整个分组,而不是单个被点击的节点。
同样的树形结构也被复用于插入流程。当用户选择一个插入位置时,浮动面板会用 generateRandomTreeData() 生成另一棵嵌套的 JsonNode 树,并在图重建之前将其插入到目标列中。在真实应用里,这些树完全可以来自产品能力地图、流程家族、组织集群或分阶段依赖模型,而不只是示例内置的无人机行业样本。
relation-graph 如何使用
外层的 RelationGraph 运行在固定布局模式下,这个示例把它当作多个独立树布局的宿主画布,而不是一个自动排布的完整大图。在 MyGraph.tsx 中,图配置设置了 layoutName: 'fixed'、矩形节点、简单正交连线,以及默认的左右连接策略。真正的树位置计算交给了 MyMixLayout,它会在运行时通过 graphInstance.createLayout(...) 为每个分组创建独立的树布局,并用 fixedRootNode = true 锁定根节点位置。
大部分组合工作都是通过插槽完成的。RGSlotOnCanvas 负责渲染来自 MyCanvasContent4Cols.tsx 的大型 HTML 阶段看板。每个阶段卡片行都会暴露一个 RGConnectTarget,然后由 connectElementToNode() 从这些 DOM 锚点向隐藏的分组根节点添加伪正交桥接线。RGSlotOnNode 将图节点收缩为紧凑的文字标签,而 RGSlotOnView 则把 RGMiniView、RGEditingNodeController 和 RGEditingReferenceLine 作为工作区叠加层加入进来。
图实例 API 在这里被大量使用。示例通过 clearGraph、addNodes 和 addLines 清空并重新加载图数据;通过 addFakeLines 连接 DOM 锚点;通过 setEditingNodes 选中整个分组;通过 updateNode 和 updateLine 更新样式变更;通过 getConnectTargetById 和 getNodesRectBox 计算带依赖关系的堆叠位置;通过 generateNewUUID 插入新分组;并在可拖拽设置窗口中通过 prepareForImageGeneration 和 restoreAfterImageGeneration 导出画布图像。
样式分两层管理。分组默认样式按组存储,并通过 applyGroupStyles() 重新应用;my-relation-graph.scss 则提供了图节点文本和按列区分主题节点变体的项目级覆盖样式。浮动编辑器本身是从相邻的 mix-layout-8 示例中引入的一个可复用标签页组件,但在这里它被有意限制为只做树布局调优,即 treeLayoutOnly={true}。
关键交互
点击任意可见节点时,会选中共享同一个 myGroupId 的整个分组。这个选中结果随后会交给 RGEditingNodeController,于是分组可以作为一个整体被移动,并在选区框上方显示删除操作。
点击空白画布会清除编辑状态。这一点很重要,因为页面把“选中”作为所有编辑行为的入口,而清除选中也就意味着关闭该分组范围内的编辑上下文。
展开或折叠某个分支会触发带依赖感知的重新布局。发生变化的分组会先重新布局,然后所有位置依赖于该分组边界的其他分组也会在之后递归重新布局。正是这种机制保证了即使某个簇变高或变矮,整个业务看板仍然保持连贯。
将鼠标悬停在阶段卡片行上时,会显示前置或后置插入热点。选择其中一个位置后,浮动面板会切换到添加分组模式,用户可以在插入前调整随机树的深度、子节点数量以及子节点生成概率,并将新分组插入到该列中的精确位置。
可拖拽窗口上的设置按钮打开的是画布级控制,而不是分组级控制。用户可以在那里切换滚轮和拖拽行为,并导出当前组合看板的截图。
关键代码片段
下面这段代码说明了该示例会基于列数据重建图,等待画布 DOM 就绪,然后在执行分组布局之前重新连接 HTML 锚点。
const applyAllColsData = async (newCols: MyColumnData[]) => {
if (newCols.length === 0) return;
await myLayout.current.loadData(newCols);
await graphInstance.sleep(300);
myLayout.current.connectElementToNode();
myLayout.current.applyAllGroupLayout();
graphInstance.zoomToFit();
};
下面这段代码展示了 HTML 阶段卡片如何通过 RGConnectTarget 暴露桥接线所需的锚点。
<RGConnectTarget
targetId={'col-item-ct-' + item.id}
junctionPoint={RGJunctionPoint.lr}
disableDrag={true}
disableDrop={true}
>
<div className={`w-1.5 h-1.5 rounded-full ${themeColor.dot}`} />
</RGConnectTarget>
下面这段代码展示了布局管理器如何把这些锚点转换成伪正交连线,从而在视觉上把每个卡片行连接到其隐藏根节点。
const fakeLine: JsonLine = {
id: 'ct-line-' + group.rootNodeId,
fromType: RGInnerConnectTargetType.CanvasPoint,
from: connectTargetId,
toType: RGInnerConnectTargetType.Node,
to: group.rootNodeId,
lineShape: RGLineShape.SimpleOrthogonal,
color: group.groupStyles.lineStyles.color || 'rgba(0,0,0,0.1)',
showEndArrow: false
};
下面这段代码展示了带依赖感知的纵向定位逻辑,它会根据另一个分组的可见边界,把当前分组堆叠到相对位置。
if (group.refs.length > 0) {
const refedGroup = this.getGroupById(group.refs[0]);
if (!group.layouted) this.layoutGroup(refedGroup, deep + 1);
const refedGroupView = this.getGroupViewInfo(refedGroup.groupId);
if (refedGroup.groupStyles.layoutOptions.layoutExpansionDirection === 'start') {
groupRootNodeXy.y = refedGroupView.minY - 60;
}
if (refedGroup.groupStyles.layoutOptions.layoutExpansionDirection === 'end') {
groupRootNodeXy.y = refedGroupView.maxY + 45;
}
}
下面这段代码展示了点击单个节点后会立刻升级为整个分组的编辑,而不是只检查该单个节点。
const onNodeClick = (node: RGNode) => {
myLayout.current.selectGroupNodes(node.data?.myGroupId);
if (editingGroupStyles.myGroupId !== node.data?.myGroupId) {
openEditGroupStylesPanel();
}
};
这个示例的独特之处
与邻近的混合布局查看型示例(如 mix-layout-2)相比,这个示例把同样的卡片锚定式组合模式进一步做成了一个创作工作区。它不止支持调整展开深度或进行被动重排,还允许用户选中整个锚定簇、调节该分组的节点、连线和树配置,删除该分组,或者在明确的行位置插入一棵新的随机树。
与 organizational-chart、mix-layout-tree 这类分组布局展示示例相比,这里的可见锚点是通过 RGSlotOnCanvas 渲染的普通 HTML 阶段卡片,而不只是图渲染节点卡片或共享根层级簇。因此,当图必须嵌入现有仪表盘或业务看板外壳时,这个示例会是更强的起点。
与 gee-node-alignment-guides 这类聚焦交互的演示相比,对齐辅助层只是更大工作流中的一个环节。这里它被放入一个具备依赖感知的看板编辑器中,这个编辑器同时还处理 DOM 锚点组合、批量样式调优、运行时分组生命周期变化,以及当分组尺寸变化时的递归重新布局。
真正少见的组合才是这个示例最值得学习的地方:RGSlotOnCanvas 阶段卡片、RGConnectTarget 锚点圆点、隐藏根节点、伪桥接线、按分组创建的树布局、由 refs 驱动的依赖定位、整组编辑、插入热点以及编辑器叠加层,全都协同工作在同一张固定画布上。正因如此,这个示例读起来更像一个业务看板图谱工作台,而不是静态的混合布局展示。
这一模式还适用于哪里
这种模式非常适合那些图结构应当从可见业务卡片延伸出来、而不是直接取代卡片的界面。产品流水线看板、平台能力地图、供应商网络、分阶段风险控制以及服务交付链,都能从“HTML 锚点卡片 + 图渲染依赖细节”这种分离方式中受益。
它也很适合混合型编辑工具,在这类工具里,用户应该操作的是一个个簇,而不是在整个画布上任意创建节点和边。团队结构分组、模块化系统包,或作品集式工作流,都可以复用这种分组级选中、标签页式重设样式以及依赖感知重新布局的模型。
最后,只要存在“需要把新的树形子结构插入到精确阶段位置”的场景,这套插入流程都很有价值。这包括新增一个供应商层级、插入一个新的流程家族、挂接一个新的解决方案包,或者在不手工重建整个场景的前提下测试不同的依赖分支。