Sunday, 20 September 2020

Fit SVG transformed element into the rect bounds with JavaScript

I am struggling with an issue to fit pragmatically transformed SVG element into the given rect bounds.

  • Destination rect is given and not transformed.
  • Input rect has any type of transformations.
  • Input rect can be a child of transformed groups.

It's an easy task when the element has only transformations by itself:

When parent groups are not transformed

In this case proportion between the destination and input getBoundingClientRect (bounding rect in screen coordinates) is equals to a proper scaling factor.

But it's not working when parent elements are transformed by itself:

   var inputElement = document.getElementById("input");
var destinationElement = document.getElementById("destination");


var inputBB = inputElement.getBoundingClientRect();
var outputBB = destinationElement.getBoundingClientRect();
var scaleX = outputBB.width / inputBB.width;
var scaleY = outputBB.height / inputBB.height;
// get offsets between figure center and destination rect center:
var offsetX = outputBB.x + outputBB.width / 2 - (inputBB.x + inputBB.width / 2);
var offsetY =
  outputBB.y + outputBB.height / 2 - (inputBB.y + inputBB.height / 2);

// get current figure transformation
let currentMatrix = (
  inputElement.transform.baseVal.consolidate() ||
  inputElement.ownerSVGElement.createSVGTransform()
).matrix;

// Get center of figure in element coordinates:
const inputBBox = inputElement.getBBox();
const centerTransform = inputElement.ownerSVGElement.createSVGPoint();
centerTransform.x = inputBBox.x + inputBBox.width / 2;
centerTransform.y = inputBBox.y + inputBBox.height / 2;
// create scale matrix:
const svgTransform = inputElement.ownerSVGElement.createSVGTransform();
svgTransform.setScale(scaleX, scaleY);

let scalingMatrix = inputElement.ownerSVGElement
  .createSVGMatrix()
  // move the figure to the center of the destination rect.
  .translate(offsetX, offsetY)
  // Apply current matrix, so old transformations are not lost
  .multiply(currentMatrix)
  .translate(centerTransform.x, centerTransform.y)
  // multiply is used instead of the scale method while for some reasons matrix scale is giving proportional scaling...
  // From a transforms proper matrix is generated.
  .multiply(svgTransform.matrix)
  .translate(-centerTransform.x, -centerTransform.y);

// Apply new created matrix to element back:
const newTransform = inputElement.ownerSVGElement.createSVGTransform();
newTransform.setMatrix(scalingMatrix);
inputElement.transform.baseVal.initialize(newTransform);

var bboundsTest= document.getElementById("bboundsTest");
const resultBBounds = inputElement.getBoundingClientRect();
bboundsTest.setAttribute('x', resultBBounds .x);
bboundsTest.setAttribute('y', resultBBounds .y);
bboundsTest.setAttribute('width', resultBBounds .width);
bboundsTest.setAttribute('height', resultBBounds .height);
<svg
  version="1.2"
  viewBox="0 0 480 240"
  width="480"
  height="240"
  xmlns="http://www.w3.org/2000/svg"
>
  <g>
<g transform="skewX(10) translate(95,1) rotate(30)">
  <g transform="skewX(30) translate(-3,3) rotate(30)">
    <g transform="skewX(10) translate(-3,4) rotate(10)">
      <rect
        id="input"
        transform="translate(95,76.5) skewX(25) translate(50,50) scale(1.5) translate(-50,-50) translate(0,0) rotate(45)"
        width="30"
        height="30"
        fill="red"
      />
    </g>
  </g>
</g>

<rect
  id="destination"
  x="20"
  y="20"
  width="100"
  height="100"
  fill="transparent"
  stroke="blue"
/>
 <rect
  id="bboundsTest"
  x="20"
  y="20"
  width="100"
  height="100"
  fill="transparent"
  stroke="black"
/>
  </g>
</svg>

Any ideas how to take parent transformations into the count to find proper scaling factors?

Thanks in advance for the ideas!



from Fit SVG transformed element into the rect bounds with JavaScript

No comments:

Post a Comment