Search code examples
javascriptdictionaryd3.jsgeolocation

D3.js, Global Chloropleth / Heat Map . Make Legend Horizontal and Add Black Borders Around Countries


I've been playing around with d3.js for the first time and have managed to create a basic chloropleth map.

enter image description here

In essence there are 3 things remaining that i'd like to do, but am not familiar enough with d3 or Javascript to do them:

  1. Make legend horizontal and move it below the coast of Africa

  2. Add thin black border to all of the countries.

  3. Perhaps automatically crop out antartica? This can be done in post processing if not possible

Not sure if these tasks are impossible or easy as I have not worked much with d3.js and wasn't making much headway.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  @import url(//fonts.googleapis.com/css?family=Times+New+Roman);




  .countries {
    fill: none;
    stroke: #fff;
    stroke-linejoin: round;
  }
  .legendThreshold {
      font-size: 12px;
      font-family: sans-serif;
  }
  .caption {
      fill: #000;
      text-anchor: start;
      font-size: 14px;
  }
  /*      font-weight: bold;*/

  .anchorNode {
       font-family: "Times New Roman";
      font-size: 2px;
  }
  .legendLinear text.label {
     fill: '#fff'
       font-family: "Times New Roman";
      font-size: 2px;
}

  .legendThreshold text.label {
     fill: '#fff'
       font-family: "Times New Roman";
     font-size: 12px;

}
* {
 font-family: "Times New Roman", Times, serif;

}



/* font-size: 100%;*/

</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.24.0/d3-legend.js"></script>
<script>
// The svg
var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

// Map and projection
var path = d3.geoPath();
var projection = d3.geoMercator()
    .scale(width / 2 / Math.PI)
    .translate([width / 2, height / 2])
var path = d3.geoPath()
    .projection(projection);

// Data and color scale
var data = d3.map();
var colorScheme = d3.schemeReds[6];
colorScheme.unshift("#eee")
var colorScale = d3.scaleThreshold()
    .domain([1, 3, 5, 10, 20, 100])
    .range(colorScheme);

// Legend
var g = svg.append("g")
    
    .attr("class", "legendThreshold")
    .attr("transform", "translate(190,300)");

g.append("text")
    .attr("class", "caption")
    .attr("x", 0)
    .attr("y", -6)
    .style("font-size","12px")

    .text("Subscribers");
var labels = ['0', '1-3', '3-5', '5-10', '10-20', '20-100', '> 100'];
var legend = d3.legendColor()
    .labels(function (d) { return labels[d.i]; })
    .shapePadding(0)
    .scale(colorScale);
svg.select(".legendThreshold")
    .call(legend);

// Load external data and boot
d3.queue()
    .defer(d3.json, "http://enjalot.github.io/wwsd/data/world/world-110m.geojson")
    .defer(d3.csv, "https://gist.githubusercontent.com/palewire/d2906de347a160f38bc0b7ca57721328/raw/3429696a8d51ae43867633ffe438128f8c396998/mooc-countries.csv", function(d) { data.set(d.code, +d.total); })
    .await(ready);

function ready(error, topo) {
    if (error) throw error;

    // Draw the map
    svg.append("g")
        .attr("class", "countries")
        .selectAll("path")
        .data(topo.features)
        .enter().append("path")
            .attr("fill", function (d){
                // Pull data for this country
                d.total = data.get(d.id) || 0;
                // Set the color
                return colorScale(d.total);
            })
            .attr("d", path);
}
</script>

Solution

  • The first 2 are trivial. 1 is just a matter of changing the translate position of the legend and making it horizontal is just from looking at the d3-legend doc (https://d3-legend.susielu.com/#color-linear):

    var g = svg.append("g")
        .attr("class", "legendThreshold")
        .attr("transform", "translate(440,440)");
    
    ...and jump ahead for next bit...
    
    var legend = d3.legendColor()
        .labels(function (d) { return labels[d.i]; })
        .shapePadding(0)
        .orient('horizontal')
        .shapeWidth(40)
        .scale(colorScale);
    

    2 you change the stroke colour to black (#000) for .countries in the css

      .countries {
        fill: none;
        stroke: #000;
        stroke-linejoin: round;
      }
    

    3 is a bit more involved, but in the data (after a bit of console.logging the returned object) we know the key for Antarctica is "ATA", so we can filter that out of the returned topo.features data

    function ready(error, topo) {
        if (error) throw error;
    
      topo.features = topo.features.filter (function(d) { return d.id !== "ATA"; });
    

    See https://jsfiddle.net/13zqyvrL/