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!
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