Search code examples
javascriptd3.jssvgzoomingtranslate-animation

D3 zoom translateExtent calculation when target smaller than container


For a work project I have a requirement to constrain a zoomable svg element within a container. I have a reduced example of the problem here:

https://codesandbox.io/s/ryzp6x5lkn

In the above codesandbox I have a red rect contained within a g (container) within a square svg (node) canvas.

The container has a zoom 'zoom' applied to it. On zoom event the d3.event.transform is applied to the rect.

The requirement is for the rect to be contained within the svg node at all zoom scales.

To do this I am applying a zoom.translateExtent and updating it on every transform.

const updateTranslateExtent = () => {

  const scale = container.property("__zoom").k;
  const containerBBox = container.node().getBBox();
  const containerTransform = container.node().getCTM();
  const containerTranslateX = containerTransform.e;
  const containerTranslateY = containerTransform.f;
  const viewportWidth = parseInt(node.attr("width"));
  const viewportHeight = parseInt(node.attr("height"));

  const extent = [
    [
      (viewportWidth - containerBBox.width - containerTranslateX) / scale * -1,
      (viewportHeight - containerBBox.height - containerTranslateY) / scale * -1
    ],
    [
      viewportWidth / scale + containerTranslateX * scale,
      viewportHeight / scale + containerTranslateY * scale
    ]
  ];

  zoom.translateExtent(extent);
};

This works to a certain extent but breaks down when a scale is applied.

Can anyone with any experience with d3 zoom help me here?

I've tried extensive googling and trial+error with the extent calculations but have not been able to get it working 100%.

Thanks in advance!


Solution

  • Turns out after much trial and error that this was the correct calculation:

    const extent = [
      [
        (viewportWidth - containerBBox.width - containerTranslateX) / scale * -1,
        (viewportHeight - containerBBox.height - containerTranslateY) / scale * -1
      ],
      [
        viewportWidth / scale + containerTranslateX / scale,
        viewportHeight / scale + containerTranslateY / scale
      ]
    ];
    

    Only slightly different but it was a lot of pain to arrive at this conclusion!

    Here's an updated codepen: https://codesandbox.io/s/7m5v9n0jmq