block by michalskop 9d2cb8950f551a33eea51d925f945dce

CZ: voters changes

Full Screen

Voters changes in Czech elections 2013 → 2017

Estimates of numbers of people changing parties between Czech elections 2013-2017 based on ecological inference model.

Individual parties:

Example (i: 1-8)

index.html?i=3

Overview

See overview chart

Analysis flow

  1. download data for 2017 (to /json): downloader.py
  2. extract data (list_2017.csv): scraper.py
  3. join 2013 and 2017, select random polling station only, to be able to run R calculation (data_filtered.csv and data_filtered_random.csv): join_data.py
  4. analysis - ecological inference (matn_filtered.csv, matp_filtered.csv): ei_psp.py calling ei.r
  5. manually recalculate numbers to real total matn_filtered_adjusted.csv
  6. manually reorder parties to have better matrix matn_filtered_adjusted_reordered.csv
  7. manually add labels/party names matn_chart.csv
  8. manually prepare lists of parties list_2013_filtered.csv and list_2017_filtered.csv
  9. overview chart: bubbles_psp.html
  10. calculate errors of model presnost.ods
  11. individual party charts (with ?i=1 parameter, i: 1-8) index.html and index_cs.html
  12. generate pictures (PNGs): generate.py

CC-BY Michal Škop

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
    <script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <style>
        .mcontainer {
            padding-left: 10px;
            padding-right: 10px;
        }
        .header {
            padding-left: 10px;
            padding-right: 10px;
        }
        .h3 {
            position: relative;
            bottom: 20px;
        }
        .note {
            padding: 10px;
        }
        .logo {
            width: 64px;
            height: 64px;
            border-radius: 50%;
            margin: 10px;
            border: 1px solid #eee;
        }
        #chart {
            border: 1px solid #eee;
        }
        .hithit {
            font-size: 1.5em;
            width: 100vw;
            /*background-color: #fed201;*/
            padding: 10px;
            height: 100vh;
            color: white;
        }
    </style>
        <div class="header">
            <h1 id="h1" class="text-center"><span id="h1pic"></span> <span id="h1name"></span></h1>
            <h3 class="text-center h3">Gains and Losts of Voters in Czech Elections 2013 → 2017</h3>
            <div class="row">
                <div class="col-xs-4 col-xs-offset-1 text-center">
                    <strong>Came to <span class="shortname"></span></strong>
                </div>
                <div class="col-xs-4 col-xs-offset-2 text-center">
                    <strong>Left from <span class="shortname"></span></strong>
                </div>
            </div>
        </div>
        <div id="chart"></div>
        <div class="note xbg-info">
            <i class="fa fa-info-circle"></i> Estimated by Ecological Inference. Only streams with more than 25,000 people shown.<br />
            CC-BY Michal Škop, KohoVolit.eu, VolebníKalkulačka.cz
        </div>
    <script>
        var formatNumber = d3.format(",.0f");

        d3.csv("list_2017_filtered.csv", function (er,d2017) {
            d3.csv("list_2013_filtered.csv", function(err,d2013) {
                d3.csv("matn_chart.csv", function(errr,source) {

                    var d13to17 = {};
                    var party2color = {};
                    d2013.forEach(function(d) {
                        d13to17[d['party']] = {}
                        party2color[d['party']] = d['color']
                    });
                    d2017.forEach(function(d) {
                        party2color[d['party']] = d['color']
                    });
                    var d17to13 = {};
                    source.forEach(function(d) {
                        d17to13[d['name']] = {}
                        for (k in d) {
                            if (k != 'name') {
                                d17to13[d['name']][k] = parseInt(d[k])
                                d13to17[k][d['name']] = parseInt(d[k])
                            }
                        }
                    })



                    var names = source['name'];
                    var i = QueryString.i;
                    if (i === undefined) {
                        i = 1;
                    }
                    var party = d2017[i]['party']
                    var prev_party = d2017[i]['previous']
                    var data = source[i];

                    d3.select("#h1name")
                        .html(d2017[i]['party_name']);
                    d3.select("#h1pic")
                        .html("<img class='logo' src='./" + d2017[i]['party'] + ".png' />");
                    d3.selectAll(".shortname")
                        .html(d2017[i]['party'])

                    var sumout = 0
                    for (k in d13to17[prev_party]) {
                        if (k != prev_party) {
                            sumout += d13to17[prev_party][k]
                        }
                    }

                    if (d2017[i]['votes'] > 1000000) {
                        var h = 700;
                    } else {
                        var h = 400;
                    }

                    var margin = {top: 10, right: 10, bottom: 10, left: 10},
                        width = 930 - margin.left - margin.right,
                        height = h - margin.top - margin.bottom;
                    var svg = d3.select("#chart").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 + ")");

                    maxsum = Math.max(sumout, d2017[i]['votes'])

                    var x = d3.scale.linear()
              		             .domain([0, 100])
              		             .range([0, width])
              		var y = d3.scale.linear()
              		             .domain([0, maxsum * 1.1])
              		             .range([0, height]);

                    // basic rectangle
                    var offset = (maxsum - d2017[i]['votes']) / 2;
                    svg.append("rect")
                        .attr("width", x(20))
                        .attr("height", y(d2017[i]['votes']))
                        .attr("x", x(40))
                        .attr("y", y(offset))
                        .attr("fill", d2017[i]['color']);

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "40px")
                        .text(party)

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 * 1.3 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "20px")
                        .text(formatNumber(d2017[i]['votes']))

                    // prepare out and in rectangles
                    var outs = []
                    for (k in d13to17[prev_party]) {
                        if ((d13to17[prev_party][k] > 25000) && (party != k)) {
                            outs.push({
                                'name': k,
                                'value': d13to17[prev_party][k],
                                'color': party2color[k]
                            })
                        }
                    }
                    outs.sort(function(a, b) {
                        return b.value - a.value;
                    });
                    var ins = []
                    for (k in d13to17) {
                        if ((d13to17[k][party] > 25000) && (prev_party != k)) {
                            ins.push({
                                'name': k,
                                'value': d13to17[k][party],
                                'color': party2color[k]
                            })
                        }
                    }
                    ins.sort(function(a, b) {
                        return b.value - a.value;
                    });

                    s = 0
                    outs.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    outs.forEach(function (out, k) {
                        outs[k]['y0'] = offset;
                        outs[k]['y1'] = offset + y(out['value'])
                        outs[k]['half'] = (outs[k]['y1'] + outs[k]['y0']) / 2
                        offset = outs[k]['y1'] + 15;
                        outs[k]['arrow'] = [
                            {"x": x(76.98), "y": outs[k]['y0']}, {"x": x(79), "y": outs[k]['half']}, {"x": x(76.98), "y": outs[k]['y1']}, {"x": x(76.98), "y": outs[k]['y0']}
                        ]
                    });

                    s = 0
                    ins.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    ins.forEach(function (inn, k) {
                        ins[k]['y0'] = offset;
                        ins[k]['y1'] = offset + y(inn['value'])
                        ins[k]['half'] = (ins[k]['y1'] + ins[k]['y0']) / 2
                        offset = ins[k]['y1'] + 15;
                        ins[k]['arrow'] = [
                            {"x": x(34.95), "y": ins[k]['y0']}, {"x": x(37), "y": ins[k]['half']}, {"x": x(34.95), "y": ins[k]['y1']},
                             {"x": x(34.95), "y": ins[k]['y0']}
                        ]
                    });


                    var arrowFunction = d3.svg.area()
                                .x(function(d) { return d.x; })
                                .y1(function(d) { return d.y; });

                    // arrows out
                    var rectouts = svg.selectAll(".rectout")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(63))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", d2017[i]['color']);

                    var arrowouts = svg.selectAll(".arrowout")
                       .data(outs)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", d2017[i]['color']);

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(70))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4.5), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4.5), 18)
                            })
                            .text(function(d) {
                                return formatNumber(my_round(d['value']))
                            })

                    //arrows in
                    svg.selectAll(".rectin")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(21))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".arrowin")
                       .data(ins)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", function(d) {
                                return d['color']
                            });

                    svg.selectAll(".textin")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(28))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return formatNumber(my_round(d['value']))
                            })

                    // outs
                    svg.selectAll(".rectend")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(84))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(92))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })
                    // ins
                    svg.selectAll(".rectstart")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(0))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textstart")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(8))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })



                    var pause = 0;

                })
            })
        })

        function my_round(n) {
            if (n < 100000) {
                return Math.round(n/10000)*10000;
            }
            return Math.round(n/20000)*20000;
        }
        var QueryString = function () {
        // This function is anonymous, is executed immediately and
        // the return value is assigned to QueryString!
          var query_string = {};
          var query = window.location.search.substring(1);
          var vars = query.split("&");
          for (var i=0;i<vars.length;i++) {
            var pair = vars[i].split("=");
                // If first entry with this name
            if (typeof query_string[pair[0]] === "undefined") {
              query_string[pair[0]] = decodeURIComponent(pair[1]);
                // If second entry with this name
            } else if (typeof query_string[pair[0]] === "string") {
              var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
              query_string[pair[0]] = arr;
                // If third or later entry with this name
            } else {
              query_string[pair[0]].push(decodeURIComponent(pair[1]));
            }
          }
          return query_string;
        }();

    </script>
</body>
</html>

ano.svg

