8 min read

(For more resources related to this topic, see here.)

There are many component vendors selling graphing controls. Frequently, these graph libraries are complicated to work with and expensive. When using them, you need to consider what would happen if the vendor went out of business or refused to fix an issue:

For simple graphs, such as the one shown in the preceding screenshot, d3.js (http://d3js.org/) brings a number of functions and a coding style that makes creating graphs a snap. Let’s create a using d3. The first thing to do is introduce an SVG element to the page. In d3, we’ll append an SVG element explicitly:

var graph = d3.select("#visualization") .append("svg") .attr("width", 500) .attr("height", 500);

d3 relies heavily on the use of method chaining. If you’re new to this concept, it is quick to pick up. Each call to a method performs some action and then returns an object, and the next method call operates on this object. So, in this case, the select method returns the div with the id of visualization. Calling append on the selected div adds an SVG element and then returns it. Finally, the attr methods set a property inside the object and then return the object.

At first, method chaining may seem odd, but as we move on you’ll see that it is a very powerful technique and cleans up the code considerably. Without method chaining, we end up with a lot of temporary variables.

Next, we need to find the maximum element in the data array. Previously we might have used a jQuery each loop to find that element. d3 has built-in array functions that make this much cleaner:

var maximumValue = d3.max(data, function(item){ return item.value;});

There are similar functions for finding minimums and means. None of the functions are anything you couldn’t get by using a JavaScript utility library such as underscore.js (http://underscorejs.org/) or lodash (http://lodash.com/), however, it is convenient to make use of the built-in versions.

In the next piece, we make us of are d3’s scaling functions:

var yScale = d3.scale.linear() .domain([maximumValue, 0]) .range([0, 300]);

Scaling functions serve to map from one dataset to another. In our case, we’re mapping from the values in our data array to the coordinates in our SVG. We use two different scales here: linear and ordinal. The linear scale is used to map a continuous domain to a continuous range. The mapping will be done linearly, so if our domain contained values between 0 and 10, and our range had values between 0 and 100, a value of 6 would map to 60, 3 to 30, and so forth. It seems trivial, but with more complicated domains and ranges, scales are very helpful. Apart from linear scales, there are power and logarithmic scales that may fit your data better.

In our example data, our y values are not continuous, they’re not even numeric. For this case we can make use of an ordinal scale:

var xScale = d3.scale.ordinal() .domain(data.map(function(item){return item.month;})) .rangeBands([0,500], .1);

ordinal scales map a discrete domain into a continuous range. Here, the domain is the list of months and the range the width of our SVG. You’ll note that instead of using range we use rangeBands. rangebands splits the range up into chunks each to which each range item is assigned. So, if our domain was {May, June} and the range 0 to 100, from May we would receive a band from 0 to 49 and 50 to 100 from June. You’ll also note that rangeBands takes an additional parameter, in our case 0.1. This is a padding value that generates a sort of no man’s land between each band. This is ideal for creating a bar or column graph, as we may not want the columns touching each other. The padding parameter can take a value between 0 and 1 as a decimal representation of how much of the range should be reserved for padding. 0.25 would reserve 25% of the range for padding.

There are also a family of built-in scales that deal with providing colors. Selecting colors for your visualization can be challenging, as the colors have to be far enough apart to be discernible. If you’re color-challenged like me, the scales category10, category20, category20b, and category20c may be for you. You can declare a color scale in the the following manner:

var colorScale = d3.scale.category10() .domain(data.map(function(item){return item.month;}));

The preceding code will assign a different color to each month out of a set of 10 pre-calculated possible colors.

Finally, we need to actually draw our graph:

var graphData = graph.selectAll(".bar") .data(data);

We select all the .bar elements inside the graph using selectAll. Hang on! There aren’t any elements inside the graph, let alone elements that match the .bar selector. Typically, selectAll will return a collection of elements matching the selector just as the $ function does in jQuery. In this case, we’re using selectAll as a short-hand method of creating an empty d3 collection that has a data method and can be chained.

We next specify a set of data to union with the data from the existing selection of elements. d3 operates on collections objects without using looping techniques. This allows for a more declarative style of programming, but can be difficult to grasp immediately. In effect, we’re unioning two sets of data: the currently existing data (found using selectAll) and the new data (provided by the data function). This method of dealing with data allows for easy updates to the data elements, should further elements be added or removed later.

When new data elements are added, you can select just those elements by using enter(). This prevents repeating actions on existing data. You don’t need to redraw the entire image, just the portions that have been updated with new data. Equally, if there are elements in the new dataset that didn’t appear in the old one, they can be selected with exit(). Typically, you want to just remove those elements that can be done by running the following:

graphData.exit().remove()

When we create elements using the newly generated dataset, the data elements will actually be attached to the newly created DOM elements. Creating the elements involves calling append:

graphData.enter() .append("rect") .attr("x", function(item){ return xScale(item.month);}) .attr("y", function(item){ return yScale(item.value);}) .attr("height", function(item){ return 300 - yScale(item.value);}) .attr("width", xScale.rangeBand()) .attr("fill", function(item, index){return colorScale(item.month);});

The following diagram shows how data() works with new and existing dataset:

You can see in this chunk of code how useful method chaining has become. It makes the code much shorter and more readable than assigning a series of temporary variables or passing the results into standalone methods. The scales also come on their own here. The x coordinate is found simply by scaling the month we have using the ordinal scale. Because that scale takes into account the number of elements as well as the padding, nothing more complicated is needed.

The y coordinate is similarly found using previously defined yScale. Because the origin in an SVG is in the top-left, we have to take the inverse of the scale to calculate the height. Again, this is a place where we wouldn’t generally be using a constant except for the brevity of our example. The width of the column is found by asking the xScale for the width of the bands. Finally, we set the color based on the color scale so it appears as follows:

Transitions

Being able to animate your visualization is a powerful technique for adding that “wow” factor. People are far more likely to stop and pay attention to your visualization if it looks cool. Spending time to make your visualization look nifty can actually have a payback other than getting mad cred from other developers.

d3 makes transitions simple by doing most of the heavy lifting for you. The transitions work by creating gradual changes in values of properties:

graph.selectAll(".bar") .attr("height", 0) .transition() .attr("height", 50)

This code will gradually change the height on a .bar from 0 to 50 px. By default, the transition will take 250ms, but this can be changed by chaining a call to duration and delayed with a call to delay:

graph.selectAll(".bar") .attr("height", 0) .transition() .duration(400) .delay(100) .attr("height", 50)

This transition will wait 100ms then grow the bar height over a course of 400ms. Transitions can also be used to change non-numeric attributes such as color. I like to use a few transitions during loading to get attention, but they can also be useful when changing the state of the visualization. Even such things as adding new elements using .data can have a transition attached to them.

If there is a problem with transitions, it is that they are too easy to use. Be careful that you don’t overuse them and overwhelm the user. You should also pay attention to the duration of the transition: if it takes too long to execute, users will lose interest.

Summary

This article shed light on the features of d3.js that can be used to create simple graphs. It also covered how to add that “wow” factor to your visualizations using transitions.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here