Roaring to life: How we built our Elwha watershed package

When Lynda Mapes, our environment reporter, came to us with the story of how the Elwha watershed had been recovering after the world’s largest dam removal, we knew we wanted to present it in a way that would make a splash. The final result (GitHub repo) drew heavily on talents from all over the newsroom, ranging from the graphics team’s hand-drawn art to Lynda’s beautiful prose. On the digital side, there were three interesting JavaScript components that deserve more explanation: the watercolor animations, scroll-triggered effects, and FLIP transitions.

You otter be in pictures

Black and white river otter sketch

Black and white base layer

Full color otter sketch

Color “paint” layer

We knew going into this project that we would want to feature the beautiful hand-drawn images of various critters in the Elwha watershed. The final technique is an adaptation of the “lead dust” effect that we used in our Loaded with Lead investigative report. Instead of a single texture that’s used to “reveal” hidden text, we split the sketches into two layers: one base image that only contained the black channel for shading, and another full-color image that gets painted on top. By using intentionally-imperfect circles as our “brushes,” and expanding these circles from randomized positions, the resulting “watercolor” effect helps add a simple splash of life to the static images, without feeling gratuitous or jarring.

Ultimately, what makes this effect possible (and performant) on a range of devices is the canvas createPattern() method, which takes an image and generates a fill pattern object. Most of the time, this is used with smaller images to add texture to a filled path, so that you don’t need to draw and clip the pattern by hand. But in this case, the pattern is the same size as the canvas itself, meaning that we can use this to copy pieces of one image into the canvas in irregular patches–perfect for our watercolor wash. (source code)

Scroll me maybe

Another key part of the presentation is that many animations trigger as they become visible, rewarding readers for scrolling down the page. This is a pretty typical part of news interactive UI these days, but it’s worth looking at how to do it without causing jank.

Our secret weapon for many scroll-triggered effects at the Seattle Times is getBoundingClientRect(), one of the many useful DOM methods that (surprisingly) originated in Internet Explorer. When called on any element, it returns an object with the coordinates for that element relative to the viewport. In other words, it tells you where the object is displayed right now, without having to walk up the DOM for offset values or check the current scroll position. This leads to easy visibility tests like this one:

