epubjs
Version:
Render ePub documents in the browser, across many devices
172 lines (159 loc) • 70 kB
HTML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:m="http://www.w3.org/1998/Math/MathML" xmlns:pls="http://www.w3.org/2005/01/pronunciation-lexicon" xmlns:ssml="http://www.w3.org/2001/10/synthesis" xmlns:svg="http://www.w3.org/2000/svg"><head><title>Chapter 12. Geomapping</title><link rel="stylesheet" type="text/css" href="core.css"/><meta name="generator" content="DocBook XSL Stylesheets V1.76.1"/><link rel="up" href="index.html" title="Interactive Data Visualization for the Web"/><link rel="prev" href="ch11.html" title="Chapter 11. Layouts"/><link rel="next" href="ch13.html" title="Chapter 13. Exporting"/></head><body><section class="chapter" title="Chapter 12. Geomapping" epub:type="chapter" id="_geomapping_2"><div class="titlepage"><div><div><h2 class="title">Chapter 12. Geomapping</h2></div></div></div><p>Bar charts, scatterplots, ring charts, and even force-directed graphs… <span class="emphasis"><em>Yeah, that’s all okay</em></span>, you’re thinking, <span class="emphasis"><em>but get to the maps already!</em></span></p><div class="sect1" title="JSON, Meet GeoJSON"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_json_meet_geojson">JSON, Meet GeoJSON</h2></div></div></div><p>You’ve already met JSON. Now meet GeoJSON, the JSON-based standard for encoding geodata for web applications. GeoJSON actually is not a totally different format, but just a very specific use of JSON.<a id="ix_geom" class="indexterm"/><a id="I_indexterm12_id317397" class="indexterm"/><a id="I_indexterm12_id317403" class="indexterm"/><a id="I_indexterm12_id317411" class="indexterm"/><a id="I_indexterm12_id317417" class="indexterm"/></p><p>Before you can generate a geographic map, you need to acquire the path data (the outlines) for the shapes you want to display. We’ll start with a common example, mapping US state boundaries. I’ve included a file <span class="emphasis"><em>us-states.json</em></span> with the sample code. This file is taken directly from one of the D3 examples, and we owe Mike Bostock a word of thanks for generating this nice, clean file of state boundaries.</p><p>Opening up <span class="emphasis"><em>us-states.json</em></span>, you’ll see it looks something like this (reformatted and greatly abbreviated here):</p><a id="I_programlisting12_id317448"/><pre class="programlisting"><code class="p">{</code>
<code class="s2">"type"</code><code class="o">:</code> <code class="s2">"FeatureCollection"</code><code class="p">,</code>
<code class="s2">"features"</code><code class="o">:</code> <code class="p">[</code>
<code class="p">{</code>
<code class="s2">"type"</code><code class="o">:</code> <code class="s2">"Feature"</code><code class="p">,</code>
<code class="s2">"id"</code><code class="o">:</code> <code class="s2">"01"</code><code class="p">,</code>
<code class="s2">"properties"</code><code class="o">:</code> <code class="p">{</code> <code class="s2">"name"</code><code class="o">:</code> <code class="s2">"Alabama"</code> <code class="p">},</code>
<code class="s2">"geometry"</code><code class="o">:</code> <code class="p">{</code>
<code class="s2">"type"</code><code class="o">:</code> <code class="s2">"Polygon"</code><code class="p">,</code>
<code class="s2">"coordinates"</code><code class="o">:</code> <code class="p">[[[</code><code class="o">-</code><code class="mf">87.359296</code><code class="p">,</code><code class="mf">35.00118</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">85.606675</code><code class="p">,</code><code class="mf">34.984749</code><code class="p">],[</code><code class="o">-</code><code class="mf">85.431413</code><code class="p">,</code><code class="mf">34.124869</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">85.184951</code><code class="p">,</code><code class="mf">32.859696</code><code class="p">],[</code><code class="o">-</code><code class="mf">85.069935</code><code class="p">,</code><code class="mf">32.580372</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">84.960397</code><code class="p">,</code><code class="mf">32.421541</code><code class="p">],[</code><code class="o">-</code><code class="mf">85.004212</code><code class="p">,</code><code class="mf">32.322956</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">84.889196</code><code class="p">,</code><code class="mf">32.262709</code><code class="p">],[</code><code class="o">-</code><code class="mf">85.058981</code><code class="p">,</code><code class="mf">32.13674</code><code class="p">]</code> <code class="err">…</code>
<code class="p">]]</code>
<code class="p">}</code>
<code class="p">},</code>
<code class="p">{</code>
<code class="s2">"type"</code><code class="o">:</code> <code class="s2">"Feature"</code><code class="p">,</code>
<code class="s2">"id"</code><code class="o">:</code> <code class="s2">"02"</code><code class="p">,</code>
<code class="s2">"properties"</code><code class="o">:</code> <code class="p">{</code> <code class="s2">"name"</code><code class="o">:</code> <code class="s2">"Alaska"</code> <code class="p">},</code>
<code class="s2">"geometry"</code><code class="o">:</code> <code class="p">{</code>
<code class="s2">"type"</code><code class="o">:</code> <code class="s2">"MultiPolygon"</code><code class="p">,</code>
<code class="s2">"coordinates"</code><code class="o">:</code> <code class="p">[[[[</code><code class="o">-</code><code class="mf">131.602021</code><code class="p">,</code><code class="mf">55.117982</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">131.569159</code><code class="p">,</code><code class="mf">55.28229</code><code class="p">],[</code><code class="o">-</code><code class="mf">131.355558</code><code class="p">,</code><code class="mf">55.183705</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">131.38842</code><code class="p">,</code><code class="mf">55.01392</code><code class="p">],[</code><code class="o">-</code><code class="mf">131.645836</code><code class="p">,</code><code class="mf">55.035827</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">131.602021</code><code class="p">,</code><code class="mf">55.117982</code><code class="p">]]],[[[</code><code class="o">-</code><code class="mf">131.832052</code><code class="p">,</code><code class="mf">55.42469</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">131.645836</code><code class="p">,</code><code class="mf">55.304197</code><code class="p">],[</code><code class="o">-</code><code class="mf">131.749898</code><code class="p">,</code><code class="mf">55.128935</code><code class="p">],</code>
<code class="p">[</code><code class="o">-</code><code class="mf">131.832052</code><code class="p">,</code><code class="mf">55.189182</code><code class="p">],</code> <code class="err">…</code>
<code class="p">]]]</code>
<code class="p">}</code>
<code class="p">}</code>
<code class="err">…</code></pre><p>In typical GeoJSON style, we see, first of all, that this is all one giant object. (Curly brackets, remember?) That object has a <code class="literal">type</code> of <code class="literal">FeatureCollection</code>, followed by <code class="literal">features</code>, which is an array of individual <code class="literal">Feature</code> objects. Each one of these <code class="literal">Feature</code>s represents a US state. You can see each state’s name under <code class="literal">properties</code>.</p><p>But the real meat in any GeoJSON file is under <code class="literal">geometry</code>. This is where the feature’s <code class="literal">type</code> is specified, followed by the many <code class="literal">coordinates</code> that constitute the feature’s boundary. Within the <code class="literal">coordinates</code> are sets of longitude/latitude pairs, each one as a small, two-value array. This is the information that cartographers dedicate their lives to compiling and refining. We owe generations of explorers and researchers our gratitude for creating these sequences of tiny, yet extremely powerful numbers.</p><p>It’s important to note that <span class="emphasis"><em>longitude</em></span> is always listed first. So despite the cultural bias toward lat/lon, GeoJSON is a lon/lat world.<a id="I_indexterm12_id317525" class="indexterm"/><a id="I_indexterm12_id317532" class="indexterm"/></p><p>Also, in case your cartographic skills are a bit rusty, here’s how you can always remember which is which:</p><div class="itemizedlist"><ul class="itemizedlist"><li class="listitem">
Longtiude is long. Therefore, longitudinal lines run vertically, as though hanging down from above.
</li><li class="listitem">
Latitude is fatitude. Therefore, latitudinal lines run horizontally, as though wrapping around the Earth’s waist.
</li></ul></div><p>Longitude and latitude together constitute an enormous grid that encircles the whole globe. Conveniently for us, lon/lat can be easily converted to x/y values for screen display. In bar charts, we map data values to display values—numbers to rectangle heights. In geomapping, we also map data values to display values—lon/lat becomes x/y. Thinking in terms of x/y also makes it easier to get used to the uncomfortable order of longitude first, latitude second.<a id="I_indexterm12_id317570" class="indexterm"/></p><div class="tip" title="Tip"><h3 class="title">Tip</h3><p><a class="ulink" href="http://teczno.com/squares" target="_top">Get Lat+Lon</a> is a great resource by Michal Migurski for double-checking coordinate values. Keep it open in a browser tab whenever you’re working on geomaps. You will reference it often.</p></div></div><div class="sect1" title="Paths"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_paths">Paths</h2></div></div></div><p>We’ve got our geodata. Now get ready to rock.<a id="I_indexterm12_id317606" class="indexterm"/><a id="I_indexterm12_id317616" class="indexterm"/></p><p>First, we define our first <span class="emphasis"><em>path generator</em></span>:</p><a id="I_programlisting12_id317630"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">path</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">geo</code><code class="p">.</code><code class="nx">path</code><code class="p">();</code></pre><p><code class="literal">d3.geo.path()</code> is a total lifesaver of a function. It does all the dirty work of translating that mess of GeoJSON coordinates into even messier messes of SVG <code class="literal">path</code> codes. All hail <code class="literal">d3.geo.path()</code>!</p><p>Now we <span class="emphasis"><em>could</em></span> paste all that GeoJSON directly into our HTML file, but ugh, so many coordinates and curly brackets—what a mess! It’s cleaner and more common to keep the geodata in a separate file and load it in using <code class="literal">d3.json()</code>:</p><a id="I_programlisting12_id317663"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">json</code><code class="p">(</code><code class="s2">"us-states.json"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">json</code><code class="p">)</code> <code class="p">{</code>
<code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"path"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">data</code><code class="p">(</code><code class="nx">json</code><code class="p">.</code><code class="nx">features</code><code class="p">)</code>
<code class="p">.</code><code class="nx">enter</code><code class="p">()</code>
<code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"path"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"d"</code><code class="p">,</code> <code class="nx">path</code><code class="p">);</code>
<code class="p">});</code></pre><p><code class="literal">d3.json()</code> takes two arguments. First, it takes a string pointing to the path of the file to load in. Second, it takes a callback function that is fired when the JSON file has been loaded and parsed. (See <a class="xref" href="ch05.html#handling_data_loading_errors_5" title="Handling Data Loading Errors">Handling Data Loading Errors</a> for details on the callback function.) <code class="literal">d3.json()</code>, just like <code class="literal">d3.csv()</code>, is <span class="emphasis"><em>asynchronous</em></span>, meaning it won’t prevent the rest of your code from running while the browser waits for that external file to load. For example, code placed <span class="emphasis"><em>after</em></span> the callback function might be executed <span class="emphasis"><em>before</em></span> the contents of the callback itself:</p><a id="I_programlisting12_id317704"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">json</code><code class="p">(</code><code class="s2">"someFile.json"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">json</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Put things here that depend on the JSON loading</code>
<code class="p">});</code>
<code class="c1">//Only put things here that can operate independently of the JSON</code>
<code class="nx">console</code><code class="p">.</code><code class="nx">log</code><code class="p">(</code><code class="s2">"I like cats."</code><code class="p">);</code></pre><p>So as a rule, when loading external datafiles, put the code that depends on that data within the callback function. (Or put the code into other custom functions, and then call those functions from within the callback.)</p><p>Back to the example. Finally, we bind the GeoJSON features to new <code class="literal">path</code> elements, creating one new <code class="literal">path</code> for each feature:</p><a id="I_programlisting12_id317728"/><pre class="programlisting"> <code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"path"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">data</code><code class="p">(</code><code class="nx">json</code><code class="p">.</code><code class="nx">features</code><code class="p">)</code>
<code class="p">.</code><code class="nx">enter</code><code class="p">()</code>
<code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"path"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"d"</code><code class="p">,</code> <code class="nx">path</code><code class="p">);</code></pre><p>Notice that last line, in which <code class="literal">d</code> (the path data attribute) is referred to our path generator, which magically takes the bound geodata and calculates all that crazy SVG code. The result is <a class="xref" href="ch12.html#geojson_simple" title="Figure 12-1. Our first view of GeoJSON data">Figure 12-1</a>.</p><div class="figure"><a id="geojson_simple"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject12_id317756"/><img src="httpatomoreillycomsourceoreillyimages1614859.png" alt="Our first view of GeoJSON data"/></div></div><div class="figure-title">Figure 12-1. Our first view of GeoJSON data</div></div><p>A map! That was so easy! Check it out in <span class="emphasis"><em>01_paths.html</em></span>. The rest is just customization.</p><div class="tip" title="Tip"><h3 class="title">Tip</h3><p>You can find lots more detail on paths and path generator options <a class="ulink" href="https://github.com/mbostock/d3/wiki/Geo-Paths" target="_top">on the wiki</a>.</p></div></div><div class="sect1" title="Projections"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_projections">Projections</h2></div></div></div><p>As an astute observer, you noticed that our map isn’t quite showing us the entire United States. To correct this, we need to modify the <span class="emphasis"><em>projection</em></span> being used.<a id="I_indexterm12_id317819" class="indexterm"/><a id="I_indexterm12_id317828" class="indexterm"/><a id="I_indexterm12_id317834" class="indexterm"/></p><p>What is a projection? Well, as an astute observer, you have also noticed that the globe is round, not flat. Round things are three-dimensional, and don’t take well to being represented on two-dimensional surfaces. A <span class="emphasis"><em>projection</em></span> is an algorithm of compromise; it is the method by which 3D space is “projected” onto a 2D plane.</p><p>We define D3 projections using a familiar structure:</p><a id="I_programlisting12_id317860"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">projection</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">geo</code><code class="p">.</code><code class="nx">albersUsa</code><code class="p">()</code>
<code class="p">.</code><code class="nx">translate</code><code class="p">([</code><code class="nx">w</code><code class="o">/</code><code class="mi">2</code><code class="p">,</code> <code class="nx">h</code><code class="o">/</code><code class="mi">2</code><code class="p">]);</code></pre><p>D3 has several built-in projections. Albers USA is a composite projection that nicely tucks Alaska and Hawaii beneath the Southwest. (You’ll see in a second.) <code class="literal">albersUsa</code> is actually the default projection for <code class="literal">d3.path.geo()</code>, but now that we’ve specified it explicitly, we can set several custom options, such as a translation value. You can see we’re translating the projection to the center of the image (half of its width and half of its height).<a id="I_indexterm12_id317879" class="indexterm"/><a id="I_indexterm12_id317886" class="indexterm"/><a id="I_indexterm12_id317892" class="indexterm"/></p><p>The only other change we have to make is to tell the path generator explicitly that it should reference our customized projection when generating all those paths:</p><a id="I_programlisting12_id317904"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">path</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">geo</code><code class="p">.</code><code class="nx">path</code><code class="p">()</code>
<code class="p">.</code><code class="nx">projection</code><code class="p">(</code><code class="nx">projection</code><code class="p">);</code></pre><p>That gives us <a class="xref" href="ch12.html#geojson_centered" title="Figure 12-2. The same GeoJSON data, but now with a centered projection">Figure 12-2</a>. Getting there! See <span class="emphasis"><em>02_projection.html</em></span> for the working code.</p><div class="figure"><a id="geojson_centered"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject12_id317930"/><img src="httpatomoreillycomsourceoreillyimages1614860.png" alt="The same GeoJSON data, but now with a centered projection"/></div></div><div class="figure-title">Figure 12-2. The same GeoJSON data, but now with a centered projection</div></div><p>We can also add a <code class="literal">scale()</code> method to our projection in order to shrink things down a bit and achieve the result shown in <a class="xref" href="ch12.html#geojson_scaled" title="Figure 12-3. The USA, scaled and centered within the image">Figure 12-3</a>:</p><a id="I_programlisting12_id317961"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">projection</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">geo</code><code class="p">.</code><code class="nx">albersUsa</code><code class="p">()</code>
<code class="p">.</code><code class="nx">translate</code><code class="p">([</code><code class="nx">w</code><code class="o">/</code><code class="mi">2</code><code class="p">,</code> <code class="nx">h</code><code class="o">/</code><code class="mi">2</code><code class="p">])</code>
<code class="p">.</code><code class="nx">scale</code><code class="p">([</code><code class="mi">500</code><code class="p">]);</code></pre><p>The default scale value is 1,000. Anything smaller will shrink the map; anything larger will expand it.</p><div class="figure"><a id="geojson_scaled"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject12_id317980"/><img src="httpatomoreillycomsourceoreillyimages1614861.png" alt="The USA, scaled and centered within the image"/></div></div><div class="figure-title">Figure 12-3. The USA, scaled and centered within the image</div></div><p>Cool! See that working code in <span class="emphasis"><em>03_scaled.html</em></span>.</p><p>By adding a single <code class="literal">style()</code> statement, we could set the path’s fills to something less severe, like the blue shown in <a class="xref" href="ch12.html#geojson_filled" title="Figure 12-4. Now more blue-ish than black">Figure 12-4</a>.</p><div class="figure"><a id="geojson_filled"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject12_id318025"/><img src="httpatomoreillycomsourceoreillyimages1614862.png.jpg" alt="Now more blue-ish than black"/></div></div><div class="figure-title">Figure 12-4. Now more blue-ish than black</div></div><p>See <span class="emphasis"><em>04_fill.html</em></span> for that. You could use the same technique to set stroke color and width, too.</p><p>Map projections are extremely powerful algorithms, and different projections are useful for different purposes and different parts of the world (near the poles, versus the equator, for example).</p><p>Thanks primarily to the contributions of <a class="ulink" href="http://www.jasondavies.com" target="_top">Jason Davies</a>, D3’s geo projections plug-ins now support essentially every obscure projection you could imagine. Reference <a class="ulink" href="https://github.com/mbostock/d3/wiki/Geo-Projections" target="_top">the complete visual reference of D3 projections on the wiki</a>. You might also find the <a class="ulink" href="http://bl.ocks.org/3711652" target="_top">projection comparison demo</a> useful.<a id="I_indexterm12_id318078" class="indexterm"/></p></div><div class="sect1" title="Choropleth"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_choropleth">Choropleth</h2></div></div></div><p>Choro-<span class="emphasis"><em>what?</em></span> This word, which can be difficult to pronounce, refers to a geomap with areas filled in with different values (light or dark) or colors to reflect associated data values. In the United States, so-called “red state, blue state” choropleth maps showing the Republican and Democratic leanings of each state are ubiquitous, especially around election time. But choropleths can be generated from any values, not just political ones.<a id="I_indexterm12_id318108" class="indexterm"/><a id="I_indexterm12_id318117" class="indexterm"/><a id="I_indexterm12_id318123" class="indexterm"/><a id="I_indexterm12_id318130" class="indexterm"/><a id="I_indexterm12_id318136" class="indexterm"/><a id="I_indexterm12_id318144" class="indexterm"/></p><p>These maps are also some of the most requested uses of D3. Although choropleths can be fantastically useful, keep in mind that they have some inherent perceptual limitations. Because they use <span class="emphasis"><em>area</em></span> to encode values, large areas with low density (such as the state of Nevada) might be overrepresented visually. A standard choropleth does not represent per-capita values fairly—Nevada is too big, and Delaware far too small. But they <span class="emphasis"><em>do</em></span> retain the geography of a place, and—as maps—they look really, really cool. So let’s dive in. (You can follow along with <span class="emphasis"><em>05_choropleth.html</em></span>.)</p><p>First, I’ll set up a scale that can take data values as input, and will return colors. This is the heart of choropleth-style mapping:</p><a id="I_programlisting12_id318178"/><pre class="programlisting"><code class="kd">var</code> <code class="nx">color</code> <code class="o">=</code> <code class="nx">d3</code><code class="p">.</code><code class="nx">scale</code><code class="p">.</code><code class="nx">quantize</code><code class="p">()</code>
<code class="p">.</code><code class="nx">range</code><code class="p">([</code><code class="s2">"rgb(237,248,233)"</code><code class="p">,</code> <code class="s2">"rgb(186,228,179)"</code><code class="p">,</code>
<code class="s2">"rgb(116,196,118)"</code><code class="p">,</code> <code class="s2">"rgb(49,163,84)"</code><code class="p">,</code><code class="s2">"rgb(0,109,44)"</code><code class="p">]);</code></pre><p>A <span class="emphasis"><em>quantize</em></span> scale functions as a linear scale, but it outputs values from within a discrete range. These output values could be numbers, colors (as we’ve done here), or anything else you like. This is useful for sorting values into “buckets.” In this case, we’re using five buckets, but there could be as many as you like.<a id="I_indexterm12_id318194" class="indexterm"/></p><p>Notice I’ve specified an output range, but not an input domain. (I’m waiting until our data is loaded in to do that.) These particular colors are taken from the <code class="literal">colorbrewer.js</code> file included in <a class="ulink" href="https://github.com/mbostock/d3/tree/master/lib/colorbrewer" target="_top">the D3 GitHub repository</a>—a collection of perceptually optimized colors, selected by Cynthia Brewer, and based on her research.</p><p>Next, we need to load in some data. I’ve provided a file <span class="emphasis"><em>us-ag-productivity-2004.csv</em></span>, which looks like this:</p><pre class="screen">state,value
Alabama,1.1791
Arkansas,1.3705
Arizona,1.3847
California,1.7979
Colorado,1.0325
Connecticut,1.3209
Delaware,1.4345
…</pre><p>This data, provided by the US Department of Agriculture, reports agricultural productivity by state during the year 2004. The units are relative to an arbitrary baseline of the productivity of the state of Alabama in 1996 (1.0), so greater values are more productive, and smaller values less so. (Find lots of open government datasets at <a class="ulink" href="http://data.gov" target="_top">http://data.gov</a>.) I expect this data will give us a nice map of states’ agricultural productivity.</p><p>To load in the data, we use <code class="literal">d3.csv()</code>:</p><a id="I_programlisting12_id318253"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">csv</code><code class="p">(</code><code class="s2">"us-ag-productivity-2004.csv"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">data</code><code class="p">)</code> <code class="p">{</code> <code class="err">…</code></pre><p>Then, in the callback function, I want to set the <code class="literal">color</code> quantize scale’s input domain (before I forget!):</p><a id="I_programlisting12_id318267"/><pre class="programlisting"> <code class="nx">color</code><code class="p">.</code><code class="nx">domain</code><code class="p">([</code>
<code class="nx">d3</code><code class="p">.</code><code class="nx">min</code><code class="p">(</code><code class="nx">data</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code> <code class="k">return</code> <code class="nx">d</code><code class="p">.</code><code class="nx">value</code><code class="p">;</code> <code class="p">}),</code>
<code class="nx">d3</code><code class="p">.</code><code class="nx">max</code><code class="p">(</code><code class="nx">data</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code> <code class="k">return</code> <code class="nx">d</code><code class="p">.</code><code class="nx">value</code><code class="p">;</code> <code class="p">})</code>
<code class="p">]);</code></pre><p>This uses <code class="literal">d3.min()</code> and <code class="literal">d3.max()</code> to calculate and return the smallest and largest data values, so the scale’s domain is dynamically calculated.</p><p>Next, we load in the JSON geodata, as before. But what’s new here is I want to <span class="emphasis"><em>merge</em></span> the agricultural data <span class="emphasis"><em>into</em></span> the GeoJSON. Why? Because we can only bind one set of data to elements at a time. We definitely need the GeoJSON, from which the <code class="literal">path</code>s are generated, but we also need the new agricultural data. So if we can smush them into a single, monster array, then we can bind them to the new <code class="literal">path</code> elements all at the same time. (There are several approaches to this step; what follows is my preferred method.)</p><a id="I_programlisting12_id318308"/><pre class="programlisting"> <code class="nx">d3</code><code class="p">.</code><code class="nx">json</code><code class="p">(</code><code class="s2">"us-states.json"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">json</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Merge the ag. data and GeoJSON</code>
<code class="c1">//Loop through once for each ag. data value</code>
<code class="k">for</code> <code class="p">(</code><code class="kd">var</code> <code class="nx">i</code> <code class="o">=</code> <code class="mi">0</code><code class="p">;</code> <code class="nx">i</code> <code class="o"><</code> <code class="nx">data</code><code class="p">.</code><code class="nx">length</code><code class="p">;</code> <code class="nx">i</code><code class="o">++</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Grab state name</code>
<code class="kd">var</code> <code class="nx">dataState</code> <code class="o">=</code> <code class="nx">data</code><code class="p">[</code><code class="nx">i</code><code class="p">].</code><code class="nx">state</code><code class="p">;</code>
<code class="c1">//Grab data value, and convert from string to float</code>
<code class="kd">var</code> <code class="nx">dataValue</code> <code class="o">=</code> <code class="nb">parseFloat</code><code class="p">(</code><code class="nx">data</code><code class="p">[</code><code class="nx">i</code><code class="p">].</code><code class="nx">value</code><code class="p">);</code>
<code class="c1">//Find the corresponding state inside the GeoJSON</code>
<code class="k">for</code> <code class="p">(</code><code class="kd">var</code> <code class="nx">j</code> <code class="o">=</code> <code class="mi">0</code><code class="p">;</code> <code class="nx">j</code> <code class="o"><</code> <code class="nx">json</code><code class="p">.</code><code class="nx">features</code><code class="p">.</code><code class="nx">length</code><code class="p">;</code> <code class="nx">j</code><code class="o">++</code><code class="p">)</code> <code class="p">{</code>
<code class="kd">var</code> <code class="nx">jsonState</code> <code class="o">=</code> <code class="nx">json</code><code class="p">.</code><code class="nx">features</code><code class="p">[</code><code class="nx">j</code><code class="p">].</code><code class="nx">properties</code><code class="p">.</code><code class="nx">name</code><code class="p">;</code>
<code class="k">if</code> <code class="p">(</code><code class="nx">dataState</code> <code class="o">==</code> <code class="nx">jsonState</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Copy the data value into the JSON</code>
<code class="nx">json</code><code class="p">.</code><code class="nx">features</code><code class="p">[</code><code class="nx">j</code><code class="p">].</code><code class="nx">properties</code><code class="p">.</code><code class="nx">value</code> <code class="o">=</code> <code class="nx">dataValue</code><code class="p">;</code>
<code class="c1">//Stop looking through the JSON</code>
<code class="k">break</code><code class="p">;</code>
<code class="p">}</code>
<code class="p">}</code>
<code class="p">}</code></pre><p>Read through that closely. Basically, for each state, we are finding the GeoJSON element with the same name (e.g., “Colorado”). Then we take the state’s data value and tuck it in under <code class="literal">json.features[j].properties.value</code>, ensuring it will be bound to the element and available later, when we need it.</p><p>Lastly, we create the <code class="literal">path</code>s just as before, only we make our <code class="literal">style()</code> value dynamic:</p><a id="I_programlisting12_id318342"/><pre class="programlisting"> <code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"path"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">data</code><code class="p">(</code><code class="nx">json</code><code class="p">.</code><code class="nx">features</code><code class="p">)</code>
<code class="p">.</code><code class="nx">enter</code><code class="p">()</code>
<code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"path"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"d"</code><code class="p">,</code> <code class="nx">path</code><code class="p">)</code>
<code class="p">.</code><code class="nx">style</code><code class="p">(</code><code class="s2">"fill"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Get data value</code>
<code class="kd">var</code> <code class="nx">value</code> <code class="o">=</code> <code class="nx">d</code><code class="p">.</code><code class="nx">properties</code><code class="p">.</code><code class="nx">value</code><code class="p">;</code>
<code class="k">if</code> <code class="p">(</code><code class="nx">value</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//If value exists…</code>
<code class="k">return</code> <code class="nx">color</code><code class="p">(</code><code class="nx">value</code><code class="p">);</code>
<code class="p">}</code> <code class="k">else</code> <code class="p">{</code>
<code class="c1">//If value is undefined…</code>
<code class="k">return</code> <code class="s2">"#ccc"</code><code class="p">;</code>
<code class="p">}</code>
<code class="p">});</code></pre><p>Instead of <code class="literal">"steelblue"</code> for everyone, now each state <code class="literal">path</code> gets a different fill value. The trick is that we don’t have data for <span class="emphasis"><em>every</em></span> state. The dataset we’re using doesn’t have information for Alaska, the District of Columbia, Hawaii, or Puerto Rico (which, although not a state, is still included in the GeoJSON and appears in the projection).</p><p>So to accommodate those exceptions, we include a little logic: an <code class="literal">if()</code> statement that checks to see whether or not the data value has been defined. If it exists, then we return <code class="literal">color(value)</code>, meaning we pass the data value to our quantize scale, which returns a color. For undefined values, we set a default of light gray (<code class="literal">#ccc</code>).</p><p>Beautiful! Just look at the result in <a class="xref" href="ch12.html#choropleth_map" title="Figure 12-5. A choropleth map showing agricultural productivity by state">Figure 12-5</a>. Check out the final code and try it yourself in <span class="emphasis"><em>05_choropleth.html</em></span>.</p><div class="figure"><a id="choropleth_map"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject12_id318408"/><img src="httpatomoreillycomsourceoreillyimages1614863.png.jpg" alt="A choropleth map showing agricultural productivity by state"/></div></div><div class="figure-title">Figure 12-5. A choropleth map showing agricultural productivity by state</div></div></div><div class="sect1" title="Adding Points"><div class="titlepage"><div><div><h2 class="title" style="clear: both" id="_adding_points">Adding Points</h2></div></div></div><p>Wouldn’t it be nice to put some cities on this map, for context? Maybe it would be interesting or useful to see how many large, urban areas there are in the most (or least) agriculturally productive states. Again, let’s start by finding the data.<a id="I_indexterm12_id318444" class="indexterm"/><a id="I_indexterm12_id318453" class="indexterm"/><a id="I_indexterm12_id318459" class="indexterm"/><a id="I_indexterm12_id318465" class="indexterm"/></p><p>Fortunately, the US Census has us covered, once again. (Your tax dollars at work!) Here’s the start of a raw CSV dataset from the Census that shows <a class="ulink" href="http://1.usa.gov/XWUSrY" target="_top">“Annual Estimates of the Resident Population for Incorporated Places Over 50,000”</a>.</p><pre class="screen">table with row headers in column A and column headers in rows 3 through 4,,,,,,,
,,,
"Table 1. Annual Estimates of the Resident Population for Incorporated Places
Over 50,000, Ranked by July 1, 2011 Population: April 1, 2010 to July 1, 2011"
,,,,,,,,,,
Rank,Geographic Area,,"April 1, 2010",,Population Estimate (as of July 1),,
,,,,Place,State,Census,Estimates Base,2010,2011,,,,
1,New York city,New York,"8,175,133","8,175,133","8,186,443","8,244,910",,,,
2,Los Angeles city,California,"3,792,621","3,792,625","3,795,761","3,819,702"
,,,,
3,Chicago city,Illinois,"2,695,598","2,695,598","2,698,283","2,707,120",,,,
4,Houston city,Texas,"2,099,451","2,099,430","2,108,278","2,145,146",,,,
5,Philadelphia city,Pennsylvania,"1,526,006","1,526,006","1,528,074","1,536,471"
,,,,
6,Phoenix city,Arizona,"1,445,632","1,445,656","1,448,531","1,469,471",,,,
7,San Antonio city,Texas,"1,327,407","1,327,606","1,334,431","1,359,758",,,,
8,San Diego city,California,"1,307,402","1,307,406","1,311,516","1,326,179",,,,
9,Dallas city,Texas,"1,197,816","1,197,816","1,201,715","1,223,229",,,,
10,San Jose city,California,"945,942","952,612","955,091","967,487",,,,
…</pre><p>This is quite messy, and I don’t even need all this data. So I’ll open the CSV up in my favorite spreadsheet program and clean it up a bit, removing unneeded columns. (You could use LibreOffice Calc, Apple Numbers, or Microsoft Excel.) I’m also interested in only the largest 50 cities, so I’ll delete all the others. Exporting back to CSV, I now have this:</p><pre class="screen">rank,place,population
1,New York city,8175133
2,Los Angeles city,3792621
3,Chicago city,2695598
4,Houston city,2099451
5,Philadelphia city,1526006
6,Phoenix city,1445632
7,San Antonio city,1327407
8,San Diego city,1307402
9,Dallas city,1197816
10,San Jose city,945942
…</pre><p>This information is useful, but to place it on the map, I’m going to need the latitude and longitude coordinates for each of these places. Looking this up manually would take <span class="emphasis"><em>forever</em></span>. Fortunately, we can use a <span class="emphasis"><em>geocoding</em></span> service to speed things up. Geocoding is the process of taking place names, looking them up on a map (or in a database, really), and returning precise lat/lon coordinates. “Precise” might be a bit of an overstatement—the geocoder does the best job it can, but it will sometimes be forced to make assumptions given vague data. For example, if you specify “Paris,” it will probably assume you mean Paris, France and not Paris, Texas. It’s good practice to eyeball the geocoder’s output once you get it on the map, and manually adjust any erroneous coordinates (using <a class="ulink" href="http://www.teczno.com/squares" target="_top">teczno.com/squares</a> as a reference).<a id="I_indexterm12_id318535" class="indexterm"/><a id="I_indexterm12_id318541" class="indexterm"/><a id="I_indexterm12_id318548" class="indexterm"/><a id="I_indexterm12_id318553" class="indexterm"/></p><p>I’ll head over to <a class="ulink" href="http://www.gpsvisualizer.com/geocoder/" target="_top">my favorite batch geocoder</a>, paste in just the place names, and click Start! A few minutes later, the geocoder spits out some more comma-separated values, which includes lat/lon pairs. I bring those back into my spreadsheet, and save out a new, unified CSV with coordinates:</p><pre class="screen">rank,place,population,lat,lon
1,New York city,8175133,40.71455,-74.007124
2,Los Angeles city,3792621,34.05349,-118.245323
3,Chicago city,2695598,45.37399,-92.888759
4,Houston city,2099451,41.337462,-75.733627
5,Philadelphia city,1526006,37.15477,-94.486114
6,Phoenix city,1445632,32.46764,-85.000823
7,San Antonio city,1327407,37.706576,-122.440612
8,San Diego city,1307402,37.707815,-122.466624
9,Dallas city,1197816,40.636,-91.168309
10,San Jose city,945942,41.209716,-112.003047
…</pre><p>That was unbelievably easy. Ten years ago that step would have taken us hours of research and tedious data entry, not seconds of mindless copying-and-pasting. Now you see why we’re experiencing an explosion of online mapping.</p><p>Our data is ready, and we already know how to load it in:</p><a id="I_programlisting12_id318590"/><pre class="programlisting"><code class="nx">d3</code><code class="p">.</code><code class="nx">csv</code><code class="p">(</code><code class="s2">"us-cities.csv"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">data</code><code class="p">)</code> <code class="p">{</code>
<code class="c1">//Do something…</code>
<code class="p">});</code></pre><p>In the callback function, we can specify how to create a new <code class="literal">circle</code> element for each city, and then <span class="emphasis"><em>position each circle</em></span> according to the corresponding city’s geo-coordinates:</p><a id="I_programlisting12_id318609"/><pre class="programlisting"> <code class="nx">svg</code><code class="p">.</code><code class="nx">selectAll</code><code class="p">(</code><code class="s2">"circle"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">data</code><code class="p">(</code><code class="nx">data</code><code class="p">)</code>
<code class="p">.</code><code class="nx">enter</code><code class="p">()</code>
<code class="p">.</code><code class="nx">append</code><code class="p">(</code><code class="s2">"circle"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cx"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">projection</code><code class="p">([</code><code class="nx">d</code><code class="p">.</code><code class="nx">lon</code><code class="p">,</code> <code class="nx">d</code><code class="p">.</code><code class="nx">lat</code><code class="p">])[</code><code class="mi">0</code><code class="p">];</code>
<code class="p">})</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"cy"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nx">projection</code><code class="p">([</code><code class="nx">d</code><code class="p">.</code><code class="nx">lon</code><code class="p">,</code> <code class="nx">d</code><code class="p">.</code><code class="nx">lat</code><code class="p">])[</code><code class="mi">1</code><code class="p">];</code>
<code class="p">})</code>
<code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"r"</code><code class="p">,</code> <code class="mi">5</code><code class="p">)</code>
<code class="p">.</code><code class="nx">style</code><code class="p">(</code><code class="s2">"fill"</code><code class="p">,</code> <code class="s2">"yellow"</code><code class="p">)</code>
<code class="p">.</code><code class="nx">style</code><code class="p">(</code><code class="s2">"opacity"</code><code class="p">,</code> <code class="mf">0.75</code><code class="p">);</code></pre><p>The magic here is in those <code class="literal">attr()</code> statements that set the <code class="literal">cx</code> and <code class="literal">cy</code> values. You see, we can access the raw latitude and longitude values as <code class="literal">d.lat</code> and <code class="literal">d.lon</code>. But what we really need for positioning these circles are x/y <span class="emphasis"><em>screen coordinates</em></span>, not <span class="emphasis"><em>geo</em></span>-coordinates.<a id="I_indexterm12_id318649" class="indexterm"/></p><p>So we bring back our magical friend <code class="literal">projection()</code>, which is basically just a two-dimensional scale method. With D3 scales, we put in one number, and get back another. With projections, we put in two numbers, and get back two. (The other main difference is that the behind-the-scenes math for projections is much more complex than the simple normalization of scales.)<a id="I_indexterm12_id318665" class="indexterm"/></p><p>The map projection takes a two-value array as input, with <span class="emphasis"><em>longitude</em></span> first (remember, it’s lon/lat, not lat/lon, in GeoJSON-ville). Then the projection returns a two-value array with x/y screen values. So, for <code class="literal">cx</code>, we use <code class="literal">[0]</code> to grab the <span class="emphasis"><em>first</em></span> of those values, which is <span class="emphasis"><em>x</em></span>. For <code class="literal">cy</code>, we use <code class="literal">[1]</code> to grab the <span class="emphasis"><em>second</em></span> of those values, which is <span class="emphasis"><em>y</em></span>. Make sense?</p><p>The resulting map in <a class="xref" href="ch12.html#choropleth_with_cities" title="Figure 12-6. The top 50 largest US cities, represented as cute little yellow dots">Figure 12-6</a> is suh-weeeet! Check out the code in <span class="emphasis"><em>06_points.html</em></span>.</p><div class="figure"><a id="choropleth_with_cities"/><div class="figure-contents"><div class="mediaobject"><a id="I_mediaobject12_id318732"/><img src="httpatomoreillycomsourceoreillyimages1614864.png.jpg" alt="The top 50 largest US cities, represented as cute little yellow dots"/></div></div><div class="figure-title">Figure 12-6. The top 50 largest US cities, represented as cute little yellow dots</div></div><p>Yet these dots are all the same size. Let’s link the population data to circle size. Instead of a static area, we’ll reference the <code class="literal">population</code> value:</p><a id="I_programlisting12_id318758"/><pre class="programlisting"> <code class="p">.</code><code class="nx">attr</code><code class="p">(</code><code class="s2">"r"</code><code class="p">,</code> <code class="kd">function</code><code class="p">(</code><code class="nx">d</code><code class="p">)</code> <code class="p">{</code>
<code class="k">return</code> <code class="nb">Math</code><code class="p">.</code><code class="nx">sqrt</code><code class="p">(</code><code class="nb">parseInt</code><code class="p">(</code><code class="nx">d</code><code class="p">.</code><code class="nx">population</code><code class="p">)</code> <code class="o">*</code> <code class="mf">0.00004</code><code class="p">);</code>
<code class="p">})</code></pre><p>Here we grab <code class="literal">d.population</code>, wrap it in <code class="literal">parseInt()</code> to convert it from a string to an integer, scale that value down b