2

After combing through the SVG specification, and guides such as this and this, I am still struggling to understand exactly how chaining transforms work.

Selected Relevant Quotes

When you apply the transform attribute to an SVG element, that element gets a "copy" of the current user coordinate system in use.

And:

When transformations are chained, the most important thing to be aware of is that, just like with HTML element transformations, each transformation is applied to the coordinate system after that system is transformed by the previous transformations.

And:

For example, if you’re going to apply a rotation to an element, followed by a translation, the translation happens according to the new coordinate system, not the inital non-rotated one.

And:

The sequence of transformations matter. The sequence the transformation functions are specified inside the transform attribute is the sequence they are applied to the shape.

Code

The first rectangle's current coordinate system is scaled, then rotated (note the order). The second rectangle's current coordinate system is rotated, then scaled.

svg {
  border: 1px solid green;
}
<svg xmlns="http://www.w3.org/2000/svg">
  <style>
    rect#s1 {
      fill: red;
      transform: scale(2, 1) rotate(10deg);
    }
  </style>
  <rect id="s1" x="" y="" width="100" height="100" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
  <style>
    rect#s2 {
      fill: blue;
      transform: rotate(10deg) scale(2, 1);
    }
  </style>
  <rect id="s2" x="" y="" width="100" height="100" />
</svg>

Question

We know that when we chain transforms, a copy is made of the current coordinate system in use for that element, then the transforms are applied in the order they are specified.

When we have a user coordinate system that is already scaled, and we apply a rotate to it, the rectangle is (as seen) effectively skewed (notice the changed angles). This does not happen if we do the two transforms the other way around (rotate, then scale).

Expert help on exactly how the scaled current coordinate system is rotated, would be deeply appreciated. I am trying to understand, from a technical (inner workings) angle, exactly why the skewing happens in the first rectangle.

Thank you.

Temani Afif
  • 245,468
  • 26
  • 309
  • 415
Magnus
  • 6,791
  • 8
  • 53
  • 84
  • well, it's the same as for CSS element, this may probably help you : https://stackoverflow.com/questions/51077632/simulating-transform-origin-using-translate/51080643#51080643 .... If you are able to do some Math to calculate the final matrix you will understand but honestly it can become very tricky – Temani Afif Dec 07 '18 at 15:05
  • *The first rectangle's current coordinate system is rotated, then scaled (note the order).* --> no, the coordinate is scaled then rotated BUT the element is rotated then scaled – Temani Afif Dec 07 '18 at 15:10
  • @temaniAfif Sorry, that was a typo. I am fixing it now. – Magnus Dec 07 '18 at 15:16
  • This is just affine transformation geometry, it's not really SVG related except insofar as you're using SVG to use to display a co-ordinate transformation. If you stretch anisotropically along the angle's edge the angle does not appear to change, if you don't, it does. There's no "order of transforms" problem here. – Robert Longson Dec 07 '18 at 15:44
  • I have edited my answer to insert more accurate details and some calculation – Temani Afif Dec 07 '18 at 20:58

2 Answers2

7

To illustrate how it works let's consider an animation to show how the scaling effect change the rotation.

.red {
  width:80px;
  height:20px;
  background:red;
  margin:80px;
  transform-origin:left center;
  animation: rotate 2s linear infinite;
}
@keyframes rotate {
  from{transform:rotate(0)}
  to{transform:rotate(360deg)}

}
<div class="container">
<div class="red">
</div>
</div>

As you can see above, the rotation is creating a perfect circle shape.

Now let's scale the container and see the difference:

.red {
  width:80px;
  height:20px;
  background:red;
  margin:80px;
  transform-origin:left center;
  animation: rotate 5s linear infinite;
}
@keyframes rotate {
  from{transform:rotate(0)}
  to{transform:rotate(360deg)}

}
.container {
  display:inline-block;
  transform:scale(3,1);
  transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>

Notice how we no more have a circle but it's an ellipse now. It's like we took the circle and we stertch it which is creating the skew effect inside our rectangle.


If we do the opposite effect and we start by having a scale effect and then we apply a rotation we won't have any skewing.

.red {
  width:80px;
  height:20px;
  background:red;
  margin:80px;
  animation: rotate 2s linear infinite;
}
@keyframes rotate {
  from{transform:scale(1,1)}
  to{transform:scale(3,1)}

}
.container {
  display:inline-block;
  transform:rotate(30deg);
  transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>

To explain it differently: Applying a rotation will keep the same ratio between both X and Y axis so you won't see any bad effect when doing scale later but scaling only one axis will break the ratio thus our shape we look bad when we try to apply a rotation.


You can check this link if you want more details about how transform are chained and how the matrix is caclulated: https://www.w3.org/TR/css-transforms-1/#transform-rendering. It's about HTML element but as said in the SVG specification it's the same.

Here is the relevant parts:

Transformations are cumulative. That is, elements establish their local coordinate system within the coordinate system of their parent.

From the perspective of the user, an element effectively accumulates all the transform properties of its ancestors as well as any local transform applied to it


Let's do some math in order to see the difference between both transformations. Let's consider matrix multiplication and since we are dealing with a 2D linear transformation we will do this on ℝ² for simplicity1.

For scale(2, 1) rotate(10deg) we will have

 |2 0|   |cos(10deg) -sin(10deg)|   |2*cos(10deg) -2*sin(10deg) |
 |0 1| x |sin(10deg) cos(10deg) | = |1*sin(10deg) 1*cos(10deg)  |

Now if we apply this matrix to an (Xi,Yi) we will obtain (Xf,Yf) like below:

 Xf = 2* (Xi*cos(10deg) - Yi*sin(10deg))
 Yf =     Xi*sin(10deg) + Yi*cos(10deg)

Note how the Xf is having an extra multiplier which is the culprit of creating the skew effect. It's like we changed the behavior or Xf and kept the Yf

Now let's consider rotate(10deg) scale(2, 1):

 |cos(10deg) -sin(10deg)|   |2 0|   |2*cos(10deg) -1*sin(10deg) |
 |sin(10deg) cos(10deg) | x |0 1| = |2*sin(10deg) 1*cos(10deg)  |

And then we will have

 Xf =  2*Xi*cos(10deg) - Yi*sin(10deg)
 Yf =  2*Xi*sin(10deg) + Yi*cos(10deg)

We can consider the 2*Xi as an Xt and we can say that we rotated the (Xt,Yi) element and this element was initially scaled considering the X-axis.


1CSS uses also affine transformation (like translate) so using ℝ² (Cartesian coordinates) isn't enough to perform our calculation so we need to consider ℝℙ² (Homogeneous coordinates). Our previous calculation will be:

 |2 0 0|   |cos(10deg) -sin(10deg) 0|   |2*cos(10deg) -2*sin(10deg) 0|
 |0 1 0| x |sin(10deg) cos(10deg)  0| = |1*sin(10deg) 1*cos(10deg)  0|
 |0 0 1|   |0          0           1|   |0            0             1|

Nothing will change in this case because the affine part is null but if we have a translation combined with another transform (ex: scale(2, 1) translate(10px,20px)) we will have the following:

 |2 0 0|   |1 0 10px|   |2 0 20px|
 |0 1 0| x |0 1 20px| = |0 1 20px|
 |0 0 1|   |0 0 1   |   |0 0  1  |

And

Xf =  2*Xi + 20px;
Yf =  Yi + 20px;
1  =  1 (to complete the multiplication) 
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • 2
    Exactly. Another way to say it is that the rectangle is rotating in a stretched "universe". From the rectangle's point of view, it is rotating in a circle. But from our point of view (outside the universe) that circle is stretched. Making the circle look like an ellipse, and the rectangle look skewed. – Paul LeBeau Dec 07 '18 at 17:14
  • This is very helpful, Temani. I will go through your math and properly understand the matrix multiplications, I think that will solve it for me. One other thing I just realized, that might help me better understand what happens: Is it correct that we first create a scaled coordinate system. Then, inside that coordinate system, we create a new coordinate system which is rotated. Since x-coordinates are twice as far out as they normally would have been (without scale), the x-coordinates somehow is rotated twice as far as y-coordinates? I will dig into the math, based on your examples. – Magnus Dec 08 '18 at 00:04
  • @Magnus yes exactly for the coordinate system which I illustrated with my animation where the rotation is creating an ellipse and no more a circle because we are rotating inside a scaled coordinate system (a stretched universe like Paul said) so we have skew effect because both X and Y no more behave the same (like you can see in the formula with the extra multiplier) – Temani Afif Dec 08 '18 at 00:26
  • Transforms are in relation to the current coordinate system in use, right? So, if scale(2, 1) leaves us with a new coordinate system xs1, then the next chained transform, here rotate(10), is creating a copy of xs1 then rotating from xs1? I think that's right, just wanted to make sure. – Magnus Dec 08 '18 at 01:01
  • @Magnus technically *n* chained transformation will be converted to only one matrix transformation so at the end we will transfrom the current coordinate to a new one using that *one* transformation. So we always have a unique transformation applied BUT since matrix isn't trivial and most of users will not really understand math we try to explain this easily by saying that we start from a Coordinate X that we transform with the first transformation to an X1 then we take X1 and we use the second one to obtain X2, ... until Xf, – Temani Afif Dec 08 '18 at 08:00
  • @Magnus That's why when explaining this we sometimes move one transformation to an upper level (like I did) to really split the unique transformation and show this chains effect and to proove that applying transformation one by one is exaclt the same as applying the global matrix transformation only once. So yes what you said is correct as an intrepretation but it doesn't really mean that the system will create the coordinate like that, he will simply create the last one in one shot – Temani Afif Dec 08 '18 at 08:02
  • @TemaniAfif How were you able to represent the different transform functions as matrices? I have read through css-transforms-1, but was not able to draw any converstion conclusion from "14. Mathematical Description of Transform Functions" – Magnus Dec 09 '18 at 16:18
  • @Magnus all is here: https://drafts.csswg.org/css-transforms/#two-d-transform-functions but not very well described .. to better find the matrix use MDN and see the page of each transform funcion to see the matrix: Ex : https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/skew / https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate – Temani Afif Dec 09 '18 at 19:16
  • Thanks Temani. I cannot see it specifying how the matrix looks like, in the 2D Transform Functions section of the spec. It does not say what the matrix values a b c d e f are supposed to represent. And each of the functions says nothing about conversion to a matrix – Magnus Dec 09 '18 at 19:27
  • @Magnus I think you won't find the mathematical details of the values because it's no more CSS but pure math. If you search about a rotation you will easily find its matrix is (https://en.wikipedia.org/wiki/Rotation_matrix) same think with translation (https://en.wikipedia.org/wiki/Translation_(geometry)) .. by the way you can find more detail in the SVG spec : https://www.w3.org/TR/SVG11/coords.html#TransformMatrixDefined but it remains math, so if you really want to understand them, you need to no more search in the Spec but to search about math – Temani Afif Dec 09 '18 at 19:44
  • Oh maan, perfect! I did not know that transformations of coordinates in a cartesian coordinate system is performed by multiplying our coordinates in a 3x1 matrix with already pre-defined affine transformation matrices. These articles show the different affine transformation matrices: https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations and https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations – Magnus Dec 09 '18 at 20:09
  • @Magnus yes, this is the homogenous coordinate (RP²) I simply used R² in the answer to show a simplified version. You can even go to the RP3 for the 3D transform – Temani Afif Dec 09 '18 at 20:24
  • Btw, I do not think the guy who commented regarding viewBox in the other SO question is right. I spent a lot of time looking into the SVG coordinate system spec, as well as guides, and as the spec says: the effect of viewBox is as if scale and translate is applied. I already know how that works, so this is not a question, I just think it is a bit frustrating that something I consider misleading is posted there. – Magnus Dec 09 '18 at 20:36
  • @Magnus sometimes it's not easy when it comes to accurate and detailed things. I myself write missleading things simply because I am not good in english so I can use wrong wordings to explain something I understand correctly. That's why I use figures and real examples to show how it works even if the written explanation is somehow not good. – Temani Afif Dec 09 '18 at 20:40
  • Yep. I made an edit to my other question, where I linked to this question and detailed the final solution. – Magnus Dec 09 '18 at 21:16
2

The way Temani Afif explained it follows the coordinate systems that every transformation spans. You start with the viewport, and each consecutive coordinate system is derived and sits somewhere different on the canvas. These coordinate systems might turn out not be cartesian (a "stretched universe"). They are constructed in the DOM tree from the outside in, and when chained in an attribute, from left to right.

But you can imagine the same transformation also in the opposite direction, from the inside out: first you draw a rectangle in its cartesian userspace coordinate system, and than you transform it by a chain of scales, rotations and so on, until when drawing it in the viewport coordinate system, it is distorted to something else.

But if you look at it this the second way, the chained transformations in an attribute need to be processed right to left: transform: scale(2, 1) rotate(10deg) means take a rectangle, first rotate it by 10deg, and then scale the rotated rectangle in the horizontal direction.

In short, these two are equivalent:

  • If you draw a grafic in a transformed coordinate system, construct the coordinate system by applying transforms to these coordinate systems left-to-right.
  • If you draw a transformed grafic in the original coordinate system, construct the grafic by applying transforms to the grafic right-to-left.
ccprog
  • 20,308
  • 4
  • 27
  • 44
  • Thanks, ccprog. I now just have to understand how a transform function is represented as a matrix. I will check in with Temani. Thanks again. – Magnus Dec 09 '18 at 14:43