<svg width="930" height="700">
    <g transform="translate(10,10)">
        <rect width="182" height="618.1818181818181" x="364" y="0" fill="#261060"></rect>
        <text x="455" y="309.09090909090907" dy="0" text-anchor="middle" fill="white" font-size="40px">ANO</text>
        <text x="455" y="401.8181818181818" dy="0" text-anchor="middle" fill="white" font-size="20px">1,500,113</text>
        <rect width="127.4" height="59.28988501048073" x="573.3" y="188.1298752766685" fill="#261060"></rect>
        <rect width="127.4" height="29.209363197669404" x="573.3" y="262.41976028714924" fill="#261060"></rect>
        <rect width="127.4" height="26.03585681393821" x="573.3" y="306.62912348481865" fill="#261060"></rect>
        <rect width="127.4" height="21.323605742912832" x="573.3" y="347.66498029875686" fill="#261060"></rect>
        <rect width="127.4" height="20.603272129257164" x="573.3" y="383.9885860416697" fill="#261060"></rect>
        <rect width="127.4" height="10.460084734222733" x="573.3" y="419.59185817092686" fill="#261060"></rect>
        <path d="M700.518,188.1298752766685L718.9,217.7748177819089L700.518,247.41976028714924L700.518,188.1298752766685L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,262.41976028714924L718.9,277.02444188598395L700.518,291.62912348481865L700.518,262.41976028714924L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,306.62912348481865L718.9,319.6470518917878L700.518,332.66498029875686L700.518,306.62912348481865L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,347.66498029875686L718.9,358.3267831702133L700.518,368.9885860416697L700.518,347.66498029875686L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,383.9885860416697L718.9,394.2902221062983L700.518,404.59185817092686L700.518,383.9885860416697L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <path d="M700.518,419.59185817092686L718.9,424.8219005380382L700.518,430.0519429051496L700.518,419.59185817092686L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#261060"></path>
        <text x="637" y="224.44148444857555" text-anchor="middle" fill="white" font-size="18">140,000</text>
        <text x="637" y="283.69110855265063" text-anchor="middle" fill="white" font-size="18">70,000</text>
        <text x="637" y="326.31371855845447" text-anchor="middle" fill="white" font-size="18">60,000</text>
        <text x="637" y="364.99344983687996" text-anchor="middle" fill="white" font-size="18">50,000</text>
        <text x="637" y="400.956888772965" text-anchor="middle" fill="white" font-size="18">50,000</text>
        <text x="637" y="431.4885672047049" text-anchor="middle" fill="white" font-size="18">30,000</text>
        <rect width="127.4" height="150.15947586493937" x="191.1" y="61.77709551941027" fill="#f1953e"></rect>
        <rect width="127.4" height="84.93301989431944" x="191.1" y="226.93657138434963" fill="#888"></rect>
        <rect width="127.4" height="82.25402171218252" x="191.1" y="326.8695912786691" fill="#8c0000"></rect>
        <rect width="127.4" height="36.106928429573486" x="191.1" y="424.1236129908516" fill="#f51941"></rect>
        <rect width="127.4" height="21.8873450927303" x="191.1" y="475.2305414204251" fill="#888"></rect>
        <rect width="127.4" height="15.993219419894558" x="191.1" y="512.1178865131553" fill="#004494"></rect>
        <rect width="127.4" height="13.29361672935795" x="191.1" y="543.1111059330499" fill="#e6ac21"></rect>
        <path d="M318.045,61.77709551941027L336.7,136.85683345187994L318.045,211.93657138434963L318.045,61.77709551941027L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#f1953e"></path>
        <path d="M318.045,226.93657138434963L336.7,269.4030813315094L318.045,311.8695912786691L318.045,226.93657138434963L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#888"></path>
        <path d="M318.045,326.8695912786691L336.7,367.99660213476034L318.045,409.1236129908516L318.045,326.8695912786691L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#8c0000"></path>
        <path d="M318.045,424.1236129908516L336.7,442.17707720563834L318.045,460.2305414204251L318.045,424.1236129908516L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#f51941"></path>
        <path d="M318.045,475.2305414204251L336.7,486.17421396679026L318.045,497.1178865131554L318.045,475.2305414204251L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#888"></path>
        <path d="M318.045,512.1178865131553L336.7,520.1144962231026L318.045,528.1111059330499L318.045,512.1178865131553L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#004494"></path>
        <path d="M318.045,543.1111059330499L336.7,549.7579142977288L318.045,556.4047226624078L318.045,543.1111059330499L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#e6ac21"></path>
        <text x="254.8" y="149.37012310729156" text-anchor="middle" fill="white" font-size="37.53986896623484">360,000</text>
        <text x="254.8" y="276.48083298936933" text-anchor="middle" fill="white" font-size="21.233254973579868">200,000</text>
        <text x="254.8" y="374.8511039441089" text-anchor="middle" fill="white" font-size="20.563505428045634">200,000</text>
        <text x="254.8" y="448.843743872305" text-anchor="middle" fill="white" font-size="20">90,000</text>
        <text x="254.8" y="492.84088063345695" text-anchor="middle" fill="white" font-size="20">50,000</text>
        <text x="254.8" y="526.7811628897692" text-anchor="middle" fill="white" font-size="20">40,000</text>
        <text x="254.8" y="556.4245809643954" text-anchor="middle" fill="white" font-size="20">30,000</text>
        <rect width="145.6" height="59.28988501048073" x="764.4" y="188.1298752766685" fill="#888"></rect>
        <rect width="145.6" height="29.209363197669404" x="764.4" y="262.41976028714924" fill="#888"></rect>
        <rect width="145.6" height="26.03585681393821" x="764.4" y="306.62912348481865" fill="#000000"></rect>
        <rect width="145.6" height="21.323605742912832" x="764.4" y="347.66498029875686" fill="#0066a5"></rect>
        <rect width="145.6" height="20.603272129257164" x="764.4" y="383.9885860416697" fill="#004494"></rect>
        <rect width="145.6" height="10.460084734222733" x="764.4" y="419.59185817092686" fill="#5d8c00"></rect>
        <text x="837.2" y="224.44148444857555" text-anchor="middle" fill="white" font-size="20">nevoliči</text>
        <text x="837.2" y="283.69110855265063" text-anchor="middle" fill="white" font-size="20">neparlamentní</text>
        <text x="837.2" y="326.31371855845447" text-anchor="middle" fill="white" font-size="20">Piráti</text>
        <text x="837.2" y="364.99344983687996" text-anchor="middle" fill="white" font-size="20">SPD</text>
        <text x="837.2" y="400.956888772965" text-anchor="middle" fill="white" font-size="20">ODS</text>
        <text x="837.2" y="431.4885672047049" text-anchor="middle" fill="white" font-size="20">STAN</text>
        <rect width="145.6" height="150.15947586493937" x="0" y="61.77709551941027" fill="#f1953e"></rect>
        <rect width="145.6" height="84.93301989431944" x="0" y="226.93657138434963" fill="#888"></rect>
        <rect width="145.6" height="82.25402171218252" x="0" y="326.8695912786691" fill="#8c0000"></rect>
        <rect width="145.6" height="36.106928429573486" x="0" y="424.1236129908516" fill="#f51941"></rect>
        <rect width="145.6" height="21.8873450927303" x="0" y="475.2305414204251" fill="#888"></rect>
        <rect width="145.6" height="15.993219419894558" x="0" y="512.1178865131553" fill="#004494"></rect>
        <rect width="145.6" height="13.29361672935795" x="0" y="543.1111059330499" fill="#e6ac21"></rect>
        <text x="72.8" y="149.37012310729156" text-anchor="middle" fill="white" font-size="37.53986896623484">ČSSD</text>
        <text x="72.8" y="276.48083298936933" text-anchor="middle" fill="white" font-size="21.233254973579868">nevoliči</text>
        <text x="72.8" y="374.8511039441089" text-anchor="middle" fill="white" font-size="20.563505428045634">KSČM</text>
        <text x="72.8" y="448.843743872305" text-anchor="middle" fill="white" font-size="20">Úsvit</text>
        <text x="72.8" y="492.84088063345695" text-anchor="middle" fill="white" font-size="20">neparlamentní</text>
        <text x="72.8" y="526.7811628897692" text-anchor="middle" fill="white" font-size="20">ODS</text>
        <text x="72.8" y="556.4245809643954" text-anchor="middle" fill="white" font-size="20">KDU-ČSL</text>
    </g>
</svg>

bubbles_psp.html

<!DOCTYPE html>
<meta charset="utf-8">
<title>Volby 2017 a 2013</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<style>

.node rect {
  cursor: move;
  fill-opacity: .9;
  shape-rendering: crispEdges;
}

.node text {
  pointer-events: none;
  text-shadow: 0 1px 0 #fff;
}

.link {
  fill: none;
  stroke: #000;
  stroke-opacity: .2;
}

.link:hover {
  stroke-opacity: .5;
}

.d3-tip {
    line-height: 1;
    font-weight: bold;
    padding: 12px;
    background: rgba(0, 0, 0, 0.8);
    color: #fff;
    border-radius: 2px;
    pointer-events: none;
    max-width: 400px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
    box-sizing: border-box;
    display: inline;
    font-size: 10px;
    width: 100%;
    line-height: 1;
    color: rgba(0, 0, 0, 0.8);
    position: absolute;
    pointer-events: none;
}
/* Northward tooltips */
.d3-tip:after {
    content: "\25BC";
    margin: -1px 0 0 0;
    top: 100%;
    left: 0;
    text-align: center;
}
.stronger {
    font-weight: bold;
    color: yellow;
}
</style>
<body>

<div class="container">
    <h1 id="h1"><span id="h1name"></span> <small>2013 → 2017</small></h1>
    <h3>Volby 2017 vs. volby 2013</h3>
    <p>Odhad počtu voličů, kteří volili stranu X ve volbách 2017 a zároveň stranu Y ve volbách 2013. Plocha jednotlivých bodů odpovídá počtu takových voličů (najetím myší na body zjistíte počty; lidé, kteří nevolili ani v jedněch z voleb, nejsou zobrazeni).
    <p id="chart">

    <p>Odhad pomocí statistické metody ekologické inference z výsledků voleb v jednotlivých volebních místnostech s výjimkou některých míst, kde se měnily volební okrsky (např. částečně v Ostravě, Brně, Plzni). Hrubý odhad chyby vypočtených čísel je +-15%.
    <p>CC-BY-SA Michal Škop
</div>

<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="./d3.tips.js"></script>
<script>

var margin = {top: 10, right: 10, bottom: 10, left: 10},
    width = 1000 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var formatNumber = d3.format(",.0f"),    // zero decimal places
    format = function(d) { return formatNumber(Math.round(d/50)*50); },
    color = d3.scale.category20();

