JavaScript is required

连通子图布局与主题切换

这个示例构建了一个全屏图谱工作台,在同一块画布上挂载两组彼此断开的 relation-graph 数据集。用户点击任意节点后,会选中该节点所属的整个连通分量,然后通过一个跟随选区移动的悬浮工具栏,仅切换这个连通分量的主题或布局。图谱覆盖层中始终显示一个 minimap,同时还有一个可拖拽的辅助窗口,用于暴露画布设置和导出图片功能。

为单个连通分量切换主题和布局

这个示例构建了什么

这个示例构建了一个全屏图谱工作台,在同一块画布上挂载两组彼此断开的 relation-graph 数据集。用户点击任意节点后,会选中该节点所属的整个连通分量,然后通过一个跟随选区移动的悬浮工具栏,仅切换这个连通分量的主题或布局。图谱覆盖层中始终显示一个 minimap,同时还有一个可拖拽的辅助窗口,用于暴露画布设置和导出图片功能。

这个示例的关键结果是:在混合画布中进行局部编辑。这个 demo 不会一次性重设整个图谱的样式或重新布局。它会保持另一个断开的网络孤岛不变,只对当前选中的连通分量进行重新着色、重设形状、重排连线和重新布置。

数据是如何组织的

MyGraph.tsx 内联定义了两个 RGJsonData 对象:network1JsonDatanetwork2JsonData。每个对象都包含自己的 rootIdnodeslines。这两组数据通过分别调用 addNodes()addLines() 推入同一个图实例,因此画布初始状态就是一个包含两个断开子网络的统一视图。

首次布局前并没有进行大量预处理。真正的运行时预处理发生在选中之后:被点击的节点 id 会存入 checkedNodeId,然后 getNetworkNodesByNode(...) 会从这个种子节点扩展到整个连通分量,getLinesBetweenNodes(...) 则会导出与之匹配的内部边,以便后续批量更新。主题元数据存放在 exampleThemes 中,其中每一项都把显示标签映射到一个条目 className 和一个色板颜色。布局元数据存放在 exampleLayouts 中,其中每个预设都打包了 layoutOptions、默认的节点与连线覆盖项,以及在 io-tree 变体下可选的 afterLayoutCallback

在真实应用中,同样的结构可以表示同一张依赖图中的多个服务集群、调查工作台中的多个账户分组、同一块白板上的多个工作流片段,或多个需要局部调优展示效果而不是重建整张画布的组织架构孤岛。

relation-graph 是如何使用的

这个 demo 包裹在 RGProvider 中,MyGraph.tsx 使用 RGHooks.useGraphInstance() 作为所有图谱行为的控制面。初始图谱配置会关闭调试模式,将矩形节点和灰色 2px 连线设为基础样式,并让画布以树形布局启动。组件挂载后,图实例会接收这两组内联数据,执行 doLayout(),然后将组合后的视图重新居中并缩放到合适范围。

选中状态通过 relation-graph 实例 API 管理,而不是通过单独的数据模型维护。onNodeClick 会存储被点击的 id,getNodeById(...) 会解析实时节点对象,getNetworkNodesByNode(...) 会找到对应的连通分量,setEditingNodes(...) 则把这个分量传给 RGEditingNodeController。由于控制器和自定义工具栏都渲染在 RGSlotOnView 内,这些控件会附着在选中的连通分量旁边,而不是固定在侧边栏中。RGMiniView 也挂载在同一个 slot 里,因此编辑过程中导航能力仍然可用。

主题切换通过对已加载图元素重新分配 class 来实现。处理函数会收集选中连通分量中的节点和连线,然后使用预设里的 itemsClassName 调用 updateNode(...)updateLine(...)。SCSS 文件定义了 .my-theme-1.my-theme-7,这些选择器会重设节点填充、边框、选中高亮、连线描边、连线文字以及线标签块。因此,主题变化只影响表现层,不改变图结构。

布局切换的处理更复杂。处理函数会先将节点几何和连线路由重置为已知默认值,再根据所选预设创建一个运行时布局对象,并调用 placeNodes(groupNodes, checkedNode),这样只有当前选中的连通分量会被重新排布。IO-tree 预设会挂接 afterLayoutCallback 函数,在布局完成后通过 getLinksBetweenNodes(...) 检查连接,并重写拐点。随后图实例会使用 enableCanvasAnimation()zoomToFit(groupNodes)getNodesRectBox(...)getOptions()setCanvasCenter(...),以保证被编辑的连通分量在工具栏展开时仍然保持可见。

辅助窗口增加了第二层运行时图谱控制。它的设置面板通过 RGHooks.useGraphStore() 读取当前的拖拽和滚轮模式,通过 setOptions(...) 更新这些模式,并通过 prepareForImageGeneration()domToImageByModernScreenshot(...)restoreAfterImageGeneration() 导出当前画布。

