SVG Essentials/Transforming the Coordinate System
From WikiContent
m (1 revision(s)) |
(Initial conversion from Docbook) |
Revision as of 12:51, 7 March 2008
Up to this point, all graphics have been displayed "as is." There will be times when you have a graphic that you would like to move to a new location, rotate, or scale. To accomplish these tasks, you add the transform attribute to the appropriate SVG elements. This chapter examines the details of these transformations.
Contents |
The translate Transformation
In Chapter 4, you saw that you can use x and y attributes with the <use> element to place a group of graphic objects at a specific place. Look at the SVG in Example 5-1, which defines a square and draws it at the upper left corner of the grid, then re-draws it with the upper left corner at coordinates (50, 50). The dotted lines in Figure 5-1 aren't part of the SVG, but serve to show the part of the canvas that we're interested in.
Example 5-1. Moving a graphic with use
<svg width="200px" height="200px" viewBox="0 0 200 200"> <g id="square"> <rect x="0" y="0" width="20" height="20" style="fill: black; stroke-width: 2;"/> </g> <use xlink:href="#square" x="50" y="50"/> </svg>
As it turns out, the x and y values are really a shorthand for one form of the more general and more powerful transform attribute. Specifically, the x and y values are converted to an attribute like transform="translate( x-value, y-value )", where translate is a fancy technical term for "move." The x-value and y-value are measured in the current user coordinate system. Let's use transform to get the same effect of making a second square with its upper left corner at (50, 50). Example 5-2 lists the SVG.
Example 5-2. Moving the coordinate system with translation
<svg width="200px" height="200px" viewBox="0 0 200 200"> <g id="square"> <rect x="0" y="0" width="20" height="20" style="fill: none; stroke:black; stroke-width: 2;"/> </g> <use xlink:href="#square" transform="translate(50,50)"/> </svg>
The resulting display will look exactly like that in Figure 5-1. You might think this was accomplished by moving the square to a different place on the grid, as shown conceptually in Figure 5-2, but you would be wrong.
What is really going on behind the scenes is an entirely different story. Rather than moving the square, the translate specification picks up the entire grid and moves it to a new location on the canvas. As far as the square is concerned, it's still being drawn with its upper left corner at (0, 0), as depicted in Figure 5-3.
Warning
A translation transformation never changes a graphic object's grid coordinates; rather, it changes the position of the grid on the canvas.
At first glance, using translate seems as ridiculous and inefficient as moving your couch further away from the outside wall of the house by moving the entire living room, walls and all, to a new position. Indeed, if translation were the only transformation available, moving the entire coordinate system would be wasteful. However, we will soon see other transformations, and combinations of a sequence of transformations, which are more mathematically and conceptually convenient if they apply to the entire coordinate system.
The scale Transformation
It is possible to make an object appear larger or smaller than the size at which it was defined by scaling the coordinate system. Such a transformation is specified either as:
- transform="scale( value )"
- Multiplies all x- and y-coordinates by the given value.
- transform="scale( x-value , y-value )"
- Multiplies all x-coordinates by the given x-value and all y-coordinates by the given y-value.
Example 5-3 is an example of the first kind of scaling transformation, which uniformly doubles the scale of both axes. Once again, the dotted lines in Figure 5-4 aren't in the SVG; they simply show the area of the canvas that we're interested in. Note that the square's upper left corner is at (10, 10).
Example 5-3. Uniformly scaling a graphic
<svg width="200px" height="200px" viewBox="0 0 200 200"> <g id="square"> <rect x="10" y="10" width="20" height="20" style="fill: none; stroke: black;"/> </g> <use xlink:href="#square" transform="scale(2)"/> </svg>
You might be thinking, "Wait a minute — I can understand why the square got larger. But I didn't ask for a translate, so why is the square in a different place?" Everything becomes clear when you look at Figure 5-5 to see what has actually occurred. The grid hasn't moved; the (0, 0) point of the coordinate system is still in the same place, but each user coordinate is now twice as large as it used to be. You can see from the grid lines that the upper left corner of the rectangle is still at (10, 10) on the new, larger grid, since objects never move. This also explains why the outline of the larger square is thicker. The stroke-width is still one user unit, but that unit has now become twice as large, so the stroke thickens.
Warning
A scaling transformation never changes a graphic object's grid coordinates or its stroke width; rather, it changes the size of the coordinate system (grid) with respect to the canvas.
It is possible to specify a different scale factor for the x-axis and y-axis of the coordinate system by using the second form of the scale transformation. Example 5-4 draws the square with the x-axis scaled by a factor of three and the y-axis scaled by a factor of one and a half. As you can see in Figure 5-6, the one-unit stroke width is also non-uniformly scaled.
Example 5-4. Non-uniform scaling of a graphic
<svg width="200px" height="200px" viewBox="0 0 200 200"> <g id="square"> <rect x="10" y="10" width="20" height="20" style="fill: none; stroke: black;"/> </g> <use xlink:href="#square" transform="scale(3, 1.5)"/> </svg>
To this point, we have only applied the transform attribute to the <use> element. You can apply a transformation to a series of elements by grouping them and transforming the group:
<g id="group1" transform="translate(3, 5)"> <line x1="10" y1="10" x1="30" y2="30"/> <circle cx="20" cy="20" r="10"/> </g>
You may also apply a transformation to a single object or basic shape. For example, here is a rectangle whose coordinate system is scaled by a factor of three:
<rect x="15" y="20" width="10" height="5" transform="scale(3)" style="fill: none; stroke: black;"/>
It's fairly clear that the width and height of the scaled rectangle should be three times as large as the unscaled rectangle. However, you may wonder if the x- and y-coordinates are evaluated before or after the rectangle is scaled. The answer is that SVG applies transformations to the coordinate system before it evaluates any of the shape's coordinates. Example 5-5 is the SVG for the scaled rectangle, shown in Figure 5-7 with grid lines that are drawn in the unscaled coordinate system.
Example 5-5. Transforming a single graphic
<!-- grid guide lines in non-scaled coordinate system --> <line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/> <line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/> <line x1="45" y1="0" x2="45" y2="100" style="stroke: gray;"/> <line x1="0" y1="60" x2="100" y2="60" style="stroke: gray;"/> <!-- rectangle to be transformed --> <rect x="15" y="20" width="10" height="5" transform="scale(3)" style="fill: none; stroke: black;"/>
Note
The effect of applying a transformation to a shape is the same as if the shape were enclosed in a transformed group. In the preceding example, the scaled rectangle is equivalent to this SVG:
<g transform="scale(3)"> <rect x="15" y="20" width="10" height="5" style="fill: none; stroke: black;"/> </g>
Sequences of Transformations
It is possible to do more than one transformation on a graphic object. You just put the transformations, separated by whitespace, in the value of the transform attribute. Here is a rectangle that undergoes two transformations, a translation followed by a scaling. (The axes are drawn to show that the rectangle has, indeed, moved.)
<!-- draw axes --> <line x1="0" y1="0" x2="0" y2="100" style="stroke: gray;"/> <line x1="0" y1="0" x2="100" y2="0" style="stroke: gray;"/> <rect x="10" y="10" height="20" width="15" transform="translate(30, 20) scale(2)" style="fill: gray;"/>
This is the equivalent of the following sequence of nested groups, and both will produce what you see in Figure 5-8.
<g transform="translate(30, 20)"> <g transform="scale(2)"> <rect x="10" y="10" height="20" width="15" style="fill: gray;"/> </g> </g>
Figure 5-9 shows what is happening at each stage of the transformation.
Note
The order in which you do a sequence of transformations affects the result. In general, transformation A followed by transformation B will not give the same result as transformation B followed by transformation A.
Example 5-6 draws the same rectangle as in the previous example, only in a light gray color. Then it draws the rectangle again, but does the scale before the translate. As you can see from the result in Figure 5-10, the rectangles end up in very different places on the canvas.
Example 5-6. Sequence of transformations -- scale followed by translate
<!-- draw axes --> <line x1="0" y1="0" x2="0" y2="100" style="stroke: gray;"/> <line x1="0" y1="0" x2="100" y2="0" style="stroke: gray;"/> <rect x="10" y="10" width="20" height="15" transform="translate(30, 20) scale(2)" style="fill: #ccc;"/> <rect x="10" y="10" width="20" height="15" transform="scale(2) translate(30, 20)" style="fill: black;"/>
The reason that the black rectangle ends up farther away from the origin is that the scaling is applied first, so that the translate of 20 units in the x-direction and 10 units in the y-direction is done with units that are now twice as large, as shown in Figure 5-11.
Technique: Converting from Cartesian Coordinates
If you are transferring data from other systems to SVG, you may have to deal with vector drawings that use Cartesian coordinates (the ones you learned about in high school algebra) to represent data. In this system, the (0, 0) point is at the lower left of the canvas, and y-coordinates increase as you move upwards. Figure 5-12 shows the coordinates of a trapezoid drawn with Cartesian coordinates.
Since the y-axis is "upside-down" relative to the SVG default, the coordinates have to be recalculated. Rather than do it by hand, you can use a sequence of transformations to have SVG do all the work for you. First, translate the picture into SVG, with the coordinates exactly as shown in Example 5-7. (We'll also include the axes as a guide.) To nobody's surprise, the picture will come out upside-down. Note that the image in Figure 5-13 is not left-to-right reversed, since the x-axis points the same direction in both Cartesian coordinates and the default SVG coordinate system.
Example 5-7. Direct use of Cartesian coordinates
<svg width="200px" height="200px" viewBox="0 0 200 200"> <!-- axes --> <line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/> <line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/> <!-- trapezoid --> <polygon points="40 40, 100 40, 70 70, 40 70" style="fill: gray; stroke: black;"/> </svg>
To finish the conversion, follow these steps:
- Find the maximum y-coordinate in the original drawing. In this case, it turns out to be 100, the endpoint of the y-axis in the original.
- Enclose the entire drawing in a <g> element.
- Enter a translate that moves the coordinate system downwards by the maximum y value.
transform="translate(0, max-y)"
- The next transform will be to scale the y-axis by a factor of -1, flipping it upside-down.
transform="translate(0, max-y) scale(1, -1)"
Example 5-8 incorporates this transformation, producing a right-side-up trapezoid in Figure 5-14.
Example 5-8. Transformed Cartesian coordinates
<svg width="200px" height="200px" viewBox="0 0 200 200"> <g transform="translate(0,100) scale(1,-1)"> <!-- axes --> <line x1="0" y1="0" x2="100" y2="0" style="stroke: black;"/> <line x1="0" y1="0" x2="0" y2="100" style="stroke: black;"/> <!-- trapezoid --> <polygon points="40 40, 100 40, 70 70, 40 70" style="fill: gray; stroke: black;"/> </g> </svg>
The rotate Transformation
It is also possible to rotate the coordinate system by a specified angle. In the default coordinate system, angle measure increases as you rotate clockwise, with a horizontal line having an angle of zero degrees, as shown in Figure 5-15.
Unless you specify otherwise, the center of rotation (a fancy term for the "pivot point") is presumed to be (0, 0). Example 5-9 shows a square drawn in gray, then drawn again in black after the coordinate system is rotated 45 degrees. The axes are also shown as a guide. Figure 5-16 shows the result. If you're surprised that the square has appeared to move, you shouldn't be. Remember, the entire coordinate system has been rotated, as shown in Figure 5-17.^{[1]}
Example 5-9. Rotation around the origin
<!-- axes --> <polyline points="100 0, 0 0, 0 100" style="stroke: black; fill: none;"/> <!-- normal and rotated square --> <rect x="70" y="30" width="20" height="20" style="fill: gray;"/> <rect x="70" y="30" width="20" height="20" transform="rotate(45)" style="fill: black;"/>
Most of the time, you will not want to rotate the entire coordinate system around the origin; you'll want to rotate a single object around a point other than the origin. You can do that via this series of transformations: translate( centerX , centerY ) rotate( angle ) translate(- centerX , - centerY ). SVG provides another version of rotate to make this common task easier. In this second form of the rotate transformation, you specify the angle and the center point around which you want to rotate:
rotate(angle, centerX, centerY)
This has the effect of temporarily establishing a new system of coordinates with the origin at the specified center x and y points, doing the rotation, and then re-establishing the original coordinates. Example 5-10 shows this form of rotate to create multiple copies of an arrow, shown in Figure 5-18.
Example 5-10. Rotation around a center point
<!-- center of rotation --> <circle cx="50" cy="50" r="3" style="fill: black;"/> <!-- non-rotated arrow --> <g id="arrow" style="stroke: black;"> <line x1="60" y1="50" x2="90" y2="50"/> <polygon points="90 50, 85 45, 85 55"/> </g> <!-- rotated around center point --> <use xlink:href="#arrow" transform="rotate(60, 50, 50)"/> <use xlink:href="#arrow" transform="rotate(-90, 50, 50)"/> <use xlink:href="#arrow" transform="rotate(-150, 50 50)"/>
Technique: Scaling Around a Center Point
While it's possible to rotate around a point other than the origin, there is no corresponding capability to scale around a point. You can, however, make concentric symbols with a simple series of transformations. To scale an object by a given factor around a center point, do this:
translate(-centerX*(factor-1), -centerY*(factor-1)) scale(factor)
You may also want to divide the stroke-width by the scaling factor so the outline stays the same width while the object becomes larger. Example 5-11 draws the set of concentric rectangles shown in Figure 5-19.^{[2]}
Example 5-11. Scaling around a center point
<!-- center of scaling --> <circle cx="50" cy="50" r="2" style="fill: black;"/> <!-- non-scaled rectangle --> <g id="box" style="stroke: black; fill: none;"> <rect x="35" y="40" width="30" height="20"/> </g> <use xlink:href="#box" transform="translate(-50,-50) scale(2)" style="stroke-width: 0.5;"/> <use xlink:href="#box" transform="translate(-75,-75) scale(2.5)" style="stroke-width: 0.4;"/> <use xlink:href="#box" transform="translate(-100,-100) scale(3)" style="stroke-width: 0.33;"/>
The skewX and skewY Transformations
SVG also has two other transformations: skewX and skewY, which let you skew one of the axes. The general form is skewX( angle ) and skewY( angle ) The skewX transformation "pushes" all x-coordinates by the specified angle, leaving y-coordinates unchanged. skewY skews the y-coordinates, leaving x-coordinates unchanged, as shown in Example 5-12.
Example 5-12. skewX and skewY
[1] <g style="stroke: gray; stroke-dasharray: 4 4;"> <line x1="0" y1="0" x2="200" y2="0"/> <line x1="20" y1="0" x2="20" y2="90"/> <line x1="120" y1="0" x2="120" y2="90"/> </g> <g transform="translate(20, 0)"> [2] <g transform="skewX(30)"> [3] <polyline points="50 0, 0 0, 0 50" [4] style="fill: none; stroke: black; stroke-width: 2;"/> <text x="0" y="60">skewX</text> [5] </g> </g> <g transform="translate(120, 0)"> [6] <g transform="skewY(30)"> <polyline points="50 0, 0 0, 0 50" style="fill: none; stroke: black; stroke-width: 2;"/> <text x="0" y="60">skewY</text> </g> </g>
[1] These dashed lines are drawn in the default coordinate system, before any transformation has occurred.
[2] This will move the entire skewed "package" to the desired location.
[3] Skew the x-coordinates 30 degrees. This transformation doesn't change the origin, which will still be at (0, 0) in the new coordinate system.
[4] To make things easier, we draw the object at the origin.
[5] Text will be covered in more detail in Chapter 8.
[6] These elements are organized exactly like the preceding ones, except the y-coordinates are skewed.
Notice that skewX leaves the horizontal lines in Figure 5-20 horizontal, and skewY leaves the vertical lines untouched. Go figure.
Transformation Reference Summary
Table 5-1 gives a quick summary of the transformations available in SVG.
Table 5-1. SVG transformations
Transformation | Description |
---|---|
translate(x,y) | Moves the user coordinate system by the specified x and y amounts. Note: If you don't specify a y value, zero is assumed. |
scale(xFactor,yFactor) | Multiplies all user coordinate systems by the specified xFactor and yFactor. The factors may be fractional or negative. |
scale(factor) | Same as scale(factor,factor). |
rotate(angle) | Rotates the user coordinate by the specified angle. The center of rotation is the origin (0, 0). In the default coordinate system, angle measure increases as you rotate clockwise, with a horizontal line having an angle of zero degrees. |
rotate(angle,centerX,centerY) | Rotates the user coordinate by the specified angle. centerX and centerY specify the center of rotation. |
skewX(angle) | Skews all x-coordinates by the specified angle. Visually, this makes vertical lines appear at an angle. |
skewY(angle) | Skews all y-coordinates by the specified angle. Visually, this makes horizontal lines appear at an angle. |
Notes
- ↑ All the figures in this chapter are static pictures. This one shows two squares; one rotated and one unrotated. To show an animation of a rotating square, use <animateTransform>, which we will discuss in Chapter 11, in Section 11.6.
- ↑ This is also a static picture, a "square bullseye." If you want to show an animation of an expanding square, you'll use <animateTransform>, which we will discuss in Chapter 11, in Section 11.6.