score:1

Accepted answer

You could have separate g elements for the nodes and the links, add the links to the first one, and the nodes to the second. Or, you could just use node.raise() to put those elements after all the links in the DOM:

const width = 800;
const height = 400;

const svg = d3.select("svg").attr("viewBox", [0, 0, width, height]);
let node = svg.selectAll(".node");
let link = svg.selectAll(".link");

const simulation = d3
  .forceSimulation()
  .force("center", d3.forceCenter(width / 2, height / 2))
  .on("tick", (d) => {
    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);

    node
      .attr("transform", d => `translate(${d.x}, ${d.y})`);
  });

const root = {
  x: width / 2,
  y: height / 2,
  level: 0,
  id: "1"
};

function genChildren(p) {
  const dist = 100 / (p.level + 1);
  const nodes = [{
      x: p.x - dist,
      y: p.y,
      level: p.level + 1,
      id: p.id + "1"
    },
    {
      x: p.x + dist,
      y: p.y,
      level: p.level + 1,
      id: p.id + "2"
    },
    {
      x: p.x,
      y: p.y - dist,
      level: p.level + 1,
      id: p.id + "3"
    },
    {
      x: p.x,
      y: p.y + dist,
      level: p.level + 1,
      id: p.id + "4"
    },
  ];
  const links = nodes.map(v => ({
    source: p.id,
    target: v.id
  }));
  return {
    nodes,
    links
  };
}


function update(nodes, links) {
  link = link.data(links)
    .join(enter => {
      return enter.append("line")
        .attr("stroke", "#000")
        .attr("stroke-width", "1.5px")
        .call(enter => enter.transition().attr("stroke-opacity", 1));
    }, update => update, exit => exit.remove());
  node = node.data(nodes, d => d.id)
    .join(
      enter => {
        const g = enter.append("g");
        g.append("circle")
          .attr("r", 12)
          .attr("cursor", "move")
          .attr("fill", "#ccc")
          .attr("stroke", "#000")
          .attr("stroke-width", "1.5px");
        return g;
      },
      update => update,
      exit => exit.remove(),
    )
    .raise()
    .attr("transform", function(d) {
      return "translate(" + d.x + ", " + d.y + ")";
    })
    .on("click", (ev, d) => {
      if (d.level !== 0) {
        return;
      }
      const childrenInfo = genChildren(root)
      const nodes = d.active ? [root] : [root].concat(childrenInfo.nodes);
      const links = d.active ? [] : childrenInfo.links;
      d.active = !d.active;
      update(nodes, links);
    });

  simulation.nodes(nodes).force("link", d3.forceLink(links).id(node => node.id).strength(-0.01));

  if (simulation.alpha() <= 1) {
    simulation.alpha(1);
    simulation.restart();
  }
}

update([root], [])
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<svg></svg>


Related Query