Step 3: Dynamic map extent

In this step, we will make the initial map extent dynamic.

Get extent from data

In the previous step, the initial extent was set based on hard-coded parameters which were found by try-and-error.

map.js - static initial extent
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ...

// We define a geographical projection
//     https://github.com/mbostock/d3/wiki/Geo-Projections
// and set the initial zoom to show the features.
var projection = d3.geo.mercator()
  // The approximate scale factor was found through try and error
  .scale(10000)
  // The geographical center of Switzerland is around 46.8°, 8.2°
  //     https://de.wikipedia.org/wiki/Älggi-Alp
  .center([8.226692, 46.80121])
  // Translate: Translate it to fit the container
  .translate([width/2, height/2]);

// ...

These parameters (scale and center) happen to work quite well for Switzerland, but they are not very flexible and would not work at all if the geographic data were to change.

In order to make the extent more dynamic, we cannot set the scale and extent before we read the GeoJSON and determine the extent from the data. Instead, we simply create an initial dummy scale which will be overwritten later.

map.js - set dummy initial scale
10
11
12
13
14
15
16
17
18
19
// ...

// We define a geographical projection
//     https://github.com/mbostock/d3/wiki/Geo-Projections
// and set some dummy initial scale. The correct scale, center and
// translate parameters will be set once the features are loaded.
var projection = d3.geo.mercator()
  .scale(1);

// ...

To determine the real extent based on the features, we can make use of a function to calculate the scale and the center parameters for a given set of Features. We put the function at the bottom of our JavaScript block.

map.js - function to calculate extent
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// ...

/**
 * Calculate the scale factor and the center coordinates of a GeoJSON
 * FeatureCollection. For the calculation, the height and width of the
 * map container is needed.
 *
 * Thanks to: http://stackoverflow.com/a/17067379/841644
 *
 * @param {object} features - A GeoJSON FeatureCollection object
 *   containing a list of features.
 *
 * @return {object} An object containing the following attributes:
 *   - scale: The calculated scale factor.
 *   - center: A list of two coordinates marking the center.
 */
function calculateScaleCenter(features) {
  // Get the bounding box of the paths (in pixels!) and calculate a
  // scale factor based on the size of the bounding box and the map
  // size.
  var bbox_path = path.bounds(features),
      scale = 0.95 / Math.max(
        (bbox_path[1][0] - bbox_path[0][0]) / width,
        (bbox_path[1][1] - bbox_path[0][1]) / height
      );

  // Get the bounding box of the features (in map units!) and use it
  // to calculate the center of the features.
  var bbox_feature = d3.geo.bounds(features),
      center = [
        (bbox_feature[1][0] + bbox_feature[0][0]) / 2,
        (bbox_feature[1][1] + bbox_feature[0][1]) / 2];

  return {
    'scale': scale,
    'center': center
  };
}

Now, we can call this function when we read the GeoJSON file to get the optimal scale and center for the current geographic data.

index.html - set optimal scale and center
23
24
25
26
27
28
29
30
31
32
33
34
35
// ...
d3.json('data/ch_municipalities.geojson', function(error, features) {

  // Get the scale and center parameters from the features.
  var scaleCenter = calculateScaleCenter(features);

  // Apply scale, center and translate parameters.
  projection.scale(scaleCenter.scale)
    .center(scaleCenter.center)
    .translate([width/2, height/2]);

  // We add a <g> element to the SVG element and give it a class to
  // ...

If we refresh the browser, we should not see a very big difference, as the manual parameters were already rather good. However, our code is now much more dynamic and not only works for Switzerland.

Verification

To verify that our extent also works in other geographic contexts, we can test by loading a different GeoJSON file.

  • You can download a GeoJSON file of Germany from Click that ‘hood!.
  • Save it to the data folder (next to ch_municipalities.geojson).
  • In the code, replace the path to the GeoJSON file: data/germany.geojson.
  • Refresh the browser, the map should now show Germany and its states, everything nicely scaled and centered.
  • Don’t forget to change the path to the GeoJSON file back to data/ch_municipalities.geojson.

Next

Proceed to Step 4: Add some colors.