JavaScript is required

节点插槽模板切换

这个示例保持一份中心布局的 relation-graph 数据,在运行时在多个自定义插槽模板之间切换节点主体显示。它结合 `RGSlotOnNode`、全图 `getNodes()` 与 `updateNode(...)` 对 `node.type` 的批量改写、容器 class 主题、`RGMiniView`,以及包含画布设置和图片导出的悬浮工具窗。适合作为无需重建图数据即可切换节点呈现方案的参考。

在同一张图中切换节点插槽模板

这个示例构建了什么

这个示例构建了一个居中的 relation-graph 场景,复用同一份小型人物数据集,同时在多个自定义插槽模板之间切换可见的节点主体。查看者会看到同一套拓扑分别以绿色徽章、全息资料卡、图标胶囊、黄色指标卡或圆形借阅者徽章的形式呈现,并且画布上方还有一个浮动 mini view 和一个浮动工具窗口。

这张图在结构上是只读的。主要交互是模板切换:辅助窗口中的标签页会重写每个在线节点的 type,随后图谱会重新居中并再次适配视口,使 300 x 450 资料卡这类较大的模板仍然易于阅读。这个示例最重要的启发在于,它把节点插槽定制视为一种运行时编排模式,而不是五个彼此独立的图谱示例。

数据是如何组织的

这个示例从 users01.json 中导入一份共享的 RGJsonData 数据。它使用 rootId: 'a',定义了七个节点,并通过六条连线把 abg 连接成一个轮辐式结构。

在调用 setJsonData() 之前没有任何预处理。JSON 会直接交给 relation-graph,后续插槽渲染器再读取 node.data.picnode.data.namenode.data.myicon,用于头像、标签和图标选择。数据加载完成后,运行时更新只会修改 node.type;图结构和业务字段保持不变。

在真实产品中,这同一套结构可以表示员工、客户、资产、设备或案例。自定义数据字段可以承载头像 URL、显示名称、角色图标、评分或状态元数据,以便在同一张图中应用多种可视化呈现方式。

relation-graph 是如何使用的

这个示例把布局和图机制保留在 relation-graph 内部,同时把几乎所有节点外观都放进插槽和 SCSS 中。MyGraph.tsx 配置了一个 center 布局,并显式设置 levelGaps、居中对齐、force_node_repulsionforce_line_elasticmaxLayoutTimes;与此同时,又通过 RGNodeShape.rectdefaultNodeBorderWidth: 0 让默认节点主体变为透明。这样一来,插槽渲染器就成为实际可见的节点表面。

RGHooks.useGraphInstance() 驱动整个生命周期。在挂载时,它通过 setJsonData() 加载导入的 JSON,然后调用 moveToCenter()zoomToFit()。同一个实例随后还承担图级别的 getNodes()updateNode(...) 变更、尺寸变化期间的临时画布动画,以及 CanvasSettingsPanel 中共享的导出流程。

定制逻辑被拆分到两个插槽中。RGSlotOnNode 会根据 node.type 分支,选择 NodeSlot1NodeSlot5RGSlotOnView 则挂载 RGMiniView 作为视口叠加层。在图谱外围,RGProvider 提供上下文,DraggableWindow 承载选择器与设置外壳,SimpleUIVTabs 渲染插槽标签页,而包装类 slot-style-${slotTeamplateId} 会在 SCSS 中激活特定插槽的选中态和主题覆盖。

这仍然是一个查看器示例,而不是编辑器。运行时 API 会改变表现形式和交互选项,但不会新增、删除或重新连接图元素。

关键交互

  • 选择 Slot2Slot3Slot4Slot5Random 时,会重写每个在线节点的 type,然后在短暂的动画辅助延迟后重新居中并再次适配图谱。
  • Random 模式会从同一组标签页列表中抽样,因此当抽中的值字面量就是 random 时,某些节点仍可能回退到默认的 NodeSlot1 分支。
  • 浮动窗口可以从标题栏拖动、最小化,并切换到设置叠加层,而无需卸载图谱。
  • 设置叠加层会修改在线图实例上的 wheelEventActiondragEventAction,因此同一个画布可以在滚动、缩放、选择、移动或禁用输入处理之间切换。
  • Download Image 操作会先为导出准备图谱 DOM,再借助 modern-screenshot 将其渲染为 blob 并下载,之后恢复图谱。
  • NodeSlot3 会响应 relation-graph 的 checked 标记,并添加动画渐变和自定义光环,因此选中反馈会随着当前插槽族而变化。
  • 当节点尺寸显著变大时,RGMiniView 会为较大的卡片模式提供稳定的导航辅助。

关键代码片段

这个片段说明,该示例隐藏了内置节点表面,从而让插槽渲染器成为可见主体。

const graphOptions: RGOptions = {
    debug: true,
    defaultJunctionPoint: RGJunctionPoint.border,
    defaultNodeColor: 'transparent',
    defaultNodeShape: RGNodeShape.rect,
    defaultNodeBorderWidth: 0,
    defaultLineShape: RGLineShape.StandardStraight,
    // ...
};

这个片段展示了居中布局配置,它为较大的节点主体模板画廊提供了足够的间距。

layout: {
    layoutName: 'center',
    levelGaps: [500, 500, 500],
    alignItemsX: 'center',
    alignItemsY: 'center',
    force_node_repulsion: 2,
    force_line_elastic: 0.1,
    maxLayoutTimes: Number.MAX_VALUE
}

这个片段说明,共享数据集只会加载一次,并且在任何插槽切换发生之前,视口就会先被归一化。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = graphJsonData;
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
    await changeNodeSlot(slotTeamplateId);
};

这个片段展示了图级别的变更模式:每个在线节点都会获得一个新的渲染器类型,然后在尺寸变化稳定后重新适配图谱。

const allNodes: RGNode[] = graphInstance.getNodes();
for (const node of allNodes) {
    const randomSlot = allSlotIds[Math.floor(Math.random() * allSlotIds.length)];
    graphInstance.updateNode(node, {
        type: newSlotId === 'random' ? randomSlot.value : newSlotId
    });
}
graphInstance.enableCanvasAnimation();
await graphInstance.sleep(50);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

这个片段说明,一个 RGSlotOnNode 分支可以分发到五种互不相同的节点主体,而不是只对应一个固定的自定义节点。

<RGSlotOnNode>
    {({ node, checked }: RGNodeSlotProps) => {
        switch (node.type) {
        case 'slot2': return <NodeSlot2 node={node} />;
        case 'slot3': return <NodeSlot3 node={node} checked={checked} />;
        case 'slot4': return <NodeSlot4 node={node} />;
        case 'slot5': return <NodeSlot5 node={node} />;
        default: return <NodeSlot1 node={node} />;
        }
    }}
</RGSlotOnNode>

这个片段说明,slot 3 中的选中态样式依赖 relation-graph 的 checked 状态,而不是单独的本地标记。

export const NodeSlot3: React.FC<RGNodeSlotProps> = ({ node, checked }) => {
    return (
        <div className={`my-slot-3-content ${checked ? 'my-node-animation-01 text-white' : ''}`}>
            <IconSwitcher iconName={node.data?.myicon} size={60} />
        </div>
    );
};

这个片段展示了包装类如何改变图谱外壳以及当前插槽族的选中态表现。

.slot-style-slot2 {
    .relation-graph {
        background-color: #050505;
        font-family: 'Orbitron', 'Noto Sans SC', sans-serif;
        --rg-checked-item-bg-color: rgba(244, 60, 229, 0.3);
        .rg-toolbar {
            background-color: rgba(248, 246, 246, 0.53);
            border: none;
        }
        .rg-miniview {
            background-color: #050505;
        }
    }
}

这个片段说明,导出流程使用的是 relation-graph 提供的准备与恢复 API,而不是盲目地直接截图画布。

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();

这个示例的独特之处

对比数据把这个示例放在 nodehand-drawn-stylecss-themecustom-line-style 附近,但它的关注点与这些相邻示例都不相同。它最清晰的区别在于,会在运行时对所有在线节点重写已加载图谱的 node.type,然后在由插槽驱动的尺寸变化稳定后重新居中并再次适配。

相较于 node,这里的核心启发并不是并排展示每个节点 JSON 样式技巧的目录。node-slot-list 更进一步,深入到图级别的模板切换:一张已经挂载的图可以在五种自定义节点主体以及一种混合随机模式之间切换,而无需重建数据集。

相较于 hand-drawn-style,这里可复用的思路更偏向渲染器编排,而不是单一主题皮肤。相较于 css-themecustom-line-style,这个示例的重点也不主要是给内置图表面重新着色或变更连线预设。它真正关注的是在保持同一份居中数据集、minimap 和浮动工作区外壳不变的前提下,直接切换节点标记本身。

它最强的特征组合是:透明的矩形基础节点、针对 center 布局的调优、由包装类驱动的选中态主题,以及基于同一份人物数据集进行的图级别 updateNode(...) 切换。这个组合使得该示例更像一个运行时节点模板画廊,而不是静态的自定义节点演示。

这种模式还适用于哪里

这种模式很适合那些需要在同一套关系上提供多种节点呈现方式的产品。例如在员工关系图中,可以在紧凑徽章和资料卡之间切换;在风险图中,可以在图标状态和评分卡之间切换;在基础设施视图中,则可以在服务图标和详情卡片之间切换。

它也适用于设计评审和白标工作流。团队可以保留一个已经准备好的图实例,对比不同的节点密度或品牌皮肤,并在最终选定生产模板之前,验证 minimap、选中态样式和导出效果。

同一套结构也可以作为插槽系统的回归测试板。由于拓扑保持不变而渲染器发生切换,因此它是检验新节点模板是否仍然适配布局、选中态处理和导出链路的一种实用途径。