D3 newbie updates a bar chart

I’ve been trying to learn D3.js via Mike Bostock’s excellent "Let’s make a bar chart" tutorial series. This post is my attempt to extend that example to handle data updates.

Starting point

Part 3 of the tutorial ends with a bar chart that shows the relative frequency of letters used in the English language.

The creation of each bar per datum is handled by this code:

chart.selectAll(".bar")
      .data(data)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d.name); })
    .attr("y", function(d) { return y(d.value); })
    .attr("height", function(d) { return height - y(d.value); })
    .attr("width", x.rangeBand());

This says we’re dealing with chart elements of the CSS class .bar for each datum. The .enter() call tells D3 we want to perform the operations that follow for any new data (data that has entered source). We can also use .exit() for data that is no longer in the source. If we want to handle updated data we can add properties directly (outside of enter() / exit()).

Adjusting the bars for new data

To specify updates I had to change the data join so D3 knows how to differentiate added, removed and updated data. In this case we will use the name property, which is a letter from A to Z.

var bar = chart.selectAll(".bar")
        .data(data, function(d) { return d.name; });

Next we’ll modify the code to specify how to handle updated and removed data, instead of just what to do on enter() for new data.

// new data:
bar.enter().append("rect")
   .attr("class", "bar")
   .attr("x", function(d) { return x(d.name); })
   .attr("y", function(d) { return y(d.value); })
   .attr("height", function(d) { return height - y(d.value); })
   .attr("width", x.rangeBand());
// removed data:
bar.exit().remove();
// updated data:
bar
   .attr("y", function(d) { return y(d.value); })
   .attr("height", function(d) { return height - y(d.value); });
   // "x" and "width" will already be set from when the data was
   // first added from "enter()".

Updating the axes

This was enough to update the chart, but the y-axis would draw the new axis over the top of the previous axis, so both values would show. This answer on StackOverflow suggested removing the axis and redrawing it each time, which worked well.

// Remove previous y-axis:
chart.select(".y.axis").remove(); // << this line added
// Existing code to draw y-axis:
chart.append("g")
      .attr("class", "y axis")
      .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Frequency");

Basic transition

The next thing I wanted to try was animating changes to existing data. This turned out to be trivial thanks to D3’s transition() method, which I just dumped prior to the code we used to update each bar.

bar
  .transition().duration(750)  // <<< added this
    .attr("y", function(d) { return y(d.value); })
    .attr("height", function(d) { return height - y(d.value); });

And that’s it!

End result

Here’s an example of the update in action. Use the radio buttons to alternate between the chart showing frequencies of letters in English and the frequencies of letters used in the source for this post.

Comments