The chart below shows the flows of money to Toronto mayoral candidates in 2006. What follows is a quick explanation and a few observations. Then I follow up with a few short tips on how I got the visualization up and running.
2006 Toronto Election Contributions
By Region, Dollar Amounts and Candidate
[iframe width=”600″ height=”520″ src=”https://zenbot.ca/elections.html”]
Source: City of Toronto
Note that I was coding anything ‘Outside Toronto’ to be more specific and got part-way (you can see Kingston and Ottawa as some locations). Basically outside Toronto extends to Mississauga, Oakville and the Golden Horseshoe. It was possible to get more specific but I didn’t for this visualization. ‘Central Toronto’ seems to be not downtown, but includes Yonge/Eglington, etc.
You can also see that proportionally, Stephen LeDrew received a relatively large amount of corporate donations (the orange links) while David Miller received none. You can also see that David Miller received money, not only from downtown, but everywhere. You can also see that the vast majority of the money is coming from individuals (blue) versus corporations (orange).
If I were going to push the analysis further, I could get number of donors per candidate. I would also have loved to get 2009, as it is more recent, but like I mentioned in part 1 that wasn’t available through the city of Toronto’s website. I am sure comparing 2006 to 2009 would have been very interesting even if the candidates are completely different.
To get the visualization working, you not only need the latest D3.js library, but also the sankey.js plugin which should both be included in your header:
script type="text/javascript" src="js/d3.v3.min.js" charset="utf-8">/script>
script type="text/javascript" src="js/sankey.js" charset="utf-8">/script>
Next, I added some in-line styling:
.link {
fill: none;
stroke-opacity: 0.4;
}
.link:hover{
stroke-opacity: 0.6;
}
svg {
font: 12px sans-serif;
}
In the main body, you need something to attach the svg chart to. In this case I picked the following:
h1 id="chart"
And finally the main bulk of the code. If you run into problems, please feel free to comment, below.
//a big thank you to Mike Bostock. Most of this code is originally his
//// modified for the purposes of this demonstration
var margin = {top: 10, right: 1, bottom: 6, left: 1},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return "$" + formatNumber(d); },
color = d3.scale.category20();
var svg = d3.select("#chart").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 sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
////this colarray is to avoid going into the JSON document to change the colors of the link
var colarray = {
'Individual': '17,203,235',
'Corporation': '252,189,53'
}
var path = sankey.link();
/////////////here is where the sankey should kick in....
d3.json("js/electionJSON.json", function(election) {
sankey
.nodes(election.nodes)
.links(election.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(election.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
// .style("stroke-width", "100")
.sort(function(a, b) { return b.dy - a.dy; })
.style("stroke",function(d) { return "rgb(" + colarray[d.contribution_type] +")"; })
link.append("title")
.text(function(d) { return d.source.name + " ā " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(election.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
});