图谱主题与布局预设切换
这个示例在同一份 relation-graph 数据挂载状态下,让用户通过画布内工具栏同时切换主题预设和布局预设。它展示了如何批量更新节点与连线 class、应用更丰富的布局预设对象,并将导出与画布控制保留在同一查看器壳层中。
在同一张图中切换主题和布局预设
这个示例构建了什么
这个示例围绕一个固定的 relation-graph 数据集,构建了一个全屏图谱展示工作台。画布初始为树形布局,随后用户可以打开调色板弹出层,应用七种项目级主题之一,或打开网络弹出层,在 center、tree、folder、circle 和 io-tree 布局预设之间切换。一个缩略图会始终显示在图谱叠层中,另有一个可拖拽的辅助窗口用于提供画布设置和图片导出功能。
真正重要的结果并不是示例数据本身。重点在于:一个已经挂载的图实例在内存中保持同一组节点和连线不变,同时在运行时切换它的视觉外观、节点几何形状、连接线样式以及布局族。
数据如何组织
数据在 initializeGraph() 内以内联方式声明为一个符合 RGJsonData 形状的对象,其中包含 rootId、nodes 和 lines。节点使用了诸如 g2-a、g2-c 和 g2-c1 这样的简单 ID,连线则定义了它们之间的父子关系。这使得数据集易于阅读,同时仍然覆盖了向上和向下的分支,这对于在同一拓扑上比较不同布局族很有帮助。
这个示例没有调用 setJsonData()。相反,它从该字面量对象中取出 nodes 和 lines 数组,并分别通过 addNodes() 和 addLines() 插入。第一次执行 doLayout() 后,它再通过 moveToCenter() 和 zoomToFit() 对视口进行归一化。在真实应用中,同样的结构可以表示组织架构图、服务依赖图、审批链,或是紧凑型知识图谱,在这些场景下业务数据保持稳定,而展示模式可以变化。
relation-graph 的使用方式
index.tsx 使用 RGProvider 包裹这个 demo,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 作为所有运行时行为的控制面。初始图选项会关闭调试模式,设置默认节点形状为矩形,定义基础连线颜色和宽度,隐藏内置工具栏,并以树形布局启动。这个基础状态很重要,因为该示例会在应用选中预设之前,反复把当前节点和连线重置为已知默认值。
图始终挂载在 <RelationGraph> 中,所有变化都通过实例 API 应用。初始加载使用 addNodes()、addLines()、doLayout()、moveToCenter() 和 zoomToFit()。主题切换使用 getNodes()、getLines()、updateNode() 和 updateLine() 来重写现有项目上的 className 值。布局切换使用 updateOptions() 替换当前布局对象,使用 sleep(100) 等待节点尺寸渲染完成,随后调用 doLayout() 重新计算位置,并为 io-tree 路由执行可选的布局后回调,最后再通过 zoomToFit() 重新适配结果。
叠层 UI 通过 RGSlotOnView 构建,而不是使用默认工具栏。在这个插槽内部,demo 挂载了 RGMiniView 和 RGMiniToolBar,然后在工具栏中放入两个自定义选择器组件。MyThemeSelect 暴露主题目录。MyLayoutSelect 暴露布局目录,并为每张预设卡片使用远程预览 PNG。共享的 DraggableWindow 组件增加了一个浮动说明框,并且可以打开设置面板。该面板使用 RGHooks.useGraphStore() 读取当前拖拽与滚轮模式,并使用 setOptions() 更新它们。
样式通过 my-relation-graph.scss 中的 SCSS 覆盖实现。这个 demo 并不只是切换外层包装器主题。相反,每次选择一个主题时,都会把类似 my-theme-1 或 my-theme-7 这样的 class 分配给实时节点和连线项目,然后由 SCSS 规则重新着色节点背景、边框、文本、连线描边、标签背景以及选中状态的光晕。这个示例不是图编辑器:它不会在运行时增删结构。唯一与编辑相关的调用是在点击画布时执行 setEditingNodes([]),以便干净地关闭任何活动中的 UI 状态。
关键交互
- 点击调色板按钮会打开一个主题卡片弹出层。选择某张卡片后,会立即重写当前每个节点和连线的
className,因此图谱可以在不重新加载数据的情况下切换外观。 - 点击网络按钮会打开一个布局卡片网格。每张卡片代表一个预设,该预设把布局选项与节点、连线默认值打包在一起;选择后会在已经加载的图上触发完整的重新布局流程。
- 布局选择处理器还会在开始重新布局之前,把选中的布局标签写入剪贴板。代码直接这样做了,尽管 UI 并没有解释这一行为。
- 点击空白画布会清除已勾选项目、清空编辑节点列表,并关闭当前处于激活状态的弹出层。
- 辅助窗口可以拖拽、最小化,并展开为设置面板。该面板会修改滚轮行为、修改画布拖拽行为,并通过在导出前准备图谱、将 DOM 栅格化、再在导出后恢复图状态的方式下载图片。
- 节点和连线点击处理器是存在的,但它们只是在控制台输出日志,并不属于主要的面向用户工作流。
关键代码片段
下面的片段展示了该示例如何把一个小型图数据集以内联方式保留,并仅一次性加载到实时实例中。
const network2JsonData: RGJsonData = {
rootId: 'g2-a',
nodes: [
{ id: 'g2-a', text: 'Root' }, { id: 'g2-R-b', text: 'R-b' },
// ...
],
lines: [
{ id: 'g2-l-1', from: 'g2-R-b', to: 'g2-a' },
// ...
]
};
graphInstance.addNodes(network2JsonData.nodes);
graphInstance.addLines(network2JsonData.lines);
await graphInstance.doLayout();
下面的片段展示了主题切换如何作为对已经渲染出的图项目进行批量更新,而不是重新构建数据集。
const onChangeNextworkStyles = (themeLabel: string) => {
const themeInfo = exampleThemes.find(n => n.label === themeLabel);
if (!themeInfo) return;
const groupNodes = graphInstance.getNodes();
const groupLines = graphInstance.getLines();
groupNodes.forEach(n => { graphInstance.updateNode(n, { className: themeInfo.themeOptions.itemsClassName }); });
groupLines.forEach(l => { graphInstance.updateLine(l, { className: themeInfo.themeOptions.itemsClassName }); });
};
下面的片段展示了一个布局预设为何比单纯的 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')
}
}
下面的片段展示了如何通过 RGSlotOnView 使用缩略图和自定义工具栏控件扩展图视图。
<RGSlotOnView>
<RGMiniView />
<RGMiniToolBar>
<MyThemeSelect
actived={acitveButton === 'theme'}
onClickButton={() => { setAcitveButton('theme'); }}
onClickTheme={(clickedLabel) => {
onChangeNextworkStyles(clickedLabel);
}} />
<MyLayoutSelect
actived={acitveButton === 'layout'}
// ...
/>
</RGMiniToolBar>
</RGSlotOnView>
这个示例的独特之处
与附近的示例相比,这个 demo 的突出点在于:它把两类运行时切换集中在同一个已挂载的图上完成,分别是项目级主题切换和多布局族切换。对比分析认为 css-theme 是最接近的主题相关邻居,但那个示例保持了一个稳定布局,重点集中在皮肤变化上。在这里,主题切换只是这个示例要表达的一半内容。
它与 switch-layout 的最大区别在于预设的丰富度。这个示例并不只是轮换不同布局对象。每个预设还可以同时改变节点形状、节点尺寸、连线形状、连接点位置,并且对于 io-tree 变体,还可以附加一个 afterLayoutCallback,在布局完成后重写连接器锚点。因此,当产品语境中的“布局预设”实际指的是一个协调好的视觉包,而不是单独换一个新的 layoutName 时,这个示例是更好的起点。
与 io-tree-layout 的比较同样有价值。后者是一个更聚焦的路由示例,而这个示例则把 io-tree 路由作为更大画廊中的一个高级选项。这里少见的地方在于它的组合方式:一个紧凑的迷你工具栏、七个命名主题加上 None、一个覆盖 center、tree、folder、circle 和 io-tree 的布局目录、实时的节点与连线归一化,以及带有缩略图的查看器外壳。辅助窗口和导出工具本身并不独特,但在这个示例中,它们共同支撑了一个更完整的展示对比工作流。
这种模式还适用于哪里
- 产品可以为同一份关系数据提供“视图预设”,例如分析师模式、高管模式、紧凑模式和路由聚焦模式,而无需重建图谱。
- 内部工具可以让用户在选定默认展示方式之前,先对组织架构图、依赖图或血缘图在不同布局族下进行比较。
- 面向图应用的设计系统可以把布局、节点几何形状和连接线样式打包为可复用预设,而不是把它们视为彼此无关的选项。
- 报表工作流可以把同一个实时图同时用于交互式检查和图片导出,并通过共享叠层面板暴露画布设置与捕获逻辑。