Reputation: 53
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
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
Reputation: 1621
rects is already a d3 selection, so you only need rects.exit().remove()
Upvotes: 0