Skip to main content

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 stunningvisualizations 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 TopoJSONand 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 filterand find the syntax should look familiar.

Functions like attrmax, 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


Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...