|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
text { |
|
font: 12px sans-serif; |
|
fill: #555; |
|
} |
|
text.title { |
|
font-size: 14px; |
|
} |
|
.avg { |
|
stroke: #555; |
|
stroke-width: .75; |
|
} |
|
.dot { |
|
stroke: white; |
|
stroke-width: .75; |
|
} |
|
.dot-2003 { fill: #EFCCFF; } |
|
.dot-2007 { fill: #E6B2FF; } |
|
.dot-2009 { fill: #DD99FF; } |
|
.dot-2013 { fill: #D57FFF; } |
|
.dot-2016 { fill: #CC66FF; } |
|
|
|
.axis-y line { |
|
stroke: #ddd; |
|
} |
|
.axis-y .zero { |
|
stroke: #aaa; |
|
stroke-dasharray: none; |
|
} |
|
.axis-y path, |
|
.axis-x path { |
|
display: none; |
|
} |
|
body:hover .err, |
|
body:hover .have-err { |
|
stroke: black; |
|
} |
|
</style> |
|
<body> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.js"></script> |
|
<script src="//d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
var margin = {top: 40, right: 10, bottom: 20, left: 40}, |
|
width = 470 - margin.left - margin.right, |
|
height = 220 - margin.top - margin.bottom; |
|
|
|
var x = d3.scaleBand().rangeRound([0, width]).padding(0.2), |
|
y = d3.scaleLinear().rangeRound([height, 0]); |
|
|
|
var voters = {}; |
|
|
|
d3.csv('registry.csv', (err, registry) => { |
|
registry.forEach(d => { |
|
var sum = 0; |
|
registry.columns.slice(1).forEach(c => { |
|
if (!voters[d.year]) { voters[d.year] = {}; } |
|
voters[d.year][c] = +d[c]; |
|
sum += voters[d.year][c]; |
|
}); |
|
voters[d.year].sum = sum; |
|
}); |
|
|
|
d3.csv('partybias.csv', (err, table) => { |
|
table.forEach(d => { |
|
d.year = +d.year; |
|
d.votes = +d.votes; |
|
d.votes_total = +d.votes_total; |
|
d.votes_year = voters[d.year].sum; |
|
d.votes_c = voters[d.year][d.constituency]; |
|
}) |
|
|
|
plot(data(table.filter(d => d.party === 'B')), |
|
'Framsóknarflokkurinn'); |
|
plot(data(table.filter(d => d.party === 'D')), |
|
'Sjálfstæðisflokkurinn'); |
|
plot(data(table.filter(d => d.party === 'V')), |
|
'Vinstrihreyfingin - grænt framboð'); |
|
plot(data(table.filter(d => d.party === 'S')), |
|
'Samfylkingin'); |
|
|
|
|
|
// draw a legend |
|
var svg = d3.select("body").append("svg") |
|
.attr("width", (width + margin.left + margin.right)*2) |
|
.attr("height", 50); |
|
|
|
var legend = svg.selectAll('g') |
|
.data(registry.map(d => d.year)) |
|
.enter().append('g'); |
|
|
|
legend.append('text') |
|
.text(String) |
|
.call(sel => { |
|
var offs = 0; |
|
sel.each((s, i, t) => { |
|
d3.select(t[i].parentNode) |
|
.attr('transform', `translate(${margin.left + offs},30)`); |
|
offs += t[i].getComputedTextLength() + 30; |
|
}); |
|
}); |
|
|
|
legend.append('circle') |
|
.attr("class", d => `dot dot-${d}`) |
|
.attr("r", x.bandwidth() / 15) |
|
.attr("cx", -x.bandwidth() / 7.5) |
|
.attr("cy", -5); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
function data (data, log) { |
|
var getVal = d => (d.votes / d.votes_c); |
|
var _data = d3.nest() |
|
.key(d => d.constituency) |
|
.key(d => d.year) |
|
.entries(data); |
|
_data.forEach(d => { |
|
var val = []; |
|
d.values.forEach(h => { |
|
var _v = h.values.map(getVal); |
|
h.total = d3.sum(_v); |
|
h.mean = d3.mean(_v); |
|
val = val.concat(_v); |
|
}) |
|
d.total = d3.sum(val); |
|
d.mean = d3.mean(val); |
|
}); |
|
return _data; |
|
} |
|
|
|
function plot ( data, title ) { |
|
var max_val = .4; |
|
var min_val = 0; |
|
|
|
y.domain([ min_val, max_val ]); |
|
x.domain(data.map(d => d.key)); |
|
|
|
var svg = d3.select("body").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})`); |
|
|
|
svg.append("g") |
|
.attr("class", "axis axis-x") |
|
.attr("transform", `translate(0,${height})`) |
|
.call(d3.axisBottom(x).tickSize(3)); |
|
|
|
svg.append("g") |
|
.attr("class", "axis axis-y") |
|
.call(d3.axisLeft(y).tickSize(-width).ticks(5, "%")) |
|
.selectAll('line') |
|
.attr('class', d => d ? '' : 'zero'); |
|
|
|
svg.append("text") |
|
.attr("class", "title") |
|
.attr("x", width/2) |
|
.attr("y", -18) |
|
.attr("dy", ".32em") |
|
.attr("text-anchor", "middle") |
|
.text(title); |
|
|
|
var polls = svg.selectAll(".dots") |
|
.data(data).enter() |
|
.append("g") |
|
.attr("class", "dots") |
|
.attr("transform", d => `translate(${x(d.key)},0)`) |
|
.selectAll(".dot") |
|
.data(p => p.values) |
|
.enter(); |
|
|
|
polls.append('circle') |
|
.attr("class", d => `dot dot-${d.key}`) |
|
.attr("r", x.bandwidth() / 15) |
|
.attr("cx", (d, i, p) => { |
|
var b = x.bandwidth(); |
|
return b / 2 + ( i + -( p.length - 1) / 2 ) * b / 5 |
|
}) |
|
.attr("cy", d => y(d.mean)); |
|
|
|
svg.selectAll(".avg") |
|
.data(data).enter() |
|
.append("line") |
|
.attr("class", "avg") |
|
.attr("x1", d => x(d.key)) |
|
.attr("y1", d => y(d.mean)) |
|
.attr("x2", d => x(d.key) + x.bandwidth()) |
|
.attr("y2", d => y(d.mean)) |
|
|
|
} |
|
</script> |