2

Here is a fiddle of what actually have. https://jsfiddle.net/Lofdujwr/

I'm using a library for zoom and pan an SVG svgpanzoom. I have a button when clicked it zooms on spain for example, so I put the coordinates in:

   $("#spain").on("click", function() {
        panZoom.zoomAtPoint(12, {x: 188, y: 185});
    });

The problem is when I resize the page that coordinates doens't work anymore I need to recalculate them, I have found a function that might work but I don't know how to use it:

var s = instance.getSizes()
var p = instance.getPan()
var relativeX = s.width / 2 / (p.x + s.viewBox.width * s.realZoom)

// After resize
var s = instance.getSizes()
var p = instance.getPan()
var x = (s.width / 2 / relativeX) - s.viewBox.width * s.realZoom
instance.pan({x: x, y: 0})

function post

And another question, is it possible to get coordinates from a path ID inside the svg for example?

EDIT: Seems like I have to calculate the actual X and Y viewport from my svg and then recalculate it giving my point (x:188, y: 185) on 0.0 viewport, anyone know any example I can see?

Cristian
  • 342
  • 4
  • 17
  • The SVG coordinates should not change if you resize the page, unless you are not using SVG coordinates (which would be bad). Also what are `path coordinates`for you? Paths have segments, not x/y coordinates. – Lain Jan 03 '20 at 13:41
  • For example on my jsfiddle I have a svg map, it contains paths with an id for each country, and would be nice if I can get the coordinates of that path – Cristian Jan 03 '20 at 13:44
  • Do you mean its center, its centroid, its segments or the bounding box? – Lain Jan 03 '20 at 13:45
  • Yes sorry, the center of that path, or some reference that can helpme to set the zoom in – Cristian Jan 03 '20 at 13:46
  • Get the bbox of the path and add its matrix for an _accurate_ bounding box. https://developer.mozilla.org/en-US/docs/Web/API/SVGGraphicsElement/getBBox – Lain Jan 03 '20 at 13:47
  • Tryed to use it on one of the path, I can get the X and Y but still no working, seems that coordinates change when zooming or resizing – Cristian Jan 03 '20 at 13:56
  • The issue is, that your plugin creates a group element and uses a matrix on that to zoom the map. In my experience it is much easier to change the viewBox of the SVG instead since you do not have to recalculate everything with that groups matrix all the time. That is why everything only works on the initial level for you. You have to regard the scale of the matrix in the group `.svg-pan-zoom_viewport`. – Lain Jan 03 '20 at 14:02
  • You need to add the matrix of `.svg-pan-zoom_viewport` to `return pt.matrixTransform(svg.getScreenCTM().inverse());` with your approach. – Lain Jan 03 '20 at 14:09
  • Sorry but i'm a newbie on this, would be nice if you can show me with an example? thanks! – Cristian Jan 03 '20 at 14:13

3 Answers3

4

To answer the actual question:

The plugin you are using is controlling the transforms by changing the matrix of g.svg-pan-zoom_viewport and not the viewBox of the svg.

In your function cursorPoint() you keep transforming the mouse-coordinates referring to the svg, yet you discard the underlying transformation on g.svg-pan-zoom_viewport. That is the reason why you are getting different coordinates while the svg is either resized or moved (=transformed).

If you refer the coordinates to the g.svg-pan-zoom_viewport instead, you will get consistent results.

function cursorPoint(evt){
    pt.x = evt.clientX; pt.y = evt.clientY;
    //return pt.matrixTransform(svg.getScreenCTM().inverse());

    var tGroup = document.querySelector('.svg-pan-zoom_viewport');
    return pt.matrixTransform(tGroup.getScreenCTM().inverse());
}

Another way would be to change the viewBox of the svg instead of using the groups matrix. Yet since your plugin works that way, you should go with it.

Update

