title: dynamic force directed graph d3.js
body: <p>I am trying to create a dynamic d3.js force directed chart. I am creating some data with my generateData function and then updating this data each 3 seconds with updateData. </p>
  
  <p>The initial data displays well, but then the new node I am adding, are stacking on the top left of the SVG and the links completely disappear.</p>
  
  <p>Hopefully, someone more experienced with d3.js can help me. Please see below my code.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>let nodes = [];
  let previousNodes = [];
  let links = [];
  
  let index = 0;
  const familyTypes = ['Temperature', 'Energy', 'Thermostat', 'Humidity', 'Light']
  
  generateData = (num) =&gt; {
    const sensors = [];
    for (let i = 0; i &lt; num; i++) {
      index++;
      const sensor = {
        id: (index).toString(),
        familyType: familyTypes[Math.floor(Math.random() * 5)]
      };
      sensors.push(sensor);
    }
    return sensors;
  }
  
  updateData = (currentSensors) =&gt; {
    const resultSensors = JSON.parse(JSON.stringify(currentSensors));
  
    // Removes elements
    resultSensors.splice(2, 5);
  
    // Adds elements
    const newSensors = generateData(5);
    newSensors.map(sensor =&gt; resultSensors.push(sensor));
  
    return resultSensors;
  }
  
  updateNode = (newNodes) =&gt; {
    const resultNodes = JSON.parse(JSON.stringify(previousNodes));
    const previousIds = previousNodes.map(node =&gt; node.id);
    const ids = newNodes.map(node =&gt; node.id);
  
    const idAdded = ids.filter(node =&gt; previousIds.indexOf(node) === -1);
    const idRemoved = previousIds.filter(node =&gt; ids.indexOf(node) === -1);
  
    const nodeRemoved = previousNodes.filter(previousNode =&gt; idRemoved.indexOf(previousNode.id) &gt; -1);
    const nodeAdded = newNodes.filter(node =&gt; idAdded.indexOf(node.id) &gt; -1);
  
    nodeRemoved.forEach(node =&gt; resultNodes.splice(previousNodes.indexOf(node), 1));
    nodeAdded.forEach(node =&gt; resultNodes.push(node));
  
    previousNodes = JSON.parse(JSON.stringify(newNodes));
    return resultNodes;
  }
  
  computeLinks = (newNodes) =&gt; {
    const groupedObject = _.groupBy(nodes, 'familyType');
    newLinks = [];
    Object.keys(groupedObject).map((key, i) =&gt; {
      const sensorsByType = groupedObject[key];
      for (let j = 0; j &lt; sensorsByType.length; j++) {
        if (j &gt; 0) newLinks.push({ source: sensorsByType[j], target: sensorsByType[j - 1] });
      }
    });
  
    return newLinks;
  }
  
  var width = window.innerWidth
  var height = window.innerHeight
  
  var color = d3.scaleOrdinal(d3.schemeCategory20);
  
  var svg = d3.select('svg')
  svg.attr('width', width).attr('height', height)
  
  var linkElements,
    nodeElements,
    textElements
  
  var linkGroup = svg.append('g').attr('class', 'links')
  var nodeGroup = svg.append('g').attr('class', 'nodes')
  var textGroup = svg.append('g').attr('class', 'texts')
  
  var simulation = d3
    .forceSimulation()
    .force('link', d3.forceLink().id(d =&gt; d.id))
    .force('charge', d3.forceManyBody())
    .force('center', d3.forceCenter(width / 2, height / 2));
  
  var data = generateData(20);
  nodes = updateNode(data);
  links = computeLinks(data);
  
  updateGraph = () =&gt; {
    // links
    linkElements = linkGroup.selectAll('line')
      .data(links, link =&gt;  link.target.id + link.source.id)
  
    linkElements.exit().remove()
  
    var linkEnter = linkElements
      .enter().append('line')
      .attr('stroke-width', 1)
      .attr('stroke', '#d2d7d3')
  
    linkElements = linkEnter.merge(linkElements)
  
    // nodes
    nodeElements = nodeGroup.selectAll('circle')
      .data(nodes, node =&gt; node.id)
  
    nodeElements.exit().remove()
  
    var nodeEnter = nodeElements
      .enter()
      .append('circle')
      .attr('r', 5)
      .attr('fill', node =&gt; color(node.familyType))
      .call(d3.drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended));
  
    nodeElements = nodeEnter.merge(nodeElements)
  
    // texts
    textElements = textGroup.selectAll('text')
      .data(nodes, node =&gt; node.id )
  
    textElements.exit().remove()
  
    var textEnter = textElements
      .enter()
      .append('text')
      .text(node =&gt; node.familyType )
      .attr('font-size', 15)
      .attr('dx', 15)
      .attr('dy', 4)
  
    textElements = textEnter.merge(textElements)
  }
  
  dragstarted = (d) =&gt; {
    if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  
  dragged = (d) =&gt; {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }
  
  dragended = (d) =&gt; {
    if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }
  
  function updateSimulation() {
    updateGraph()
  
    simulation.nodes(nodes).on('tick', () =&gt; {
      nodeElements
        .attr('cx', function (node) { return node.x })
        .attr('cy', function (node) { return node.y })
      textElements
        .attr('x', function (node) { return node.x })
        .attr('y', function (node) { return node.y })
      linkElements
        .attr('x1', function (link) { return link.source.x })
        .attr('y1', function (link) { return link.source.y })
        .attr('x2', function (link) { return link.target.x })
        .attr('y2', function (link) { return link.target.y })
    })
  
    simulation.force('link').links(links)
  }
  
  updateSimulation()
  
  setInterval(() =&gt; {
    data = updateData(data);
    nodes = updateNode(data);
    updateSimulation()
  }, 3000)</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore.js"&gt;&lt;/script&gt;
  &lt;script src="https://d3js.org/d3.v4.min.js"&gt;&lt;/script&gt;
  &lt;svg width="960" height="600"&gt;&lt;/svg&gt;</code></pre>
  </div>
  </div>
  </p>