Source: mapDrawer.js

var votes = [];
var quartiles = [[0.25,0.5,0.75],[0.25,0.5,0.75],[0.25,0.5,0.75],[0.25,0.5,0.75]];
var ranges = [[0.0,1.0],[0.0,1.0],[0.0,1.0],[0.0,1.0]];
var x_axis = [0,0,0,0];
var x = [0,0,0,0];
var width = 0;
var height = 0;

$(document).ready(initMaps);

/**
 * Initializes all maps, legends and boxplots and and starts aynchronous loading of map data
 */
function initMaps(){
  for (var i = 0; i < 4; i++) {
    votes.push(d3.map());
    // get map properties
    var svg = d3.select(".map"+i);
    width = $(".map"+i).width();
    height = $("#svgWrapper").height()/2-10;
    $(".map"+i).attr("height", height);

    // add scale
    var g = svg.append("g")
        .attr("class", "key")
        .attr("transform", "translate(30,14) scale(0.9,0.9)");

    // create scale variable x
    x[i] = d3.scaleLinear()
        .domain([0, 1])
        .rangeRound([0, width]); // size of scale top right

    // plot scale
    x_axis[i] = d3.axisBottom(x[i]);
    g.call(x_axis[i])
        .select(".domain")
        .remove();

    // plot boxplot
    var boxplot = g.append("g")
        .attr("class", "boxplot")
        .attr("transform", "translate(0,20)");

    var boxheight = 12;
    boxplot.append("line")
        .attr("x1", 0)
        .attr("x2", 0)
        .attr("y1", 0)
        .attr("y2", boxheight);

    boxplot.append("line")
        .attr("x1", width)
        .attr("x2", width)
        .attr("y1", 0)
        .attr("y2", boxheight);

    boxplot.append("line")
        .attr("x1", 0)
        .attr("x2", width)
        .attr("y1", boxheight/2)
        .attr("y2", boxheight/2);

    boxplot.append("rect")
        .attr("id", "lbox")
        .attr("x", width*0.25)
        .attr("y", 0)
        .attr("width", width*0.25)
        .attr("height", boxheight);

    boxplot.append("rect")
        .attr("id","rbox")
        .attr("x", width*0.5)
        .attr("y", 0)
        .attr("width", width*0.25)
        .attr("height", boxheight);

   g.append("text")
      .attr("class", "caption")
      .attr("x", x[i].range()[0])
      .attr("y", -6)
      .attr("fill", "#000")
      .attr("text-anchor", "start")
      .attr("font-weight", "bold")
      .attr("font-size", "14")
      .text("Van der Bellen");

    // Plot color scale
    values = [];
    for (let i=0; i<width; i++){
      var legenditem = {};
      legenditem.index = i;
      legenditem.value = i/width;
      legenditem.color = valueToColor(legenditem.value, i);
      values.push(legenditem);
    }
    var legend = g.selectAll("rect").data(values).enter().append("rect")
      .attr("fill", (d) => d.color)
      .attr("x",  (d) => d.index)
      .attr("width", 1)
      .attr("height", 6);

    // load map async
    d3.queue()
        .defer(d3.json, $(".map"+i).data("geo"))
        .defer(d3.csv, $(".map"+i).data("csv"), setVotes(i))
        .defer(d3.json, $(".map"+i).data("geo"))
        .await(readyMap(i, svg));

    /**
     * Returns a map construction function which should be called for every submap with the asynchronously loaded map data
     * 
     * @param {integer} i - the mapindex for which the vote should be set
     * @param {d3.Selection<SVGElement>} svg - the maps svg object selected via d3 selection
     * @returns {function} the map constuction function
    */
    function readyMap(i, svg) {
      return (error, json) => {
          if (error) throw error;

          // create projection
          var projection = d3.geoMercator().translate([0, 0]).scale(1);
          var path = d3.geoPath().projection(projection);
          // create temp geojson to get boundingbox for view bounds
          var boundsCollection = { type: "FeatureCollection", features: []}
          for (var key in json.objects) {
              if (json.objects.hasOwnProperty(key)) {
                  var layer = json.objects[key]; // Topojson unpacks one layer at a time
                  var geojson = topojson.feature(json, layer);
                  boundsCollection.features = boundsCollection.features.concat( geojson.features );
              };
          }
          // Calculate bounding box transforms for entire collection
          var b = path.bounds(boundsCollection),
              s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height * 1.20),
              t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2 + 20];
          projection.scale(s).translate(t); // Update the projection for view bounds

          // add element per map-part
          svg.append("g")
              .attr("class", "counties")
              .selectAll("path")
              .data(topojson.feature(json, json.objects.gemeinden).features)
              .enter().append("path")
              .attr("fill", (d) => valueToColor(d.vote = votes[i].get(+d.properties.iso), i))
              .attr("d", path)
              .append("title")
              .text((d) => +(d.vote*100).toFixed(2) + "%");
      }
    }
  }
}

/**
 * Returns a function to set votes of a specific submap referenced by the mapindex i
 * 
 * @param {integer} i - the mapindex for which the vote should be set
 * @returns {function} the votes setting function
*/
function setVotes(i) {
  return (d) => {
      let returnValue = votes[i].set(d.iso, d.vdb / d.gueltig);
      return returnValue;
  };
}

/**
 * Initiates a color update and shows a waiting spinner in the meantime
 */
function updateColors(){
    // Show spinner and call fill()
    console.log("Updating Colors");
    setTimeout("document.getElementById(\"svgSpinner\").style.display='initial'",0);
    setTimeout("fill()",100);
}

/**
 * Fills all maps and color scales with a new color and updates the boxplots as well
 */
function fill(){
    for (i = 0; i < 4; i++) {
      map = d3.select(".map"+i);
      q = quartiles[i];
      r = ranges[i];

      // Update scale number values
      x[i].domain(ranges[i]);
      map.selectAll(".key").transition().call(x_axis[i]); 

      // Update scale color values
      map.selectAll(".key rect").attr("fill", (d) => valueToColor(d.value*(r[1]-r[0])+r[0], i));
      
      // Update boxplot
      map.select(".boxplot #lbox").transition()
        .attr("x", width*q[0])
        .attr("width", width*(q[1]-q[0]));
      map.select(".boxplot #rbox").transition()
        .attr("x", width*quartiles[i][1])
        .attr("width", width*(q[2]-q[1]));
      
      // Update map colors
      map.selectAll(".counties path").attr("fill", (d) => valueToColor(d.vote, i));
    }
    document.getElementById("svgSpinner").style.display='none';
}