Trouble setting fixed nodes in force graph: Fixing nodes break links (D3)

26 views Asked by At

New to D3 and from what I've found on the internet a lot of the questions/examples are rather outdated so I'm having trouble coming up with a solution.

Ultimately I'd like to have a network graph composed on nodes and linkages that are automatically optimally spaced. There are two "starting" nodes which need to be fixed in the center. The remaining nodes need to be spaced around these. I don't need this graph to be draggable.

My starting point is this force directed graph. A lot of other posts mention that fixing a the position of the node should work and the other nodes will naturally be placed around that fixed node but that is not my experience. Instead, although the nodes are fixed, the links appear to be positioned into empty space (or the space where the node would have been - see image below).

enter image description here

I'm fixing the nodes like so inside the ticked function:

  function ticked() {
    node
      .attr('cx', (d) => {
        if (d.id === 'NAME_ONE') {
          return -50;
        } else if (d.id === 'NAME_TWO') {
          return 50;
        } else {
          return d.x;
        }
      })
      .attr('cy', (d) => {
        if (FIXED_NODES.includes(d.id)) {
          return 0;
        } else {
          return d.y;
        }
      });

    link
      .attr('x1', (d) => d.source.x)
      .attr('y1', (d) => d.source.y)
      .attr('x2', (d) => d.target.x)
      .attr('y2', (d) => d.target.y);
  }

Edit: I should note that I've tried to fix the link between these two nodes as well inside the same tick function but that doesn't appear to be fruitful either. Surely there is an easier way.

1

There are 1 answers

0
Andrew Reid On

The force simulation doesn't track the circles in the SVG, it isn't aware of them. Changing the cx/cy of a circle therefore will have no effect on the force simulation.

The force simulation tracks nodes (and therefore link endpoints) via the d.x and d.y properties of the items in the data array. Since these properties are typically used to place circles and lines in an SVG, altering these values will alter their placement.

A D3 force simulation checks two properties each tick: d.fx and d.fy, if these are set, the simulation uses the d.fx and f.fy properties to position the node in the simulation instead of applying any forces (fixing the node in place). So in your case, instead of fixing the SVG element with a cx value, fix the data item with d.fx:

if (d.id === 'NAME_ONE') {
      d.fx = -50;
}

Now you can place all nodes with d.x within the tick function.

Of course you don't need to set d.fx each tick (unless you change the names of the nodes mid simulation) you could run through the nodes once at the beginning and set a fx/fy property once where appropriate.

See also