svg-pan-zoom icon indicating copy to clipboard operation
svg-pan-zoom copied to clipboard

Panning & Zooming to particular location - inside a translate

Open davidworkman9 opened this issue 6 years ago • 5 comments

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()?

davidworkman9 avatar Oct 23 '18 23:10 davidworkman9

Any luck here :)

sgavinolla001 avatar Nov 09 '18 08:11 sgavinolla001

@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 });

davidworkman9 avatar Nov 30 '18 17:11 davidworkman9

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 avatar Dec 01 '18 11:12 bumbu

@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.

davidworkman9 avatar Dec 04 '18 16:12 davidworkman9

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.

bumbu avatar Dec 08 '18 12:12 bumbu