score:2

Accepted answer

If I understand the question correctly:

The issue you are running into is that your translate extent is not correct

behavior
  // set min to 1 to prevent zooming out of data
  .scaleExtent([1, Infinity])
  // prevent dragging data out of view
  .translateExtent([[0, 0], [width, height]])
  .on("zoom", this.zoom);

In the above, width and height refer to the width and height of the SVG, not the canvas. Also, zoom extent is not often specified explicitly, but if zoom extent is not specified with zoom.extent(), the zoom extent defaults to the dimensions of the container it was called on.

If your translate extent is equal in size to your zoom extent - by default the extent of the container (the SVG) - which it is, you can zoom and pan anywhere within that container's coordinate space, but not to coordinates beyond it. Consequently, when zoom scale is 1, we cannot pan anywhere as we would by definition pan beyond the translate extent.

Note: This logically means translate extent must contain and not be smaller than the zoom extent.

But, in this scenario, if we zoom in, we can pan and remain within the translate extent.

We can see if you zoom in you cannot pan up beyond the intended limits. This is because the top of the canvas is at y==0, this is the bounds of the translate extent.

As you note if you zoom in you can pan down beyond the intended limits. The bottom of the canvas is h, which is smaller than height which is the translate extent limit, so as we zoom in, we can pan further and further down as the gap between h and height increases each time we zoom (and as noted above, cannot be panned when k==1).

We could try to change the translate extent to reflect the bounds of the canvas. But, as the canvas is smaller than the SVG this won't work as the translate extent would be smaller than the zoom extent. As noted above and noted here by Mike: "The problem is that the translateExtent you’ve specified is smaller than the zoom extent. So there’s no way to satisfy the requested constraint."

We can modify the translateExtent and the zoom's extent, however:

behavior
  // set min to 1 to prevent zooming out of data
  .scaleExtent([1, Infinity])
  // set the zoom extent to the canvas size:
  .extent([[0,0],[w,h]])
  // prevent dragging data out of view
  .translateExtent([[0, 0], [w, h]])
  .on("zoom", this.zoom);

The above creates a zoom behavior that constrains the canvas to its original extent - we would be providing the same parameters if we were calling the zoom on the canvas and wanted to not be able to pan beyond it (except we could rely on the default zoom extent to provide the appropriate values rather than specifying the zoom extent manually).

Here's an updated sandbox.


Related Query