Search code examples
javascriptsvgtransform

SVG absolute transform list wrong value for text elements


This question is based on a brilliant answer here.

I am trying to utilize the same concept for svg text element and I don't think it is returning the correct value. I am inheriting this svg and I can't do anything about the way the transform elements are applied. I was hoping javascript can return the absolute x and y value for each of the svg elements. For circle, it does an excellent job, but for text, it fails.

//build gridline with svg tooltip with "title" element
const widthPoints = [];
const heightPoints = [];

for (let i = 0; i <= 720; i += 10) {
    heightPoints.push(i)
};

for (let i = 0; i <= 1280; i += 10) {
    widthPoints.push(i)
};

const scaleY = d3.scaleLinear()
    .range([720, 0])
    .domain(d3.extent(heightPoints, d => d))

const scaleX = d3.scaleLinear()
    .range([0, 1280])
    .domain(d3.extent(widthPoints, d => d));

d3.select('svg')
    .append('g')
    .attr('class', 'yAxisLeft')
    .call(d3.axisLeft(scaleY)
        .ticks(heightPoints.length - 1)
        .tickSizeInner([(-(1280))])
    );

const newHeight = JSON.parse(JSON.stringify(heightPoints)).reverse();

d3.selectAll('body > svg > g.yAxisLeft > g.tick')
    .each(
        function(d, i) {
            const selection = d3.select(this);
            selection.selectAll('title')
                .data(function() {
                    return newHeight.filter((a, k) => k === i)
                })
                .join('title')
                .text((d) => {
                    return 'y Coordinate=' + `${d}`
                })
        }
    );

d3.select('svg')
    .append('g').attr('class', 'xAxisBottom')
    .call(d3.axisBottom(scaleX)
        .ticks(widthPoints.length - 1)
        .tickSizeInner([(-(720))])
    )
    .style('transform', `translateY(720px)`)

d3.selectAll('body > svg > g.xAxisBottom > g.tick')
    .each(
        function(d, i) {
            const selection = d3.select(this);
            selection.selectAll('title')
                .data([d])
                .join('title')
                .text((d) => {
                    return 'x Coordinate=' + `${d}`
                })
        }
    );
 
 // return x,y coordinate of each item as a result of all transform
 
 const svg = document.querySelector('svg');
const matrixViewBox = svg.getCTM();

const circCoord = (element) => {

    const matrixElement = element.getCTM();
    const x = element.cx.baseVal.value; // takes care of unit conversion
    const y = element.cy.baseVal.value;

    const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement);
    const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox);

    return coord;

};

const circleVal =[];

document.querySelectorAll('circle')
    .forEach(
        (a) => {
            circleVal.push(circCoord(a));
        }
    );

console.log(circleVal);

const textVal =[];


const textCoord = (element) => {
    const matrixElement = element.getCTM();
    const x = element.x.baseVal.value; // takes care of unit conversion
    const y = element.y.baseVal.value;

    const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement);
    const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox);

    return coord;
};

document.querySelectorAll('#textGroup > g > text')
    .forEach(
        (a) => {
            textVal.push(textCoord(a));
        }
    );
    
console.log(textVal);
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <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" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1280 720">
<rect class="vBoxRect" width="1280" height="720" fill="#EFEFEF"></rect>
<g transform="translate(10,10)">
    <g transform="translate(100 100)">
        <circle class="circ1" r="20" cx="25" cy="25" fill="green"
        transform="translate (100,400) scale(2)" />
    </g>
</g>
<g transform="translate(10,10)">
    <g transform="translate(100 100)">
        <circle class="circ2" r="20" cx="25" cy="25" fill="red"
        transform="translate (-100,-40) scale(2)" />
    </g>
</g>
  <g id="textGroup" transform="translate(400,300)">
    <g class="test">
      <text x="-247.890625" y="-60" transform="translate(-247.890625,-60)translate(247.890625,60)">P</text>
      <text x="-227.015625" y="-60" transform="translate(-227.015625,-60)translate(227.015625,60)">i</text>
      <text x="-210.09375" y="-60" transform="translate(-210.09375,-60)translate(210.09375,60)">z</text>
      <text x="-193.171875" y="-60" transform="translate(-193.171875,-60)translate(193.171875,60)">z</text>
      <text x="-181.296875" y="-60" transform="translate(-181.296875,-60)translate(181.296875,60)">a</text>     
    </g>
  </g>
</svg>
    <script type="text/javascript" src="prod.js">
    </script>
</body>

</html>

The values returned in textVal don't seem to be accurate. They all seem to be same while each of them has a different x

