How can I scale svg graphics group to desired size(not by factor)?

Viewed 8836

I have svg graphics group (I mean paths, recatangles etc.). I want to scale group to certain size, for examle to 70,70. I know that svg provides scale transformation, but scale transformation needs scale factor that I don't know because group can have many shapes and it's not easy to determine bounding size of whole group.
I played with viewBox parameter of svg elemenent and preserveAspectRatio parameter but I could not get desired result(scale group to 70,70).
Maybe is it possible to scale group with maxtrix transform or exists other way?

2 Answers

You might face some issues to fit the SVG element into a rectangle bound if path\rect transformations are already applied.

The code below is demonstrating how to fit transformed react input that grouped, skewed, scaled and transformed into the bounds. You have to preserve existing transformations and apply new transformations.

Code is doing next things:

  • Calculate scale and position offset in screen coordinated (in the screen coordinates while all transformations are counted in this case)
  • Create a new matrix, apply transformation offset first (move to the center of the destination).
  • Apply new scaling with the center of the element as the origin of the transform.
  • While scaling should be done in screen coordinates, we should convert the current element transformation including all the parent transformations to the screen coordinates, transform all those with a given scale and convert back to the element coordinates.

Exact line is:

var toScreenMatrix = inputElement.getScreenCTM();
// Scale element by a matrix in screen coordinates and convert it back to the element coordinates:
currentMatrix = currentMatrix.multiply(toScreenMatrix.inverse().multiply(scaleAndTransform).multiply(toScreenMatrix));
    

This code is generic for all the svg elements, so any shape can be fit into the given rect:

    function fitElement(from, to, changePosition) {
        var inputElement = document.getElementById(from);
        var destinationElement = document.getElementById(to);
        // Get center of figure in element coordinates:
        var inputScreenBBox = inputElement.getBoundingClientRect();
        var destinationScreenBBox = destinationElement.getBoundingClientRect();
        var scaleX = destinationScreenBBox.width / inputScreenBBox.width;
        var scaleY = destinationScreenBBox.height / inputScreenBBox.height;

        var inputCenter = getCenter(inputScreenBBox);
        var offsetX = 0;
        var offsetY = 0;
        if (changePosition) {
            var destCenter = getCenter(destinationScreenBBox);
            offsetX = destCenter.x - inputCenter.x;
            offsetY = destCenter.y - inputCenter.y;
        }

        // create scale matrix:
        var scaleMatrix = getScaleMatrix(scaleX, scaleY, inputElement);
        // get element self transformation matrix:
        var currentMatrix = getElementMatrix(inputElement);

        scaleAndTransform = inputElement.ownerSVGElement.createSVGMatrix()
            // 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.
            .translate(offsetX, offsetY)
            // Scale in screen coordinates around screen center:
            .translate(inputCenter.x, inputCenter.y)
            .multiply(scaleMatrix)
            .translate(-inputCenter.x, -inputCenter.y)
        
        var toScreenMatrix = inputElement.getScreenCTM();
        // Scale element by a matrix in screen coordinates and convert it back to the element coordinates:
        currentMatrix = currentMatrix.multiply(toScreenMatrix.inverse().multiply(scaleAndTransform).multiply(toScreenMatrix));
        // Apply new created transform:
        var newTransform = inputElement.ownerSVGElement.createSVGTransform();
        newTransform.setMatrix(currentMatrix);
        inputElement.transform.baseVal.initialize(newTransform);

    }
    function getElementMatrix(element) {
        // Get consolidated element matrix:
        var currentMatrix =
            (element.transform.baseVal.consolidate() ||
                element.ownerSVGElement.createSVGTransform()).matrix;
        return currentMatrix;
    }
    function getScaleMatrix(scaleX, scaleY, el) {
        // Return DOM matrix
        var svgTransform = el.ownerSVGElement.createSVGTransform();
        // Transform type is used because of the bug in chrome applying scale to the DOM matrix:
        svgTransform.setScale(scaleX, scaleY);
        var scaleMatrix = svgTransform.matrix;
        return scaleMatrix
    }

    function getCenter(rect) {
        return new DOMPoint((rect.x + rect.width / 2), (rect.y + rect.height / 2));
    }

    fitElement('source', 'destination', true);
<svg width="1380" height="1340" xmlns="http://www.w3.org/2000/svg">
<g transform="skewX(10) translate(-3,4) rotate(30)">
<g transform="skewX(30) translate(-3,4) rotate(30)">
<g transform="skewX(10) translate(-3,4) rotate(10)">
<g transform="translate(350,30) skewX(10) rotate(30)">
<rect id="source" transform="scale(2) rotate(30) skewX(10)" x="20" y="50" width="30" height="30"
fill="red" />
</g>
</g>
</g>
</g>
<rect id="destination" x="30" y="30" width="120" height="100" fill="transparent" stroke="blue" />
</svg>

GitHub gist link

Related