Matrix transforms in SVG

Using a matrix to transform an element is a pretty big thing in SVG. Sadly most of the tutorials I have seen either trying to introduce you to matrix math or just omit dealing with matricies at all. This is a pretty sad state of affairs, so after needing to figure this out for one of my projects I figure I would try to fill that gap a little to make things easier next time I need to deal with this.

To keep this clear lets use an small set of elements:

<html>
<body>
<svg xmlns="http://www.w3.org/2000/svg">
  <g transform="scale(0.5)">
  <rect transform="translate(50,50)" id="rect" x=0 y=0 height=50 width=50 fill="blue" />
    </g>
  </svg>
</body>
</html>

A transform applied to a parent element will cascade down to its child elements. This means that in our example here the rect element in the middle will be both scaled AND translated since the scale(0.5) also applies to it.
With that in mind you can get a matrix that represents the sum of all the transformations on a given element by calling the getCTM (the Current Transformation Matrix) function:

svg = document.querySelector('svg') // the svg root element holds many helper functions we will need.
rect = document.querySelector("rect")
​​currentMatrix = rect.getCTM()
>SVGMatrix {f: 1.2132034355964265, e: -17.071067811865476, d: 1.4142135623730951, c: -1.414213562373095, b: 1.414213562373095…}

All SVG objects also have a list of transforms that have been applied to them. You can access it with the transform property:

rect.transform
>SVGAnimatedTransformList {animVal: SVGTransformList, baseVal: SVGTransformList}

So the goal it to manipulate that list to get the effect you are looking for. The least complicated thing you can do is simply append another transform to the existing list of transforms:

newTransform = svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(150,50))
rect.transform.baseVal.appendItem(newTransform)

This will move the rect element 150 px to the left and 50 px down.

The other way to approach this would be to replace the existing transform(s) with a single new transform. You can accomplish that with the initialize function.:

rect.transform.baseVal.initialize(newTransform)

If we wanted to move our element to 750px while keeping everything else the same (and replacing all existing transforms), it might look something like this:

current_matrix = rect.getCTM()
>SVGMatrix {f: 25, e: 25, d: 0.5, c: 0, b: 0…}&amp;lt;/div&amp;gt;
transformed_matrix = current_matrix.inverse().multiply(svg.createSVGMatrix().translate(750, 50).scale(0.5))
>SVGMatrix {f: 50, e: 1450, d: 1, c: 0, b: 0…}
rect.transform.baseVal.initialize(svg.createSVGTransformFromMatrix(transformed_matrix))
>SVGTransform {angle: 0, matrix: SVGMatrix, type: 1, setMatrix: function, setTranslate: function…}

Matricies can be a little hard on the head and understanding SOME matrix multiplication is very helpful. The tricky part is learning enough so you can get your project done without having to redo your whole high-school math curriculum. The best resource for a concise explanation is Coursera’s Machine Learning course. Check out the linear algebra review in section 3. He gets right down to business and explains it all really clearly.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s