D3 zoom manual

A concise zoom and pan recipe

This is a summary of the article D3 Zoom: The Missig Manual. It’s 5 simple synchronous steps to get your D3 zooming and panning up and running.

Some terminology first

  • A zoom transform is a simple object produced and maintained by D3. It holds three values: the x and y translation as well as the scale factor called k. This is how it looks in its initial state:

img

  • The zoom behaviour is the event system that keeps track of and passes on the transform values. A listener consumes the users’ actions. Once activated it will send an event object with information about this event to a handler function, you write. The most important piece of information your handler will receive is the above transform at every zoom activity. In its simplest from you set the zoom behaviour up like so:
var zoom = d3.zoom().on(‘zoom’, zoomed);
  • The zoom base is the parent element the zoom is attached to or registered on, as they say. It’s the surface that takes in all the user’s moves and gestures and it holds the transform object (the x, the y and the scale factor k).

  • The zoom targets are all the elements we want to move around. If you want to zoom in and out of a circle, then this circle would be your zoom target.

We also distinguish between two types of zoom:

  • Geometric zoom means elements just get scaled up or down without any differentiation. All their properties will get scaled up or down. Think of it as moving or scaling the coordinate system of the respective elements.
  • Semantic zoom means we control each single element’s property during the zoom. You are taking control over every aspect of your elements’ moves.

The manual:

  1. Build your static visual first

    In order to zoom into a visual, you will need a visual.

  2. Identify your zoom base and your zoom targets

    1. Choose your zoom base element first. You can attach the zoom to an svg, g, rect or any other element that your mouse has access to. Note here, that g elements can only register events where they have children with a fill. So, if you have a large g element with a circle of radius 1, your zoom gestures will only work on that tiny circle. Best is often to set-up a dedicated SVG rectangle (rect) with fill but 0 opacity and pointer-events set to all to register the zoom listener on. You might have to unset pointer events of ascendant elements.

    2. Identify your target elements and write them down. Which elements do you want to move? Make a list of all elements that will move.

    3. For each target identify if you want to use geometric or semantic zoom. Best is to note down which properties for each target you want to change.

      Here’s an example table you might end up with:

    FunctionElementZoom TypeScale props
    Zoom baserect#listener-rect--
    Zoom targetcircle.planetsemanticonly circle radius
    Zoom targetx axis tick linessemanticno
    Zoom targetx axis tick labelssemanticno
  3. Set up the zoom behaviour

    1. Create the zoom behaviour with at least:

      var zoom = d3.zoom().on(‘zoom’, zoomed);

      Check out the D3 API reference for d3.zoom() for helper methods like scaleExtent and translateExtent.

    2. Call the zoom behaviour on your base element like"

      zoomBaseElement.call(zoom)
  4. Write the handler

    1. The first thing you want to do is capture the transform object passed into the handler by the listener at every user interaction (wheel or mouse):

      var transform = d3.event.transform

    Now you have all 3 values, you need to do whatever you want with it: tx, ty and the scale k.

    1. If you want to only administer geometric zoom, you just call

      zoomTargetElement .attr(‘transform’, ‘translate(‘ + transform.x + ‘, ‘ + transform.y + ‘) scale(‘ + transform.k + ‘)’);

      or

      zoomTargetElement.attr(‘transform’, transform.toString());

    which is exactly the same. This assumes you want to apply all transform values. You can also focus on tx, ty or only the scale kof course.

    1. If you want semantic zoom you need to rescale.

      Assuming all your data values went through a scale to be translated from data to screen space, this translation changes on zoom. If your data point x = 10 was translated to pixel space 50 before zoom, the zoom will move it to a different point.

      Assuming you translate the x by 5 and scale by 2, the new position will be

      x2 = x1 × k + tx

      or

      x2 = 50 × 2 + 5 = 105.

      Luckily you don’t have to produce these calculations yourself (you could now) but you can rescale your scale on each zoom and apply it to the target properties you want to change. Including axes or circles or rects or whatever target shapes and components you have.

      Assuming you have a scale called xScale, you can use the sugar function .rescaleX() and apply it like so:

    var updatedScale = transform.rescaleX(xScale)

    Now you can use updatedScale in your zoomed function for all the elements you want to update. For example an axis:

    xAxis.scale(updatedScale) gAxis.call(xAxis)

    Or a set of circle x positions:

    circles.attr(‘cx’. function(d) { return updatedScale(d.value); })
  5. Do you need to programmatically move your target into a position?

    1. Calculate/determine the position and the scale

    2. Capture the new positions tx and ty and the new scale k in D3’s own transform object format by saying

      var t = d3.zoomIdentity.translateBy(tx, ty).scale(k)
    3. Store the object in the zoom base AND propagate the changes by calling your first zoom handler which will move the targets with

      zoomBaseElement.call(zoom.transform, t)
    4. Now enable user triggered zoom with

      zoomBaseElement.call(zoom)

Read the full article at D3 Zoom: The Missig Manual!