How We Made "A Vanishing Aquifer"

In our digital feature, A Vanishing Aquifer, we take you through a massive underground water basin in the central US, known as the High Plains Aquifer, and show how it's changed. We do this using a series of responsive and dynamic maps and graphics. I'll walk you through the construction of the project.

The interactive was largely derived from a magazine graphic spread by Lauren James, who was also instrumental in the construction of the digital version.

The Map

This graphic has been reworked for a digital experience in order to introduce concepts and geography gradually, and be readily consumable on small screens. From print, the core map's component parts have been split into a series of layers, as opposed to a single static image, in order to activate labels and features sequentially. We composited the following layers:

raster layers

These layers were derived from data acquired from the USGS. This data, the result of an analysis comparing current aquifer levels (2011-2013) to a historic baseline (1950-2013), was brought into ArcGIS, mapped to a color ramp, and exported according to various color breaks.

Atop those we layer an SVG (vector) representing rivers, states, and country boundaries. We source the data from a mix of internal databases, Natural Earth features, and the National Hydrography Dataset. It's prepared in ArcGIS, then styled in Illustrator, and exported as an SVG:

svg layer

In the browser, by using SVG we can selectively activate, re-style and scale pieces of the graphic. In Illustrator, we also set names of features within the Layers panel, which translate into IDs that can be selected with javascript after an SVG export. The SVG exporter in Illustrator CC 2015 is much improved over previous versions, yielding a more web-friendly SVG.

Using Illustrator to essentially pre-render SVG's from input geospatial data is a different technique than we've done in previous online map projects like Tracking Ivory and Yellowstone's Elk. In those projects we're rendering directly from data, in JSON and GeoJSON. We used the javascript library D3 to render SVG shapes on the fly, in a real geospatial sense. That means taking latitude/longitude coordinates and letting the browser plot them according to a particular map projection as the page is loading. This is a great technique if data is to be later transformed from one form to another, or if data changes over time. We've also used Leaflet to render tiled base maps, and Canvas on top to render animated data with better performance than SVG.

However, for this project, we didn't have data changing over time, so it saved development time to serve up pre-rendered SVGs, using Illustrator for fine-grained cartographic control. It was also a more clear division of labor between myself and Lauren—she prepared static cartography, I worked on its web implementation. In future projects, I could see using both on-the-fly and pre-rendered SVG, as long as everything can be registered to a true geographic space. In this project, no lat/long pairs are referenced in the browser.

States and rivers are one of a few SVG layers that we used. We're loading in another layer of location symbols, leader lines, and curved text (image left). These pieces are activated along with accompanying HTML map labels (image right).

These labels (right) aren't an SVG layer. By rendering HTML, we make these labels more accessible to search engines, more responsive-friendly, and readily editable after export. It saves a lot of time to work out label placement and typography directly in Illustrator (initially GIS software) and then use the NYTimes' ai2html script to generate their placement within the browser. Ai2html is a great tool, and it can export HTML/CSS along with a properly registered underlying base graphic, but it has some limitations. It doesn't export imagery as SVG or transparent PNG well, and while it renders rotated text, it doesn't respect text on a path. Given that, we basically only used ai2html to export uncurved label placement, and exported all other features out manually. That's why our curved text (text on a path) is included within the other SVG layer.

This comprises the map of the aquifer, but we also have an introductory USA locator map, and we've seamlessly integrated this map by zooming out to it:

In this locator map, the red box depicts the exact placement of the aquifer in the zoomed in scale:

That means that if we scale the aquifer raster to the height of the screen, and then zoom that red box to the height of the screen, the two layers will align perfectly.

We achieve this zoom by tweening (transitioning between number values) the viewBox attribute of the locator SVG <svg viewBox="0 0 288 184"> to a viewBox that represents the red box. This is a surprisingly simple technique:

First we get the viewBox of the locator:

var targetViewbox = $("#gfx-svg-locator svg")[0].getAttribute('viewBox')  
// results in: "0 0 288 184"

We then use the getBBox() function on the red box SVG element within the locator SVG, and turn it into the string format that the viewbox wants viewBox="x y width height".

var bbox = $("#L-align_Mobile rect")[0].getBBox();  
var sourceViewbox =  [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")  
// results in "99.33000183105469 49.90999984741211 56.1300048828125 97.44999694824219"

We then smoothly transition each of the viewBox values respectively to each other across a scroll distance (detailed below), and the map scales. There's been some great articles written on scaling SVGs if you want to dig deeper:
here, here.

Meanwhile, while this zooming animation is going on, we're tweening a host of other properties—stroke weights, shape fills, and text visibility, and finally raster visibility to get a seamless transition.

But how are we manipulating layers and properties according to the scroll, in a way that works well across devices?

The Scroll

This scroll-driven interactive has a few goals for a more satisfying UX:

  • Minimal autopilot: transition elements only according to scroll movement. Put another way, don't trigger actions that animate on their own by reaching certain thresholds of scroll distance.
  • No scrolljacking: use the browser's natural scroll/touch functionality, tied to the <body>.
  • Maintain good performance across devices.
  • Be a responsible parallaxer: don't do things just for animation's sake.

In order to pull this off we've frankensteined a few different libraries together. We're adapted the WSJ's scroll-watcher script, which depends on the jQuery fixTo plugin. It essentially pins and un-pins a container according to a position on a page, using javascript or native device features (like the experimental -webkit-sticky). Using -webkit-sticky vastly improves mobile behavior for this kind of thing. This aspect of it allows us to make sure the interactive doesn't overlap the header or footer of the surrounding National Geographic site.

Throughout the scroll distance of the pinned element, scroll-watcher keeps track of percentage distance. While the creators of scroll-watcher use this to control data activation in D3-based interactives, we essentially use this scroll percentage as a vertical timeline to control a sequence of events.

To control event sequencing we're using the very underrated Greensock Animation Platform to tween events. Tweening (in-between-ing) means smoothly interpolating a property from one value to another over a period of time. Interpolation and transitions are something that D3 does well, but I've felt that GSAP does so in a more intuitive way with cleaner code. It's been a while since I didn't use D3 at all in a project, so this was refreshing.

Given that there are a lot of elements changing state over the course of the page we needed a less confusing way to manage this. The page was broken down into active areas--think of a series of invisible yardsticks stacked one after another. If we can track distance travelled across a specific yardstick, we can make tweens occur over these distances. I made these yardsticks a reality when viewing the project in debug mode:

Physically knowing this distance was really helpful in knowing when to trigger what, and I could directly pump those percentage numbers into the application logic. This technique combined with GSAP to handing tweening is actually very similar to what ScrollMagic does.

You can visit the interactive here: