An example of a stacked bar chart visualising NSS scores per department at Imperial.
This is part of a series of visualisations called My Visual Vocabulary which aims to recreate every visualisation in the FT's Visual Vocabulary from scratch using D3.
forked from tlfrd's block: Stacked Bar Chart
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400, 600" rel="stylesheet">
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
text {
font-family: 'Open Sans', sans-serif;
}
.title {
font-size: 17px;
opacity: 0.7;
font-weight: 600;
}
.outline {
fill: none;
}
.below-bar {
fill: #a0448c;
}
.eq-bar {
fill: #b5b6b9;
}
.above-bar {
fill: #adce49;
}
.legend-label {
font-size: 14px;
opacity: 0.7;
}
.y-axis text {
font-size: 12px;
opacity: 0.7;
}
.bar-groups text {
fill: white;
font-weight: 600;
}
.annotation {
font-size: 12px;
opacity: 0.7;
}
</style>
</head>
<body>
<script>
var margin = {top: 150, right: 100, bottom: 125, left: 200};
var width = 960 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
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 + ")");
function getValues(d) {
for (var i = 1; i <= 27; i++) {
d[i] = +d[i];
}
return d;
}
var x = d3.scaleLinear()
.range([0, width]);
var y = d3.scaleBand()
.range([0, height])
.padding(0.2);
d3.csv("nss-results.csv", getValues, function(error, results) {
if (error) throw error;
var englandAverage = results[results.length - 2],
imperialAverage = results[results.length - 1];
results = results.slice(0, -2);
results.forEach(function(d) {
var eng = [],
imp = [];
for (var i = 1; i <= 27; i++) {
eng[i - 1] = (d[i] - englandAverage[i]).toPrecision(2);
imp[i - 1] = (d[i] - imperialAverage[i]).toPrecision(2);
}
d.englandAvgDiff = eng;
d.imperialAvgDiff = imp;
});
results.forEach(function(d) {
var above = 0,
equal = 0,
below = 0;
for (var x in d.englandAvgDiff) {
if (+d.englandAvgDiff[x] < 0) {
below++;
} else if (+d.englandAvgDiff[x] > 0) {
above++;
} else {
equal++;
}
}
d.above = above;
d.equal = equal;
d.below = below;
});
results.sort(function(a, b) {
if (a.below < b.below) return 1;
if (a.below > b.below) return -1;
return 0;
})
x.domain([0, results[0].englandAvgDiff.length]);
y.domain(results.map(d => d.department));
var title = svg.append("g")
.attr("class", "title")
.attr("transform", "translate(" + [0, -margin.top / 2] + ")");
title.append("text")
.attr("text-anchor", "stat")
.text("No. of NSS Questions with Scores Above or Below the England Average");
var legend = svg.append("g")
.attr("transform", "translate(" + [0, -margin.top / 4] + ")");
legend.selectAll("rect")
.data(["below-bar", "eq-bar", "above-bar"])
.enter().append("rect")
.attr("class", d => d)
.attr("x", (d, i) => (width / 4) * i)
.attr("width", d => 20)
.attr("height", y.bandwidth());
legend.append("rect")
.attr("class", "outline")
.attr("width", width)
.attr("height", y.bandwidth());
legend.selectAll("text")
.data(["Below Average", "Equal To Average", "Above Average"])
.enter().append("text")
.attr("class", "legend-label")
.attr("x", (d, i) => (width / 4) * i + 30)
.attr("y", y.bandwidth() / 2)
.attr("dy", 5)
.text(d => d);
var annotation = svg.append("g")
.attr("transform", "translate(" + [0, height + margin.bottom / 3] + ")");
annotation.append("text")
.attr("class", "annotation")
.text("*Molecular Biology, Biophysics, and Biochemisty")
var yAxisGroup = svg.append("g")
.attr("class", "y-axis-group");
var yAxis = yAxisGroup.append("g")
.attr("class", "y-axis")
.call(d3.axisLeft(y).tickSize(0));
yAxis.attr("transform", "translate(" + [-10, -1] + ")")
yAxis.select(".domain").style("opacity", 0);
var xAxisGroup = svg.append("g")
.attr("class", "x-axis-group")
.attr("transform", "translate(" + [0, height] + ")")
var barGroups = svg.append("g")
.attr("class", "bar-groups")
.selectAll("g")
.data(results)
.enter().append("g")
.attr("class", "bar-group")
.attr("transform", d => "translate(" + [0, y(d.department)] + ")")
var lowerBars = barGroups.append("rect")
.attr("class", "below-bar")
.attr("x", 0)
.attr("width", d => x(d.below))
.attr("height", y.bandwidth());
var lowerLabels = barGroups.append("text")
.attr("x", d => x(d.below) / 2)
.attr("y", y.bandwidth())
.attr("text-anchor", "middle")
.text(d => d.below > 0 ? d.below : "");
var eqBars = barGroups.append("rect")
.attr("class", "eq-bar")
.attr("x", d => x(d.below))
.attr("width", d => x(d.equal))
.attr("height", y.bandwidth());
var eqLabels = barGroups.append("text")
.attr("x", d => x(d.below) + x(d.equal) / 2)
.attr("y", y.bandwidth())
.attr("text-anchor", "middle")
.text(d => d.equal > 0 ? d.equal : "");
var aboveBars = barGroups.append("rect")
.attr("class", "above-bar")
.attr("x", d => x(d.below + d.equal))
.attr("width", d => x(d.above))
.attr("height", y.bandwidth());
var aboveLabels = barGroups.append("text")
.attr("x", d => x(d.below) + x(d.equal) + x(d.above) / 2)
.attr("y", y.bandwidth())
.attr("text-anchor", "middle")
.text(d => d.above > 0 ? d.above : "");
barGroups.selectAll("text")
.attr("dy", -y.bandwidth() * 0.225)
.style("font-size", y.bandwidth() * 0.7)
var outlines = barGroups.append("rect")
.attr("class", "outline")
.attr("width", width + 1)
.attr("height", y.bandwidth());
});
</script>
</body>
https://d3js.org/d3.v4.min.js