跳至内容

力导向模拟

力导向模拟使用 速度Verlet 数值积分器来模拟作用于粒子(节点)的物理力。模拟假设每个步骤的恒定单位时间步长 Δt = 1 以及所有粒子的恒定单位质量 m = 1。因此,作用于粒子的力 F 等效于时间间隔 Δt 内的恒定加速度 a,可以通过简单地将力添加到粒子的速度来模拟,然后将速度添加到粒子的位置。

forceSimulation(nodes)

源代码 · 使用指定的 nodes 数组和无 forces 创建一个新的模拟。如果没有指定 nodes,则默认为空数组。

警告

此函数是不纯的;它会修改传入的 nodes。请参阅 simulation.nodes

js
const simulation = d3.forceSimulation(nodes);

模拟器会 自动启动;使用 simulation.on 监听模拟运行时的 tick 事件。如果希望手动运行模拟,请调用 simulation.stop,然后根据需要调用 simulation.tick

simulation.restart()

源代码 · 重新启动模拟的内部计时器并返回模拟。结合 simulation.alphaTargetsimulation.alpha,此方法可用于在交互过程中“重新加热”模拟,例如拖动节点时,或在使用 simulation.stop 暂时暂停模拟后恢复模拟。

simulation.stop()

源代码 · 停止模拟的内部计时器(如果正在运行),并返回模拟。如果计时器已停止,则此方法不执行任何操作。此方法可用于手动运行模拟;请参阅 simulation.tick

simulation.tick(iterations)

源代码 · 手动将模拟运行指定数量的 iterations 步长,并返回模拟。如果未指定 iterations,则默认为 1(单步)。

对于每次迭代,它会将当前 alpha 增加 (alphaTarget - alpha) × alphaDecay;然后调用每个已注册的 force,传递新的 alpha;然后将每个 node 的速度减少 velocity × velocityDecay;最后将每个节点的位置增加 velocity

此方法不会分发 events;事件仅由内部计时器在模拟 创建 时自动启动或调用 simulation.restart 时分发。当模拟启动时,自然 tick 数为 ⌈log(alphaMin) / log(1 - alphaDecay)⌉;默认情况下,为 300。

此方法可与 simulation.stop 结合使用以计算 静态力导向布局。对于大型图,应在 Web Worker 中 计算静态布局以避免冻结用户界面。

simulation.nodes(nodes)

源代码 · 如果指定了 nodes,则将模拟的节点设置为指定的对象数组,如果需要,则初始化它们的位置和速度,然后 重新初始化 任何绑定的 forces;返回模拟。如果未指定 nodes,则返回模拟的节点数组,如 构造函数 中指定的那样。

警告

此函数是不纯的;它会修改传入的 nodes 以分配索引 node.index、位置 node.x & node.y 以及速度 node.vx & node.vy。位置和速度会在模拟运行时由 simulation.tick 进一步更新。

每个 node 必须是一个对象。以下属性由模拟分配

  • index - 节点在 nodes 中的零索引
  • x - 节点的当前 x 坐标
  • y - 节点的当前 y 坐标
  • vx - 节点的当前 x 速度
  • vy - 节点的当前 y 速度

位置 ⟨x,y⟩ 和速度 ⟨vx,vy⟩ 可能会随后被 forces 和模拟修改。如果 vxvy 为 NaN,则速度将初始化为 ⟨0,0⟩。如果 xy 为 NaN,则位置将在 叶序排列 中初始化,因此选择确保确定性、均匀分布。

要将节点固定在给定位置,您可以指定两个额外的属性

  • fx - 节点的固定 x 坐标
  • fy - 节点的固定 y 坐标

在每次 tick 结束时,在应用任何力后,具有已定义 node.fx 的节点将重置 node.x 为该值,并将 node.vx 设置为零;类似地,具有已定义 node.fy 的节点将重置 node.y 为该值,并将 node.vy 设置为零。要取消固定先前固定的节点,请将 node.fx 和 node.fy 设置为 null,或删除这些属性。

如果指定的 nodes 数组被修改,例如在模拟中添加或删除节点时,则必须使用新的(或更改后的)数组再次调用此方法,以通知模拟和绑定的力有关更改;模拟不会创建指定的数组的防御性副本。

simulation.alpha(alpha)

源代码 · alpha 大致类似于 模拟退火 中的温度。它会随着时间的推移而减小,因为模拟“冷却下来”。当 alpha 达到 alphaMin 时,模拟停止;请参阅 simulation.restart

如果指定了 alpha,则将当前 alpha 设置为指定范围 [0,1] 内的数字,并返回此模拟。如果未指定 alpha,则返回当前 alpha 值,默认值为 1。

simulation.alphaMin(min)

源代码 · 如果指定了 min,则将最小 alpha 设置为指定范围 [0,1] 内的数字,并返回此模拟。如果未指定 min,则返回当前最小 alpha 值,默认值为 0.001。当当前 alpha 小于最小 alpha 时,模拟的内部计时器停止。默认的 alpha 衰减率 约为 0.0228,对应 300 次迭代。

simulation.alphaDecay(decay)

源代码 · 如果指定了 decay,则将 alpha 衰减率设置为指定范围 [0,1] 内的数字,并返回此模拟。如果未指定 decay,则返回当前的 alpha 衰减率,默认为 0.0228… = 1 - pow(0.001, 1 / 300),其中 0.001 是默认的 最小 alpha

alpha 衰减率决定了当前 alpha 如何快速插值到所需的 目标 alpha;由于默认目标 alpha 为零,因此默认情况下这控制了模拟冷却的速度。较高的衰减率会导致模拟更快地稳定,但也可能陷入局部最小值;较低的值会导致模拟运行时间更长,但通常会收敛到更好的布局。要使模拟以当前 alpha 永久运行,请将 decay 率设置为零;或者,设置一个 目标 alpha 大于 最小 alpha

simulation.alphaTarget(target)

源代码 · 如果指定了 target,则将当前目标 alpha 设置为指定范围 [0,1] 内的数字,并返回此模拟。如果未指定 target,则返回当前目标 alpha 值,默认为 0。

simulation.velocityDecay(decay)

源代码 · 如果指定了 decay,则将速度衰减因子设置为指定范围 [0,1] 内的数字,并返回此模拟。如果未指定 decay,则返回当前的速度衰减因子,默认为 0.4。衰减因子类似于大气摩擦;在 tick 期间应用任何力后,每个节点的速度将乘以 1 - decay。与降低 alpha 衰减率 一样,较小的速度衰减可能会收敛到更好的解决方案,但可能会导致数值不稳定和振荡。

simulation.force(name, force)

源代码 · 如果指定了 force,则为指定的 name 分配 并返回此模拟。如果未指定 force,则返回具有指定名称的力,或者如果不存在这样的力,则返回 undefined。例如,要创建一个新的模拟来布局图,你可以说

js
const simulation = d3.forceSimulation(nodes)
    .force("charge", d3.forceManyBody())
    .force("link", d3.forceLink(links))
    .force("center", d3.forceCenter());

要删除具有给定 name 的力,请将 null 传递为 force。例如,要删除电荷力

js
simulation.force("charge", null);

simulation.find(x, y, radius)

源代码 · 返回最靠近位置 ⟨x,y⟩ 的节点,并具有给定的搜索 radius。如果未指定 radius,则默认为无穷大。如果搜索区域内没有节点,则返回 undefined。

simulation.randomSource(source)

源代码)

如果指定了 source,则设置用于生成随机数的函数;这应该是一个返回 0(包含)到 1(不包含)之间的数字的函数。如果未指定 source,则返回此模拟的当前随机源,该源默认为一个固定种子的 线性同余发生器。另见 random.source

simulation.on(typenames, listener)

源代码 · 如果指定了 listener,则为指定的 typenames 设置事件 listener 并返回此模拟。如果为相同类型和名称的事件侦听器已注册,则在添加新的侦听器之前将删除现有的侦听器。如果 listener 为 null,则删除为指定的 typenames 注册的当前事件侦听器(如果有)。如果未指定 listener,则返回与指定的 typenames 匹配的第一个当前分配的侦听器(如果有)。当派发指定的事件时,每个 listener 将使用模拟作为 this 上下文来调用。

typenames 是一个字符串,包含一个或多个由空格分隔的 typename。每个 typename 都是一个 type,可以选择后跟一个句点 (.) 和一个 name,例如 tick.footick.bar;该名称允许为相同 type 注册多个侦听器。type 必须是以下之一

  • tick - 在模拟内部计时器的每次滴答后。
  • end - 在模拟的计时器停止后,当 alpha < alphaMin 时。

请注意,当 simulation.tick 被手动调用时,不会派发 tick 事件;事件仅由内部计时器派发,旨在用于模拟的交互式渲染。要影响模拟,请注册 而不是在 tick 事件侦听器中修改节点的位置或速度。

有关详细信息,请参阅 dispatch.on

自定义力

是一个修改节点位置或速度的函数。它可以模拟电荷或重力等物理力,也可以解决几何约束,例如将节点保持在边界框内或将连接的节点保持在固定距离内。例如,以下是一种将节点移向原点的力

js
function force(alpha) {
  for (let i = 0, n = nodes.length, node, k = alpha * 0.1; i < n; ++i) {
    node = nodes[i];
    node.vx -= node.x * k;
    node.vy -= node.y * k;
  }
}

力通常读取节点的当前位置 ⟨x,y⟩,然后改变节点的速度 ⟨vx,vy⟩。力也可以“提前查看”节点的预期下一个位置 ⟨x + vx,y + vy⟩;这对于通过 迭代松弛 解决几何约束是必要的。力也可以直接修改位置,这有时很有用,可以避免向模拟添加能量,例如在视窗中重新居中模拟时。

force(alpha)

应用此力,可以选择观察指定的 alpha。通常,该力应用于之前传递给 force.initialize 的节点数组,但是,某些力可能应用于节点的子集,或者行为不同。例如,forceLink 应用于每个链接的源和目标。

force.initialize(nodes)

为该力提供 nodes 数组和 random 源。当力通过 simulation.force 绑定到模拟时以及当模拟的节点通过 simulation.nodes 更改时,会调用此方法。力可以在初始化期间执行必要的工作,例如评估每个节点的参数,以避免在每次应用力时重复执行工作。