Mapping the World: Creating Beautiful Maps and Populating them with Data using D3.js 

creating maps with d3

creating maps with d3

If you’ve been around the web long enough, chances are high you’ve come across some pretty stunning visualizations all over the web.

A lot of these are built using D3.js, a Javascript library that “brings data to life” using open web standards – HTML, CSS, and SVG. It allows you to bind any kind of data to the DOM and apply different kinds of transformations on them. A simple example would be using D3 to create a HTML table from an array of JSON objects. “Well”, you might be thinking, “that’s not so great. That can be done using plain Javascript.”

Another great benefit D3 brings to the table is its extensibility, with over a dozen different
plugins to help you create everything from Euler diagrams to 3D visualizations. The same data can be simply used to make the charts interactive and even add on some smooth transitions when the data changes, for instance. Essentially, D3 provides an invaluable tool for data visualization without the need for jumping through a thousand different hoops.

Of course, D3 is more than just a data visualization tool. One of the most compelling use-cases is making beautiful maps.

A Gentle Introduction to D3.js

A lot of developers are afraid of libraries like D3 because some forms of data visualization require you to understand some pretty complicated mathematics. However, it’s a lot less intimidating once you start. By the time you have to deal with complex graphs, you should already have mastered the basics. As long as you understand web concepts like SVG, HTML, and CSS, the concepts should come pretty naturally.

D3 is the shorthand for Data-Driven Documents, it’s a Javascript library that binds any arbitrary data onto the DOM. These can then be manipulated to produce any kind of visualization you need for your project.

One major advantage D3 has as compared to using plain Javascript is that it is highly functional. This helps to make your code easy to reuse since it relies on method chaining to a great degree. This extensibility means dozens of plugins and components can be used together with d3. These can be incredibly useful if you’re in a hurry or understand the underlying concepts and don’t want to reinvent the wheel.

As outlined by the site’s homepage, the library can be used for everything from simplifying your code from verbose and not-easily-readable

To simple and declarative:

But that’s jQuery’s area to shine. D3 is loved for its ability to turn data:

into HTML elements

What does all this have to do with maps?

Well, one of the most compelling reasons to use D3 comes in the form of cartography. Again, as the site itself outlines, functions like d3.geoPath have made it a simple and reliable tool for projecting geographic data into SVG path data. This is with the help of another web standard called TopoJSON.

TopoJSON is an extension of GeoJson that comes with a trove of additional features, the most important of which is perhaps the reduced file size.

For instance, if we wanted to create a simple map of the United States, all we have to do is import the TopoJSON and use D3 to render their coordinates on an SVG.

If it looks intimidating, let’s break it all down:

First, we create a container where the elements we want to draw will go. If you’ve used jQuery in the past, the syntax should look familiar. However, there are two key differences to be aware of:

  • d3.append method only takes the name of the element, not actual markup
    ie. d3.select(‘body’).append(‘svg’) and not $(‘body’).append(‘<svg/>’)
  • d3.append returns the appended element, not the parent. Therefore, any attributes added afterward will be appended to the svg, not the body like in jQuery.

The geoAlbersUsa projection is used to provide an Albers Projection of the United States.

This creates a new geographic path generator for us. A path generator is simply a function that takes a GeoJSON feature and prints out SVG path data. If a projection is specified, it’s used in rendering the SVG.

The d3.json() method sends a HTTP request to the specified path and retrieves JSON objects, and we listen for a response in the callback. For this project, I load a modified TopoJSON originally from leaflet.js.Have you noticed something? The ‘selectAll()’ method is supposed to select all elements of a particular type, but as of yet, no ‘path’ elements exist. So, what is it selecting?

This method works such that if it doesn’t find the element it’s looking for, we get an empty selection. Think of this as preparing the SVG with placeholders for data that’s going to be inserted later. And in the next, line, that’s exactly what we do.

The data method is the life and blood of D3. Its core function is to bind data to page elements. It works together with the enter function to refer to incoming data that is not yet represented on the page.

Here is a section of the TopoJSON we are working with. The most important part of this file is under the ‘features’ key.

For every feature fed to the data function, we will append a ‘path’ and that path will have an attribute, ‘d’ with the value passed from the geoPath we had created earlier.

initial setup image

initial setup image

So far, we haven’t done very much. All that work produces the following (rather dull) map:

We could make this a little more exciting by adding some CSS styling:

And adding one line line to our Javascript code:

creating border map

creating border map

That makes the state borders better visible, but even then it still produces the following (still kind of boring) map:

So we’ll add a bit more interactivity, some more CSS:

Which produces:

gif of boring d3 map - adding interactivity

gif of boring d3 map – adding interactivity

Great. When we hover any state, its color changes to red. It’s not perfect, but better. Things are starting to come together. But let’s step things up a little bit more. For instance, we could plot real data to these states.

Say we wanted to plot data like unemployment rates in the US state by state in 2020. First we’ll need to make sure the data is easy to access. I used a JSON store online to make it easily-accessible, so we’re past the first step.

Let’s get crackin’.

Plotting data to a map with D3

The most basic elements every map should have are a title, a scale, and a legend. D3 comes with methods that can help us take care of the latter two features – all the first takes is some simple HTML.

To produce a scale, we need a few crucial details from the data we are going to plot: the domain and range. The ‘domain’ is the set of all possible input values while ‘range’ is the set of all possible output values.

These are both fairly easy to find with D3.

