[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:force-layout-runtime-parameter-controls":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 使用数据集预设进行运行时 Force 布局调优\n\n## 这个示例构建了什么\n\n这个示例围绕 relation-graph 内置的 `force` 布局构建了一个全屏的力导向布局调试场。页面会在主画布上显示一个蓝色的圆形节点网络，并在其上方悬浮一个控制窗口。用户可以在一个紧凑的加权样例和一个更大的分支样例之间切换，调整六个全局力系数，选择让布局持续运行还是在固定迭代次数后停止，并打开共享的画布工具，例如拖拽模式设置、滚轮模式设置和图片导出。\n\n这个示例的重点不是自定义渲染，而是内置布局器的一种运行时控制模式：保持图谱已挂载，直接修改当前启用的 force 求解器，并且只在迭代策略变化时重新执行布局。\n\n## 数据是如何组织的\n\n这个示例在 [`example-data.ts`](/Users/gaokui/WebstormProjects/relation-graph-site-react/src/v3-examples/force-layout-runtime-parameter-controls//example-data.ts) 中保留了两个本地 `RGJsonData` 预设。`exampleDataSmall` 是一个紧凑的类树形图，包含一个更大的根节点，并设置了 `force_weight: 10000`；`exampleDataBig` 则是一个更稠密的分支图，包含更多后代节点。在加载任一预设之前，`initializeGraph()` 会遍历每一条连线并补充生成的 `id`，例如 `l1`、`l2` 和 `l3`，然后用 `rootId: 'a'` 重建整份载荷。\n\n在生产环境图谱中，相同的结构可以表示同一领域数据的两个已保存场景：聚焦的子图与更广泛的网络、低密度客户关系视图与完整账户邻域，或者用于调试布局参数的小型测试用例与更接近真实情况的数据集。\n\n## relation-graph 是如何使用的\n\n入口组件先用 `RGProvider` 包裹页面，再在 `MyGraph` 中渲染 `RelationGraph`，这样所有运行时图谱 API 都可以通过 `RGHooks.useGraphInstance()` 获取，而不需要依赖逐层透传 props。图谱始终使用内置的 `force` 布局，节点为圆形、连线为直线、连接点位于边框上，并且节点和连线都使用一致的蓝色默认样式。[`my-relation-graph.scss`](/Users/gaokui/WebstormProjects/relation-graph-site-react/src/v3-examples/force-layout-runtime-parameter-controls//my-relation-graph.scss) 中的样式表只覆盖了选中状态下的文本和线条颜色，并没有替换节点或连线模板。\n\n运行时控制模式被拆成两层。第一层是直接修改当前求解器：读取 `graphInstance.layoutor`，将其转换为 `RGLayouts.ForceLayout`，然后调用 `updateOptions(myForceLayoutOptions)`。这让六个滑块成为实时调参控件，而不是走完整重载流程。第二层是在用户切换持续布局模式和固定迭代布局模式时，调用 `graphInstance.updateOptions({ layout })`，随后执行 `doLayout()`。这种拆分让系数调节保持轻量，同时页面仍能演示受限迭代次数下的重新布局策略。\n\n数据集切换使用更显式的重载流程。示例会选择 `small` 或 `big`，为连线数组重新生成 id，然后调用 `stopAutoLayout()`、`clearGraph()`、`sleep(500)` 和 `setJsonData(...)`。加载完成后，再通过 `moveToCenter()` 将图谱移回中心，并把缩放重置为 `30`。这个示例没有定义自定义节点、连线、画布或视口插槽，而是依赖内置渲染和共享辅助组件。\n\n悬浮工具窗口来自共享组件 [`DraggableWindow.tsx`](/Users/gaokui/WebstormProjects/relation-graph-site-react/src/v3-examples/simples-common-components/DraggableWindow.tsx)。这个包装组件增加了拖动、最小化/恢复、画布设置面板和截图导出功能。分段选择器来自 [`SimpleUISelect.tsx`](/Users/gaokui/WebstormProjects/relation-graph-site-react/src/v3-examples/simples-common-components/SimpleUISelect.tsx)，而图片导出最终使用 [`domToImageByModernScreenshot.ts`](/Users/gaokui/WebstormProjects/relation-graph-site-react/src/v3-examples/simples-common-components/domToImageByModernScreenshot.ts) 将准备好的图谱 DOM 转换为可下载的 blob。\n\n## 关键交互\n\n- 切换数据集预设时，图谱会从 `exampleDataSmall` 或 `exampleDataBig` 重新加载，然后再次将视口居中并调整缩放。\n- 移动六个范围输入中的任意一个，都会原地更新当前的 `ForceLayout` 实例，因此用户可以在不重建组件的情况下观察当前图谱的响应。\n- 在 Layout Forever 和 Layout Fixed Times(...) 之间切换，会改变 force 求解器的运行时长。选择固定模式时，还会出现一个额外的范围输入，用于调整迭代上限。\n- 悬浮控制面板本身也可交互：可以拖动、最小化、展开，并切换到共享设置面板。\n- 共享设置面板可以改变滚轮行为、改变画布拖拽行为，并把当前图谱导出为图片。这些工具之所以可用，是因为使用了共享辅助窗口，而不是因为这个示例自己实现了专用导出流程。\n\n## 关键代码片段\n\n这段代码说明，这个示例是围绕 relation-graph 内置的 `force` 布局和内置默认渲染配置的，而不是依赖自定义插槽。\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: true,\n    defaultNodeBorderWidth: 0,\n    defaultNodeShape: RGNodeShape.circle,\n    defaultNodeWidth: 80,\n    defaultNodeHeight: 80,\n    defaultLineColor: 'rgb(0,139,189)',\n    defaultNodeColor: 'rgb(0,139,189)',\n    defaultLineShape: RGLineShape.StandardStraight,\n    layout: {\n        layoutName: 'force',\n        maxLayoutTimes: Number.MAX_SAFE_INTEGER\n    }\n};\n```\n\n这段代码展示了显式的数据集重载路径，包括生成的连线 id，以及用于清空、重载、居中和重置缩放的 graph-instance API。\n\n```tsx\nconst linesWithIds = exampleData.lines.map((line, index) => ({\n    ...line,\n    id: `l${index + 1}`\n}));\n\nconst myJsonData: RGJsonData = {\n    rootId: 'a',\n    nodes: exampleData.nodes,\n    lines: linesWithIds\n};\ngraphInstance.stopAutoLayout();\ngraphInstance.clearGraph();\nawait graphInstance.sleep(500);\nawait graphInstance.setJsonData(myJsonData);\n```\n\n这段代码是运行时调参的核心技巧：页面读取当前布局器，并把新的全局力系数推送到实时的 `ForceLayout` 中。\n\n```tsx\nconst updateMyOptions = async () => {\n    const forceLayout = graphInstance.layoutor as InstanceType\u003Ctypeof RGLayouts.ForceLayout>;\n    if (forceLayout) {\n        forceLayout.updateOptions(myForceLayoutOptions);\n    }\n};\n```\n\n这段代码说明，持续布局模式与固定次数布局模式的处理逻辑，与实时系数更新是分开的。\n\n```tsx\nconst restartForceLayout = async () => {\n    graphInstance.updateOptions({\n        layout: {\n            layoutName: 'force',\n            maxLayoutTimes: layoutForever ? Number.MAX_SAFE_INTEGER : maxLayoutTimes\n        }\n    });\n    await graphInstance.doLayout();\n    graphInstance.moveToCenter();\n    graphInstance.setZoom(30);\n};\n```\n\n这段代码证明，force 控制面板暴露了六个彼此独立的全局系数，而不只是一个或两个简单滑块。\n\n```tsx\n\u003Cdiv className=\"py-2\">Node Repulsion: {myOptions.force_node_repulsion}\u003C/div>\n\u003Cinput type=\"range\" min=\"0.2\" max=\"3\" step=\"0.1\" value={myOptions.force_node_repulsion} />\n\u003Cdiv className=\"py-2\">Line Elastic: {myOptions.force_line_elastic}\u003C/div>\n\u003Cinput type=\"range\" min=\"0.2\" max=\"3\" step=\"0.1\" value={myOptions.force_line_elastic} />\n\u003Cdiv className=\"py-2\">maxTractionLength: {myOptions.maxTractionLength}\u003C/div>\n\u003Cinput type=\"range\" min=\"100\" max=\"1000\" step=\"50\" value={myOptions.maxTractionLength} />\n```\n\n这段代码说明，继承而来的悬浮窗口也通过 graph-instance API 暴露了画布设置和导出功能。\n\n```tsx\nconst canvasDom = await graphInstance.prepareForImageGeneration();\nlet graphBackgroundColor = graphInstance.getOptions().backgroundColor;\nif (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n    graphBackgroundColor = '#ffffff';\n}\nconst imageBlob = await domToImageByModernScreenshot(canvasDom, {\n    backgroundColor: graphBackgroundColor\n});\nif (imageBlob) {\n    downloadBlob(imageBlob, 'my-image-name');\n}\nawait graphInstance.restoreAfterImageGeneration();\n```\n\n## 这个示例的独特之处\n\n这个示例之所以独特，是因为它把三个在其他演示里经常分开的模式组合到了一起。第一，它通过专用面板暴露了六个全局力系数，并使用 `RGLayouts.ForceLayout.updateOptions(...)` 将它们应用到实时布局器上。第二，它不是把用户限制在一张静态图上，而是让这些控制项在两个预设数据集之间做对比。第三，它把实时系数修改和迭代策略触发的重新布局分离开来，因此比起更简单的 force 示例，这个示例更适合做运行时调优。\n\n与 `layout-force` 相比，这个页面不再只是一个最小化的内置 force 基线示例，而更像一个用于比较不同预设图谱上全局 force 设置的实验室。与 `performance-test-force-layout` 相比，它仍然使用常规内置渲染和精心准备的本地数据集，而没有进入 `performanceMode`、超大规模合成图、缩略图或自定义节点插槽压测的场景。与 `io-tree-layout` 和 `layout-tree` 这类布局切换示例相比，这里的核心经验是运行中 force 实例上的求解器行为，而不是朝向变化或层级专用的连线路由规则。\n\n较小的预设还包含一个尺寸更大且带权重的根节点，这会让 force 求解器的效果在视觉上更容易观察。这个细节属于样例数据本身，而不是单独的 UI 控件。\n\n## 这个模式还适用于哪里\n\n当产品需要为内置 force 布局提供调参界面，但又不想从零开始构建自定义布局器时，这个模式就很适用。典型场景包括：内部知识图谱工具，分析人员需要稳定节点间距；调查视图，需要将稠密邻域与较小的聚焦子图做对比；以及售前演示，希望展示同一个图引擎在不同密度和不同求解时长下的表现。\n\n它同样适用于管理或诊断类工具。支持控制台可以暴露类似的悬浮面板，用于控制拖拽行为、滚轮行为、截图导出和有限的布局参数，同时保持图谱自身为只读。这里最重要的思想，是把数据重载、实时求解器修改和重新运行策略彼此分离，使每一种交互都保持可预测。\n",false,500,1782615385722]