May 30, 2012

D3.js Transitions: Zoom, Zoom

Earlier this year I created a lap chart visualization using D3.js.  At the time I mentioned that it could be improved by adding the ability to "zoom" in on laps that feature a lot of overtaking.  I've implemented this feature using D3.js transitions.  You can try the interactive version (modern browser required) or refer to the image below.



When you click on a lap the chart expands the lap and those either side of it, making it easier to see what occurred during the lap.  All other laps are compressed to accommodate the additional space taken up by the expanded laps.  A second click collapses the expanded laps.

Transitions between expanded and collapsed states are animated using D3.js transitions.  Transitions are easy to implement; all that's needed is to select the elements that change during the transition, and the attributes and styles that are changed.  For example, here's the code that transitions the marker circles in the chart:

vis.selectAll('circle.marker') 
    .transition() 
    .duration(TRANSITION_DURATION) 
    .attr('cx', function(d) { 
        return SCALES.x(xform(d.lap, lap)); 
    });

The first line selects the circular markers, then a transition is defined (with one second duration) that is applied to the x-coordinate of each circle's centre.  Similar transitions are defined for the other elements that are transformed during a transition (position traces, lap tick-lines, lap labels, marker labels, etc.)

The xform() function is responsible for transforming x-coordinates when expanding a given lap.  It's not particularly interesting - refer to the code if you're really keen.

That's really all there is to implementing a transition.  The transitions API supports controlling other aspects of the transition such as delays and easing functions but the defaults were sufficient for my purposes.

Transitions automatically handle interpolation between several data types such as numbers and colours (RGB values or CSS names).  You can also implement custom interpolators for other data types.

The only awkward part of the implementation was listening for mouse clicks.  I needed to capture clicks anywhere in the chart to trigger a transition.  Ordinarily, a transparent svg:rect in the foreground of the chart could have been added to capture clicks.  However, the chart already had listeners for mouseover and mouseout to implement highlighting of drivers' lap placings.  A foreground rectangle would block the mouseover/mouseout events from reaching the elements that were listening for them.  Ultimately, I added all clickable elements to a CSS class "zoom", selected these using d3.selectAll and added a click listener to them.

The transitions API is a simple but powerful way of animating charts.  Consider using it next time you build a chart that provides multiple views.

The visualization is shared using a Creative Commons license, and the source-code is available on GitHub.