How can I draw an arrowed line between two circles, given:
I am using
OK, so I thought I'd give it a shot and implement this with some vector math, it's prettier and the result is reusable.
A few clarifications:
Assuming we want this to to work dynamically ("per tick"), the initial links adjustment looks like this (I am using 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)
What we want to do is move the source and target nodeRadius
away from the circle. For that we use vector math to
nodeRadius
. This new vector represents the distance between the node center and its border, with the same direction as the link.OK, so we will use the following functions to do this:
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)
This might look a little overwhelming, it's very compact because coffeescript allows us to deconstruct things directly in the method signature, quite handy!
As you can see there is another function called scale
. It's simply a convenience function to combine steps 2. & 3.
Now let's try and set the new x coordinate for the link source. Remember: The coordinate should be moved by nodeRadius
, so that it starts on the border of the circle instead of inside it.
(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
Nothing to it! Putting all of that into the tick()
function, we get:
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)
Oh, and don't forget to subtract from the target coordinates, otherwise you'd just be making the line longer again (i.e. moving it by nodeRadius
).