How to make a Bar Chart in D3
Basic Bar Chart
Step 1: Prepare your data as a CSV file
Make sure your data are real numbers, and have no commas or symbols in them (decimals are OK).
Note, that the headers of your spreadsheet will become properties of your data when you bring it in.
To access each piece of data, the following notation would be used:
data[0]["Age Group"] //will return "Less than 35"
data[1]["Age Group"] //will return "35-44"
data[2]["Age Group"] //will return "45-54"
data[3]["Age Group"] //will return "55-64"
//...and so on
data[0]["Median Price"] //will return "7100"
data[1]["Median Price"] //will return "23000"
data[2]["Median Price"] //will return "44000"
data[3]["Median Price"] //will return "59200"
//...and so on
Save your spreadsheet as a .csv
file in a folder where you will put the html for your bar chart.
Step 2: Create your HTML file
Here is a basic boilerplate template with the D3 library included to start you off. Save this with the .html
file extension in the same folder as your .csv
file.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Basic Bar Chart</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
/* custom css styles will go here */
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//your d3 code will go here
</script>
</body>
</html>
Step 3: Setup your size variables and your SVG
Next, setup some basic size variables for our SVG container.
var margin = {top:0, right:0, bottom:20, left:50},
width = 800,
height = 500;
var svg = d3.select("body")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", "0 0 " + width + " " + height);
The viewBox
attribute specifies the internal coordinate system for your chart using an x y width height
format. This way you can set the width and height to 100%, and your chart will become responsive.
Step 4: Set your scales
Next, set the scales for the X and Y axis. For a column bar chart chart, your X scale will be determined the number of elements you have, and your Y scale will be determined the maximum value of your data.
We also subtract the margins, since these will add space for our axes and any labels we decide to add.
var yScale = d3.scale.linear()
.range([height - margin.top - margin.bottom, 0]);
var xScale = d3.scale.ordinal()
.rangeRoundBands([0, width - margin.right - margin.left], .1);
As a reminder, .rangeRoundBands
is a special bar-chart function to make the bars clean and spaced properly.
Step 5: Add functions for axes
Axes are pretty straight forward. Use the scale functions to determine the tick values of the axes.
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
Step 6: Running a Python SimpleHTTPServer
For Macs, you can launch the Terminal program, navigate to the folder where your project is using terminal commands cd
(tutorial here), then run the following command:
python -m SimpleHTTPServer 8000
Step 7: Loading in the CSV file using D3
Next, we load in the CSV file.
d3.csv("stockowners.csv", function(error, data){
//code in next sections will go here.
})
Step 8: Coercing the strings as numbers
D3 pulls in data as strings, even when they’re numbers. You can convert your data so that the strings will be recast as numbers.
//map function goes through every element, then returns a number for median price
data = data.map(function(d){
d["Median Price"] = +d["Median Price"];
return d;
});
Step 9: Setting the domains for the X and Y scales
Since we are loading in our data via CSV, we can’t set the domains until the data is loaded in. After we load in the data and coerce as numbers, we can set our domains. Our X scale simply needs unique strings for each element in the data (in our case, Age Group), and our Y scale need a domain from zero to the maximum value.
In each situation, we map the data from the array of objects.
//yscale's domain is from zero to the maximum "Median Price" in your data
yScale.domain([0, d3.max(data, function(d){ return d["Median Price"]; })]);
//xscale is unique values in your data (Age Group, since they are all different)
xScale.domain(data.map(function(d){ return d["Age Group"]; }));
Step 10: Creating the Bar Chart
Next, we run through our data and build the bar chart. We will add the bar chart to a grouping “g” element so that we can account for margins. It’s important to add a class name to each bar so that we can style it with CSS.
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d){ return xScale(d["Age Group"]); })
.attr("y", function(d){ return yScale(d["Median Price"]); })
.attr("height", function(d){ return height - margin.top - margin.bottom - yScale(d["Median Price"]); })
.attr("width", function(d){ return xScale.rangeBand(); });
Step 11: Adding the axes
To add the axes, append a “g” group element, and use the transform attribute to position it in the correct location accounting for the margins. Then call either the xAxis
or yAxis
functions you created earlier.
We also add two class names so we can style each axis individually.
//adding y axis to the left of the chart
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
//adding x axis to the bottom of chart
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(" + margin.left + "," + (height - margin.bottom) + ")")
.call(xAxis);
In addition, when working with currency, you may want to add the dollar symbol to the front of each number. You can do this by going back to where you created the yAxis
variable, and setting a tickFormat
.
//go back to where you created yAxis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.tickFormat(d3.format("$,"));//...then add this line, a tickFormat for currency
Step 12: Add CSS for styling
If you looked at your chart at this point, it might appear somewhat ugly. To improve the appearance, add some CSS styling based on the class names you’ve assigned.
<style>
.axis text{
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
text-anchor: end;
}
.axis path{
fill:none;
stroke:black;
stroke-width: 0.5px;
shape-rendering: crispEdges;
}
.bar{
stroke: none;
fill: steelblue;
}
.textlabel{
font-family: Helvetica, Arial, sans-serif;
font-size:14px;
text-anchor: middle;
}
</style>
Step 13 (Optional): Make adjustments
Once your data is in and you see your chart, you may wish to make some adjustments so that the graph can communicate effectively. You can make adjustments to your domain on your yScale, for example, so that the maximum value isn’t all the way at the top.
//increasing domain to static 140000, so it's more than maximum value in your data
yScale.domain([0, 140000]);
It’s also possible to add labels at the top of each bar by running through the data again. (Another more efficient way would have been to create a grouping for each bar then append text to that).
Here we use the same X and Y scales to place each text label above the bars.
//add text labels to the top of each bar
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.selectAll(".textlabel")
.data(data)
.enter()
.append("text")
.attr("class", "textlabel")
.attr("x", function(d){ return xScale(d["Age Group"]) + (xScale.rangeBand()/2); })
.attr("y", function(d){ return yScale(d["Median Price"]) - 3; })
.text(function(d){ return d3.format("$,")(d["Median Price"]); });
//more info about d3 format method:
// http://koaning.s3-website-us-west-2.amazonaws.com/html/d3format.html
You can also adjust the padding to account for a label at the top.
//add additional top margin to make room for label
var margin = {top:50, right:0, bottom:20, left:50}
Then add the title at the top. We use width divided by 2, to center (make sure the text-anchor is set to middle for the CSS styling to properly center the text).
//adding a label at the top of the chart
svg.append("g")
.attr("transform", "translate(" + (width/2) + ", 15)")
.append("text")
.text("Stock Wealth by Age")
.style({"text-anchor":"middle", "font-family":"Arial", "font-weight":"800"});