[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:force-layout-icon-button-nodes":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 力导向布局图标按钮节点\n\n## 这个示例构建了什么\n\n这个示例在一个力导向 `relation-graph` 之上构建了一个以样式表现为重点的关系查看器。画布铺满整个屏幕并使用芥末黄色背景，连线是半透明的白色直线，而所有可见节点都被替换成同一种分层图标按钮样式，而不是使用库默认的标签主体。\n\n用户可以直接查看图谱，在悬停或聚焦某个节点时显示其标签，打开一个悬浮设置面板，切换滚轮和拖拽行为，并将画布导出为图片。这个示例的重点不是图编辑，而是展示如何通过一个可复用的节点插槽，把一组通用图数据集转换成一致且具有产品感的图标界面。\n\n## 数据是如何组织的\n\n图数据以内联方式声明为一个 `RGJsonData` 对象。它使用简单的 `rootId`、扁平的 `nodes` 数组，以及带有 `from` 和 `to` 引用的扁平 `lines` 数组。每个节点还携带 `data.icon`，这是本示例中最关键的元数据，因为它同时驱动渲染出的图标和对应的 CSS 修饰类。\n\n在调用 `setJsonData(...)` 之前没有预处理步骤。示例会直接加载这份字面量数据集，然后让图居中并自适应显示。在真实应用里，同样的数据形态可以表示服务分类、设备分组、产品家族、功能集群，或任何需要让每种节点类型都拥有统一品牌化视觉皮肤的实体网络。\n\n## relation-graph 是如何使用的\n\n`RGProvider` 包裹整个示例，而 `RGHooks.useGraphInstance()` 提供实时的图实例。本地的 `graphOptions` 会把布局切换为 `force`，把 `maxLayoutTimes` 提高到 `80000`，让默认节点外观变为透明，保留默认的矩形节点形状，设置背景色，并把内置工具栏水平放置在右下角。\n\n关键的渲染钩子是 `RGSlotOnNode`。这个示例没有混用多个插槽分支，而是为每个节点统一使用一个插槽模板。这个模板会读取 `node.data.icon`，把它拼接进按钮类名，并将同一个值传给 `MyNodeIcon`，由其中的 Lucide 图标映射来选择具体字形。最终效果是通过一个渲染器而不是许多节点专用组件，实现由元数据驱动的主题化渲染。\n\n外围 UI 来自共享辅助组件。`DraggableWindow` 提供悬浮说明外壳、最小化行为和设置覆盖层。在这个辅助组件内部，`CanvasSettingsPanel` 会从 store 读取图状态，通过 `setOptions(...)` 更新运行时选项，为图片生成准备图实例，捕获画布 DOM，然后再恢复图状态。随后通过 SCSS 覆盖样式移除内置节点边框、弱化工具栏外观，并定义分层图标按钮的视觉效果和悬停动效。\n\n## 关键交互\n\n最显眼的交互发生在节点本身：悬停或通过键盘聚焦时，后层面板会旋转，前层面板会在 3D 空间中向前推出，而图标下方的文本标签会淡入显示。正是这个交互让整张图更像一个基于按钮的查看器，而不是静态网络图。\n\n示例还绑定了 `onNodeClick` 和 `onLineClick`，但这些处理函数只是在控制台中记录 id。它们是用于观察的钩子，不是会改变状态的功能。悬浮辅助窗口的功能性更强：它可以被拖动、最小化、展开为设置覆盖层，可用来把滚轮行为切换为滚动、缩放或无，也可用来把画布拖拽行为切换为框选、移动或无，还可以用来下载图的图片。\n\n## 关键代码片段\n\n下面这段展示了核心插槽渲染器：一个节点模板同时使用 `node.data.icon` 来控制主题样式和图标选择。\n\n```tsx\nconst CustomNodeComponent: React.FC\u003CRGNodeSlotProps> = ({ node }) => {\n    return (\n        \u003Cdiv>\n            \u003Cbutton className={`icon-btn icon-btn--${node.data?.icon}`} type=\"button\">\n                \u003Cspan className=\"icon-btn__back\">\u003C/span>\n                \u003Cspan className=\"icon-btn__front\">\n                    \u003CMyNodeIcon color=\"#ffffff\" size=\"30px\" name={node.data?.icon} />\n                \u003C/span>\n                \u003Cspan className=\"icon-btn__label\">{node.text}\u003C/span>\n            \u003C/button>\n        \u003C/div>\n    );\n};\n```\n\n下面这段展示了在任何自定义插槽渲染发生之前，如何在库层面配置整张图的视觉外观。\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: true,\n    defaultLineColor: 'rgba(255, 255, 255, 0.6)',\n    defaultNodeColor: 'transparent',\n    defaultNodeShape: RGNodeShape.rect,\n    backgroundColor: '#d2a904',\n    toolBarDirection: 'h',\n    toolBarPositionH: 'right',\n    toolBarPositionV: 'bottom',\n    defaultLineShape: RGLineShape.StandardStraight,\n    layout: {\n        layoutName: 'force',\n        maxLayoutTimes: 80000\n    }\n};\n```\n\n下面这段展示了该示例如何保持简单的数据模型，并将 `data.icon` 作为可复用的展示元数据。\n\n```tsx\nconst myJsonData: RGJsonData = {\n    rootId: 'a',\n    nodes: [\n        { id: 'a', text: 'a', data: { icon: 'football' } },\n        { id: 'b', text: 'b', data: { icon: 'fries' } },\n        { id: 'b1', text: 'b1', data: { icon: 'delivery_truck' } },\n        { id: 'b1-1', text: 'b1-1', data: { icon: 'burger' } }\n        // ...\n    ],\n    lines: [\n        { from: 'a', to: 'b', text: '' },\n        { from: 'b', to: 'b1', text: '' }\n        // ...\n    ]\n};\n```\n\n下面这段展示了组件挂载时的图初始化流程。\n\n```tsx\nif (graphInstance) {\n    console.log('Instance ID:', graphInstance.options.instanceId);\n\n    await graphInstance.setJsonData(myJsonData);\n    graphInstance.moveToCenter();\n    graphInstance.zoomToFit();\n}\n```\n\n下面这段展示了如何通过 SCSS 规则实现悬停时显示标签和空间纵深效果。\n\n```scss\n.icon-btn:focus-visible .icon-btn__back, .icon-btn:hover .icon-btn__back {\n    transform: rotate(22.5deg)\n}\n\n.icon-btn:focus-visible .icon-btn__front, .icon-btn:hover .icon-btn__front {\n    transform: translateZ(3em) rotateX(20deg) rotateY(20deg)\n}\n\n.icon-btn:focus-visible .icon-btn__label, .icon-btn:hover .icon-btn__label {\n    opacity: 1;\n    transform: translateY(20%)\n}\n```\n\n下面这段展示了共享运行时控件，它们可以切换画布行为并触发图片导出。\n\n```tsx\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={wheelMode}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\n## 这个示例的独特之处\n\n根据对比数据，这个示例的突出点不在于引入了很多不同的节点模板，而在于它围绕一种可重复使用的模式保持了足够克制。与 `node-style4` 和 `node-style` 相比，它坚持使用同一个图标按钮家族，并让 `node.data.icon` 控制差异，而不是分支出多个互不相关的插槽设计，或固定不变的 chip 主题。\n\n与 `node-style2` 相比，它把相似的查看器外壳从基于内置树节点的 CSS 微调，转向了在力导向布局中通过 `RGSlotOnNode` 完整替换节点主体。与更宽泛的 `node` 示例相比，它是一个范围更窄、也更偏产品化的参考：单一的元数据驱动插槽家族、单一的芥末色展示画布、仅在悬停时显示的标签，以及按图标区分的渐变后层面板，而不是一整套样式技巧目录。\n\n共享的悬浮辅助窗口、运行时设置和图片导出在这里当然有用，但它们并不是最能区分这个示例的主张，因为对比数据表明这些工具在附近的示例中也会出现。更稳妥的差异点，是把元数据驱动的图标映射、图标专属渐变、透明化的图谱外观，以及带有 3D 感的悬停动效，组合进同一个连贯的节点渲染器里。\n\n## 这种模式还能应用到哪里\n\n这种模式非常适合用于那些节点需要少量、但足够容易识别的视觉身份，同时又不希望整张图演变成完整编辑器的查看器。例如服务地图中，不同服务分类各自使用独立图标样式；产品导航图中，每个产品家族使用带品牌感的 chip；餐饮或零售目录中，商品分组需要紧凑的视觉标记；以及设备或能力地图中，仅在用户需要细节时通过悬停显示标签。\n\n它也适合那些希望在数据与设计之间建立可复用渲染约定的团队。如果后端能够提供稳定的元数据键，例如 `icon`，前端就可以维持一套 `RGSlotOnNode` 实现，并随着时间推移持续扩展图标映射和 SCSS 主题库，而无需改动图本身的结构。\n",false,500,1782615372328]