archdev
archdev

Reputation: 53

d3 selection confusion with exit() and remove()

So I'm working through some d3 tutorials and learning a bit about why things work the way they do, and I've run into a peculiar instance of selection not behaving as expected. So I'm wondering if anyone can explain this to me.

var sales = [
    { product: 'Hoodie',  count: 7 },
    { product: 'Jacket',  count: 6 },
    { product: 'Snuggie', count: 9 },
];

var rects = svg.selectAll('rect')
  .data(sales).enter();

var maxCount = d3.max(sales, function(d, i) {
    return d.count;
});
var x = d3.scaleLinear()
    .range([0, 300])
    .domain([0, maxCount]);
var y = d3.scaleBand()
    .rangeRound([0, 75])
    .domain(sales.map(function(d, i) {
        return d.product;
    }));

rects.append('rect')
    .attr('x', x(0))
    .attr('y', function(d, i) {
        return y(d.product);
    })
    .attr('height', y.bandwidth())
    .attr('width', function(d, i) {
        return x(d.count);
    });  

This all works good and fine, generates 3 horizontal bars that correspond to the data in sales, but here's where I'm seeing the ambiguity:

sales.pop();

rects.data(sales).exit().remove();

The last line is supposed to remove the bar that was popped, from the visual but it doesn't work. I think that there must be something going on with the d3 selection that I'm missing, because this does work:

d3.selectAll('rect').data(sales).exit().remove();

Also when I break out the first one that doesn't work, it does appear to be selecting the correct element on exit, but just doesn't seem to be removing it. Anyway if anyone can explain what's going on here that would be very helpful, thanks!

Note: using d3 v4

Upvotes: 3

Views: 726

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

This is your rects selection:

var rects = svg.selectAll('rect')
    .data(sales).enter();

So, when you do this:

rects.data(sales).exit().remove();

You are effectively doing this:

svg.selectAll('rect')
    .data(sales)
    .enter()
    .data(sales)
    .exit()
    .remove();

Thus, you are binding data, calling enter, binding data again and calling exit on top of all that! Wow!

Solution: just create a regular, old-fashioned "update" selection:

var rects = svg.selectAll('rect')
    .data(sales);

Here is a basic demo with your code, calling exit after 2 seconds:

var svg = d3.select("svg")

var sales = [{
  product: 'Hoodie',
  count: 7
}, {
  product: 'Jacket',
  count: 6
}, {
  product: 'Snuggie',
  count: 9
}, ];

draw();

function draw() {

  var rects = svg.selectAll('rect')
    .data(sales);

  var maxCount = d3.max(sales, function(d, i) {
    return d.count;
  });
  var x = d3.scaleLinear()
    .range([0, 300])
    .domain([0, maxCount]);
  var y = d3.scaleBand()
    .rangeRound([0, 75])
    .domain(sales.map(function(d, i) {
      return d.product;
    }))
    .padding(.2);

  var rectsEnter = rects.enter().append('rect')
    .attr('x', x(0))
    .attr('y', function(d, i) {
      return y(d.product);
    })
    .attr('height', y.bandwidth())
    .attr('width', function(d, i) {
      return x(d.count);
    });

  rects.exit().remove();

}

d3.timeout(function() {
  sales.pop();
  draw()
}, 2000)
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Upvotes: 2

nizantz
nizantz

Reputation: 1621

rects is already a d3 selection, so you only need rects.exit().remove()

Upvotes: 0

Related Questions