图谱 CSS 主题切换
这个示例保持一棵左到右 relation-graph 树不变,并在运行时切换 8 套 CSS 皮肤。它结合容器 class 主题、图标插槽节点、带主题样式的 `RGMiniView`,以及 `getNodes()` + `updateNode(...)` 批量更新,确保小地图与当前强调色同步。共享悬浮面板还提供滚轮模式、拖拽模式和图片导出工具。
在同一个关系图树上切换八种 CSS 主题
这个示例构建了什么
这个示例渲染了一个从左到右的层级结构,节点采用圆形图标样式,并允许用户在运行时将同一张图切换为八种不同的视觉主题。图数据、布局以及 RelationGraph 实例本身保持不变,而外层包装类会从 my-theme-1 切换到 my-theme-8,因此地图背景、节点表面、连线颜色、选中状态、工具栏外观以及缩略图都会一起变化。
页面是一个全高查看器,画布上方悬浮着一个工具窗口。用户可以拖动这个窗口、最小化它、打开设置浮层、选择主题色块、修改滚轮和拖拽行为,并将当前图导出为图片。
这个示例的重点不在自定义布局逻辑,而在一种面向整张图的 CSS 换肤模式;其中还包含一个重要的运行时更新步骤,用来确保 RGMiniView 与当前主题保持颜色同步。
数据是如何组织的
图数据在 initializeGraph() 内部以内联方式声明为一个 RGJsonData 对象。它使用一个 rootId、一个扁平的 nodes 数组和一个扁平的 lines 数组。每个节点都把自己的图标键存放在 node.data.icon 中,而部分连线记录还携带各自的渲染覆盖项,例如 useTextOnPath、lineShape、fromJunctionPoint 和 toJunctionPoint。
在布局之前没有预处理步骤。代码会直接把这份 JSON 交给 graphInstance.setJsonData(),然后通过 moveToCenter() 和 zoomToFit() 让视口重新居中并适配显示。
在真实产品中,同样的数据结构可以表示能力树、服务层级、产品分类、站点地图或工作流概览。data.icon 字段可以映射分类图标或状态图标,而针对单条连线的覆盖配置则可以在不改变整体布局策略的前提下,突出展示特殊的关系类型。
relation-graph 是如何使用的
index.tsx 使用 RGProvider 包裹整个示例,因此图组件和共享的辅助窗口都能解析到同一个 relation-graph 上下文。在 MyGraph.tsx 中,RelationGraph 只渲染一次,并采用一个内置的树形布局,从左向右展开。布局间距被刻意设置得较大,其中 treeNodeGapH: 310、treeNodeGapV: 70,这样图标节点和带主题样式的连线标签就有足够的展示空间。
图配置被设置为将视觉控制权交给 CSS。defaultLineColor、defaultNodeColor 和 defaultNodeBorderColor 都设为 auto,同时 defaultNodeShape 为圆形,默认连接器使用 RGLineShape.StandardCurve 与 RGJunctionPoint.lr。这意味着渲染器仍然负责图的几何计算,但真正可见的配色来自各个主题对应的 SCSS 规则。
这个示例在两个地方使用了 RGHooks.useGraphInstance()。在 MyGraph 中,它负责加载 JSON、让图重新居中、适配缩放、通过 getNodes() 读取当前节点,并在每次主题切换后调用 updateNode(...)。在 CanvasSettingsPanel 中,它通过 setOptions(...) 更新运行时配置,并使用 prepareForImageGeneration()、getOptions() 和 restoreAfterImageGeneration() 完成图片导出流程。这里也使用了 RGHooks.useGraphStore(),从而让设置面板能够反映当前的滚轮模式和拖拽模式。
有两个插槽对查看器进行了定制。RGSlotOnNode 用 node.data.icon 选择的大型 Lucide 图标替换了默认节点内容,RGSlotOnView 则在画布上方挂载了 RGMiniView。当前激活的样式来自 my-relation-graph.scss,其中每个 .my-theme-* 包装类都会作用于 relation-graph 的内置表面,例如 .rg-map、.rg-node、.rg-line、.rg-toolbar 和 .rg-miniview。
这个示例仍然以查看为主。它没有为节点或连线添加编辑工具;这些运行时 API 主要用于加载、换肤、画布行为控制和导出。
关键交互
核心交互是主题切换。在 ThemeSelect 中点击某个色块会修改 themeId,这不仅会切换包装类,还会更新每个节点的 color 属性,使缩略图能够使用与主图相同的强调色。
悬浮工具窗口可以被拖到新的位置,也可以最小化以节省空间。这样既能保持图画布清晰,又能保留主题选择器的可访问性。
设置按钮会打开共享的 CanvasSettingsPanel。在这里,用户可以将鼠标滚轮切换为滚动、缩放或禁用;将画布拖拽切换为框选、移动或禁用;并把当前带主题样式的图下载为图片。
SCSS 还定义了节点和连线在选中状态下的样式。这个示例没有额外添加自定义选择处理器,但当 relation-graph 应用其选中类时,这些视觉效果会继承当前激活的主题。
关键代码片段
下面这段配置展示了该示例如何把布局和几何计算交给 relation-graph,同时把可见颜色的决定权交给 CSS。
const graphOptions: RGOptions = {
debug: false,
defaultLineColor: 'auto',
defaultNodeColor: 'auto',
defaultNodeBorderColor: 'auto',
defaultNodeShape: RGNodeShape.circle,
defaultPolyLineRadius: 10,
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.lr,
layout: {
layoutName: 'tree',
from: 'left',
treeNodeGapH: 310,
treeNodeGapV: 70
}
};
下面这段数据片段说明,该示例使用一份内联的树形数据集,在节点上存储图标键,并允许单条连线覆盖默认连接器样式。
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'a', data: { icon: 'align_bottom' } },
{ id: 'b', text: 'b', data: { icon: 'basketball' } },
// ...
],
lines: [
{ from: 'b', to: 'b1', text: 'useTextOnPath: true', useTextOnPath: true },
{
from: 'c', to: 'c1', text: 'lineShape:RGLineShape.StandardStraight',
lineShape: RGLineShape.StandardStraight, fromJunctionPoint: RGJunctionPoint.border, toJunctionPoint: RGJunctionPoint.border
}
]
};
下面这个主题更新函数证明,缩略图同步并不只是依赖 CSS;它会通过图实例显式重写节点颜色。
const setTheme = (id: string, mainColor: string) => {
setThemeId(id);
const nodes = graphInstance.getNodes();
nodes.forEach(node => {
graphInstance.updateNode(node, {
color: mainColor
});
});
};
下面这段渲染代码展示了两个实时扩展点:一个负责驱动主题选择的包装类,以及两个用于替换节点内容和添加缩略图的插槽。
<div className={`my-theme-${themeId}`} style={{ height: '100vh', width: '100%', position: 'relative' }}>
<DraggableWindow>
<div className="c-theme-desc">Line style when selected</div>
<div className="c-option-name">Switch Theme:</div>
<ThemeSelect themeId={themeId} setTheme={setTheme} />
</DraggableWindow>
<RelationGraph options={graphOptions}>
<RGSlotOnNode>{/* icon slot */}</RGSlotOnNode>
<RGSlotOnView><RGMiniView /></RGSlotOnView>
</RelationGraph>
</div>
下面这段 SCSS 片段展示了某个主题包装类如何直接作用于 relation-graph 的内置表面,而不是替换渲染器本身。
.my-theme-1 {
.relation-graph {
.rg-map {
background-color: rgba(91, 5, 241, 0.1);
}
.rg-node-peel {
.rg-node {
background-color: rgba(91, 5, 241, 0.61);
border: 2px solid rgba(91, 5, 241, 0.3);
color: #ffffff;
}
}
}
}
下面这个导出辅助函数展示了共享设置面板如何在不修改图数据的情况下,捕获当前带主题样式的图。
const downloadImage = async () => {
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
if (imageBlob) {
downloadBlob(imageBlob, 'my-image-name');
}
await graphInstance.restoreAfterImageGeneration();
};
这个示例的独特之处
对比数据将这个示例放在 canvas-bg2、custom-line-style、node-style3、use-dagre-layout 和 generate-image-with-background 附近,但它关注的范围比这些相邻示例中的任何一个都更广。它最鲜明的特征是:保持同一棵从左到右的 relation-graph 树不变,仅通过切换八套 CSS 皮肤,就能改变其外观,而无需改动数据、布局或图实例。
与 canvas-bg2 相比,这个示例把“图主题画廊”的思路推进得更远。它保留了同样的包装类思路,但增加了更多主题变体、圆形图标插槽节点、带主题的缩略图,以及一段显式的 getNodes() 加 updateNode(...) 更新流程,从而避免缩略图与当前选中的强调色失去同步。
与 custom-line-style 相比,这里的可复用经验并不是连线家族的设计,而是整张图各类表面的主题协同。节点、连线、工具栏外观、选中状态以及缩略图表面都会在一次主题切换下同步变化。
与 node-style3 相比,自定义节点插槽只是配套基础设施,而不是主体内容。与 use-dagre-layout 和 generate-image-with-background 相比,悬浮辅助外壳、运行时模式切换以及图片导出虽然都具备,但属于次要内容;真正持续可复用的经验,是对一个实时图查看器进行品牌化的运行时换肤。
这个模式还能用在哪里
这种模式适用于白标产品或多租户产品,即同一套图实现需要根据不同客户或工作空间切换不同的品牌皮肤。它也适用于需要多种展示模式的内部仪表盘,例如运维视图、按环境区分的控制台,或者明暗等不同显示预设。
对于那些要求辅助表面与主画布视觉保持一致、以展示为导向的图查看器,这种模式同样很有价值。典型场景包括演示环境、高管仪表盘、自助终端显示以及总览屏幕,在这些场景里,缩略图、工具栏和选中状态都需要继承与主图一致的主题。