d3力导向图向下力模拟

时间:2014-02-03 13:55:46

标签: javascript svg d3.js tree force-layout

也许这不是最好的方法,但是现在我已经走下了兔子洞,我想知道它是如何运作的。我正在尝试使用d3来创建一个树,其中节点将向下沉降,就像有重力一样。我希望这一点,以及每个节点的电荷以及绳索中的张力将使它最终看起来像我想象的那样,并将其平衡。我试图在d3中模拟这种恒定的向下加速度,但我是d3的初学者,并且不知道具体如何。我也遇到了模拟停止运行的问题,并且d3.timer(force.resume)没有帮助。

基本上,我希望将一个根节点固定在一个特定的位置,如一个枢轴,子节点从它上面移开,重力,电荷和张力存在,这样孩子们就会自动解决并平衡自己成一棵树结构

2 个答案:

答案 0 :(得分:20)

强制定向模拟是布局树,图或图形的好方法。此方法用于许多开源和商业库和应用程序,因此D3只是其中之一。此外,这个主题不断吸引应用数学家的注意。

我只是想告诉你这是一个很大的话题。如果只将自己限制在D3,那么仍然有很多方法可以做你在问题中描述的内容。由于这个答案必须有一个合理的长度,我将尝试通过3个D3示例向您展示一些亮点。我将从一个简单的强制导向树布局示例开始(这不是你想要的,它很简单),每个例子都将建立在之前,并且将更接近你想要实现的目标。

示例1

link to jsfiddle

enter image description here

这是通过D3力布局以径向方式布置的树的基本示例。您确实需要了解所使用的 ALL 代码。我在这里只提一些亮点:

  • function getData()返回一个测试树(这是一个方便的子集 所谓的“耀斑”测试树);
  • function flatten()将树对象转换为数组,这是初始化D3 force layout所需的格式;
  • 初始化所有节点的位置(否则D3力布局会表现得有些奇怪和不可预测);
  • 根节点位置初始化为图的中心,根节点声明为 fixed ;
  • 提供了
  • 函数“ontick”,它只是在模拟过程中完全按照它们的行为绘制链接和节点;
  • D3力布局算法初始化为D3重力0.2和D3充电-200;
  • 通过调用方法start()来启动D3强制布局算法。

如果您有与此示例相关的其他问题,请与我们联系。我知道对于没有D3经验的人来说代码看起来有点奇怪 - 但是,实际上,一旦你得到D3力布局就知道它非常简单,易于理解。您可以使用不同的D3重力和D3充电,以更好地理解为什么选择特定值0.2和-200。

当然,这不是你想要的,但它可以作为我们的“规范范例”。

示例2

link to jsfiddle

enter image description here

Codewise,这个例子与previus的区别在于两件事:

  • 根节点位置初始化为(宽度/ 2,100)而不是(宽度/ 2,高度/ 2) - 这是“悬挂”布局的根的自然位置“
  • 自定义力在“ontick”函数中定义如下:

var ky = e.alpha;

links.forEach(function(d, i) {

d.target.y += (d.target.depth * 100 - d.target.y) * 5 * ky;

});

此自定义力会将相同深度的节点吸引到同一水平线。这足以产生图中的图表。

但是,您可以注意到图中某些节点的不自然的安排 - 通常,它们看起来不像是从父节点“悬挂”。下一个例子是尝试修复它。

示例3

link to jsfiddle

enter image description here

这看起来比上一个例子更自然。

这个例子中的新内容只是一个自定义力:一个将所有父母“居中”到孩子水平位置中间的力量。您可以在“ontick()”函数中轻松找到负责该代码的代码。

这看起来更接近你想要的东西。我并不是说这是一个满足您需求的理想布局。但是,它可以作为一个很好的起点。一旦理解了所有这些示例,您就可以修改它们,并且如果需要,可以根据不同的自定义力量创建不同的模拟。

所以,最后一个例子不是基于物理引力的模拟,但它具有类似于物理引力的效果。如果你想模拟物理引力,这将需要更多的代码 - 但如果效果几乎相同,你真的需要吗?

希望这会有所帮助。如果您有疑问,需要澄清等,请告诉我。

答案 1 :(得分:1)

最简单的方法是简单地调整“大小”,如果布局使现有“重力”力的中心位于显示屏的底部,而根节点固定在顶部显示器。但是,您可能需要对参数进行大量调整才能使事情看起来很好。

此处示例:http://jsfiddle.net/cSn6w/13/

var force = d3.layout.force()
    .on("tick", tick)
    .charge(-30)
    .linkDistance(10)
    .gravity(0.005)
    .size([w, 2*h]);

另一种选择是添加“自定义力”,类似于Multi-foci force layout example中使用的“自定义力量”。该演示中使用的计算的细节是不相关的,但这个想法很简单,即在每个tick()函数的开头,每个数据点在某个方向上稍微调整一下。如果该方向抛弃了任何其他约束,则它将在下一个滴答中通过布局的内部计算来反作用。

此处示例:http://jsfiddle.net/cSn6w/14/

var g = 0.0005;
function gravityFunction(d,i) {
    if (d.fixed) return;
   //used to modify each data object's position
   //at the start of every tick
   var distToGround = h-d.y;
   if (distToGround <= 0) d.y = h; //make "ground" solid
   else d.y += g * (Math.min(distToGround, h/4) ); 
}
function tick() {
    nodeCircles.each(gravityFunction) 
       //adjust each node according to the custom force
        .attr("cx", function (d) {
            return d.x;
        })
        /* etc. */

获得针对漂亮布局优化的重力功能将是棘手的部分。根据我的实验,你可能想要跟踪树中每个节点的深度,并且重力函数或链接强度与深度相关,这样你就不会伸出“树干”你的树太多了。