在D3力布局链接中间显示箭头

时间:2013-03-31 13:30:22

标签: javascript svg d3.js force-layout

我正在使用D3绘制力导向图,这与此示例非常相似:http://bl.ocks.org/mbostock/1153292

我正在尝试将箭头放在链接的中间而不是结尾。

使用标记的attr("refX", 0)并没有多大帮助,因为它是绝对的而不是相对于链接长度 - 我的链接长度各不相同。

我一直在谷歌搜索,我最好的想法是根据examplelink.attr("marker-end", ...)替换link.attr("marker-segment", ...)(查看图中间的十字架)。但这似乎不起作用..我猜是因为它只是SVG2草案的一部分,但我的浏览器支持较低版本? (我正在使用Chrome btw的最新版本。)

如何将箭头放在链接的中间?

3 个答案:

答案 0 :(得分:19)

不要将标记放在末尾,而是在行的中间创建一行(<polyline><path>)并使用marker-mid来应用箭头。

我的This demo

this answer使用此技术沿着路径的长度创建多个内部点以及marker-mid。您只需创建一个“航点”而不是许多。

编辑:这是对现有演示的一个简单的黑客,显示我的意思:

演示:http://jsfiddle.net/tk7Wv/2/

Arrowheads in the middle of curves

唯一的变化是:

  1. marker-end更改为marker-mid
  2. 修改路径生成代码以创建两个弧(在中间连接)而不是一个。
  3. 需要一些简单的三角函数,如Superboggly所示,根据你想要的线条中的弯曲量来选择合适的中点。

答案 1 :(得分:13)

我是OP,这个答案只是上面的优秀答案的补充,为了其他人有同样的问题。

答案显示了如何使用弧形链接实现此目的。如果您有直接链接,则需要稍微修改已接受的答案。这是如何:

您的直接链接可能是line实现的,需要转换为polyline。像这样:

// old: svg.selectAll(".link").data(links).enter().append("line")
svg.selectAll(".link").data(links).enter().append("polyline")

然后我们必须根据此example对折线进行编码,因此您的原始代码会对line进行编码:

force.on("tick", function() {
   link.attr("x1", function(d) {return d.source.x;})
       .attr("y1", function(d) {return d.source.y;})
       .attr("x2", function(d) {return d.target.x;})
       .attr("y2", function(d) {return d.target.y;});

变更为:

force.on("tick", function() {
   link.attr("points", function(d) {
      return d.source.x + "," + d.source.y + " " + 
             (d.source.x + d.target.x)/2 + "," + (d.source.y + d.target.y)/2 + " " +
             d.target.x + "," + d.target.y; });

最后,不要忘记将marker-end转换为marker-mid

// old: link.attr("marker-end",
link.attr("marker-mid",

致@Phrogz表示道路。

答案 2 :(得分:10)

我采取了与Phrogz略有不同的方法。我尝试删除路径的标记,而是使用一条新路径绘制它,该路径转到弧的中点并且其笔划是不可见的。计算中点有点挑剔,所以如果你想改变弧的特性,你可能会更好地使用Phrogz的方法,或者一些混合方法。

在这种情况下,触发不是那么糟糕,因为如果仔细观察,您会注意到用于生成链接的弧来自具有相同半径的圆和节点之间的距离。所以你有一个等边三角形,所有你需要做的就是计算通过链接线段中点到弧中点的距离。

我想我需要一个图表来解释:

Find arc midpoint 因此,三角形ABC是等边的,EC等于AB。由于我们有A和B的坐标,因此发现M很容易(平均坐标)。这意味着我们也知道从A到B的斜率(delta y / delta x),因此从M到E的斜率是负反。知道M和E的斜率意味着我们几乎就在那里,我们只需知道要走多远。

为此,我们使用ACM为30-60-90三角形的事实

| CM | = sqrt(3)* | AM |

但是| AM | = | AB | / 2和| CE | = | AB |

所以

| ME | = | AB | - sqrt(3)* | AB | / 2

为了使这个长度有意义,我们必须用斜率矢量的长度对其进行标准化,我们已经知道它与圆的半径相同。

无论如何,把它们放在一起你得到:

var markerPath = svg.append("svg:g").selectAll("path.marker")
  .data(force.links())
  .enter().append("svg:path")
  .attr("class", function(d) { return "marker_only " + d.type; })
  .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });


... later in the tick handler

markerPath.attr("d", function(d) {
  var dx = d.target.x - d.source.x,
      dy = d.target.y - d.source.y,
      dr = Math.sqrt(dx * dx + dy * dy);

  // We know the center of the arc will be some distance perpendicular from the
  // link segment's midpoint. The midpoint is computed as:
  var endX = (d.target.x + d.source.x) / 2;
  var endY = (d.target.y + d.source.y) / 2;

  // Notice that the paths are the arcs generated by a circle whose 
  // radius is the same as the distance between the nodes. This simplifies the 
  // trig as we can simply apply the 30-60-90 triangle rule to find the difference
  // between the radius and the distance to the segment midpoint from the circle 
  // center.
  var len = dr - ((dr/2) * Math.sqrt(3));

  // Remember that is we have a line's slope then the perpendicular slope is the 
  // negative inverse.
  endX = endX + (dy * len/dr);
  endY = endY + (-dx * len/dr);

  return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + endX + "," + endY;
});

查看here。请注意,标记路径的路径css设置为透明红色,通常您需要将笔划设置为“无”以隐藏线条。