Skip to content Skip to sidebar Skip to footer

How To Get X,y Coordinates From Path Element Inside Svg?

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 ex

Solution 1:

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.

functioncursorPoint(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/

Solution 2:

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 elementvar svg = document.querySelector('#mapa-mundi');

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

      //Get point in global SVG spacefunctioncursorPoint(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/

Solution 3:

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

Post a Comment for "How To Get X,y Coordinates From Path Element Inside Svg?"