简体   繁体   中英

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

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> 

rects已经是d3选择,所以你只需要rects.exit().remove()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM