Skip to content

Instantly share code, notes, and snippets.

@tmcw
Last active December 6, 2022 14:04
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tmcw/5561921 to your computer and use it in GitHub Desktop.
Save tmcw/5561921 to your computer and use it in GitHub Desktop.
Accompaniment to dcjq

This is a more wordy, narrative accompaniment to my pretty bare presentation about d3 that I gave to the jQuery DC Meetup.

What is d3?

  • Not a chart library (though you can make charts with it)
  • Not a map library (though you can make maps with it)

Which is to say, d3 can be used for building things, but the 'atomic parts' are lower-level than bar graphs or projections or so on. This is a powerful fact. It also means that d3 is a good basis for simple interfaces, like Vega.js, that make its power accessible in other ways.

  • Not a compatibility layer (it doesn't work with bad browsers)
  • Not about SVG or HTML or Canvas (though you can use it with them)

That is, d3 is built around web standards and takes an optimistic or futuristic view towards browsers. This doesn't mean that it makes no concessions to browser problems - it has some compatibility code. But that's to make its functionality work, rather than to encapsulate browser functionality.

d3 via not-d3

d3.select('#foo')
  .style('background', '#000')
  .on('click', function() {})
  .append('div');

$('#foo')
  .css('background', '#000')
  .click(function() {})
  .append($('<div></div>'));

d3 and jQuery both provide conveniences over stuff like setting CSS classes and styles, setting inner text, etc. They both do method chaining (so-called monads) to make this easier.

d3.json('foo.json',
  function(err, data) { });

$.getJSON('foo.json',
  function(data) { });

They also provide basic stuff like AJAX wrappers and so on. d3's code for these things is typically smaller and simpler because it aims to support fewer browsers.

joins and imperativeness

Let's select a ul item with the id foo and add 10 items, each with a number.

in jQuery: http://jsfiddle.net/tmcw/ScrrN/

var $foo = $('#foo');
for (var i = 0; i < 10; i++) {
    $foo.append($('<li></li>').text(i))
}

in d3: http://jsfiddle.net/tmcw/rA3bC/

d3.select('#foo')
    .data(d3.range(10))
    .enter()
    .append('li')
    .text(String);

Besides length considerations (it's probably easier to write either in fewer chars), there's a big difference here: d3 tends to have fewer loops and be a more declarative system: you are looking for 10 list elements and when you don't find one, you have a specific 'thing to do'.

The advantage of the d3 way is visible in a few different places, but let's see one common shortfall: let's have each li element say its number when clicked.

in jQuery, a novice would write the following code: http://jsfiddle.net/tmcw/4phm7/1/

var $foo = $('#foo');
for (var i = 0; i < 10; i++) {
    $foo.append($('<li></li>').text(i).on('click', function() {
        alert(i);
    }));
}

Of course, every element will say '10' because the value of i will change with the loop. There are a few routes out of this - creating a closure with the value of i, or using a .forEach loop rather than a for loop. But all of them have echoes of the core problem, that you have data, and you aren't really 'binding' it in any kind of good way. HTML5 data attributes don't count, since they can only be strings and incur a DOM cost.

Whereas the d3 way would be: http://jsfiddle.net/tmcw/rK9RY/1/

d3.select('#foo')
    .data(d3.range(10))
    .enter()
    .append('li')
    .text(String)
    .on('click', function(d) { alert(d); });

Not only does it not have the pitfall, but it's also solving the data problem is a reusable way: there's a convention of associating data with elements.

why SVG, SVG vs Canvas

It's somewhat odd that d3 is SVG-centric, given that much attention has been directed towards HTML5 Canvas, and it's easier to write performant one-off implementations in Canvas. Let's do a really high-level overview of why:

Canvas is a raster-based drawing interface. You draw things onto a Canvas and they're drawn, but you can't ask the canvas for rectanges and circles drawn on it, only pixels. Similarly, if you draw something on Canvas, you can't get click events out of it. Of course, you could implement things on top of canvas to provide the scenegraph necessary, but few people do.

Essentially: it's harder to make Canvas elements interactive in the way we expect out of HTML than SVG.

SVG is a DOM for graphical elements. Where HTML has div and span, SVG has circle and rect: graphical primitives. These are elements just like HTML on your page, and you can add and remove them, get events from mouse interaction on them, and so on.

Also, amongst other concerns, SVG 'automatically' does retina since it's vector-based and so the renderer does 2x, just like your fonts on a retina display. Canvas doesn't, and you'll have to basically 2x all of your drawing.

the full API

The best place to really read about the full scope of d3 is in the API docs, which cover everything, to some level of depth. But also:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment