Skip to content

Instantly share code, notes, and snippets.

@maggie-lee
Created October 1, 2014 13:25
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 maggie-lee/4247b667f372453789be to your computer and use it in GitHub Desktop.
Save maggie-lee/4247b667f372453789be to your computer and use it in GitHub Desktop.
Multi-series bar graph with clickable legend tutorial
State Pre-teens Teens Seniors
Alabama 75 43.8 38.1
Georgia 80 37.4 32.7
Louisiana 82 30 38.8
Mississippi 78 39.2 32.6
South Carolina 90 42.7 36
United States 76 41.5 35.7
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>Cootie Vaccination Rate</title>
<head></head>
<style>
body {
font-family: Arial, sans-serif;
font-size: 12px;
}
.axis path{
stroke: none;
fill: none;
stroke: #FFF;
shape-rendering: crispEdges;
}
.axis line {
stroke: none;
fill: none;
stroke: #FFF;
shape-rendering: crispEdges;
}
</style>
<body>
<div class="title"><font size=4> Annual Cootie Vaccination Rate, 2013-2014</font></div>
<div class="subtitle">Deep South states v. national average<br>
Teens & seniors woefully underprotected from 2014 strain of Cooties<br><br></div>
<div class="explainer">
A multi-series bar chart with clickable legend.
<br>Based on <a href="http://bost.ocks.org/mike/constancy/" target=_blank>Mike Bostock's Object Constancy example.</a>
<br><br>
</div>
<p>Click to see how your demographic stacks up:</p>
<!-- This <div> will hold an svg that holds the legend only. -->
<div class = "legendHolder"> </div>
<!-- This <div> will hold the drawing -->
<div class="chart"></div>
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>
<script src="http://d3js.org/d3.v2.min.js?2.9.1"></script>
<script>
console.log("ok");
// ************ DECLARATIONS
var margin = {top: 20, right: 100, bottom: 10, left: 10},
width = 400
height = 150 - margin.top - margin.bottom;
var states,
age;
var x = d3.scale.linear()
.domain([0, 100])// Don't use max function. Hardcode 100 b/c 100 is the real-life maximum vaccination rate.
.range([0, width]);
var y = d3.scale.ordinal()
.rangeRoundBands([0, height], .1);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.tickSize(-height - margin.bottom); // draws vertical white gridlines
// ********* NB, no y Axis!
// We won't need it because what looks to the eye like y-axis labels are actually labels stuck to each bar.
// ************
var color = d3.scale.category10();
var svg = d3.select(".chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis");
// ******** END DECLARATIONS
d3.csv("flu_vaccination.csv", function(data) {
// make a copy of data
states = data;
// ********
//figure out your categories, the things that correspond to column names.
// State, Pre-Teens, Teens, Seniors
// Alabama, 52.1, 43.8, 38.1
// etc
//
// Go through the first "row" and make an object that contains only those cells //that are not "State"
var ages = d3.keys(states[0]).filter(function(key) {
return key != "State";
});
// returns ["Pre-teens", "Teens", "Seniors"]
// *************
color.domain(ages); // feed the three ages into the color funciton. Each age will get mapped to a color.
default_age = "Pre-teens"; // initialize the age group to be graphed
// ******* DATA SORTED, LOADED, FILTERED
//******** LEGEND
// legendolder is going to be an svg of this hight
var legendHolder = d3.select(".legendHolder").append("svg").attr("height", height*0.13);
// We're going to append some legend entries into legendHolder
var legend = legendHolder.selectAll(".legend")
.data(color.domain().slice())
.enter().append("g")
.attr("class", "legend")
.attr("transform",
function (d, i)
{ return "translate(" + i*(width*0.2) + ",0)";});
// translate along x: set each legend element at (i * two-tenths of the width)
// and dont translate along y at all
// ****** make all of the legends's rects and texts clickable.
legend.append("rect")
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function (d) {
default_age = d; // what ever you've clicked on becomes the default_age
return change();}); //then default_age will be ready when redraw() calls it
legend.append("text")
.attr("dy", "1em")
.attr("dx", width*0.05)
.style("text-anchor", "start")
.text(function (d) {return d; })
.on("click", function (d) {
default_age = d; // what ever you've clicked on becomes the default_age
return change();}); //then default_age will be ready when redraw() calls it
// **************
redraw();
}); // CLOSE INITIAL FUNCTION
// ******** CHANGE FUNCTION
function change() { // defines very simple transition. redraw() each bar and take 750 ms to do it.
d3.transition()
.duration(750)
.each(redraw);
}
//********* DRAW FUNCTION
function redraw() {
// declare a local variable to hold default_age
var age1 = default_age,
// then sort by this local variable age1
top = states.sort(function(a, b) { return b[age1] - a[age1]; });
// Map y.domain to the states in the newly-sorted data
y.domain(top.map(function(d) { return d.State; }));
// Using the sorted data, make a bar for each state and class it ".bar"
var bar = svg.selectAll(".bar")
.data(top, function(d) { return d.State; })
.attr("class", "bar");
// entering transition. This can be and is very bare-bones. Just "hey, make some bars and attach the state names to each one"
var barEnter = bar.enter().insert("g", ".axis")
.attr("class", "bar");
barEnter.append("rect")
.attr("height", y.rangeBand());
barEnter.append("text") // Attach the state name to the bar. So the state name always travels with it
.attr("class", "label")
.attr("x", -3)
.attr("y", y.rangeBand() / 2)
.attr("dy", ".35em")
.attr("text-anchor", "end")
.text(function (d) {return d.State;});
//now drawing updated bar
age = age1; // put the age that's been clicked on into global variable age
// d3.transition(bar) is where the magic is. Makes each bar smoothly move where it needs to be
var barUpdate = d3.transition(bar)
//transform bars a little to the right b/c we have long labels
.attr("transform", function(d) { return "translate(80," + (d.y0 = y(d.State)) + ")"; });
barUpdate.select("rect")
.attr("width", function (d) { return x(d[age]);})
.attr("fill", function (d) {return color(age) ;})
.attr("fill-opacity", function (d) {
if (d.State == "United States") {return 0.5;} ;} );// Highlight U.S. average
// now to wipe away bars
// pretty much we are just going to remove() them.
var barExit = d3.transition(bar.exit())
.remove();
barExit.select("rect")
d3.transition(svg).select(".axis")
//again, translate a little further right for space for labels
.attr("transform", function(d) { return "translate(80," + 0 + ")"; })
.call(xAxis);
}
// ****** END DRAW FUNCTION
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment