Be the first user to complete this post

  • 0
Add to List

Using d3js to draw a bar chart with an axis and some basic transitions

d3_animated_bar_chart_71-138
D3js lets you make your charts a bit more lively by adding simple transitions. In this post, we will build upon our previous article by giving our bar chart an x and y axis and give each bar a simple 'rising from the bottom' transition effect. In this post, we will also refactor our previous example and make it a much more configurable chart. The complete jsfiddle for this tutorial can be found here. Before I begin, lets outline the various steps involved. d3_animated_bar_steps
We start by creating our data and presetting some that we wish to have for our svg and some inner padding.
var employees = [
    {dept: 'A', age : 22},
    {dept: 'B', age : 26},
    {dept: 'C', age : 35},
    {dept: 'D', age : 30},
    {dept: 'E', age : 27}
];

var svgHeight = 400;
var svgWidth = 400;
var maxAge = 65; // You can also compute this from the data
var barSpacing = 1; // The amount of space you want to keep between the bars
var padding = {
    left: 20, right: 0,
    top: 20, bottom: 20
};
We give our chart some inner padding so that when we render our axis, there is enough space to show their labels on the left and right of the chart. The next step is to define the actual drawing area for the bars themselves.
function animateBarsUp() {
    var maxWidth = svgWidth - padding.left - padding.right;
    var maxHeight = svgHeight - padding.top - padding.bottom;
}
The maxWidth and maxHeight defines the area within which our bars will be drawn.

Defining our conversion functions

With this knowledge, we can now define our conversion functions that can be used later to convert our raw data into pixel coordinates for the svg.
// Define your conversion functions
var convert = {
    x: d3.scale.ordinal(),
    y: d3.scale.linear()
};

Notice that we use an ordinal scale function in for x. Thats because we want to render the department titles on the x axis which is ordinal(i.e. follows an orderly sequence but there is no way to derive one item from the other)
NOTE: Keep in mind that although convert.x and convert.y might appear as variables at first glance, they are actually functions that can take an argument and return a transformed value depeneding upon its domain and range settings.

Setting up the axis functions

Now lets define the functions that will be used to render the x and y axis.
// Define your axis
var axis = {
    x: d3.svg.axis().orient('bottom'),
    y: d3.svg.axis().orient('left')
};

// Define the conversion function for the axis points
axis.x.scale(convert.x);
axis.y.scale(convert.y);
There are two things to be observed in the above code.
  1. An orient parameter is use to describe the axis. The value of the parameter tells you on which side of the axis are the values of the labels rendered. For the x axis, we want to render the labels below the axis and for the y axis, we want to render it on on the left of the axis.
  2. We also had to define the scale functions against which the axis will be drawn.
Now lets breathe some life into our conversion functions by defining their domain and range.

Setup the domain and range for the conversion functions

// Define the output range of your conversion functions
convert.y.range([maxHeight, 0]);
convert.x.rangeRoundBands([0, maxWidth]);

convert.x.domain(employees.map(function (d) {
        return d.dept;
    })
);
convert.y.domain([0, maxAge]);
Defining the domain for y was pretty easy however since we are using ordinal values on the x axis that is a property of each data point, we needed to use a function that returns the dept attribute from each data point.

Setup the drawing area

The next two steps are pretty simple, we prepare our SVG and the chart within it.
// Setup the markup for your SVG
var svg = d3.select('.chart')
    .attr({
        width: svgWidth,
        height: svgHeight
    });

// The group node that will contain all the other nodes
// that render your chart
var chart = svg.append('g')
    .attr({
        transform: function (d, i) {
          return 'translate(' + padding.left + ',' + padding.top + ')';
        }
    });
I always prefer to use the 'map' notation when setting properties on a selection because it makes things more configurable for future editing.

Drawing the axis

We will first quickly draw our axis
chart.append('g') // Container for the axis
    .attr({
        class: 'x axis',
        transform: 'translate(0,' + maxHeight + ')'
    })
    .call(axis.x); // Insert an axis inside this node

chart.append('g') // Container for the axis
    .attr({
        class: 'y axis',
        height: maxHeight
    })
    .call(axis.y); // Insert an axis inside this node
Notice above how we invoke the call() function on our group and pass it the axis.x and axis.y functions to actually draw the axis.

Drawing the bars

The next part involves joining data and adding as many bars as there are data points. You can checkout the post on data joining, domain and range if you have any doubts about how that is achieved.
var bars = chart
    .selectAll('g.bar-group')
    .data(employees)
    .enter()
    .append('g') // Container for the each bar
    .attr({
      transform: function (d, i) {
        return 'translate(' + convert.x(d.dept) + ', 0)';
      },
      class: 'bar-group'
    });
Note that we just created as many group nodes as there are data points and bound those data points to those group nodes. To draw the actual bars, we will need to render a rectangle in each of the group nodes. However, unlike last time, we will render our rectangles with height of 0. And then by applying a transition, we will make it grow to its actual height.
bars.append('rect')
    .attr({
        y: maxHeight,
        height: 0,
        width: function(d) {return convert.x.rangeBand(d) - 1;},
        class: 'bar'
    })
    .transition()
    .duration(1500)
    .attr({
    y: function (d, i) {
        return convert.y(d.age);
    },
    height: function (d, i) {
        return maxHeight - convert.y(d.age);
    }
});
Ok, we did a few things in the above code. We first created our bars with a default height of 0 and a default width of the scaled width - 1. We subtract 1 so that there is some spacing between our bars. The next critical part is the transition() function. By invoking the transition function on our bars, we make them transition ready and the attribute that we define after that become the 'end' of the transition state and the current state is inferred as the 'beginning' of the transition state. It is here where we compute the height and the node y coordinate of the node. As discussed in our previous article on creating a simple bar graph, since the canvas is inverted, i.e. y=0 at the top, we need to specify both the y coordianate (whose range is a reverse mapping of the max to min) and a height to draw our bars.



Also Read:

  1. A visual explanation of the Enter, Update and Exit Selections in D3js
  2. d3 Fundamentals : Understanding domain, range and scales in D3js
  3. Render a d3js Tree as a React Component
  4. Render a d3 tree with a minimum distance between the tree nodes