力导向布局节点权重与连线弹性
这是一个全屏力导布局调试场,用于在同一实时图上对比按节点权重和按连线弹性两种效果。用户可随机化取值范围、重新调参运行中的求解器,并在 relation-graph 重新计算布局时动态添加子节点。
节点权重与连线弹性的力导向布局实验室
这个示例构建了什么
这个示例围绕一个根节点和一圈与之相连的子节点,构建了一个全屏的力导向布局实验场。图中使用黑色圆形节点、直线连线、右侧内置工具栏以及一个悬浮的白色控制窗口,因此整个画布看起来更像一个物理实验室,而不是业务图表。
用户可以在节点和连线之间切换实验对象,随机化该对象使用的数值范围,重新调整正在运行的力求解器,并向选中的节点或根节点添加两个新的子节点。这个示例的重点不仅是让力导向布局运行起来,更在于通过节点大小、节点文字、连线标签和线宽,把元素级别的受力数值直接展示在画布上。
数据是如何组织的
初始数据集在 initializeGraph() 中以内联方式组装。它先创建一个根节点,然后追加 30 个子节点和 30 条从根节点连到子节点的连线,最后再调用 setJsonData(...)。
数据挂载完成后,这个示例会立即对当前在线图数据做预处理,而不是为每次实验重新构建 JSON。在节点模式下,它会清除之前的覆盖值,为每个非根节点分配一个随机 force_weight,根据该数值缩放节点尺寸,把采样值写入节点标签,然后先把所有子节点重置到一个便于比较的环形位置,再重新启动自动布局。在连线模式下,它会清除之前的覆盖值,并为每条已渲染的连线重新写入随机 force_elastic、数值标签以及对应的线宽。
这种结构很适合映射到一个中心节点连接多个依赖项的真实数据。在生产系统中,同样的模式可以表示一个中心服务及其下游消费者、一个团队负责人及其直接协作者、一个包及其依赖项,或者任何希望让节点重要性和连线强度直观影响间距的图结构。
relation-graph 是如何使用的
这个演示被包裹在 RGProvider 中,RelationGraph 使用 relation-graph 内置的 force 布局进行挂载。它的选项让图保持精简:关闭调试模式,节点默认是黑色圆形,连线为直线,工具栏竖直放在右侧,连线连接点附着在节点边界上。
RGHooks.useGraphInstance() 是主要的控制入口。示例用它通过 setJsonData(...) 加载数据,通过 getNodes()、getLines()、getRootNode() 和 getCheckedNode() 读取当前在线图,通过 updateNode(...) 和 updateLine(...) 修改已渲染元素,通过 addNodes(...) 和 addLines(...) 追加结构,并通过 startAutoLayout()、stopAutoLayout()、enableNodeXYAnimation()、disableNodeXYAnimation()、moveToCenter()、zoomToFit()、enableCanvasAnimation() 和 disableCanvasAnimation() 管理布局运动。
力导向布局的控制项是在不重新挂载图的情况下更新的。一个 React state 对象保存滑块数值,而 graphInstance.layoutor 会被转换为 RGLayouts.ForceLayout,这样就能通过 updateOptions(...) 把新的系数推送到正在运行的求解器中。
悬浮控制面板来自共享组件 DraggableWindow。这个窗口提供拖拽、最小化、共享的画布设置覆盖层,以及通过 prepareForImageGeneration(...) 和 restoreAfterImageGeneration() 实现的图片导出。辅助控制组件则拆分为 MyForceLayoutOptions 用于求解器滑块、SimpleUISelect 用于模式切换,以及 SimpleUINumberRange 用于可编辑的随机范围设置。
样式刻意保持轻量。本地 SCSS 会把深色节点上的文字覆盖为白色,让连线标签默认保持在白色底片上,并在选中连线时反转标签与描边颜色,以确保被选中的弹性采样值仍然清晰可读。
关键交互
Observation Object选择器会在节点权重实验和连线弹性实验之间切换面板内容。- 数值范围输入框定义了重置按钮使用的随机区间,因此用户可以在重新运行实验前扩大或缩小采样的受力数值范围。
Randomly Reset Node Weight会重写每个节点的force_weight、尺寸、文字和位置,然后从一个便于比较的环形状态重新启动力导向布局。Randomly Reset Line Elastic会在已挂载的图上重写每条连线的force_elastic、标签和线宽,然后继续自动布局。Add Two Child Nodes to Selected Nodes会向当前选中节点追加新节点和新连线;如果没有选中节点,则追加到根节点,然后重新启动求解器。- 悬浮窗口可以拖动或最小化,它的设置面板还允许用户切换滚轮和拖拽行为,或把当前图导出为图片。
关键代码片段
这个片段展示了图如何保持在 relation-graph 内置的力导向布局上,并围绕这一选择配置画布。
const graphOptions: RGOptions = {
debug: false,
defaultNodeBorderWidth: 0,
defaultLineShape: RGLineShape.StandardStraight,
defaultNodeWidth: 70,
defaultNodeHeight: 70,
defaultNodeColor: '#000000',
defaultNodeShape: RGNodeShape.circle,
toolBarDirection: 'v',
toolBarPositionH: 'right',
toolBarPositionV: 'center',
layout: {
layoutName: 'force',
maxLayoutTimes: Number.MAX_SAFE_INTEGER
},
这个片段说明,在节点模式下,元素级别的 force_weight 数值会被写回到每个子节点的尺寸和标签中。
nodes.forEach(node => {
if (node.id === rootNode.id) return;
const nodeWeight = rangeForNode[0] + Math.random() * rangeForNode[1];
const size = 10 + 10 * Math.sqrt(nodeWeight);
graphInstance.updateNode(node, {
width: size,
height: size,
color: '#000000',
force_weight: nodeWeight,
text: nodeWeight.toFixed(1),
data: { ...node.data }
});
});
这个片段展示了一个分阶段的重置流程,在自动布局恢复之前,先让不同权重更便于直观比较。
nodes.forEach((node, nodeIndex) => {
if (node.id === rootNode.id) return;
const resetNodeXy = getOvalXy(rootNode.x, rootNode.y, 100, nodeIndex, nodes.length);
graphInstance.updateNode(node, {
x: resetNodeXy.x,
y: resetNodeXy.y
});
});
await graphInstance.sleep(500);
graphInstance.disableNodeXYAnimation();
graphInstance.startAutoLayout();
这个片段展示了连线模式如何使用当前在线的连线集合,把 force_elastic 同时编码为物理数据和可视样式。
graphInstance.getLines().forEach(line => {
const lineElastic = rangeForLine[0] + Math.random() * rangeForLine[1];
graphInstance.updateLine(line, {
text: lineElastic.toFixed(1),
force_elastic: lineElastic,
lineWidth: 0.5 + lineElastic,
color: '#000000',
fontColor: '#000000',
data: { ...line.data }
});
});
这个片段展示了 “添加子节点” 操作所使用的增量式图变更路径。
graphInstance.addNodes(newNodes);
graphInstance.addLines(newLines);
graphInstance.startAutoLayout();
这个片段展示了滑块面板如何在不重建图的情况下更新当前活动的 ForceLayout 实例。
const updateMyOptions = async () => {
const forceLayout = graphInstance.layoutor as InstanceType<typeof RGLayouts.ForceLayout>;
if (forceLayout) {
forceLayout.updateOptions(myForceLayoutOptions);
}
};
这个示例的独特之处
与附近的 layout-force-options 示例相比,这个演示远不只是关于全局求解器系数本身。它最核心的启发在于:同一张已挂载的图上,可以把节点质量和连线拉力强度都作为元素级行为来进行测试。
与 layout-force 和 performance-test-force-layout 相比,这个示例的重点也不主要是一个基础实验场或规模测试。它增加了分阶段重置流程、显式数值编码以及轻量级图结构变更,因此用户可以通过视觉比较受力行为,而不是仅仅拖动滑块、观察布局漂移。
与 line-shape-and-label 相比,这里的连线更新主要不是为了连接线渲染效果。它们存在的目的是把 force_elastic 暴露到求解器内部,因此连线标签和线宽会变成这场力学实验的测量读数。
在对比数据中,这个示例最强的一组少见组合是:观测模式切换、逐节点 force_weight 覆盖、逐连线 force_elastic 覆盖、在线 ForceLayout.updateOptions(...) 调优,以及在同一个可拖拽工作区内执行 “添加两个子节点” 变更。这使它比起通用图浏览场景,更适合作为比较式力行为研究的起点。
这种模式还适用于哪里
这种模式非常适合用于在加入真实样式之前,解释或调试布局行为的内部工具。团队可以用同样的方法测试依赖强度、工作负载权重、影响力分数或通信强度应如何影响图上的间距。
它也适用于教学和调优场景。例如,工作流设计器可以比较阶段之间的强弱转换,基础设施团队可以可视化高负载服务与弱耦合关系,组织结构图原型也可以先展示角色权重如何影响聚类,再继续演进到更丰富的节点模板和领域数据。