Skip to content

Instantly share code, notes, and snippets.

@heillygalvez
Last active September 24, 2017 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save heillygalvez/c4e675de5c47bfe3eb9a8fa559757a69 to your computer and use it in GitHub Desktop.
Save heillygalvez/c4e675de5c47bfe3eb9a8fa559757a69 to your computer and use it in GitHub Desktop.
Sankey Diagram
license: mit

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

<!DOCTYPE html>
<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>
{
"Labour": {
"Conservative": 9,
"Labour": 21,
"LiberalDemocrat": 11,
"UKIP": 1,
"Green": 9,
"SNP": 1,
"Other": 2,
"ICouldNotVote": 18,
"Abstained": 6,
"Spoiled": 1,
"Total": 79
},
"Conservative": {
"Conservative": 15,
"Labour": 2,
"LiberalDemocrat": 0,
"UKIP": 2,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 8,
"Abstained": 0,
"Spoiled": 0,
"Total": 27
},
"LiberalDemocrat": {
"Conservative": 6,
"Labour": 0,
"LiberalDemocrat": 2,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 2,
"Abstained": 0,
"Spoiled": 0,
"Total": 10
},
"Other": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 1,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 1,
"Spoiled": 0,
"Total": 2
},
"UKIP": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 0,
"Spoiled": 0,
"Total": 0
},
"Green": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 0,
"Spoiled": 0,
"Total": 0
},
"SNP": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 0,
"Spoiled": 0,
"Total": 0
},
"ICannotVote": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 0,
"Spoiled": 0,
"Total": 0
},
"IDoNotIntendToVote(ButIAmEligibleTo)": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 0,
"Spoiled": 0,
"Total": 0
},
"IWillSpoilMyBallot": {
"Conservative": 0,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 0,
"Abstained": 0,
"Spoiled": 0,
"Total": 0
},
"Undecided": {
"Conservative": 1,
"Labour": 0,
"LiberalDemocrat": 0,
"UKIP": 0,
"Green": 0,
"SNP": 0,
"Other": 0,
"ICouldNotVote": 2,
"Abstained": 0,
"Spoiled": 0,
"Total": 3
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment