Home > Software design >  Misconception regarding the matrix values e and f in svg
Misconception regarding the matrix values e and f in svg

Time:05-09

I seem to have a logical misconception, regarding the enter image description here

or [1 0 0 1 tx ty], where tx and ty are the distances to translate coordinates in x and y, respectively.

Example

Following a small example providing two svg. Each containing one path which both get rotated by 15°. I tried to mark the point p(x1,y1) by a lime circle.

;window.onload = () => {
  //REM: Query the first element in svg => [path, path]
  document.querySelectorAll('svg > *[transform]').forEach((element) => {
    let tSVG = element.ownerSVGElement;
    let tBBox = element.getBBox();
    let tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Draw a circle at p(x1,y1) - before rotation
    let tOldX = tBBox.x * tMatrix.a   tMatrix.e;
    let tOldY = tBBox.y * tMatrix.d   tMatrix.f

    let tCircle1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle1.setAttributeNS(null, 'r', '5');
    tCircle1.setAttributeNS(null, 'fill', 'orange');
    tCircle1.setAttributeNS(null, 'fill-opacity', '0.5');
    tCircle1.setAttributeNS(null, 'cx', tOldX);
    tCircle1.setAttributeNS(null, 'cy', tOldY);
    tSVG.appendChild(tCircle1);

    //REM: Rotate by 15° around center of itself
    let tTransform = tSVG.createSVGTransform();
    tTransform.setRotate(15, tBBox.x   tBBox.width / 2, tBBox.y   tBBox.height / 2);
    element.transform.baseVal.appendItem(tTransform);
    element.transform.baseVal.initialize(element.transform.baseVal.consolidate());

    //REM: Fetch new matrix
    tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Pure scale
    let tScaleX = Math.sqrt(tMatrix.a * tMatrix.a   tMatrix.b * tMatrix.b);
    let tScaleY = Math.sqrt(tMatrix.c * tMatrix.c   tMatrix.d * tMatrix.d);

    //REM: Draw a circle at p(x1,y1) - after rotation
    let tNewX = tBBox.x * tScaleX   tMatrix.e;
    let tNewY = tBBox.y * tScaleY   tMatrix.f;

    let tCircle2 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle2.setAttributeNS(null, 'r', '5');
    tCircle2.setAttributeNS(null, 'fill', 'lime');
    tCircle2.setAttributeNS(null, 'fill-opacity', '0.9');
    tCircle2.setAttributeNS(null, 'cx', tNewX);
    tCircle2.setAttributeNS(null, 'cy', tNewY);
    tSVG.appendChild(tCircle2);

    //REM: Connection
    let tLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
    tLine.setAttributeNS(null, 'stroke', 'green');
    tLine.setAttributeNS(null, 'x1', tOldX);
    tLine.setAttributeNS(null, 'y1', tOldY);
    tLine.setAttributeNS(null, 'x2', tNewX);
    tLine.setAttributeNS(null, 'y2', tNewY);
    tSVG.appendChild(tLine);

    console.log(
      'e: '   tMatrix.e,
      'f: '   tMatrix.f
    )
  })
}
<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '950,-250,100,400' height='800' width='300'>
  <path d = 'm1029.31 112.6h97.40v57.01h-97.40z' fill = 'red' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)' />
</svg>

<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '-40,-40,150,100' height='200' width='300'>
  <path d = 'M0 0L100 0L100 60L0 60z' fill = 'blue' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)'></path>
</svg>

Question

To my understanding, I should be able to get the point p(x1,y1) aka top-left, by using the following calculation:

  • x1: (bbox.x * scale) matrix.e //original position x scaled object moved by x
  • y1: (bbox.y * scale) matrix.f //original position y scaled object moved by y

Yet, it merely works in the second/blue svg and not the first/red one. The f of the first/red svg seems way off to me:

  • e: 80.92766714917656 f: -261.78134648854723 //first/red svg
  • e: 23.52145237337207 f: -25.726854765665173 //second/blue svg

Where am I going wrong with this?

Remark

While I added the Javascript tag for the syntax, the focus is not on Javascript. Javascript offers other possibilities to place the lime circle correctly.

CodePudding user response:

I didn't look into why exactly it was failing. I have a couple of ideas. But if you apply the correct matrix multiplication formulas, it works correctly.

let tNewX = tBBox.x * tMatrix.a   tBBox.y * tMatrix.c   tMatrix.e;
let tNewY = tBBox.x * tMatrix.b   tBBox.y * tMatrix.d   tMatrix.f;

;window.onload = () => {
  //REM: Query the first element in svg => [path, path]
  document.querySelectorAll('svg > *[transform]').forEach((element) => {
    let tSVG = element.ownerSVGElement;
    let tBBox = element.getBBox();
    let tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Draw a circle at p(x1,y1) - before rotation
    let tOldX = tBBox.x * tMatrix.a   tMatrix.e;
    let tOldY = tBBox.y * tMatrix.d   tMatrix.f

    let tCircle1 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle1.setAttributeNS(null, 'r', '5');
    tCircle1.setAttributeNS(null, 'fill', 'orange');
    tCircle1.setAttributeNS(null, 'fill-opacity', '0.5');
    tCircle1.setAttributeNS(null, 'cx', tOldX);
    tCircle1.setAttributeNS(null, 'cy', tOldY);
    tSVG.appendChild(tCircle1);

    //REM: Rotate by 15° around center of itself
    let tTransform = tSVG.createSVGTransform();
    tTransform.setRotate(15, tBBox.x   tBBox.width / 2, tBBox.y   tBBox.height / 2);
    element.transform.baseVal.appendItem(tTransform);
    element.transform.baseVal.initialize(element.transform.baseVal.consolidate());

    //REM: Fetch new matrix
    tMatrix = element.transform.baseVal.getItem(0).matrix;

    //REM: Draw a circle at p(x1,y1) - after rotation
    let tNewX = tBBox.x * tMatrix.a   tBBox.y * tMatrix.c   tMatrix.e;
    let tNewY = tBBox.x * tMatrix.b   tBBox.y * tMatrix.d   tMatrix.f;

    let tCircle2 = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    tCircle2.setAttributeNS(null, 'r', '5');
    tCircle2.setAttributeNS(null, 'fill', 'lime');
    tCircle2.setAttributeNS(null, 'fill-opacity', '0.9');
    tCircle2.setAttributeNS(null, 'cx', tNewX);
    tCircle2.setAttributeNS(null, 'cy', tNewY);
    tSVG.appendChild(tCircle2);

    //REM: Connection
    let tLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
    tLine.setAttributeNS(null, 'stroke', 'green');
    tLine.setAttributeNS(null, 'x1', tOldX);
    tLine.setAttributeNS(null, 'y1', tOldY);
    tLine.setAttributeNS(null, 'x2', tNewX);
    tLine.setAttributeNS(null, 'y2', tNewY);
    tSVG.appendChild(tLine);

    console.log(
      'e: '   tMatrix.e,
      'f: '   tMatrix.f
    )
  })
}
<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '950,-250,100,400' height='800' width='300'>
  <path d = 'm1029.31 112.6h97.40v57.01h-97.40z' fill = 'red' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)' />
</svg>

<svg xmlns = 'http://www.w3.org/2000/svg' viewBox = '-40,-40,150,100' height='200' width='300'>
  <path d = 'M0 0L100 0L100 60L0 60z' fill = 'blue' transform = 'matrix(0.9, 0, 0, 0.9, 15, -15)'></path>
</svg>

  • Related