I played around with that linked plugin a bit and for me the function zoomAtPoint() is doing something wrong. Let us assume Spain in the linked fiddle is at 165:165. Now to constantly zoom to that location correctly you need to reset it before:

panZoom.reset();
panZoom.zoomAtPoint(6, {x: 165, y: 165});

Else the function either does nothing or zooms somewhere else.

Now to get the coordinates of "argentinia" and zoom to it:

panZoom.reset();

//REM: Apparently the values need some time to adjust after reset() is called, yet is seems to have no callback.
window.setTimeout(function(){
    var tViewport = document.querySelector('g.svg-pan-zoom_viewport');
    var tMatrix = tViewport.transform.baseVal.getItem(0).matrix;
    var tBBox = document.querySelector('#argentina').getBBox();
    var tPoint = {x: (tBBox.x + tBBox.width / 2) * tMatrix.a + tMatrix.e, y: (tBBox.y + tBBox.height / 2) * tMatrix.d + tMatrix.f}

    //REM: Approximate values, I leave the exact calculation up to you.
    panZoom.zoomAtPoint(6, tPoint);
}, 500)

Working fiddle with example buttons: https://jsfiddle.net/04Lg9ruj/

Lain
  • 3,657
  • 1
  • 20
  • 27
  • But my initial problem was to find the coordinates for the path spain on the button for example, not by clicking in to map, I just put the cursor function to show that coordinates change – Cristian Jan 03 '20 at 14:40
1

What about a small change to how you attach the event listener?

Attach to each country instead?

    $(document).ready(function() {
  var panZoom = svgPanZoom('#mapa-mundi', {
        zoomEnabled: true,
        controlIconsEnabled: true,
        fit: true,
        center: true,
        minZoom: 1, 
        maxZoom: 200,
        zoomScaleSensitivity: 1
      });

   $(window).resize(function(){
        panZoom.resize();
        panZoom.fit();
        panZoom.center();
      })

      var svg_rect = document.querySelector('#mapa-mundi').getBoundingClientRect();
      alert("svg: " + svg_rect.top + " " + svg_rect.right + " " + svg_rect.bottom + " " + svg_rect.left);

      $("#spain").on("click", function() {
        panZoom.zoomAtPoint(12, {x: 188, y: 185});
      });

      //Find your root SVG element
      var svg = document.querySelector('#mapa-mundi');

      //Create an SVGPoint for future math
      var pt = svg.createSVGPoint();

      //Get point in global SVG space
      function cursorPoint(evt){
       pt.x = evt.clientX; pt.y = evt.clientY;
       return pt.matrixTransform(svg.getScreenCTM().inverse());
      }

      var country = document.querySelectorAll('.map-hover-svg');

      var my_dict = {};

      country.forEach(function(element){
        var rect = element.getBoundingClientRect();
        my_dict[element.id] = [rect.top, rect.right, rect.bottom, rect.left, rect.bottom - rect.top, rect.right - rect.left];
        //console.log(element.id);
        //console.log(rect.top, rect.right, rect.bottom, rect.left);
      });

      country.forEach(function(element){
        element.addEventListener('click',function(evt){
         var loc = cursorPoint(evt);
         var rect = element.getBoundingClientRect();
         var curr_pan = panZoom.getPan();
         var curr_zoom = panZoom.getZoom();
         var curr_sizes =panZoom.getSizes();
         var real_zoom = curr_sizes.realZoom;
         alert(curr_pan.x + " " + curr_pan.y + " " + curr_zoom + " " + real_zoom);
         panZoom.reset();
         var my_x = my_dict[evt.target.id][3] - svg_rect.left + (my_dict[evt.target.id][5] / 2);
         var my_y = my_dict[evt.target.id][0] - svg_rect.top + (my_dict[evt.target.id][4] / 2);
         //panZoom.zoomAtPoint(3, {x: loc.x - curr_pan.x - svg_rect.left, y: loc.y - curr_pan.y - svg_rect.top});
         panZoom.zoomAtPoint(3, {x: my_x, y: my_y});
         alert(evt.target.id + " at " + loc.x +" "+ loc.y);
        },false);
      });

      ///svg.addEventListener('click',function(evt){
       ///var loc = cursorPoint(evt);
       ///alert(loc.x+" "+loc.y);
      ///},false);

});

This way the event is only fired when you click those red countries. Also, the coordinates of the click seem accurate to me, I played around with it and I got the values expected.

Tried to initially loop through all elements with the class '.map-hover-svg' and add their top right bottom left to a dictionary/hash-table with the key as the id, then you can reference these dictionary items using the event.target.id.

Then you can use the offset of the svg element's top and left properties and half the width and height of the country path elements to always zoom to the middle of the country clicked:

https://jsfiddle.net/ct89f0pj/2/

Alex L
  • 4,168
  • 1
  • 9
  • 24
  • seems like the coordinates are fine but how I would implement that on my button to made it zoom to the country? – Cristian Jan 03 '20 at 14:08
  • Hey Christian, I've made a start, check the fiddle: https://jsfiddle.net/negy8md1/ and let me know? I think we just need some more math corrections for the current pan position. – Alex L Jan 06 '20 at 23:15
  • Seems like if u zoom it and then press the button u wont get centered on the country anymore – Cristian Jan 07 '20 at 08:17
  • Yeah, I need to figure out how to adapt for this case with some math corrections for the current pan position etc. I think this is where we have to drop your function in: `var s = instance.getSizes() var p = instance.getPan() var x = (s.width / 2 / relativeX) - s.viewBox.width * s.realZoom instance.pan({x: x, y: 0})` etc. but I need to take some more time to understand that first before dropping it in. I will hopefully get some time later today to try :) – Alex L Jan 07 '20 at 08:29
  • OR, we save your coordinates for each country in a dictionary on page load and then reset the zoom and pan and then just use `zoomAtPoint()` to these saved coordinates each time.... That could be faster and simpler... – Alex L Jan 07 '20 at 08:30
  • I tryed that before, hardcoded the coords and when I wanted to go some coord i just reseted it but it's the same, coordinations change if the window have diferent size/resolution – Cristian Jan 07 '20 at 08:37
  • Ahh ok understood, then I will try to understand the function you referenced and try to drop it in later today. – Alex L Jan 07 '20 at 08:56
  • 1
    Hey Christian, I tried by creating a dictionary of the country path elements coordinates and height and widths and it seemed to work! https://jsfiddle.net/ct89f0pj/2/ – Alex L Jan 10 '20 at 10:28
0

Actually you're in luck, I developed an hugo shortcode to zoom on a text from the SVG passed as a url parameter, for that I have to search for the text, find its id, from there it's the same as your goal to zoom on that id, so find the bounding box and apply the zoom.

A live demo is here https://www.homesmartmesh.com/docs/microcontrollers/nrf52/thread_sensortag/?svg=nrf52-sensor-tag&text=VEML6030

The source code is embedding the svg-pan-zoom in an hugo short code and the code of interest starts from here https://github.com/WebSVG/hugo-book/blob/7a7d44ea0f7f91a51c15bcb8e8efd294ef29f42f/static/js/setup-svg-pan-zoom.js#L70

text_highlight_zoom(embed,svg,element)

and the magic happens here

    let e_box = element.getBoundingClientRect();
    let svg_box = svg.getBBox();
    const x = (e_box.x + e_box.width/2)  / svg_box.width
    const y = (e_box.y + e_box.height/2) / svg_box.height

then it's a simple call to svg_pz.zoomAtPoint(z,{x:x,y:y},true)

Let me know if this level of details helps you or don't hesitate to ask if you have more questions.

And as bonus, you get a smooth css transition for the zoom operation otherwise the user does not understand what happened, and you get an svg filter animation to highlight the target text.

wassfila
  • 1,173
  • 8
  • 18