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.
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>
window.parent.parent.postMessage("Hello from the iframe!", "*");
window.addEventListener("message", function(event) {
console.log(event.data);
console.log(event.origin);
}, false);
// var columns = [];
// function create_column() {
// var title = null,
// types = [Number, String, Date],
// required = false
// var column = function() {
// return;
// }
// column.title = function(_) {
// if (!arguments.length) return title
// title = "" + _;
// return column;
// }
// column.types = function(_) {
// if (!arguments.length) return types;
// types = Array.from(arguments);
// return column;
// }
// column.required = function(_) {
// if (!arguments.length) return required;
// required = _;
// return column;
// }
// return column;
// }
// function column() {
// var column = create_column();
// columns.push(column);
// return column;
// }
// function toJson(column) {
// return {
// title: column.title(),
// types: column.types().map(type => type.name),
// required: column.required(),
// }
// }
// function sheet(callback) {
// var schema = columns.map(column => toJson(column));
// port.postMessage({schema: schema});
// port.onMessage.addListener(function(msg) {
// callback(msg.data);
// })
// }
// sheet();
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