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.

13 comments:

  1. This is great but why am I getting all the redraw flicker when the mouse passes over the laps?

    ReplyDelete
  2. I think you're seeing the highlight effect. If you position the mouse over a driver's lap trace (or name) then it will be highlighted (all others dimmed). If you move the mouse about the chart then this effect can appear as a "flicker". I should probably introduce a delay before highlighting (as with tooltips) to avoid the flicker.

    ReplyDelete
  3. Right, a delay to avoid the rapid blink effect would be good. So the pointer would park for a bit before the closest lap is highlighted. Then when the pointer moves away all is visible again. How about fading the lap the pointer is on up/brighter while fading the rest to the background? That would look quite smooth?

    Anyways, really great work! There is some rendering I'm very much hoping to try with D3 but I would like to work with someone more familiar with D3. Are you available for hire? The project would start in a week or two.

    ReplyDelete
  4. > Are you available for hire?

    Please contact me at chris AT focal-technologies DOT com

    ReplyDelete
  5. Really great work! Thanks for sharing.

    Is possible to change colors via json file?
    Thanks
    JM

    ReplyDelete
  6. Configuring colours might best be handled in CSS.

    Currently the colours are hard-coded using d3.scale.category20().

    The source-code is public - you're welcome to fork it:
    https://gist.github.com/2040990

    ReplyDelete
  7. The idea is to apply this to a soccer tournament, and assign to each team the color of the club (Man United -> red, R. Madrid -> white and so on). Or in your work: Ferrari -> Red :)!

    Thanks for the sugestion.
    JMorais

    ReplyDelete
  8. OK, so you could put the colour map in a separate JSON file:

    {
    "Manchester United": "red",
    "Real Madrid": "white",
    ...
    }

    or CSS

    .manchester_united {
    color: red
    stroke: red
    }

    ReplyDelete
  9. Thanks again for your kindness, but my knowledge of javascript does not allow me to go very far.
    I tried your solution (css), adding it to the css file, but I am not able to put it to work.
    I Think I will have to study more javascript before continuing ...
    THANK YOU.
    JM

    ReplyDelete
  10. I think I reached my goal with some modifications in two files: lap-chart.js and 2010au.json.
    In the file 2010au.json added a new field:
    "color": "rgb (r,g,b)"}.
    In lap- chart.js I changed line 717 to: "return (vis, d.color);"
    I do not know if it's the best solution, but is doing what I need ...

    I would love to hear your opinion about this.
    Thanks
    JM

    ReplyDelete
  11. If it works, that's great.

    Can you share a link to it?

    ReplyDelete
  12. Now its only in my local server but here is a link to a zip file in my dropbox:
    http://dl.dropbox.com/u/4853197/futebol.zip

    ReplyDelete
    Replies
    1. Finally got time to look at your visualization - great work!

      Delete