- 数据可视化
- 前端
- 图表库
【免费下载链接】G6
♾ A Graph Visualization Framework in JavaScript.
Fruchterman(FR)力导向布局是图可视化中最经典的布局算法之一,它通过模拟原子间的斥力与弹簧引力,让节点自动铺开并收敛到能量最低的稳定平衡态。本文以 AntV G6 内置的fruchterman布局为核心,系统讲解其算法原理、全部配置项、基本布局与聚类布局的完整实操代码,并结合仓库源码剖析 G6 如何调度这类迭代式布局、如何通过layout/stopLayout控制布局过程,以及如何注册 GPU 与 WASM 加速变体。读完本文,你将能够独立在 G6 项目中配置出均匀清晰的网络关系图,以及带分组语义的社区聚类图。
概述:什么是 Fruchterman 力导向布局
Fruchterman 布局基于经典的 "Graph Drawing by Force-directed Placement"(力导向放置图绘制)算法实现,属于迭代式力导向布局(iterative force-directed layout)。其核心思想是:把图中的每个节点视作一个带电粒子,节点之间同时存在两种作用力——
- 斥力(repulsive force):任意两个节点之间相互排斥,保证节点不会过度重叠;
- 引力(attractive force / 弹簧力):由边连接的节点之间相互吸引,使相邻节点聚拢,同时所有节点被一个指向画布中心(
center)的中心力拉拢,避免整体漂移出可视区域。
在多轮迭代中,系统不断更新每个节点的位置与速度,模拟物理作用,最终使整个布局自动达到能量最小的稳定平衡状态。在 G6 中,fruchterman布局通过灵活的参数配置可以支持基础均匀分布与聚类布局两种形态。
从源码结构看,Fruchterman 布局并非在 G6 内部重新实现,而是由@antv/layout2.x 提供算法本体、G6 通过适配器接入:在 packages/g6/src/registry/build-in.ts 中,fruchterman: FruchtermanLayout被注册为内置布局;在 packages/g6/src/layouts/types.ts 中,其类型声明为type: 'fruchterman' | 'fruchterman-gpu',说明内置类型同时支持 GPU 加速变体(fruchterman-gpu需额外注册,详见后文)。依赖方面,packages/g6/package.json 声明了@antv/layout: ^2.0.0作为布局算法来源,并可选引入@antv/layout-gpu、@antv/layout-wasm作为加速方案。
使用场景
Fruchterman 布局适合两类典型场景:
- 基础均匀分布:适用于展示节点均匀分布、整体结构清晰的网络关系图,例如网络拓扑、知识图谱。这类图没有明确的语义分组,重点在于让节点舒展、边的交叉尽可能少。
- 聚类布局:适用于具有内部聚合特性或分组的数据可视化展示,例如社区结构展示、关联组分析。通过为节点数据打上分组字段,布局会额外施加"类内引力",让同组节点聚拢成簇,簇与簇之间自然分离。
快速开始:最简 Fruchterman 布局
在 G6 中启用 Fruchterman 布局非常简单,只需在Graph配置的layout字段中指定type: 'fruchterman',并配合基础数据与节点样式即可。以下是最小可运行示例(完整版见后文"展开查看完整代码"):
import { Graph } from '@antv/g6'; const graph = new Graph({ container: 'container', width: 500, height: 250, data: { nodes: [ { id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }, { id: '5' }, { id: '6' }, { id: '7' }, { id: '8' }, { id: '9' }, { id: '10' }, ], edges: [ { source: '0', target: '1' }, { source: '0', target: '2' }, { source: '0', target: '3' }, { source: '0', target: '4' }, { source: '0', target: '7' }, { source: '0', target: '8' }, { source: '0', target: '9' }, { source: '0', target: '10' }, { source: '2', target: '3' }, { source: '4', target: '5' }, { source: '4', target: '6' }, { source: '5', target: '6' }, { source: '9', target: '10' }, ], }, node: { style: { labelFill: '#fff', labelPlacement: 'center', labelText: (d) => d.id, }, }, layout: { type: 'fruchterman', gravity: 5, speed: 5, }, behaviors: ['drag-canvas', 'drag-element'], }); graph.render();说明:上述代码取自文档标准示例。仓库中还提供了一份基于
cluster.json数据集的等价 Demo packages/g6/tests/demos/layout-fruchterman-basic.ts,可直接作为测试与演示起点。
基本布局完整代码
import { Graph } from '@antv/g6'; const data = { nodes: [ { id: '0' }, { id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }, { id: '5' }, { id: '6' }, { id: '7' }, { id: '8' }, { id: '9' }, { id: '10' }, ], edges: [ { source: '0', target: '1' }, { source: '0', target: '2' }, { source: '0', target: '3' }, { source: '0', target: '4' }, { source: '0', target: '7' }, { source: '0', target: '8' }, { source: '0', target: '9' }, { source: '0', target: '10' }, { source: '2', target: '3' }, { source: '4', target: '5' }, { source: '4', target: '6' }, { source: '5', target: '6' }, { source: '9', target: '10' }, ], }; const graph = new Graph({ container: 'container', data, node: { style: { labelFill: '#fff', labelPlacement: 'center', labelText: (d) => d.id, }, }, layout: { type: 'fruchterman', gravity: 5, speed: 5, }, behaviors: ['drag-canvas', 'drag-element'], }); graph.render();配置项详解
Fruchterman 布局的全部配置项如下表所示,其中type为必选,其余均可按需设置:
| 属性 | 描述 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| type | 布局类型 | 'fruchterman' | - | ✓ |
| height | 布局的高度 | number | 容器高度 | |
| width | 布局的宽度 | number | 容器宽度 | |
| maxIteration | 最大迭代次数 | number | 500 | |
| minMovement | 收敛阈值 | number | 0.4 | |
| gravity | 中心力大小,指所有节点被吸引到center的力。数字越大,布局越紧凑 | number | 10 | |
| speed | 每次迭代节点移动的速度。速度太快可能会导致强烈震荡 | number | 5 | |
| onTick | 每一次迭代的回调函数 | (data: LayoutMapping) => void | - |
下面逐个展开关键参数的实际影响:
type(必选):固定为'fruchterman'。G6 会通过注册表(见 packages/g6/src/registry/build-in.ts)找到对应的布局实现。若希望使用 GPU 版本,可在注册@antv/layout-gpu后使用'fruchterman-gpu'(类型上同样被 packages/g6/src/layouts/types.ts 接受)。width/height:布局区域的宽高。默认取容器(画布)尺寸。从源码 packages/g6/src/runtime/layout.ts 可以看到,G6 在初始化图布局时会读取viewport.getCanvasSize()作为默认宽高,并把center设置为画布中心[width / 2, height / 2],然后通过deepMix合并进布局配置。也就是说,布局默认以画布中心为引力中心。maxIteration:最大迭代次数,默认 500。迭代次数越多,布局越接近能量最低的稳定态,但计算耗时也越长。对于大型图(数百以上节点),可以适当降低该值以换取首屏速度。minMovement:收敛阈值,默认 0.4。当节点在一次迭代中的平均位移小于该值时,算法认为布局已收敛并提前终止迭代,避免无谓的计算。gravity:中心力大小,默认 10。所有节点被持续拉向center。数值越大,整体布局越紧凑;数值过小,节点可能散落并漂移出画布。speed:每次迭代节点移动的速度,默认 5。速度越快收敛越快,但速度太快可能导致节点在平衡位置附近强烈震荡,甚至无法收敛。调参时通常需要与gravity配合。onTick:每一次迭代结束后的回调,参数为当前迭代的LayoutMapping(包含各节点最新坐标的布局映射数据)。它是实现"布局过程可视化"的关键:G6 在启用布局动画时,正是通过把onTick回调桥接到updateElementPosition,让节点随着每次迭代实时移动(详见下文"运行时调度"一节)。
布局的基础通用配置
除了上表列出的 Fruchterman 专属参数,layout对象还继承自BaseLayoutOptions(见 packages/g6/src/layouts/types.ts),以下通用能力同样适用于fruchterman:
nodeFilter/comboFilter:函数过滤器,返回boolean,决定哪些节点/组合参与布局。preLayout:是否使用前置布局(在元素初始化前计算布局),不适用于流水线布局。isLayoutInvisibleNodes:不可见节点是否参与布局,仅在preLayout为true时生效。animation:是否启用布局动画。对迭代式布局,开启后会在两次迭代之间做动画过渡(见 packages/g6/src/layouts/types.ts)。enableWorker/iterations:enableWorker控制是否在 WebWorker 中运行布局(缓解大数据量下主线程阻塞);iterations是迭代式布局的迭代次数设置,G6 运行时默认取 300(见 packages/g6/src/runtime/layout.ts)。
聚类布局:让分组数据自动聚簇
当数据具有明确的分组字段时,可以开启聚类布局。G6 会为每个分组额外施加"类内引力",使同一组的节点聚拢成簇,不同簇之间自然分开,非常适合社区发现、关联组分析等场景。
聚类专属配置项
| 属性 | 描述 | 类型 | 默认值 | 必选 |
|---|---|---|---|---|
| clustering | 是否按照聚类布局 | boolean | false | |
| nodeClusterBy | 聚类布局依据的节点数据data中的字段名,在clustering为true时生效 | string | 'cluster' | |
| clusterGravity | 聚类内部的重力大小,影响聚类的紧凑程度,在clustering为true时生效 | number | 10 |
聚类布局完整示例
下面的示例中,节点数据通过data: { cluster: 'a' | 'b' | 'c' }声明分组,布局配置clustering: true、nodeClusterBy: 'cluster'指定按cluster字段聚类,clusterGravity: 3控制类内紧凑度;节点调色板使用type: 'group'按分组字段上色,便于视觉区分:
import { Graph } from '@antv/g6'; const graph = new Graph({ container: 'container', width: 500, height: 250, data: { nodes: [ { id: '0', data: { cluster: 'a' } }, { id: '1', data: { cluster: 'a' } }, { id: '2', data: { cluster: 'a' } }, { id: '3', data: { cluster: 'a' } }, { id: '4', data: { cluster: 'a' } }, { id: '5', data: { cluster: 'b' } }, { id: '6', data: { cluster: 'b' } }, { id: '7', data: { cluster: 'b' } }, { id: '8', data: { cluster: 'c' } }, { id: '9', data: { cluster: 'c' } }, { id: '10', data: { cluster: 'c' } }, ], edges: [ { source: '0', target: '1' }, { source: '0', target: '2' }, { source: '0', target: '4' }, { source: '0', target: '6' }, { source: '2', target: '3' }, { source: '2', target: '4' }, { source: '3', target: '4' }, { source: '5', target: '6' }, { source: '6', target: '7' }, { source: '7', target: '8' }, { source: '8', target: '9' }, { source: '8', target: '10' }, ], }, node: { style: { labelFill: '#fff', labelPlacement: 'center', labelText: (d) => `${d.data.cluster}-${d.id}`, }, palette: { type: 'group', field: 'cluster', }, }, edge: { style: { endArrow: true, }, }, layout: { type: 'fruchterman', gravity: 6, speed: 5, // 聚类布局参数 clustering: true, nodeClusterBy: 'cluster', clusterGravity: 3, }, behaviors: ['drag-canvas', 'drag-element'], }); graph.render();提示:仓库中 packages/g6/tests/demos/layout-fruchterman-cluster.ts 提供了等价实现,其数据来自
cluster.json数据集;值得注意是 Demo 在构造节点时做了字段摊平处理(把data.cluster复制到节点顶层cluster字段),说明nodeClusterBy读取的字段既可以是节点data内的字段,也可以按数据实际结构进行适配。
聚类布局完整代码
import { Graph } from '@antv/g6'; const data = { nodes: [ { id: '0', data: { cluster: 'a' } }, { id: '1', data: { cluster: 'a' } }, { id: '2', data: { cluster: 'a' } }, { id: '3', data: { cluster: 'a' } }, { id: '4', data: { cluster: 'a' } }, { id: '5', data: { cluster: 'b' } }, { id: '6', data: { cluster: 'b' } }, { id: '7', data: { cluster: 'b' } }, { id: '8', data: { cluster: 'c' } }, { id: '9', data: { cluster: 'c' } }, { id: '10', data: { cluster: 'c' } }, ], edges: [ { source: '0', target: '1' }, { source: '0', target: '2' }, { source: '0', target: '4' }, { source: '0', target: '6' }, { source: '2', target: '3' }, { source: '2', target: '4' }, { source: '3', target: '4' }, { source: '5', target: '6' }, { source: '6', target: '7' }, { source: '7', target: '8' }, { source: '8', target: '9' }, { source: '8', target: '10' }, ], }; const graph = new Graph({ container: 'container', data, node: { style: { labelFill: '#fff', labelPlacement: 'center', labelText: (d) => `${d.data.cluster}-${d.id}`, }, palette: { type: 'group', field: 'cluster', }, }, edge: { style: { endArrow: true, }, }, layout: { type: 'fruchterman', gravity: 6, speed: 5, // 聚类布局参数 clustering: true, nodeClusterBy: 'cluster', clusterGravity: 3, }, behaviors: ['drag-canvas', 'drag-element'], }); graph.render();源码剖析:G6 如何调度迭代式布局
理解 Fruchterman 在 G6 中的执行路径,有助于针对大数据量场景做正确的调优。核心逻辑位于 packages/g6/src/runtime/layout.ts 的graphLayout方法,G6 通过isLayoutWithIterations判断布局是否支持逐帧迭代,并据此分两条路径处理:
启用布局动画(
animation为真):调用layout.execute(data, { animate: true, maxIteration: iterations, onTick }),其中onTick被桥接为(tickData) => this.updateElementPosition(tickData, false),即每迭代一次就把最新坐标写回数据模型并重绘元素,从而在画布上呈现力导向逐渐展开的动画过程。关闭布局动画(默认):直接
layout.execute(data)跑完整迭代,随后layout.stop()停止,再调用layout.tick(iterations)取出终态位置一次性更新,首帧即呈现最终布局结果。
此外,graphLayout前会通过initGraphLayout做配置装配(packages/g6/src/runtime/layout.ts):从 viewport 读取画布尺寸作为width/height,计算画布中心作为center,并从元素计算得到nodeSize,最后deepMix(layout.options, config, restOptions)将用户配置、默认值与运行时信息合并。
Fruchterman 算法本体来自@antv/layout2.x。G6 在 packages/g6/src/utils/layout.ts 中提供了layoutAdapter适配器,把@antv/layout的布局类包装成 G6 的BaseLayout子类:它负责把 G6 的GraphData转换为布局模型(节点含x/y/z、parentId、isCombo等字段),执行布局后把每个节点的坐标、边上的controlPoints等结果回写到 G6 数据模型;onTick也会被包装为把布局内部数据转换回 G6GraphData再回调。对于更早期的@antv/layout1.x 布局,则走legacyLayoutAdapter(packages/g6/src/utils/layout.ts),用graphlib图结构承载数据并处理 combo 层级关系。
布局过程控制:重新布局与停止迭代
力导向布局在数据量较大或迭代较慢时,可能长时间处于动画迭代中。G6 提供了两个与布局生命周期相关的图实例方法(见 packages/g6/src/runtime/graph.ts):
graph.layout(layoutOptions?):手动触发(重新)布局,可传入新的布局配置。常用于数据更新或交互后重新布局。graph.stopLayout():停止当前布局迭代。对迭代式布局(如fruchterman、force)有效,一般用于布局迭代时间过长时手动中断,例如在点击画布/节点的监听中调用。
仓库中的 packages/g6/tests/demos/layout-fruchterman-fix.ts 演示了一种典型组合:监听node:dragstart调用graph.stopLayout()冻结力导向迭代,避免拖拽节点时布局反复拉扯;监听node:dragend调用graph.layout()基于当前数据重新布局。这为"拖拽固定节点 + 力导向重排"的交互需求提供了可直接参考的实现范式(该 Demo 源码注释也指出,当前实现是整体重新布局,而非仅基于当前画布数据局部迭代)。
进阶加速:GPU 与 WASM 变体
对于上万节点的巨型图,纯 CPU 的 Fruchterman 迭代可能力不从心。G6 仓库的依赖中声明了@antv/layout-gpu与@antv/layout-wasm(见 packages/g6/package.json),并提供了对应的接入 Demo:
GPU 加速:packages/g6/tests/demos/layout-fruchterman-gpu.ts 通过
register('layout', 'fruchterman-gpu', (await import('@antv/layout-gpu')).FruchtermanLayout)注册 GPU 布局,然后在layout配置中指定type: 'fruchterman-gpu',并支持maxIteration、minMovement、gravity、speed等与 CPU 版一致的参数,以及distanceThresholdMode: 'mean'等 GPU 专属选项。WASM 加速:packages/g6/tests/demos/layout-fruchterman-wasm.ts 展示了基于
@antv/layout-wasm的接入方式:从包中导入FruchtermanLayout、initThreads、supportsThreads,用supportsThreads()探测浏览器多线程能力,再initThreads(supported)初始化线程池,注册'fruchterman-wasm'后在配置中传入threads、dimensions: 2等参数。
两个变体的注册方式完全一致——register('layout', '<类型名>', 布局类),之后即可像内置布局一样在layout.type中引用。这说明 G6 的布局注册表是开放可扩展的(注册入口见 packages/g6/src/registry/build-in.ts 的整体结构),第三方布局可以无缝接入。
测试与验证:快照回归
Fruchterman 布局在仓库中有对应的单元测试 packages/g6/tests/unit/layouts/fruchterman.spec.ts,覆盖basic与cluster两个用例:测试通过createDemoGraph分别创建基于 layout-fruchterman-basic.ts 与 layout-fruchterman-cluster.ts 的图实例,并使用toMatchSnapshot与渲染快照比对,防止布局行为发生非预期回归。由于力导向布局依赖随机初始位置,测试在beforeEach中调用mockRandom()固定随机种子,保证快照结果可复现。如果你在改造或自定义布局,可以参考该模式建立自己的回归测试。
调参实践建议
最后给出几条基于参数语义与源码行为的实用建议:
- 先看连通性,再调力:图明显松散时增大
gravity(默认 10),节点扎堆时减小gravity并适当调大speed;gravity与clusterGravity配合可控制"簇内紧、簇外松"的视觉层次。 - 大数据量优先降迭代:把
maxIteration从 500 下调(如 200~300),并保持minMovement收敛阈值,可显著缩短首次渲染耗时;若仍需流畅动画,配合iterations(运行时默认 300)控制动画总帧数。 - 交互场景善用生命周期:拖拽节点时先
stopLayout()再layout()重排,可避免布局与交互互相干扰,参考 layout-fruchterman-fix.ts。 - 超大规模图切换加速方案:节点数上万时,考虑通过
register接入fruchterman-gpu或fruchterman-wasm变体;G6 的内置注册表与适配器(packages/g6/src/utils/layout.ts)已经为这类扩展做好了桥接。
至此,你已经掌握了 G6 Fruchterman 力导向布局从基础配置、聚类模式到源码调度与加速方案的全链路知识,可以直接在自己的项目中落地实践。
- 数据可视化
- 前端
- 图表库
【免费下载链接】G6
♾ A Graph Visualization Framework in JavaScript.
相关推荐
G6 Fruchterman 力导向布局实战:基本使用、聚类、Web Worker 与 GPU 加速
G6 Fruchterman 力导向布局实战:基本使用、聚类、Web Worker 与 GPU 加速 导读 Fruchterman 是一种经典的力导向(Forc
数据可视化前端图表库G6 Fruchterman 力导向布局完全指南:参数配置、聚类模式与底层实现原理
G6 Fruchterman 力导向布局完全指南:参数配置、聚类模式与底层实现原理 Fruchterman 布局是 G6(JavaScript 图可视化框架)内
数据可视化前端图表库G6 D3Force 力导向布局实战指南:五种基础力、迭代控制与团队聚类案例
G6 D3Force 力导向布局实战指南:五种基础力、迭代控制与团队聚类案例 本篇指南系统讲解 G6( @antv/g6 )内置的 d3 force 力导向布局
数据可视化前端图表库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考