block by nitaku e4b91e095376c896f0651ef567435623

World map

Full Screen

A reference gist for the implementation of a general purpose map of the world in d3 version 4.

The projection of choice is the Winkel Tripel projection, which has a very low average error in representing area, direction, and distance (see this article for more details).

Data is obtained from Mike Bostock’s topojson world atlas, which is in turn based on the Natural Earth dataset.

Zooming and panning is enabled via SVG transforms, so no reprojection and clipping operations happen during user interaction.

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var contents, graticule, height, path, projection, svg, width, zoom, zoomable_layer;

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  zoomable_layer = svg.append('g');

  zoom = d3.zoom().scaleExtent([-Infinity, Infinity]).on('zoom', function() {
    return zoomable_layer.attrs({
      transform: d3.event.transform
    });
  });

  svg.call(zoom);

  projection = d3.geoWinkel3().rotate([0, 0]).center([0, 0]).scale((width - 3) / (2 * Math.PI)).translate([width / 2, height / 2]);

  path = d3.geoPath(projection);

  graticule = d3.geoGraticule();

  svg.append('defs').append('path').datum(graticule.outline()).attrs({
    id: 'sphere',
    d: path
  });

  zoomable_layer.append('use').attrs({
    "class": 'sphere_fill',
    'xlink:href': '#sphere'
  });

  contents = zoomable_layer.append('g');

  zoomable_layer.append('path').datum(graticule).attrs({
    "class": 'graticule',
    d: path
  });

  zoomable_layer.append('use').attrs({
    "class": 'sphere_stroke',
    'xlink:href': '#sphere'
  });

  d3.json('https://unpkg.com/world-atlas@1/world/50m.json', function(geo_data) {
    return contents.append('path').datum(topojson.feature(geo_data, geo_data.objects.countries)).attrs({
      "class": 'countries',
      d: path
    });
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>World map</title>
  <link type="text/css" href="index.css" rel="stylesheet"/>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
  <script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
  <script src="//d3js.org/topojson.v2.min.js"></script>
</head>
<body>
  <svg></svg>
  <script src="index.js"></script>
</body>
</html>

index.coffee

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height


# ZOOM
zoomable_layer = svg.append 'g'

zoom = d3.zoom()
  .scaleExtent [-Infinity, Infinity]
  .on 'zoom', () ->
    zoomable_layer
      .attrs
        transform: d3.event.transform

svg.call(zoom)

# PROJECTION
projection = d3.geoWinkel3()
  .rotate [0, 0]
  .center [0, 0]
  .scale (width - 3) / (2 * Math.PI)
  .translate [width/2, height/2]
path = d3.geoPath projection

# GRATICULE and OUTLINE
graticule = d3.geoGraticule()

svg.append 'defs'
  .append 'path'
    .datum graticule.outline()
    .attrs
      id: 'sphere'
      d: path

zoomable_layer.append 'use'
  .attrs
    class: 'sphere_fill'
    'xlink:href': '#sphere'

contents = zoomable_layer.append 'g'

zoomable_layer.append 'path'
  .datum graticule
  .attrs
    class: 'graticule'
    d: path

zoomable_layer.append 'use'
  .attrs
    class: 'sphere_stroke'
    'xlink:href': '#sphere'

d3.json 'https://unpkg.com/world-atlas@1/world/50m.json', (geo_data) ->
  contents.append 'path'
    .datum topojson.feature(geo_data, geo_data.objects.countries)
    .attrs
      class: 'countries'
      d: path
    

index.css

body, html {
  padding: 0;
  margin: 0;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
  background: white;
}

.sphere_stroke {
  fill: none;
  stroke: black; 
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

.sphere_fill {
  fill: white;
}

.graticule {
  fill: none;
  stroke: #777;
  stroke-width: 0.5px;
  stroke-opacity: 0.5;
  vector-effect: non-scaling-stroke;
}

.countries {
  fill: black;
  stroke: white;
  stroke-width: 0.5;
  vector-effect: non-scaling-stroke;
}