The purpose of the tutorial is to make a world map using d3.js library. You can find many tutorials on this topic but after going through them, you will be left with some unanswered questions, which are very important to master the art of creating maps using d3.js. I am assuming that you are familiar with d3.js, SVG and javascript. Let's code now.

Setup

To begin with we need HTML page. Create index.html file and load jquery, d3.js and TopoJSON library. Note a div with class map where we are going to render map.

  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>World Map</title>
    <style>/** Write CSS here **/</style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/2.2.0/topojson.min.js"></script>
</head>
<body>
    <div class="map"></div>
    <script>/** Write JavaScript Here **/</script>
</body>
</html>

Write following script in place of 'Write JavaScript Here' before closing body tag.

  var jMap = $(".map"),
    height = jMap.height(),
    width = jMap.width(),
    mapJsonUrl = 'https://ucarecdn.com/8e1027ea-dafd-4d6c-bf1e-698d305d4760/world110m2.json',
    svg = d3.select(".map").append("svg").attr("width", width).attr("height", height);

Lets define the height and width of div with class map where we are going to render map. mapJsonUrl is data file for the world map which is generated by Mike Bostock using geoJSON and TopoJSON libraries. Generating that data file is out of scope for this article.

  d3.select(".map").append("svg").attr("width", width).attr("height", height)

As you can guess above line of code selects the element with class map and appends svg element with a height and width that we have already calculated.

Loading data

  d3.json(mapJsonUrl, function (error, worldJson) {
    if (error) throw error;
    var projection = getProjection();
});

Above code block will load world TopoJSON file. Real action starts now.

Setting Projection As you can see that getProjection() method is undefined as of now. Define getProjection and mercatorBounds function as below.

  var getProjection = function(worldJson) {
 var scale = 1,
     offset = [ width / 2, height / 2 ],
     projection = d3.geoEquirectangular().scale( scale ).rotate( [0,0] ).center([0,5]).translate( offset ),
     bounds = mercatorBounds( projection ),
     scaleExtent;

    scale = width / (bounds[ 1 ][ 0 ] - bounds[ 0 ][ 0 ]);
    scaleExtent = [ scale, 10 * scale ];

    projection
      .scale( scaleExtent[ 0 ] );

  return projection;
},

mercatorBounds = function(projection) {
  var maxlat = 83,
      yaw = projection.rotate()[ 0 ],
      xymax = projection( [ -yaw + 180 - 1e-6, -maxlat ] ),
      xymin = projection( [ -yaw - 180 + 1e-6, maxlat ] );

   return [ xymin, xymax ];
};

getProjection method takes worldJson as an argument. This argument is nothing but TopoJSON file that we loaded. Before explaning what above methods do, let me explain few terms that we are going to use in this tutorial.

  • projection: projection is used to project spherical coordinate to our two dimensional screen. Basically TopoJSON file provides spherical coordinates in degrees and projection converts it to Cartesian coordinates in pixels.

  • center: projection.center method takes location([lon,lat]) as an array and sets the projection's center to the specified location. For example if I use [0, 5] the map will be centered to 0 degrees West and 5 degrees North. North is for positive values of latitude and South is for negative values of latitude.

  • translate: The translation offset determines the pixel coordinates of the projection’s center and returns the projection

  • scale: sets the projection’s scale factor to the specified value and returns the projection

  • rotate: sets the projection’s three-axis rotation to the specified angles λ, φ and γ (longitude, latitude and roll) in degrees and returns the projection

You can read more about projections from official documentation.

Lets come back to method getProjection. First we are just making guess for the projection like this

  var center = d3.geoCentroid( worldJson ),
     scale = 1,
     offset = [ width / 2, height / 2 ],
     projection = d3.geoEquirectangular().scale( scale ).rotate( [0,0] ).center([0,5]).translate( offset );

mercatorBounds method calculates the bounds of current projection. Bounds will give us the exact value to translate and scale the projection. Once we have bounds, update the scale of the projection. Calculating bounds and updating projection will make sure that your map will take full dimension of the container provided and will always be centered. This is by far the hardest thing to understand when creating maps in d3. Other tutorials/articles on internet give hardcoded values for above properties and leaves the most difficult part which I explained above.

Till now you haven't seen anything on your screen. Bear with me, we are almost done. Rest part is piece of cake

Draw geography

Define path generator like this

  var path = d3.geoPath().projection( projection );
svg.selectAll( 'path.land' )
      .data( topojson.feature( worldJson, worldJson.objects.countries ).features )
      .enter().append( 'path' )
      .attr( 'class', 'land' )
      .attr( 'd', path );

This is the final snippet which draws world map as expected. Here we are binding country data with path elements. topojson.feature( worldJson, worldJson.objects.countries ).features this pulls the data from TopoJSON file which defines the countries.

Beautifying

This map looks bit ugly. Lets add some stlyes and make the world beautiful. Add following styles in place of 'Write CSS here'

  html {
  height: 100%;
}

body {
  background: #161614;
  height: inherit;
  margin: 0;
  padding: 0;
}

.map {
  height: 100%;
}

.water {
    fill: none;
}

.land {
  fill: #40403E;
  stroke: #31322D;
  stroke-width: 0.7px;
}

Our world map looks likes this


2,598 1 26