Visualizing data is a big part of BlueCanary and d3.js plays a big role in building those visualization. D3 provides such a powerful toolset with tons of features and built in capabilities. Chief among those are:
- DOM Manipulation
- Data Fetching, e.g. Ajax
- Data parsing, e.g. CSV, JSON, XML
- Data Manipulation (Grouping, transforming, filtering, etc.)
- And of course, SVG
There is definitely some overlap with jQuery‘s features but d3 takes a slightly different approach.
For example, jQuery more or less assumes you are starting with a DOM and wish to do something interesting with it. D3 on the other hands assumes that you have data and wish to iterate over it and display it in some interesting manner.
To illustrate what I mean, first start with an array of values, e.g.:
var data = [1, 2, 3, 4];
To display these values with jQuery you could write something like this:
data.forEach(function(d) {
$('body').append('<div>' + d + '</div>');
});
Nothing fancy. The resulting HTML looks like this:
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
In contrast, to display these values with d3 you could write something like this:
d3.select('body')
.selectAll('div')
.data(data)
.enter()
.append('div')
.text(function(d,i) { return d; });
The output is exactly the same as the jQuery snippet. But the first thing to notice is that there is no obvious or familiar looping construct. Since d3 assumes you are starting with an array of data and obviously need to loop over it, it does away with some of the ceremony of setting up a loop. It introduces its own syntax. So, in d3 the sequence
.selectAll()
.data()
.enter();
is its primary looping mechanism. There is quite a bit going on in those three lines but they form the basis for all of d3’s data binding. Another key element is the callback you see in this line:
.text(function(d,i) { return d; });
The callback – function(d,i) – mirrors the callback in the forEach method where you get access to each data element as the dataset is being iterated over. The callback is passed to methods that you use to set properties and attributes of container elements and shapes. This means that whatever property you are setting you have access to the current data element itself. That’s essentially how d3 achieves data binding.
To look at a more typical d3 example:
var data = [1, 2, 3, 4];
d3.select('body')
.append('svg')
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('height', function (d) { return d * 10; })
.attr('width', function (d) { return d * 20; })
.attr('x', function (d, i) { return d * 100; });
The SVG output is this:
<svg>
<rect height=”10″ width=”20″ x=”100″></rect>
<rect height=”20″ width=”40″ x=”200″></rect>
<rect height=”30″ width=”60″ x=”300″></rect>
<rect height=”40″ width=”80″ x=”400″></rect>
</svg>
And looks like this
Again, nothing fancy. The rectangles though are easily configured according to the data elements in the array. The above pattern is pervasive throughout d3 visualizations and illustrates the rudiments of d3’s approach to data binding. In a nutshell:
1) Looping over data with minimal ceremony
2) Providing access to data elements within the loop via callback
The example above also shows another prevalent pattern in d3 visualizations – method chaining. It may take a little getting used to since it behaves slightly differently than method chaining in jQuery. But all together you can create elegant code that can do a lot of interesting things with data.