If you’re the kind of programmer who likes to learn by taking apart sample code, getting started with D3 can be fairly bumpy. There are plenty of good examples to work from, but they typically aren’t clear about the terms they’re using. The aim of this blog post is to explain some of D3’s vocabulary so you can understand what you’re doing sooner.
For the sake of our discussion, we’ll be using this simple chart:
var height = 500;
var width = 900;
var xScale = d3.scale.linear().range([0, width]).domain([0, 10]);
var yScale = d3.scale.linear().range([height, 0]).domain([0, 100]);
var yAxisGenerator = d3.svg.axis().scale(yScale).orient('right');
var pathDefinition = d3.svg.line()
.x(function(datum, index) { return xScale(index); })
.y(function(datum) { return yScale(datum); });
var data = [12, 90, 0, -40, 25, 16, 15, 120, 8];
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var yAxis = svg.append('g')
.call(yAxisGenerator);
var path = svg.append('path')
.datum(data)
.attr('d', pathDefinition);
Scale
var xScale = d3.scale.linear().range([0, width]).domain([0, 10]);
var yScale = d3.scale.linear().range([height, 0]).domain([0, 100]);
A D3 scale is a function which maps values from a domain to a range. The domain is the minimum and maximum data values that you would like to be visible. The range is the minimum and maximum pixel values the domain should map to. You can see this in action in the screenshot above: the two values which exceed the limits of the domain cause the line to reach off the top and bottom of the graph.
Why does the yScale range go from high to low? Because SVGs, like the HTML DOM, define the origin as the top left corner of the window, but most charts define the origin as the bottom left. Inverting the range is an easy way to flip the axis to match our expectations.
Generators and Definitions
var yAxisGenerator = d3.svg.axis().scale(yScale).orient('right');
var pathDefinition = d3.svg.line()
.x(function(datum, index) { return xScale(index); })
.y(function(datum) { return yScale(datum); });
D3 provides a number of reusable functions for defining the behavior or appearance of graph components. These require careful naming, because you’ll often want to store the functions and their output, both of which will pertain to the same domain concept.
I like to call these functions generators and definitions. Generators output DOM elements, and definitions output DOM elements’ SVG attributes.
DOM Elements
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var yAxis = svg.append('g')
.call(yAxisGenerator);
var path = svg.append('path')
.datum(data)
.attr('d', pathDefinition);
Now that we have our generators, we can use them to produce visible DOM elements. As is familiar to jQuery users, append inserts elements, and attr sets attributes. call looks more complicated than it is—all it does is pass the current selection into its parameter, and return the selection. datum binds data to a DOM element so it can later be accessed (and iterated over) by generators. Finally d is just the SVG path definition; the function passed in will be passed the bound datum, and its output will be set in the d attribute.
Wrapping Up
This is only the tip of the iceberg. We haven’t covered enter, exit, data vs datum, and lots more. D3 is an extremely powerful tool, and using it to the fullest means understanding the many concepts of its fundamental grammar.
If all of this seems a bit extreme, you’re probably right. D3 is a low level tool, giving you absolute control over how you want your data visualized. For simple charts you’re much better off using a high level tool built on top of D3 like Rickshaw, Dimple, or NVD3.