Reputation: 331
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.
Upvotes: 7
Views: 3037
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
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
Reputation: 101820
cx
and cy
values of the circlefunction 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
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