I have a force directed graph with links between each nodes. Now some node pairs have multiple links going to each other. I have found this example : Drawing multiple edges between two nodes with d3.
This worked great, I thought. But if you have fixed nodes and drag, the paths end up overlapping each other. I have put together an edited version of this example : http://jsfiddle.net/thatOneGuy/7HZcR/502/
Click the button to fix the nodes and move them around to see what I mean.
Code for working out amount of arc :
//sort links by source, then target
links.sort(function(a,b) {
if (a.source > b.source) {return 1;}
else if (a.source < b.source) {return -1;}
else {
if (a.target > b.target) {return 1;}
if (a.target < b.target) {return -1;}
else {return 0;}
}
});
//any links with duplicate source and target get an incremented 'linknum'
for (var i=0; i<links.length; i++) {
if (i != 0 &&
links[i].source == links[i-1].source &&
links[i].target == links[i-1].target) {
links[i].linknum = links[i-1].linknum + 1;
}
else {links[i].linknum = 1;};
};
Can anyone think of another way of doing this or fixing this way maybe ? I could have 3 maybe even 4 links between two nodes.
The important code is the one giving the radius of the arc. I propose the following function:
path.attr("d", function(d) {
var curve=2;
var homogeneous=3.2;
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx*dx+dy*dy)*(d.linknum+homogeneous)/(curve*homogeneous); //linknum is defined above
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
The main difference is that it scales linearly with the node distance (which I think is best). Then, there are these two parameters which I call curve
and homogeneous
: you should play with them until you find suitable values.
See http://jsfiddle.net/7HZcR/504/
PS: the overlap happens when the radius given for the arc is smaller than half the distance between the nodes (then the radius is increased to reach this value, and all arcs get the same radius).
Based on the solutions above I have a refined solution that has links evenly spread out, using the sweep parameter of the ellipse: http://jsfiddle.net/bigman73/v03x572h/
function tick() {
path.attr("d", function(d) {
let dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
t = Math.sqrt(dx * dx + dy * dy),
half_n = Math.floor(links.length / 2) + links.length % 2,
dr = d.linknum == half_n ? t * 100 : 2.25 * t * (half_n -
Math.abs(d.linknum - half_n)) / links.length,
sweep = d.linknum <= half_n ? 1 : 0;
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0," + sweep + " " + d.target.x + "," + d.target.y;
});
The curve shape is currently hard coded to 2.25, but that's easy to refactor. It should also work generically with different number of links (though I only tested on 3)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With