Czechia, 10-11/2019
<!DOCTYPE html>
<html lang="cs">
<meta charset="utf-8" />
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Kalam&display=swap" rel="stylesheet">
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<style>
html {
font-family: 'Kalam', cursive;
}
.weak {
opacity: 0.5
}
</style>
<script>
let bins = 6
let binLabels = ['Koalice']
var margin = {top: 100, right: 30, bottom: 100, left: 60}
var svg = d3.select("svg")
.attr('width', 1500 + margin.left + margin.right)
.attr('height', 300 + margin.top + margin.bottom),
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.domain([0, 100])
.range([0, width]),
y = d3.scaleLinear()
.domain([0, 8])
.range([height, 0])
function age2y(age) {
return parseInt(age[0])
}
function addProperties( obj, arr , property ) {
let merged = obj
arr.forEach((item, i) => {
if (item[property] == obj[property]) {
merged = {...obj, ...item}
// console.log(merged)
}
});
return merged
}
function compare( a, b, property, reverse=1 ) {
if ( a[property] < b[property] ){
return -1 * reverse;
}
if ( a[property] > b[property] ){
return 1 * reverse;
}
return 0;
}
function rebin(obj) {
let reobj = obj
if (parseInt(obj.bin) == 1 && obj.strength == 'weak') {
reobj.bin = 2
}
if (parseInt(obj.bin) == 5 && obj.strength == 'weak') {
reobj.bin = 4
}
reobj.bin = parseInt(obj.bin)
return reobj
}
function getAgeGroups(data) {
let ag = []
data.forEach((item, i) => {
if (!ag.includes(item.age)) {
ag.push(item.age)
}
});
return ag
}
d3.csv("data200_2.csv", function(error, data) {
d3.csv("parties.csv", function(e, parties) {
if (error) throw error;
var g = svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// PREPARE DATA
// join data, re-bin weak data
let displayData = data.map(function (obj) {
// console.log(addProperties(obj, parties, 'party'))
return rebin(addProperties(obj, parties, 'party'))
})
// order data
displayData.sort((a, b) => compare(a, b, 'order', -1))
displayData.sort((a, b) => compare(a, b, 'age', -1))
displayData.sort((a, b) => compare(a, b, 'strength'))
displayData.sort((a, b) => compare(a, b, 'bin'))
// get age groups
ageGroups = getAgeGroups(displayData)
// indexes
indexes = {}
ageGroups.forEach((item, i) => {
indexes[item] = {}
for (i = 1; i <= bins; i++) {
indexes[item][i] = 0
}
});
// prepare points
let points = []
let lines = [18, 25, 35, 47, 65]
let centers = [lines[0] / 2]
for (i = 0; i < (bins - 2); i++) {
centers.push((lines[i] + lines[i + 1]) / 2)
}
centers.push((lines[i] + 90) / 2)
displayData.forEach((item, i) => {
for (i = 0; i < parseInt(item['value']); i++) {
let newItem = Object.assign({}, item)
if (item.bin == 1) {
newItem.x = lines[0] - indexes[item.age][item.bin]
}
if (item.bin == 2) {
newItem.x = lines[1] - indexes[item.age][item.bin]
}
if (item.bin == 3) {
newItem.x = (lines[2] + lines[1]) / 2 + ((indexes[item.age][item.bin] % 2) * 2 - 1) * Math.round(indexes[item.age][item.bin] / 2)
}
if (item.bin == 4) {
newItem.x = lines[2] + 1 + indexes[item.age][item.bin]
}
if (item.bin == 5) {
newItem.x = lines[3] + 1 + indexes[item.age][item.bin]
}
if (item.bin == 6) {
newItem.x = lines[4] + 1 + indexes[item.age][item.bin]
}
indexes[item.age][item.bin]++
newItem.y = age2y(item.age)
points.push(newItem)
}
})
// DISPLAY points
g.selectAll(".icon")
.data(points)
.enter()
.append("text")
.attr('font-family', 'FontAwesome')
.attr('font-size', 30)
.attr('fill', function(d) {return d.color})
.attr('class', function(d) { return d.strength })
.attr('stroke-width', 1)
.attr('stroke', '#aaa')
.attr('text-anchor',"middle")
.attr('x',function(d) {return x(d.x)})
.attr('y',function(d) {return y(d.y)})
.text('\uf183')
// display counts
counts = []
for (i = 0; i < bins; i++) {
c = {
'count': 0,
'x': centers[i]
}
counts.push(c)
}
for(var a in indexes) {
for(var b in indexes[a]) {
counts[b - 1].count = counts[b - 1].count + indexes[a][b]
}
}
svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.selectAll(".counts")
.data(counts)
.enter()
.append("text")
.attr("x", function(d) { return x(d.x) })
.attr("y", function() { return y(-1) })
.attr('text-anchor',"middle")
.attr("font-size", 48)
.attr("font-weight", "bold")
.attr("fill", function(d, i) {
if (i == counts.length - 1) {
return "gray"
} else {
return "green"
}
})
.text(function(d) { return d.count })
// bins
let dlines = lines
dlines[0]++
dlines[1]++
svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.selectAll(".bins")
.data(dlines)
.enter()
.append("line")
.attr("x1", function(d) { return x(d) })
.attr("y1", function() { return y(-1)})
.attr("x2", function(d) { return x(d) })
.attr("y2", function() { return y(10)})
.attr("stroke", "green")
.attr("stroke-width", 1)
.attr("stroke-dasharray", 4)
// display ages
svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.selectAll(".ages")
.data(ageGroups)
.enter()
.append("text")
.attr("x", function() { return x(0) })
.attr("y", function(d, i) { return y(ageGroups.length - i) })
.attr('text-anchor',"end")
.attr("font-size", 24)
.text(function(d) { return d })
svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.selectAll(".ages")
.data(ageGroups)
.enter()
.append("line")
.attr("x1", function() { return x(-2) })
.attr("y1", function(d, i) { return y(ageGroups.length - i - 0.2)})
.attr("x2", function() { return x(100) })
.attr("y2", function(d, i) { return y(ageGroups.length - i - 0.2)})
.attr("stroke", "#aaaaaa")
.attr("stroke-width", 1)
.attr("stroke-dasharray", 4)
svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("line")
.attr("x1", function() { return x(lines[lines.length - 1]) })
.attr("y1", function() { return y(-1)})
.attr("x2", function() { return x(lines[lines.length - 1]) })
.attr("y2", function() { return y(10)})
.attr("stroke", "#aaaaaa")
.attr("stroke-width", 5)
})
})
</script>
</html>
party,strength,age,value
ČSSD,core,18-19,0
ČSSD,core,20-29,0
ČSSD,core,30-39,1
ČSSD,core,40-49,2
ČSSD,core,50-59,2
ČSSD,core,60-69,2
ČSSD,core,70-79,2
ČSSD,core,80-…,0
ČSSD,weak,18-19,0
ČSSD,weak,20-29,0
ČSSD,weak,30-39,1
ČSSD,weak,40-49,2
ČSSD,weak,50-59,2
ČSSD,weak,60-69,2
ČSSD,weak,70-79,1
ČSSD,weak,80-…,0
ODS,core,18-19,0
ODS,core,20-29,1
ODS,core,30-39,3
ODS,core,40-49,4
ODS,core,50-59,3
ODS,core,60-69,2
ODS,core,70-79,1
ODS,core,80-…,1
ODS,weak,18-19,0
ODS,weak,20-29,1
ODS,weak,30-39,2
ODS,weak,40-49,3
ODS,weak,50-59,2
ODS,weak,60-69,2
ODS,weak,70-79,1
ODS,weak,80-…,0
KSČM,core,18-19,0
KSČM,core,20-29,1
KSČM,core,30-39,1
KSČM,core,40-49,1
KSČM,core,50-59,2
KSČM,core,60-69,3
KSČM,core,70-79,5
KSČM,core,80-…,2
KSČM,weak,18-19,0
KSČM,weak,20-29,0
KSČM,weak,30-39,0
KSČM,weak,40-49,0
KSČM,weak,50-59,0
KSČM,weak,60-69,1
KSČM,weak,70-79,1
KSČM,weak,80-…,1
KDU-ČSL,core,18-19,0
KDU-ČSL,core,20-29,1
KDU-ČSL,core,30-39,1
KDU-ČSL,core,40-49,1
KDU-ČSL,core,50-59,1
KDU-ČSL,core,60-69,1
KDU-ČSL,core,70-79,1
KDU-ČSL,core,80-…,1
KDU-ČSL,weak,18-19,0
KDU-ČSL,weak,20-29,0
KDU-ČSL,weak,30-39,0
KDU-ČSL,weak,40-49,1
KDU-ČSL,weak,50-59,0
KDU-ČSL,weak,60-69,1
KDU-ČSL,weak,70-79,0
KDU-ČSL,weak,80-…,1
TOP 09,core,18-19,0
TOP 09,core,20-29,1
TOP 09,core,30-39,1
TOP 09,core,40-49,1
TOP 09,core,50-59,0
TOP 09,core,60-69,0
TOP 09,core,70-79,0
TOP 09,core,80-…,0
TOP 09,weak,18-19,0
TOP 09,weak,20-29,1
TOP 09,weak,30-39,1
TOP 09,weak,40-49,1
TOP 09,weak,50-59,0
TOP 09,weak,60-69,0
TOP 09,weak,70-79,0
TOP 09,weak,80-…,0
ANO,core,18-19,1
ANO,core,20-29,4
ANO,core,30-39,6
ANO,core,40-49,7
ANO,core,50-59,7
ANO,core,60-69,9
ANO,core,70-79,8
ANO,core,80-…,2
ANO,weak,18-19,1
ANO,weak,20-29,1
ANO,weak,30-39,2
ANO,weak,40-49,3
ANO,weak,50-59,3
ANO,weak,60-69,3
ANO,weak,70-79,3
ANO,weak,80-…,1
Piráti,core,18-19,1
Piráti,core,20-29,4
Piráti,core,30-39,3
Piráti,core,40-49,2
Piráti,core,50-59,1
Piráti,core,60-69,0
Piráti,core,70-79,0
Piráti,core,80-…,0
Piráti,weak,18-19,1
Piráti,weak,20-29,6
Piráti,weak,30-39,5
Piráti,weak,40-49,3
Piráti,weak,50-59,1
Piráti,weak,60-69,1
Piráti,weak,70-79,0
Piráti,weak,80-…,0
SPD,core,18-19,0
SPD,core,20-29,1
SPD,core,30-39,1
SPD,core,40-49,1
SPD,core,50-59,1
SPD,core,60-69,1
SPD,core,70-79,0
SPD,core,80-…,0
SPD,weak,18-19,0
SPD,weak,20-29,0
SPD,weak,30-39,1
SPD,weak,40-49,1
SPD,weak,50-59,1
SPD,weak,60-69,1
SPD,weak,70-79,0
SPD,weak,80-…,0
STAN,core,18-19,0
STAN,core,20-29,0
STAN,core,30-39,1
STAN,core,40-49,0
STAN,core,50-59,0
STAN,core,60-69,0
STAN,core,70-79,0
STAN,core,80-…,0
STAN,weak,18-19,0
STAN,weak,20-29,0
STAN,weak,30-39,1
STAN,weak,40-49,1
STAN,weak,50-59,1
STAN,weak,60-69,1
STAN,weak,70-79,0
STAN,weak,80-…,0
Ostatní/neví,core,18-19,0
Ostatní/neví,core,20-29,0
Ostatní/neví,core,30-39,0
Ostatní/neví,core,40-49,0
Ostatní/neví,core,50-59,0
Ostatní/neví,core,60-69,0
Ostatní/neví,core,70-79,0
Ostatní/neví,core,80-…,0
Ostatní/neví,weak,18-19,0
Ostatní/neví,weak,20-29,5
Ostatní/neví,weak,30-39,4
Ostatní/neví,weak,40-49,4
Ostatní/neví,weak,50-59,2
Ostatní/neví,weak,60-69,3
Ostatní/neví,weak,70-79,2
Ostatní/neví,weak,80-…,0
Nevoliči,weak,18-19,5
Nevoliči,weak,20-29,20
Nevoliči,weak,30-39,26
Nevoliči,weak,40-49,29
Nevoliči,weak,50-59,22
Nevoliči,weak,60-69,19
Nevoliči,weak,70-79,11
Nevoliči,weak,80-…,7
party,color,side,order,bin
ANO,#261060,left,1,1
KSČM,#8c0000,left,2,1
ČSSD,#F07D00,left,6,1
SPD,#ea2329,middle1,3,3
Ostatní/neví,#888888,middle1,1,3
ODS,#004494,right,5,5
KDU-ČSL,#e6ac21,right,4,5
TOP 09,#723769,right,3,5
STAN,#5d8c00,right,2,5
Piráti,#000000,right,1,5
Nevoliči,#888888,,0,6