Adding nodes dynamically to D3 Force Layout in version 4
<p>I am trying to implement a simple force layout in which nodes (without links) can be dynamically added and removed. I was successful in implementing the concept in D3 version 3, but I am unable to translate it to version 4. After adding and updating nodes the simulation freezes and incoming circles are drawn in the upper left corner of the svg. Does someone knows why this is the case? Thanks for any help :)</p>

<p>My concept is based on this solution:
<a href="https://stackoverflow.com/questions/9539294/adding-new-nodes-to-force-directed-layout/9544595#9544595">Adding new nodes to Force-directed layout</a></p>

<p><a href="https://jsfiddle.net/Lvkg1avo/" rel="nofollow noreferrer">JSFiddle: working code in d3 v3</a></p>

<pre><code>/* Define class */
class Planet {
  constructor(selector) {
    this.w = $(selector).innerWidth();
    this.h = $(selector).innerHeight();

    this.svg = d3.select(selector)
      .append('svg')
      .attr('width', this.w)
      .attr('height', this.h);

    this.force = d3.layout.force()
      .gravity(0.05)
      .charge(-100)
      .size([this.w, this.h]);

    this.nodes = this.force.nodes();
  }

  /* Methods (are called on object) */
  update() {
    /* Join selection to data array -&gt; results in three new selections enter, update and exit */
    const circles = this.svg.selectAll('circle')
      .data(this.nodes, d =&gt; d.id); // arrow function, function(d) { return d.y;}

    /* Add missing elements by calling append on enter selection */
    circles.enter()
      .append('circle')
      .attr('r', 10)
      .style('fill', 'steelblue')
      .call(this.force.drag);

    /* Remove surplus elements from exit selection */
    circles.exit()
      .remove();

    this.force.on('tick', () =&gt; {
      circles.attr('cx', d =&gt; d.x)
        .attr('cy', d =&gt; d.y);
    });

    /* Restart the force layout */
    this.force.start();
  }

  addThought(content) {
    this.nodes.push({ id: content });
    this.update();
  }

  findThoughtIndex(content) {
    return this.nodes.findIndex(node =&gt; node.id === content);
  }

  removeThought(content) {
    const index = this.findThoughtIndex(content);
    if (index !== -1) {
      this.nodes.splice(index, 1);
      this.update();
    }
  }
}

/* Instantiate class planet with selector and initial data*/
const planet = new Planet('.planet');
planet.addThought('Hallo');
planet.addThought('Ballo');
planet.addThought('Yallo');
</code></pre>

<p>This is my intent of translating the code into v4:</p>

<pre><code>/* Define class */
class Planet {
  constructor(selector) {
    this.w = $(selector).innerWidth();
    this.h = $(selector).innerHeight();

    this.svg = d3.select(selector)
      .append('svg')
      .attr('width', this.w)
      .attr('height', this.h);

    this.simulation = d3.forceSimulation()
      .force('charge', d3.forceManyBody())
      .force('center', d3.forceCenter(this.w / 2, this.h / 2));

    this.nodes = this.simulation.nodes();
  }

  /* Methods (are called on object) */
  update() {
    /* Join selection to data array -&gt; results in three new selections enter, update and exit */
    let circles = this.svg.selectAll('circle')
      .data(this.nodes, d =&gt; d.id); // arrow function, function(d) { return d.y;}

    /* Add missing elements by calling append on enter selection */
    const circlesEnter = circles.enter()
      .append('circle')
      .attr('r', 10)
      .style('fill', 'steelblue');

    circles = circlesEnter.merge(circles);

    /* Remove surplus elements from exit selection */
    circles.exit()
      .remove();

    this.simulation.on('tick', () =&gt; {
      circles.attr('cx', d =&gt; d.x)
        .attr('cy', d =&gt; d.y);
    });

    /* Assign nodes to simulation */
    this.simulation.nodes(this.nodes);

    /* Restart the force layout */
    this.simulation.restart();
  }

  addThought(content) {
    this.nodes.push({ id: content });
    this.update();
  }

  findThoughtIndex(content) {
    return this.nodes.findIndex(node =&gt; node.id === content);
  }

  removeThought(content) {
    const index = this.findThoughtIndex(content);
    if (index !== -1) {
      this.nodes.splice(index, 1);
      this.update();
    }
  }
}
</code></pre>