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

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

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

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)