svg-pan-zoom
svg-pan-zoom copied to clipboard
Panning & Zooming to particular location - inside a translate
Hi, I have a question about how to pan to and zoom in on a particular coordinate. My exact use-case is inside potentially multiple translates that makes the problem a bit harder. I posted this on stackoverflow but haven't gotten any bites.
The stackoverflow link has an example. I've been messing around with it. I've tried calling matrixTransform
with my coordinates on the results of calling getScreenCTM
on the SVG, as well as the g
element that's used to set the viewport by svg-pan-zoom
but haven't made much progress. One thing I noticed, is that results of calling getPan
returns different results depending on your zoom level, which I found surprising. Should I be setting zoom to 0
before calling pan()
?
Any luck here :)
@sgavinolla001, Kind of.
The coordinates that zoomAtPoint
are expecting aren't SVG coordinates but screen coordinates. If you take your SVG's top
and left
from getClientBoundingRect
and subtract it from the top
and left
of the SVG item you want to zoom in on you'll get the coordinates you need. If you don't have an item you want to zoom in on but instead just X and Y coordinates you can simply position a circle there, grab the top
and left
values and remove it.
This gets it's super close to the right spot, but it's left value for my use case at least is off.
panZoom.reset();
const arrowRect = circle.getBoundingClientRect();
const svgRect = svg.getBoundingClientRect();
const top = arrowRect.top - svgRect.top;
const left = arrowRect.left - svgRect.left;
panZoom.zoomAtPoint(10, { x: left, y: top });
You may also need to consider current zoom using . getSizes()
.
Also when zooming at a point, it just takes into account that specific point (aka the point will be visible on screen after zoom), if you want to zoom into an object, you'll have to account for that so that the object stays visible.
@bumbu, the current zoom is always 1 as I call reset first, right?
Here's jsfiddle showing my current problem.
https://jsfiddle.net/4d2cs0u5/1/
If you click any of the fruit links it's supposed to zoom into the text of that bar, but as you make your way down the list you'll that the closer to the edge of the SVG the further off the panning is. It seems that SVGPanZoom doesn't want to pan past the borders of main SVG object. Is this correct? Ideally it would pan to the point of the x and y coordinate being in the centre of the SVG viewport.
Here's jsfiddle showing my current problem. https://jsfiddle.net/4d2cs0u5/1/
Thank you for the fiddle, it helps understand the issue better.
It seems that SVGPanZoom doesn't want to pan past the borders of main SVG object. Is this correct?
It does pan past the borders.
If you'd call panZoomInstance.getSizes()
you'd see that the actual zoom is different. But that doesn't seem to be the problem here.
Issue 1: Looking at your code, you're using .getBoundingClientRect()
on the SVG, which gives you the X and Y position of the element starting from document corner (aka if you'd add 100px margin to #container
div, your X and Y will be by 100px off). So you have to take this into account first.
Issue 2: You're zooming into the top-left corner of the text, so top-left corner of your text is always in view. You may want to zoom into the middle of your text like this:
const { left, width, top, height } = $(`g[data-fruit="${fruit}"] text`).get(0).getBoundingClientRect();
panZoomInstance.zoomAtPoint(3, { x: left + width/2, y: top + height/2 });
which will give you better results, but still some text will be cut.
Alternative solution: An easy solution is to zoom first, then check if your element is visible and if not - adjust it's position like:
$('.fruit-link').on('click', function () {
const fruit = $(this).attr('data-fruit');
panZoomInstance.zoom(3);
setTimeout(() => {
// WARNING getBoundingClientRect returns values based off viewport
const { left, width, top, height } = $(`g[data-fruit="${fruit}"] text`).get(0).getBoundingClientRect();
if (left < 0) panZoomInstance.panBy({x: -left, y: 0});
if (top < 0) panZoomInstance.panBy({x: 0, y: -top});
// Do same for bottom and right
}, 250);
});
Ideal solution: Ideally you'd calculate what will be the size of your element after zoom and based on that calculate where to pan as to centre on your element. Also you may want to make sure that the element would fit and adjust the zoom accordingly.