// append the svg canvas to the page
var svg = d3.select("#chart").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 + ")");

// var nonvoters = {
//     "name": "Nevoliči",
//     "party": "Nevoliči",
//     "color": "#444444",
// }
// var others = {
//     "name": "Ostatní",
//     "party": "Ostatní",
//     "color": "#999999",
// }
// var plus = {
//     "name": "Přibylí",
//     "party": "Přibylí",
//     "color": "#444444",
// }
// var minus = {
//     "name": "Odešlí",
//     "party": "Odešlí",
//     "color": "#444444",
// }

d3.select("#h1name")
    .html('Voličské přesuny');

var name2data = {};
// load the data
d3.csv("list_2017_filtered.csv", function (er,da) {

    d3.csv("list_2013_filtered.csv", function(err,daa) {

        parties = [];
        daa.forEach(function(d) {
            parties.push(d.party + " 13");
            name2data[d.party + " 13"] = d;
        });
        // parties.push(others.name + " 13");
        // name2data[others.name + " 13"] = others;
        // parties.push(minus.name + " 13");
        // name2data[minus.name + " 13"] = minus;
        // parties.push(nonvoters.name + " 13");
        // name2data[nonvoters.name + " 13"] = nonvoters;


        cands = []
        da.forEach(function (d) {
            cands.push(d.party + " 17");
            name2data[d.party + " 17"] = d;
        });
        // cands.push(others.name + " 17");
        // name2data[others.name + " 17"] = others;
        // cands.push(plus.name + " 17");
        // name2data[plus.name + " 17"] = plus;
        // cands.push(nonvoters.name + " 17");
        // name2data[nonvoters.name + " 17"] = nonvoters;


        //set up scales
        var x = d3.scale.linear()
		             .domain([-5,parties.length])
		             .range([0, width])
		var y = d3.scale.linear()
		             .domain([-3,cands.length])
		             .range([0, height]);
        var r = d3.scale.linear()
		             .domain([0,Math.sqrt(1500000)])
		             .range([0,50]);
        d3.text("matn_filtered_adjusted_reordered.csv", function(text) {

            var dat = d3.csv.parseRows(text).map(function(row) {
                return row.map(function(value) {
                    return +value;
                });
            });

            points = [];
            i = 0;
            len = dat.length;
            dat.forEach(function (d) {
                len2 = d.length;
                d.forEach(function (dd,ii) {
                    if (((i + 1) < len) || ((ii + 1) < len2)) {
                        points.push({
                            'i': i,
                            'j': ii,
                            'value': dd,
                            'd1': name2data[cands[ii]],
                            'd2': name2data[parties[i]]
                        })
                    }
                });
                i++;
             });

             /* Initialize tooltip */
             function my_round(n) {
                 if (n < 100000) {
                     return Math.round(n/10000)*10000;
                 }
                 return Math.round(n/20000)*20000;
             }
             tip = d3.tip().attr('class', 'd3-tip').html(function(d) {
               return "<span>zhruba <span class='stronger'>" + my_round(d["value"]) + "</span> lidí volilo zároveň:<br><br><span class='stronger'>" + d.d1.party + "</span> v roce 2017<br><span class='stronger'>" + d.d2.party + "</span> v roce 2013<br>";
             });

             /* Invoke the tip in the context of your visualization */
             svg.call(tip);

             var circles = svg.selectAll(".circle")
                .data(points)
                    .enter()
                .append("circle")
                    .attr("cx",function(d) {
                        return x(d.i);
                    })
                    .attr("cy",function(d) {
                        return y(d.j);
                    })
                    .attr("r", function(d) {
                        return r(Math.sqrt(d.value));
                    })
                    .attr("title", function(d) {
                        return Math.round(d/50)*50;
                    })
                    .attr("fill", function(d) {
                        return d.d1.color;
                    })
                    // .attr("stroke",function(d) {
                    //     return d.d1.color;
                    // })
                    // .attr("stroke-width", function (d) {
                    //     return Math.sqrt(d.value/100);
                    // })
                    .attr("fill-opacity", function(d) {
                        return d.value/200000;
                    })
                    // .attr("stroke-opacity", function(d) {
                    //     return d.value/2000;
                    // })
                    .on('mouseover', tip.show)
                    .on('mouseout', tip.hide)
                    ;

            var cands_names = svg.selectAll(".text")
                .data(cands)
                    .enter()
                .append("text")
                    .attr("x", function() {
                        return (x(-0.5));
                    })
                    .attr("y", function(d,i) {
                        return y(i)+6;
                    })
                    .attr("text-anchor", "end")
                    .attr("font-size",12)
                    .text(function(d) {
                        return d;
                    });
            var parties_names = svg.selectAll(".text")
                .data(parties)
                    .enter()
                .append("text")
                    .attr("x", function(d,i) {
                        return (x(i));
                    })
                    .attr("y", function(d,i) {
                        return y(-0.5);
                    })
                    .attr("transform", function(d,i) {
                        return "rotate(-45,"+x(i)+","+y(-0.5)+")";
                    })
                    // .attr("text-anchor", "end")
                    .attr("font-size",12)
                    .text(function(d) {
                        return d;
                    })

        });

    });
});


</script>
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-8592359-13', 'ocks.org');
  ga('send', 'pageview');

</script>
</body>
</html>

cssd.svg

<svg width="930" height="400">
    <g transform="translate(10,10)">
        <rect width="182" height="158.54860350066403" x="364" y="93.45297097694069" fill="#f1953e"></rect>
        <text x="455" y="172.7272727272727" dy="0" text-anchor="middle" fill="white" font-size="40px">ČSSD</text>
        <text x="455" y="196.50956325237232" dy="0" text-anchor="middle" fill="white" font-size="20px">368,347</text>
        <rect width="127.4" height="156.84322903835096" x="573.3" y="-5.268486660833162" fill="#f1953e"></rect>
        <rect width="127.4" height="112.21510309146976" x="573.3" y="166.5747423775178" fill="#f1953e"></rect>
        <rect width="127.4" height="30.08595628276032" x="573.3" y="293.78984546898755" fill="#f1953e"></rect>
        <rect width="127.4" height="11.847230363630672" x="573.3" y="338.8758017517479" fill="#f1953e"></rect>
        <path d="M700.518,-5.268486660833162L718.9,73.15312785834232L700.518,151.5747423775178L700.518,-5.268486660833162L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#f1953e"></path>
        <path d="M700.518,166.5747423775178L718.9,222.68229392325267L700.518,278.78984546898755L700.518,166.5747423775178L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#f1953e"></path>
        <path d="M700.518,293.78984546898755L718.9,308.8328236103677L700.518,323.8758017517479L700.518,293.78984546898755L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#f1953e"></path>
        <path d="M700.518,338.8758017517479L718.9,344.7994169335632L700.518,350.72303211537854L700.518,338.8758017517479L700.518,0L700.518,0L718.9,0L700.518,0Z" fill="#f1953e"></path>
        <text x="637" y="84.77114482414609" text-anchor="middle" fill="white" font-size="34.85405089741132">360,000</text>
        <text x="637" y="230.99452378188005" text-anchor="middle" fill="white" font-size="24.93668957588217">260,000</text>
        <text x="637" y="315.4994902770344" text-anchor="middle" fill="white" font-size="18">70,000</text>
        <text x="637" y="351.46608360022987" text-anchor="middle" fill="white" font-size="18">30,000</text>
        <rect width="127.4" height="21.355919724296513" x="191.1" y="148.94787657907924" fill="#888"></rect>
        <rect width="127.4" height="11.2028725720904" x="191.1" y="185.30379630337575" fill="#8c0000"></rect>
        <path d="M318.045,148.94787657907924L336.7,159.62583644122748L318.045,170.30379630337575L318.045,148.94787657907924L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#888"></path>
        <path d="M318.045,185.30379630337575L336.7,190.90523258942096L318.045,196.50666887546615L318.045,185.30379630337575L318.045,0L318.045,0L336.7,0L318.045,0Z" fill="#8c0000"></path>
        <text x="254.8" y="166.29250310789413" text-anchor="middle" fill="white" font-size="20">50,000</text>
        <text x="254.8" y="197.57189925608762" text-anchor="middle" fill="white" font-size="20">30,000</text>
        <rect width="145.6" height="156.84322903835096" x="764.4" y="-5.268486660833162" fill="#261060"></rect>
        <rect width="145.6" height="112.21510309146976" x="764.4" y="166.5747423775178" fill="#888"></rect>
        <rect width="145.6" height="30.08595628276032" x="764.4" y="293.78984546898755" fill="#0066a5"></rect>
        <rect width="145.6" height="11.847230363630672" x="764.4" y="338.8758017517479" fill="#8c0000"></rect>
        <text x="837.2" y="86.22339694487157" text-anchor="middle" fill="white" font-size="39.21080725958774">ANO</text>
        <text x="837.2" y="232.03355251420848" text-anchor="middle" fill="white" font-size="28.053775772867443">nevoliči</text>
        <text x="837.2" y="315.4994902770344" text-anchor="middle" fill="white" font-size="20">SPD</text>
        <text x="837.2" y="351.46608360022987" text-anchor="middle" fill="white" font-size="20">KSČM</text>
        <rect width="145.6" height="21.355919724296513" x="0" y="148.94787657907924" fill="#888"></rect>
        <rect width="145.6" height="11.2028725720904" x="0" y="185.30379630337575" fill="#8c0000"></rect>
        <text x="72.8" y="166.29250310789413" text-anchor="middle" fill="white" font-size="20">nevoliči</text>
        <text x="72.8" y="197.57189925608762" text-anchor="middle" fill="white" font-size="20">KSČM</text>
    </g>
</svg>

d3.tips.js

// d3.tip
// Copyright (c) 2013 Justin Palmer
//
// Tooltips for d3.js SVG visualizations

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module with d3 as a dependency.
    define(['d3'], factory)
  } else if (typeof module === 'object' && module.exports) {
    // CommonJS
    module.exports = function(d3) {
      d3.tip = factory(d3)
      return d3.tip
    }
  } else {
    // Browser global.
    root.d3.tip = factory(root.d3)
  }
}(this, function (d3) {

  // Public - contructs a new tooltip
  //
  // Returns a tip
  return function() {
    var direction = d3_tip_direction,
        offset    = d3_tip_offset,
        html      = d3_tip_html,
        node      = initNode(),
        svg       = null,
        point     = null,
        target    = null

    function tip(vis) {
      svg = getSVGNode(vis)
      point = svg.createSVGPoint()
      document.body.appendChild(node)
    }

    // Public - show the tooltip on the screen
    //
    // Returns a tip
    tip.show = function() {
      var args = Array.prototype.slice.call(arguments)
      if(args[args.length - 1] instanceof SVGElement) target = args.pop()

      var content = html.apply(this, args),
          poffset = offset.apply(this, args),
          dir     = direction.apply(this, args),
          nodel   = d3.select(node),
          i       = directions.length,
          coords,
          scrollTop  = document.documentElement.scrollTop || document.body.scrollTop,
          scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft

      nodel.html(content)
        .style({ opacity: 1, 'pointer-events': 'all' })

      while(i--) nodel.classed(directions[i], false)
      coords = direction_callbacks.get(dir).apply(this)
      nodel.classed(dir, true).style({
        top: (coords.top +  poffset[0]) + scrollTop + 'px',
        left: (coords.left + poffset[1]) + scrollLeft + 'px'
      })

      return tip
    }

    // Public - hide the tooltip
    //
    // Returns a tip
    tip.hide = function() {
      var nodel = d3.select(node)
      nodel.style({ opacity: 0, 'pointer-events': 'none' })
      return tip
    }

    // Public: Proxy attr calls to the d3 tip container.  Sets or gets attribute value.
    //
    // n - name of the attribute
    // v - value of the attribute
    //
    // Returns tip or attribute value
    tip.attr = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).attr(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.attr.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Proxy style calls to the d3 tip container.  Sets or gets a style value.
    //
    // n - name of the property
    // v - value of the property
    //
    // Returns tip or style property value
    tip.style = function(n, v) {
      if (arguments.length < 2 && typeof n === 'string') {
        return d3.select(node).style(n)
      } else {
        var args =  Array.prototype.slice.call(arguments)
        d3.selection.prototype.style.apply(d3.select(node), args)
      }

      return tip
    }

    // Public: Set or get the direction of the tooltip
    //
    // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
    //     sw(southwest), ne(northeast) or se(southeast)
    //
    // Returns tip or direction
    tip.direction = function(v) {
      if (!arguments.length) return direction
      direction = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: Sets or gets the offset of the tip
    //
    // v - Array of [x, y] offset
    //
    // Returns offset or
    tip.offset = function(v) {
      if (!arguments.length) return offset
      offset = v == null ? v : d3.functor(v)

      return tip
    }

    // Public: sets or gets the html value of the tooltip
    //
    // v - String value of the tip
    //
    // Returns html value or tip
    tip.html = function(v) {
      if (!arguments.length) return html
      html = v == null ? v : d3.functor(v)

      return tip
    }

    function d3_tip_direction() { return 'n' }
    function d3_tip_offset() { return [0, 0] }
    function d3_tip_html() { return ' ' }

    var direction_callbacks = d3.map({
      n:  direction_n,
      s:  direction_s,
      e:  direction_e,
      w:  direction_w,
      nw: direction_nw,
      ne: direction_ne,
      sw: direction_sw,
      se: direction_se
    }),

    directions = direction_callbacks.keys()

    function direction_n() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.n.y - node.offsetHeight,
        left: bbox.n.x - node.offsetWidth / 2
      }
    }

    function direction_s() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.s.y,
        left: bbox.s.x - node.offsetWidth / 2
      }
    }

    function direction_e() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.e.y - node.offsetHeight / 2,
        left: bbox.e.x
      }
    }

    function direction_w() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.w.y - node.offsetHeight / 2,
        left: bbox.w.x - node.offsetWidth
      }
    }

    function direction_nw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.nw.y - node.offsetHeight,
        left: bbox.nw.x - node.offsetWidth
      }
    }

    function direction_ne() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.ne.y - node.offsetHeight,
        left: bbox.ne.x
      }
    }

    function direction_sw() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.sw.y,
        left: bbox.sw.x - node.offsetWidth
      }
    }

    function direction_se() {
      var bbox = getScreenBBox()
      return {
        top:  bbox.se.y,
        left: bbox.e.x
      }
    }

    function initNode() {
      var node = d3.select(document.createElement('div'))
      node.style({
        position: 'absolute',
        top: 0,
        opacity: 0,
        'pointer-events': 'none',
        'box-sizing': 'border-box'
      })

      return node.node()
    }

    function getSVGNode(el) {
      el = el.node()
      if(el.tagName.toLowerCase() === 'svg')
        return el

      return el.ownerSVGElement
    }

    // Private - gets the screen coordinates of a shape
    //
    // Given a shape on the screen, will return an SVGPoint for the directions
    // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
    // sw(southwest).
    //
    //    +-+-+
    //    |   |
    //    +   +
    //    |   |
    //    +-+-+
    //
    // Returns an Object {n, s, e, w, nw, sw, ne, se}
    function getScreenBBox() {
      var targetel   = target || d3.event.target;

      while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
          targetel = targetel.parentNode;
      }

      var bbox       = {},
          matrix     = targetel.getScreenCTM(),
          tbbox      = targetel.getBBox(),
          width      = tbbox.width,
          height     = tbbox.height,
          x          = tbbox.x,
          y          = tbbox.y

      point.x = x
      point.y = y
      bbox.nw = point.matrixTransform(matrix)
      point.x += width
      bbox.ne = point.matrixTransform(matrix)
      point.y += height
      bbox.se = point.matrixTransform(matrix)
      point.x -= width
      bbox.sw = point.matrixTransform(matrix)
      point.y -= height / 2
      bbox.w  = point.matrixTransform(matrix)
      point.x += width
      bbox.e = point.matrixTransform(matrix)
      point.x -= width / 2
      point.y -= height / 2
      bbox.n = point.matrixTransform(matrix)
      point.y += height
      bbox.s = point.matrixTransform(matrix)

      return bbox
    }

    return tip
  };

}));

downloader.py

# download all data
import json
import requests
import xmltodict

next = True
i = 1
j = 0
while (next):
    print(i)
    url = "https://www.volby.cz/pls/ps2017/vysledky_okrsky?davka=" + str(i)
    try:
        r = requests.get(url)
        o = xmltodict.parse(r.text)
        try:
            o["VYSLEDKY_OKRSKY"]["CHYBA"]["@KOD_CHYBY"]
            next = False
        except Exception:
            with open('json/' + str(i) + ".json", "w") as fin:
                o = xmltodict.parse(r.text)
                json.dump(o, fin)
            # next = False
            i += 1
            j = 0
    except Exception:
        tryagain = True
        j += 1
        if j >= 10:
            next = False

ei.r


library("Zelig")
library("ZeligEI")
sink()
sink("/dev/null")   # Suppress output after command or script run in R console

ncalc = 5  # number of repetitions of the calculcations (to get better means)
mats = list()

data = read.csv(par_input, header = TRUE, fileEncoding = "utf-8", encoding = "utf-8")
variables = names(data)
As = variables[which(grepl("A", variables) == TRUE)]
Bs = variables[which(grepl("B", variables) == TRUE)]

for (i in 1:ncalc) {
    z.out = zeirxc$new()
    eval(parse(text = paste0( "z.out$zelig(cbind(" , paste(Bs, collapse = ", "), ") ~ cbind(", paste(As, collapse = ", "), "), N='ZeligN', data = data)")))
    mat = matrix(apply(as.matrix(z.out$getcoef()[[1]][[3]]),2,'mean'),ncol=length(Bs))
    mats[[i]] = mat
}

# z.out$zelig(cbind(K2_2, K2_7, K2_9) ~ cbind(K1_1, K1_2, K1_3, K1_4, K1_5, K1_6, K1_7, K1_8, K1_9), N="ZeligN", data = data)
# eval(parse(text = paste0("z.out <- zelig(cbind(", paste(parties2016, collapse = ", "), ") ~ ", paste(parties2012, collapse = " + "), ", covar = NULL, model = 'ei.RxC', data = data)")))

matsd = matrix(apply(as.matrix(z.out$getcoef()[[1]][[3]]),2,'sd'),ncol=length(Bs))

mat_ave = apply(simplify2array(mats),1:2,mean)
matp = mat_ave / apply(mat_ave,1,'sum')

sink()

z.out = zeirxc$new()

#matp = mat / apply(mat,1,'sum')

ei_psp.py

# calculate ecological inference

import csv
import numpy
import rpy2.robjects as robjects
r = robjects.r

path = "/home/michal/dev/anal2017/scraper/"

robjects.globalenv["par_input"] = path + "data_filtered_random.csv"
try:
    r.source("ei.r")
    with open(path + "matp_filtered.csv", "w") as fout:
        csvw = csv.writer(fout)
        for row in numpy.array(robjects.globalenv['matp']):
            csvw.writerow(list(row))
    with open(path + "matn_filtered.csv", "w") as fout:
        csvw = csv.writer(fout)
        for row in numpy.array(robjects.globalenv['mat_ave']):
            csvw.writerow(list(row))
except Exception:
    print("skipping")

generate.py

import os

for i in range(8, 9):
    command = "chromium-browser --headless --disable-gpu --screenshot --hide-scrollbars --window-size=952,756 --save-to-png=q50.png http://localhost/michal/dev/anal2017/scraper/changes.html?i=" + str(i)
    # command = "chromium-browser --headless --disable-gpu --screenshot --hide-scrollbars --window-size=952,1065 --save-to-png=q50.png http://localhost/michal/dev/anal2017/scraper/changes.html?i=" + str(i)
    os.system(command)
    command = "cp screenshot.png parties/party_c_" + str(i) + ".png"
    os.system(command)
    print(i)

index_cs.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
    <script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <style>
        .mcontainer {
            padding-left: 10px;
            padding-right: 10px;
        }
        .header {
            padding-left: 10px;
            padding-right: 10px;
        }
        .h3 {
            position: relative;
            bottom: 20px;
        }
        .note {
            padding: 10px;
        }
        .logo {
            width: 64px;
            height: 64px;
            border-radius: 50%;
            margin: 10px;
            border: 1px solid #eee;
        }
        #chart {
            border: 1px solid #eee;
        }
        .hithit {
            font-size: 1.5em;
            width: 100vw;
            /*background-color: #fed201;*/
            padding: 10px;
            height: 100vh;
            color: white;
        }
    </style>
        <div class="header">
            <h1 id="h1" class="text-center"><span id="h1pic"></span> <span id="h1name"></span></h1>
            <h3 class="text-center h3">Zisky a ztráty voličů 2013 → 2017</h3>
            <div class="row">
                <div class="col-xs-4 col-xs-offset-1 text-center">
                    <strong>Přišli k <span class="shortname"></span></strong>
                </div>
                <div class="col-xs-4 col-xs-offset-2 text-center">
                    <strong>Odešli od <span class="shortname"></span></strong>
                </div>
            </div>
        </div>
        <div id="chart"></div>
        <div class="note xbg-info">
            <i class="fa fa-info-circle"></i> Odhady metodou ekologické inference. Zobrazeny přesuny voličů mezi stranami přesahující 25 000 lidí.<br />
            CC-BY Michal Škop, KohoVolit.eu, VolebníKalkulačka.cz
        </div>
        <div class="hithit text-center bg-primary">
            <h3></h3>
            <h3>Podpořte Volební kalkulačku pro prezidentské volby!</h3>
            <h3>www.Hithit.cz/VolebniKalkulacka</h3>
        </div>
    <script>
        var formatNumber = d3.format(",.0f");

        d3.csv("list_2017_filtered.csv", function (er,d2017) {
            d3.csv("list_2013_filtered.csv", function(err,d2013) {
                d3.csv("matn_chart.csv", function(errr,source) {

                    var d13to17 = {};
                    var party2color = {};
                    d2013.forEach(function(d) {
                        d13to17[d['party']] = {}
                        party2color[d['party']] = d['color']
                    });
                    d2017.forEach(function(d) {
                        party2color[d['party']] = d['color']
                    });
                    var d17to13 = {};
                    source.forEach(function(d) {
                        d17to13[d['name']] = {}
                        for (k in d) {
                            if (k != 'name') {
                                d17to13[d['name']][k] = parseInt(d[k])
                                d13to17[k][d['name']] = parseInt(d[k])
                            }
                        }
                    })



                    var names = source['name'];
                    var i = QueryString.i;
                    if (i === undefined) {
                        i = 1;
                    }
                    var party = d2017[i]['party']
                    var prev_party = d2017[i]['previous']
                    var data = source[i];

                    d3.select("#h1name")
                        .html(d2017[i]['party_name']);
                    d3.select("#h1pic")
                        .html("<img class='logo' src='./" + d2017[i]['party'] + ".png' />");
                    d3.selectAll(".shortname")
                        .html(d2017[i]['party'])

                    var sumout = 0
                    for (k in d13to17[prev_party]) {
                        if (k != prev_party) {
                            sumout += d13to17[prev_party][k]
                        }
                    }

                    if (d2017[i]['votes'] > 1000000) {
                        var h = 700;
                    } else {
                        var h = 400;
                    }

                    var margin = {top: 10, right: 10, bottom: 10, left: 10},
                        width = 930 - margin.left - margin.right,
                        height = h - margin.top - margin.bottom;
                    var svg = d3.select("#chart").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 + ")");

                    maxsum = Math.max(sumout, d2017[i]['votes'])

                    var x = d3.scale.linear()
              		             .domain([0, 100])
              		             .range([0, width])
              		var y = d3.scale.linear()
              		             .domain([0, maxsum * 1.1])
              		             .range([0, height]);

                    // basic rectangle
                    var offset = (maxsum - d2017[i]['votes']) / 2;
                    svg.append("rect")
                        .attr("width", x(20))
                        .attr("height", y(d2017[i]['votes']))
                        .attr("x", x(40))
                        .attr("y", y(offset))
                        .attr("fill", d2017[i]['color']);

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "40px")
                        .text(party)

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 * 1.3 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "20px")
                        .text(formatNumber(d2017[i]['votes']))

                    // prepare out and in rectangles
                    var outs = []
                    for (k in d13to17[prev_party]) {
                        if ((d13to17[prev_party][k] > 25000) && (party != k)) {
                            outs.push({
                                'name': k,
                                'value': d13to17[prev_party][k],
                                'color': party2color[k]
                            })
                        }
                    }
                    outs.sort(function(a, b) {
                        return b.value - a.value;
                    });
                    var ins = []
                    for (k in d13to17) {
                        if ((d13to17[k][party] > 25000) && (prev_party != k)) {
                            ins.push({
                                'name': k,
                                'value': d13to17[k][party],
                                'color': party2color[k]
                            })
                        }
                    }
                    ins.sort(function(a, b) {
                        return b.value - a.value;
                    });

                    s = 0
                    outs.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    outs.forEach(function (out, k) {
                        outs[k]['y0'] = offset;
                        outs[k]['y1'] = offset + y(out['value'])
                        outs[k]['half'] = (outs[k]['y1'] + outs[k]['y0']) / 2
                        offset = outs[k]['y1'] + 15;
                        outs[k]['arrow'] = [
                            {"x": x(76.98), "y": outs[k]['y0']}, {"x": x(79), "y": outs[k]['half']}, {"x": x(76.98), "y": outs[k]['y1']}, {"x": x(76.98), "y": outs[k]['y0']}
                        ]
                    });

                    s = 0
                    ins.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    ins.forEach(function (inn, k) {
                        ins[k]['y0'] = offset;
                        ins[k]['y1'] = offset + y(inn['value'])
                        ins[k]['half'] = (ins[k]['y1'] + ins[k]['y0']) / 2
                        offset = ins[k]['y1'] + 15;
                        ins[k]['arrow'] = [
                            {"x": x(34.95), "y": ins[k]['y0']}, {"x": x(37), "y": ins[k]['half']}, {"x": x(34.95), "y": ins[k]['y1']},
                             {"x": x(34.95), "y": ins[k]['y0']}
                        ]
                    });


                    var arrowFunction = d3.svg.area()
                                .x(function(d) { return d.x; })
                                .y1(function(d) { return d.y; });

                    // arrows out
                    var rectouts = svg.selectAll(".rectout")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(63))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", d2017[i]['color']);

                    var arrowouts = svg.selectAll(".arrowout")
                       .data(outs)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", d2017[i]['color']);

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(70))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4.5), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4.5), 18)
                            })
                            .text(function(d) {
                                return formatNumber(my_round(d['value']))
                            })

                    //arrows in
                    svg.selectAll(".rectin")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(21))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".arrowin")
                       .data(ins)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", function(d) {
                                return d['color']
                            });

                    svg.selectAll(".textin")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(28))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4.5), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4.5), 18)
                            })
                            .text(function(d) {
                                return formatNumber(my_round(d['value']))
                            })

                    // outs
                    svg.selectAll(".rectend")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(84))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(92))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })
                    // ins
                    svg.selectAll(".rectstart")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(0))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textstart")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(8))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })



                    var pause = 0;

                })
            })
        })

        function my_round(n) {
            if (n < 100000) {
                return Math.round(n/10000)*10000;
            }
            return Math.round(n/20000)*20000;
        }
        var QueryString = function () {
        // This function is anonymous, is executed immediately and
        // the return value is assigned to QueryString!
          var query_string = {};
          var query = window.location.search.substring(1);
          var vars = query.split("&");
          for (var i=0;i<vars.length;i++) {
            var pair = vars[i].split("=");
                // If first entry with this name
            if (typeof query_string[pair[0]] === "undefined") {
              query_string[pair[0]] = decodeURIComponent(pair[1]);
                // If second entry with this name
            } else if (typeof query_string[pair[0]] === "string") {
              var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
              query_string[pair[0]] = arr;
                // If third or later entry with this name
            } else {
              query_string[pair[0]].push(decodeURIComponent(pair[1]));
            }
          }
          return query_string;
        }();

    </script>
</body>
</html>

