Skip to content

Instantly share code, notes, and snippets.

@tmaybe
Last active January 5, 2018 01:31
Show Gist options
  • Save tmaybe/6144082 to your computer and use it in GitHub Desktop.
Save tmaybe/6144082 to your computer and use it in GitHub Desktop.
Stacked to Normalized Stacked Bar Chart

Based on mbostock's examples Stacked Bar Chart and Normalized Stacked Bar Chart. Adds transitions between the two states.

Data shows answers to the question "Generally speaking, do you think things in Afghanistan today are going in the right direction, or do you think they are going in the wrong direction?" in the Asia Foundation's 2012 Survey of the Afghan People broken down by gender, age, location, ethnicity, income, education and region.

Absolutes Total: Male Female 18-24 y.o. 25-34 y.o. 35-44 y.o. 45-54 y.o. over 55 y.o. Villages Urban Pashtun Tajik Uzbek Hazara Other Less than 2,000 Afs 2,001 - 3,000 Afs 3,001 - 5,000 Afs (incl. refused and DK) 5,001 - 10,000 Afs 10,001+ Afs Never went to school (incl. refused and DK) 1-6 grade 7-9 grade 10+ grade Central/Kabul Eastern South East South Western Western North East Central/Hazarjat North West
Base: All Respondents 6348 3638 2710 1572 1724 1512 945 594 4983 1365 2620 2009 553 695 471 551 1066 1240 1991 1500 3675 970 581 1122 1428 617 680 708 856 927 222 910
Right direction 2933 1698 1235 723 775 715 429 291 2392 541 1170 935 253 349 226 281 540 536 888 688 1721 444 266 502 511 283 266 362 400 508 131 472
Wrong direction 2200 1340 859 549 607 521 325 197 1623 576 976 683 183 190 167 174 348 419 710 548 1202 350 213 434 666 264 226 234 252 251 46 262
Some in right, some in wrong direction (vol.) 1084 531 554 267 307 242 175 93 857 228 425 345 100 143 71 76 156 250 363 240 658 165 90 171 228 65 170 109 170 151 32 160
Refused (vol.) 6 3 3 0 3 3 0 0 5 1 2 3 0 1 0 0 2 2 0 2 5 0 0 1 2 0 0 0 2 2 0 0
Don't know (vol.) 125 65 59 33 32 31 16 13 106 19 47 44 15 11 8 19 20 33 31 22 88 11 12 13 20 6 19 4 33 15 13 16
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
rect.bordered {
stroke: #E6E6E6;
stroke-width: 2px;
}
body {
font-size: 9pt;
font-family: Consolas, courier;
}
text.axis {
fill: #000;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
.legend line {
stroke: #000;
shape-rendering: crispEdges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
<script src="http://d3js.org/d3.v3.js" type="text/javascript"></script>
<title></title>
</head>
<body>
<form>
<label><input type="radio" name="mode" value="bypercent" checked> Percent</label>
<label><input type="radio" name="mode" value="bycount"> Number of Respondants</label>
</form>
<div id="chart"></div>
<script type="text/javascript">
var margin = {top: 20, right: 231, bottom: 140, left: 40},
width = 1000 - margin.left - margin.right,
height = 800 - margin.top - margin.bottom;
var xscale = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var yscale = d3.scale.linear()
.rangeRound([height, 0]);
var colors = d3.scale.ordinal()
.range(["#63c172", "#ee9952", "#46d6c4", "#fee851", "#98bc9a"]);
var xaxis = d3.svg.axis()
.scale(xscale)
.orient("bottom");
var yaxis = d3.svg.axis()
.scale(yscale)
.orient("left")
.tickFormat(d3.format(".0%")); // **
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 + ")");
// load and handle the data
d3.tsv("data.tsv", function(error, data) {
// rotate the data
var categories = d3.keys(data[0]).filter(function(key) { return key !== "Absolutes"; });
var parsedata = categories.map(function(name) { return { "Absolutes": name }; });
data.forEach(function(d) {
parsedata.forEach(function(pd) {
pd[d["Absolutes"]] = d[pd["Absolutes"]];
});
});
// map column headers to colors (except for 'Absolutes' and 'Base: All Respondents')
colors.domain(d3.keys(parsedata[0]).filter(function(key) { return key !== "Absolutes" && key !== "Base: All Respondents"; }));
// add a 'responses' parameter to each row that has the height percentage values for each rect
parsedata.forEach(function(pd) {
var y0 = 0;
// colors.domain() is an array of the column headers (text)
// pd.responses will be an array of objects with the column header
// and the range of values it represents
pd.responses = colors.domain().map(function(response) {
var responseobj = {response: response, y0: y0, yp0: y0};
y0 += +pd[response];
responseobj.y1 = y0;
responseobj.yp1 = y0;
return responseobj;
});
// y0 is now the sum of all the values in the row for this category
// convert the range values to percentages
pd.responses.forEach(function(d) { d.yp0 /= y0; d.yp1 /= y0; });
// save the total
pd.totalresponses = pd.responses[pd.responses.length - 1].y1;
});
// sort by the value in 'Right Direction'
// parsedata.sort(function(a, b) { return b.responses[0].yp1 - a.responses[0].yp1; });
// ordinal-ly map categories to x positions
xscale.domain(parsedata.map(function(d) { return d.Absolutes; }));
// add the x axis and rotate its labels
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xaxis)
.selectAll("text")
.attr("y", 5)
.attr("x", 7)
.attr("dy", ".35em")
.attr("transform", "rotate(65)")
.style("text-anchor", "start");
// add the y axis
svg.append("g")
.attr("class", "y axis")
.call(yaxis);
// create svg groups ("g") and place them
var category = svg.selectAll(".category")
.data(parsedata)
.enter().append("g")
.attr("class", "category")
.attr("transform", function(d) { return "translate(" + xscale(d.Absolutes) + ",0)"; });
// draw the rects within the groups
category.selectAll("rect")
.data(function(d) { return d.responses; })
.enter().append("rect")
.attr("width", xscale.rangeBand())
.attr("y", function(d) { return yscale(d.yp1); })
.attr("height", function(d) { return yscale(d.yp0) - yscale(d.yp1); })
.style("fill", function(d) { return colors(d.response); });
// position the legend elements
var legend = svg.selectAll(".legend")
.data(colors.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(20," + ((height - 18) - (i * 20)) + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", colors);
legend.append("text")
.attr("x", width + 10)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) { return d; });
// animation
d3.selectAll("input").on("change", handleFormClick);
function handleFormClick() {
if (this.value === "bypercent") {
transitionPercent();
} else {
transitionCount();
}
}
// transition to 'percent' presentation
function transitionPercent() {
// reset the yscale domain to default
yscale.domain([0, 1]);
// create the transition
var trans = svg.transition().duration(250);
// transition the bars
var categories = trans.selectAll(".category");
categories.selectAll("rect")
.attr("y", function(d) { return yscale(d.yp1); })
.attr("height", function(d) { return yscale(d.yp0) - yscale(d.yp1); });
// change the y-axis
// set the y axis tick format
yaxis.tickFormat(d3.format(".0%"));
svg.selectAll(".y.axis").call(yaxis);
}
// transition to 'count' presentation
function transitionCount() {
// set the yscale domain
yscale.domain([0, d3.max(parsedata, function(d) { return d.totalresponses; })]);
// create the transition
var transone = svg.transition()
.duration(250);
// transition the bars (step one)
var categoriesone = transone.selectAll(".category");
categoriesone.selectAll("rect")
.attr("y", function(d) { return this.getBBox().y + this.getBBox().height - (yscale(d.y0) - yscale(d.y1)) })
.attr("height", function(d) { return yscale(d.y0) - yscale(d.y1); });
// transition the bars (step two)
var transtwo = transone.transition()
.delay(350)
.duration(350)
.ease("bounce");
var categoriestwo = transtwo.selectAll(".category");
categoriestwo.selectAll("rect")
.attr("y", function(d) { return yscale(d.y1); });
// change the y-axis
// set the y axis tick format
yaxis.tickFormat(d3.format(".2s"));
svg.selectAll(".y.axis").call(yaxis);
}
});
d3.select(self.frameElement).style("height", (height + margin.top + margin.bottom) + "px");
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment