Skip to main content
2 of 5
deleted 48 characters in body
obrob
  • 151
  • 3

Visualize data on interactive map + plot via d3.js

I created an interactive web map with d3. I am fairly new to web programming and am interested what you think about the code. The map is based on unemployment rates for each NUTS-2 regeion in europe. I created a sample JSON, so you maybe can use this for a working reproducible example. I hope the code provided is not too much.

sample JSON

{
"type": "FeatureCollection",
"crs": {
    "type": "name",
    "properties": {
        "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
    }
},
"features": [{
        "type": "Feature",
        "geometry": {
            "type": "Polygon",
            "coordinates": [[[10.00, 45.00], [10.00, 50.00], [15.00, 50.00], [15.00, 45.00], [10.00, 45.00]]]
        },
        "properties": {
            "2005": "7.8",
            "2006": "7.3",
            "2007": "4.1",
            "2008": "4.2",
            "2009": "7",
            "2010": "4",
            "2011": "1.8",
            "2012": "4.6",
            "2013": "4.3",
            "2014": "4.8",
            "2015": "7.2",
            "NUTS_ID": "AT11",
            "STAT_LEVL_": 2,
            "SHAPE_Leng": 4.87315,
            "SHAPE_Area": 0.467459,
            "Region": "Burgenland (AT)"
        },
        "id": 1453
    }, {
        "type": "Feature",
        "geometry": {
            "type": "Polygon",
            "coordinates": [[[5.00, 45.00], [5.00, 50.00], [10.00, 50.00], [10.00, 45.00], [10.00, 45.00]]]
        },
        "properties": {
            "2005": "12.7",
            "2006": "12.5",
            "2007": "12",
            "2008": "10.7",
            "2009": "12.7",
            "2010": "10.9",
            "2011": "12.5",
            "2012": "12.6",
            "2013": "15",
            "2014": "15.1",
            "2015": "15.2",
            "NUTS_ID": "AT12",
            "STAT_LEVL_": 2,
            "SHAPE_Leng": 8.788773,
            "SHAPE_Area": 2.321136,
            "Region": "Niederösterreich"
        },
        "id": 1454
    }
]
}

HTML

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title> Europe Unemployment (NUTS-2 Regions) </title>
    <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js'></script>
    <script src="//code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
    <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.6/d3.min.js'></script>
    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.0/themes/smoothness/jquery-ui.css">
    <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' type='text/css'>
    <link rel="stylesheet" href="css/customCSS" type="text/css">
</head>

<body>

     <h1 style="color:#ffffff;">Europe - Unemploymentrate</h1>

    <button id="resetButton" class="btn">Reset</button>

    <div class="wrapper">
        <div id="svgMap"></div>
        <div class="infobox-wrapper">
            <div class="infobox" id="regionName">
                Europe
            </div>
            <div class="infobox" id="infobox-content">
                <p>Year: <span class="infoBox" id="currentYear">2005</span></p>
                <p>Unemployment: <span class="infoBox" id="unemploymentRate">100</span> %</p>
                <p>Inhabitants: <span class="infoBox">default</span></p>
                <p>Country: <span class="infoBox">default</span></p>
            </div>
            <div class="infobox" id="infobox-plot">
                <div id="svgPlot"></div>
            </div>
        </div>
    </div>

    <div class="wrapper">
        <div id="playButtonSlider" class="btn glyphicon glyphicon-play"></div>
        <div id="slider"></div>
    </div>

    <script src="javascript/EUmap.js"></script>

</body>

</html>

CSS

  body {
      background-color: #18121E;
      margin: 50px;
  }

  #svgMap {
      width: 65%;
      height: 100%;
      background-color: #233237;
      border: 2px solid grey;
      border-radius: 10px;
      float: left;
  }

  .infobox-wrapper {
    width: 35%;
    height: 100%;
    padding: 5px 0px 0px 15px;
    float: right;
  }

  .infobox {
    margin-top: 15px;
    padding: 5px 5px 5px 10px;
    border: 2px solid grey;
    border-radius: 10px;
    background-color:#233237;
    color:#ffffff;
  }

  /* RESET BUTTON */

  .btn {
    -webkit-border-radius: 15;
    -moz-border-radius: 15;
    border-radius: 15px;
    background: #7c100a;
    border: solid #ffffff 1px;
    text-decoration: none;

  }

  .btn:hover {
    background: #7d5f5e;
    text-decoration: none;
  }

  #resetButton {
    margin: 15px 0 0 10px;
    position: absolute;
    color: #ffffff;
    font-size:0.8em;
  }

  /* SLIDER + SLIDER PLAY BUTTON */
  #playButtonSlider {
    margin: 10px 0 0 15px;
    float: left;
    font-size:0.7em;
  }

  .glyphicon {
    color: #ffffff;
    padding: 3px 5px 3px 5px;
  }

  #slider {
      margin: 15px 0 0 25px;
      width: 50%;
      background-color: #233237;
      float: left;
  }

  #slider label {
      position: absolute;
      width: 20px;
      margin-top: 20px;
      margin-left: -20px;
      text-align: center;
      font-size: 0.8em;
      font-family: "Lucida Console", Monaco, monospace;
      color: grey;
  }

  .ui-slider-tick-mark {
      display: inline-block;
      width: 2px;
      background: grey;
      height: 10px;
      position: absolute;
  }

  .ui-widget-content .ui-corner-all {
      background: #7c100a;
      border-color: #ffffff;
  }

  .ui-widget-content .ui-corner-all:hover {
      background: #7d5f5e;
      border-color: #ffffff;
  }

  /* PLOT */

  .euPlot {
      fill: steelblue;
      stroke: white;
  }

  .yAxis path{
    stroke: white;
    color:white;
  }
  .yAxis line{
    stroke: white;
    color:white;
  }
  .yAxis text{
    fill: white;
  }

  .xAxis path{
    stroke: white;
  }
  .xAxis line{
    stroke: white;
  }
  .xAxis text{
    fill: white;
  }