index_cs_print.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
    <script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
    <style>
        .mcontainer {
            padding-left: 10px;
            padding-right: 10px;
        }
        .header {
            padding-left: 10px;
            padding-right: 10px;
        }
        .h3 {
            position: relative;
            bottom: 20px;
        }
        .note {
            padding: 10px;
        }
        .logo {
            width: 64px;
            height: 64px;
            border-radius: 50%;
            margin: 10px;
            border: 1px solid #eee;
        }
        #chart {
            border: 1px solid #eee;
        }
        .hithit {
            font-size: 1.5em;
            width: 100vw;
            /*background-color: #fed201;*/
            padding: 10px;
            height: 100vh;
            color: white;
        }
    </style>
        <div class="header">
            <h1 id="h1" class="text-center"><span id="h1pic"></span> <span id="h1name"></span></h1>
            <h3 class="text-center h3">Zisky a ztráty voličů 2013 → 2017</h3>
            <div class="row">
                <div class="col-xs-4 col-xs-offset-1 text-center">
                    <strong>Přišli k <span class="shortname"></span></strong>
                </div>
                <div class="col-xs-4 col-xs-offset-2 text-center">
                    <strong>Odešli od <span class="shortname"></span></strong>
                </div>
            </div>
        </div>
        <div id="chart"></div>
        <div class="note xbg-info">
            <i class="fa fa-info-circle"></i> Odhady metodou ekologické inference. Zobrazeny přesuny voličů mezi stranami přesahující 25 000 lidí.<br />
            CC-BY Michal Škop, KohoVolit.eu, VolebníKalkulačka.cz
        </div>
        <div class="hithit text-center bg-primary">
            <h3></h3>
            <h3>Podpořte Volební kalkulačku pro prezidentské volby!</h3>
            <h3>www.Hithit.cz/VolebniKalkulacka</h3>
        </div>
    <script>
        // var formatNumber = d3.format(",.0f");

        d3.csv("list_2017_filtered.csv", function (er,d2017) {
            d3.csv("list_2013_filtered.csv", function(err,d2013) {
                d3.csv("matn_chart.csv", function(errr,source) {

                    var d13to17 = {};
                    var party2color = {};
                    d2013.forEach(function(d) {
                        d13to17[d['party']] = {}
                        party2color[d['party']] = d['color']
                    });
                    d2017.forEach(function(d) {
                        party2color[d['party']] = d['color']
                    });
                    var d17to13 = {};
                    source.forEach(function(d) {
                        d17to13[d['name']] = {}
                        for (k in d) {
                            if (k != 'name') {
                                d17to13[d['name']][k] = parseInt(d[k])
                                d13to17[k][d['name']] = parseInt(d[k])
                            }
                        }
                    })



                    var names = source['name'];
                    var i = QueryString.i;
                    if (i === undefined) {
                        i = 1;
                    }
                    var party = d2017[i]['party']
                    var prev_party = d2017[i]['previous']
                    var data = source[i];

                    d3.select("#h1name")
                        .html(d2017[i]['party_name']);
                    d3.select("#h1pic")
                        .html("<img class='logo' src='./" + d2017[i]['party'] + ".png' />");
                    d3.selectAll(".shortname")
                        .html(d2017[i]['party'])

                    var sumout = 0
                    for (k in d13to17[prev_party]) {
                        if (k != prev_party) {
                            sumout += d13to17[prev_party][k]
                        }
                    }

                    if (d2017[i]['votes'] > 1000000) {
                        var h = 700;
                    } else {
                        var h = 400;
                    }

                    var margin = {top: 100, right: 10, bottom: 10, left: 10},
                        width = 930 - margin.left - margin.right,
                        height = h - margin.top - margin.bottom;
                    var svg = d3.select("#chart").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 + ")");

                    maxsum = Math.max(sumout, d2017[i]['votes'])

                    var x = d3.scale.linear()
              		             .domain([0, 100])
              		             .range([0, width])
              		var y = d3.scale.linear()
              		             .domain([0, maxsum * 1.1])
              		             .range([0, height]);

                    // basic rectangle
                    var offset = (maxsum - d2017[i]['votes']) / 2;
                    svg.append("rect")
                        .attr("width", x(20))
                        .attr("height", y(d2017[i]['votes']))
                        .attr("x", x(40))
                        .attr("y", y(offset))
                        .attr("fill", d2017[i]['color']);

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "40px")
                        .text(party)

                    svg.append("text")
                        .attr("x", x(50))
                        .attr("y", function() {
                            return y(d2017[i]['votes']/2 * 1.3 + offset);
                        })
                        .attr("dy", 0)
                        .attr("text-anchor", "middle")
                        .attr("fill", "white")
                        .attr("font-size", "20px")
                        .text(customFormat(d2017[i]['votes']))

                    // prepare out and in rectangles
                    var outs = []
                    for (k in d13to17[prev_party]) {
                        if ((d13to17[prev_party][k] > 25000) && (party != k)) {
                            outs.push({
                                'name': k,
                                'value': d13to17[prev_party][k],
                                'color': party2color[k]
                            })
                        }
                    }
                    outs.sort(function(a, b) {
                        return b.value - a.value;
                    });
                    var ins = []
                    for (k in d13to17) {
                        if ((d13to17[k][party] > 25000) && (prev_party != k)) {
                            ins.push({
                                'name': k,
                                'value': d13to17[k][party],
                                'color': party2color[k]
                            })
                        }
                    }
                    ins.sort(function(a, b) {
                        return b.value - a.value;
                    });

                    s = 0
                    outs.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    outs.forEach(function (out, k) {
                        outs[k]['y0'] = offset;
                        outs[k]['y1'] = offset + y(out['value'])
                        outs[k]['half'] = (outs[k]['y1'] + outs[k]['y0']) / 2
                        offset = outs[k]['y1'] + 15;
                        outs[k]['arrow'] = [
                            {"x": x(76.98), "y": outs[k]['y0']}, {"x": x(79), "y": outs[k]['half']}, {"x": x(76.98), "y": outs[k]['y1']}, {"x": x(76.98), "y": outs[k]['y0']}
                        ]
                    });

                    s = 0
                    ins.forEach(function (out,k) {
                        s += y(out['value'])
                        s += 15
                    })
                    s = s - 15
                    var offset = (y(maxsum) - s) / 2;
                    ins.forEach(function (inn, k) {
                        ins[k]['y0'] = offset;
                        ins[k]['y1'] = offset + y(inn['value'])
                        ins[k]['half'] = (ins[k]['y1'] + ins[k]['y0']) / 2
                        offset = ins[k]['y1'] + 15;
                        ins[k]['arrow'] = [
                            {"x": x(34.95), "y": ins[k]['y0']}, {"x": x(37), "y": ins[k]['half']}, {"x": x(34.95), "y": ins[k]['y1']},
                             {"x": x(34.95), "y": ins[k]['y0']}
                        ]
                    });


                    var arrowFunction = d3.svg.area()
                                .x(function(d) { return d.x; })
                                .y1(function(d) { return d.y; });

                    // arrows out
                    var rectouts = svg.selectAll(".rectout")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(63))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", d2017[i]['color']);

                    var arrowouts = svg.selectAll(".arrowout")
                       .data(outs)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", d2017[i]['color']);

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(70))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4.5), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4.5), 18)
                            })
                            .text(function(d) {
                                return customFormat(my_round(d['value']))
                            })

                    //arrows in
                    svg.selectAll(".rectin")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(14))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(21))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".arrowin")
                       .data(ins)
                           .enter()
                        .append("path")
                            .attr("d", function(d) {
                                return arrowFunction(d['arrow']);
                            })
                            .attr("fill", function(d) {
                                return d['color']
                            });

                    svg.selectAll(".textin")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(28))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4.5), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4.5), 18)
                            })
                            .text(function(d) {
                                return customFormat(my_round(d['value']))
                            })
                            .attr("class", "number")

                    // outs
                    svg.selectAll(".rectend")
                       .data(outs)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(84))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textout")
                       .data(outs)
                           .enter()
                        .append("text")
                            .attr("x", x(92))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })
                    // ins
                    svg.selectAll(".rectstart")
                       .data(ins)
                           .enter()
                       .append("rect")
                            .attr("width", x(16))
                            .attr("height", function(d) {
                                return d['y1'] - d['y0'];
                            })
                            .attr("x", x(0))
                            .attr("y", function(d) {
                                return d['y0'];
                            })
                            .attr("fill", function(d){
                                return d['color'];
                            });

                    svg.selectAll(".textstart")
                       .data(ins)
                           .enter()
                        .append("text")
                            .attr("x", x(8))
                            .attr("y", function(d){
                                return d['half'] + Math.max(y(d['value'] / 4), 20)/3
                            })
                            .attr("text-anchor", "middle")
                            .attr("fill", "white")
                            .attr("font-size", function(d) {
                                return Math.max(y(d['value'] / 4), 20)
                            })
                            .text(function(d) {
                                return d['name']
                            })

                    svg.append("text")
                        .text(function (d) { return "Přišli k " + party;})
                        .attr("text-anchor", "middle")
                        .attr("x", x(20))
                        .attr("y", -30)
                        .attr("class", "label-small")
                        .style("font-size", "1.2em")
                        .style("font-weight", "bold")

                    svg.append("text")
                        .text(function (d) { return "Odešli od " + party;})
                        .attr("text-anchor", "middle")
                        .attr("x", x(80))
                        .attr("y", -30)
                        .attr("class", "label-small")
                        .style("font-size", "1.2em")
                        .style("font-weight", "bold")

                    var pause = 0;

                })
            })
        })

        function my_round(n) {
            if (n < 100000) {
                return Math.round(n/10000)*10000;
            }
            return Math.round(n/20000)*20000;
        }
        function customFormat(nStr)
            {
            	nStr += '';
            	x = nStr.split('.');
            	x1 = x[0];
            	x2 = x.length > 1 ? '.' + x[1] : '';
            	var rgx = /(\d+)(\d{3})/;
            	while (rgx.test(x1)) {
            		x1 = x1.replace(rgx, '$1' + ' ' + '$2');
            	}
            	return x1 + x2;
            }

        var QueryString = function () {
        // This function is anonymous, is executed immediately and
        // the return value is assigned to QueryString!
          var query_string = {};
          var query = window.location.search.substring(1);
          var vars = query.split("&");
          for (var i=0;i<vars.length;i++) {
            var pair = vars[i].split("=");
                // If first entry with this name
            if (typeof query_string[pair[0]] === "undefined") {
              query_string[pair[0]] = decodeURIComponent(pair[1]);
                // If second entry with this name
            } else if (typeof query_string[pair[0]] === "string") {
              var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
              query_string[pair[0]] = arr;
                // If third or later entry with this name
            } else {
              query_string[pair[0]].push(decodeURIComponent(pair[1]));
            }
          }
          return query_string;
        }();

    </script>
</body>
</html>

join_data.py

# join the 2013 and 2017 data for ei
import csv
import random


def n2(n):
    if n < 10:
        return "0" + str(n)
    else:
        return str(n)


def n(s):
    if s == '':
        return 0
    else:
        return int(s)


# data rows
d = {
    '2013': {},
    '2017': {}
}
# stations in municipality for matching in changed numbering municipality
stations = {
    '2013': {},
    '2017': {}
}
# selected data, filtered out 6 % of polling stations
data = {}
for y in d:
    with open("data_" + y + ".csv") as fin:
        dr = csv.DictReader(fin)
        for row in dr:
            try:
                stations[y][row['OBEC']]
            except Exception:
                stations[y][row['OBEC']] = []
                d[y][row['OBEC']] = {}
            stations[y][row['OBEC']].append(row['OKRSEK'])
            d[y][row['OBEC']][row['OKRSEK']] = row

for muni in d['2017']:
    same = True
    for okr in stations['2017'][muni]:
        try:
            d['2013'][muni][okr]
        except Exception:
            same = False
    if same:
        data[muni] = {}
        for okr in stations['2017'][muni]:
            data[muni][okr] = {}
            data[muni][okr]['2013'] = d['2013'][muni][okr]
            data[muni][okr]['2017'] = d['2017'][muni][okr]
    else:
        try:
            if len(stations['2013'][muni]) == len(stations['2017'][muni]):
                data[muni] = {}
                for i in range(0, len(stations['2013'][muni])):
                    o2017 = stations['2017'][muni][i]
                    o2013 = stations['2013'][muni][i]
                    data[muni][o2017] = {}
                    data[muni][o2017]['2013'] = d['2013'][muni][o2013]
                    data[muni][o2017]['2017'] = d['2017'][muni][o2017]
            else:
                print(muni, len(stations['2017'][muni]), '::not the same number or stations')
        except Exception:
            print(muni, len(stations['2017'][muni]), '::not in 2013')

# filter parties, randomly select some districts to be able to perfomr EI
parties = {
    '2013': [1, 3, 4, 6, 11, 17, 19, 20],
    '2017': [1, 4, 7, 8, 15, 20, 21, 24, 29]
}

with open("data_filtered_random.csv", "w") as foutR:
    select = 0.25
    header = []
    for k in parties['2013']:
        header.append('A' + str(k))
    header.append('A997')
    header.append('A998')
    header.append('A999')
    for k in parties['2017']:
        header.append('B' + str(k))
    header.append('B997')
    header.append('B998')
    header.append('B999')
    drR = csv.DictWriter(foutR, header)
    drR.writeheader()
    with open("data_filtered.csv", "w") as fout:
        dr = csv.DictWriter(fout, header)
        dr.writeheader()
        for muni in data:
            for okr in data[muni]:
                row = {}
                for y in parties:
                    if y == '2013':
                        code = 'A'
                    else:
                        code = 'B'
                    s = 0
                    for k in parties[y]:
                        try:
                            num = int(data[muni][okr][y]['HLASY_' + n2(k)])
                        except Exception:
                            num = 0
                        s += num
                        row[code + str(k)] = num
                    row[code + '997'] = int(data[muni][okr][y]['HLASY']) - s
                    row[code + '999'] = int(data[muni][okr][y]['VOLICI']) - int(data[muni][okr][y]['HLASY'])

                if int(data[muni][okr]['2013']['VOLICI']) >= int(data[muni][okr]['2017']['VOLICI']):
                    row['B998'] = int(data[muni][okr]['2013']['VOLICI']) - int(data[muni][okr]['2017']['VOLICI'])
                    row['A998'] = 0
                else:
                    row['A998'] = int(data[muni][okr]['2017']['VOLICI']) - int(data[muni][okr]['2013']['VOLICI'])
                    row['B998'] = 0
                if (1.25 * int(data[muni][okr]['2013']['VOLICI']) >= int(data[muni][okr]['2017']['VOLICI'])) and (0.8 * int(data[muni][okr]['2013']['VOLICI']) <= int(data[muni][okr]['2017']['VOLICI'])):
                    dr.writerow(row)
                    if random.random() < select:
                        drR.writerow(row)

list_2013_filtered.csv

party_id,party_name,party,color,party_group
20,Komunistická strana Čech a Moravy,KSČM,#8c0000,KSČM
17,Úsvit přímé demokracie Tomia Okamury,Úsvit,#f51941,Úsvit
1,Česká strana sociálně demokratická,ČSSD,#F07D00,ČSSD
19,ANO 2011,ANO,#261060,ANO
11,Křesťanská a demokratická unie - Československá strana lidová,KDU-ČSL,#e6ac21,KDU-ČSL
6,Občanská demokratická strana,ODS,#004494,ODS
4,TOP 09,TOP 09,#723769,TOP 09
3,Česká pirátská strana,Piráti,#000000,Piráti
,,neparlamentní,#888,neparlamentní
,,odešlí,#888,přibylí
,,nevoliči,#888,nevoliči

list_2017_filtered.csv

party_id,party_name,party,color,party_group,votes,previous
8,Komunistická strana Čech a Moravy,KSČM,#8c0000,KSČM,393100,KSČM
29,Strana přímé demokracie - Tomio Okamura,SPD,#eb1b26,SPD,538574,Úsvit
4,Česká strana sociálně demokratická,ČSSD,#f1953e,ČSSD,368347,ČSSD
21,ANO 2011,ANO,#261060,ANO,1500113,ANO
24,Křesťanská a demokratická unie – ČS strana lidová,KDU-ČSL,#e6ac21,KDU-ČSL,293643,KDU-ČSL
1,Občanská demokratická strana,ODS,#004494,ODS,572962,ODS
20,TOP 09,TOP 09,#723769,TOP 09,268811,TOP 09
15,Česká pirátská strana,Piráti,#000000,Piráti,546393,Piráti
7,Starostové a nezávislí,STAN,#5d8c00,STAN,262157,STAN
,,neparlamentní,#888,ostatní,,neparlamentní
,,přibylí,#888,přibylí,,přibylí
,,nevoliči,#888,nevoliči,,nevoliči

matn_chart.csv

name,KSČM,Úsvit,ČSSD,ANO,KDU-ČSL,ODS,TOP 09,Piráti,neparlamentní,odešlí,nevoliči
KSČM,254240.33761823,15254.9561311596,27524.0801666488,14661.461412228,7060.6503764015,6606.2215657717,6568.8162519496,4881.4612660556,8067.301072003,3819.8452305577,48180.5033989776
SPD,110885.659896705,102298.917625353,69897.7099995061,51745.5319231317,10481.6355602412,9642.1208214317,7085.8426458823,14201.0764789418,35216.8066028686,8504.0678863227,114819.093021927
ČSSD,26027.692578728,10271.9827739147,201700.891754245,13883.3915585773,18187.6621206604,8014.9992964658,8493.4192920129,7679.1331630617,17825.4570580152,4538.6671493368,49615.6937885127
ANO,199602.379621616,87619.3887537366,364385.497673393,448372.098296437,32259.8820710975,38810.3232289038,21723.3494718457,14646.9676159827,53113.7528059145,11588.66300895,206103.405374292
KDU-ČSL,5723.693363904,4347.5584599632,9448.3688663844,8447.6461234501,194148.336745684,4634.886935861,6218.3882643493,3161.7924101176,6494.4974140694,3102.5589235842,48981.0005014631
ODS,9045.7136077175,8432.0079495786,12695.1988701911,49997.2769304596,9825.6369130624,153956.22451832,177851.93713776,8351.0393541643,21743.7598107156,19463.2396303026,81557.2191297929
TOP 09,5654.6431691234,4507.6845616574,8908.426337364,9894.8788829583,5535.0040007416,14451.3395811804,98583.1795880245,4474.3548491156,9338.7020503307,8143.5039505982,81205.3848081083
Piráti,9132.1926171196,10774.8796716244,17668.2869795353,63180.8567209001,13854.1200037068,67327.5909964825,136266.805220292,22911.8646555925,82126.7378611841,19571.7803315446,80233.9498011818
STAN,6919.8442976441,8893.7432758664,10577.9310669867,25383.5938004626,8602.9328431908,10030.9107825919,52475.0714498077,10841.9986740398,13936.1247669976,9637.8447953855,106021.841124621
neparlamentní,9901.9249809547,15330.2605191835,14246.8744935569,70881.6883821733,12669.9896863278,19399.0535416654,34513.2854022375,15159.558361107,56409.178921035,7667.6580360965,50522.604393226
přibylí,4200.4368488644,2984.0157325085,6524.984061325,5791.5965389629,3144.8998992596,3072.5851775286,4029.4459053668,1726.0335401903,4024.1169427956,914.8328495339,169625.640379655
nevoliči,101635.193285955,71227.9390835938,260703.397920503,143876.398961153,24791.0494570795,37682.669833029,17508.9503145272,19989.9403941614,163677.13974441,55406.4116310692,2304456.46399612

matn_filtered.csv

2989.94479034,47251.2306124,2495.26104718,4714.8540045,3565.87812321,2005.56249396,80688.4879505,1994.16697752,21837.2806654,4052.78713092,1508.76734299,50790.7788611
1508.65575994,1640.80223916,1870.27715993,1270.10237013,4138.77222821,889.299273267,2477.55523198,724.748618985,1678.3766394,3061.8225035,397.51562821,8884.07234729
38141.0559569,2081.77357675,11976.5702724,1517.64820612,28864.6769857,21748.1172909,5337.10663514,1548.40207754,2106.97843566,9457.16867432,949.864402243,3640.63748627
34680.6474202,2336.58433847,2130.41564824,1313.7006982,13671.8135917,4232.52470632,6872.81078384,1210.36486191,2330.15410934,5595.35670959,731.718394687,8190.90873747
2983.14187624,3303.68042038,1990.39210102,1787.84873116,2968.59559378,1203.6569198,7878.93725438,42917.6641845,2944.7033865,2584.11837734,765.758189562,4596.50296539
2022.74613451,2312.86673277,1912.57375247,2991.02923104,2305.82377917,977.834866268,15428.7907021,960.29936823,20408.1726844,5165.61682707,639.979413203,21107.2665087
7312.45183629,3238.67819045,4342.53659128,2717.56106356,18010.0563922,2177.48719272,104564.168867,1902.92009048,13938.4982848,8572.50951205,1240.97135079,33992.1606287
1839.39335322,4233.97028765,1551.10144317,58791.1063417,2267.30971872,1192.2509042,44418.9260869,1209.21526072,23463.4839941,2879.71601754,880.168906612,22911.3576855
9289.94796685,3334.07490133,3608.63850677,2050.05066606,19217.9756943,1801.40441592,11851.639111,1478.8036064,5216.85767907,13890.7818534,940.606379454,32541.2192195
4432.56497908,1010.91171362,2149.6930467,781.558184067,3370.23691826,1950.86243265,3541.13767382,722.790913216,2323.22643162,2115.39922455,181.874084583,11386.7443979
18125.4437052,10911.7504037,24635.7642524,10642.2444212,18171.663722,17652.6645527,46436.502369,10936.4205079,23005.2125702,11030.007683,37688.7178887,515679.607924

