Visualising the results of Imperial's 2017 NSS as a Heat Map.
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; }
.score rect {
fill: none;
}
text {
opacity: 0.7;
font-family: 'Open Sans', sans-serif;
}
.legend-labels {
font-size: 16px;
}
.score-labels {
font-size: 15px;
}
.x-label {
font-weight: 600;
}
.uni-labels {
font-size: 14px;
}
.annotation {
font-size: 12px;
}
</style>
</head>
<body>
<script>
var margin = {top: 100, right: 100, bottom: 125, left: 250};
var width = 960 - margin.left - margin.right,
height = 500 - 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 + ")");
var negativeScale = d3.scaleLinear().range(["red", "white"]);
var positiveScale = d3.scaleLinear().range(["white", "green"]);
var config = {};
function getValues(d) {
for (var i = 1; i <= 27; i++) {
d[i] = +d[i];
}
return d;
}
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;
d.engAvgDiffSum = d3.sum(d.englandAvgDiff);
d.impAvgDiffSum = d3.sum(d.imperialAvgDiff);
});
results.sort(function(a, b) {
if (a.engAvgDiffSum < b.engAvgDiffSum) return 1;
if (a.engAvgDiffSum > b.engAvgDiffSum) return -1;
return 0;
})
var rectWidth = width / results[0].englandAvgDiff.length,
rectHeight = height / results.length;
var grid = svg.append("g")
.attr("class", "grid");
var max = d3.max(results, function(d) {
return d3.max(d.englandAvgDiff, function(x) {
return +x;
})
});
var min = d3.min(results, function(d) {
return d3.min(d.englandAvgDiff, function(x) {
return +x;
})
});
var maxAbs = Math.max(Math.abs(max), Math.abs(min));
negativeScale.domain([-maxAbs, 0]);
positiveScale.domain([0, maxAbs]);
var xLabel = svg.append("g")
.attr("transform", "translate(" + [width / 2, -margin.top / 2] + ")")
.append("text")
.attr("class", "x-label")
.attr("text-anchor", "middle")
.text("How each Department Scores per NSS Question");
var annotation = svg.append("g")
.attr("transform", "translate(" + [width / 2, height + margin.bottom / 3] + ")");
annotation.append("text")
.attr("class", "annotation")
.text("*Molecular Biology, Biophysics, and Biochemisty")
var universities = grid.selectAll("g")
.data(results)
.enter().append("g")
.attr("class", "university")
.attr("transform", (d, i) => "translate(" + [0, i * rectHeight] + ")");
var universityLabels = universities.append("text")
.attr("class", "uni-labels")
.attr("text-anchor", "end")
.attr("x", -10)
.attr("y", rectHeight)
.attr("dy", -3)
.text(d => d.department);
var scoreLabels = grid.append("g")
.attr("class", "score-labels")
.selectAll("text")
.data(d3.range(results[0].englandAvgDiff.length))
.enter().append("text")
.attr("x", d => (rectWidth * d) + rectWidth / 2)
.attr("dy", -10)
.attr("text-anchor", "middle")
.text(d => d + 1);
var scores = universities.selectAll("g")
.data(d => d.englandAvgDiff)
.enter().append("g")
.attr("class", "score")
.attr("transform", (d, i) => "translate(" + [i * rectWidth, 0] + ")");
var scoreRect = scores.append("rect")
.attr("width", rectWidth + 1)
.attr("height", rectHeight + 2)
.style("fill", function(d) {
if (d < 0) {
return negativeScale(d);
} else if (d > 0) {
return positiveScale(d);
} else {
return "#fff";
}
});
var legend = svg.append("g")
.attr("class", "legend")
.attr("transform", "translate(" + [0, height + 50] + ")");
var colours = legend.selectAll("rect")
.data(d3.range(-maxAbs, maxAbs + maxAbs/3, maxAbs/3))
.enter().append("rect")
.attr("x", (d, i) => i * rectWidth)
.attr("width", rectWidth + 1)
.attr("height", rectHeight)
.style("fill", function(d) {
if (d < 0) {
return negativeScale(d);
} else if (d > 0) {
return positiveScale(d);
} else {
return "#fff";
}
});
var legendOffsets = 30;
var legendLabels = legend.append("g")
.attr("class", "legend-labels")
.attr("transform", "translate(" + [-legendOffsets, -10] + ")")
legendLabels.append("text")
.attr("text-anchor", "start")
.text("← Below Avg")
legendLabels.append("text")
.attr("x", rectWidth * 7 + legendOffsets * 2)
.attr("text-anchor", "end")
.text("Above Avg →")
});
</script>
</body>
https://d3js.org/d3.v4.min.js