|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
body { |
|
margin: 0; |
|
font: 12px sans-serif; |
|
} |
|
.female .registered { |
|
fill: #F8CC98; |
|
} |
|
.female .voted { |
|
fill: #FAA43A; |
|
} |
|
.male .registered { |
|
fill: #A1BFE3; |
|
} |
|
.male .voted { |
|
fill: #5DA5DA; |
|
} |
|
.female .label { |
|
text-anchor: start; |
|
} |
|
.male .label { |
|
text-anchor: end; |
|
} |
|
.label { |
|
font: 11px sans-serif; |
|
fill: #fff; |
|
fill-opacity: .9; |
|
} |
|
.axislabel { |
|
fill: #000; |
|
font: 13px sans-serif; |
|
} |
|
.avg line { |
|
stroke: black; |
|
stroke-dasharray: 2 2; |
|
} |
|
.avg text, .y_label { |
|
fill: black; |
|
text-anchor: middle; |
|
} |
|
.axis, .avg { |
|
shape-rendering: crispEdges; |
|
} |
|
</style> |
|
<body> |
|
<svg width="960" height="500"></svg> |
|
<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> |
|
// no margins |
|
var svg = d3.select('svg'), |
|
width = +svg.attr('width'), |
|
height = +svg.attr('height'); |
|
|
|
// with margins |
|
var pad = { top: 25, right: 65, bottom: 30, left: 65 }, |
|
width = +svg.attr('width') - pad.left - pad.right, |
|
height = +svg.attr('height')- pad.top - pad.bottom; |
|
svg = svg.append('g') |
|
.attr('transform', `translate(${pad.left},${pad.top})`); |
|
|
|
var percent = (d, s) => Math.round(d) + '% ' + (s||''); |
|
|
|
var parse = r => { |
|
r.male = +r.male; |
|
r.female = +r.female; |
|
r.turnout = parseFloat(r.turnout); |
|
r.turnout_f = parseFloat(r.turnout_f); |
|
r.turnout_m = parseFloat(r.turnout_m); |
|
return r; |
|
}; |
|
|
|
d3.csv('data.csv', parse, (err, data) => { |
|
|
|
var size = 420; |
|
var age = d3.scaleBand() |
|
.domain(data.map(d => d.age)) |
|
.range([size, 0]) |
|
.padding(.15); |
|
|
|
var _males = data.map(d => d.male); |
|
var _females = data.map(d => d.female); |
|
var total = d3.sum(_males.concat(_females)); |
|
var maxCount = d3.max(_males.concat(_females)); |
|
|
|
var voted_f = 0; |
|
var voted_m = 0; |
|
data.forEach(d => { |
|
d.voted_f = (d.turnout_f / 100) * d.female; |
|
d.voted_m = (d.turnout_m / 100) * d.male; |
|
voted_f += d.voted_f; |
|
voted_m += d.voted_m; |
|
}); |
|
|
|
var turnout_m = voted_m / d3.sum(_males) * 100; |
|
var turnout_f = voted_f / d3.sum(_females) * 100; |
|
|
|
var count = d3.scaleLinear() |
|
.domain([0, maxCount]) |
|
.range([0, size]); |
|
|
|
var axy = svg.append('g') |
|
.attr('class', 'y_label') |
|
.attr('transform', d => `translate(${width/2},0)`); |
|
|
|
axy.selectAll('.y_label') |
|
.data(data).enter() |
|
.append('text') |
|
.attr('dy', '.34em') |
|
.attr('transform', d => `translate(0,${age(d.age)+age.bandwidth()/2})`) |
|
.text(d => d.age); |
|
|
|
axy.append('text') |
|
.attr('dy', '-.34em') |
|
.text('Age'); |
|
|
|
// === === === === === |
|
|
|
var females = svg.append('g') |
|
.attr('class', 'females') |
|
.attr('transform', d => `translate(${width/2+25},0)`); |
|
|
|
var count_f = d3.scaleLinear() |
|
.domain([0, maxCount]) |
|
.range([0, size]); |
|
|
|
var g = females.selectAll('.female') |
|
.data(data).enter() |
|
.append('g') |
|
.attr('class', 'female') |
|
.attr('transform', d => `translate(0, ${age(d.age)})`); |
|
|
|
g.append('rect') |
|
.attr('class', 'registered') |
|
.attr('height', age.bandwidth()) |
|
.attr('width', d => count_f(d.female)); |
|
|
|
g.append('rect') |
|
.attr('class', 'voted') |
|
.attr('height', age.bandwidth()) |
|
.attr('width', d => count_f(d.voted_f)); |
|
|
|
g.append('text') |
|
.attr('class', 'label') |
|
.attr('dy', '.34em') |
|
.attr('transform', `translate(5,${age.bandwidth() / 2})`) |
|
.text(d => percent(d.turnout_f)); |
|
|
|
females.append('g') |
|
.attr('class', 'axis') |
|
.attr('transform', `translate(0,${size+5})`) |
|
.call(d3.axisBottom().scale(count_f).tickFormat(String).ticks(5)) |
|
.append('text') |
|
.attr('class', 'axislabel') |
|
.attr('x', count_f(maxCount/2)) |
|
.attr('y', 35) |
|
.text('Number of female voters'); |
|
|
|
var avg = females.append('g') |
|
.attr('class', 'avg') |
|
.attr('transform', `translate(${count_f(voted_f/data.length)},0)`); |
|
avg.append('text') |
|
.attr('dy', '-.34em') |
|
.text(percent(turnout_f, 'avg.')); |
|
avg.append('line') |
|
.attr('y2', size+5); |
|
|
|
// === === === === === |
|
|
|
var males = svg.append('g') |
|
.attr('class', 'males') |
|
.attr('transform', d => `translate(${width/2-25},0)`); |
|
|
|
var count_m = d3.scaleLinear() |
|
.domain([maxCount, 0]) |
|
.range([-size, 0]); |
|
|
|
var g = males.selectAll('.male') |
|
.data(data).enter() |
|
.append('g') |
|
.attr('class', 'male') |
|
.attr('transform', d => `translate(0, ${age(d.age)})`); |
|
|
|
g.append('rect') |
|
.attr('class', 'registered') |
|
.attr('x', d => -count_f(d.male)) |
|
.attr('height', age.bandwidth()) |
|
.attr('width', d => count_f(d.male)); |
|
|
|
g.append('rect') |
|
.attr('class', 'voted') |
|
.attr('x', d => -count_f(d.voted_m)) |
|
.attr('height', age.bandwidth()) |
|
.attr('width', d => count_f(d.voted_m)); |
|
|
|
g.append('text') |
|
.attr('class', 'label') |
|
.attr('dy', '.34em') |
|
.attr('transform', `translate(-5,${age.bandwidth() / 2})`) |
|
.text(d => percent(d.turnout_m)); |
|
|
|
males.append('g') |
|
.attr('class', 'axis') |
|
.attr('transform', `translate(0,${size+5})`) |
|
.call(d3.axisBottom().scale(count_m).tickFormat(String).ticks(5)) |
|
.append('text') |
|
.attr('class', 'axislabel') |
|
.attr('x', count_m(maxCount/2)) |
|
.attr('y', 35) |
|
.text('Number of male voters'); |
|
|
|
var avg = males.append('g') |
|
.attr('class', 'avg') |
|
.attr('transform', `translate(${-count_f(voted_m/data.length)},0)`); |
|
avg.append('text') |
|
.attr('dy', '-.34em') |
|
.text(percent(turnout_m, 'avg.')); |
|
avg.append('line') |
|
.attr('y2', size+5); |
|
|
|
}); |
|
</script> |