d3-force

该模块实现了用于在粒子上模拟物理力的velocity Verlet数值积分器。模拟是简化了的:对于每个步骤假设恒定的单位时间步长Δt = 1,对于所有粒子假定恒定的单位质量m = 1。结果,作用于粒子的力F相当于在时间间隔Δt内的恒定加速度α,并且可以简单地通过增加粒子的速度来模拟,然后将其加到粒子的位置。

在信息可视化领域,物理模拟对研究networks(网络)hierarchies(层级)非常有用!

你还可以模拟具有碰撞的圆(磁盘),例如bubble charts(气泡图)或beeswarm plots(蜂群图):

你甚至可以用它作为一个基本的物理引擎,比如模拟布料:

要使用此模块,请为节点数组创建一个simulation(模拟),并组合所需的。然后监听tick事件,以便在你首选的图形系统(如Canvas或SVG)中更新节点时渲染节点。

Installing

如果你使用npm,请键入npm install d3-force。否则,请下载最新版本。你也可以直接从d3js.org加载,作为standalone library(独立库)或D3 4.0的一部分来使用。支持AMD,CommonJS和vanilla环境。在vanilla环境下,会输出一个全局的d3_force

<script src="https://d3js.org/d3-collection.v1.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-quadtree.v1.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<script src="https://d3js.org/d3-force.v1.min.js"></script>
<script>

var simulation = d3.forceSimulation(nodes);

</script>

API Reference

Simulation

d3.forceSimulation([nodes])

用指定的nodes数组创建一个新的模拟,不应用任何。如果不指定nodes,则默认为空数组。模拟器自动启动 ;使用simulation.on在模拟运行时监听tick事件。如果你想手动运行模拟,请调用simulation.stop,然后调用simulation.tick

simulation.restart()

重启模拟的内部计时器并返回此模拟。会同simulation.alphaTargetsimulation.alpha,此方法可用于在交互期间“reheat(热更新)”模拟,就像拖动节点时一样,或在使用simulation.stop暂停之后恢复模拟。

simulation.stop()

停止模拟的内部计时器(如果它正在运行)并返回此模拟。如果计时器已经停止,此方法将什么也不做。此方法对于手动运行模拟非常有用;另请参阅simulation.tick。

simulation.tick()

通过(alphaTarget - alpha)× alphaDecay增加当前alpha ;然后调用每个注册的,传入新的alpha; 然后通过velocity × velocityDecay递减每个节点的速度 ;最后通过velocity递增每个节点的位置。

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

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

simulation.nodes([nodes])

如果指定了nodes,则设置模拟的节点为指定的对象数组,初始化它们的位置和速度(必要时),然后重新初始化任何bound forces(束缚力) ;返回此模拟。如果不指定nodes,则返回指定给构造函数的模拟的节点数组。

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

  • index- nodes中从0开始的索引
  • x- 节点当前的x位置
  • y- 节点当前的y位置
  • vx- 节点当前的x速度
  • vy- 节点当前的y速度

位置⟨x,y ⟩和速度⟨ vx,vy ⟩之后可以通过和模拟修改。如果vxvy是NaN,则速度初始化为⟨0,0⟩。如果xy是NaN,则该位置将以phyllotaxis arrangement(叶序排列)进行初始化,以便确保原点周围具有确定的均匀分布。

要固定给定位置的节点,你可以指定两个附加属性:

  • fx- 节点的固定x位置
  • fy- 节点的固定y位置

在每次tick结束时,在应用任何力之后,定义了node.fx的节点将node.x重置为此值,并将node.vx设置为0;同样,定义了node.fy的节点将node.y重置为此值,并将node.vy设置为0。要释放之前固定的节点,请将node.fx和node.fy设置为空,或删除这些属性。

如果修改了指定的nodes数组,例如将节点添加到模拟或从模拟中移除,则必须使用新(或更改)数组再次调用此方法,以通知模拟和束缚力相应的变化;模拟不会制作指定数组的defensive copy(保护性拷贝)。

simulation.alpha([alpha])

如果指定了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是0,默认情况下,它控制模拟冷却的速度。较高的衰减速率使模拟更加稳定,但风险是在局部最小时会卡住; 较低的值会导致模拟运行时间更长,但通常会收敛到更好的布局。为了让模拟在当前的alpha上永久运行,请将decay率设置为0 ;或者,设置一个目标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。(默认情况下,新的模拟没有力。)例如,要创建一个新的模拟来布置图形,你可以这样写:

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

要移除给定name的力,请将null作为force传入。例如,要移除charge力:

simulation.force("charge", null);
simulation.find(x, y[, radius])

用给定的搜索radius返回最接近位置⟨x,y⟩的节点。如果不指定radius,则默认为无穷大。如果搜索区域内没有节点,则返回undefined。

simulation.on(typenames, [listener])

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

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

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

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

另请参阅dispatch.on了解更多。

Forces

force仅仅是修改节点的位置或速度的函数;在这种情况下,force可以应用诸如charge或重力之类的经典物理力,它也可以解决几何约束,例如将节点保持在边界框内或保持链接节点间固定的距离。例如,将节点移动到原点⟨0,0⟩的简单positioning force(定位力)可以实现为:

function force(alpha) {
  for (var 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⟩;这对于通过iterative relaxation(迭代松弛法)来解决几何约束是必需的。力也可以直接修改位置,这有时可用于避免向模拟添加能量,例如在视口中将模拟重新居中时。

模拟通常根据需要组合多个力。此模块提供了几个供你消遣:

力可以选择执行force.initialize来接收模拟的节点数组。

force(alpha)

应用此力,可选择观察指定的alpha。通常,该力被应用到先前传递到force.initialize的节点数组,然而,一些力可应用到节点的子集,或表现不同。例如,d3.forceLink适用于每个链接的source(源)和target(目标)。

force.initialize(nodes)

nodes数组分配给此力。当一个力通过simulation.force绑定到一个模拟并且当模拟的节点通过simulation.nodes改变时,此方法将被调用。力可以在初始化期间执行必要的工作,诸如对每个节点的参数求值,以避免在每次施加力时重复执行工作。

Centering

centering force(定心力)均匀地转换节点,使得所有节点的平均位置(重心,如果所有节点都具有相同的权重)在给定位置⟨ xy ⟩。此力修改每个应用程序上节点的位置;它不会修改速度,因为这样做通常会导致节点过冲并在所需中心周围振荡。此力有助于将节点保持在视口的中心,与定位力不同,它不会扭曲它们的相对位置。

d3.forceCenter([x, y])

用给定xy坐标创建一个新的定心力。如果不指定xy,则默认为⟨0,0⟩。

center.x([x])

如果指定了x,则设置居中位置的x坐标为给定的数字,并返回此力。如果不指定x,则返回当前的x坐标,默认为0。

center.y([y])

如果指定了y,则设置居中位置的y坐标为指定的数字,并返回此力。如果不指定y,则返回当前的y坐标,默认为0。

Collision

collision force(碰撞力)将节点视为具有给定半径的圆而不是点,并防止节点重叠。更正式地说,两个节点ab是分开的,这样ab之间的距离至少是radius(a)+ radius(b)。为了减少抖动,默认情况下,这是一个具有可配置强度迭代次数的“软”约束。

d3.forceCollide([radius])

用指定的radius创建一个新的圆形碰撞力。如果不指定radius,则默认为所有节点都为常量1。

collide.radius([radius])

如果指定了radius,则设置radius accessor为指定的数字或函数,对每个节点的radius accessor重新求值,并返回该力。如果不指定radius,则返回当前的radius accessor,默认为:

function radius() {
  return 1;
}

为模拟中的每个节点调用radius accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,这样每个节点的半径仅在力被初始化时或在用新radius调用此方法时才重新计算,而不是在力的每次应用时重新计算。

collide.strength([strength])

如果指定了strength,则将力强度设置为给定的数字(范围为[0,1]),并返回此力。如果不指定strength,则返回当前强度,默认为0.7。

重叠节点通过迭代松弛法来解决。对于每个节点,可确定其他预期将在下一tick重叠(使用预期位置⟨x + vx,y + vy⟩)的节点;然后修改节点的速度以将节点推出每个重叠节点。速度的变化会受到力强度的影响,因此同时发生的重叠可以混合在一起以找到稳定的解决方案。

collide.iterations([iterations])

如果指定了iterations,则设置每个应用的迭代次数为给定的数字,并返回此力。如果不指定iterations,则返回当前迭代计数,默认为1。增加迭代次数会极大地增加约束的刚度并避免节点的部分重叠,但同时会增加运行时评估力的成本。

link force(链接力)按照期望的链接距离将链接的节点推到一起或分开。力的强度与链接节点的距离和目标距离之间的差成正比,类似于spring force(弹簧力)。

使用指定的links和默认参数创建一个新的链接力。如果不指定links,则默认为空数组。

如果指定了links,则设置与该力相关联的链接数组,为每个链接重新计算距离强度参数,并返回该力。如果不指定links,则返回当前的链接数组,默认为空数组。

每个链接都是具有以下属性的对象:

  • source- 链接的源节点;另请参阅simulation.nodes
  • target- 链接的目标节点;另请参阅simulation.nodes
  • index- 此方法分配的从0开始的links的索引。

为了方便起见,可以使用数字或字符串标识符而不是对象引用来初始化链接的源和目标属性;另请参阅links.id。当链接力被初始化(或重新初始化,当节点或链接改变时)时,任何不是对象的link.source或link.target属性都会被对象引用替换为具有给定标识符的相应node

如果修改了指定的links数组,例如向模拟添加链接或从模拟中移除链接,则必须再次使用新(或更改)数组调用此方法以通知力相应的变化;该力不会制作指定数组的保护性副本。

如果指定了id,则设置节点id accessor为指定的函数,并返回此力。如果不指定id,则返回当前的节点id accessor,默认为数字的node.index:

function id(d) {
  return d.index;
}

默认的id accessor允许将每个链接的源和目标指定为节点数组中的从0开始的索引。例如:

var nodes = [
  {"id": "Alice"},
  {"id": "Bob"},
  {"id": "Carol"}
];

var links = [
  {"source": 0, "target": 1}, // Alice → Bob
  {"source": 1, "target": 2} // Bob → Carol
];

现在考虑一个不同的id accessor,它返回一个字符串:

function id(d) {
  return d.id;
}

有了这个accessor,你可以使用具名的源和目标:

var nodes = [
  {"id": "Alice"},
  {"id": "Bob"},
  {"id": "Carol"}
];

var links = [
  {"source": "Alice", "target": "Bob"},
  {"source": "Bob", "target": "Carol"}
];

这在用JSON表示图形时特别有用,因为JSON不允许引用。请参阅这个例子

每当节点被初始化时,就会为每个节点调用id accessor,就像节点链接发生变化时一样,传入该node及其从0开始的index

如果指定了distance,则设定distance accessor为指定的数字或函数,为每个链接的distance accessor重新求值,并返回该力。如果不指定distance,则返回当前的distance accessor,默认为:

function distance() {
  return 30;
}

为每个链接调用distance accessor,并传入link及其从0开始的index。然后将得到的数字存储在内部,这样每个链接的距离仅在力被初始化时或者当这个方法用新distance调用时才重新计算,而不是在力的每次应用时重新计算。

如果指定了strength,则将strength accessor设置为指定的数字或函数,为每个链接的strength accessor重新求值,并返回此力。如果不指定strength,则返回当前的strength accessor,默认为:

function strength(link) {
  return 1 / Math.min(count(link.source), count(link.target));
}

其中count(node)是一个函数,它返回给定节点作为源或目标的链接数。选择此默认值是因为它会自动降低链接到重度链接节点的链接的强度,从而提高稳定性。

为每个链接调用strength accessor,并传入link及其从0开始的index。然后将得到的数字存储在内部,这样每个链接的强度只在力被初始化时或者以新的strength调用此方法时才重新计算,而不是在力的每次应用时重新计算。

如果指定了iterations,则设置每个应用的迭代次数为指定的数字,并返回此力。如果不指定iterations,则返回当前的迭代计数,默认为1。增加迭代次数大大增加了约束的刚度,对于诸如格子之类的复杂结构非常有用,但也会增加运行时评估力的成本。

Many-Body

many-body(或n-body)force(多体力)在所有节点之间互斥。如果强度为正值,它可以用来模拟重力(吸引力);如果强度为负值,它可以用来模拟静电荷(斥力)。此实现使用四叉树和Barnes–Hut approximation来极大地提高性能;可以使用theta参数来定制精度。

与只影响两个相连节点的链接不同,charge force是全局的:每个节点都会影响每个其他节点,即使它们位于断开连接的子图上。

d3.forceManyBody()

使用默认参数创建一个新的多体力。

manyBody.strength([strength])

如果指定了strength,则设置strength accessor为指定的数字或函数,为每个节点的strength accessor重新求值,并返回该力。正值会导致节点相互吸引,类似于重力,而负值导致节点相互排斥,类似于静电荷。如果不指定strength,则返回当前的strength accessor,默认为:

function strength() {
  return -30;
}

对模拟中的每个节点调用strength accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,以便每个节点的强度仅在力被初始化时或者以新strength调用此方法时重新计算,而不是在力的每次应用时重新计算。

manyBody.theta([theta])

如果指定了theta,则将Barnes–Hut approximation标准设置为指定的数字并返回此力。如果不指定theta,则返回当前值,默认值为0.9。

为了加速计算,此力实现了Barnes–Hut approximation,每个应用需要O(n _log _n),其中n节点的数量。对于每个应用程序,四叉树存储当前节点位置;那么对于每个节点,计算给定节点上所有其他节点的组合力。对于远离节点的群集,可以通过将群集视为单个更大的节点来近似计算charge force。theta参数决定近似的精度:如果四叉树单元的宽度w与从节点到单元质心的距离l的比率w/l小于theta,给定单元中的所有节点都将被视为单个节点而不是单独节点。

manyBody.distanceMin([distance])

如果指定了distance,则设置在其上考虑该力的节点之间的最小距离。如果不指定distance,则返回当前的最小距离,默认为1。最小距离建立了两个相邻节点之间力的强度的上限,从而避免不稳定性。特别是,如果两个节点完全重合,它就避免了无限大的力;在这种情况下,力的方向是随机的。

manyBody.distanceMax([distance])

如果指定了distance,则设置在其上考虑该力的节点之间的最大距离。如果不指定distance,则返回当前的最大距离,默认为无穷大。指定有限的最大距离可以提高性能并生成更多局部布局。

Positioning

xy positioning force(定位力)将节点沿可配置强度的给定维度推向期望的位置。radius force(径向力)是类似的,除了它是将节点在给定的圆中推向最近的节点。力的强度与节点位置和目标位置之间的一维距离成正比。虽然这些力可用于定位单个节点,但它们主要用在应用于所有(或大多数)节点的全局力。

d3.forceX([x])

沿x轴向指定位置x创建一个新的定位力。如果不指定x,则默认为0。

x.strength([strength])

如果指定了strength,则设置strength accessor为指定的数字或函数,对每个节点的strength accessor重新求值,并返回该力。strength决定了节点的x -velocity增加多少:(x - node.x) × strength。例如,值为0.1表示节点应该从每个应用程序的当前x位置移动到目标x位置的十分之一。更高的值使节点更快地移动到目标位置,通常会牺牲其他力或约束。不建议使用[0,1]之外的值。

如果不指定strength,则返回当前的strength accessor,默认为:

function strength() {
  return 0.1;
}

对模拟中的每个节点调用strength accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,以便每个节点的强度仅在力被初始化时或者以新strength调用此方法时重新计算,而不是在力的每次应用时重新计算。

x.x([x])

如果指定了x,则设置x-coordinate accessor为指定的数字或函数,对每个节点的x

-accessor重新求值,并返回该力。如果不指定x,则返回当前的x -accessor,默认为:

function x() {
  return 0;
}

为模拟中的每个节点调用x -accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,这样每个节点的目标x坐标只在力被初始化时或在用新的x调用此方法时重新计算,而不是在每次应用程序时重新计算。

d3.forceY([y])

沿y轴向指定位置y创建一个新的定位力。如果不指定y,则默认为0。

y.strength([strength])

如果指定了strength,则设置strength accessor为指定的数字或函数,对每个节点的strength accessor重新求值,并返回该力。strength决定了节点的y -velocity增加多少:(y- node.y) × strength。例如,值为0.1表示节点应该从每个应用程序的当前y位置移动到目标y位置的十分之一。更高的值使节点更快地移动到目标位置,通常会牺牲其他力或约束。不建议使用[0,1]之外的值。

如果不指定strength,则返回当前的strength accessor,默认为:

function strength() {
  return 0.1;
}

对模拟中的每个节点调用strength accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,以便每个节点的强度仅在力被初始化时或者以新strength调用此方法时重新计算,而不是在力的每次应用时重新计算。

y.y([y])

如果指定了y,则设置y-coordinate accessor为指定的数字或函数,对每个节点的y

-accessor重新求值,并返回该力。如果不指定y,则返回当前的y-accessor,默认为:

function y() {
  return 0;
}

为模拟中的每个节点调用y -accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,这样每个节点的目标y坐标只在力被初始化时或在用新的y调用此方法时重新计算,而不是在每次应用程序时重新计算。

d3.forceRadial(radius[, x][, y])

创建一个新的定位力,朝向给定radius、中心在⟨x,y⟩的圆。如果不指定xy,则默认为⟨0,0⟩。

radial.strength([strength])

如果指定了strength,则设置strength accessor为指定的数字或函数,对每个节点的strength accessor重新求值,并返回该力。strength决定了节点的xy -velocity增加多少。例如,值为0.1表示节点在圆上应该从每个应用程序的当前位置移动到最近点的十分之一。更高的值使节点更快地移动到目标位置,通常会牺牲其他力或约束。不建议使用[0,1]之外的值。

如果不指定strength,则返回当前的strength accessor,默认为:

function strength() {
  return 0.1;
}

对模拟中的每个节点调用strength accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,以便每个节点的强度仅在力被初始化时或者以新strength调用此方法时重新计算,而不是在力的每次应用时重新计算。

radial.radius([radius])

如果指定了radius,则设置圆半径为指定的数字或函数,对每个节点的radius accessor重新求值,并返回该力。如果不指定radius,则返回当前的radius accessor。

为模拟中的每个节点调用radius accessor,并传入该node及其从0开始的index。然后将得到的数字存储在内部,使得每个节点的目标半径仅在力被初始化时或者在使用新radius调用此方法时重新计算,而不是在力的每次应用中重新计算。

radial.x([x])

如果指定了x,则设置圆心的x坐标为指定的数字,并返回此力。如果不指定x,则返回中心的当前x坐标,默认为0。

radial.y([y])

如果指定了y,则设置圆心的y坐标为指定的数字,并返回此力。如果不指定y,则返回中心的当前y坐标,默认为0。

results matching ""

    No results matching ""