关键交互

  • 点击节点不会立即改变样式。它会先存储 checkedNodeId,随后由一个后续 effect 解析完整的连通分量,并为这一组激活编辑控制器。
  • 点击 Theme 按钮会打开一个色板弹层。选择某个色板后,只会重写选中连通分量内部节点和连线的 className 值。
  • 点击 Layout 按钮会打开一个预设图库,其中包含远程预览缩略图,以及一个显示当前选中节点 id 的徽标。选择某个预设后,只会改变选中连通分量的节点几何、连接器样式和布局。
  • IO-tree 预设在布局完成后还会多一步交互处理:它会重写连接器锚点和偏移量,使改道后的连线符合所选 io-tree 方向。
  • 点击空白画布会清除选中状态,清空 editing-node 选区,并关闭当前打开的弹层。
  • 悬浮辅助窗口可以被拖动、最小化、切换到设置面板,并用于修改滚轮与拖拽行为或下载图片。
  • 连线点击事件已经接入,但它们只会输出到控制台,不属于编辑工作流的一部分。

关键代码片段

下面这个片段展示了:在开始任何按连通分量进行的编辑之前,页面先把两组彼此断开的数据集合并到同一个实时图实例中。

graphInstance.addNodes(network1JsonData.nodes);
graphInstance.addLines(network1JsonData.lines);
graphInstance.addNodes(network2JsonData.nodes);
graphInstance.addLines(network2JsonData.lines);
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();

下面这个片段展示了:选中会先从一个被勾选的节点扩展到它所属的整个连通分量,然后才更新覆盖层控制器。

const checkedNode = graphInstance.getNodeById(checkedNodeId);
if (checkedNode) {
    const groupNodes = getGroupNodes(checkedNode);
    graphInstance.setEditingNodes(groupNodes);
}

下面这个片段展示了:主题切换是通过对选中节点及其内部连线批量重新分配 class 来实现的。

const groupNodes = getGroupNodes(checkedNode);
const groupLines = graphInstance.getLinesBetweenNodes(groupNodes);
groupNodes.forEach(n => {
    graphInstance.updateNode(n, { className: themeInfo.themeOptions.itemsClassName });
});
groupLines.forEach(l => {
    graphInstance.updateLine(l, { className: themeInfo.themeOptions.itemsClassName });
});

下面这个片段展示了:io-tree 布局预设不只是一个 layoutName,它还打包了几何默认值以及一个布局后的路由回调。

{
    label: 'IO-Tree-1',
    layoutOptions: {
        layoutName: 'io-tree',
        from: 'left',
        treeNodeGapH: 50,
        treeNodeGapV: 10
    },
    defaultOptions: {
        node: { nodeShape: RGNodeShape.circle, width: 50, height: 50 },
        line: { lineShape: RGLineShape.StandardOrthogonal, fromJunctionPoint: RGJunctionPoint.right, toJunctionPoint: RGJunctionPoint.top }
    },
    afterLayoutCallback: (graphInstance, nodes) => { updateLinesForIOTree(graphInstance, nodes, 'left') }
}

下面这个片段展示了:重新布局之后会跟着进行一次视口调整,使被编辑的连通分量停留在工具栏旁边,而不是简单地重新适配整张画布。

graphInstance.enableCanvasAnimation();
graphInstance.zoomToFit(groupNodes);
const nodesRect = graphInstance.getNodesRectBox(groupNodes);
const nodesLeftCenterOnCanvas = {
    x: nodesRect.minX,
    y: nodesRect.minY + nodesRect.height / 2
}
const options = graphInstance.getOptions();
const scale = options.canvasZoom / 100;
graphInstance.setCanvasCenter(nodesLeftCenterOnCanvas.x + 100 / scale, nodesLeftCenterOnCanvas.y);

这个示例的独特之处

switch-network-layout 相比,这个示例更强调展示层的变更,而不只是局部重新布局。两个 demo 都使用节点点击选中、连通分量发现和 RGEditingNodeController,但这个示例允许同一个被选中的连通分量同时经历主题重分配和基于预设的重新布局。

switch-layout-pro 相比,关键差异在于作用范围。后者为整张图谱使用了类似的主题和布局工具集,而这个示例则只把同样的样式目录应用到被点击节点周围的连通分量上。双网络画布这一点很重要,因为它证明未激活的孤岛可以保持挂载且完全不受影响。

与基于 Dagre 的相邻示例相比,这不是一个外部布局集成示例。它的独特组合在于:内置预设切换可覆盖 center、tree、folder、circle 和 io-tree 布局;基于 class 的运行时主题切换;IO-tree 路由回调;跟随选区移动的覆盖层;以及让当前活跃连通分量停留在控件旁边的视口偏移。这种组合使它更适合作为产品实现的起点,尤其是在需要在更大的共享画布中,对局部子图进行重新着色和重新布局的场景下。

这种模式还能应用到哪里

  • 依赖分析工具可以允许用户只重设某一个服务集群的样式或布局,而不干扰同一张地图上其他断开的系统。
  • 调查工作台可以让分析人员点击一个种子实体,隔离其连通邻域,并为这一邻域切换视觉预设以便审查或导出。
  • 流程图或组织架构工具可以在同一块画布上保留多个图谱孤岛,同时允许按孤岛粒度调优展示效果,而不是强制使用一种全局布局模式。
  • 图谱设计系统可以把布局族、节点几何、连接器样式和条目 class 打包成可复用的按连通分量生效的预设,而不是把这些设置当作互不相关的开关。