block by michalskop 0f6fe50f727499421a44fa80aef74ab5

CZ: support by age

Full Screen

Distribution of voters’ support by age

Czechia, 10-11/2019

index.html

<!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>

data200_2.csv

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

parties.csv

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