Search code examples
javascriptd3.js

Adding a new node to a d3 directed graph


I'm trying to add a new node to an existing graph in d3.

I can't find what's the problem with my code here:

Fiddle

This is the code for the add node:

   function handleNodeClick(node) {
  // Create a node connected to the clicked node
  const newNode1 = {
    id: "NewNode1",
    label: "New Node 1",
    group: "New Nodes"
  };

  // Create a new edge connecting the clicked node to newNode1
  const newLink1 = {
    source: node.id,
    target: newNode1.id,
    label: "New link 1"
  };

  // Update the graph data
  graphData.nodes.push(newNode1);
  graphData.links.push(newLink1);

  // Update the simulation nodes and edges
  simulation.nodes(graphData.nodes);
  simulation.force("link").links(graphData.links);

  // Restart the simulation
  simulation.alpha(1).restart();
}

Solution

  • The issue is hard to identify at first, but after you find it it's quite obvious: you are indeed adding the new node to the simulation... but you are never drawing the node on the screen, that is, the simulation is running and calculating the position of an element you never painted in the SVG.

    Have a look at your nodes selection:

    const nodes = svg.selectAll("circle")
      .data(graphData.nodes)
      .enter()
      .append("circle")
      .attr("r", 10)
      .attr("fill", "blue")
    

    That's just an "enter" selection, the new node will not be added. The same goes for the links.

    An easy solution is creating a function you can pass the data (here named drawElements with a proper D3 set of selections, which include:

    • the update selection
    • the exit selection
    • the enter selection

    Here is the demo:

    // Define the graph data
    const graphData = {
      nodes: [{
          id: "Node1"
        },
        {
          id: "Node2"
        }
      ],
      links: [{
        source: "Node1",
        target: "Node2"
      }]
    };
    
    // Set up the SVG container
    const width = 400;
    const height = 400;
    const svg = d3.select("#graph")
      .attr("width", width)
      .attr("height", height);
    
    // Create the simulation
    const simulation = d3.forceSimulation(graphData.nodes)
      .force("charge", d3.forceManyBody().strength(-100))
      .force("link", d3.forceLink(graphData.links).id(d => d.id).distance(100))
      .force("center", d3.forceCenter(width / 2, height / 2));
    
    drawElements(graphData.nodes, graphData.links);
    
    function drawElements(nodesData, linksData) {
      let links = svg.selectAll("line")
        .data(linksData);
    
      links.exit().remove();
    
      links = links.enter()
        .append("line")
        .attr("stroke", "black")
        .attr("stroke-width", 2)
        .merge(links);
    
      // Create the nodes
      let nodes = svg.selectAll("circle")
        .data(nodesData);
    
      nodes.exit().remove();
    
      nodes = nodes.enter()
        .append("circle")
        .attr("r", 10)
        .attr("fill", "blue")
        .merge(nodes)
        .on("click", handleNodeClick);
    
      simulation.on("tick", () => {
        links
          .attr("x1", d => d.source.x)
          .attr("y1", d => d.source.y)
          .attr("x2", d => d.target.x)
          .attr("y2", d => d.target.y);
    
        nodes
          .attr("cx", d => d.x)
          .attr("cy", d => d.y);
      });
    };
    
    
    function handleNodeClick(node) {
      // Create A new node connected to the clicked node
      const newNode1 = {
        id: "NewNode1",
        label: "New Node 1",
        group: "New Nodes"
      };
    
      // Create a new edge connecting the clicked node to newNode1
      const newLink1 = {
        source: node.id,
        target: newNode1.id,
        label: "New link 1"
      };
    
      // Update the graph data
      graphData.nodes.push(newNode1);
      graphData.links.push(newLink1);
    
      // Update the simulation nodes and edges
      simulation.nodes(graphData.nodes);
      simulation.force("link").links(graphData.links);
    
      drawElements(graphData.nodes, graphData.links);
      // Restart the simulation
      simulation.alpha(1).restart();
    
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    <svg id="graph"></svg>

    This is just to get you started, there are other issues to fix (for instance, try to click a leaf node).