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

D3 Force Layout - Text Wrapping and Node Overlaps

这是我关于堆栈溢出的第一个问题,所以如果我犯了一些新手错误,请多多包涵。我在这里搜索了很多问题并没有找到我正在寻找的东西(在一种情况下我有,但不知道如何实现它)。而且好像只有问过类似问题的人没有得到任何答复

我已经使用 D3 创建了强制布局,并且一切都按照我希望的方式工作。我在编辑时遇到了两件事:

1) 防止节点重叠:是的,我已经阅读并重新阅读了 Mike Bostock 的集群力布局代码。我不知道如何在不出现严重错误的情况下将其实现到我的代码中!我从教程中尝试了这段代码,但它把我的节点固定在一个角落里,并在整个 canvas:

中展开了链接
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;
});
};
}

您可以在我的 fiddle(链接如下)中看到添加的刻度功能被注释掉了。

2) 包装我的文本标签,使它们适合节点。现在他们在悬停时扩展到节点的全名,但我最终会把它变成一个工具提示(一旦我解决了这些问题,我就会想出一个工具提示)——现在我只想要原始的、短的名字包裹在节点内。我看过 this answer and this answer (http://jsfiddle.net/Tmj7g/4/) 但是当我尝试将它实现到我自己的代码中时,它没有响应或最终将所有节点聚集在左上角 (??)。

非常感谢任何和所有输入,并随时在这里编辑我的 fiddle:https://jsfiddle.net/lilyelle/496c2bmr/

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

1) 碰撞检测: 这是来自 mbostock 的更新,有效 jsFiddle, which was guided by this example。添加碰撞检测主要是 copy/paste 的重要部分。具体来说,在 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 的代码在上面的 collide 函数中访问 node.radius。您的示例没有设置节点的半径,因此在 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 值来在它们之间获得一些白色 space。

2) 文本换行: 这很难。没有简单的方法可以使 SVG <text> 以一定宽度换行。只有常规 html div/span 可以自动执行此操作,但即使 html 元素也无法换行以适合圆,只能换行到恒定宽度。

您或许可以做出一些让您始终适合某些文本的折衷方案。例如,如果您的数据都是提前知道的,并且圆圈的大小始终是相同的固定值,那么您就可以提前知道哪些标签适合,哪些不适合。那些没有的,您可以通过向 JSON 中的每个节点添加一个 short_name 属性并将其设置为绝对适合的内容来缩短。或者,如果事先知道大小和标签,您可以预先确定如何将标签分成多行并将其硬编码到您的 JSON 中。然后,当您呈现时,您可以使用多个 SVG <text> 元素呈现该文本,这些元素您手动定位为多行。最后,如果事先什么都不知道,那么您可以通过切换到将文本呈现为 SVG 顶部(和外部)的绝对定位 div 来获得一个好的解决方案,其宽度与圆圈的宽度相匹配, 这样文本就会自动换行。