Creating maps with D3

In this section, we will create map-charts based on SVG. We will use the GeoJSON file with the countries to create a choropleth map that shows the distortions introduced by the Mercator projection.

We will also create maps using the more compact format, TopoJSON, and use topologic information contained in the file to find the neighbors and specific frontiers between countries.

Creating a choropleth map

In this section, we will create a choropleth map to compare the areas of different countries. We will paint each country according to its area; countries with greater areas will be colored with darker colors. In general, the Mercator projection is not suitable to create choropleth maps showing large areas, as this projection shows the regions near the poles bigger than they really are. For instance, Antarctica is smaller than Russia, but using the Mercator projection, it seems bigger. Brazil has a greater area than Greenland, but with this projection, it looks smaller.

In this example, we will use the Mercator projection to show this effect. Our choropleth map will allow us to compare the size of the countries. We will use the GeoJSON file, chapter10/data/countries.geojson, available in the code bundle. The chapter10/01-countries-geojson.html file displays the contents of the file for a more convenient inspection of the features and their attributes.

We will begin by reading the contents of the GeoJSON file and creating the SVG element to display the map. GeoJSON is encoded in the JSON format, so we can use the d3.json method to retrieve and parse the content from GeoJSON:

d3.json(geoJsonUrl, function(error, data) {

    // Handle errors getting or parsing the GeoJSON file
    if (error) { return error; }

    // Create the SVG container selection
    var div = d3.select('#map01'),
        svg = div.selectAll('svg').data([data]);

    // Create the SVG element on enter
    svg.enter().append('svg')
        .attr('width', width)
        .attr('height', height);
});

The data variable contains the GeoJSON object. In this case, the GeoJSON object contains a FeatureCollection object, and the features array contains Feature objects, one for each country.

