首页
/ d3-force 位置力详解:forceX、forceY 与 forceRadial 的原理、参数与实战

d3-force 位置力详解:forceX、forceY 与 forceRadial 的原理、参数与实战

2026-09-04 21:03:46作者:宣利权Counsellor

本文基于 d3 官方文档 位置力参考,系统讲解 d3-force 模块中的三类位置力(x 力、y 力、径向力)的创建方式、strength / x / y / radius 等访问器 API 的确切语义与默认值,并结合本仓库中的仿真示例与模拟器文档,说明这些力如何嵌入 velocity Verlet 积分循环、如何与 alpha 冷却机制协同工作,以及在实际布局中该如何取值调参。

位置力是什么:沿指定维度把节点推向目标位置

在 d3-force 的力导向仿真中,位置力(Position forces)由三个力构成:

  • x 力(forceX:沿 x 轴把节点推向给定的目标位置;
  • y 力(forceY:沿 y 轴把节点推向给定的目标位置;
  • 径向力(forceRadial:与二者类似,但把节点推向以某点为圆心、给定半径的圆上距离节点最近的点

三者的共同特征是:力的强度与“节点当前位置到目标位置的一维距离成正比——离目标越远,推力越大,接近目标后推力逐渐减弱,因此节点会平滑地趋近目标而不是生硬地跳过去。文档同时强调,虽然这些力也可以用来定位个别节点,但其主要用途是作为作用于所有(或大多数)节点的全局力,例如把整张图拉向画布中心。

CHANGES.md 的历史记录可以看出:d3-force v3 重构时引入了独立的 x / y 位置力,取代了旧版 d3.layout.force 中的 gravity(重力)配置,并且力参数普遍支持按节点或按链接单独配置。也就是说,位置力是 d3 力仿真“可组合、可扩展”设计的一部分:仿真器本身不再内置任何力,你在 仿真 上通过 simulation.force(name, force) 自由挂载。

forceX(x):创建 x 方向的位置力

const x = d3.forceX(width / 2);

forceX(*x*) 创建一个沿 x 轴指向给定位置 x 的新位置力。若未指定 x,目标位置默认为 0。典型用法是把图整体拉向画布水平中心 width / 2

x.strength(strength):力的强度

若指定 strength,则设置强度访问器(number 或 function),对每个节点重新求值后返回该力;若不指定,则返回当前强度访问器。

strength 决定每次施加力时为节点 x 速度增加的量,计算式为:

Δvx = (*x* - node.x) × strength

例如取 0.1 表示每次施加时,节点向其目标 x 位置移动“剩余距离的十分之一”。值越大,节点越快到达目标位置,但通常以牺牲其他力或约束的效果为代价(比如把斥力、链接力压过)。官方明确建议:不建议使用 [0,1] 范围之外的值——小于 0 会反转方向(变成排斥),大于 1 会导致速度超调、数值不稳定。

默认强度访问器为:

function strength() {
  return 0.1;
}

访问器会被用于仿真中的每个节点(参见 仿真节点),调用时传入 node 与其零基 index。返回的数字会被内部缓存,即每个节点的强度只在两种时机重新计算:

  1. 该力被 initialize(绑定到新仿真或节点数组变更时);
  2. 调用 x.strength(新值) 时。

而不是在每次 tick 施加力时都重新求值——这对性能敏感的大图布局很重要。

x.x(x):目标 x 坐标访问器

若指定 x,设置 x 坐标访问器(number 或 function),对每个节点重新求值后返回该力;若不指定,返回当前 x 访问器,默认为:

function x() {
  return 0;
}

与 strength 相同,x 访问器接收 node 与零基 index,结果数字被内部缓存,目标坐标只在力初始化或调用 x.x(新值) 时重新计算,而非每次施加力时。利用“可按节点取不同目标值”这一点,可以做出分层布局:例如把不同组的节点推向不同的纵列位置:

const x = d3.forceX()
    .x((d) => d.column * 160)   // 每个节点的目标 x 由其所属列决定
    .strength(0.1);

forceY(y):创建 y 方向的位置力

forceYforceX 完全对称:

const y = d3.forceY(height / 2);

forceY(*y*) 创建沿 y 轴指向给定位置 y 的位置力,未指定时默认为 0

y.strength(strength)

指定时设置强度访问器并返回该力;未指定时返回当前访问器。strength 决定为节点 y 速度增加的量:

Δvy = (*y* - node.y) × strength

同样地,0.1 表示每次移动剩余距离的十分之一;高值收敛更快但可能压过其他力;不建议使用 [0,1] 范围之外的值。默认访问器:

function strength() {
  return 0.1;
}

访问器对每个节点求值,传入 node 与零基 index,结果被内部缓存,仅在力初始化或设置新 strength 时重算。

y.y(y)

指定时设置 y 坐标访问器并返回该力;未指定时返回当前访问器,默认为:

function y() {
  return 0;
}

语义与 x.x 一致:访问器按节点求值、结果缓存、仅在初始化或重新设置时更新。实践中 forceX() + forceY() 成对使用、均指向画布中心,是 d3 网络图最经典的“软居中”手段。

forceRadial(radius, x, y):径向位置力

const radial = d3.forceRadial(r, width / 2, height / 2);

forceRadial(*radius*, *x*, *y*) 创建一个朝向指定 radius、以 ⟨x, y⟩ 为圆心的的位置力。xy 未指定时默认圆心为 ⟨0,0⟩。与一维位置力不同,径向力的目标是“圆上离节点最近的点”,因此节点最终会散布在这个圆环上,适合做同心环图、旭日式布局或按层级半径分层的网络图。

radial.strength(strength)

指定时设置强度访问器并返回该力;未指定时返回当前访问器。strength 决定为节点 xy 速度增加的量:0.1 表示每次施加时,节点向圆上最近点移动剩余距离的十分之一。高值收敛更快但通常以牺牲其他力或约束为代价;不建议使用 [0,1] 范围之外的值。默认访问器:

function strength() {
  return 0.1;
}

访问器对每个节点求值(传入 node 与零基 index),结果内部缓存,仅在力初始化或设置新 strength 时重算。

radial.radius(radius)

指定时把圆的半径设置为给定的 number 或 function,对每个节点重新求值后返回该力;未指定时返回当前 radius 访问器。与 strength、坐标访问器同理,radius 访问器对每个节点求值(传入 node 与零基 index),结果被缓存,目标半径仅在力初始化或调用 radial.radius(新值) 时重新计算。按节点返回不同半径(如按层级 depth 取值)即可实现多层同心环。

radial.x(x) 与 radial.y(y)

  • radial.x(*x*):指定时把圆心 x 坐标设置为给定的 number 并返回该力;未指定时返回当前圆心 x 坐标,默认为 0
  • radial.y(*y*):指定时把圆心 y 坐标设置为给定的 number 并返回该力;未指定时返回当前圆心 y 坐标,默认为 0

注意二者与 x.x() / y.y() 的语义差异:径向力的圆心坐标是普通数值,不是按节点求值的访问器——圆心对全体节点是同一个点,而 forceRadial 中按节点变化的量只有 radiusstrength

位置力如何嵌入仿真的每个 tick

理解参数行为的前提是理解力的执行时机。按 仿真正文 的描述,d3-force 实现了一个 velocity Verlet 数值积分器,假设恒定单位时间步 Δt = 1 与单位质量 m = 1,因此力等价于对该时间段的恒定加速度,直接累加到粒子速度上再累加到位置。

每次 simulation.tick 的流程是:

  1. 当前 alpha 增加 (alphaTarget - alpha) × alphaDecay(默认 alphaDecay ≈ 0.0228,对应 300 次迭代后冷却到 alphaMin = 0.001);
  2. 依次调用每个已注册的力,并传入新的 alpha——位置力就在此刻把 Δv = (目标 - 当前位置) × strength 计入节点速度;
  3. 每个节点速度按 velocity × velocityDecay(默认 0.4)衰减;
  4. 节点位置增加速度。

由此可以推断位置力调参的两个联动关系:

  • alpha 冷却决定“收手”时机:力每次 tick 都收到当前 alpha,仿真逐渐冷却、alpha 降到 alphaMin 时定时器停止。位置力越晚被加入或 strength 越大,越可能在冷却前改变最终布局;
  • velocityDecay 的摩擦竞争:默认 0.4 的速度衰减相当于空气阻力。位置力 strength 高、摩擦低时节点运动更活跃,适合交互中持续重加热;反之则布局更快静止。

另外,节点的固定位置 fx / fy(见 simulation.nodes)在每个 tick 力施加完成后被强制重置,因此被 fx/fy 钉住的节点不会被位置力移动——拖拽交互中正是利用了这一点。

仓库中的真实用法

本仓库 VitePress 文档站自带了两个直接使用位置力的示例组件,展示了不同的取值策略:

默认强度居中——力导向图主页示例 ExampleDisjointForce.vue 用默认参数成对挂载 x、y 力,把整张图拉向坐标原点附近:

simulation = d3.forceSimulation(nodes)
    .force("link", d3.forceLink(links).id((d) => d.id))
    .force("charge", d3.forceManyBody())
    .force("x", d3.forceX())
    .force("y", d3.forceY())
    .on("tick", ticked);

这里 forceX() / forceY() 的目标坐标与强度全部取默认值(目标 0、strength 0.1),SVG 的 viewBox[-width/2, -height/2, width, height] 定义、原点居中,因此节点自然聚集在画布中央。

弱强度“围栏”——碰撞检测示例 ExampleCollideForce.vue 把强度调低到 0.01

simulation = d3.forceSimulation(nodes)
    .velocityDecay(0.1) // low friction
    .force("x", d3.forceX().strength(0.01))
    .force("y", d3.forceY().strength(0.01))
    .force("collide", d3.forceCollide().radius((d) => d.r + 1).iterations(4))
    .force("charge", d3.forceManyBody().strength((d, i) => i ? 0 : -width * 2 / 3))
    .on("tick", ticked);

该示例把 velocityDecay 降到 0.1 以降低摩擦、让碰撞分离更明显,同时把 x/y 力降到 0.01,只起“防止节点逃逸出画布”的软约束作用,而不是主导布局——这正是文档所说“高值会牺牲其他力效果”的逆向应用:当布局应由碰撞、斥力等主导时,位置力就应弱化为边界约束。

参数速查与调参建议

创建签名 默认目标 strength 默认 可访问器化的参数
x 力 d3.forceX(x) x = 0 0.1 strengthx
y 力 d3.forceY(y) y = 0 0.1 strengthy
径向力 d3.forceRadial(radius, x, y) 圆心 ⟨0,0⟩ 0.1 strengthradiusxy 为普通数值)

调参要点(均以 位置力文档 的表述为准):

  1. strength 保持在 [0,1] 内0.1 是“每次靠近剩余距离 10%”的默认折中;0.01 量级适合做围栏/软约束;接近 1 时节点几乎瞬间吸附到目标,会压过斥力与链接力,还可能振荡。
  2. 坐标/半径/强度都可以按节点取值。传入函数即可在节点级别差异化,例如按 groupdepth 指定不同的目标列、目标半径或强度;记住访问器结果被缓存,只在 initialize 或 setter 调用时重算——若依赖运行时变化的外部状态,需重新调用 setter 触发重算。
  3. x/y 力是“软”居中,不同于 forceCenter 的刚性平移fx/fy 钉住的节点不受其影响。
  4. 径向力的圆心是全局唯一数值,按节点变化的量只有 radiusstrength,设计多层环时靠 radial.radius(d => ...) 区分层级。

完整的 API 索引可在 docs/api.md 的 d3-force 一节中找到:d3.forceXx.strengthx.xd3.forceYy.strengthy.yd3.forceRadialradial.strengthradial.radiusradial.xradial.y 十一条均对应 docs/d3-force/position.md 中的同名小节;与仿真器的关系参见 docs/d3-force.mddocs/d3-force/simulation.md

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384