Mapping with D3.js

As a simple web search will tell you, D3 is a data visualization library that runs like lightening in your web browser. You may hear data visualization and think I don't need a data viz lib, I make maps. Well, maps run on data, plus D3 is actually pretty good with maps, so keep an open mind. You might also wonder why you would choose D3 over another library like Leaflet.js. The short answer is it really depends on your use case and the type of data you want to display. The long answer is this tutorial.

D3 stands for Data Driven Documents. We will unpack this title in three parts. First, we'll take a look at Documents, then check out Data and finally explore Driven. By the time we dive into the meat and potatoes of the tutorial, you should have a pretty clear sense of how D3 relates to technology you've used in the past.

House keeping

To successfully follow this tutorial you need a text editor (such as Notepad++ or Sublime Text). Go ahead and use your personal favorite, or if you don't have one installed, download Sublime Text.

Also, to complete this tutorial without running a local web server (not what we're here to learn), you'll need to open your web map in Firefox. Download Firefox.

Documents

At its core, D3 takes your information and transforms it into a visual output. That output is your document, and for all intents and purposes, your document is an SVG. Scalable Vector Graphics is a file format that encodes vector data for use in a wide array of applications, including your web browser. SVGs are used all over the place to display all kinds of data. If you've ever exported a map from desktop GIS and styled it in a graphics program, chances are your data was stored as SVG at some stage of the process.

SVGs are human readable, which works well for us because we aren't computers. This is an SVG:

<svg width="720" height="120">
  <circle cx="40" cy="60" r="10"></circle>
  <circle cx="80" cy="60" r="10"></circle>
  <circle cx="120" cy="60" r="10"></circle>
</svg>

This circle example is the same document embedded in a really simple web page. Right click one of the circles and select Inspect element from the drop down.

Each of those circles is an element in your SVG, which has a width and height. This is the type of document D3 writes to your web browser. You can tell it to add circles or move circles or remove circles. Checkout this selection tutorial for more on the subject. It's also worth noting that D3 has the ability to write and edit many types of shapes, not just circles.

Data

So, you have a tool that can write a Scalable Vector Graphic in your web browser... That doesn't really do anything for you unless you have something you want to draw. In D3, that something is almost always based on data.

Let's take a look at this scatterplot. This type of visualization is familiar to most and easy to understand. Sepia Width is plotted on the x-axis and Sepia Length is plotted on the y-axis. D3 draws the entire graphic. But how does it know what to draw and where to draw it? Scroll down the page until you see data.tsv (or just click that link). When this web page loads, D3 reads this file and adds each record as a circle in the scatter plot. Each circle's coordinates and color are defined by the data in the record.

Driven

At this point Driven might not make sense. If we stopped here, we could probably make a case to change Driven to Defined. Let's keep going.

Driven is actually one of the defining characteristics of D3. Remember the work flow we discussed where you export an SVG from desktop GIS to do custom design work in a graphics editor? As soon as your vector data leaves the GIS, features lose the data that defines them. At best, the great attributes you honed are represented graphically and at worst are totally gone.

That type of thing doesn't happen in D3. Not only does your data define the elements in your SVG, the data is also bound (joined) to the elements in your document. A circle isn't just a circle element with an x,y and radius, it's also the data that originated the element in the first place. This characteristic of D3 allows data to drive your visualization, not only upon creation, but throughout its life cycle.

Tips

Tutorial Time!

By the time we finish this tutorial, we will have built our first (or nth) D3 web map! This will not be the prettiest map you've ever made, but hopefully once you've made it, you will have a launching pad to make an even better D3 map in the future.

STEP 1: Review Web Pages

Before we get going, let's take a walk over to our web map tutorial. While there, go through the steps to make an empty HTML file (through Add a boilerplate to map.html).

At this stage, you should have a map.html file on your machine that you can open in your web browser. Give that a shot by dragging map.html into Firefox. This is what your file looks like:

Save your eyes. Feel free to delete lines 7-9:

    body {
      background-color: green;
    }

Include the D3.js library

Now you can insert D3 into your page. For now we'll use an externally hosted version of the library instead of copying it into a new file. Add the following after the <head> tag of map.html.

    <script src="http://d3js.org/d3.v3.min.js"></script>

STEP 2: Build the document

Before we add a map to our web page, we need to make a place to put it. The following work will take place between <script> tags.

Define width and height of your graphic

    var width = 600,
        height = 600;

Create your SVG

    var svg = d3.select("body").append("svg")
        .attr("width", width)
        .attr("height", height);

This chunk of code tells your browser that the variable svg is an SVG element under the body tag. If the SVG doesn't exist, D3 creates one. Add this to map.html and reload the page. Right click in the upper left region of the page. Select Inspect Element, and you'll see that your page contains an empty SVG element.

Define your projection

    var projection = d3.geo.mercator()
        .scale(96) // scales your map
        .translate([width / 2, height / 2]); // centers in SVG

When you define a projection, you tell D3 how to transform your data from spherical to Cartesian coordinates. Take a look at the projection docs to get a sense of how you can project your data with D3.

Translate to screen coordinates

    var path = d3.geo.path()
        .projection(projection);

When D3 renders your SVG, it has to translate geo coordinates to pixel coordinates on your screen. This functionality is called a path generator. In this block, we store our path generator in the variable path so we can access it through out our code.

At this point, all you have is an empty SVG on your page. Now that our document is ready, we need data.

STEP 3: Grab map data

The data we'll use for this map is stored in the GeoJSON format. We won't address specifics here, but if you want to learn more about the spatial format GeoJSON, take a look at GeoJSON & Git.

Your data is waiting for you here.

Download Gist

Download the data and unpack it! If you don't have software to unzip the data, you can also copy the raw contents to a file on your computer and save as us.json. Move us.json to the same location as map.html.

STEP 4: Load the data

When you complete this step, you will have a map. Below your path generator, insert the following:

    d3.json("us.json", function(json) { // loads JSON file
      svg.selectAll("path") // selects path elements, will make them if they don't exist
       .data(json.features) // iterates over geo feature
       .enter() // adds feature if it doesn't exist as an element
       .append("path") // defines element as a path
       .attr("d", path) // path generator translates geo data to SVG
    });

This chunk of code goes through us.json and adds each feature to the SVG on your page. This is a great time to Inspect Element on your web page or dig into the GeoJSON. As you can see, your SVG has a "path" element (shape) for each feature in your GeoJSON. With a little extra digging, you will find that feature attributes in the source data are preserved in your visualization. See how data drives this document?

Congratulations! You built a web map in D3.js!

STEP 5: Challenge

  1. Center the projection. Hint: look at the docs. What is a natural place to add this information?
  2. Change the scale. Hint: where did we set scale again?
  3. Change the projection. Dump that mercator! Hint: search "mercator()" and try something else
  4. Find the data. Use your JS console to access a element data. Hint: it involves a selection

Fin.

Hope that was helpful! Please fill out our survey when you are done even if you couldn't attend the meeting. We want to make sure the MaptimeSEA tutorials are teaching what you want to learn. bit.ly/maptimesea_survey