Tribune DataViz

Matters of interest, from the data reporters and developers across Tribune Publishing

Archive for the ‘Javascript’ Category

Responsive Charts with D3 and Backbone

with 2 comments

So, you started building charts with D3 and quickly realized there are certain behaviors you want all of your charts to have. Being the excellent developer you are, you decided to wrap the basics up in a nicely packaged, reusable bit of code that will help you build charts faster in the future.

For our team, this meant creating a simple Backbone view to encapsulate all of our charts’ must-haves. Why Backbone? Because we like it, though you can certainly accomplish all of what you’ll see here with a jQuery plugin or your own homebrew JavaScript lib.

Here’s how you can use it.

Simple bar chart

Just an example of a simple bar chart. If you’re not familiar with the code below, you’ll want to check out Michael Bostock’s Let’s make a bar chart.

Making your chart code reusable

At this point you’ve created a single, crappy little bar chart and you can hardly contain your joy. You can’t deny your desire to plaster these things all over your site. You WILL make this code reusable. Soon you will have loads of these things EVERYWHERE.

Before you do, however, consider a few things that might make your crappy little bar chart a little less crappy. You need:

  • Responsiviosity, responsiveness, responsivity — whatever you call it, it’s a way to redraw the chart when the window resizes
  • A simple way to extend and modify your chart to create different versions
  • A way to vary the appearance of the chart at certain viewport breakpoints
  • A fallback mechanism for browsers that don’t support D3

Enter ChartView.js — a simple Backbone view put together by my teammate David Eads to address these very needs.

So let’s wrap our bar chart up in a `ChartView` based view.

First thing we do is define `BarChartView` by extending `ChartView`. The only function we must override is the `draw` member function of `ChartView`. The code within our `draw` function looks a lot like the code we wrote to render our simple chart, but it takes advantage of some of the values that `ChartView` calculates and tracks for us. For example, instead of defining a `width` variable, we use `this.dimensions.width,` which is calculated based on the chart’s parent element.

var BarChartView = ChartView.extend({
  draw: function() {
    var scale = d3.scale.linear()
      .domain([0, d3.max(])
      .range([0, this.dimensions.width]);
      .attr('class', 'bar-chart')
          .style('width', function(d) { return scale(d) + 'px'; })
          .style('height', (this.dimensions.wrapperHeight / 5) + 'px')
          .html(function(d) { return '' + d + ''; });

The next thing we do is create a new instance (or two, or three, etc — remember charts EVERYWHERE) of our `BarChartView`. The minimum you need to get started is an options object with `el` and `data` or `collection` defined (note: you can only use one of `data` or `collection` with views that extend `ChartView` — not both).

var chart_one_data = [3, 8, 12, 7, 17];
var chart_two_data = [4, 10, 13, 14, 7];

var chart_one = new BarChartView({
  el: '#one',
  data: chart_one_data,
  base_height: 220

var chart_two = new BarChartView({
  el: '#two',
  data: chart_two_data,
  base_height: 220

Remember to call .render() or you won’t see your chart!

If you resize your browser, you’ll see these charts are now responsive. At viewport breakpoints of 420 and 728 pixels wide, the height of the charts’ containers will be adjusted to 0.7 and 0.9 of the base_height option we passed when creating them. These breakpoints are, of course, customizable:

var chart_one = new BarChartView({
  el: '#responsive-bar-chart',
  data: chart_data,
  base_height: 220,
  breakpoints: {
    728: 0.9,
    420: 0.7,
    380: 0.65

An advanced example

I know what you’re thinking. The examples are awesome, but yo, you don’t even use SVG for these charts. How about showing some real code?

You’re right. To show how to use this in a real life scenario, I thought I’d refactor some of the code we wrote for our Broken Bonds series. Trust me when I tell you you don’t want to see the original code. You can, however, see the refactored code by clicking here. Look at this code in action below.

Note that this chart has a few more options specified:

var obli_chart = new ObligationDebtChartView({
  el: '#ob_chart_container',
  collection: new Backbone.Collection(bonds),
  y_key: 'debt_per_capita',
  y_scale_max: '7e3',
  base_height: 425,
  breakpoints: {
    600: 0.75,
    380: 0.5

The `y_key` is used to determine which key to pluck from each item in our dataset to draw the chart’s bars. The `y_scale_max` option is used to adjust the maximum value that can be plotted on the chart — in this case, $7,000.

Also, notice we’re specifying a `collection`. When this option is present, `ChartView` will bind to the collection’s “sync” event, triggering a re-rendering of the chart any time the collection data changes. A big yay for events!

I won’t go through ObligationDebtChartView.js line-by-line. What’s important here is that the process is the same regardless of how complex your chart’s D3 render code is.

 draw: function() {
    return this;

We have our draw function, which calls a handful of other functions that do the heavy D3 lifting. Our chart is responsive — the bars squish as the viewport is constrained and axis labels change to be legible on smaller screens.

Fallback for older browsers

One last thing to cover — `ChartView` checks whether the browser supports D3 and will display a message if it does not.

In this example, I call .fallback_draw() directly to illustrate the point, but you should never have to do this. `ChartView` will replace the default .draw() method with .fallback_draw() when needed:

   // Fallback if d3 is unavailable, add some formatters otherwise.
    if (!this.d3) {
      this.draw = this.fallback_draw;

Again, you can customize the fallback behavior by overriding this method. For example, you might want to show an image of your chart:

  fallback_draw: function() {
    this.$el.append('<img src="" />');


Written by Ryan Nagle

March 7, 2014 at 4:12 pm