D3 Force Layout - 文本环绕和节点重叠

时间:2016-06-29 20:27:14

标签: javascript d3.js force-layout

这是我第一个堆叠溢出的问题所以如果我犯了一些新手错误,请耐心等待。我在这里搜索了很多问题,并没有找到我正在寻找的东西(在一个案例中我有,但不知道如何实现它)。似乎唯一提出类似问题的人没有得到任何答案。

我用D3创建了一个力布局,事情几乎按照我想要的方式工作。我编辑的两件事情:

1)保持节点不重叠:是的,我已阅读并重新阅读Mike Bostock的集群力布局代码。我不知道如何在我的代码中实现这一点而不会出现严重错误!我从一个教程中尝试了这个代码,但它将我的节点固定在一个角落,并在画布上显示了链接:

var padding = 1, // separation between circles
  radius=8;
function collide(alpha) {
var quadtree = d3.geom.quadtree(graph.nodes);
   return function(d) {
var rb = 2*radius + padding,
    nx1 = d.x - rb,
    nx2 = d.x + rb,
    ny1 = d.y - rb,
    ny2 = d.y + rb;
quadtree.visit(function(quad, x1, y1, x2, y2) {
  if (quad.point && (quad.point !== d)) {
    var x = d.x - quad.point.x,
        y = d.y - quad.point.y,
        l = Math.sqrt(x * x + y * y);
      if (l < rb) {
      l = (l - rb) / l * alpha;
      d.x -= x *= l;
      d.y -= y *= l;
      quad.point.x += x;
      quad.point.y += y;
    }
  }
  return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}

你可以看到我的小提琴(下面链接)中的滴答功能的添加注释掉了。

2)包装我的文本标签,使它们适合节点内部。现在,他们在悬停时扩展到节点的全名,但我最终会将其更改为工具提示(一旦我解决了这些问题,我就会找出工具提示) - 现在我只是想要包含在节点内的原始短名称。我已经查看了this answerthis answerhttp://jsfiddle.net/Tmj7g/4/)但是当我尝试将其实现到我自己的代码中时,它没有响应或最终聚集了所有节点左上角(??)。

非常感谢任何和所有输入,并且可以在此处编辑我的小提琴:https://jsfiddle.net/lilyelle/496c2bmr/

我也知道我的所有语言都不完全一致或编写D3代码的最简单方式 - 这是因为我已经从不同来源复制和拼接了很多东西,但我仍然在尝试找出为自己写这些东西的最佳方法。对此方面的任何建议也表示赞赏。

1 个答案:

答案 0 :(得分:0)

1)碰撞检测:这是一个更新的,有效的jsFiddle,由this example从mbostock引导。添加碰撞检测主要是重要位的复制/粘贴。具体来说,在tick函数中,我添加了遍历所有节点的代码,如果它们发生碰撞则调整它们的位置:

var q = d3.geom.quadtree(nodes),
    i = 0,
    n = nodes.length;

while (++i < n) q.visit(collide(nodes[i]));

由于你的jsFiddle没有设置变量nodes,我把它添加到最后一个剪切的上方:

var nodes = force.nodes()

此外,该循环需要定义函数collide,就像在Bostock的示例中一样,所以我也将它添加到你的jsFiddle:

function collide(node) {
  var r = node.radius + 16,
      nx1 = node.x - r,
      nx2 = node.x + r,
      ny1 = node.y - r,
      ny2 = node.y + r;
  return function(quad, x1, y1, x2, y2) {
    if (quad.point && (quad.point !== node)) {
      var x = node.x - quad.point.x,
          y = node.y - quad.point.y,
          l = Math.sqrt(x * x + y * y),
          r = node.radius + quad.point.radius;
      if (l < r) {
        l = (l - r) / l * .5;
        node.x -= x *= l;
        node.y -= y *= l;
        quad.point.x += x;
        quad.point.y += y;
      }
    }
    return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
  };
}

最后一个必要的位来自于检测节点冲突需要知道它们的大小。 Bostock的代码访问上面node.radius函数内的collide。您的示例没有设置节点&#39;半径,因此collide函数node.radiusundefined。设置此半径的一种方法是将其添加到json,例如

{radius: 30, "name":"AATF", "full_name":"African Agricultural Technology Foundation", "type":1}

如果您的所有节点都具有相同的半径,那就太过分了。

另一种方法是使用硬编码的数字替换node.radius的两次出现,例如30

我选择在这两个选项之间做一些事情:通过循环加载的json为每个节点分配一个常量node.radius

json.nodes.forEach(function(node) {
    node.radius = 30;
})

这是让碰撞检测工作的原因。我使用半径30,因为它是用于渲染这些节点的半径,如.attr("r", 30)中所示。这将保持所有节点聚集 - 不重叠但仍然相互接触。您可以尝试使用较大的node.radius值来获得它们之间的空白区域。

2)文字环绕:这是一个艰难的选择。没有简单的方法可以使SVG <text>包裹在某个宽度上。只有常规的html div / span可以自动执行此操作,但即使是html元素也无法换行以适应圆圈,只能达到恒定的宽度。

您或许能够提出一些妥协方案,以便您始终能够使用某些文字。例如,如果您的数据是全部已知的,并且圆圈的大小始终是相同的固定值,那么您可以提前知道哪些标签可以适合哪些标签,哪些标签不适合。通过在JSON中为每个节点添加short_name属性,并将其设置为绝对合适的内容,您可以缩短它们。或者,如果事先知道大小和标签,您可以预先确定如何将标签分解为多行并将其硬编码到JSON中。然后,在渲染时,您可以使用多个SVG <text>元素渲染该文本,并将其手动定位为多行。最后,如果没有提前知道,那么你可以通过切换到将文本渲染为SVG顶部(和外部)的绝对定位div来获得一个好的解决方案,其宽度与圆圈相匹配&#39 ;宽度,以便文本自动换行。