如何从SVG内的路径元素获取X、Y坐标?

huangapple go评论82阅读模式
英文:

How to get X,Y coordinates from path element inside SVG?

问题

Here is the translated content:

这里是一个实际情况的代码示例。
https://jsfiddle.net/Lofdujwr/

我正在使用一个用于缩放和平移SVG的库 svgpanzoom
当点击按钮时,它会缩放到西班牙等位置,所以我将坐标放入代码中:

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

问题是,当我调整页面大小时,这些坐标不再起作用,我需要重新计算它们。我找到了一个可能有效的函数,但我不知道如何使用它:

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

// 调整大小后
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})

还有一个问题,是否可以从SVG中的路径ID获取坐标?

编辑: 看起来我需要计算我的SVG中的实际X和Y视口,然后重新计算它,以便在0.0视口上给出我的点 (x:188, y: 185)。有人知道我可以参考的示例吗?

英文:

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?

答案1

得分: 4

To answer the actual question:

你正在使用的插件通过更改 g.svg-pan-zoom_viewport 的矩阵来控制转换,而不是 svgviewBox

在你的 cursorPoint() 函数中,你一直在将鼠标坐标转换为 svg 的坐标,但是你却丢弃了 g.svg-pan-zoom_viewport 上的底层变换。这就是为什么在 svg 被调整大小或移动(= 转换)时会得到不同的坐标的原因。

如果你将坐标引用到 g.svg-pan-zoom_viewport 上,你将获得一致的结果。

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

另一种方法是改变 svgviewBox 而不是使用组的矩阵。但由于你的插件是这样工作的,你应该继续使用它。

更新:

我对链接的插件进行了一些尝试,对我来说,zoomAtPoint() 函数有点问题。让我们假设链接的示例中西班牙的坐标是 165:165。现在,为了始终正确缩放到该位置,你需要在此之前重置它:

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

否则,该函数要么不起作用,要么会缩放到其他位置。

现在,要获取 "阿根廷" 的坐标并缩放到它:

panZoom.reset();

//备注:显然,调用 reset() 后的值需要一些时间来调整,但似乎没有回调函数。
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}

    //备注:这些是近似值,我将精确的计算留给你。
    panZoom.zoomAtPoint(6, tPoint);
}, 500)

带有示例按钮的工作示例:
https://jsfiddle.net/04Lg9ruj/

英文:

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/

答案2

得分: 1

关于如何附加事件侦听器,考虑一下进行小的更改吗?

附加到每个国家?

$(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});
  });

  // 找到你的根SVG元素
  var svg = document.querySelector('#mapa-mundi');

  // 为将来的计算创建一个SVGPoint
  var pt = svg.createSVGPoint();

  // 获取全局SVG空间中的点
  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];
  });

  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: my_x, y: my_y});
      alert(evt.target.id + " at " + loc.x +" "+ loc.y);
    },false);
  });

});

这样事件只会在点击那些红色国家时触发。点击坐标似乎对我来说是准确的,我试着玩了一下,得到了预期的值。

尝试初始循环遍历所有具有类'.map-hover-svg'的元素,并将它们的上、右、下、左添加到一个字典/哈希表中,以id作为键,然后您可以使用event.target.id引用这些字典项。

然后,您可以使用svg元素的顶部和左侧属性的偏移以及国家路径元素宽度和高度的一半,始终缩放到点击的国家的中间:

https://jsfiddle.net/ct89f0pj/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 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/

答案3

得分: 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."

英文:

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.

huangapple
  • 本文由 发表于 2020年1月3日 21:37:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/59579575.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定