Search code examples
javascriptd3.js

Min and max length of a d3.js graph links


I want to make a graph simulation with d3 where I can drag the nodes and move them around, but the length of the links must not be less than 300 or more than 600, how can I approach this?

Here is my simulation:

const simulation = d3
  .forceSimulation(nodes)
  .force("charge", d3.forceManyBody().strength(-300))
  .force(
    "link",
    d3
      .forceLink(links)
      .id((d) => d.id)
      .distance(300),
  )
  .force(
    "center",
    d3.forceCenter(
      container.node().clientWidth / 2 - 75,
      container.node().clientHeight / 2 - 75,
    ),
  );

For now I could only set the constat length of a link.

I tried to search in the d3 documentation, but didn't find anything, if there is alerady such a problem with described solution, I would be glad to see it.

Thank you!


Solution

  • I believe you'll have to implement your own force since none of the built-in forces support your use case. For example, forceLink.distance() only initializes the distances once.

    A custom implementation would look something like this (see live demo):

    function linkForce(links, {
      min = 50, max = 200, strength = .5, id = d => d.id
    } = {}) {
      const clamp = (a, b, v) => v <= a ? a : v >= b ? b : v;
      
      const update = (alpha) => {
        const s = .5 * alpha * strength;
        for(const {source: a, target: b} of links) {
          const dx = a.x + a.vx - b.x - b.vy;
          const dy = a.y + a.vy - b.y - b.vy;
          const dn = Math.hypot(dx, dy);
          const dc = clamp(min, max, dn);
          if(dn === dc) continue;
          const f = (dc - dn) / dn * .5 * alpha * strength;
          a.vx += dx * f;
          a.vy += dy * f;
          b.vx -= dx * f;
          b.vy -= dy * f;
        }
      };
    
      const initialize = nodes => {
        const map = new Map(nodes.map(d => [id(d), d]));
        for(const link of links) {
          link.source = map.get(link.source);
          link.target = map.get(link.target);
        }
      };
      
      return Object.assign(update, {initialize});
    }