[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:graph-hand-drawn-style-switcher":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 将 Relation Graph 切换为手绘呈现模式\n\n## 这个示例构建了什么\n\n这个示例构建了一个居中层级查看器，可以在运行时在普通卡片呈现和类似草图的手绘皮肤之间切换。画布中展示了七个 KPI 风格节点、六条带标签的连线、一个浮动控制窗口，以及一个内置 minimap。\n\n用户可以开启或关闭手绘模式、修改节点边框样式，并选择纸张、斜线、网格或点状等纹理叠加效果。图结构本身不会变化。相反，这个示例保持同一份已加载的层级数据不变，并原地重新设定其节点、连线、标签和箭头的主题样式。\n\n这个示例最值得关注的一点在于，草图效果来自多层 relation-graph 功能的组合，而不是依赖单独的渲染器。自定义节点插槽、SVG filters、SCSS 覆盖以及图实例更新 API 协同工作，使同一张已有图能够呈现出截然不同的插画风格外观。\n\n## 数据是如何组织的\n\n数据来自 `getJsonData()`，返回一个 `RGJsonData` 对象，其中包含 `rootId: \"a\"`、一个扁平的 `nodes` 数组，以及一个扁平的 `lines` 数组。每条节点记录都带有 `data.name` 和 `data.myicon`，而六条连线最初只定义了 `from` 和 `to`。\n\n在调用 `setJsonData()` 之前有一个预处理步骤。初始化期间，代码会遍历每一条连线，并注入统一的标签（`Line Text`）、随机的连接点偏移、一个随机的 `junctionOffset`，以及自定义的 `my-hand-drawn-arrow-end` marker。数据加载完成后，后续的样式变化不会重建 JSON，而是通过 `updateNodeData()` 和 `updateLine()` 更新已经渲染出来的节点与连线。\n\n在真实产品中，这种结构同样可以表示小型组织架构图、团队仪表盘、能力地图或功能层级。`data.name` 可以映射为人员、部门、产品或服务，而运行时写入的这些样式字段则可以代表主题预设、展示模式，或品牌定制的插画风格变体。\n\n## relation-graph 是如何使用的\n\n`index.tsx` 通过 `RGProvider` 包裹整个示例，`MyGraph.tsx` 则通过 `RGHooks.useGraphInstance()` 使用共享的图上下文。图被配置为 `center` 布局，设置了明确的 `levelGaps`，在两个轴向上都使用居中对齐，并设置 `defaultLineWidth: 2`。同时，通过 `defaultNodeColor: 'transparent'` 和 `defaultNodeBorderWidth: 0` 有意弱化 relation-graph 内置的节点填充和边框渲染，让可见的卡片主体改由自定义插槽内容来定义。\n\n主要扩展点是 `RGSlotOnNode`。每个节点都会渲染一个大图标、一个名称以及三行固定 KPI，然后根据 `enableHandDrawn`，将这些内容包裹在 `MyNodeContentBox` 或 `HandDrawnBox` 中。`HandDrawnBox` 会应用不对称的 border-radius 数值和可选的基于 SVG 的纹理，而 `IconSwitcher` 会将 `node.data.myicon` 映射到 Lucide 图标，若未匹配则回退到 `HelpCircle`。\n\n这个示例还使用了 `RGSlotOnView` 来挂载 `RGMiniView`，因此查看器无需编写自定义视口代码就获得了一个内置总览面板。运行时行为由图实例 API 驱动：`setJsonData()` 负责加载图，`getNodes()` 配合 `updateNodeData()` 传播边框和纹理状态，`getLines()` 配合 `updateLine()` 切换线条形状，而 `moveToCenter()`、`zoomToFit()` 和 `zoomToFitWithAnimation()` 则在视觉变化后保持视口对齐。\n\n手绘效果并不止于节点插槽。`MySvgFilters` 注入了 `static-hand-drawn` SVG filter 和自定义箭头 marker，而 `my-relation-graph.scss` 会在 `.node-filter-hand-drawn` 包裹层下定向作用于 relation-graph 内置的节点、连线和标签层。该 SCSS 添加了滤镜扭曲效果、便签风格的虚线标签，以及围绕自定义内容的选中态覆盖，而不是使用默认图阴影。\n\n浮动控制面板来自共享组件 `DraggableWindow`。它在本示例中直接承载样式选择器，并且其设置面板使用 `RGHooks.useGraphStore()` 配合 `setOptions()` 来调整滚轮与拖拽行为。同一个共享辅助组件还通过 `prepareForImageGeneration()`、`domToImageByModernScreenshot()` 和 `restoreAfterImageGeneration()` 提供截图导出能力。\n\n## 关键交互\n\n核心交互是手绘模式开关。切换它会更改根包裹层 class，在 `RGLineShape.Curve8` 与 `RGLineShape.StandardStraight` 之间重写所有连线，并重新适配视口。\n\n当手绘模式启用后，会额外出现两个选择器，用于设置边框样式和背景纹理。任意一个发生变化，都会触发一次全图遍历，把 `nodeBorderStyle` 和 `nodeBackgroundStyle` 写入每个节点的数据中，从而让整个层级一起改变外观。\n\n浮动窗口本身也是可交互的。用户可以通过标题栏拖动它、将其最小化、打开设置面板，并在浏览当前展示时始终让它悬浮在图上方。\n\n设置面板增加的是查看器级别的控制，而不是内容编辑能力。它可以切换滚轮行为、切换画布拖拽行为，并将当前图 DOM 导出为图片。\n\n## 关键代码片段\n\n下面这个 options 配置块说明，该示例保留了 relation-graph 原生的中心布局，同时将默认节点主体尽量简化，从而让插槽内容来定义可见的卡片样式。\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: true,\n    defaultJunctionPoint: RGJunctionPoint.border,\n    defaultNodeColor: 'transparent',\n    defaultNodeShape: RGNodeShape.rect,\n    defaultNodeBorderWidth: 0,\n    defaultLineWidth: 2,\n    layout: {\n        layoutName: 'center',\n        levelGaps: [500, 400, 400],\n        alignItemsX: 'center',\n        alignItemsY: 'center'\n    }\n};\n```\n\n这个初始化步骤说明，在图加载之前就已经注入了连线标签、偏移量以及自定义草图箭头 marker。\n\n```tsx\nconst myJsonData: RGJsonData = await getJsonData();\nmyJsonData.lines.forEach(line => {\n    line.text = 'Line Text';\n    line.fromJunctionPointOffsetX = Math.random() * 10;\n    line.fromJunctionPointOffsetY = Math.random() * 10;\n    line.toJunctionPointOffsetX = Math.random() * 10;\n    line.toJunctionPointOffsetY = Math.random() * 10;\n    line.junctionOffset = Math.random() * 20 - 10;\n    line.endMarkerId = 'my-hand-drawn-arrow-end';\n});\nawait graphInstance.setJsonData(myJsonData);\n```\n\n这个运行时更新函数是该演示的核心：它把选定的节点样式和线条几何形态传播到已经渲染出来的图中。\n\n```tsx\ngraphInstance.getNodes().forEach(node => {\n    graphInstance.updateNodeData(node, {\n        nodeBorderStyle,\n        nodeBackgroundStyle\n    });\n});\ngraphInstance.getLines().forEach(line => {\n    graphInstance.updateLine(line, {\n        lineShape: enableHandDrawn ? RGLineShape.Curve8 : RGLineShape.StandardStraight,\n    });\n});\n```\n\n这个控制片段展示了手绘开关是入口点，并且只有在草图模式启用后，边框和纹理选择器才会出现。\n\n```tsx\n\u003CSimpleUISelect data={[\n    { value: false, text: 'None' },\n    { value: true, text: 'Hand Drawn Style' }\n]} currentValue={enableHandDrawn} onChange={setEnableHandDrawn} />\n{\n    enableHandDrawn && \u003C>\n        \u003CSimpleUISelect data={[\n            { value: 'rough', text: 'Rough' },\n            { value: 'pencil', text: 'Pencil' },\n            { value: 'thick', text: 'Thick' }\n        ]} currentValue={nodeBorderStyle} onChange={setNodeBorderStyle} />\n    \u003C/>\n}\n```\n\n这个节点插槽片段展示了同一个 KPI 卡片模板如何在不改变图数据结构的前提下，被包裹进普通盒子或手绘盒子中。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node }) => {\n        const nodeContent = \u003Cdiv>{/* icon, name, KPI rows */}\u003C/div>;\n        if (enableHandDrawn) {\n            return \u003CHandDrawnBox variant={node.data.nodeBorderStyle} texture={node.data.nodeBackgroundStyle}>\n                {nodeContent}\n            \u003C/HandDrawnBox>\n        } else {\n            return \u003CMyNodeContentBox>{nodeContent}\u003C/MyNodeContentBox>\n        }\n    }}\n\u003C/RGSlotOnNode>\n```\n\n这个包裹组件说明，手绘卡片并不只是颜色主题变化。边框几何形态和描边粗细都会随变体而改变。\n\n```tsx\nconst getWobbleStyle = () => {\n  switch (variant) {\n    case \"pencil\":\n      return {\n        borderRadius: \"255px 15px 225px 15px/15px 225px 15px 255px\",\n        borderWidth: \"1px\",\n        borderStyle: \"solid\",\n        borderColor: color,\n      };\n    case \"thick\":\n      return {\n        borderRadius: \"4px 6px 4px 10px / 8px 4px 10px 5px\",\n        borderWidth: \"4px\",\n        borderStyle: \"solid\",\n        borderColor: color,\n      };\n```\n\n这个 SCSS 片段展示了该示例如何把手绘模式作用到 relation-graph 内置的节点、连线和标签 DOM 层上。\n\n```scss\n.node-filter-hand-drawn {\n    .relation-graph {\n        .rg-node-peel {\n            .rg-node {\n                filter: url(#static-hand-drawn);\n            }\n        }\n        .rg-line-peel {\n            .rg-line {\n                filter: url(#static-hand-drawn);\n            }\n            .rg-line-label {\n                filter: url(#static-hand-drawn);\n                background: #fff;\n                border: 2px dashed #666;\n            }\n        }\n    }\n}\n```\n\n这段共享辅助代码说明，截图导出是通过图实例的准备和恢复流程完成的，而不是直接截取任意页面 DOM。\n\n```tsx\nconst downloadImage = async () => {\n    const canvasDom = await graphInstance.prepareForImageGeneration();\n    let graphBackgroundColor = graphInstance.getOptions().backgroundColor;\n    if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n        graphBackgroundColor = '#ffffff';\n    }\n    const imageBlob = await domToImageByModernScreenshot(canvasDom, {\n        backgroundColor: graphBackgroundColor\n    });\n    if (imageBlob) {\n        downloadBlob(imageBlob, 'my-image-name');\n    }\n```\n\n## 这个示例的独特之处\n\n对比数据表明，这个示例与 `custom-line-animation`、`custom-line-style`、`node` 和 `deep-each` 较为接近，但它的关注重点与这些相邻示例都不同。最突出的区别在于，它把一个固定的居中层级图变成了一个完整的图外观切换器。通过同一个控制窗口，可以统一切换节点包裹层、边框变体、纹理叠加、线条形状、连线抖动、标签处理方式以及箭头样式。\n\n与 `custom-line-animation` 相比，这个示例的重点不在于维护一整套动画或线条预设，而在于构建一种覆盖节点、连线、标签和包裹层的统一草图展示模式。与 `custom-line-style` 相比，它不只是切换 CSS 连线类名，还会通过 `updateNodeData()` 进一步传播节点级别的边框和纹理状态。\n\n与主要并排比较不同节点渲染技术的 `node` 示例相比，这个示例保持同一个卡片模板不变，而是在运行时对整张图进行重设主题。与 `deep-each` 相比，它的图实例更新重点是整场景的展示切换，而不是子树强调或聚焦行为。\n\n稀有性数据也支持一个更具体的判断：`HandDrawnBox`、注入式 SVG filters、逐节点纹理选择器、自定义箭头 marker，以及 `Curve8` 与直线之间的切换，这一组合对于一个偏样式导向的演示来说相当丰富。对于那些需要插画式或品牌化展示层、但又不想替换 relation-graph 渲染器的团队来说，它是一个更强的起点。\n\n## 这种模式还适用于哪里\n\n这种模式适用于图数据保持稳定、但视觉语言需要切换的仪表盘和展示视图。典型场景包括投资人叙事页面、产品战略图、教育内容、工作坊看板、白标租户主题，以及面向营销场景的组织图或能力地图。\n\n它也适用于团队只需要一个临时展示模式，而不是永久性的自定义渲染器的情况。产品可以保留正常的 relation-graph 数据模型和查看器行为，然后在导出、现场演示、干系人评审或特殊报告模式下叠加一层风格化主题。\n",false,500,1782615373401]