Links and Arrowheads to terminate at borders of nodes in D3

My question is how to create a network visualization scheme such that the edges and/or arrowheads terminate at the borders of the nodes.

I am drawing a directed graph using D3.js based on the Curved Links base model with added "marker" arrowheads as described in this other question. The nodes in my visualization vary their size and opacity based on their properties. This introduces two problems: (1) The arrowheads do not point to the edge of the nodes when the nodes change size, and (2) the tails of the edges appear through the nodes when they are partially transparent.

For the first problem, there are a few solutions available: this one purports to get the arrow heads offset correctly, but it does not affect the link end terminations. There are also suggestions of solutions here, but I didn't see any actual complete working code there. This JS fiddle has exactly the arrowhead look that I'd like, but the code is rather opaque and not modular in a way I can figure out how to apply to my own case.

As I said, my links are defined based on the Curved Links example:

graph.links.forEach(function(link) {
    var s = nodes[link.source],
        t = nodes[link.target],
        i = {}, // intermediate node
    property1 = link.property1;
    nodes.push(i);
    links.push({source: s, target: i}, {source: i, target: t});
    bilinks.push([s, i, t, property1]);
});

Then, if my loose understanding of how D3 works is basically correct, the links are drawn each tick via the following code:

force.on("tick", function() {
  link.attr("d", function(d) {
    if (d[0] == d[2]) {
      return "M" + d[0].x + "," + d[0].y
        + "A" + "20,20 -50 1,1 " + (1.001 * d[2].x) + "," + (1.001 * d[2].y) 
        ;
    } else {
     return "M" + d[0].x + "," + d[0].y
        + "S" + d[1].x + "," + d[1].y
        + " " + d[2].x + "," + d[2].y;
    }
  });
  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });
});

So my question is how to change this code in a way that achieves the generally desired (and I think normal) visualization scheme such that the edges and/or arrowheads terminate at the border of the nodes even as they change size.

I've created a JS Fiddle that includes all the necessary bits to see and solve the problem. It also includes an adjustment for getting the arrowheads to match the links they are on, and that capability needs to be compatible with the solution to this issue.

Answers:

Answer

Since I wasn't getting any responses I went ahead and powered through answering my own question. As result, the answer I came up with is probably not the best because I'm still new to all this, but it works and it's similar to this answer...heavily adapted to handle the curved links and reflexive links.

The core of the necessary change is the following code:

force.on("tick", function() {
  link.attr("d", function(d) {
    diffX0 = d[0].x - d[1].x;
    diffY0 = d[0].y - d[1].y;
    diffX2 = d[2].x - d[1].x;
    diffY2 = d[2].y - d[1].y;
    pathLength01 = Math.sqrt((diffX0 * diffX0) + (diffY0 * diffY0));
    pathLength12 = Math.sqrt((diffX2 * diffX2) + (diffY2 * diffY2));
    offsetX0 = 1.00 * (diffX0 * d[0].group ) / pathLength01;
    offsetY0 = 1.00 * (diffY0 * d[0].group) / pathLength01;
    offsetX2 = (4.0 * (diffX2 / Math.abs(diffX2) )) + ((diffX2 * d[2].group) / pathLength12);
    offsetY2 = (4.0 * (diffY2 / Math.abs(diffY2) )) + ((diffY2 * d[2].group) / pathLength12);

    if (d[0] == d[2]) {
     return "M" + (d[0].x) + "," + (d[0].y - d[0].group)
        + "A" + "20,23 -50 1,1 "
        + " " + (d[2].x + (5.0 * 0.866) + (0.866 * d[2].group)) 
        + "," + (d[2].y + (5.0 * 0.5) + (0.5 * d[2].group));
    } else {
     return "M" + (d[0].x - offsetX0) + "," + (d[0].y - offsetY0)
        + "S" + (1.01 * d[1].x) + "," + (1.01 * d[1].y)
        + " " + (d[2].x - offsetX2) + "," + (d[2].y - offsetY2);
    }
  });
  node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
  });
});

This is designed to work with the arrowhead marker offset to 6 (i.e. .attr("refX", 6)) so that the end of the link is nearly in the middle of the arrowhead and the arrowhead extends about 4 units further toward the node. The arrowheads and the link tails are therefore offset by different amounts to the node border, so if you aren't using a directed graph you will need to adjust the target end's offset to match the source end and get them both right on the border.

Here is an updated JSFiddle that includes all the features necessary to do a directed force layout that includes:

  • Color circles by JSON node property with transparency
  • Color curved edges AND arrowheads by JSON link property
  • Links begin and end at node borders so they do not overlap the start/end nodes
  • Matches style for reflexive edges (that is, source and target are the same node)
  • Changing edge thickness and transparency is also supported

There are some other things that you'll want to tweak for your own application. For example, I added a radius variable to my node data that includes the proper scaling of the property (currently group) to the circle radius, then for the circle it's .attr("r", function(d) { return d.radius;}. I couldn't figure out a way to get the node's circle's r to use in the force function (which I prefer and I would love if somebody could figure out), so that was my work-around.

I think that wraps up a lot of visualization features that I expected to be standard for a tool like D3, but instead were impossible to find and somewhat difficult to implement. But now it's done, and I hope this will save some other people a lot of time in implementing directed networks in D3.

Answer

This has a very simple solution.

1) Draw your nodes at the last. So that your nodes will be on the top of the svg and will hide any section of links inside of it.

2) If you are updating your nodes and links, follow the same pattern and update your node at last.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.