我正在使用d3.js 3.5.6。我们如何在自己的渲染循环中标记力布局?
看来,当我调用force.start()
时,它会自动启动强制布局自身的内部渲染循环(使用requestAnimationFrame)。
如何防止d3进行渲染循环,以便可以制作自己的渲染并自己调用force.tick()
?
最佳答案
这个答案是完全错误的。不要引用它,不要使用它。
我写了new one解释了如何正确执行此操作。我记得花了几天的时间来研究这个问题,尽管我发现了一个错误。而且,根据评论和投票,我成功地欺骗了其他人(包括Lars Kotthoff等传奇人物),使我沿着错误的道路前进。无论如何,我从错误中学到了很多东西。如果您没有抓住机会从错误中学习,您只需为自己的错误感到羞耻。
一旦这个答案不被接受,我将删除它。
起初,我为问题中缺少代码而感到烦恼,并认为答案相当简单明了。但是,事实证明,该问题具有一些意想不到的含义,并产生了一些有趣的见解。如果您对这些细节不感兴趣,则可能希望在底部查看我对可执行解决方案的最终想法。
我已经看过通过显式调用force.tick
进行力布局计算的代码和文档。
# force.tick()
一步执行力布局模拟。此方法可以与start和stop结合使用以计算静态布局。例如:
force.start();
for (var i = 0; i < n; ++i) force.tick();
force.stop();
这段代码对我而言似乎总是令人怀疑,但是我认为这是理所当然的,因为文档中有此代码,而Mike Bostock自己使用文档中的代码制作了"Static Force Layout"块。事实证明,我的直觉是正确的,并且Block和文档都是错误的,或者至少在很大程度上偏离了轨道:
调用
start
将对您的节点进行很多初始化并链接数据(请参见nodes()
和links()
的文档。您不能仅凭自己的经验就取消该调用。如果没有它,强制布局将无法运行。start
最终将要做的另一件事是通过调用requestAnimationFrame
或setTimeout
来触发处理循环,并提供force.tick
作为回调。这将导致异步处理,该处理将反复调用force.tick
,从而进行计算并调用滴答处理程序(如果提供)。打破此循环的唯一非hacky方法是通过调用alpha
或force.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/