a rough prototype for the Close Votes UK election datavis design exercise
<html>
<head>
<script src="https://d3js.org/d3.v3.js"></script>
<script src="https://d3js.org/queue.v1.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Quicksand: 400, 700' rel='stylesheet' type='text/css'>
<meta property="og:title" content="CLOSE VOTES UK">
<meta property="og:image" content="//tulpinteractive.com/projects/close-votes/close_votes_large.png" />
<meta property="og:description" content="Based on the 2015 parliamentary elections in United Kingdom, this visualization shows which cities distribute their votes similarly over the political parties" />
<title>CLOSE VOTES UK</title>
<style>
/*body { background-color: #DFCCA5; }*/
body {
font-family: 'Quicksand', sans-serif;
font-size: 11px;
text-align: center;
background: rgb(240,249,255); /* Old browsers */
background: -moz-radial-gradient(center, ellipse cover, rgba(240,249,255,1) 0%, rgba(203,235,255,1) 47%, rgba(174, 218, 288,1) 100%); /* FF3.6+ */
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(240,249,255,1)), color-stop(47%,rgba(203,235,255,1)), color-stop(100%,rgba(174, 218, 288,1))); /* Chrome,Safari4+ */
background: -webkit-radial-gradient(center, ellipse cover, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(174, 218, 288,1) 100%); /* Chrome10+,Safari5.1+ */
background: -o-radial-gradient(center, ellipse cover, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(174, 218, 288,1) 100%); /* Opera 12+ */
background: -ms-radial-gradient(center, ellipse cover, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(174, 218, 288,1) 100%); /* IE10+ */
background: radial-gradient(ellipse at center, rgba(240,249,255,1) 0%,rgba(203,235,255,1) 47%,rgba(174, 218, 288,1) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f0f9ff', endColorstr='#a1dbff',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
}
ul { list-style: none; padding: 0; margin: 0; display: inline-block; position: absolute; z-index: 100; right: 0px; top: 30px; }
select { position: absolute; right: 0px; top: 924px; }
a:hover { color: #F9786C; text-decoration: underline; }
h1 { font-size: 32px; margin-bottom: 0;}
h2 { font-size: 18px; margin-top: 0; margin-bottom: 0; }
h4 { margin: 0 auto; font-weight: normal; width: 500px; }
li { text-align: left; }
li span { width: 50px; display: inline-block; text-align: right; margin-right: 5px; }
.selected { color: #000; font-weight: bold; text-decoration: none; cursor: default; }
.selected:hover { text-decoration: none; color: #000; }
.notselected { font-weight: normal; text-decoration: underline; cursor: pointer; color: #91B6D4; }
.notselected:hover { color: #F9786C; }
#viz { width: 100%; }
#selectorcontainer { width: 1000px; text-align: right; margin: auto auto; position: relative; }
#credit { position: absolute; top: 924px; left: 0; font-size: 9px; }
#credit a, #credit a:link, #credit a:active { font-weight: normal; text-decoration: none; cursor: pointer; color: #000; }
#credit a:hover { color: #F9786C; }
</style>
</head>
<body>
<h1>CLOSE VOTES</h1>
<h2>Which cities vote like yours?</h2>
<h4>Based on the 2015 parliamentary elections in The United Kingdom, this visualization shows which cities distribute their votes similarly over the political parties</h4>
<div id="selectorcontainer">
<ul>
<li><span>size:</span><a href="Javascript: void(0);" id="size_by_pop" class="notselected">population</a> / <a href="Javascript: void(0);" id="size_by_similarity" class="selected">similarity</a></li>
<li><span>layout:</span><a href="Javascript: void(0);" id="layout_radial" class="notselected">radial</a> / <a href="Javascript: void(0);" id="layout_geo" class="selected">geographical</a></li>
</ul>
<span id="credit">inspired by <a href="//tulpinteractive.com">TULP interactive</a></span>
</div>
<div id="viz"></div>
<script type="text/javascript">
queue()
.defer(d3.json, 'data.json')
.defer(d3.json, 'parties.json')
.await(vis); // function that uses files
function vis(error, data, parties) {
var index = Math.round(Math.random() * data.length),
w = 1000,
h = 1000;
// create the drop down menu of cities
var selector = d3.select("#selectorcontainer")
.append("select")
.attr("id", "metroselector")
.selectAll("option")
.data(data)
.enter().append("option")
.text(function(d) { return d.metro; })
.attr("value", function (d, i) {
return i;
});
d3.select("#metroselector").property("selectedIndex", index);
//27861
var rl = d3.scale.linear().domain([0, 75]).range([0, w / 2 - 60]);
var rs = d3.scale.log().domain([1, 74]).range([0, w / 2 - 60]);
var rr = d3.scale.linear().domain([Math.sqrt(1 / Math.PI), Math.sqrt(15 / Math.PI), Math.sqrt(74 / Math.PI)]).range([22, 5, 2]);
//var rr = d3.scale.linear().domain([Math.sqrt(1 / Math.PI), Math.sqrt(74 / Math.PI)]).range([22, 2])
var rpopulation = d3.scale.linear().domain(d3.extent(data, function(d) { return Math.sqrt(d.pop / Math.PI); })).range([2, 40])
// set the domain equal to the observed min and max in the results.json dataset
var ro = d3.scale.linear().domain([68,316] /* d3.extent(data, function(d) { return d.opk; }) */ ).range([2, 70])
var c = d3.scale.log().domain([1, 20, 74]).range(["#F9786C", "#41415F", "#193244"]);
var axes = [
{ 'label': 'very similar', 'value': 15 },
{ 'label': 'similar', 'value': 30 },
{ 'label': 'different', 'value': 45 },
{ 'label': 'very different', 'value': 60 },
{ 'label': 'extremely different', 'value': 75}
];
var rfuncs = [
function(d, i) { return rpopulation(Math.sqrt(d.pop / Math.PI)); }, // population
function(d, i) { return rr(Math.sqrt(d['eud'][index] / Math.PI) == 0 ? 1 : Math.sqrt(d['eud'][index] / Math.PI)); }, // similarity
// hardcoding a common value for opk from the results.json dataset
// want to find out how to calculate 'opk' for the new dataset
function(d, i) { return ro(80/*d.opk*/); } // show up
]
var rfunc = rfuncs[1];
var layout = "geo";
var size = "sim";
var svg = d3.select("#viz").append("svg")
.attr("width", w)
.attr("height", h);
var merc = d3.geo.mercator();
merc.translate([140, 3100]);
merc.scale(2900);
var selectedmetro = svg.append("text")
.attr("id", "selectedmetro")
.attr("x", 74)
.attr("y", 30)
.text(data[index]['metro'])
.style("fill", "#000")
.style("text-anchor", "start")
.style("font-family", "Quicksand")
.style("font-size", "24px");
var selectors = svg.append("foreignObject")
.attr("transform", "translate(200, 200)")
.append("body")
.append("ul");
selectors.append("li");
selectors.append("li");
var g = svg.append('g')
.attr('transform', 'translate(' + w / 2 + ', ' + h / 2 + ')');
var arc = d3.svg.arc()
.outerRadius(function(d) { return rl(d.value); })
.startAngle(0)
.endAngle(2 * Math.PI);
var ga = g.append("g")
.attr("id", "axisgroup")
.style("opacity", 0);
ga.selectAll(".axispath")
.data(axes)
.enter().append("path")
.attr("id", function(d, i) { return "axispath" + i; })
.attr("class", "axispath")
.attr("d", arc)
.style("stroke", "#91B6D4")
.style("fill", "none")
.style("opacity", 0.6);
ga.selectAll(".axislabel")
.data(axes)
.enter().append("text")
.attr("class", "axislabel")
.attr("dy", -5)
.attr("dx", 0)
.style("fill", "#91B6D4")
.style("font-size", "11px")
.style("text-anchor", "middle")
.append("textPath")
.attr("xlink:href", function(d, i) { return "#axispath" + i; })
.attr("startOffset", "40%")
.text(function(d, i) { return d.label; })
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
data.forEach(function(d, i) {
d.s = i;
})
d3.select("#metroselector")
.on("change", function(d) {
index = this.value;
update();
})
d3.select("#size_by_pop")
.on("click", function() {
rfunc = rfuncs[0];
circle.each(function(d, i) {
d3.select(this)
.transition()
.duration(800)
.attr('r', rfunc)
})
d3.select(this)
.attr("class", "selected")
d3.select("#size_by_similarity")
.attr("class", "notselected")
size = "pop";
})
d3.select("#size_by_similarity")
.on("click", function() {
rfunc = rfuncs[1];
circle.each(function(d, i) {
d3.select(this)
.transition()
.duration(800)
.attr('r', rfunc)
})
d3.select(this)
.attr("class", "selected")
d3.select("#size_by_pop")
.attr("class", "notselected")
size = "sim";
})
d3.select("#layout_radial")
.on("click", function() {
layout = "radial";
d3.selectAll(".metro")
.transition()
.duration(1300)
.attr("cy", 0)
.attr("cx", function(d, i) { return rs(d['eud'][index] == 0 ? 1 : d['eud'][index])})
.attr("transform", function(d, i) { return "rotate(" + d.s/data.length * 360 + " 0 0)"; })
d3.selectAll("#axisgroup")
.transition()
.duration(1300)
.style("opacity", 1)
d3.select(this)
.attr("class", "selected")
d3.select("#layout_geo")
.attr("class", "notselected")
})
d3.select("#layout_geo")
.on("click", function() {
layout = "geo";
d3.selectAll(".metro")
.transition()
.duration(1300)
.attr('cy', function(d) { return merc([d['long'], d['lat']])[1]; })
.attr('cx', function(d) { return merc([d['long'], d['lat']])[0]; })
.attr("transform", function(d, i) { return "rotate(0 0 0)"; })
d3.selectAll("#axisgroup")
.transition()
.duration(1300)
.style("opacity", 0)
d3.select(this)
.attr("class", "selected")
d3.select("#layout_radial")
.attr("class", "notselected")
})
function update() {
if (layout == "radial") {
d3.selectAll('.metro')
.transition()
.duration(800)
.attr("cx", function(d, i) { return rs(d['eud'][index] == 0 ? 1 : d['eud'][index])})
.attr('r', rfunc)
.style('fill', function(d, i) { return index == d.s ? "#F9786C" : c(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
} else {
d3.selectAll('.metro')
.transition()
.duration(800)
.attr('r', rfunc)
.style('fill', function(d, i) { return index == d.s ? "#F9786C" : c(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
}
d3.selectAll(".label")
.remove();
d3.select("#selectedmetro")
.text(data[index]['metro'])
bar
.data(data[index]['voteShare'])
.transition()
.duration(800)
.attr("width", function(d) { return bs(d); })
}
var circle = g.selectAll('.metro')
.data(data)
.enter().append('circle')
.attr('class', 'metro')
.attr('r', rfunc)
.attr('cy', function(d) { return merc([d['long'], d['lat']])[1]; })
.attr('cx', function(d) { return merc([d['long'], d['lat']])[0]; })
.style('fill', function(d, i) { return index == d.s ? "#F9786C" : c(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
.style('stroke', 'white')
.style('stroke-opacity', 0.3)
.style("opacity", 0.9)
.on("click", function(d, ind) {
console.log(d);
d3.selectAll('.metro').each(function(d, ind) {
if (d.s == index) {
d3.select(this)
.moveToFront()
}
})
index = ind;
d3.select("#metroselector").property("selectedIndex", index);
update();
})
.on("mouseover", function(d, i) {
var labelbackground = d3.select(this.parentNode)
.append('text')
.attr('class', 'label')
.style('text-anchor', 'middle')
.text(function() { return d.metro; })
.style('font-family', "'Quicksand', sans-serif")
.style('font-size', '16px')
.style('font-weight', 'bold')
.style('stroke', 'rgb(240,249,255)')
.style('stroke-width', 3.5)
.style('stroke-opacity', 0.6)
.style('filter', 'url:(#dropshadow)')
.attr('dy', function() { return size == "pop" ? -1 * rpopulation(Math.sqrt(d.pop / Math.PI)) - 5 : -1 * rr(Math.sqrt(d['eud'][index] / Math.PI) == 0 ? 1 : Math.sqrt(d['eud'][index] / Math.PI)) - 5; })
.style('fill', 'none')
var labelforeground = d3.select(this.parentNode)
.append('text')
.attr('class', 'label')
.style('text-anchor', 'middle')
.text(function() { return d.metro; })
.style('font-family', "'Quicksand', sans-serif")
.style('font-size', '16px')
.style('font-weight', 'bold')
.attr('dy', function() { return size == "pop" ? -1 * rpopulation(Math.sqrt(d.pop / Math.PI)) - 5 : -1 * rr(Math.sqrt(d['eud'][index] / Math.PI) == 0 ? 1 : Math.sqrt(d['eud'][index] / Math.PI)) - 5; })
.style('fill', '#000')
if (layout == "geo") {
labelbackground
.attr('y', function() { return merc([d['long'], d['lat']])[1]; })
.attr('x', function() { return merc([d['long'], d['lat']])[0]; })
labelforeground
.attr('y', function() { return merc([d['long'], d['lat']])[1]; })
.attr('x', function() { return merc([d['long'], d['lat']])[0]; })
} else {
labelbackground
.attr('x', function() { return index == i ? 0 : Math.cos(d.s/data.length * 2 * Math.PI) * rs(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
.attr('y', function() { return index == i ? 0 : Math.sin(d.s/data.length * 2 * Math.PI) * rs(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
labelforeground
.attr('x', function() { return index == i ? 0 : Math.cos(d.s/data.length * 2 * Math.PI) * rs(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
.attr('y', function() { return index == i ? 0 : Math.sin(d.s/data.length * 2 * Math.PI) * rs(d['eud'][index] == 0 ? 1 : d['eud'][index]); })
}
bg.selectAll(".marker")
.data(data[d.s]["voteShare"])
.enter().append("line")
.attr("class", "marker")
.attr("x1", function(d) { return 5 + bs(d); })
.attr("y1", function(d, i) { return i * 11; })
.attr("x2", function(d) { return 5 + bs(d); })
.attr("y2", function(d, i) { return 10 + i * 11; })
.style("stroke", "#000")
.style("opacity", 0.8)
})
.on("mouseout", function(d, i) {
d3.selectAll('.label')
.remove()
d3.selectAll(".marker")
.remove()
});
var bs = d3.scale.linear()
.domain([0, 100])
.range([0, 200]);
var bg = svg.append("g")
.attr("transform", "translate(200, 50)")
var party = bg.selectAll(".party")
.data(parties) // ["VVD", "PvdA", "PVV", "SP", "CDA", "D66", "CU", "GrLinks", "SGP", "PvdD", "50+"]
.enter().append("text")
.attr("class", "party")
.attr("x", 0)
.attr("y", function(d, i) { return 8 + i * 11; })
.text(String)
.style("text-anchor", "end")
.style("fill", "#91B6D4")
var bar = bg.selectAll("rect")
.data(data[index]["voteShare"])
.enter().append("rect")
.attr("x", 5)
.attr("y", function(d, i) { return i * 11; })
.attr("height", 10)
.attr("width", function(d) { return bs(d); })
.style("fill", "#91B6D4")
};
</script>
<script>
/*
var _gaq=[['_setAccount','UA-27449759-1'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
*/
</script>
</body>
</html>
[
"Conservative",
"Labour",
"Scottish National Party",
"Democratic Unionist Party",
"Liberal Democrat",
"Sinn Fein",
"Plaid Cymru",
"Social Democratic & Labour Party",
"Ulster Unionist Party",
"Green Party",
"Independent",
"Other",
"UKIP",
"Alliance Party",
"Cannabis Is Safer Than Alcohol",
"Christian Peoples Alliance",
"English Democrats",
"Monster Raving Loony Party",
"TUSC"
]