A better way to structure D3 code

Check out the updated version of this blog post updated for D3 v4 and ES6 class syntax.

Note: This blog post is aimed at beginner-to-intermediate users of the D3 JavaScript library. If you want to skip straight to the good stuff, check out the accompanying example on bl.ocks.org.

Code written using D3 is difficult to manage. Even in a simple line chart, there will be almost a dozen important variables such as (deep breath): width, height, margins, SVG, x-scale, x-axis generator function, x-axis SVG group, x-scale, y-axis generator function, y-axis SVG group, line generator function, path element and (most important of all) a data array/object. And that’s just the bare minimum.

Because most of these need to be accessible at several points in a script, the temptation is to structure the entire thing in one giant function. Many examples on bl.ocks.org are essentially unstructured, which makes the concepts nice and clear but in real-world code can lead to an unmanageable mess.

Credit where credit is due: I was introduced to this idea by my colleague Jason French. I’ve since adopted it and use it regularly. This is my attempt at formalising it.

A solution: object-oriented programming

Think of a D3 chart or visualisation as a ‘widget’ on the page. This provides a number of benefits:

Here’s what we’re aiming for: being able to create the chart as if it were a Highcharts/C3-style thing.

var chart = new Chart({
  element: document.querySelector(".chart-container"),
  data: [
    [new Date(2016, 0, 1), 10],
    [new Date(2016, 1, 1), 70],
    [new Date(2016, 2, 1), 30],
    [new Date(2016, 3, 1), 10],
    [new Date(2016, 4, 1), 40],
  ],
});

Which we could then modify like so:

// load in new data
chart.setData(newData);

// change line colour
chart.setColor("blue");

// redraw chart, perhaps on window resize
chart.redraw();

A quick introduction to constructor functions

Before we move onto the D3-specific stuff, it’s worth learning how to use constructor functions. This is a useful general-purpose pattern for JavaScript code used frequently in both JavaScript’s native functions and in third-party libraries.

You may already be familiar with:

var d = new Date(2016, 0, 1);

This creates a new object stored in d, which is based on (but does not replace) the original Date object. Date is a constructor, and d is an instance of Date.

We can make our own constructor functions like so:

var Cat = function () {
  // nothing here yet
};
Cat.prototype.cry = function () {
  return "meoww";
};

The .prototype bit defines an instance method, which will be available to each instance of the Cat constructor. We would call it like so:

var bob = new Cat();
bob.cry(); // => 'meoww'

Inside of the constructor function, there is a special variable called this which refers to the current instance. We can use it to share variables between instance methods.

var Cat = function (crySound) {
  this.crySound = crySound;
};
Cat.prototype.cry = function () {
  return this.crySound;
};

In this case, we are customising the new cat’s crySound.

var bob = new Cat("meoww");
var noodle = new Cat("miaow");
bob.cry(); // => 'meoww'
noodle.cry(); // => 'miaow'

Because each instance is a new object, this style of coding is called object-oriented programming.

There’s a lot more to constructor functions, and if you want to learn more I recommend reading CSS Tricks’ Understanding JavaScript Constructors and Douglas Crockford’s more hardcore Classical Inheritance in JavaScript.

A chart as a constructor function

Instead of Cat — which is obviously a fairly useless constructor — we could instead make a constructor for a D3 chart:

var Chart = function (opts) {
  // stuff
};
Chart.prototype.setColor = function () {
  // more stuff
};
Chart.prototype.setData = function () {
  // even more stuff
};

Here’s a live example of a chart made using a Chart constructor. Try clicking the buttons below and resizing the window.

<style>
/* a little bit of CSS to make the chart readable */
.line {
    fill: none;
    stroke-width: 4px;
}
.axis path, .tick line {
    fill: none;
    stroke: #333;
}
</style>

<!-- here's the div our chart will be injected into -->
<div class="chart-container" style="max-width: 1000px;"></div>

<!-- these will be made useful in the script below -->
<button class="color" data-color="red">red line</button>
<button class="color" data-color="green">green line</button>
<button class="color" data-color="blue">blue line</button>
<button class="data">change data</button>

<script>
// create new chart using Chart constructor
var chart = new Chart({
	element: document.querySelector('.chart-container'),
    data: [
        [new Date(2016,0,1), 10],
        [new Date(2016,1,1), 70],
        [new Date(2016,2,1), 30],
        [new Date(2016,3,1), 10],
        [new Date(2016,4,1), 40]
    ]
});

// change line colour on click
d3.selectAll('button.color').on('click', function(){
    var color = d3.select(this).text().split(' ')[0];
    chart.setColor( color );
});

// change data on click to something randomly-generated
d3.selectAll('button.data').on('click', function(){
    chart.setData([
        [new Date(2016,0,1), Math.random()*100],
        [new Date(2016,1,1), Math.random()*100],
        [new Date(2016,2,1), Math.random()*100],
        [new Date(2016,3,1), Math.random()*100],
        [new Date(2016,4,1), Math.random()*100]
    ]);
});

// redraw chart on each resize
// in a real-world example, it might be worth ‘throttling’ this
// more info: http://sampsonblog.com/749/simple-throttle-function
d3.select(window).on('resize', function(){
    chart.draw();
});

</script>

And here’s the corresponding JavaScript for the chart. To see how it’s being used, read the full code on bl.ocks.org.

A few things worth emphasising here:

Update 30 May 2016: Here’s an example of a more complicated chart using the constructor pattern, courtesy of Nick Strayer.

Watch out for anonymous functions

The only catch with using constructor functions is that the value of this will change inside of anonymous functions — which, in D3, are everywhere.

What do I mean by that? Inside of Chart or a Chart.prototype method, this refers to the Chart instance, as expected.

var Chart = function (opts) {
  // here, `this` is the chart
};
Chart.prototype.setColor = function () {
  // here, `this` is still the chart
};

However, the value of this can change when inside an anonymous function:

Chart.prototype.example = function () {
  // here, `this` is the chart
  var line = d3.svg.line().x(function (d) {
    // but in here, `this` is the SVG line element
  });
};

There’s a simple solution, which is to load this into a variable called _this:

Chart.prototype.example = function () {
  var _this = this;
  var line = d3.svg.line().x(function (d) {
    // in here, `this` is the SVG line element
    // but `_this` (with an underscore) is the chart
  });
};

Hardly difficult to get around, then, but worth keeping in mind. Some people prefer to use that instead of _this, which is just as good.2

Rules to live by

To keep the Chart function’s responsibilities from spiralling out of control, I try to stick by these rules:

It works, honest

I’ve used this pattern several times now in graphics published on WSJ.com, including ECB Meets, Euro Reacts and The World’s Safest Bonds Are Actually Wild Risks. In both cases, the constructor pattern made managing these dynamically-updating charts a breeze. The rest of the code, on the other hand…

Ps. If you enjoyed this, you might like my previous blog post on how my JavaScript coding style has changed since 2014.

Thanks to Amelia Bellamy-Royds for providing feedback on a draft of this post.

Footnotes

  1. In this case, there is no practical difference between the object’s ‘private’ and ‘public’ methods — they are all accessible from outside of the object. For a list of ways to make pseudo-private or actual private methods, see this article.

  2. _this and that are both fine, but self is not a good option because it may conflict with window.self. Another option, if you’re using ES6 (the newest version of JavaScript, which is only supported in the very latest browsers), is “fat arrow functions”, which always inherit the value of this.

  3. If you’re feeling especially brave, they could inherit from a parent object. I’ve never tried this myself.

Published .