在两个圆圈之间画一个箭头?

时间:2012-10-31 19:09:31

标签: svg d3.js

如何在两个圆圈之间绘制箭头线,给定:

  1. cirlces中心的位置
  2. 圆圈的半径
  3. 我正在使用标记 svg对象。

    如果我将箭头绘制到圆圈的“中心” - 那么箭头是不可见的。 如果我将箭头向后移动太远 - 那么该线显示并隐藏箭头的尖端(为了更好的可见性,这里夸大了): Arrows moved waaaay back

    根据请求,这是我的代码的相关位(在lifecript中):

    # Draw an arrow to use for lines
    svg.append("svg:defs")
     .append("svg:marker")
      .attr("id", "arrow")
      .attr("viewBox", "0 0 10 10")
      .attr("refX", 27)
      .attr("refY", 5)
      .attr("markerUnits", "strokeWidth")
      .attr("markerWidth", 8)
      .attr("markerHeight", 6)
      .attr("orient", "auto")
      .append("svg:path")
      .attr("d", "M 0 0 L 10 5 L 0 10 z")
    
    svg.append("line")
     .attr "x1" 5 
     .attr "x2" 50 
     .attr "y1" 5 
     .attr "y2" 50
     .style "stroke" "black"
     .attr "stroke-width" 2
     .attr "marker-end" "url(\#arrow)"
    

    或者,这里是工作示例的JSFiddle(请注意,箭头“坐立不安”看起来恰到好处):http://jsfiddle.net/yeQS2/

4 个答案:

答案 0 :(得分:15)

如果我理解正确,您需要找到需要添加到源的2D矢量才能到达目标圆的边界。

伪代码:

d = distance between A and B; // (sqrt((xB-xA)² + (yB-yA)²)).
d2 = d - radius;

ratio = d2 / d;

dx = (xB - xA) * ratio;
dy = (yB - yA) * ratio;

x = xA + dx;
y = yA + dy;

答案 1 :(得分:7)

我有同样的问题,我在这里解决了这个问题。对original fiddle所做的更改:

.attr("refX", 27)更改为.attr("refX", 0)。这使得箭头绘制超出了行的末尾。

使用三角函数计算线条的正确结束位置,计算箭头,方法是将以下代码添加到" tick":

var arrowheadLength = 8, // from markerWidth
    nodeRadius = 10;
link.each(function(d) {
  var x1 = d.source.x,
      y1 = d.source.y,
      x2 = d.target.x,
      y2 = d.target.y,
      angle = Math.atan2(y2 - y1, x2 - x1);
  d.targetX = x2 - Math.cos(angle) * (nodeRadius + arrowheadLength);
  d.targetY = y2 - Math.sin(angle) * (nodeRadius + arrowheadLength);
});

使用计算的targetX和targetY链接属性:

.attr("x2", function(d){
  return d.targetX;
}).attr("y2", function(d){
  return d.targetY;
})

这是updated fiddle

答案 2 :(得分:4)

好的,所以我想我会试一试,用一些矢量数学来实现它,它更漂亮,结果可以重复使用。

一些澄清:

  • “向量”只是两个数字(x和y)
  • “坐标”在结构上与向量相同,它们只是意味着与我们不同的东西。我们可以在上面运行相同的数学。
  • “定位向量”是两个向量(如源和目标)
  • 您可以通过从第二个向量中减去第一个向量来“释放”定位向量(您将获得一个不再锚定在坐标系中的新向量)
  • 使用pythagoras theorem(也称为norm)
  • 计算向量的“长度”
  • Vector addition”只是添加两个或多个向量的xs和ys,从而产生一个新的向量。
  • Scalar multiplication”并通过将x和y除以标量
  • 来进行除法
  • unit vector”是向量除以其长度

假设我们希望动态工作(“每滴答”),初始链接调整看起来像这样(我正在使用coffeescript):

links.attr('x1', ({source,target}) -> source.x)
     .attr('y1', ({source,target}) -> source.y)
     .attr('x2', ({source,target}) -> target.x)
     .attr('y2', ({source,target}) -> target.y)

我们要做的是将源和目标nodeRadius移离圆圈。为此我们使用矢量数学

  1. 释放定位向量(链接由两个坐标组成,我们需要一个单独的无锚向量)
  2. 计算自由向量的单位向量
  3. 一旦我们有了,我们就可以乘以nodeRadius。此新向量表示节点中心与其边界之间的距离,方向与链接相同。
  4. 将矢量添加到源坐标,这些新坐标将位于圆的边缘。
  5. 好的,我们将使用以下函数来执行此操作:

    length = ({x,y}) -> Math.sqrt(x*x + y*y)
    sum = ({x:x1,y:y1}, {x:x2,y:y2}) -> {x:x1+x2, y:y1+y2}
    diff = ({x:x1,y:y1}, {x:x2,y:y2}) -> {x:x1-x2, y:y1-y2}
    prod = ({x,y}, scalar) -> {x:x*scalar, y:y*scalar}
    div = ({x,y}, scalar) -> {x:x/scalar, y:y/scalar}
    unit = (vector) -> div(vector, length(vector))
    scale = (vector, scalar) -> prod(unit(vector), scalar)
    
    free = ([coord1, coord2]) -> diff(coord2, coord1)
    

    这可能看起来有点压倒性,它非常紧凑,因为coffeescript允许我们直接在方法签名中解构东西,非常方便! 如您所见,还有另一个名为scale的函数。它只是一个结合步骤2的便利功能。 3。

    现在让我们尝试为链接源设置新的x坐标。请记住:坐标应移动nodeRadius,以便它从圆圈的边框开始而不是在其内部。

    (d) ->
        # Step 1
        freed = free(d)
        # Step 2
        unit = unit(freed)
        # Step 3
        scaled = prod(unit, nodeRadius)
        # Step 2+3 would be scale(freed, nodeRadius)
        # Step 4, coords are pretty much just vectors,
        # so we just use the sum() function to move the source coords
        coords = sum(d.source, scaled)
        return coords.x
    

    没什么!将所有内容放入tick()函数中,我们得到:

    links.attr('x1', ({source,target}) -> sum(source, scale(free([source,target]), nodeRadius)).x)
         .attr('y1', ({source,target}) -> sum(source, scale(free([source,target]), nodeRadius)).y)
         .attr('x2', ({source,target}) -> diff(target, scale(free([source,target]), nodeRadius)).x)
         .attr('y2', ({source,target}) -> diff(target, scale(free([source,target]), nodeRadius)).y)
    

    哦,不要忘记从目标坐标中减去,否则你只需再次使线条更长(即将它移动nodeRadius)。

答案 3 :(得分:2)

正如@andsens所说,你正在做一个简单的矢量操作。

如果将它包装在一个像样的库中,这可以更干净地完成。例如,我使用了漂亮的Sylvester矩阵和矢量库。

你实际上在计算的是:

V subscript edge equals open paren modulus V minus R close paren times unit vector V

其中 v 是目标中心的向量, v edge 到半径为<目标边缘的向量< EM> - [R

您可以轻松完成:

// Assume source and target both have x and y properties
// Assume target has radius property
function path2TargetEdge(source, target){

  // V is the vector from the source to the target's center
  var V = $V([target.x-source.x, target.y-source.y]);

  // Vt is the vector from the source to the edge of the target
  var Vt = V.toUnitVector().multiply(V.modulus() - target.radius);

  return {x: Vt.e(1), y: Vt.e(2) }; // Vectors are 1-indexed
}