Show Buttons
Share On Facebook
Share On Twitter
Share On Google Plus
Share On Linkdin
Share On Reddit
Share On Stumbleupon
Contact us
Hide Buttons

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 sim­ple tran­si­tions. In this post, we will build upon our pre­vi­ous arti­cle by giv­ing our bar chart an x and y axis and give each bar a sim­ple ‘ris­ing from the bot­tom’ tran­si­tion effect.

In this post, we will also refac­tor our pre­vi­ous exam­ple and make it a much more con­fig­urable chart.

The com­plete jsfid­dle for this tuto­r­ial can be found here.

Before I begin, lets out­line the var­i­ous steps involved.

d3_animated_bar_steps


We start by cre­at­ing our data and pre­set­ting 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 ren­der 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 draw­ing area for the bars themselves.

function animateBarsUp() {
    var maxWidth = svgWidth - padding.left - padding.right;
    var maxHeight = svgHeight - padding.top - padding.bottom;
}

The maxWidth and max­Height defines the area within which our bars will be drawn.


Defin­ing our con­ver­sion functions

With this knowl­edge, we can now define our con­ver­sion func­tions that can be used later to con­vert our raw data into pixel coor­di­nates for the svg.

// Define your conversion functions
var convert = {    
    x: d3.scale.ordinal(),
    y: d3.scale.linear()
};

Notice that we use an ordi­nal scale func­tion in for x. Thats because we want to ren­der the depart­ment titles on the x axis which is ordinal(i.e. fol­lows 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 vari­ables at first glance, they are actu­ally func­tions that can take an argu­ment and return a trans­formed value depened­ing upon its domain and range settings.


Set­ting up the axis functions

Now lets define the func­tions that will be used to ren­der 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 ori­ent para­me­ter is use to describe the axis. The value of the para­me­ter tells you on which side of the axis are the val­ues of the labels ren­dered. For the x axis, we want to ren­der the labels below the axis and for the y axis, we want to ren­der it on on the left of the axis.
  2. We also had to define the scale func­tions against which the axis will be drawn.

Now lets breathe some life into our con­ver­sion func­tions by defin­ing their domain and range.


Setup the domain and range for the con­ver­sion 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]);

Defin­ing the domain for y was pretty easy how­ever since we are using ordi­nal val­ues on the x axis that is a prop­erty of each data point, we needed to use a func­tion that returns the dept attribute from each data point.


Setup the draw­ing area

The next two steps are pretty sim­ple, we pre­pare 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 pre­fer to use the ‘map’ nota­tion when set­ting prop­er­ties on a selec­tion because it makes things more con­fig­urable for future editing.


Draw­ing 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() func­tion on our group and pass it the axis.x and axis.y func­tions to actu­ally draw the axis.


Draw­ing the bars

The next part involves join­ing data and adding as many bars as there are data points. You can check­out the post on data join­ing, 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 cre­ated 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 ren­der a rec­tan­gle in each of the group nodes.

How­ever, unlike last time, we will ren­der our rec­tan­gles with height of 0. And then by apply­ing a tran­si­tion, 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 cre­ated our bars with a default height of 0 and a default width of the scaled width — 1. We sub­tract 1 so that there is some spac­ing between our bars.

The next crit­i­cal part is the tran­si­tion() func­tion. By invok­ing the tran­si­tion func­tion on our bars, we make them tran­si­tion ready and the attribute that we define after that become the ‘end’ of the tran­si­tion state and the cur­rent state is inferred as the ‘begin­ning’ of the tran­si­tion state. It is here where we com­pute the height and the node y coor­di­nate of the node.

As dis­cussed in our pre­vi­ous arti­cle on cre­at­ing a sim­ple bar graph, since the can­vas is inverted, i.e. y=0 at the top, we need to spec­ify both the y coor­dianate (whose range is a reverse map­ping of the max to min) and a height to draw our bars.


You may also like...