Vad0k
Vad0k

Reputation: 331

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

Upvotes: 7

Views: 3037

Answers (4)

Maurice M&#252;ller
Maurice M&#252;ller

Reputation: 1490

I want to append to the excellent answer of Paul LeBeau a more general approach (not only for SVGCircle) and that also takes into account a possible rotation (which needs special handling if you need to know the corners and not the center):

export interface AbsolutePosition {
    x: number
    y: number
}

export function getAbsoluteCenterSVG(element: SVGGraphicsElement): AbsolutePosition {
    const svgRoot = element.ownerSVGElement!;
    const center = svgRoot.createSVGPoint();
    center.x = element.getBBox().x + element.getBBox().width / 2;
    center.y = element.getBBox().y + element.getBBox().height / 2;
    const result = center.matrixTransform(getTransformToElement(element, svgRoot));
    return {
        x: result.x,
        y: result.y,
    };
}

export function getAbsoluteTopLeftPositionSVG(element: SVGGraphicsElement): AbsolutePosition {
    const corners = getAbsoluteDiagonalCorners(element);
    return {
        x: Math.min(corners[0].x, corners[1].x),
        y: Math.min(corners[0].y, corners[1].y),
    };
}

export function getAbsoluteTopRightPositionSVG(element: SVGGraphicsElement): AbsolutePosition {
    const corners = getAbsoluteDiagonalCorners(element);
    return {
        x: Math.max(corners[0].x, corners[1].x),
        y: Math.min(corners[0].y, corners[1].y),
    };
}

function getAbsoluteDiagonalCorners(element: SVGGraphicsElement): AbsolutePosition[] {
    const svgRoot = element.ownerSVGElement!;
    const bbox = element.getBBox();

    const topLeft = svgRoot.createSVGPoint();
    topLeft.x = bbox.x;
    topLeft.y = bbox.y;

    const bottomRight = svgRoot.createSVGPoint();
    bottomRight.x = bbox.x + bbox.width;
    bottomRight.y = bbox.y + bbox.height;

    const transformMatrix = getTransformToElement(element, svgRoot);
    return [topLeft.matrixTransform(transformMatrix), bottomRight.matrixTransform(transformMatrix)];
}

export function getTransformToElement(fromElement: SVGGraphicsElement, toElement: SVGGraphicsElement) {
    return toElement.getCTM()!.inverse().multiply(fromElement.getCTM()!);
}

With those methods you can do something like:

const absolutePosition = getAbsoluteTopRightPositionSVG(targetDOMElement);

If you want/need to add new SVG elements at this position:

const absolutePosition = getAbsoluteTopRightPositionSVG(targetDOMElement);
const newElementGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
newElementGroup.setAttribute("transform", `translate(${absolutePosition.x}, ${absolutePosition.y})`);
targetDOMElement.ownerSVGElement.appendChild(newElementGroup);

And then, inside the newElementGroup, you can add the overlaying elements relative to their group (which removes a lot of calculations if you have more than one element).

Upvotes: 0

Aorlinn
Aorlinn

Reputation: 778

I just found, that you can use getBBox() on a SVG element to get its visible rectangle.

var rect = document.getElementById("myrect");
var visibleRect = rect.getBBox();
console.log("x=" + visibleRect.x + ", y=" + visibleRect.y + ", width=" + visibleRect.width + ", height=" + visibleRect.height);

Upvotes: 0

Paul LeBeau
Paul LeBeau

Reputation: 101820

  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>

Upvotes: 7

Ndifreke
Ndifreke

Reputation: 784

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

Upvotes: 1

Related Questions