To map the feature coordinates, we will need a projection function. D3 includes about a dozen of the most used projections, and there are even more available as plugins (see https://github.com/d3/d3-geo-projection for the complete list). We will create an instance of the Mercator projection and translate it so that the point with the coordinates [0, 0] lies in the center of the SVG figure:

// Create an instance of the Mercator projection
var projection = d3.geo.mercator()
    .translate([width / 2, height / 2]);

With this function, we can compute the SVG coordinates of any point on earth. For instance, we can compute the SVG coordinates of a point in the coast of Aruba by invoking the projection function with the [longitude, latitude] array as an argument:

projection([-69.899121, 12.452001])
// [17.004529145013294, 167.1410458329102]

We have the geometric description of each feature; the geometries contain arrays of coordinates. We could use these arrays to compute the projection of each point and draw the shapes using the d3.svg.path generator, but this will involve interpreting the geometries of the features. Fortunately, D3 includes a geographic path generator that does the work for us:

// Create the path generator and configure its projection
var pathGenerator = d3.geo.path()
    .projection(projection);

The d3.geo.path generator needs the projection to compute the paths. Now, we can create the path objects that will represent our features:

// Create a selection for the countries
var features = svg.selectAll('path.feature')
    .data(data.features);

// Append the paths on enter
features.enter().append('path')
    .attr('class', 'feature'),

// Set the path of the countries
features.attr('d', pathGenerator);

We have added the class feature to each path in order to configure its style using CSS. After including the chapter10/map.css style sheet file, our map will look similar what is shown in to the following figure:

Creating a choropleth map

A map of the world countries, using the Mercator projection with the default scale

We can see that the features are correctly drawn, but we would like to add colors for the ocean and scale the map to show all the countries. Projections have a scale method that allows you to set the projection's scale. Note that different projections interpret the scale in different ways. In the case of the Mercator projection, we can map the entire world by setting the scale as the ratio between the figure width and the complete angle, in radians:

// The width will cover the complete circumference
var scale = width / (2 * Math.PI);

// Create and center the projection
var projection = d3.geo.mercator()
    .scale(scale)
    .translate([width / 2, height / 2]);

We will also want to add a background to represent the oceans. We could simply add a SVG rectangle before inserting the features; instead, we will create a feature object that spans the globe and uses the path generator to create an SVG path. This is a better approach because if we change the projection later, the background will still cover the complete globe. Note that we need to close the polygon by adding the first point in the last position:

var globeFeature = {
    type: 'Feature',
    geometry: {
        type: 'Polygon',
        coordinates: [
            [
                [-179.999, 89.999],
                [ 179.999, 89.999],
                [ 179.999, -89.999],
                [-179.999, -89.999],
                [-179.999, 89.999]
            ]
        ]
    }
};

To avoid overlapping, the rectangle defined by the coordinates doesn't completely cover the globe. We can now create the path for the globe and add a style to it, as we did for the rest of the features:

// Create a selection for the globe
var globe = svg.selectAll('path.globe')
    .data([globeFeature]);

// Append the graticule paths on enter
globe.enter().append('path')
    .attr('class', 'globe'),

// Set the path data using the path generator
globe.attr('d', pathGenerator);

We will also add reference lines for the meridians and parallels. These lines are called as graticules, and D3 includes a generator that returns a MultiLineString object with the description of the lines:

// Create the graticule feature generator
var graticule = d3.geo.graticule();

// Create a selection for the graticule
var grid = svg.selectAll('path.graticule')
    .data([graticule()])

// Append the graticule paths on enter
grid.enter().append('path')
    .attr('class', 'graticule'),

// Set the path attribute for the graticule
grid.attr('d', pathGenerator);

We have also added a class to the graticule lines to apply styles using CSS. The map now looks better:

Creating a choropleth map

The map with the oceans and the graticule

When creating a choropleth map, one of the most important choices to be made is the color scale to be used. Color Brewer (http://colorbrewer2.org/) is an online tool that helps developers and designers to choose good color scales for their maps. There are color scales for qualitative dimensions as well as for sequential and diverging quantitative dimensions. The colors of each palette have been carefully chosen so that there is a good contrast between the colors and they look good on the screen.

For a map that shows a qualitative dimension, the color scale should be composed of colors that differ primarily in hue but have a similar brightness and saturation. An example of this could be a map showing which languages are spoken in each country, as shown in the following figure:

Creating a choropleth map

A qualitative color scale doesn't suggest order between the items

For quantitative variables that are sequential, that is, ranging from less to more in one dimension, a color scale with increasing darkness will be a good choice. For instance, to display differences in income or housing costs, a sequential scale would be a good fit, as shown in the following figure:

Creating a choropleth map

A sequential color scale is a good choice for ordinal variables

For a quantitative variable that covers two extremes, the color palette should emphasize the extremes with dark colors of different hue and show the critical mid-range values with lighter colors. An example of this could be a map that shows the average winter temperatures, showing the countries with temperatures below zero in blue, temperatures above zero in red, and the zero value in white, as shown in the following figure:

Creating a choropleth map

A diverging color scale is useful to show variables that cover extremes

In our case, we will use a sequential scale because our quantitative variable is the area of each country. Countries with a bigger surface will be shown in a darker color. We will use Color Brewer to generate a sequential palette:

var colorRange = [
    '#f7fcfd',
    '#e0ecf4',
    '#bfd3e6',
    '#9ebcda',
    '#8c96c6',
    '#8c6bb1',
    '#88419d',
    '#6e016b'];

This color palette has eight levels, ranging from almost white to dark purple. The d3.geo.area method computes the area of a feature in steradians, which is the measurement unit for solid angles. We will use the d3.scale.quantize scale to assign the area of each feature to one of the colors of our palette:

// Create the color scale for the area of the features
var colorScale = d3.scale.quantize()
    .domain(d3.extent(data.features, d3.geo.area))
    .range(colorRange);

We use the d3.geo.area method to compute the area of each feature and to compute the fill color using the color scale:

// Set the path of the countries
features.attr('d', pathGenerator)
    .attr('fill', function(d) {
        return colorScale(d3.geo.area(d));
    });

We obtain a map with each country colored according to its area, as shown in the following figure:

Creating a choropleth map

The choropleth that shows the area of each country

When we take a look at the choropleth, we can see several inconsistencies between the size and the colors. Greenland, for instance, looks twice as big as Brazil, but it's actually smaller than Brazil, Australia, and the United States.

Mapping topology

As mentioned in the previous section, TopoJSON files are more compact than their GeoJSON counterparts, but the real power of TopoJSON is that it encodes topology, that is, information about connectedness and the boundaries of the geometries it describes. Topology gives us access to more information than just the shapes of each feature; we can identify the neighbors and boundaries between geometries.

In this section, we will use the countries.topojson file to create a world map, replacing GeoJSON from the previous section. We will also identify and map the neighboring countries of Bolivia and identify a particular frontier using the TopoJSON library. As we did in the previous section, we have created the chapter10/03-countries-topojson.html file to display the contents of the TopoJSON file for easier inspection.

To create a world map with TopoJSON, we need to create the SVG container and the projection in the same way that we did with the GeoJSON file. The geographic path generator expects GeoJSON objects, but at this point, we have a TopoJSON object that encodes the geometry of our features. The topojson.feature object computes the GeoJSON Feature or FeatureCollection object, corresponding to the object given as the second argument. Remember that the object attribute contains the TopoJSON geometry objects, which have an array of references to the arcs defined at the top level of the TopoJSON object. In this case, the geodata variable stores a FeatureCollection object, which we can use to generate the shapes using the same code as that used earlier:

d3.json(url, function(error, data) {

    // Create the SVG container...

    // Construct the Feature Collection
    var geodata = topojson.feature(data,data.objects.countries);

    // Render the features
});

The geodata object is a FeatureCollection object, and we can use the same projection and path generator that we used in the last section to generate the world map:

// Create a selection for the countries and bind the feature data
var features = svg.selectAll('path.feature')
    .data(geodata.features)
    .enter()
    .append('path')
    .attr('class', 'feature')
    .attr('d', pathGenerator);

This allows us to replace the GeoJSON file that we used in the previous section with the TopoJSON file, which is about one-eight the size of the countries.geojson file and obtains the same result, as shown in the following figure:

Mapping topology

The world map using TopoJSON

As TopoJSON files describe geometries by listing references to the arcs, we can verify that two geometries are neighbors by checking whether they have arcs in common. The topojson.neighbors method does exactly this, given an array of geometries; it returns an array with the indices of the neighbors of each geometry object.

To illustrate this point, we will create a map that highlights the countries that share a boundary with Bolivia. We will begin by scaling and centering the map to display South America. We begin by filtering the countries that belong to South America and creating a FeatureCollection object for them:

// Construct the FeatureCollection object
var geodata = topojson.feature(data, data.objects.countries);

// Filter the countries in South America
var southAmerica = geodata.features.filter(function(d) {
        return (d.properties.continent === 'South America'),
    });

// Create a feature collection for South America
var southAmericaFeature = {
        type: 'FeatureCollection',
        features: southAmerica
    };

We would like to adapt the scale and translation of the projection to just display South America. D3 provides tools to compute the bounding box and centroid of a feature:

// Compute the bounds, centroid, and extent of South America
// to configure the projection
var bounds = d3.geo.bounds(southAmericaFeature),
    center = d3.geo.centroid(southAmericaFeature);

The d3.geo.bounds method returns the bottom-left and top-right corners of the feature's bounding box in geographic coordinates. The d3.geo.centroid method returns an array with the longitude and latitude of the centroid of the feature.

To compute a scale factor that displays our feature object properly, we need to know the angular distance between the two corners of our bounding box. We could also use another characteristic distance of the feature, such as the distance from the centroid to one of the bounding box corners:

// Compute the angular distance between bound corners
var distance = d3.geo.distance(bounds[0], bounds[1]);

The d3.geo.distance method takes two locations and returns the angular distance between the points (in radians). For the Mercator projection, we can compute the scale as the ratio between the desired screen size and the angular span of our feature. We can recompute the scale with the angular distance between the corners of the bounding box:

// The width will cover the complete circumference
var scale = width / distance;

We can now center and scale our projection. This will show our feature centered in the screen and at a better scale:

// Create and scale the projection
var projection = d3.geo.mercator()
    .scale(scale)
    .translate([width / 2, height / 2])
    .center(center);

Note that this way of computing the scale will only work with the Mercator projection. The scales are not consistent among projections, as shown in the following figure:

Mapping topology

Centering and scaling a map around a feature

Having centered and scaled the South American continent properly, we can proceed to compute the neighbors of Bolivia. We begin by obtaining the neighbors of each country in our dataset:

// Compute the neighbors of each geometry object.
var neighbors = topojson.neighbors(data.objects.countries.geometries);

This will return an array with as many elements as the input array. Each element will be an array with the indices of the neighbor geometries of each object. The geometry of Bolivia is described by the thirtieth element in the data.objects.countries.geometries array. The contents of neighbors[30] is the [8, 31, 39, 169, 177] array; each element is the index of the geometry element of each neighboring country. To find Bolivia's neighbors, we need to know the index of Bolivia, so, we will search for it in the geometries array:

// Find the index of Bolivia in the geometries array
var countryIndex = 0;
data.objects.countries.geometries.forEach(function(d, i) {
    if (d.properties.admin === 'Bolivia') {
        countryIndex = i;
    }
});

The neighbors[countryIndex] array will contain the indices of the neighbors of Bolivia. To display the neighbor's features in the map, we need to compute a feature that contains their geometries. We can create a GeometryCollection object in order to use the topojson.feature method to construct the FeatureCollection object with the neighbors:

// Construct a Geometry Collection with the neighbors
var geomCollection = {
    type: 'GeometryCollection',
    geometries: []
};

// Add the neighbor's geometry object to the geometry collection
neighbors[countryIndex].forEach(function(i) {
    var geom = data.objects.countries.geometries[i];
    geomCollection.geometries.push(geom);
});

The geometry collection object we just created contains the geometries of the countries that share boundaries with Bolivia. We will create a feature collection object for these geometries in order to add them to the map:

// Construct a Feature object for the neighbors
var neighborFeature = topojson.feature(data, geomCollection);

We can now create the path for the feature containing the neighbors, and add a class to the path to set its style with CSS:

// Add paths for the neighbor countries
var neighborPaths = svg.selectAll('path.neighbor')
    .data([neighborFeature])
    .enter()
    .append('path')
    .attr('class', 'neighbor')
    .attr('d', pathGenerator);

The countries that share a boundary with Bolivia are highlighted in the following figure:

Mapping topology

The TopoJSON file gives us more information. Let's say that we need to show the frontier between Bolivia and Brazil. We know that this frontier is identifiable, because we could inspect the geometries for both countries and select the arcs that are common to both geometries, but there is an easier way. The topojson.mesh method returns a GeoJSON MultiLineString geometry that represents the mesh for a given object and geometry. It has an optional argument to filter the arcs that meet a condition. We will use this method to generate a MultiLineString object, representing the boundary between Brazil and Bolivia:

// Compute the mesh of the boundary between Brazil and Bolivia
var frontier = topojson.mesh(data, data.objects.countries, function(a, b) {
    return ((a.properties.admin === 'Brazil') && (b.properties.admin === 'Bolivia')) ||
           ((a.properties.admin === 'Bolivia') && (b.properties.admin === 'Brazil'));
});

Note that the optional filter receives two TopoJSON geometries, not GeoJSON features. To obtain the frontier between Brazil and Bolivia, we need to select the arcs that are shared by both the countries. We can now create a path for the frontier and add it to our map:

// Add the frontier to the SVG element
var frontierPath = svg.selectAll('path.frontier')
    .data([frontier])
    .enter()
    .append('path')
    .attr('class', 'frontier')
    .attr('d', pathGenerator);

The boundary between the two countries is highlighted in the following updated map:

Mapping topology

Note that the topojson.mesh method can be used to identify frontiers of any kind. In other datasets, this can be useful to show or hide internal frontiers or frontiers with countries that meet certain conditions.

In this section, we learned how to use GeoJSON to create SVG-based maps. We have also learned how to use TopoJSON files to reconstruct GeoJSON objects and create a map. We've also learned how to create maps that highlight topologic relations between places, such as highlighting countries that are connected to each other and show specific boundaries between features.

Using Mapbox and D3

SVG-based maps are great for data visualization projects, but sometimes, we will need more advanced features in our maps, such as a feature that allows us to search for an address or location, get information at the street level, or show satellite images. The most convenient way to provide these features is to integrate our visualization with map providers such as Google Maps, Yahoo! Maps, or Mapbox. In this section, we will learn how to integrate D3 with Mapbox, an excellent map provider.

Mapbox is an online platform used to create custom-designed maps for web and mobile applications. It provides street maps and terrain and satellite view tiles. The street maps from Mapbox use data from OpenStreetMap, a community-powered open data repository with frequent updates and accurate information.

A distinctive feature of Mapbox is that it allows customization of the map views. Users can customize the visual aspects of every feature in their maps. The web platform has tools to customize the maps, and the desktop tool, TileMill, makes this customization even easier.

To follow the examples in this section, you will need a Mapbox account. The free plan allows you to create maps, add markers and features, and get up to 3,000 views per month. To create a Mapbox account, visit https://www.mapbox.com.

Note

Mapbox counts the views to your map, and each plan has a limit on the number of views per month. If you create a visualization using Mapbox and it becomes popular (I hope so!), you might want to upgrade your account. In any case, you will be notified if you are about to spend your monthly quota.

The Mapbox JavaScript API is implemented as a Leaflet plugin and includes a release of Leaflet, an open source library to create interactive map applications. Leaflet provides an API that allows you to create layers, interactive elements (such as zooming and panning), add custom markets, and many others. We will use the Mapbox and Leaflet APIs to create maps and integrate the maps with D3.

Creating a Mapbox project

We will begin by pointing the browser to https://www.mapbox.com/projects/ and create a new project. In Mapbox, a project is a map that we can customize according to our needs. By clicking on Create a Project, we can access the map editor, where we can customize the colors of land, buildings, and other features; select the base layer (street, terrain, or satellite); and select the primary language for the features and locations in the map. We can also add markers and polygons and export them to KML or GeoJSON in order to use them in other projects. You can also set the map as private or public, remove geocoding, or remove sharing buttons.

Once we save the map, it will be given a map ID. This ID is necessary to load the map using the JavaScript API, view the tiles, or embed the map in a page. The ID is composed of the username and a map handle. To use the JavaScript API, we need to include the Mapbox.js library and styles. These files can also be downloaded and installed locally with Bower (bower install --save-devmapbox.js):

<script src='https://api.tiles.mapbox.com/mapbox.js/v1.6.2/mapbox.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v1.6.2/mapbox.css'rel='stylesheet' />

To include the map in our page, we need to create a container div and set its position to absolute. We will also create a container for this div element in order to give the map container a positioning context:

<div class="map-container">
  <div id="map"></div>
</div>

The top and left offsets of the map container will be governed by the parent element. For this example, we will add the styles at the top of our page:

<style>
.map-container {
    position: relative;
    width: 600px;
    height: 400px;
}

#map {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 100%;
}
</style>

The next step is to create an instance of the map. We can set the view's center and zoom level by chaining the setView method:

<script>
var mapID = 'username.xxxxxxxx', // replace with your map ID
    center = [12.526, -69.997],
    zoomLevel = 11;

var map = L.mapbox.map('map', mapID)
    .setView(center, zoomLevel);
</script>

The map of Aruba created by Mapbox is shown in the following figure:

Creating a Mapbox project

The map will be rendered in the container div. Note that the maps support all the interactions that we expect; we can zoom in or out and drag the map to explore the surrounding areas.

Integrating Mapbox and D3

In this example, we will create a bubble plot map to show the population of the main cities in Aruba. We have created a JSON file with the necessary information. The JSON file has the following structure:

{
  "country": "Aruba",
  "description": "Population of the main cities in Aruba",
  "cities": [
    {
      "name": "Oranjestad",
      "population": 28294,
      "coordinates": [12.519, -70.037]
    },
    ...
  ]
}

To create the bubbles, we will create a layer. Layers are objects attached to a particular location, such as markers or tiles. Most Leaflet objects are created by extending the L.Class object, which implements simple classical inheritance and several utility methods. We will create a D3 layer class by extending the L.Class object. Layers must at least have an initialize and the onAdd methods. We will also include the onRemove method to remove the bubbles if the layer is removed. A basic layer will have the following structure:

var D3Layer = L.Class.extend({

    initialize: function(arguments...) {
        // Initialization code
    },

    onAdd: function(map) {
        // Create and update the bubbles
    },

    onRemove: function(map) {
        // Clean the map
    }
});

The initialize method will be invoked when the layer instance is created. The onAdd method is invoked when the layer is added to the map. It receives the map object as an argument, which gives us access to the panes, zoom level, and other properties of the map. The onRemove method is invoked when the layer is removed from the map, and it also receives the map as an argument.

Whenever the user interacts with the map by dragging or zooming, the map triggers the viewreset event. This method notifies that the layers need to be repositioned. The latLngToLayerPoint method can be used to set the new position of the objects, given their geographic coordinates.

In our case, the initialize method will receive a single data argument, which will contain the array with the cities of Aruba. We will store the data array as an attribute of the layer:

    initialize: function(data) {
        this._data = data;
    },

The data array will be accessible from each method of the layer. In the onAdd method, we will select a pane from the map and append the bubbles to it. The container div for our bubbles will be the overlay pane of the map, which is a pane designed to contain custom objects. The overlay pane gets repositioned automatically each time the user pans the map.

One strategy is to create a single svg container to hold our bubbles and to resize it each time the user zooms in or out. Another strategy we will use is to create one svg element for each bubble and position it absolutely using the projection of the coordinates of each feature. We will create a selection for the svg elements and bind the data to the selection:

    onAdd: function(map) {

        // Create SVG elements under the overlay pane
        var div = d3.select(map.getPanes().overlayPane),
            svg = div.selectAll('svg.point').data(this._data);

        // Create the bubbles...
    },

To position the SVG elements, we need to project the latitude and longitude of each point and use these coordinates to set the offsets of our svg containers. We will add the L.LatLng objects with the coordinates of each city to each data item:

        // Stores the latitude and longitude of each city
        this._data.forEach(function(d) {
            d.LatLng = new L.LatLng(d.coordinates[0], d.coordinates[1]);
        });

We will use this attribute in just a few moments. The svg elements will be created to contain just one bubble, so they don't overlap other areas of the map. Before setting the size of the svg elements, we need to compute the radius scale. The area of the bubbles should be proportional to the population of the city:

        // Create a scale for the population
        var rScale = d3.scale.sqrt()
            .domain([0, d3.max(this._data, function(d) {
                return d.population;
            })])
            .range([0, 35]);

We can now create the svg elements and set their size and position. Note that the svg element's width and height are twice as big as the radius of the bubble:

        svg.enter().append('svg')
            .attr('width', function(d) {
                return 2 * rScale(d.population);
            })
            .attr('height', function(d) {
                return 2 * rScale(d.population);
            })
            .attr('class', 'point leaflet-zoom-hide')
            .style('position', 'absolute'),

We have added the leaflet-zoom-hide class to each svg element so that they are hidden when the map is being zoomed in or out by the user. We also set the position of the svg container to absolute. We can finally add the bubbles as usual, appending a circle to each svg container:

        svg.append('circle')
            .attr('cx', function(d) { return rScale(d.population); })
            .attr('cy', function(d) { return rScale(d.population); })
            .attr('r', function(d) { return rScale(d.population); })
            .attr('class', 'city')
            .on('mouseover', function(d) {
                d3.select(this).classed('highlight', true);
            })
            .on('mouseout', function(d) {
                d3.select(this).classed('highlight', false);
            });

We added event listeners for the mouseover and mouseout events. In these events, we add the highlight class to the circles, which will increase the opacity of the circles.

When the user drags the map, the overlay pane will be moved as well and the bubbles will be well positioned. When the user zooms in/out of the map, the viewreset event will be triggered, and we must reposition the svg containers. We will create an updateBubbles function to update the position of the bubbles on zoom, and invoke this function on viewreset:

        // Update the position of the bubbles on zoom
        map.on('viewreset', updateBubbles);

When the callback of the viewreset event is invoked, the map.latLngToLayerPoint projection method is updated with the new zoom level and position. So, we can use it to set the offsets of the svg elements:

        function updateBubbles() {
            svg
                .style('left', function(d) {
                    var dx = map.latLngToLayerPoint(d.LatLng).x;
                    return (dx - rScale(d.population)) + 'px';
              })
                .style('top', function(d) {
                    var dy = map.latLngToLayerPoint(d.LatLng).y;
                    return (dy - rScale(d.population)) + 'px';
                });
        }

Finally, we invoke the updateBubbles method to render the bubbles:

        // Render the bubbles on add
        updateBubbles();

The onRemove method is simpler; we just select the overlay pane and remove all the svg elements from it:

    onRemove: function(map) {
        var div = d3.select(map.getPanes().overlayPane);
        div.selectAll('svg.point').remove();
    }

Having created our layer, we can retrieve the JSON file and append it to the map:

// Retrieve the dataset of cities of Aruba
d3.json('/chapter10/data/aruba-cities.json', function(error, data) {

    // Handle errors getting or parsing the data
    if (error) { return error; }

    // Create a layer with the cities data
    map.addLayer(new D3Layer(data.cities));
});

Our following map shows the bubbles that represent the population of the cities of Aruba:

Integrating Mapbox and D3

A bubble plot map with D3 and Mapbox

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset