A sankey diagram of a poll I conducted for Felix, Imperial's Student Newspaper. We asked students who voted in 2015 who they intended to vote for in the 2017 general election. Featured in Issue 1666 of Felix.
Uses d3-sankey.
forked from tlfrd's block: Sankey Diagram
xxxxxxxxxx
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey@0.5"></script>
<script>
var colours = {
"Conservative_2015": "#0087DC",
"Conservative_2017": "#0087DC",
"Labour_2015": "#DC241f",
"Labour_2017": "#DC241f",
"Green_2015": "#6AB023",
"Green_2017": "#6AB023",
"UKIP_2015": "#70147A",
"UKIP_2017": "#70147A",
"LiberalDemocrat_2015": "#FDBB30",
"LiberalDemocrat_2017": "#FDBB30",
"SNP_2015": "#FFFF00",
"SNP_2017": "#FFFF00",
"Abstained_2015": "#614126",
"Spoiled_2015": "#C3834C",
"Other_2015": "#7F7F7F",
"Other_2017": "#7F7F7F"
}
var svg = d3.select("svg"),
margin = {top: 50, right: 160, bottom: 50, left: 180},
width = +svg.attr("width") -margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var formatNumber = d3.format(",.0f"),
color = d3.scaleOrdinal(d3.schemeCategory10);
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.iterations(1)
.extent([[1, 1], [width - 1, height - 6]]);
var link = g.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = g.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
var indexLookup = {};
var pollNodes = {
"nodes": [],
"links": []
};
d3.json("poll.json", function(error, poll) {
if (error) throw error;
// first convert the data into a suitable format for generating a sankey diagram
// generate 2015 nodes
for (var party2017 in poll) {
if (party2017 == "Conservative") {
for (var party2015 in poll[party2017]) {
if (party2015 !== "Total" && party2015 !== "ICouldNotVote") {
pollNodes["nodes"].push({
"name": party2015 + "_2015"
});
var currentSize = pollNodes["nodes"].length - 1;
indexLookup[party2015 + "_2015"] = currentSize;
}
}
}
if (party2017 !== "UKIP" && party2017 !== "Green" && party2017 !== "SNP"
&& party2017 !== "ICannotVote" && party2017 !== "IWillSpoilMyBallot"
&& party2017 !== "IDoNotIntendToVote(ButIAmEligibleTo)") {
// generate 2017 nodes
pollNodes["nodes"].push({
"name": party2017 + "_2017"
});
}
var currentSize = pollNodes["nodes"].length - 1;
indexLookup[party2017 + "_2017"] = currentSize;
}
var total = 0;
for (var party2017 in poll) {
for (var party2015 in poll[party2017]) {
if (party2015 !== "Total" && party2015 !== "ICouldNotVote") {
if (poll[party2017][party2015] !== 0) {
pollNodes["links"].push({
"source": indexLookup[party2015 + "_2015"],
"target": indexLookup[party2017 + "_2017"],
"value": poll[party2017][party2015]
});
total += poll[party2017][party2015];
}
}
}
}
// generate sankey layout
sankey(pollNodes);
link = link
.data(pollNodes.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.dy); })
.attr("stroke", function(d) {
return colours[d.source.name];
})
.on("mouseover", function() {
d3.select(this)
.attr("stroke-opacity", 0.6);
})
.on("mouseout", function() {
d3.select(this)
.attr("stroke-opacity", 0.2);
})
link.append("title")
.text(function(d) { return d.value; });
node = node
.data(pollNodes.nodes)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.attr("fill", function(d) {
return colours[d.name] || "#D3D3D3";
})
.append("title")
.text(function(d) { return d.name + "\n" + d.value; });
node.append("text")
.attr("x", sankey.nodeWidth() + 10)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "start")
.attr("transform", null)
.text(function(d) { return d.name.slice(0, -5) + ": " + Math.round(d.value / total * 100) + "%"; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", sankey.nodeWidth() - 25)
.attr("text-anchor", "end");
});
</script>
https://d3js.org/d3.v4.min.js
https://unpkg.com/d3-sankey@0.5