----------------------------------------------------------
d3.js Bar Charts
A bar chart is a visual representation using either horizontal or vertical bars to show comparisons between discrete categories. There are a number of variations of bar charts including stacked, grouped, horizontal and vertical.
There is a wealth of examples of bar charts on the web, but I would recommend a visit to the D3.js gallery maintained by Christophe Viau as a starting point to get some ideas.
We will work through a simple vertical bar chart that uses a value on the y axis and date values on the x axis.
The end result will look like this;
Bar chart |
The data
The data for this example will be sourced from an external csv file named bar-data.csv
. It consists of a column of dates in year-month format and it’s contents are as follows;
date,value
2013-01,53
2013-02,165
2013-03,269
2013-04,344
2013-05,376
2013-06,410
2013-07,421
2013-08,405
2013-09,376
2013-10,359
2013-11,392
2013-12,433
2014-01,455
2014-02,478
bar-data.csv
. It consists of a column of dates in year-month format and it’s contents are as follows;date,value
2013-01,53
2013-02,165
2013-03,269
2013-04,344
2013-05,376
2013-06,410
2013-07,421
2013-08,405
2013-09,376
2013-10,359
2013-11,392
2013-12,433
2014-01,455
2014-02,478
The code
The full code listing for the example we are going to work through is as follows;
<!DOCTYPE html>
<meta
charset=
"utf-8"
>
<head>
<style>
.axis
{
font
:
10px
sans-serif
;
}
.axis
path
,
.axis
line
{
fill
:
none
;
stroke
:
#000
;
shape
-
rendering
:
crispEdges
;
}
</style>
</head>
<body>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script>
var
margin
=
{
top
:
20
,
right
:
20
,
bottom
:
70
,
left
:
40
},
width
=
600
-
margin
.
left
-
margin
.
right
,
height
=
300
-
margin
.
top
-
margin
.
bottom
;
// Parse the date / time
var
parseDate
=
d3
.
time
.
format
(
"%Y-%m"
).
parse
;
var
x
=
d3
.
scale
.
ordinal
().
rangeRoundBands
([
0
,
width
],
.
05
);
var
y
=
d3
.
scale
.
linear
().
range
([
height
,
0
]);
var
xAxis
=
d3
.
svg
.
axis
()
.
scale
(
x
)
.
orient
(
"bottom"
)
.
tickFormat
(
d3
.
time
.
format
(
"%Y-%m"
));
var
yAxis
=
d3
.
svg
.
axis
()
.
scale
(
y
)
.
orient
(
"left"
)
.
ticks
(
10
);
var
svg
=
d3
.
select
(
"body"
).
append
(
"svg"
)
.
attr
(
"width"
,
width
+
margin
.
left
+
margin
.
right
)
.
attr
(
"height"
,
height
+
margin
.
top
+
margin
.
bottom
)
.
append
(
"g"
)
.
attr
(
"transform"
,
"translate("
+
margin
.
left
+
","
+
margin
.
top
+
")"
);
d3
.
csv
(
"bar-data.csv"
,
function
(
error
,
data
)
{
data
.
forEach
(
function
(
d
)
{
d
.
date
=
parseDate
(
d
.
date
);
d
.
value
=
+
d
.
value
;
});
x
.
domain
(
data
.
map
(
function
(
d
)
{
return
d
.
date
;
}));
y
.
domain
([
0
,
d3
.
max
(
data
,
function
(
d
)
{
return
d
.
value
;
})]);
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"x axis"
)
.
attr
(
"transform"
,
"translate(0,"
+
height
+
")"
)
.
call
(
xAxis
)
.
selectAll
(
"text"
)
.
style
(
"text-anchor"
,
"end"
)
.
attr
(
"dx"
,
"-.8em"
)
.
attr
(
"dy"
,
"-.55em"
)
.
attr
(
"transform"
,
"rotate(-90)"
);
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"y axis"
)
.
call
(
yAxis
)
.
append
(
"text"
)
.
attr
(
"transform"
,
"rotate(-90)"
)
.
attr
(
"y"
,
6
)
.
attr
(
"dy"
,
".71em"
)
.
style
(
"text-anchor"
,
"end"
)
.
text
(
"Value ($)"
);
svg
.
selectAll
(
"bar"
)
.
data
(
data
)
.
enter
().
append
(
"rect"
)
.
style
(
"fill"
,
"steelblue"
)
.
attr
(
"x"
,
function
(
d
)
{
return
x
(
d
.
date
);
})
.
attr
(
"width"
,
x
.
rangeBand
())
.
attr
(
"y"
,
function
(
d
)
{
return
y
(
d
.
value
);
})
.
attr
(
"height"
,
function
(
d
)
{
return
height
-
y
(
d
.
value
);
});
});
</script>
</body>
<!DOCTYPE html>
<meta
charset=
"utf-8"
>
<head>
<style>
.axis
{
font
:
10px
sans-serif
;
}
.axis
path
,
.axis
line
{
fill
:
none
;
stroke
:
#000
;
shape
-
rendering
:
crispEdges
;
}
</style>
</head>
<body>
<script
src=
"http://d3js.org/d3.v3.min.js"
></script>
<script>
var
margin
=
{
top
:
20
,
right
:
20
,
bottom
:
70
,
left
:
40
},
width
=
600
-
margin
.
left
-
margin
.
right
,
height
=
300
-
margin
.
top
-
margin
.
bottom
;
// Parse the date / time
var
parseDate
=
d3
.
time
.
format
(
"%Y-%m"
).
parse
;
var
x
=
d3
.
scale
.
ordinal
().
rangeRoundBands
([
0
,
width
],
.
05
);
var
y
=
d3
.
scale
.
linear
().
range
([
height
,
0
]);
var
xAxis
=
d3
.
svg
.
axis
()
.
scale
(
x
)
.
orient
(
"bottom"
)
.
tickFormat
(
d3
.
time
.
format
(
"%Y-%m"
));
var
yAxis
=
d3
.
svg
.
axis
()
.
scale
(
y
)
.
orient
(
"left"
)
.
ticks
(
10
);
var
svg
=
d3
.
select
(
"body"
).
append
(
"svg"
)
.
attr
(
"width"
,
width
+
margin
.
left
+
margin
.
right
)
.
attr
(
"height"
,
height
+
margin
.
top
+
margin
.
bottom
)
.
append
(
"g"
)
.
attr
(
"transform"
,
"translate("
+
margin
.
left
+
","
+
margin
.
top
+
")"
);
d3
.
csv
(
"bar-data.csv"
,
function
(
error
,
data
)
{
data
.
forEach
(
function
(
d
)
{
d
.
date
=
parseDate
(
d
.
date
);
d
.
value
=
+
d
.
value
;
});
x
.
domain
(
data
.
map
(
function
(
d
)
{
return
d
.
date
;
}));
y
.
domain
([
0
,
d3
.
max
(
data
,
function
(
d
)
{
return
d
.
value
;
})]);
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"x axis"
)
.
attr
(
"transform"
,
"translate(0,"
+
height
+
")"
)
.
call
(
xAxis
)
.
selectAll
(
"text"
)
.
style
(
"text-anchor"
,
"end"
)
.
attr
(
"dx"
,
"-.8em"
)
.
attr
(
"dy"
,
"-.55em"
)
.
attr
(
"transform"
,
"rotate(-90)"
);
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"y axis"
)
.
call
(
yAxis
)
.
append
(
"text"
)
.
attr
(
"transform"
,
"rotate(-90)"
)
.
attr
(
"y"
,
6
)
.
attr
(
"dy"
,
".71em"
)
.
style
(
"text-anchor"
,
"end"
)
.
text
(
"Value ($)"
);
svg
.
selectAll
(
"bar"
)
.
data
(
data
)
.
enter
().
append
(
"rect"
)
.
style
(
"fill"
,
"steelblue"
)
.
attr
(
"x"
,
function
(
d
)
{
return
x
(
d
.
date
);
})
.
attr
(
"width"
,
x
.
rangeBand
())
.
attr
(
"y"
,
function
(
d
)
{
return
y
(
d
.
value
);
})
.
attr
(
"height"
,
function
(
d
)
{
return
height
-
y
(
d
.
value
);
});
});
</script>
</body>
The full code for this example can be found on github, in the appendices of this book or in the code samples bundled with this book (bar.html and bar-data.csv). A working example can be found on bl.ocks.org.
|
The bar chart explained
In the course of describing the operation of the file I will gloss over the aspects of the structure of an HTML file which have already been described at the start of the book. Likewise, aspects of the JavaScript functions that have already been covered will only be briefly explained.
The start of the file deals with setting up the document’s head and body, loading the d3.js script and setting up the css in the <style>
section.
The css section sets styling for the axes. It sizes the font to be used and make sure the lines are formatted appropriately.
.axis
{
font
:
10px
sans-serif
;
}
.axis
path
,
.axis
line
{
fill
:
none
;
stroke
:
#000
;
shape
-
rendering
:
crispEdges
;
}
Then our JavaScript section starts and the first thing that happens is that we set the size of the area that we’re going to use for the chart and the margins;
var
margin
=
{
top
:
20
,
right
:
20
,
bottom
:
70
,
left
:
40
},
width
=
600
-
margin
.
left
-
margin
.
right
,
height
=
300
-
margin
.
top
-
margin
.
bottom
;
The next section of our code includes some of the functions that will be called from the main body of the code.
We have a familiar parseDate
function with a slight twist. Since our source data for the date is made up of only the year and month, these are the only two portions of the date that need to be recognised;
var
parseDate
=
d3
.
time
.
format
(
"%Y-%m"
).
parse
;
The next section declares the function to determine positioning in the x domain.
var
x
=
d3
.
scale
.
ordinal
().
rangeRoundBands
([
0
,
width
],
.
05
);
The ordinal scale is used to describe a range of discrete values. In our case they are a set of monthly values. The rangeRoundBands
operator provides the magic that arranges our bars in a graceful way across the x axis. In our example we use it to set the range that our bars will cover (in this case from 0 to the width
of the graph) and the amount of padding between the bars (in this case we have selected .05
which equates to approximately (depending on the number of pixels available) 5% of the bar width.
The function to set the scaling in the y domain is the same as most of our other graph examples;
var
y
=
d3
.
scale
.
linear
().
range
([
height
,
0
]);
The declarations for our two axes are relatively simple, with the only exception being to force the format of the labels for the x axis into a ‘year-month’ format.
var
xAxis
=
d3
.
svg
.
axis
()
.
scale
(
x
)
.
orient
(
"bottom"
)
.
tickFormat
(
d3
.
time
.
format
(
"%Y-%m"
));
var
yAxis
=
d3
.
svg
.
axis
()
.
scale
(
y
)
.
orient
(
"left"
)
.
ticks
(
10
);
The next block of code selects the body
on the web page and appends an svg object to it of the size that we have set up with our width
, height
and margin
’s.
var
svg
=
d3
.
select
(
"body"
).
append
(
"svg"
)
.
attr
(
"width"
,
width
+
margin
.
left
+
margin
.
right
)
.
attr
(
"height"
,
height
+
margin
.
top
+
margin
.
bottom
)
.
append
(
"g"
)
.
attr
(
"transform"
,
"translate("
+
margin
.
left
+
","
+
margin
.
top
+
")"
);
It also adds a g
element that provides a reference point for adding our axes.
Then we begin the main body of our JavaScript. We load our csv file and then loop through it making sure that the dates and numerical values are recognised correctly;
d3
.
csv
(
"bar-data.csv"
,
function
(
error
,
data
)
{
data
.
forEach
(
function
(
d
)
{
d
.
date
=
parseDate
(
d
.
date
);
d
.
value
=
+
d
.
value
;
});
We then then work through our x and y data and ensure that it is scaled to the domains we are working in;
x
.
domain
(
data
.
map
(
function
(
d
)
{
return
d
.
date
;
}));
y
.
domain
([
0
,
d3
.
max
(
data
,
function
(
d
)
{
return
d
.
value
;
})]);
Following that we append our x axis;
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"x axis"
)
.
attr
(
"transform"
,
"translate(0,"
+
height
+
")"
)
.
call
(
xAxis
)
.
selectAll
(
"text"
)
.
style
(
"text-anchor"
,
"end"
)
.
attr
(
"dx"
,
"-.8em"
)
.
attr
(
"dy"
,
"-.55em"
)
.
attr
(
"transform"
,
"rotate(-90)"
);
This is placed in the correct position .attr("transform", "translate(0," + height + ")")
and the text is positioned (using dx
and dy
) and rotated (.attr("transform", "rotate(-90)" );
) so that it is aligned vertically.
Then we append our y axis in a similar way and append a label (.text("Value ($)");
);
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"y axis"
)
.
call
(
yAxis
)
.
append
(
"text"
)
.
attr
(
"transform"
,
"rotate(-90)"
)
.
attr
(
"y"
,
6
)
.
attr
(
"dy"
,
".71em"
)
.
style
(
"text-anchor"
,
"end"
)
.
text
(
"Value ($)"
);
Lastly we add the bars to our chart;
svg
.
selectAll
(
"bar"
)
.
data
(
data
)
.
enter
().
append
(
"rect"
)
.
style
(
"fill"
,
"steelblue"
)
.
attr
(
"x"
,
function
(
d
)
{
return
x
(
d
.
date
);
})
.
attr
(
"width"
,
x
.
rangeBand
())
.
attr
(
"y"
,
function
(
d
)
{
return
y
(
d
.
value
);
})
.
attr
(
"height"
,
function
(
d
)
{
return
height
-
y
(
d
.
value
);
});
This block of code creates the bars (selectAll("bar")
) and associates each of them with a data set (.data(data)
).
We then append a rectangle (.append("rect")
) with values for x/y position and height/width as configured in our earlier code.
The end result is our pretty looking bar chart;
<style>
section. .axis
{
font
:
10px
sans-serif
;
}
.axis
path
,
.axis
line
{
fill
:
none
;
stroke
:
#000
;
shape
-
rendering
:
crispEdges
;
}
var
margin
=
{
top
:
20
,
right
:
20
,
bottom
:
70
,
left
:
40
},
width
=
600
-
margin
.
left
-
margin
.
right
,
height
=
300
-
margin
.
top
-
margin
.
bottom
;
parseDate
function with a slight twist. Since our source data for the date is made up of only the year and month, these are the only two portions of the date that need to be recognised;var
parseDate
=
d3
.
time
.
format
(
"%Y-%m"
).
parse
;
var
x
=
d3
.
scale
.
ordinal
().
rangeRoundBands
([
0
,
width
],
.
05
);
rangeRoundBands
operator provides the magic that arranges our bars in a graceful way across the x axis. In our example we use it to set the range that our bars will cover (in this case from 0 to the width
of the graph) and the amount of padding between the bars (in this case we have selected .05
which equates to approximately (depending on the number of pixels available) 5% of the bar width.var
y
=
d3
.
scale
.
linear
().
range
([
height
,
0
]);
var
xAxis
=
d3
.
svg
.
axis
()
.
scale
(
x
)
.
orient
(
"bottom"
)
.
tickFormat
(
d3
.
time
.
format
(
"%Y-%m"
));
var
yAxis
=
d3
.
svg
.
axis
()
.
scale
(
y
)
.
orient
(
"left"
)
.
ticks
(
10
);
body
on the web page and appends an svg object to it of the size that we have set up with our width
, height
and margin
’s.var
svg
=
d3
.
select
(
"body"
).
append
(
"svg"
)
.
attr
(
"width"
,
width
+
margin
.
left
+
margin
.
right
)
.
attr
(
"height"
,
height
+
margin
.
top
+
margin
.
bottom
)
.
append
(
"g"
)
.
attr
(
"transform"
,
"translate("
+
margin
.
left
+
","
+
margin
.
top
+
")"
);
g
element that provides a reference point for adding our axes.d3
.
csv
(
"bar-data.csv"
,
function
(
error
,
data
)
{
data
.
forEach
(
function
(
d
)
{
d
.
date
=
parseDate
(
d
.
date
);
d
.
value
=
+
d
.
value
;
});
x
.
domain
(
data
.
map
(
function
(
d
)
{
return
d
.
date
;
}));
y
.
domain
([
0
,
d3
.
max
(
data
,
function
(
d
)
{
return
d
.
value
;
})]);
svg
.
append
(
"g"
)
.
attr
(
"class"
,
"x axis"
)
.
attr
(
"transform"
,
"translate(0,"
+
height
+
")"
)
.
call
(
xAxis
)
.
selectAll
(
"text"
)
.
style
(
"text-anchor"
,
"end"
)
.
attr
(
"dx"
,
"-.8em"
)
.
attr
(
"dy"
,
"-.55em"
)
.
attr
(
"transform"
,
"rotate(-90)"
);
.attr("transform", "translate(0," + height + ")")
and the text is positioned (using dx
and dy
) and rotated (.attr("transform", "rotate(-90)" );
) so that it is aligned vertically..text("Value ($)");
); svg
.
append
(
"g"
)
.
attr
(
"class"
,
"y axis"
)
.
call
(
yAxis
)
.
append
(
"text"
)
.
attr
(
"transform"
,
"rotate(-90)"
)
.
attr
(
"y"
,
6
)
.
attr
(
"dy"
,
".71em"
)
.
style
(
"text-anchor"
,
"end"
)
.
text
(
"Value ($)"
);
svg
.
selectAll
(
"bar"
)
.
data
(
data
)
.
enter
().
append
(
"rect"
)
.
style
(
"fill"
,
"steelblue"
)
.
attr
(
"x"
,
function
(
d
)
{
return
x
(
d
.
date
);
})
.
attr
(
"width"
,
x
.
rangeBand
())
.
attr
(
"y"
,
function
(
d
)
{
return
y
(
d
.
value
);
})
.
attr
(
"height"
,
function
(
d
)
{
return
height
-
y
(
d
.
value
);
});
selectAll("bar")
) and associates each of them with a data set (.data(data)
)..append("rect")
) with values for x/y position and height/width as configured in our earlier code.Bar chart |
The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).
Hi, I made myself crazy trying to work through this example. I couldn't get it to work. I was using our shared copy of D3, version 3.0.4. It would break in the second to last section just before the bars are added. I pointed to http://d3js.org/d3.v3.js, version 3.4.4 and it worked. I point this out to perhaps save someone hours beating your head against a wall.
ReplyDeleteThanks for sharing. Much appreciated and well done.
DeleteWhen the first bar is tall enough it runs over the rotated "Value ($)" label. How can this label be moved to the left side of the y-axis to prevent this issue? Thanks.
ReplyDeleteYou can change the position of the label by adding in a .attr("x", "-10") line or something similar to the block that prints that label. Have a play with the values in the code to see what you can make it do. The x axis label block has both x and y movement you might also play with to see how it changes. Have fun.
DeleteHi there, how would you get two of these charts using different csv files as data to display on one html page?
ReplyDeleteHi there, is there any way that you could have two of these charts to display on one html page as I am having difficulty in doing so.
ReplyDeleteI reccomend using bootstrap, but there are a couple of different ways. Read here for an outdated guide to using bootstrap with d3.js https://leanpub.com/D3-Tips-and-Tricks/read#leanpub-auto-using-bootstrap-with-d3js using with version 3 is pretty much the same, but they just call the spans something different
DeleteThanks for this tutorial.
ReplyDeleteI don't have data for each day, is it possible to plot dates without data to have a linear scale on the time?
That's a good question. I found muself asking a similar question a while back. check out the resolution here; https://leanpub.com/D3-Tips-and-Tricks/read#leanpub-auto-padding-for-zero-values
Deleteis it possible to have a grouped bar chart with different colors for each part of a group and a line showing a target value?
ReplyDeletethe data would look something like this:
Date, value1, value2, value3
1/1/2001, 55,33,91
1/8/2001, 45,44,92
1/15/2001, 35,55,33
Target, 80
The target could be 80,80,80 if it needs to be, I would like it to be a single line across the bars
Apologies for the really late reply. I think you can achieve what you're looking for here with a standard grouped bar chart (https://bl.ocks.org/mbostock/3887051) and by appending a line over the top. Sorry, I've never actually made a grouped bar chart with d3, so I don't have my own example to share.
DeleteHow can I add images over the bars?
ReplyDeleteThat's a good question. Firstly you will want to have the appropriate image associated with the appropriate data. (in the csv file for instance). Then while appending the specific bars you will want to associate the images and append as necessary. The closest example I have is one I've done with a tree diagram (http://bl.ocks.org/d3noob/9662ab6d5ac823c0e444). You should be able to make it happen with some experimentation and study of the code for both examples. Remember, take it easy when experimenting. Start with a really simple example (say one image) and build from there.
DeleteI am using SPA, angularjs(ui-router), d3.js, MVS/ASPNET. The chart is displayed on the view using angular controller (ng-controller). When the view is changed, remainings of the chart stay in the view (SVG -black bars ). How could the chart be erased/destroyed when the view is changed ?
ReplyDeleteWow! Sorry for the late reply, and sorry that I can't really help answer your question. Half the acronyms you used there I'm not familiar with. It's really out of my experience I'm afraid.
Deletei am little bit confuse about where i have to put the CSV file or CSV data for getting the data as charts
ReplyDeleteThe simplest answer for this example is to put the bar.html file in the same directory as the bar-data.csv file. In practice they can be in different directories and you can adjust the `d3.csv("bar-data.csv", function(error, data) {` line to pick up the data from somewhere different. For example if you had your bar-data.csv file in a directory called `data` in the same location as your bar.html file you could access it by changing the line to;
Deleted3.csv("data/bar-data.csv", function(error, data) {
I hope that made sense.