Want an introduction to D3 Zoom? Check out this primer.

This is a summary of my longer, much more in-depth tutorial published here and on my blog. In this article, I’ll introduce you to 5 simple synchronous steps to get started with D3 zooming and panning.

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:
  • The zoom behaviour is the event system that keeps track of and passes on the transform values. A listener consumes the user’s 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 behavior 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 element’s 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

  • 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. It’s often best 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.
  • Identify your target elements and write them down. Which elements do you want to move? Make a list of all elements that will move.
  • For each target, identify if you want to use geometric or semantic zoom. You should note down which properties for each target you want to change.
  • Here’s an example table you might end up with:

3. Set up the zoom behaviour

  • 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.
  • Call the zoom behaviour on your base element like this:
zoomBaseElement.call(zoom)

You don’t have to call your zoom base zoomBaseElement of course.

4. Write the handler

  • 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 that you have all 3 values, you need to do whatever you want with it: tx, ty and the scale k.
  • If you want to only administer geometric zoom, you just call
zoomTargetElement
.attr(‘transform’,
‘translate(‘ + transform.x + ‘, ‘ + transform.y + ‘)
scale(‘ + transform.k + ‘)’);

or simpler:

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 k, of course.

  • 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). You can rescale your scale on each zoom and apply it to the target properties you want to change. This includes 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?

  • Calculate/determine the position and the scale
  • 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);
  • 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);
  • Now enable user triggered zoom with
zoomBaseElement.call(zoom)

That was it! You can read the full article with pictures in pink and blue and sources and all that over here or on my blog at Understanding D3 Zoom in SVG and Canvas. Thanks!


Read more of my work on my blog at datamake.io.