hwilson1 hwilson1 - 1 month ago 6
Javascript Question

d3 Force: Calculating position of text on links where links between nodes are arcs

One way to apply links to text is to use the force.links() array for the text elements and centre the text on the midpoint of the link.

I have some nodes with bidirectional links, which I've rendered as paths that bend at their midpoint to ensure it's clear that there's two links between the two nodes.

For these bidirectional links, I want to move the text so that it sits correctly over the bending path.

To do this, I've attempted to calculate the intersection(s) of a circle centred on the centre point of the link and a line running perpendicular to the link that also passes through its centre. I think in principal this makes sense, and it seems to be half working, but I'm not sure how to define which coordinate returned through calculating the intersections to apply to which label, and how to stop them jumping between the curved links when I move the nodes around (see jsfiddle - https://jsfiddle.net/sL3au5fz/6/).

The function for calculating coordinates of text on arcing paths is as follows:

function calcLinkTextCoords(d,coord, calling) {
var x_coord, y_coord;
//find centre point of coords
var cp = [(d.target.x + d.source.x)/2, (d.target.y + d.source.y)/2];
// find perpendicular gradient of line running through coords
var pg = -1 / ((d.target.y - d.source.y)/(d.target.x - d.source.x));
// define radius of circle (distance from centre point text will appear)
var radius = Math.sqrt(Math.pow(d.target.x - d.source.x,2) + Math.pow(d.target.y - d.source.y,2)) / 5 ;
// find x coord where circle with radius 20 centred on d midpoint meets perpendicular line to d.
if (d.target.y < d.source.y) {
x_coord = cp[0] + (radius / Math.sqrt(1 + Math.pow(pg,2)));
} else {
x_coord = cp[0] - (radius / Math.sqrt(1 + Math.pow(pg,2)));
};
// find y coord where x coord is x_text and y coord falls on perpendicular line to d running through midpoint of d
var y_coord = pg * (x_coord - cp[0]) + cp[1];
return (coord == "x" ? x_coord : y_coord);
};


Any help either to fix the above or propose another way to achieve this would be appreciated.

Incidentally I've tried using textPath to line my text up with my links but I don't find that method to be performant when displaying upward of 30-40 nodes and links.

Update: Amended above function and now works as intended. Updated fiddle here:https://jsfiddle.net/o82c2s4x/6/

Answer

You can calculate the projection of the chord to x and y axis and add it to the source node coordinates:

function calcLinkTextCoords(d,coord) {

        //find chord length
        var  dx = (d.target.x - d.source.x);
        var dy = (d.target.y - d.source.y);
        var chord = Math.sqrt(dx*dx + dy*dy);

        //Saggita
        // since radius is equal to chord
        var sag = chord - Math.sqrt(chord*chord - Math.pow(chord/2,2));

         //Find the angles
        var t1 = Math.atan2(sag, chord/2);
        var t2 = Math.atan2(dy,dx);
        var teta = t1+t2;

        var h = Math.sqrt(sag*sag + Math.pow(chord/2,2));

        return ({x: d.source.x + h*Math.cos(teta),y: d.source.y + h*Math.sin(teta)});

    };

Here is the updated JsFiddle