matn_filtered_adjusted.csv

12695.1988701911,201700.891754245,10577.9310669867,27524.0801666488,17668.2869795353,8908.426337364,364385.497673393,9448.3688663844,69897.7099995061,14246.8744935569,6524.984061325,260703.397920503
8351.0393541643,7679.1331630617,10841.9986740398,4881.4612660556,22911.8646555925,4474.3548491156,14646.9676159827,3161.7924101176,14201.0764789418,15159.558361107,1726.0335401903,19989.9403941614
177851.93713776,8493.4192920129,52475.0714498077,6568.8162519496,136266.805220292,98583.1795880245,21723.3494718457,6218.3882643493,7085.8426458823,34513.2854022375,4029.4459053668,17508.9503145272
153956.22451832,8014.9992964658,10030.9107825919,6606.2215657717,67327.5909964825,14451.3395811804,38810.3232289038,4634.886935861,9642.1208214317,19399.0535416654,3072.5851775286,37682.669833029
9825.6369130624,18187.6621206604,8602.9328431908,7060.6503764015,13854.1200037068,5535.0040007416,32259.8820710975,194148.336745684,10481.6355602412,12669.9896863278,3144.8998992596,24791.0494570795
8432.0079495786,10271.9827739147,8893.7432758664,15254.9561311596,10774.8796716244,4507.6845616574,87619.3887537366,4347.5584599632,102298.917625353,15330.2605191835,2984.0157325085,71227.9390835938
49997.2769304596,13883.3915585773,25383.5938004626,14661.461412228,63180.8567209001,9894.8788829583,448372.098296437,8447.6461234501,51745.5319231317,70881.6883821733,5791.5965389629,143876.398961153
9045.7136077175,26027.692578728,6919.8442976441,254240.33761823,9132.1926171196,5654.6431691234,199602.379621616,5723.693363904,110885.659896705,9901.9249809547,4200.4368488644,101635.193285955
21743.7598107156,17825.4570580152,13936.1247669976,8067.301072003,82126.7378611841,9338.7020503307,53113.7528059145,6494.4974140694,35216.8066028686,56409.178921035,4024.1169427956,163677.13974441
19463.2396303026,4538.6671493368,9637.8447953855,3819.8452305577,19571.7803315446,8143.5039505982,11588.66300895,3102.5589235842,8504.0678863227,7667.6580360965,914.8328495339,55406.4116310692
81557.2191297929,49615.6937885127,106021.841124621,48180.5033989776,80233.9498011818,81205.3848081083,206103.405374292,48981.0005014631,114819.093021927,50522.604393226,169625.640379655,2304456.46399612

matn_filtered_adjusted_reordered.csv

254240.33761823,110885.659896705,26027.692578728,199602.379621616,5723.693363904,9045.7136077175,5654.6431691234,9132.1926171196,6919.8442976441,9901.9249809547,4200.4368488644,101635.193285955
15254.9561311596,102298.917625353,10271.9827739147,87619.3887537366,4347.5584599632,8432.0079495786,4507.6845616574,10774.8796716244,8893.7432758664,15330.2605191835,2984.0157325085,71227.9390835938
27524.0801666488,69897.7099995061,201700.891754245,364385.497673393,9448.3688663844,12695.1988701911,8908.426337364,17668.2869795353,10577.9310669867,14246.8744935569,6524.984061325,260703.397920503
14661.461412228,51745.5319231317,13883.3915585773,448372.098296437,8447.6461234501,49997.2769304596,9894.8788829583,63180.8567209001,25383.5938004626,70881.6883821733,5791.5965389629,143876.398961153
7060.6503764015,10481.6355602412,18187.6621206604,32259.8820710975,194148.336745684,9825.6369130624,5535.0040007416,13854.1200037068,8602.9328431908,12669.9896863278,3144.8998992596,24791.0494570795
6606.2215657717,9642.1208214317,8014.9992964658,38810.3232289038,4634.886935861,153956.22451832,14451.3395811804,67327.5909964825,10030.9107825919,19399.0535416654,3072.5851775286,37682.669833029
6568.8162519496,7085.8426458823,8493.4192920129,21723.3494718457,6218.3882643493,177851.93713776,98583.1795880245,136266.805220292,52475.0714498077,34513.2854022375,4029.4459053668,17508.9503145272
4881.4612660556,14201.0764789418,7679.1331630617,14646.9676159827,3161.7924101176,8351.0393541643,4474.3548491156,22911.8646555925,10841.9986740398,15159.558361107,1726.0335401903,19989.9403941614
8067.301072003,35216.8066028686,17825.4570580152,53113.7528059145,6494.4974140694,21743.7598107156,9338.7020503307,82126.7378611841,13936.1247669976,56409.178921035,4024.1169427956,163677.13974441
3819.8452305577,8504.0678863227,4538.6671493368,11588.66300895,3102.5589235842,19463.2396303026,8143.5039505982,19571.7803315446,9637.8447953855,7667.6580360965,914.8328495339,55406.4116310692
48180.5033989776,114819.093021927,49615.6937885127,206103.405374292,48981.0005014631,81557.2191297929,81205.3848081083,80233.9498011818,106021.841124621,50522.604393226,169625.640379655,2304456.46399612

matp_filtered.csv

0.0133542276082,0.211041919705,0.0111447823631,0.0210583264678,0.0159265643413,0.00895760286722,0.360385394718,0.00890670616816,0.0975335789786,0.0181012846688,0.00673872727389,0.226850884839
0.0528573947145,0.0574872902795,0.0655271936071,0.0444994173544,0.145006384564,0.0311575668582,0.0868038410755,0.0253923557909,0.0588037502416,0.107274280131,0.0139273922013,0.311263133182
0.299450859362,0.0163443006732,0.0940297579685,0.0119152720902,0.226620687648,0.170747564504,0.0419023838827,0.0121567251122,0.0165421876082,0.0742495774069,0.00745752062686,0.0285831631174
0.416349297336,0.0280512424033,0.0255761389755,0.0157712846585,0.16413332523,0.0508124507044,0.0825097036369,0.0145307137342,0.0279740459961,0.0671735681908,0.00878445075677,0.098333778377
0.039290640451,0.0435124191028,0.026215240053,0.0235475631367,0.039099052931,0.0158532356905,0.103772634236,0.565263933941,0.0387843712413,0.0340351449107,0.010085718664,0.0605400456422
0.0265337338752,0.0303394426662,0.0250885279665,0.0392353604219,0.0302470554637,0.012826923593,0.202389919092,0.012596898564,0.267707852038,0.067760901802,0.00839504431419,0.276878340203
0.0361984646121,0.0160322666722,0.0214966417072,0.0134526066212,0.0891542814327,0.0107791059488,0.517618775638,0.00941993015436,0.0689990509618,0.0424360651059,0.00614311841387,0.168269692731
0.0111048995594,0.0255615878461,0.00936440577143,0.354937311135,0.0136883427639,0.00719793105567,0.268168693699,0.00730034932034,0.141655199858,0.0173856000286,0.0053138102767,0.138321868686
0.0882890266945,0.031686100828,0.0342954753452,0.0194830992194,0.182642182189,0.0171200358853,0.112634611687,0.0140541294254,0.0495795335487,0.132014045099,0.00893925585385,0.309262504225
0.1304962163,0.0297615837023,0.063287692369,0.0230093380065,0.0992209178985,0.0574340516575,0.104252294104,0.0212792096216,0.0683965740753,0.062278070614,0.00535443473321,0.335229616918
0.024332198134,0.0146482964572,0.0330718688448,0.0142865026677,0.024394245421,0.0236975236841,0.0623379043665,0.0146814144251,0.0308829620657,0.0148070489599,0.0505945876967,0.692265447277

scraper.py

# prepage basic csv

import csv
import json


def n2(n):
    if n < 10:
        return "0" + str(n)
    else:
        return str(n)


with open("data_2017.csv", "w") as fout:
    header = ['OBEC', 'OKRSEK', 'VOLICI', 'OBALKY_VYDANE', 'OBALKY_ODEVZDANE', 'HLASY']
    for j in range(1, 32):
        header.append("HLASY_" + n2(j))
    dw = csv.DictWriter(fout, header)
    dw.writeheader()
    for i in range(1, 70):
        with open("json/" + str(i) + ".json") as fin:
            js = json.load(fin)
            if not isinstance(js['VYSLEDKY_OKRSKY']['OKRSEK'], list):
                js['VYSLEDKY_OKRSKY']['OKRSEK'] = [js['VYSLEDKY_OKRSKY']['OKRSEK']]
            for item in js['VYSLEDKY_OKRSKY']['OKRSEK']:
                d = {
                    "OBEC": item['@CIS_OBEC'],
                    "OKRSEK": item['@CIS_OKRSEK'],
                    "VOLICI": item['UCAST_OKRSEK']['@ZAPSANI_VOLICI'],
                    "OBALKY_VYDANE": item['UCAST_OKRSEK']['@VYDANE_OBALKY'],
                    "OBALKY_ODEVZDANE": item['UCAST_OKRSEK']['@ODEVZDANE_OBALKY'],
                    "HLASY": item['UCAST_OKRSEK']['@PLATNE_HLASY']
                }
                for j in range(1, 32):
                    d["HLASY_" + n2(j)] = ''
                try:
                    if not isinstance(item['HLASY_OKRSEK'], list):
                        item['HLASY_OKRSEK'] = [item['HLASY_OKRSEK']]
                    for p in item['HLASY_OKRSEK']:
                        d["HLASY_" + n2(int(p['@KSTRANA']))] = p['@HLASY']
                except Exception:
                    nothing = None
                dw.writerow(d)