I am updating a D3 stacked bar graph in combination with AngularJS. In this, all data is initially visible, and updates filter out undesired data. In the past it has worked without issue using this model:
data = [{
name: John Doe,
splits: [{distance: 1, time: 1234},]
},...]
Now I am attempting to to add one more bar to each stack using this model:
data = [{
name: John Doe
time: 12345,
splits: [{distance: 1, time: 1234},]
},...]
My issue is updating the data. My calculated values are recalculated correctly, such as the domain for scaling. A line for time
update still only recognizes the data values from before the update (code snippet heavily truncated for brevity):
// Set ranges of values in axes
x.domain(data.map(function(d) { return d.name}));
y.domain([ min , max]);
// Y Axis is updated to the correct time range
chart.append('g').attr('class', 'y axis').call(yAxis).selectAll('text').style('font-family','Open Sans').style('font-size', '.9rem');
// data join / Select Athlete
var athlete = chart.selectAll(".athlete").data(data),
athdata = athlete.enter(),
console.log(data) // data is correct here
// add container for each athlete
athplace = athdata.append("g")
.attr("class", "athlete")
.attr("transform", function(d) { return "translate(" + x(d.name) + ",0)"; })
.text(function(d) { return d.name}),
// ENTER time
athplace.append('rect')
.attr('class', "time")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.time.time); })
.attr("height", 0).transition().duration(300)
.attr("height", function(d) { return height-y(d.time); });
... enter splits & labels
// exit splits & athlete
splitlabels.exit().transition().duration(300).attr('opacity', 0).remove();
splits.exit().transition().duration(300).attr('height', 0).attr('opacity', 0).remove();
athlete.exit().transition().duration(300).attr('width',0).attr('opacity', 0).remove();
console.log(athlete) // data is still correct
// UPDATE time, this chain has the problem with data not being updated. "d" is equal to previous values
athlete.selectAll('rect.time')
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.time.time); })
.attr("height", function(d) { return height-y(d.time); });
Due to these errors, the updated columns represent the wrong data and produce the wrong visualization. I have been staring/testing at this all day trying to isolate the issue to what I have now. Could someone more experienced with D3 give me some insight?
Note: for those interested, this is all in an Angular directive where I $watch
for changes to the data values, although I am 100% sure that that is not the issue.
Here is a JSFiddle that illustrates the same error as in my script. All of the code was extracted directly from the script exhibiting the issue. The interval update at the bottom imitates the swapping of data that would normally happen.
I played with your example for a little bit and made a few changes that may be of use.
First, I re-factored your global var
's up top to inside your update
function. By doing this, we are not "double-appending" where our x
and y
axis left off. This was seemingly drawing our chart on top of our old chart.
However, fixing this then gave me the new chart overlaying on top of our old. To fix this, I call d3.selectAll("svg > *").remove();
within update
to remove all attached SVG elements and groupings, giving us a "clean slate" to render our new chart on. This solves the overlaying issue.
You mention this is shortened for brevity, so I hope this example can help with your actual application
Updated Fiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With