Question

I've built a D3.js barchart comparing several years of data. This is a demo.

As per Mike Bostock's Let's Make a Bar Chart tutorials, the structure of bars is as follows, with a rect and a text element (representing the bar and its label respsectively) nested within a g.bar element, and all g.bars being held in g.chart.

<g class="chart" transform="translate(45,10)">
    <g class="bar" transform="translate(0,0)">
      <rect style="fill: rgb(117, 220, 205); stroke-width: 0.5; stroke: rgb(255, 255, 255);"  height="2.0673635307781666" width="56.9547619047619" y="116.5993031358885">
      <text class="total" y="111.5993031358885" x="28.47738095238095" text-anchor="middle" alignment-baseline="middle" style="stroke: none; fill: rgb(51, 51, 51); font-size: 0.5em; font-weight: bold; display: none;">60</text>
    </g>
    <g class="bar" transform="translate(0,0)">
        <rect style="fill: rgb(117, 220, 205); stroke-width: 0.5; stroke: rgb(255, 255, 255);"  height="2.0673635307781666" width="56.9547619047619" y="116.5993031358885">
        <text class="total" y="111.5993031358885" x="28.47738095238095" text-anchor="middle" alignment-baseline="middle" style="stroke: none; fill: rgb(51, 51, 51); font-size: 0.5em; font-weight: bold; display: none;">60</text>
    </g>
    ...
</g>

Each g.bar should only have one rect and one text element nested within it. In testing my chart, I've noticed that some exits don't appear to be occurring properly. You have to reproduce this by inspecting the demo with Firebug or Chrome dev tools, but after changing a year or indeed on loading the chart without changing the year, each g.bar contains more than one rect and text element. Am I not implementing the enter() exit() remove() pattern correctly? How can I fix this? Thanks!

Was it helpful?

Solution 2

Once you read this, you won't believe how stupid and how nasty the bug was.


Observation

Here is jsfiddle version of the original code, for convenience.

If you play with it long enough, you'll notice that problematic cases are those where the values (heights) of bars are the same in different years (they could be for different countries). For example, value 44 is mentioned both in 2012, and 2009, and when we switch from 2012 to 2009, or vice versa, we have the problem you described.

Moreover, if a value is mentioned twice in the same year, one of two correspondent bars will be missing!

This leads us to conclusion that binding to data is not entirely correct.

And, really, the problem is call to data() after enter()!

Documentation about data() says that the second argument should be key! Not the values as you pass! This explains behavior from above. Anytime the value duplicates, d3 will interpret this as the same key for new or existing data, and will make a mess of your bars!

Solution

Instead of

.data(json[year], function(d){return d;});

use

.data(json[year], function(d, i){ return [year, d, i]; });

and your problem will disappear!

Here is jsfiddle with corrected code - working like charm!


OTHER TIPS

You only want to append rects and text off the .enter selection. So you have to capture the enter selection and then use it when appending rects and text. Like this:

var barsEnter = bars.enter()
    .append("g")
    .classed("bar",true);

...then apply it:

barsEnter.append("rect")
...
barsEnter.append("text")
...

Here is a PLUNK illustrating this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top