Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

d3.js - v3 and v4 - Enter and Update differences

I'm trying to get the values for x and y to make a circles using d3.js v4. With the following code I manage to create the chart like behavior of the circles, but when I try to run the same code in v4 it doesn't work anymore. I know that there are some differences in the update to v4 but I didn't find any information about it. So i was wondering if someone can help me to run this code in d3.js v4.

Here is the code using v3 (it will break using v4):

var svg = d3.select('body').append('svg')
  .attr('width', 250)
  .attr('height', 250);

//render the data
function render(data) {
  //Bind 
  var circles = svg.selectAll('circle').data(data);

  //Enter
  circles.enter().append('circle')
    .attr('r', 10);
  //Update
  circles
    .attr('cx', function(d) {
      return d.x;
    })
    .attr('cy', function(d) {
      return d.y;
    });


  //Exit
  circles.exit().remove();
}



var myObjects = [{
  x: 100,
  y: 100
}, {
  x: 130,
  y: 120
}, {
  x: 80,
  y: 180
}, {
  x: 180,
  y: 80
}, {
  x: 180,
  y: 40
}];


render(myObjects);
<script src='https://d3js.org/d3.v3.min.js'></script>
like image 343
Richard Avatar asked Oct 31 '17 09:10

Richard


1 Answers

This is the expected behaviour, and I've explained this before in this answer (not a duplicate, though).

What happened is that Mike Bostock, D3 creator, introduced a magic behaviour in D3 v2, which he kept in D3 v3.x, but decided to abandon in D3 v4.x. To read more about that, have a look here: What Makes Software Good? This is what he says:

D3 2.0 introduced a change: appending to the enter selection would now copy entering elements into the update selection [...] D3 4.0 removes the magic of enter.append. (In fact, D3 4.0 removes the distinction between enter and normal selections entirely: there is now only one class of selection.)

Let's see it.

Here is your code with D3 v3:

var svg = d3.select('body').append('svg')
  .attr('width', 250)
  .attr('height', 250);

//render the data
function render(data) {
  //Bind 
  var circles = svg.selectAll('circle').data(data);

  //Enter
  circles.enter().append('circle')
    .attr('r', 10);
  //Update
  circles
    .attr('cx', function(d) {
      return d.x;
    })
    .attr('cy', function(d) {
      return d.y;
    });

  //Exit
  circles.exit().remove();
}

var myObjects = [{
  x: 100,
  y: 100
}, {
  x: 130,
  y: 120
}, {
  x: 80,
  y: 180
}, {
  x: 180,
  y: 80
}, {
  x: 180,
  y: 40
}];


render(myObjects);
<script src='https://d3js.org/d3.v3.min.js'></script>

Now the same code, with D3 v4. It will "break":

var svg = d3.select('body').append('svg')
  .attr('width', 250)
  .attr('height', 250);

//render the data
function render(data) {
  //Bind 
  var circles = svg.selectAll('circle').data(data);

  //Enter
  circles.enter().append('circle')
    .attr('r', 10);
  //Update
  circles
    .attr('cx', function(d) {
      return d.x;
    })
    .attr('cy', function(d) {
      return d.y;
    });

  //Exit
  circles.exit().remove();
}

var myObjects = [{
  x: 100,
  y: 100
}, {
  x: 130,
  y: 120
}, {
  x: 80,
  y: 180
}, {
  x: 180,
  y: 80
}, {
  x: 180,
  y: 40
}];


render(myObjects);
<script src='https://d3js.org/d3.v4.min.js'></script>

By "break" I mean the circles will be appended, but they will not receive the x and y properties in the "enter" selection, and they will default to zero. That's why you see all circles at the top left corner.

Solution: merge the selections:

circles.enter().append('circle')
  .attr('r', 10)
  .merge(circles) //from now on, enter + update
  .attr('cx', function(d) {
    return d.x;
  })
  .attr('cy', function(d) {
    return d.y;
  });

According to the API, merge()...

... is commonly used to merge the enter and update selections after a data-join. After modifying the entering and updating elements separately, you can merge the two selections and perform operations on both without duplicate code.

Here is the code with merge():

var svg = d3.select('body').append('svg')
  .attr('width', 250)
  .attr('height', 250);

//render the data
function render(data) {
  //Bind 
  var circles = svg.selectAll('circle').data(data);

  //Enter
  circles.enter().append('circle')
    .attr('r', 10)
    .merge(circles) //from now on, enter + update
    .attr('cx', function(d) {
      return d.x;
    })
    .attr('cy', function(d) {
      return d.y;
    });

  //Exit
  circles.exit().remove();
}

var myObjects = [{
  x: 100,
  y: 100
}, {
  x: 130,
  y: 120
}, {
  x: 80,
  y: 180
}, {
  x: 180,
  y: 80
}, {
  x: 180,
  y: 40
}];


render(myObjects);
<script src='https://d3js.org/d3.v4.min.js'></script>
like image 111
Gerardo Furtado Avatar answered Sep 30 '22 23:09

Gerardo Furtado