Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get absolute coordinates of element inside SVG using JS

Good time forum users. I apologize in advance for my English. I could not find the answer (I decided to ask the English-speaking audience).

Absolute positioning (coordinates) of an element nested in groups relative to the parent container ().

<svg width="100%" height="100%" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMin slice" x="0" y="0" tabindex="1">
  <g transform="translate(100 100)">
       <g transform="translate(100 100)"> 
            <circle r="50" cx="25" cy="25" fill="yellow" />
       </g>
  </g>
<svg>

I would like to get using ES6 + circle coordinates relative to SVG. That is x = 100 + 100 + 25, y = 100 + 100 + 25 for .

How can I get these coordinates? (can be up to an infinite nesting of groups). Thanks for helping.

enter image description here

like image 726
Vad0k Avatar asked Dec 05 '18 15:12

Vad0k


2 Answers

  1. Find the cx and cy values of the circle
  2. Apply any transform that the circle has
  3. Step up to each ancestor element and apply any tranforms found
  4. Stop when you reach the root SVG element

function getCirclePosition(circleElemId)
{
  var elem = document.getElementById(circleElemId);
  var svg = elem.ownerSVGElement;

  // Get the cx and cy coordinates
  var pt = svg.createSVGPoint();
  pt.x = elem.cx.baseVal.value;
  pt.y = elem.cy.baseVal.value;

  while (true)
  {
    // Get this elements transform
    var transform = elem.transform.baseVal.consolidate();
    // If it has a transform, then apply it to our point
    if (transform) {
      var matrix = elem.transform.baseVal.consolidate().matrix;
      pt = pt.matrixTransform(matrix);
    }
    // If this element's parent is the root SVG element, then stop
    if (elem.parentNode == svg)
      break;
    // Otherwise step up to the parent element and repeat the process
    elem = elem.parentNode;
  }
  return pt;
}


var pos = getCirclePosition("thecircle");
console.log("Coordinates are: " + pos.x + "," + pos.y);
<svg width="100%" height="100%" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMin slice" x="0" y="0" tabindex="1">
  <g transform="translate(100 100)">
       <g transform="translate(100 100)"> 
            <circle id="thecircle" r="50" cx="25" cy="25" fill="yellow" />
       </g>
  </g>
<svg>

Update

As @Vad0k pointed out, there is a simpler, but slighlty less accurate approach, that can be used instead:

function getCirclePosition(circleElemId)
{
  var elem = document.getElementById(circleElemId);
  var svg = elem.ownerSVGElement;

  // Get the cx and cy coordinates
  var pt = svg.createSVGPoint();
  pt.x = elem.cx.baseVal.value;
  pt.y = elem.cy.baseVal.value;

  return pt.matrixTransform(getTransformToElement(elem, svg));
}


function getTransformToElement(fromElement, toElement) {
  return toElement.getCTM().inverse().multiply(fromElement.getCTM());
};


var pos = getCirclePosition("thecircle");
console.log("Coordinates are: " + pos.x + "," + pos.y);
<svg width="100%" height="100%" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMin slice" x="0" y="0" tabindex="1">
  <g transform="translate(100 100)">
       <g transform="translate(100 100)"> 
            <circle id="thecircle" r="50" cx="25" cy="25" fill="yellow" />
       </g>
  </g>
<svg>
like image 65
Paul LeBeau Avatar answered Oct 04 '22 10:10

Paul LeBeau


Obtain the childs and parents top and left and get the difference

const circleLeft = circleElement.getBoundingRect().offsetLeft
const circleTop = circleElement.getBoundingRect().top
const parentLeft = circleElement.parentElement.getBoundingRect().offsetLeft
const parentTop = circleElement.parentElement.getBoundingRect().top
const changeInX = parentLeft - cirleLeft
const changeInY = parentTop - circleTop

If you are registering events on this elements, Register them as capturing event rather than bubbling event by passing true as third argument to addEventListener

like image 35
Ndifreke Avatar answered Oct 04 '22 11:10

Ndifreke