如何在两个圆圈之间绘制箭头线,给定:
我正在使用行和标记 svg对象。
如果我将箭头绘制到圆圈的“中心” - 那么箭头是不可见的。 如果我将箭头向后移动太远 - 那么该线显示并隐藏箭头的尖端(为了更好的可见性,这里夸大了):
根据请求,这是我的代码的相关位(在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/
答案 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;
})
答案 2 :(得分:4)
好的,所以我想我会试一试,用一些矢量数学来实现它,它更漂亮,结果可以重复使用。
一些澄清:
假设我们希望动态工作(“每滴答”),初始链接调整看起来像这样(我正在使用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
移离圆圈。为此我们使用矢量数学
nodeRadius
。此新向量表示节点中心与其边界之间的距离,方向与链接相同。好的,我们将使用以下函数来执行此操作:
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 是目标中心的向量, 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
}