Simple column chart with D3

Michael Bostock’s D3 is a brilliantly powerful visualization framework. It can be used to generate beautiful and diverse visualizations, but most of them would be impossible without data backing them up. So how can you get data from Socrata data sites quickly and easily into D3?

Fortunately this is extremely easy with D3’s d3.csv function and SODA’s built in CSV output type.

In this sample, we’ll walk you through the creation of the simple stacked column chart below. If you’d like to follow along at home, you can fork this jsFiddle sample project.

This example pulls data live from this Chicago Transit Authority ridership dataset via the SODA API.

To start, we’ll need to initialize some of our margins and scales for D3. This is mostly just boilerplate:

// Set our margins
var margin = {
    top: 20,
    right: 20,
    bottom: 30,
    left: 60
},
width = 700 - margin.left - margin.right,
    height = 350 - margin.top - margin.bottom;

// Our X scale
var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

// Our Y scale
var y = d3.scale.linear()
    .rangeRound([height, 0]);

// Our color bands
var color = d3.scale.ordinal()
    .range(["#308fef", "#5fa9f3", "#1176db"]);

// Use our X scale to set a bottom axis
var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

// Same for our left axis
var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(d3.format(".2s"));

Next we’ll create the SVG container that we’ll add our chart components to:

// Add our chart to the #chart div
var svg = d3.select("#chart").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 + ")");

Then we’ll pull in our data using the SODA CSV output type and D3’s d3.csv function. We don’t need the total column that the dataset uses, so we’ll use the $select parameter to filter down to the four columns we really care about. We’ll also use the $where parameter to only get ridership after 1999, and the $$app-token parameter to pass our application token. In this case we’ve “redacted” out the application token - you should register and supply your own:

d3.csv("https://data.cityofchicago.org/resource/w8km-9pzd.csv?"
  + "$select=year,bus,paratransit,rail"
  + "&$where=year>1999"
  + "&$$app_token=[REDACTED]", function (error, data) {
  ...
});

d3.csv takes a function as a parameter that is called when it retrieves your CSV data. That’s where we’ll handle our actual input. The rest of this example is from within that function body.

First we’ll make sure that D3 has properly interpreted our numbers as actual numbers. CSV doesn’t convey typing very well.

// Make sure our numbers are really numbers
data.forEach(function (d) {
    d.year = +d.year;
    d.bus = +d.bus;
    d.paratransit = +d.paratransit;
    d.rail = +d.rail;
});

Next we’ll associate our data with our color bands:

// Map our columns to our colors
color.domain(d3.keys(data[0]).filter(function (key) {
    return key !== "year";
}));

data.forEach(function (d) {
    var y0 = 0;
    d.types = color.domain().map(function (name) {
        return {
            name: name,
            y0: y0,
            y1: y0 += +d[name]
        };
    });
    d.total = d.types[d.types.length - 1].y1;
});

We want our columns sorted by year, so let’s make sure that’s the case:

// Sort by year
data.sort(function (a, b) {
    return a.year - b.year;
});

Set up our axes:

// Our X domain is our set of years
x.domain(data.map(function (d) {
    return d.year;
}));

// Our Y domain is from zero to our highest total
y.domain([0, d3.max(data, function (d) {
    return d.total;
})]);

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis);

Now we actually generate rectangles for all of our data values:

var year = svg.selectAll(".year")
    .data(data)
    .enter().append("g")
    .attr("class", "g")
    .attr("transform", function (d) {
    return "translate(" + x(d.year) + ",0)";
});

year.selectAll("rect")
    .data(function (d) {
    return d.types;
})
    .enter().append("rect")
    .attr("width", x.rangeBand())
    .attr("y", function (d) {
    return y(d.y1);
})
    .attr("height", function (d) {
    return y(d.y0) - y(d.y1);
})
    .style("fill", function (d) {
    return color(d.name);
});

Finally, we add a legend:

var legend = svg.selectAll(".legend")
    .data(color.domain().slice().reverse())
    .enter().append("g")
    .attr("class", "legend")
    .attr("transform", function (d, i) {
    return "translate(0," + i * 20 + ")";
});

legend.append("rect")
    .attr("x", width - 18)
    .attr("width", 18)
    .attr("height", 18)
    .style("fill", color);

legend.append("text")
    .attr("x", width - 24)
    .attr("y", 9)
    .attr("dy", ".35em")
    .style("text-anchor", "end")
    .text(function (d) {
    return d;
});

That’s it! Got a great example of your own? Please contribute to our community-maintained documentation.