We create a linear scale and assign it a domain and range. Again, the domain is simply the set of all numbers accepted by a function. That is – all the numbers you want to represent on the map. For most applications, all you have to do is supply the function with the lowest and highest numbers in our set, which the range function accepts as range([min, max]). (That’s not always the case, however, as we’ll see later on)

Another area where D3 comes in strongly is with regards to the dozens of small helpers that it makes available. In this case, we rely on the min and max functions. They are pretty intuitive to use:

But here is what the ‘data’ portion of the dataset we’re working with looks like:

To deal with such problems, aside from the simple functions provided by d3 such as min and max, more complex objects can be accessed using a function. If you’re familiar with higher-order Javascript functions like filter and find the syntax should look familiar.

Functions like attr, max, and min accept two arguments – the data and a callback function that tells the parent function which data to access. The first parameter in the callback is usually the current data while the second is the index of the data.

Finally, we give our scale function a range of data within which it can work. In this case, the ‘range’ is the colors that will be used to represent the ‘intensity’ of a particular metric that we pass. The spectrum I used was helpfully generated by this tool.

Now, what needs to happen is merging these two datasets in one file that can be referenced by D3. For this, we used lodash and a helpful function from Stackoverflow.

We merge ‘uState.features’ into ‘response.data’ using the ‘properties.name’ and ‘State’ keys respectively. You could do the same using two for loops if you’d rather not add another library to your project.

Here is what our merged object will look like:

Ideally, all extra properties should be within the ‘properties’ key, but this works well enough for demonstration purposes. And finally, we use the scale to return suitable colors for our map:

And voila!

adding suitable colors

adding suitable colors

It’s quite a… striking map, but hopefully, you’re better at picking colors than me. All that color imbalance is because we have a rather small domain, especially given how small the differences between data values are.

Creating a more suitable d3 scale

Alaska has the highest unemployment rate – 0.064 while Alabama has the lowest – 0.035, giving us a [0.035, 0.064] domain to work with. Since this function doesn’t know anything else about our data, the linear function used within can’t provide a whole lot of color variation (linear functions preserve proportional differences between pieces of data). We have to expand the domain somehow – but providing all the data would just slow down our site, especially when dealing with large amounts of data.

Instead, let’s try and fairly represent the data with the following function:

So that our new domain becomes:

Note: Notice how we avoided using parseInt to convert our strings to numbers Using parseInt and map together can often be disastrous.

colors don't change on d3 map

colors don’t change on d3 map – hovering over states

Anyway, once that’s done, we can go back to our map, and, you might notice something strange:

Hovering over the states doesn’t change the colors any longer. That’s because the style function sets inline styles and those take precedence over external styles or styles defined in the head. We need a better solution.

Changing colors on hover with d3’s on method

The saving function comes in the form of d3’s on method, which allows us to listen to methods like ‘mouseover, like so:

When we hover over a state, I want to change its color so that it’s just slightly darker. For this, I use a utility library – tinyColor.

The complete function will look like this:

pretty d3 map

pretty d3 map – further work on the map

And that’s great! We’re down to an even prettier version of our original map:

But we’re still not yet done. The map looks great but it’s impossible to read any kind of data from it so far. If we want to tell a potential visitor what the colors indicate, we are going to need a legend.

Adding a legend

D3 doesn’t have a specific module for creating legends, it’s fairly simple to create one with the available APIs.

round is a utility function that can be used to round off numbers. It’s defined as

final version of the map

final version of the map

That brings us down to:

Adding a tooltip

Now we have almost everything we need. All that’s left is to add a tooltip so that whenever someone hovers over a state, we can read the information we want faster:

First, we add the styling

Then add a listener for whenever the mouse moves:

Now, all that’s left is to add a title and we’ll be done.
Complete graph with d3

Notes:

  • While this graph is great, one improvement it could use is the labeling of the legend.
  • The tooltip pointer could use a little CSS styling to add an arrow
  • A small delay could be added to the movement of the tooltip to make its movements feel more natural
  • To access the full code, check out this gist.
  • Some colors may be lost due to image compression.

Is that all?

As it turns out, D3 is a really large and complex library capable of a lot more than just data visualization. For one, it’s a really good choice for placing additional data at latitudes and longitudes on rendered maps. It’s also great for some complicated maps, once you’ve gotten the hang of it.

In addition to which, it’s a great solution for creating slippy maps – that is, maps that can be panned and zoomed. This is usually done in combination with libraries like Google Maps or Leaflet.js.

We are going to cover the implementation of the latter of these in the coming days, so keep an eye out!

Summary

D3 is a solid data visualization library. However, its uses go far beyond simple charts. It can also be used to overlay data over maps and even to draw maps themselves. The possibilities are endless.

Code: https://gist.github.com/Bradleykingz/659792d186326da8b2684a425871a8e0
Git repo: https://github.com/Bradleykingz/working-with-d3
jsFiddle: https://jsfiddle.net/uaz8gpeo/

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

26.03.2024

An Introduction to Clustering in Node.js

Picture your Node.js app starts to slow down as it gets bombarded with user requests. It's like a traffic jam for your app, and no developer likes ...

15.03.2024

JAMstack Architecture with Next.js

The Jamstack architecture, a term coined by Mathias Biilmann, the co-founder of Netlify, encompasses a set of structural practices that rely on ...

Rendering Patterns: Static and Dynamic Rendering in Nextjs

Next.js is popular for its seamless support of static site generation (SSG) and server-side rendering (SSR), which offers developers the flexibility ...

1 comment

Alina Shumarina January 22, 2020 at 3:39 pm
0

Bradley, thank you for the great piece. Working with geospatial data is just the next level of complexity 🙂

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy