An example of a column chart visualising NSS scores per question 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.
| license: mit | 
An example of a column chart visualising NSS scores per question 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.
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src="https://d3js.org/d3.v4.min.js"></script> | |
| <style> | |
| body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
| text { | |
| font-family: monospace; | |
| } | |
| .outline { | |
| fill: none; | |
| } | |
| .below-bar { | |
| fill: #d7191c; | |
| } | |
| .eq-bar { | |
| fill: #c9c9c9; | |
| } | |
| .above-bar { | |
| fill: #2c7bb6; | |
| } | |
| .legend-label { | |
| font-size: 11px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| var margin = {top: 100, right: 200, bottom: 100, left: 100}; | |
| 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 + ")"); | |
| function getValues(d) { | |
| for (var i = 1; i <= 27; i++) { | |
| d[i] = +d[i]; | |
| } | |
| return d; | |
| } | |
| var englandMode = true; | |
| var x = d3.scaleBand() | |
| .range([0, width]) | |
| .padding(0.2); | |
| var y = d3.scaleLinear() | |
| .range([height, 0]); | |
| 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(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; | |
| }); | |
| var resultsByQuestion = d3.range(27); | |
| resultsByQuestion = resultsByQuestion.map(d => c = { | |
| belowEng: 0, | |
| equalEng: 0, | |
| aboveEng: 0, | |
| belowImp: 0, | |
| equalImp: 0, | |
| aboveImp: 0 | |
| }); | |
| results.forEach(d => { | |
| d.englandAvgDiff.forEach((x, i) => { | |
| if (x > 0) resultsByQuestion[i].aboveEng++; | |
| if (x < 0) resultsByQuestion[i].belowEng++; | |
| if (x == 0) resultsByQuestion[i].equalEng++; | |
| }) | |
| d.imperialAvgDiff.forEach((x, i) => { | |
| if (x > 0) resultsByQuestion[i].aboveImp++; | |
| if (x < 0) resultsByQuestion[i].belowImp++; | |
| if (x == 0) resultsByQuestion[i].equalImp++; | |
| }) | |
| }); | |
| x.domain(d3.range(27)); | |
| y.domain([0, results.length]); | |
| var title = svg.append("g") | |
| .attr("transform", "translate(" + [width / 2, -30] + ")"); | |
| title.append("text") | |
| .attr("text-anchor", "middle") | |
| .text("No. of Departments w/ Scores Above or Below England Avg Per Question"); | |
| var legend = svg.append("g") | |
| .attr("transform", "translate(" + [width + margin.right / 5, 0] + ")"); | |
| legend.selectAll("rect") | |
| .data(["above-bar", "eq-bar", "below-bar"]) | |
| .enter().append("rect") | |
| .attr("class", d => d) | |
| .attr("y", (d, i) => (height / 3) * i) | |
| .attr("height", d => height / 3) | |
| .attr("width", x.bandwidth()); | |
| legend.append("rect") | |
| .attr("class", "outline") | |
| .attr("height", height) | |
| .attr("width", x.bandwidth()); | |
| legend.selectAll("text") | |
| .data(["Above", "Equal", "Below"]) | |
| .enter().append("text") | |
| .attr("class", "legend-label") | |
| .attr("y", (d, i) => (height / 3) * i) | |
| .attr("x", x.bandwidth()) | |
| .attr("dx", 10) | |
| .attr("dy", height / 6) | |
| .text(d => d) | |
| 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(" + [-5, 0] + ")"); | |
| yAxis.select(".domain").style("opacity", 0); | |
| var xAxisGroup = svg.append("g") | |
| .attr("class", "x-axis-group") | |
| .attr("transform", "translate(" + [0, height] + ")") | |
| var xAxis = xAxisGroup.append("g") | |
| .attr("class", "x-axis") | |
| .call(d3.axisBottom(x).tickSize(0).tickFormat(d => d + 1)); | |
| xAxis.attr("transform", "translate(" + [0, 5] + ")") | |
| xAxis.select(".domain").style("opacity", 0); | |
| var xLabel = xAxisGroup.append("text") | |
| .attr("transform", "translate(" + [width / 2, margin.bottom / 2] + ")") | |
| .attr("text-anchor", "middle") | |
| .text("Question Number") | |
| var barGroups = svg.append("g") | |
| .attr("class", "bar-groups") | |
| .selectAll("g") | |
| .data(resultsByQuestion) | |
| .enter().append("g") | |
| .attr("class", "bar-group") | |
| .attr("transform", (d, i) => "translate(" + [x(i), 0] + ")") | |
| var aboveBars = barGroups.append("rect") | |
| .attr("class", "above-bar") | |
| .attr("y", d => y(d.belowEng + d.equalEng + d.aboveEng)) | |
| .attr("height", d => height - y(d.belowEng + d.equalEng + d.aboveEng)) | |
| .attr("width", x.bandwidth()); | |
| var eqBars = barGroups.append("rect") | |
| .attr("class", "eq-bar") | |
| .attr("y", d => y(d.equalEng + d.belowEng)) | |
| .attr("height", d => height - y(d.equalEng + d.belowEng)) | |
| .attr("width", x.bandwidth()); | |
| var belowBars = barGroups.append("rect") | |
| .attr("class", "below-bar") | |
| .attr("y", d => y(d.belowEng)) | |
| .attr("height", d => height - y(d.belowEng)) | |
| .attr("width", x.bandwidth()); | |
| var outlines = barGroups.append("rect") | |
| .attr("class", "outline") | |
| .attr("height", d => y(0)) | |
| .attr("width", x.bandwidth()); | |
| d3.select("svg").on("click", switchBetween); | |
| function switchBetween() { | |
| function newY(d) { | |
| var aY, eY, bY; | |
| if (englandMode) { | |
| aY = y(d.belowImp + d.equalImp + d.aboveImp), | |
| eY = y(d.equalImp + d.belowImp), | |
| bY = y(d.belowImp); | |
| } else { | |
| aY = y(d.belowEng + d.equalEng + d.aboveEng), | |
| eY = y(d.equalEng + d.belowEng), | |
| bY = y(d.belowEng); | |
| } | |
| return [aY, eY, bY]; | |
| } | |
| aboveBars | |
| .transition() | |
| .attr("y", d => newY(d)[0]) | |
| .attr("height", d => height - newY(d)[0]); | |
| eqBars | |
| .transition() | |
| .attr("y", d => newY(d)[1]) | |
| .attr("height", d => height - newY(d)[1]); | |
| belowBars | |
| .transition() | |
| .attr("y", d => newY(d)[2]) | |
| .attr("height", d => height - newY(d)[2]); | |
| if (englandMode) { | |
| title.select("text") | |
| .text("No. of Departments w/ Scores Above or Below Imperial Avg Per Question"); | |
| } else { | |
| title.select("text") | |
| .text("No. of Departments w/ Scores Above or Below England Avg Per Question"); | |
| } | |
| englandMode = !englandMode; | |
| } | |
| }); | |
| </script> | |
| </body> | 
| department | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Medicine | 0.88 | 0.82 | 0.91 | 0.80 | 0.75 | 0.82 | 0.93 | 0.73 | 0.76 | 0.56 | 0.55 | 0.85 | 0.74 | 0.72 | 0.76 | 0.77 | 0.75 | 0.88 | 0.93 | 0.91 | 0.78 | 0.91 | 0.93 | 0.80 | 0.72 | 0.75 | 0.90 | |
| Other Med | 0.88 | 0.74 | 0.89 | 0.86 | 0.80 | 0.77 | 0.70 | 0.50 | 0.68 | 0.35 | 0.59 | 0.73 | 0.68 | 0.59 | 0.35 | 0.53 | 0.59 | 0.71 | 0.84 | 0.85 | 0.64 | 0.88 | 0.92 | 0.83 | 0.64 | 0.56 | 0.70 | |
| Biology | 0.87 | 0.84 | 0.89 | 0.82 | 0.83 | 0.81 | 0.71 | 0.75 | 0.62 | 0.71 | 0.48 | 0.85 | 0.66 | 0.67 | 0.72 | 0.86 | 0.75 | 0.88 | 0.82 | 0.86 | 0.52 | 0.89 | 0.87 | 0.69 | 0.62 | 0.47 | 0.81 | |
| Other Bio | 0.90 | 0.78 | 0.83 | 0.82 | 0.83 | 0.86 | 0.80 | 0.66 | 0.64 | 0.70 | 0.52 | 0.82 | 0.58 | 0.62 | 0.67 | 0.86 | 0.86 | 0.78 | 0.85 | 0.86 | 0.51 | 0.89 | 0.88 | 0.74 | 0.74 | 0.51 | 0.83 | |
| Chemistry | 0.84 | 0.75 | 0.89 | 0.75 | 0.80 | 0.74 | 0.82 | 0.51 | 0.56 | 0.59 | 0.53 | 0.75 | 0.60 | 0.52 | 0.52 | 0.74 | 0.67 | 0.85 | 0.84 | 0.89 | 0.57 | 0.78 | 0.79 | 0.63 | 0.48 | 0.53 | 0.73 | |
| Phys & Astro | 0.82 | 0.73 | 0.86 | 0.71 | 0.71 | 0.68 | 0.60 | 0.47 | 0.43 | 0.46 | 0.56 | 0.82 | 0.56 | 0.62 | 0.57 | 0.70 | 0.70 | 0.78 | 0.81 | 0.87 | 0.45 | 0.76 | 0.82 | 0.54 | 0.50 | 0.40 | 0.67 | |
| Geology | 0.99 | 0.97 | 0.96 | 0.93 | 0.93 | 0.94 | 0.97 | 0.93 | 0.87 | 0.97 | 0.94 | 0.97 | 0.97 | 0.99 | 0.99 | 0.91 | 0.99 | 1.00 | 0.96 | 1.00 | 0.89 | 0.94 | 0.99 | 0.99 | 0.97 | 0.86 | 0.97 | |
| Maths & Stats | 0.87 | 0.76 | 0.83 | 0.79 | 0.85 | 0.70 | 0.62 | 0.57 | 0.69 | 0.53 | 0.55 | 0.87 | 0.68 | 0.68 | 0.76 | 0.73 | 0.81 | 0.91 | 0.82 | 0.91 | 0.59 | 0.70 | 0.88 | 0.78 | 0.67 | 0.52 | 0.78 | |
| Computing | 0.82 | 0.79 | 0.94 | 0.82 | 0.85 | 0.87 | 0.91 | 0.54 | 0.79 | 0.54 | 0.63 | 0.90 | 0.78 | 0.64 | 0.74 | 0.74 | 0.87 | 0.92 | 0.89 | 0.93 | 0.71 | 0.98 | 0.95 | 0.85 | 0.80 | 0.66 | 0.86 | |
| Gen Eng | 0.92 | 0.91 | 0.97 | 0.86 | 0.91 | 0.97 | 0.85 | 0.57 | 0.74 | 0.77 | 0.66 | 0.91 | 0.80 | 0.69 | 0.86 | 0.82 | 0.85 | 0.80 | 0.81 | 0.91 | 0.85 | 0.94 | 0.92 | 0.94 | 0.83 | 0.57 | 0.97 | |
| Mech Eng | 0.91 | 0.83 | 0.93 | 0.87 | 0.91 | 0.90 | 0.88 | 0.77 | 0.81 | 0.78 | 0.83 | 0.90 | 0.87 | 0.88 | 0.85 | 0.83 | 0.92 | 0.85 | 0.88 | 0.89 | 0.74 | 0.99 | 0.99 | 0.84 | 0.89 | 0.57 | 0.86 | |
| Aero Eng | 0.71 | 0.67 | 0.81 | 0.75 | 0.72 | 0.77 | 0.74 | 0.65 | 0.67 | 0.43 | 0.57 | 0.86 | 0.73 | 0.59 | 0.70 | 0.70 | 0.75 | 0.90 | 0.87 | 0.87 | 0.57 | 0.96 | 0.86 | 0.61 | 0.57 | 0.53 | 0.78 | |
| E&E Eng | 0.88 | 0.81 | 0.92 | 0.85 | 0.88 | 0.89 | 0.86 | 0.75 | 0.71 | 0.60 | 0.65 | 0.86 | 0.69 | 0.68 | 0.80 | 0.79 | 0.88 | 0.89 | 0.88 | 0.97 | 0.72 | 0.96 | 0.92 | 0.80 | 0.85 | 0.66 | 0.86 | |
| Civ Eng | 0.93 | 0.81 | 0.94 | 0.88 | 0.86 | 0.89 | 0.93 | 0.69 | 0.75 | 0.87 | 0.69 | 0.96 | 0.90 | 0.82 | 0.90 | 0.90 | 0.90 | 0.96 | 0.96 | 0.94 | 0.85 | 0.97 | 0.94 | 0.92 | 0.79 | 0.77 | 0.94 | |
| Chem Eng | 0.86 | 0.79 | 0.88 | 0.94 | 0.88 | 0.87 | 0.86 | 0.75 | 0.70 | 0.61 | 0.68 | 0.95 | 0.74 | 0.74 | 0.84 | 0.82 | 0.95 | 0.80 | 0.84 | 0.86 | 0.70 | 0.96 | 0.94 | 0.85 | 0.92 | 0.58 | 0.84 | |
| Materials | 0.85 | 0.78 | 0.85 | 0.73 | 0.91 | 0.90 | 0.73 | 0.54 | 0.69 | 0.65 | 0.69 | 0.79 | 0.71 | 0.73 | 0.66 | 0.82 | 0.77 | 0.94 | 0.89 | 0.98 | 0.72 | 0.91 | 0.85 | 0.80 | 0.65 | 0.71 | 0.87 | |
| Business | 0.79 | 0.64 | 0.71 | 0.67 | 0.69 | 0.76 | 0.76 | 0.57 | 0.67 | 0.50 | 0.55 | 0.79 | 0.57 | 0.59 | 0.62 | 0.69 | 0.79 | 0.81 | 0.83 | 0.88 | 0.56 | 0.88 | 0.80 | 0.69 | 0.66 | 0.59 | 0.66 | |
| England Average | 0.89 | 0.83 | 0.85 | 0.82 | 0.84 | 0.85 | 0.82 | 0.72 | 0.73 | 0.73 | 0.74 | 0.86 | 0.79 | 0.75 | 0.71 | 0.79 | 0.77 | 0.83 | 0.87 | 0.86 | 0.72 | 0.86 | 0.84 | 0.76 | 0.62 | 0.58 | 0.84 | |
| Imperial Average | 0.87 | 0.80 | 0.89 | 0.82 | 0.83 | 0.83 | 0.82 | 0.66 | 0.69 | 0.62 | 0.62 | 0.87 | 0.73 | 0.70 | 0.74 | 0.78 | 0.81 | 0.87 | 0.87 | 0.91 | 0.68 | 0.90 | 0.90 | 0.78 | 0.73 | 0.61 | 0.84 |