JavaScript is required

力导向布局节点权重与连线弹性

这是一个全屏力导布局调试场,用于在同一实时图上对比按节点权重和按连线弹性两种效果。用户可随机化取值范围、重新调参运行中的求解器,并在 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-forceperformance-test-force-layout 相比,这个示例的重点也不主要是一个基础实验场或规模测试。它增加了分阶段重置流程、显式数值编码以及轻量级图结构变更,因此用户可以通过视觉比较受力行为,而不是仅仅拖动滑块、观察布局漂移。

line-shape-and-label 相比,这里的连线更新主要不是为了连接线渲染效果。它们存在的目的是把 force_elastic 暴露到求解器内部,因此连线标签和线宽会变成这场力学实验的测量读数。

在对比数据中,这个示例最强的一组少见组合是:观测模式切换、逐节点 force_weight 覆盖、逐连线 force_elastic 覆盖、在线 ForceLayout.updateOptions(...) 调优,以及在同一个可拖拽工作区内执行 “添加两个子节点” 变更。这使它比起通用图浏览场景,更适合作为比较式力行为研究的起点。

这种模式还适用于哪里

这种模式非常适合用于在加入真实样式之前,解释或调试布局行为的内部工具。团队可以用同样的方法测试依赖强度、工作负载权重、影响力分数或通信强度应如何影响图上的间距。

它也适用于教学和调优场景。例如,工作流设计器可以比较阶段之间的强弱转换,基础设施团队可以可视化高负载服务与弱耦合关系,组织结构图原型也可以先展示角色权重如何影响聚类,再继续演进到更丰富的节点模板和领域数据。