Home > Mobile >  svg getCTM() not returning expected x/y values
svg getCTM() not returning expected x/y values

Time:06-25

I am working with a SVG element as following.

The pattern of this SVG is that the SVG element circle in this case would always have some parent element <g><g></g></g> in this case and the parent element may or may not have some sort of transform attributes and the element itself may or may not have some sort of transform attributes.

<!DOCTYPE html>
<html lang="en">

<head>

    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
    <rect x="0" y="0" width="1280" height="720" fill="ghostwhite"></rect>
        <g transform="translate(10,10)">
            <g transform="translate(100 100)">
                <circle id="circ" r="20" cx="25" cy="25" fill="green"
                transform="translate (100,400) scale(2)" />
            </g>
        </g>
    </svg>
</body>
</html>

I need to know the absolute x,y(transformMatrix.e, transformMatrix.f) coordinate of the circle element that equates to the cumulative transform of each of the attributes coming from any of its parent elements plus its own transform element.

So, in this case, I want javascript to return x=10 100 25*2 100=260 and y=10 100 25*2 400=560.

I tried using getCTM() as my understanding was it takes into account the effect of all cumulative transform for a particular element and tried it like this. I also took help from this post to convert DOMMatrix to svgMatrix.

However, it does not quite do the job. The javascript returns convertedMatrix.e=210 and convertedMatrix.f=510. I don't know where this is going wrong in using getCTM().

I can't figure out how can I achieve the desired using this approach and what adjustments do I need to make here?

const toElement = document.querySelector('svg');
const fromElement = document.querySelector('circle')
const toCTM = toElement.getCTM();
const fromCTM = fromElement.getCTM();
const convertedMatrix = toCTM.inverse().multiply(fromCTM);
console.log(convertedMatrix);
<!DOCTYPE html>
<html lang="en">

<head>

    <title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
    <rect x="0" y="0" width="1280" height="720" fill="ghostwhite"></rect>
        <g transform="translate(10,10)">
            <g transform="translate(100 100)">
                <circle id="circ" r="20" cx="25" cy="25" fill="green"
                transform="translate (100,400) scale(2)" />
            </g>
        </g>
    </svg>
</body>
<script type="text/javascript">
</script>

</html>

CodePudding user response:

The transform matrix is not the same as the result of its application to a point. The transform list

translate(10,10) translate(100 100) translate(100,400) scale(2)

consolidates to (note the a and d values)

matrix(2, 0, 0, 2, 210, 510)

and make this computation:

cx' = cx * 2   210
cy' = cy * 2   510

For a point (25, 25), this computes to

25 * 2   210 = 260
25 * 2   510 = 510

just like you expected.

In the form of a script, you need to construct a point that the matrix can be applied to. Then read its coordinates. Don't bother about the old interfaces SVGMatrix and SVGPoint, that is a problem of ten years past.

What you need to bother about is the result of implicit transformations by the viewBox and preserveAspectRatio attributes. They are included in the matrix produced by .getCTM().

If you want to exclude the implicit transformation, you need to apply it inversely, which is what the code you took from the linked answer does.

Note: I am a bit stumped to find that Firefox returns null on svg.getCTM(). That is a bug. Chrome handles that case correctly.

const svg = document.querySelector('svg');
const matrixViewBox = svg.getCTM();
const circle = document.querySelector('circle');
const matrixCircle = circle.getCTM();
const x = circle.cx.baseVal.value; // takes care of unit conversion
const y = circle.cy.baseVal.value;

console.log(new DOMPoint(x, y).matrixTransform(matrixCircle));

matrixFromCircleToViewBox = matrixViewBox.inverse().multiply(matrixCircle);
console.log(new DOMPoint(x, y).matrixTransform(matrixFromCircleToViewBox));
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 1280 720">
    <rect x="0" y="0" width="1280" height="720" fill="ghostwhite"></rect>
    <g transform="translate(10,10)">
        <g transform="translate(100 100)">
            <circle id="circ" r="20" cx="25" cy="25" fill="green"
            transform="translate (100,400) scale(2)" />
        </g>
    </g>
</svg>

  • Related