Skip to content

Instantly share code, notes, and snippets.

@sebg
Forked from mbostock/.block
Last active January 11, 2018 12:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sebg/a1995c7f37e0734314cfd3bf4b63b846 to your computer and use it in GitHub Desktop.
Save sebg/a1995c7f37e0734314cfd3bf4b63b846 to your computer and use it in GitHub Desktop.
Grouped Bar Chart
license: gpl-3.0

This grouped bar chart is constructed from a CSV file storing the populations of different states by age group. The chart employs conventional margins and a number of D3 features:

State Under 5 Years 5 to 13 Years 14 to 17 Years 18 to 24 Years 25 to 44 Years 45 to 64 Years 65 Years and Over
CA 2704659 4499890 2159981 3853788 10604510 8819342 4114496
TX 2027307 3277946 1420518 2454721 7017731 5656528 2472223
NY 1208495 2141490 1058031 1999120 5355235 5120254 2607672
FL 1140516 1938695 925060 1607297 4782119 4746856 3187797
IL 894368 1558919 725973 1311479 3596343 3239173 1575308
PA 737462 1345341 679201 1203944 3157759 3414001 1910571
<!DOCTYPE html>
<style>
.axis .domain {
display: none;
}
</style>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Commented version of
// https://bl.ocks.org/mbostock/3887051
// Variables
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
// SVG G to provide D3 Margin Convention
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// X0 (horizontal axis) represents the state categories
var x0 = d3.scaleBand()
.rangeRound([0, width])
.paddingInner(0.1);
// X1 (horizontal axis) represents the age group categories
var x1 = d3.scaleBand()
.padding(0.05);
// Y (vertical axis) represents the population in millions
var y = d3.scaleLinear()
.rangeRound([height, 0]);
// Z represents the color scheme to be used for the various age groups
var z = d3.scaleOrdinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
// Define variable to give us access to AJAX callback function data
var outsideData;
// CSV AJAX call to ingest data
// data file requested: data.csv
// function to process data: function(d, i, columns) {...}
// function to be used on call back: function(error, data) {...}
d3.csv("data.csv", function(d, i, columns) {
for (var i = 1, n = columns.length; i < n; ++i) d[columns[i]] = +d[columns[i]];
return d;
}, function(error, data) {
if (error) throw error;
// assign internal data variable to outsideData variable so we can see data
// once the AJAX callback function has been run.
outsideData = data;
// Get the keys by taking a slice off of the column headings
// (means remove the element 0, and return array from index 1 to end)
// Returns array containing:
// "Under 5 Years", "5 to 13 Years", "14 to 17 Years",
// "18 to 24 Years", "25 to 44 Years", "45 to 64 Years",
// "65 Years and Over"
var keys = data.columns.slice(1);
// Finish defining the X0 (horizontal) state categories scale
x0.domain(data.map(function(d) { return d.State; }));
// Finish defining the X1 (horizontal) state categories scale
// Use the bandwidth of each state to set to range of x1
// The rounding gives a nice visual effect
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
// Finish defining the Y axis
// Note the following:
// - d3.max(data...)
// - d3.max(keys, ...)
// => Think of it as a nested for loop
// => loop through each state and then within each state loop through each age group
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return d[key]; }); })]).nice();
// Double Data Join
// First data join will give us the states
// - Each g receives state and age group object
// Second data join will give us the rectangles
// - Within the data join, we build a new object from scratch
// - These will be the specific age groups
// - Each rectangle receives the specific age group per state
// - By the end of this, we have a small bar chart for each agre group.
g.append("g")
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x1.bandwidth())
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", function(d) { return z(d.key); });
// Create the X Axis
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x0));
// Create the Y Axis
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(null, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks().pop()) + 0.5)
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.text("Population");
// Create the legend g's
// Note the data join
// Note the transform translate based on the selection element index
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// Create rectangles for each legend g
// Pass rect index to Z color ordinal scale
legend.append("rect")
.attr("x", width - 19)
.attr("width", 19)
.attr("height", 19)
.attr("fill", z);
// Create text for each legend g
// Use the data that it inherts to create the SVG text
legend.append("text")
.attr("x", width - 24)
.attr("y", 9.5)
.attr("dy", "0.32em")
.text(function(d) { return d; });
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment