Wednesday, October 26, 2016

Visualize world data automagically

Several years ago I started a project writing a plug-in for D3.js to combine the map projections with data markers. That project became the earth plug-in (which you can get in my d3-plugins repo – https://github.com/hrobertking/d3-plugins). Although it's been fully functional for a while, I've recently made a few changes to it that enable it to go a little further, so I thought this would be a good opportunity to write a (hopefully) brief post to explain a little better what it can do.

For this post, I'm going to describe hypothetical scenarios that will almost certainly never occur in real life, but scenarios that, hopefully, will be similar to real-life scenarios you can imagine. Also, since I have been a strong advocate – dare I say 'evangelist' – of Progressive Enhancement and a few other UI engineering topics for many years, this post will have a specific structure and make a few assumptions without going too deeply into the case for Progressive Enhancement or other topics, such as accessibility. Even though those topics are important and seldom, even in my own blog posts, get the attention they deserve, they're not the focus here.

That all being said, let's start with the task at hand – developing an interesting page that shows data points scattered across the globe. For our example, we'll use the world's busiest airports...but it could as easily be something else, like sales data or web traffic.

The most descriptive thing we can do with the data is to put it in a table, which also happens to be the best approach for accessibility issues. So we have a table something like this...


HTML for our example

<table class="earth-viz" id="busiest-airports">   <caption>10 of the world's busiest airports</caption>   <thead>     <tr>       <th>Country</th>       <th>City</th>       <th>Activity</th>       <th>Name</th>       <th>Latitude</th>       <th>Longitude</th>       <th>Size</th>     </tr>   </thead>   <tbody>     <tr>       <td>US</td>       <td>ATL</td>       <td>68343</td>       <td>Hartsfield Jackson Atlanta International</td>       <td>33.636719</td>       <td>-84.428067</td>       <td>16</td>     </tr>     <tr>       <td>US</td>       <td>ORD</td>       <td>59692</td>       <td>Chicago O'Hare International</td>       <td>41.978603</td>       <td>-87.904842</td>       <td>14</td>     </tr>     <tr>       <td>US</td>       <td>DFW</td>       <td>56496</td>       <td>Dallas Fort Worth International</td>       <td>32.896828</td>       <td>-97.037997</td>       <td>12</td>     </tr>     <tr>       <td>US</td>       <td>LAX</td>       <td>51396</td>       <td>Los Angeles International</td>       <td>33.942536</td>       <td>-118.408075</td>       <td>10</td>     </tr>     <tr>       <td>CN</td>       <td>PEK</td>       <td>48226</td>       <td>Capital International</td>       <td>40.080111</td>       <td>116.584556</td>       <td>10</td>     </tr>     <tr>       <td>US</td>       <td>CLT</td>       <td>44583</td>       <td>Charlotte Douglas International</td>       <td>35.214</td>       <td>-80.943139</td>       <td>8</td>     </tr>     <tr>       <td>US</td>       <td>DEN</td>       <td>44438</td>       <td>Denver International</td>       <td>39.861656</td>       <td>-104.673178</td>       <td>8</td>     </tr>     <tr>       <td>US</td>       <td>LAS</td>       <td>41164</td>       <td>McCarran International</td>       <td>36.080056</td>       <td>-115.15225</td>       <td>6</td>     </tr>     <tr>       <td>US</td>       <td>IAH</td>       <td>39808</td>       <td>George Bush Intercontinental</td>       <td>29.984433</td>       <td>-95.341442</td>       <td>6</td>     </tr>     <tr>       <td>GB</td>       <td>LHR</td>       <td>37680</td>       <td>London Heathrow</td>       <td>51.4775</td>       <td>-0.461389</td>       <td>4</td>     </tr>   </tbody> </table>


...and we can add all the standard accessibility information to describe it and we can make the 'relative size' column a simple rank or we can scale the number, as I've done here, or, for example, scale it so it reflects a percentage of total volume.

Note that the longitude and latitude columns here are important for the visualization even if they're not important to the reader. If you decide to hide them from the reader (and AT), they still need to be present in the code.

As you'll notice at this point, this is not unlike code you might push on a daily (or more frequently) basis – which is the point. At this juncture, you would, of course, add CSS to style the table...perhaps to hide the longitude and latitude columns. After getting the table styled as you want it, it's time to add the JavaScript.

Here you'll add the D3.js geo libraries – I use d3.v3.min.js for the core, d3.geo.projection.v0.min.js, and topojson.v1.min.js – and my earth plug-in library. With the enhancements made earlier this month, that's all you have to do. The plug-in will pull the marker information using the class earth-viz and generate the visualization. If you want the visualization rendered in another part of the page (rather than where the table is placed), you can still use the constructor to call the plug-in explicitly.

One other change is that you can call the constructor with either some or all four parameters – visualizationElement, projection, visualizationWidth, and markerDataTable – or use can use a config object { data:<marker-data-table>, element:<visualization-element>, style:<projection>, width:<visualization-width> }. This change is stylistic only – there is no functional difference, it's only purpose is to allow greater flexibility so that engineers using it can maintain consistent coding practices.

Of course, calling the constructor is entirely optional now as long as the markup contains the key class, so if you're displaying static information, that's really all there is to it – you need do nothing more. If your visualization needs to be updated, or if you're just interested to learn more, keep reading.

The second major change in the most recent version is that the visualization object is maintained in the window scope, and can be accessed at any point after it's been created. The name of the window property is the same as the visualization ID (i.e., the ID of the marker table with '-earth-viz' added to the end). This means that with our example,the visualization is accessible by referencing window['busiest-airports-earth-viz'].

When this is tied to the ability to get the marker table id directly from the visualization, this makes it remarkably easy to hook updates to the visualization to updates to the table, by simply calling the parseMarkerData function something like... window['busiest-airports-earth-viz'].parseMarkerData('busiest-airports');. If you already have a timed refresh of the data that is updating the table (along with all the accessibility hooks that announce the live data), simply add this into the callback and your visualization is updated on the data refresh.

I know this has been a quick jaunt through a fairly powerful plug-in, but hopefully it's explained a bit about the newest features. As always –

Happy coding.

No comments:

Post a Comment