我正在使用d3.js 3.5.6。我们如何在自己的渲染循环中标记力布局?

看来,当我调用force.start()时,它会自动启动强制布局自身的内部渲染循环(使用requestAnimationFrame)。

如何防止d3进行渲染循环,以便可以制作自己的渲染并自己调用force.tick()

最佳答案

这个答案是完全错误的。不要引用它,不要使用它。

我写了new one解释了如何正确执行此操作。我记得花了几天的时间来研究这个问题,尽管我发现了一个错误。而且,根据评论和投票,我成功地欺骗了其他人(包括Lars Kotthoff等传奇人物),使我沿着错误的道路前进。无论如何,我从错误中学到了很多东西。如果您没有抓住机会从错误中学习,您只需为自己的错误感到羞耻。

一旦这个答案不被接受,我将删除它。



起初,我为问题中缺少代码而感到烦恼,并认为答案相当简单明了。但是,事实证明,该问题具有一些意想不到的含义,并产生了一些有趣的见解。如果您对这些细节不感兴趣,则可能希望在底部查看我对可执行解决方案的最终想法。



我已经看过通过显式调用force.tick进行力布局计算的代码和文档。


  # force.tick()
  
  一步执行力布局模拟。此方法可以与startstop结合使用以计算静态布局。例如:

force.start();
for (var i = 0; i < n; ++i) force.tick();
force.stop();



这段代码对我而言似乎总是令人怀疑,但是我认为这是理所当然的,因为文档中有此代码,而Mike Bostock自己使用文档中的代码制作了"Static Force Layout"块。事实证明,我的直觉是正确的,并且Block和文档都是错误的,或者至少在很大程度上偏离了轨道:


调用start将对您的节点进行很多初始化并链接数据(请参见nodes()links()的文档。您不能仅凭自己的经验就取消该调用。如果没有它,强制布局将无法运行。

start最终将要做的另一件事是通过调用requestAnimationFramesetTimeout来触发处理循环,并提供force.tick作为回调。这将导致异步处理,该处理将反复调用force.tick,从而进行计算并调用滴答处理程序(如果提供)。打破此循环的唯一非hacky方法是通过调用alphaforce.alpha(0.005)force.stop()设置为低于硬编码的freezing point 0.005。这将在下一次调用tick时停止循环。除非以这种方式停止计时器,否则它将继续循环log0.99(0.005 / 0.1)≈298次,直到alpha降到冰点以下。

应该注意的是,文档或Block并非如此。因此,由force.start()开始的滴答循环将继续异步运行并进行计算。
随后的for循环可能会或可能不会对强制布局的结果产生任何影响。如果计时器碰巧仍在后台运行,则意味着同时从计时器和for循环中调用force.tick。无论如何,一旦alpha下降到足够低的程度(当达到298次对tick的调用时),计算将停止。这可以从以下lines中看到:

force.tick = function() {
  // simulated annealing, basically
  if ((alpha *= 0.99) < 0.005) {
    timer = null;
    event.end({type: "end", alpha: alpha = 0});
    return true;
  }

  // ...
}


从那时起,您可以随时调用tick,而无需更改布局的结果。输入了方法,但是由于alpha值低,将立即返回。您将看到的是重复触发end事件。

要影响迭代次数,您必须控制alpha。


块中的布局看起来是静态的,这是由于没有注册"tick"事件的回调,该回调可以在每个刻度上更新SVG。最终结果仅绘制一次。并且仅需298次迭代就可以准备好结果,后续的显式调用tick不会更改此结果。对force.stop()的最终调用也不会更改任何内容,只是将alpha设置为0。这对结果没有任何影响,因为力的布局早已隐式地停止了。

结论

像斯蒂芬·托马斯(Stephen A. Thomas)的伟大系列"Understanding D3.js Force Layout"一样,通过巧妙地开始和停止布局可以避免第1项。在该系列中,example 3中的他使用按钮控件逐步进行计算。但是,这也将在298个步骤后停止。要完全控制迭代,您需要


提供一个滴答处理程序,并通过在其中调用force.stop()立即停止计时器。到那时,该步骤的所有计算都将完成。
在您自己的循环中,计算alpha的新值。通过force.alpha()设置此值将重新启动布局。一旦完成下一步的计算,便会执行滴答处理程序,导致立即停止,如上所示。为此,您必须在循环中跟踪Alpha。


最后的想法

侵入性最小的解决方案可能是正常调用force.start(),而是更改force.tick函数以立即暂停计时器。由于使用的计时器是正常的d3.timer,因此可以通过从回调(即true方法)返回tick来中断它。这可以通过在方法周围放置轻质包装纸来实现。包装器将委托给原始的tick方法,该方法已关闭,然后将立即返回true,从而停止计时器。

force.tick = (function(forceTick) {
  return function() {  // This will be the wrapper around tick which returns true.
    forceTick();       // Delegate to the original tick method.
    return true;       // Truth hurts. This will end the timer.
  }
}(force.tick));        // Pass in the original method to be closed over.


如上所述,您现在可以独自管理alpha的减小值,以控制布局移动的减慢。但是,这仅需要简单的演算和循环即可设置alpha并根据需要调用force.tick。有很多方法可以做到这一点。为了进行简单的展示,我选择了一种较为冗长的方法:

// To run the computing steps in our own loop we need
// to manage the cooling by ourselves.
var alphaStart = 0.1;
var alphaEnd   = 0.005;
var alpha      = alphaStart;
var steps      = n * n;
var cooling = Math.pow(alphaEnd / alphaStart, 1 / steps);

// Calling start will initialize our layout and start the timer
// doing the actual calculations. This timer will halt, however,
// on the first call to .tick.
force.start();

// The loop will execute tick() a fixed number of times.
// Throughout the loop the cooling of the system is controlled
// by decreasing alpha to reach the freezing point once
// the desired number of steps is performed.
for (var i = 0; i < steps; i++) {
  force.alpha(alpha*=cooling).tick();
}
force.stop();


为了解决这个问题,我分叉了Mike Bostock的Block来自己构建executable example

关于javascript - 如何制作单独的强制布局渲染循环?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/40210172/

10-11 23:48