Creating Simple Line and Bar Charts Using D3.js
In a previous article, we learned how to implement bubble charts using D3.js, a JavaScript library for creating data-driven documents. D3.js helps to visualize data using HTML, SVG, and CSS. In this article, we’ll see how to implement line and bar charts using D3.js. Before moving on, you should download D3.js and be familiar with the material in my previous article.
Creating Line Charts
First, we’ll need some data to plot. We’re going to use the following data.
var lineData = [{
x: 1,
y: 5
}, {
x: 20,
y: 20
}, {
x: 40,
y: 10
}, {
x: 60,
y: 40
}, {
x: 80,
y: 5
}, {
x: 100,
y: 60
}];
We’re also going to need a <svg>
element to plot our graph on.
<svg id="visualisation" width="1000" height="500"></svg>
Next, we need to create our x and y axes, and for that we’ll need to declare a domain and range. The domain defines the minimum and maximum values displayed on the graph, while the range is the amount of the SVG we’ll be covering. Both of the axes need to scale as per the data in lineData
, meaning that we must set the domain and range accordingly. The code for drawing the axes is shown below.
var vis = d3.select('#visualisation'),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 50
},
xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(lineData, function(d) {
return d.x;
}), d3.max(lineData, function(d) {
return d.x;
})]),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(lineData, function(d) {
return d.y;
}), d3.max(lineData, function(d) {
return d.y;
})]),
xAxis = d3.svg.axis()
.scale(xRange)
.tickSize(5)
.tickSubdivide(true),
yAxis = d3.svg.axis()
.scale(yRange)
.tickSize(5)
.orient('left')
.tickSubdivide(true);
vis.append('svg:g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
.call(xAxis);
vis.append('svg:g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (MARGINS.left) + ',0)')
.call(yAxis);
In this code, we have defined the WIDTH
, HEIGHT
, and MARGINS
for our graph. The xRange
and yRange
variables represent the domains for the respective axes. We set the range for our axes as per the left and right margins.
Next, since the domain is the data we will show on the graph, we need to get the min and max values from lineData
. This is done using the d3.max()
and d3.min()
methods.
Next, we created our axes as per the xRange
and yRange
variables. For both axes, we have defined the scale as xRange
and yRange
for the X and Y axes, respectively. And then we simply appended both the axis to the SVG and applied the transform. Now, if we have a look at the Y axis it needs to be oriented to the left. Hence, we applied a left orientation to the yAxis
. We have transformed both the axes, keeping the defined margins in view so that the axes don’t touch the SVG margins.
Here is a demo of the above code showing both axes.
Next, we need to apply the xRange
and the yRange
to the coordinates to transform them into the plotting space and to draw a line across the plotting space. We’ll be using d3.svg.line()
to draw our line graph. For this, we need to create a line generator function which returns the x and y coordinates from our data to plot the line. This is how we define the line generator function:
var lineFunc = d3.svg.line()
.x(function(d) {
return xRange(d.x);
})
.y(function(d) {
return yRange(d.y);
})
.interpolate('linear');
The interpolate('linear')
call tells D3 to draw straight lines.
Next, we need to set the d
attribute of the SVG path to the coordinates returned from the line function. This is accomplished using the following code.
vis.append('svg:path')
.attr('d', lineFunc(lineData))
.attr('stroke', 'blue')
.attr('stroke-width', 2)
.attr('fill', 'none');
We have set the line color using stroke
. The line’s width is defined using stroke-width
. We have set fill
to none
, as not to fill the graph boundaries. Here is a demo of the line graph with linear
interpolation in action, and here is the same graph demo with basis
interpolation.
Creating Bar Charts
Next, we’ll look at creating bar charts. Since, we already created our axes, we won’t need to reinvent the wheel. However, we will modifiy the existing code a bit. First, the sample data and code for creating our chart’s axes:
function InitChart() {
var barData = [{
'x': 1,
'y': 5
}, {
'x': 20,
'y': 20
}, {
'x': 40,
'y': 10
}, {
'x': 60,
'y': 40
}, {
'x': 80,
'y': 5
}, {
'x': 100,
'y': 60
}];
var vis = d3.select('#visualisation'),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 50
},
xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(barData, function(d) {
return d.x;
}),
d3.max(barData, function (d) {
return d.x;
})
]),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(barData, function(d) {
return d.y;
}),
d3.max(barData, function (d) {
return d.y;
})
]),
xAxis = d3.svg.axis()
.scale(xRange)
.tickSize(5)
.tickSubdivide(true),
yAxis = d3.svg.axis()
.scale(yRange)
.tickSize(5)
.orient("left")
.tickSubdivide(true);
vis.append('svg:g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
.call(xAxis);
vis.append('svg:g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (MARGINS.left) + ',0)')
.call(yAxis);
}
InitChart();
Here is a demo of the previous code. If you have a look at the Y axis, the scale starts at five. This minimum comes from our sample data, where 5 is the min Y value. Therefore, we need to scale the Y axis from 0. For that, we need to modify the domain of the yRange
in the InitChart()
function as shown below:
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0,
d3.max(barData, function(d) {
return d.y;
})]);
In the case of bar charts, we’ll be using ordinal
scales instead of the linear
scales. Ordinal scales help to maintain a discrete domain. For a more detailed info refer to the official documentation on ordinal scales.
We’ll also be using rangeRoundBands
to divide the width across the chart bars. We’ll modify the xRange
using ordinal
scale and rangeRoundBands
as shown below. Notice that we have also set the spacing between the bars to 0.1.
xRange = d3.scale.ordinal().rangeRoundBands([MARGINS.left, WIDTH - MARGINS.right], 0.1).domain(barData.map(function(d) {
return d.x;
}));
Next, we need to create rectangular bars for the chart data. We’ll be binding our sample data to the rectangles, using the x and y coordinates to set the height
and width
of the rectangular bars. Here is how the code looks:
vis.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', function(d) { // sets the x position of the bar
return xRange(d.x);
})
.attr('y', function(d) { // sets the y position of the bar
return yRange(d.y);
})
.attr('width', xRange.rangeBand()) // sets the width of bar
.attr('height', function(d) { // sets the height of bar
return ((HEIGHT - MARGINS.bottom) - yRange(d.y));
})
.attr('fill', 'grey'); // fills the bar with grey color
Here is a demo of our bar chart in action.
Adding Events
In order to improve interactivity, we can also attach events to the bars. We can attach an event to highlight the bar on mouseover
. Here is how it can be accomplished:
vis.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', function(d) {
return xRange(d.x);
})
.attr('y', function(d) {
return yRange(d.y);
})
.attr('width', xRange.rangeBand())
.attr('height', function(d) {
return ((HEIGHT - MARGINS.bottom) - yRange(d.y));
})
.attr('fill', 'grey')
.on('mouseover', function(d) {
d3.select(this)
.attr('fill', 'blue');
});
In this code, the on('mouseover')
adds an event handler that is invoked on mouse over, which makes the hovered bars blue. Here is a demo that illustrates this effect.
You might notice that the bars don’t turn grey again on mouseout
. Let’s attach another event to revert it back to its previous color on mouse out. The updated code is shown below:
vis.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', function(d) {
return xRange(d.x);
})
.attr('y', function(d) {
return yRange(d.y);
})
.attr('width', xRange.rangeBand())
.attr('height', function(d) {
return ((HEIGHT - MARGINS.bottom) - yRange(d.y));
})
.attr('fill', 'grey')
.on('mouseover', function(d) {
d3.select(this)
.attr('fill', 'blue');
})
.on('mouseout', function(d) {
d3.select(this)
.attr('fill', 'grey');
});
And, here is a demo of the above code in action.
Conclusion
D3.js is an awesome JavaScript libray for data visualization. In this tutorial, we focused on creating fairly simple bar and line charts. If you’re interested in experimenting more, try adding additional visualization techniques from the D3 library to the charts in this article.