var bounds = element.getBoundingClientRect();
if ( < window.innerHeight && bounds.bottom > 0) {
  //element is onscreen in some form

getBoundingClientRect() is fast and easy to use for a variety of purposes, and it’s well-supported everywhere. While browsers continue to work to make scrolling smoother, it’s still important to do as little work on scroll listeners as possible, especially when the page may contain many of them (as the Elwha package does). We created a unified scroll notification queue for this project to try to keep the load down. Which brings us to our last animation feature…

FLIP and forget it

I’m a big fan of Paul Lewis’ FLIP animation technique, which leverages the browser to perform smooth animations with as little JavaScript as possible. It’s worth reading the article for more details, but the acronym spells out the basic steps: get the first position, move to the last position, invert the difference through the use of transforms, and then play by removing the offsets.

Elwha feature section

One of our animated feature sections. The animals on the right swap into the main section with a smooth expand effect when clicked.

The nice thing about FLIP is that it’s not only buttery-smooth, but it also adapts well to various lower-level page changes. In this case, when a user clicks on one of the sidebar items to swap between featured animals, we actually move the entire block between two different container elements for styling purposes. But because FLIP works entirely via on-screen coordinates and transforms, none of that  matters. We only have to know the first and last positions to create the transform, and those are easy to get via our old pal getBoundingClientRect().

Although the code for FLIP isn’t hard to write, it comes up often enough (see also: EB-5 visas, teacher pay, and modern dating) that we’ve added a small module to our news app template to make it even easier. The function takes two arguments: an element that you want to animate, and a callback that’s executed between measurements. Just make whatever changes you want during the callback, and the module will figure out the difference and apply transforms to blend between the two. Feel free to take and adapt for your own purposes!

18 holes in 150,000 polygons: A topographic map for the U.S. Open

This year’s U.S. Open is being held at Chambers Bay, just south of Seattle. It’s a unique course for many reasons: its fescue grass changes the way the ball bounces, its wild elevation shifts and terrain require creative approaches to each hole, and there’s only a single tree on the entire course. We wanted to let readers tour Chambers Bay right from their web browser, so we got in touch with Robert Trent Jones II, the course architect, and asked if we could use the elevation data.

Luckily, they said yes! Unluckily, they provided the elevations as a rather hefty 15MB AutoCAD file. We didn’t have an AutoCAD license, but a trial version works for 30 days, which gave us a deadline on figuring out how to convert it to something more web-friendly.

Chambers Bay as viewed in AutoCAD using the 5-foot elevation contours

Chambers Bay as viewed in AutoCAD using the 5-foot elevation contours

The second problem was that the file didn’t contain actual polygons, which we would need to run it in a browser. Instead, it was a set of unconnected contour lines. They were at the right height and looked great, but didn’t form the shapes that a computer could use. I looked into several solutions — most of which were miserable failures —and was about to try writing an AutoLISP script to extract elevations into a file when an architect friend suggested using Google’s SketchUp. Some simplification and use of the “drape” function later, we had ourselves a mesh.

The course mesh as generated in SketchUp

The course mesh as generated in SketchUp



(Incidentally, SketchUp starts every new project with what looks like Stan Lee standing awkwardly in the middle of an empty green plane. This isn’t related to our project at all, I just think it’s weird.)

For this project, we used the industry-standard three.js library for creating our WebGL scene. In addition to handling the camera and object positioning, three.js made it easy to put together the various props (flags, golf balls, tree) that we needed. It also has a great web-based editor that I could use to prototype object placement and try out various model formats.

Once we had the landscape loading, our next job was to texture it by applying color from an image to make it look real. Unlike most 3D models, we didn’t have coordinates at each vertex for the texture position (nor did we have a texture to use). But we did have a PDF of the AutoCAD model prior to mesh conversion. Our graphics team was also struggling with the USGA course diagrams, which didn’t include all the hills and other elevation features. Graphic designer Garland Potts and I worked out a deal: she used our CAD diagram to create a more detailed print graphic, then handed back a shaded and textured image for us to apply to the landscape.

Before and after images of our texture map

Before and after images of our texture map

I wrote a custom WebGL shader to “project” this image down onto the terrain, then added lighting and some dithering to make up for the low resolution. This approach to texturing wouldn’t be appropriate for anything with a lot of vertical surfaces, but the relatively gentle slopes of Chambers Bay are more tolerant.


Finally, over about a week, I placed the tees and holes in the landscape, then set up camera positions for each one. Every hole gets a flyover tour, which usually just consists of moving the camera up and over from tee to green. For several, however, we hand-coded camera movements to draw attention to course features, or to follow the path that the ball would have to take.


Looking back at the project, there are several improvements we’d like to make. The landscape file is enormous, and could probably be culled to reduce size without removing visual detail. We also never added a free camera to the experience, and it would have been nice to have more information about the ideal path of the ball. But overall, I’m very happy with how it turned out.

More importantly, I’m really looking forward to applying this to other projects, where being able to explore something spatially is an asset to our storytelling. For example, stories about large-scale redevelopment, our local mountain ranges or distinctive machinery (see: tunnel digger Bertha) are all excellent potential 3D experiences. Now that we have more experience with the 3D workflow, it’s just another technique in our repertoire for data journalism.

Fifty shades of purple: mapping Seattle’s city council districts

One of the most important lessons I’ve learned as a news developer is that there’s no right way to build a data visualization. But there are plenty of wrong ways.

Reporters frequently come to us with story ideas and actual datasets, if we’re lucky. It’s usually up to us to figure out how to best visualize the data, and the design process often continues long after our development work has begun.

Such was the case for our recent Seattle city council district interactive. The project featured a choropleth map inviting readers to compare the city’s seven newly-created districts across a variety of demographic measures, including age, income, and ethnicity.

The initial map was fairly easy to set up. For each demographic category, we colored the districts on a single-hue progression ranging from white (indicating that zero percent of the district’s population fell into that category) to purple (representing the maximum percentage, i.e. the district with the highest number of residents falling into that category). Most districts ended up somewhere in the middle, as in these views of the racial distribution of Seattle’s white (left) and black (right) populations:

Distribution of Seattle's white population          Distribution of Seattle's black population

These maps succeeded in providing some insight into the city’s racial makeup. It was clear, for instance, that District 2 (Rainier Valley) housed the majority of Seattle’s black population, whereas the rest of the city was consistently white.

There’s a limit to what differences the human eye can distinguish, however, and we were concerned that we were drowning out our data in a sea of subtly different shades of purple. We experimented with bumping up the contrast — first by intensifying the saturation of the maximum value (left), and then by decreasing the saturation of the minimum value (right):

Distribution of Seattle's white population          Distribution of Seattle's white population

Neither of these solutions left us happy. Both seemed to be misrepresenting the data, either by overplaying larger numbers or underplaying smaller, but not insignificant, ones.

In retrospect, the main problem with our initial approach was that it limited us to a single-hue spectrum defined by absolute maximum and minimum values. We had to apply the same color rules to multiple demographic views, and that meant that small variances between districts (i.e. number of people who bike to work, ranging from 2 percent to 6 percent) appeared blown out of proportion, while larger variances appeared washed out.

We decided to return to our original color progression, with the addition of a legend and some new styling:

Distribution of Seattle's white population          Distribution of Seattle's black population

We were still concerned that readers were going to have to work too hard to translate the map’s colors into numbers. It was easy enough, for example, to see that District 2 had the lowest number of white people (31 percent of the district’s population), but it was much more difficult to see that District 6 (Ballard) had the highest percentage (83 percent) — 16 percent higher than the citywide average.

In the week before publication, we buckled down and made a series of significant changes. The result was a map based on a two-tone color progression, indicating how each district stacked up to the citywide average for each demographic. We also switched out the legend boxes with a gradient scale to make it more readable:          

Distribution of Seattle's white population          Distribution of Seattle's black population

This new approach addressed several of our concerns. Switching to a two-tone system made it much easier to identify small differences that fell just above or below average. Additionally, by centering the progression around an average value rather than scaling it from an absolute maximum, we were able to provide a more accessible, at-a-glance view of what the city looked like as a whole.

Reporters, editors, and developers all put their heads together to work out the best presentation for this map, and the final form didn’t materialize until fairly late in the process. Our efforts were well worth it. Fielding criticisms and suggestions at each stage of the design process allowed us to identify and slowly chip away at discrete problems, and resulted in a product that everyone was satisfied with.

Burying the lead: better journalism through iteration

“Kill your darlings” isn’t only good advice for print journalism. Developing a successful digital project requires ruthless editing, no matter how attached you may be to that perfect paragraph or clever piece of code. Our recent Loaded with lead series, on the dangerous contamination found at gun ranges throughout the country, is a perfect example. While the final design is striking, bold, and distinctly digital, we threw away a lot of work to reach that point. Today I’d like to show how we moved through various iterations of the project until we found something right for the piece.

When we first started putting together our online plans for “Loaded with lead,” we didn’t yet have a final version of the story, or a solid headline photo to serve as inspiration. I began by experimenting with a James Bond-like screen wipe, blooming out from a gun or target to reveal the headline. Even as rough prototypes, these concepts were underwhelming.

Click the background to play the animation

Once we had the photo that would be used for the story in print, I tried another approach. Using WebGL and a handmade depth map, I set up a shifting perspective effect, changing the viewpoint in response to the mouse position to let readers look down the contaminated shooting range. It was a neat trick, but it didn’t really have any relationship to the reporting, or the problem of lead contamination, so we dropped it.

Move the mouse to see the depth effect in browsers that support WebGL

At this point, art director Susan Jouflas and I started on a new concept for the design. One of the dangers of lead in gun ranges is that it’s ejected from the gun as airborne dust: from there, it’s inhaled by shooters, settles on nearby surfaces, and gets absorbed into clothing. How could we portray this pervasive contamination to readers in the browser? We spent a lot of time looking at the ways that dust is shown in film, such as the Emmy-winning title sequence for the BBC’s Great Expectations:

To produce a similar effect, I built a multi-layered particle system in WebGL. We spawned the particles from behind the headline, as though the words “loaded with lead” were themselves emitting poisonous dust. A canvas-based fallback meant that browsers without WebGL would still get a similar–if far less elaborate–display. By tweaking the balance of sizes and directions for the particles, we found ourselves with a pretty convincing simulation to place over the image. Alongside the airborne dust, we added a smear of grime that would accompany the user’s cursor (or finger, on a touchscreen), and created a treatment for the article’s pullquotes in which grime would accumulate in the corners of the quote box as the reader scrolled past.

Click to play the “grime” animation

In general, we liked the dust and grime, but it didn’t provoke the strong reaction we were hoping for, and some people who saw it were annoyed by the contrast between the white floating particles and the black accumulation. The only design element that our test readers really loved was the dirty fingerprints left on touchscreens, which gave the piece a gritty feel in keeping with the reporting.

With that in mind, I decided to try one more idea that had been kicking around in our discussions. Susan used chalk and watercolor to create a texture made of heavy, black dust, which would be swiped “onto” the screen in response to touch or cursor movements. As if in a wax-resist painting, the user’s trail of contamination would reveal the white headline text against the previously white background. Immediately, we knew we had something special. Testers loved the effect, and it had a strong visual identity that we could use throughout the story.

Lead web producer Katrina Barlow, who did much of the design and layout on this piece, ran with the concept and integrated the black texture into the pull quotes, replacing the accumulation effect. From there, it was natural to allow the quotes to respond to a user the same way that the title did, although we made sure that they started with the top line or so already revealed. The effect was strong enough that we even dropped the cursor smudge effects, although we kept the fingerprints on large touchscreens.

Throwing away those earlier prototypes was hard to do, but we wouldn’t have been nearly as happy with the final result if we hadn’t. This is a hard lesson to learn, especially for beginning developers, who are still learning their craft and are (rightly) attached to their hard-won code, but it’s ultimately just as important as tooling and experience. When building news apps, be willing to kill your darlings: you’ll be glad you did.

Introducing our news app template

Hi, I’m Thomas Wilburn, newsroom web developer here at The Seattle Times. I work with editors and reporters to tell stories on the web, ranging from data visualizations to custom news applications. One of my most important tools for putting out great projects under deadline pressure is our news app template.

Digital storytelling is not new at The Seattle Times — you only have to look to Sea Change or our election guides to see that — but it hasn’t had a consistent process for development. Some of our news apps were built in Django, some in WordPress, and others in notepad.exe, depending on the staff assigned and their mood at the time. When I joined the newsroom earlier this year, one of my goals was to create a standard platform for digital projects, generating static files for ease of maintenance and low-stress hosting.

The result is a scaffolding system built on top of Grunt and NodeJS for producing news applications with the absolute minimum of friction (editorial or technical). In addition to populating a project with boilerplate HTML, the project initialization process also sets up the following helpful features:

  • A local development server, with live reload integration
  • Lo-dash templates, with some useful built-in helper functions
  • LESS and JavaScript compilation via AMD modules
  • A “watch” server to run build tasks automatically whenever files change
  • Bower pre-configured to install client-side libraries in a standard location
  • The ability to pull data from CSV, JSON, or Google Docs
  • One-command publication to Amazon S3

In many ways, this list is similar to scaffolding solutions from other organizations, including the NPR app template and the Chicago Tribune’s Tarbell. However, being built on NodeJS, the Seattle Times template is a bit easier to set up, and runs on more diverse software (namely, Windows). As a result, it’s been easy to get our web producers working on the same stack that we use for our big projects.

Our experiences using this app scaffolding have been positive so far. Using this scaffolding, we can be up and running on a new project in minutes, and the common structure means that it’s easy to reuse components from one app to another. Fast deployment makes our development faster, and being able to pull from Google Docs makes it easier to bring in editors and reporters. If they can use a spreadsheet, they can edit our interactives. We’ve used it to power many of our online features this year, including “Where the Bidding Wars Are?” and our Oso Landslide timeline. It even runs our Seahawks fan map!

As big proponents of open-source software, our team believes this kind of slick development experience is just too cool to keep to ourselves. So we’ve made our scaffolding available on GitHub under a GPL license. There are a few Seattle Times-specific bits you’ll need to adapt if you use it for yourself, such as our ad and tracking code. But other than that, I think it could be useful for anyone building static sites — inside or outside of a newspaper. If you build something with it, we’d love to hear about it!