[
  {"x": 400,"y": 300,"z": 0,"w": 1},
  {"x": 400,"y": 300,"z": 0,"w": 1},
  {"x": 400,"y": 300,"z": 0,"w": 1},
  {"x": 400,"y": 300,"z": 0,"w": 1},
  {"x": 400,"y": 300,"z": 0,"w": 1}
]

Solution

  • Text elements can have multiple x and y values so you want the one at index 0.

    Additionally getCTM on the svg root element is never what you want. I assume you meant getScreenCTM instead.

    //build gridline with svg tooltip with "title" element
    const widthPoints = [];
    const heightPoints = [];
    
    for (let i = 0; i <= 720; i += 10) {
        heightPoints.push(i)
    };
    
    for (let i = 0; i <= 1280; i += 10) {
        widthPoints.push(i)
    };
    
    const scaleY = d3.scaleLinear()
        .range([720, 0])
        .domain(d3.extent(heightPoints, d => d))
    
    const scaleX = d3.scaleLinear()
        .range([0, 1280])
        .domain(d3.extent(widthPoints, d => d));
    
    d3.select('svg')
        .append('g')
        .attr('class', 'yAxisLeft')
        .call(d3.axisLeft(scaleY)
            .ticks(heightPoints.length - 1)
            .tickSizeInner([(-(1280))])
        );
    
    const newHeight = JSON.parse(JSON.stringify(heightPoints)).reverse();
    
    d3.selectAll('body > svg > g.yAxisLeft > g.tick')
        .each(
            function(d, i) {
                const selection = d3.select(this);
                selection.selectAll('title')
                    .data(function() {
                        return newHeight.filter((a, k) => k === i)
                    })
                    .join('title')
                    .text((d) => {
                        return 'y Coordinate=' + `${d}`
                    })
            }
        );
    
    d3.select('svg')
        .append('g').attr('class', 'xAxisBottom')
        .call(d3.axisBottom(scaleX)
            .ticks(widthPoints.length - 1)
            .tickSizeInner([(-(720))])
        )
        .style('transform', `translateY(720px)`)
    
    d3.selectAll('body > svg > g.xAxisBottom > g.tick')
        .each(
            function(d, i) {
                const selection = d3.select(this);
                selection.selectAll('title')
                    .data([d])
                    .join('title')
                    .text((d) => {
                        return 'x Coordinate=' + `${d}`
                    })
            }
        );
     
     // return x,y coordinate of each item as a result of all transform
     
     const svg = document.querySelector('svg');
    const matrixViewBox = svg.getScreenCTM();
    
    const circCoord = (element) => {
    
        const matrixElement = element.getCTM();
        const x = element.cx.baseVal.value; // takes care of unit conversion
        const y = element.cy.baseVal.value;
    
        const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement);
        const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox);
    
        return coord;
    
    };
    
    const circleVal =[];
    
    document.querySelectorAll('circle')
        .forEach(
            (a) => {
                circleVal.push(circCoord(a));
            }
        );
    
    console.log(circleVal);
    
    const textVal =[];
    
    
    const textCoord = (element) => {
        const matrixElement = element.getCTM();
        const x = element.x.baseVal[0].value; // takes care of unit conversion
        const y = element.y.baseVal[0].value;
    
        const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement);
        const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox);
    
        return coord;
    };
    
    document.querySelectorAll('#textGroup > g > text')
        .forEach(
            (a) => {
                textVal.push(textCoord(a));
            }
        );
        
    console.log(textVal);
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <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" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1280 720">
    <rect class="vBoxRect" width="1280" height="720" fill="#EFEFEF"></rect>
    <g transform="translate(10,10)">
        <g transform="translate(100 100)">
            <circle class="circ1" r="20" cx="25" cy="25" fill="green"
            transform="translate (100,400) scale(2)" />
        </g>
    </g>
    <g transform="translate(10,10)">
        <g transform="translate(100 100)">
            <circle class="circ2" r="20" cx="25" cy="25" fill="red"
            transform="translate (-100,-40) scale(2)" />
        </g>
    </g>
      <g id="textGroup" transform="translate(400,300)">
        <g class="test">
          <text x="-247.890625" y="-60" transform="translate(-247.890625,-60)translate(247.890625,60)">P</text>
          <text x="-227.015625" y="-60" transform="translate(-227.015625,-60)translate(227.015625,60)">i</text>
          <text x="-210.09375" y="-60" transform="translate(-210.09375,-60)translate(210.09375,60)">z</text>
          <text x="-193.171875" y="-60" transform="translate(-193.171875,-60)translate(193.171875,60)">z</text>
          <text x="-181.296875" y="-60" transform="translate(-181.296875,-60)translate(181.296875,60)">a</text>     
        </g>
      </g>
    </svg>
        <script type="text/javascript" src="prod.js">
        </script>
    </body>
    
    </html>