力学布局实时参数调节
这个示例基于单一静态分支数据构建力导网络,并提供节点斥力、连线弹性和重布局时长的实时控制。它是更新当前 ForceLayout 实例并重新运行内置求解器(无需重建图数据)的紧凑参考。
Force 布局实时参数调优
这个示例构建了什么
这个示例围绕一个静态分支数据集构建了一个全屏力导向图试验场。画布显示统一的圆形节点和笔直的青绿色连线,同时一个悬浮的白色控制窗口允许用户调节节点斥力、连线弹性以及布局迭代次数。
重点并不在于自定义渲染或图编辑。真正有用的部分在于图只加载一次,然后直接原地调整正在运行的力求解器。同一个悬浮窗口还暴露了共享工作区工具,例如滚轮模式切换、拖动画布模式切换以及图片导出。
数据如何组织
图数据直接在 MyGraph.tsx 中内联组装。它使用一个 RGJsonData 对象,其中包含 rootId: 'a'、一个 rawNodes 数组和一个 rawLines 数组。在调用 setJsonData 之前,示例会把每条原始连线映射为一个带有生成 id 的新对象,因此最终传入 relation-graph 的数据集具备显式的连线标识符。
示例数据构成了一个有根的分支结构:一个根节点 a、四个顶层分支(b、c、d、e),以及每个分支下更深层的子节点组。总计来看,这个文件定义了 103 个节点和 102 条连线。在生产应用中,同样的结构可以表示组织树、依赖展开、分类体系、事故传播树,或任何仍然更适合使用力导向间距而不是严格树布局的类层级网络。
relation-graph 的使用方式
页面包裹在 RGProvider 中,而 MyGraph 通过 RGHooks.useGraphInstance() 获取作用域属于该 provider 的图实例。RelationGraph 组件接收了一组聚焦的图配置:圆形节点、60x60 的默认节点尺寸、连接到边框的直线、青色与青绿色默认配色,以及内置的 force 布局。
图实例 API 驱动了运行时行为。initializeGraph() 负责加载准备好的 RGJsonData、将图居中,并设置一个固定缩放级别。updateMyOptions() 会把 graphInstance.layoutor 视为 RGLayouts.ForceLayout,并在不重建数据集的情况下推入新的物理参数值。另一条单独的 restartForceLayout() 路径则会重写 layout.maxLayoutTimes、调用 doLayout(),然后重新居中并重新设置画布缩放,以便用户对比连续布局与固定迭代预算两种效果。
这个示例中没有节点插槽、连线插槽,也没有编辑 API。样式通过图配置以及一个本地 SCSS 文件完成,该文件会强制节点文字为白色,并定义选中连线的覆盖样式。可拖拽的辅助窗口及其设置面板来自共享的本地组件,并再次使用 relation-graph hooks 来操作画布选项和图片生成。
关键交互
Node Repulsion滑块会修改当前 force 布局器上的force_node_repulsion。Line Elastic滑块会修改当前 force 布局器上的force_line_elastic。- 分段选择器用于在
Layout Forever和Layout Fixed Times(...)之间切换。 - 选择固定模式后,一个范围输入会控制
maxLayoutTimes,并触发一次新的doLayout()运行。 - 悬浮辅助窗口可以拖动和最小化,因此用户可以在保持控件近旁可用的同时查看图。
- 辅助窗口的设置视图可以修改滚轮行为、修改画布拖动行为,并下载图像快照。
节点和连线点击处理器都已存在,但在审阅过的源码中,它们只会把被点击的对象输出到控制台。在这里,它们并不驱动布局、选择或编辑行为。
关键代码片段
下面这段片段展示了该示例使用 relation-graph 内置的 force 布局,并配合最小化的视觉样式,而不是自定义节点渲染。
const graphOptions: RGOptions = {
debug: true,
defaultNodeBorderWidth: 0,
defaultNodeShape: RGNodeShape.circle,
defaultNodeWidth: 60,
defaultNodeHeight: 60,
defaultLineColor: 'rgba(0, 186, 189, 1)',
defaultNodeColor: 'rgba(0, 206, 209, 1)',
defaultLineShape: RGLineShape.StandardStraight,
layout: { layoutName: 'force', maxLayoutTimes: Number.MAX_SAFE_INTEGER },
defaultJunctionPoint: RGJunctionPoint.border
};
下面这段片段展示了图数据只准备一次:在把它加载到图实例之前,先为每条连线补上显式的 ID。
const linesWithIds = rawLines.map((line, index) => ({
...line,
id: `l${index + 1}`
}));
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: rawNodes,
lines: linesWithIds
};
下面这段片段展示了一次性数据加载,以及随后显式执行的视口重置。
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.setZoom(30);
下面这段片段展示了第一条运行时控制路径:当滑块变化时,直接更新当前激活的 ForceLayout 实例。
const forceLayout = graphInstance.layoutor as InstanceType<typeof RGLayouts.ForceLayout>;
if (forceLayout) {
forceLayout.updateOptions(myForceLayoutOptions);
}
下面这段片段展示了第二条运行时控制路径:通过重写布局选项并重新运行求解器来切换重新布局模式。
graphInstance.updateOptions({
layout: {
layoutName: 'force',
maxLayoutTimes: layoutForever ? Number.MAX_SAFE_INTEGER : maxLayoutTimes
}
});
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.setZoom(30);
下面这段片段展示了悬浮 UI 如何把连续布局与带有滑块上限的布局运行区分开来。
<SimpleUISelect currentValue={layoutForever} data={[
{ value: true, text: 'Layout Forever' },
{ value: false, text: `Layout Fixed Times(${maxLayoutTimes})` }
]} onChange={(newValue: boolean) => { setLayoutForever(newValue); }} />
{!layoutForever && <input
type="range"
min="10"
max="1000"
step="20"
value={maxLayoutTimes}
这个示例的独特之处
这个示例的独特之处在于,它把内置的 force 布局当作一个最小化的实时基线,而不是一个庞大的控制面板。图始终停留在同一份内联数据集上,可见的力导向控制项只保留了斥力和连线弹性,而迭代控制则被放进一个独立的“连续布局 / 固定次数重新布局”切换中。
与 layout-force-options 相比,这个版本更适合作为起点来阅读,因为它没有加入数据集预设,也没有扩展出更宽泛的全局力参数面板。与 performance-test-force-layout 相比,它避开了性能测试脚手架,例如合成规模预设、缩略图导航以及自定义节点渲染。与 layout-force-options-pro 相比,它停留在全局求解器调优层面,而不是深入到按节点或按连线的力参数覆盖,以及运行时图变更。
另一个独特之处是它在视觉上的克制。页面保持了无边框的 60px 圆形节点、笔直的青绿色连线,以及一个可拖拽的白色辅助窗口,因此真正被突出展示的是力导向行为本身。
这种模式还适用于哪里
这种模式很适合迁移到内部工具中,在团队确定最终图设计之前先调试间距行为。例如,同样的方法可以用于依赖图、服务拓扑图、组织结构、分类浏览器等场景,在这些场景中数据保持稳定,但目标中的力导向行为仍需要持续校准。
它同样是面向操作人员工作台的实用模式。产品可以只加载一次图,让用户调整求解器强度和重新运行时长,然后在保持同一数据集不变的前提下,对比不同布局参数下的可读性、重叠减少效果以及导出质量。