Javascript

      // Slider
  $("#slider").slider({
          value: 2005,
          min: 2005,
          max: 2015,
          step: 0.1,
          create: function(event, ui) {
              setSliderTicks(event.target);
          },
      })
      .each(function() {
          var opt = $(this).data().uiSlider.options;
          var vals = opt.max - opt.min;
          for (var i = 0; i <= vals; i++) {
              var el = $('<label>' + (i + opt.min) + '</label>').css('left', (i / vals * 100) + '%');
              $("#slider").append(el);
          }
      });

  function setSliderTicks(el) {
      var $slider = $(el);
      var max = $slider.slider("option", "max");
      var min = $slider.slider("option", "min");
      var spacing = 100 / (max - min);

      $slider.find('.ui-slider-tick-mark').remove();
      for (var i = 1; i < max - min; i++) {
          $('<span class="ui-slider-tick-mark"></span>').css('left', (spacing * i) + '%').appendTo($slider);
      }
  }
  // Responsive Design
  var windowWidth = window.innerWidth;
  var windowHeight = window.innerHeight;

  var wrapperWidth = 0.9 * windowWidth;
  var wrapperHeight;

  if (windowWidth < windowHeight) {
    wrapperHeight = 0.5 * windowHeight;
  } else {
    wrapperHeight = 0.75 * windowHeight;
  }

  $(".wrapper").css("width", wrapperWidth);
  $(".wrapper").css("height", wrapperHeight);

  // Map EU
  var svgMapWidth =  (0.65 * wrapperWidth)  - 4;
  var svgMapHeight = wrapperHeight - 4;

  var myProjection = d3.geoMercator()
      .center([8.00, 57.00])
      .scale(500)
      .translate([svgMapWidth / 2, svgMapHeight / 2]);

  var geoPath = d3.geoPath().projection(myProjection);

  var color = d3.scaleThreshold()
      .domain([0, 2, 4, 6, 8, 10, 15, 20, 25, 50])
      .range(["#006837", "#1a9850", "#66bd63", "#a6d96a", "#d9ef8b", "#fee08b", "#fdae61", "#f46d43", "#d73027", "#a50026", "#808080"]);

  var svgEurope = d3.select("#svgMap").append("svg")
      .attr("width", svgMapWidth)
      .attr("height", svgMapHeight);

  var g = svgEurope.append("g");

  var zoom = d3.zoom()
      .scaleExtent([0.25, 8])
      .on("zoom", zoomed);

  svgEurope.call(zoom);

  d3.queue()
    .defer(d3.json, "data/sampleData.json")
    .await(initializeData);

  function initializeData(error, euRegionsJSON) {
    if (error) throw error;
    $('#slider').slider().bind('slidechange',e => renderMap(e,euRegionsJSON, sliderValue)); //same as $('#slider').slider().bind('slidechange',function e() {return renderMap(e,euRegionsJSON, sliderValue)});
    $('#slider').trigger('slidechange');
  };

  var sliderValue = 2005;

  $('#slider').slider().bind('slidechange', function(event, ui) {
      sliderValue = $('#slider').slider("value");
      sliderValue = Math.floor(sliderValue);
      $("#currentYear").text(sliderValue);
  });

  $("#playButtonSlider").on("click",playButtonSlider);
  function playButtonSlider() {
    function startTimer(counter) {
      if(counter <= 10) {
        setTimeout(function(){
          sliderValue = $("#slider").slider('value',2005+counter);
          counter += 0.1;
          startTimer(counter);
        },1);
      }
    }
    startTimer(0);
  }

  function renderMap(error, euRegionsJSON, sliderValue) {
    g.selectAll(".euMap").remove();
    g.selectAll("path")
        .data(euRegionsJSON.features)
        .enter().append("path")
        .attr("d", geoPath)
        .attr("class", "euMap")
        .attr("fill", function(d) {return color(d.properties[sliderValue]);})
        .style("stroke", "black")
        .style("stroke-width", "0.4px")
        .on("click",function(d) {
            highlight(this);
            setInfoBoxContent(d);
            createPlot(d);
        });
  }

  function highlight(mapClickedPath){
    d3.selectAll('path').style('fill',null);
    d3.select(mapClickedPath).style("fill", "#3333ff");
  }

  function setInfoBoxContent(mapClickedFeature) {
    $('#regionName').text(mapClickedFeature.properties.Region);
    $('#unemploymentRate').text(mapClickedFeature.properties[sliderValue]);
  }

  function zoomed() {
    g.style("stroke-width", 1.5 / d3.event.transform.k + "px");
    g.attr("transform", d3.event.transform);
  }

  $("#resetButton").on("click",reset);
  function reset(){
    svgEurope.transition().call(zoom.transform, d3.zoomIdentity);
    d3.selectAll('path').style('fill',null);
    $('#regionName').text("Europe");
    $('#unemploymentRate').text("100");
  }

  // Barplot
  var margin = {top: 10, right: 20, bottom: 35, left: 20};

  var svgPlotWidth = (0.33 * wrapperWidth) - margin.left - margin.right;
  var svgPlotHeight = 250 - margin.top - margin.bottom;

  var svgEuropeDiagramm = d3.select("#svgPlot").append("svg")
      .attr("width", svgPlotWidth + margin.left + margin.right)
      .attr("height", svgPlotHeight + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var x = d3.scaleBand()
            .rangeRound([0, svgPlotWidth])
            .padding(0.25);
  var y = d3.scaleLinear()
            .rangeRound([svgPlotHeight, 0]);

  function createPlot(mapClickedFeature) {

    var getFeatureEntries = d3.entries(mapClickedFeature.properties)
    var featureEntries = [];

    for (var i=0;i<=10;i++){
      if (getFeatureEntries[i].value == "NA") {
        getFeatureEntries[i].value = 0;
        featureEntries.push(getFeatureEntries[i])
      } else {
        featureEntries.push(getFeatureEntries[i])
      }
    }

    x.domain(featureEntries.map(function(d) { return d.key; }));
    y.domain([0, 50]);

    svgEuropeDiagramm.selectAll(".euPlot").remove();
    svgEuropeDiagramm.selectAll(".euPlot")
        .data(featureEntries)
        .enter().append("rect")
        .attr("class", "euPlot")
        .attr("x", function(d) { return x(d.key); })
        .attr("width", x.bandwidth())
        .attr("y", function(d) { return y(d.value); })
        .attr("height", function(d) { return svgPlotHeight - y(d.value); });

    svgEuropeDiagramm.selectAll(".xAxis").remove();
    svgEuropeDiagramm.append("g")
        .attr("transform", "translate(0," + svgPlotHeight + ")")
        .attr("class","xAxis")
        .call(d3.axisBottom(x))
      .selectAll("text")
        .attr("y", 0)
        .attr("x", 9)
        .attr("dy", ".35em")
        .attr("transform", "rotate(90)")
        .style("text-anchor", "start");

    svgEuropeDiagramm.selectAll(".yAxis").remove();
    svgEuropeDiagramm.append("g")
        .attr("class","yAxis")
        .call(d3.axisLeft(y));
  }
obrob
  • 151
  • 3