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).