0
votes

I'm working on a simple visual editor using SVG and Snap.SVG library. It has a bunch of complex shapes as templates, each shape is a <g> element that contains paths.

The editor lets the user transform shapes by moving, scaling and rotating them. This is done by applying the transforms as a matrix on the main <g> element of each shape. The math is mainly done by the Snap.SVG library.

The transforms must by applied in a specific order for things to look right. The user might do any of the transforms in any order they like. So each user interaction with the shape is an update to the transform matrix of that shape.

When the user moves, rotates, scales, rotates again and scales again, what is the best way to update the transform matrix on the shape and keep the transforms in correct order?

Currently, all transforms are done in the order of: translate => rotate => scale. In this case, updating the rotation angle after the scale has already been applied to the matrix would break the shape. Because the matrix is already calculated with the scale taken into account. It would become translate => rotate => scale => rotate and the last rotation would get stretched by the scale transform.

I'm trying to avoid storing any extra data about transforms (e.g in data- attributes) and also avoid using transform strings, such as transform="translate(x, y) rotate(a, x, y) scale(x, y)" as it would be too complicated to parse it.

1
I don't really see how you can keep them in order if just using a matrix. If you want to store them in order, I think all you could do is store it in Snap transforms built up in a string ('t10,10r20,s2,t20,20' etc). Matrices are very difficult to work with as well. Personally I would store translate, rotate, scale as a data element, but you said you don't want to do this (not sure why). - Ian
I'm actually not set on doing it in any particular way. I'm open to any suggestion. Just seems to me that keeping extra attributes for this might be redundant and there may be some better way to do it. I'm looking for a 'best practice' way. I'm also not sure why I can't find anything about this on the web. I can't be the first one to do this. :/ - Yev

1 Answers

2
votes

I can only really advise on what I have done before that has worked.

This is the bits of code from an example I did a while back...(originally from a plugin, but the updateTransform function is the key bit when it comes to orders)

    Element.prototype.ftStoreInitialTransformMatrix = function() {
        this.data('initialTransformMatrix', this.transform().localMatrix );
        return this;
    };

    Element.prototype.ftGetInitialTransformMatrix = function() {
        return this.data('initialTransformMatrix');
    };

    Element.prototype.ftUpdateTransform = function() {
        var tstring = "t" + this.data("tx") + "," + this.data("ty") + this.ftGetInitialTransformMatrix().toTransformString() + "r" + this.data("angle") + 'S' + this.data("scale" );        
        this.attr({ transform: tstring });
        return this;
    };

These are the key bits I did from this free transform thing I was playing with here (you can view the source if you want to see it all).

Not sure if that helps at all, fiddling purely with the matrix was hard to keep track. I stored the original transform as well, partly because any imported object may have a transform already applied, but also keeping it as an update as you edit it.