【问题标题】:Display an arrow head in the middle of D3 force layout link在 D3 力布局链接中间显示一个箭头
【发布时间】:2013-03-21 17:24:33
【问题描述】:

我正在使用 D3 绘制一个力有向图,和这个例子非常相似:http://bl.ocks.org/mbostock/1153292

我试图将箭头放在链接的中间而不是末尾。

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

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

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

【问题讨论】:

  • 请务必注意,marker-mid 属性仅在您的路径在开始/结束之间有顶点时才有效。

标签: javascript svg d3.js force-layout


【解决方案1】:

不要将标记放在末端,而是在线条中间创建一个点(<polyline><path>),然后使用 marker-mid 应用箭头。

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

编辑:这是对现有演示的简单破解,说明我的意思:

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

唯一的变化是:

  1. marker-end 更改为marker-mid,并且
  2. 修改路径生成代码以创建两条弧线(在中间连接)而不是一条。

这将需要一些简单的三角函数,如 Superboggly 所示,根据您想要的线条弯曲量选择合适的中点。

【讨论】:

  • 谢谢,很好的回答。我的图表实际上也有一些直接的链接(用线完成)。根据你的建议,我把它们改成了折线,并在中间加了一个点。然后marker-mid,就像一个魅力一样工作。
  • 添加了另一个答案以向其他人反映这一点:)
  • 对角线或banzir曲线呢?
  • @TrailMix 不能修改 JSFiddle 的特定版本;保存更改总是会创建一个新链接。我正在加载的 D3 库很可能已经更新,并且该实现现在添加了额外的段而不是单个弧。
  • 请注意,这不适用于 Chrome 版本 61.0.3163.100。它呈现多个箭头。
【解决方案2】:

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

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

我想我需要一张图来解释:

所以三角形ABC是等边的,EC平分AB。因为我们有 A 和 B 的坐标,所以很容易找到 M(平均坐标)。这意味着我们也知道从 A 到 B 的斜率(delta y / delta x),所以从 M 到 E 的斜率是负数。知道 M 和到 E 的斜率意味着我们快到了,我们只需要知道要走多远。

为此,我们使用 ACM 是一个 30-60-90 三角形等事实

|CM| = sqrt(3) * |上午|

但是|上午| = |AB| / 2 和 |CE| = |AB|

所以

|我| = |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 设置为透明红色,通常您希望将 stroke 设置为“none”以隐藏线条。

【讨论】:

  • 很好的解释和努力提供了一个工作示例。我仍然更喜欢使用标记,但如果可以的话,我会给你超过 +1。
  • 谢谢!真的,我认为折线更灵活,但我喜欢能够使用弧形路径。所以我觉得我们的解决方案的组合可以很好地工作。
  • 哦,但是您可以轻松地使用带有中间标记的弧形路径:只需将其分成两条弧线,每条弧线各走一半。
  • 是的! - 但是你仍然必须计算那个弧中点,这实际上是我解释的大部分内容。这就是为什么我认为它可以很好地与您的解决方案一起使用。
  • 我第二次伟大的解释和图表!很棒的工作!我最终选择了替代方法(使用marker-mid),但这仍然是一个很棒的答案。
【解决方案3】:

我是 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 为我们指明了方向。

【讨论】:

  • 正是我需要的!谢谢。
  • 这真的很酷。直线的诀窍正是我想要的方式。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-22
  • 2012-06-22
  • 2016-05-05
  • 2017-11-17
  • 1970-01-01
  • 2013-04-25
  • 1970-01-01
相关资源
最近更新 更多