Search code examples
d3.jscanvaszooming

d3 zoom relative to viewport size


I’m currently trying to use the d3 zoom library to add smooth zooming and panning to my canvas element. It should work for different viewport sizes, so I need to have the size of the canvas elements relative to the viewport.

When zoomed out as far as possible (as controlled by the scaleExtent) the elements should fill the screen width nicely on all devices.

const canvas = document.querySelector('canvas')
const context = canvas.getContext('2d')

canvas.width = canvas.offsetWidth
canvas.height = canvas.offsetHeight

const zoom = d3.zoom()
  .scaleExtent([0.4, 2])
  .on('zoom', ({transform}) => zoomed(transform))

d3.select(canvas).call(zoom)
zoomed(d3.zoomIdentity)

function zoomed(transform)  {
  console.log(transform);

  context.save()
  context.clearRect(0,0, canvas.width, canvas.height)
  const scale = transform.k * canvas.width/2000

  context.translate(transform.x, transform.y)
  context.scale(scale, scale)
  
  drawPaths()

  context.restore()
}

I tried adding some sort of value (that’s relative to the canvas width) to the scale value, but I’m just guessing the 2000 and I am afraid it might mess with my code later on, when I don’t manipulate the d3 transform object.

Here is a Codepen: https://codepen.io/lkssmnt-the-lessful/pen/wvgjmbw


Solution

  • I solved it by using a zoom-to-fit method on initialization:

    function initCanvas() {
      const midX = bbox.x + bbox.width / 2;
      const midY = bbox.y + bbox.height / 2;
    
      const scale = Math.min(viewportWidth / bbox.width, viewportHeight / bbox.height);
      const translateX = viewportWidth / 2 - midX * scale;
      const translateY = viewportHeight / 2 - midY * scale;
    
      d3.select(canvas).call(
        zoom_function.transform,
        d3.zoomIdentity.translate(translateX, translateY).scale(scale)
      );
    }
    

    This will center your paths on initialization but will not make the zoomExtent relative to the viewportsize. This could however also be done by just giving the scaleExtent function a relative value.

    Additional info about bounding box: In my case, as the paths were not changing, figuring out the bounding box was not something I had to do programmatically but there are ways to do it like this: https://codepen.io/TomashKhamlai/pen/KNrjqE