In the course
of the previous chapters we used coordinates intensively. What
exactly do we know about them? The SVG document provides us with
a default coordinate system  the initial User
Coordinate System.
Figure
61. User coordinate System


With this user
coordinate system comes along a plane 2Dspace. This canvas is
theoretically infinite in both dimensions. To specify a point on
the canvas, we also need to have a unit measure affiliated to
each coordinate axis. SVG provides us with initial User Units. Initially one
user unit is equal to the size of one pixel.
We know, that a pixel (picture element) is the smallest visible
point of a raster graphics device. Consequently, the coordinates,
and so the position and size of graphic elements, are device and
resolution dependent.
The canvas is infinite, however your computer screen is
finite in both dimensions. So the SVG specification furthermore
defines a finite rectangular region. Now rendering occurs only to
that window  the socalled Viewport..
Figure 62. ViewPort
<svg width="200" height="100">
You
can define the width and the height of the initial viewport by
the SVG root element <svg>via its width and height attributes. The grid in the above
picture has a line spacing of 10 user units. The viewport's upper
left corner coincides with the initial user coordinate system's
origin. The positive xaxis is pointing towards the right, the
positive yaxis is pointing downward. Now with that all defined, we can exactly
predict, where a circle is drawn, when cx and cy is
specified. Please note, that the
coordinate system's origin is not for ever fixed to the upper
left corner. A simple zoom or pan operation can move it to
somewhere else.
The
mathematicians and CAD users among you will feel that coordinate
axis alignment somewhat unusual, since they are accustomed to a
Cartesian coordinate system, with the yaxis up. Though we need
to get used to this coordinate system with the yaxis down, since
it is common in the field of computer graphics.
Again, the
<svg> root element
automatically defines a viewport with dimensions specified by
it's width and height attributes.
Figure 63. Some objects in viewport
<svg width="200"height="100">
<circle cx="10" cy="86" r="15"
fill="red" stroke="black" />
<! code for the car goes here
>
</svg>
All graphical elements, that are partly
outside of the viewport's window are clipped, i.e. only those
parts of the elements inside of the viewport are rendered.
Elements, that are completely outside are simply
invisible.
Now, as we
are familiarized with the root element's viewport characteristic, we are not astonished to
learn, that SVG allows the definition of multiple viewports. As
any other element every additional viewport also has to be
defined as a descendant element of the root element.
Figure
64. Circles in different viewports
<svg
width="200" height="100">
<circle cx="10" cy="86" r="15" fill="red"
stroke="black"/>
<svg x="125" y="15" width="50"
height="70">
<circle cx="10" cy="60" r="15" fill="blue"
stroke="black"/>
</svg>
</svg>
In the
above example a second viewport is defined inside the document's
viewport. It's upper left corner is located at (125,25) and it is 50 units wide and 70 units high, expressed in coordinates and
units of its parent's  the root element's  viewport.
The
positioning of a viewport via x
and yattribute is supported for
all <svg> elements except the
outermost <svg> element. This
new viewport establishes its own new coordinate system with the
origin again in the upper left corner and initial user units by
default. So all its elements are using that coordinate system.
This viewport is also clipping all graphics elements, that are
not completely inside its window. But you can suppress this
behaviour by setting the presentation attribute to overflow="visible".
The above
drawing shows, that both viewports use the same length units. So
two circles with identical radius are rendered to the same size.
What, if we want another scale? To achieve this, we can use <svg>'s viewBox attribute.
Figure 65. Two others viewports
<svg
width="200" height="100">
<circle cx="10" cy="86" r="15" fill="red"
stroke="black" />
<svg x="135" y="15" width="50" height="70" viewBox="0 0 100 140">
<circle cx="10" cy="60" r="15" fill="blue"
stroke="black" />
</svg>
</svg>
The viewBox attribute's value consists of
four numbers xmin, ymin, width,
height. With these you can specify a rectangular user
space that will be mapped to the bounds of the affiliated
viewport.
Syntax:
viewBox="xmin, ymin, width, height"
xmin minimal xcoordinate corresponding to the viewports
upper left corner
ymin minimal ycoordinate corresponding to the viewports
upper left corner
width width of the viewport
height
height of the viewport
When we discussed
the <g> and <use> elements in a previous chapter, we
didn't pay extra attention to the choice of the coordinate
system. Now we need to recover that. We refer again to the rack
example from chapter 3 and start defining a pallet consisting of
four rectangles.
Figure
66. A pallet for the rack
This part
should be provided for the purpose of multiple reuse. So we
design it as a group in the document's <defs> section. Now, when we need to
define the pallet's <rect>
elements, we wonder what coordinate values to choose. Since the
object's dimensions are predefined, we have not much choice. We
can arbitrarily define at least one point's coordinates, the
others result more or less uniquely from the dimensions given. So
we define the pallet's upper left corner to have the coordinates
(0,0). By having fixed this, we
implicitly refer to a coordinate system with it's origin at (0,0).
Figure 67. Showing
viewport for pallet
<g id="pallet"
stroke="black" fill="tan" >
<defs>
<rect id="block" width="16" height="12"
/>
</defs>
<rect x="0" y="0"
width="132" height="3" />
<use xlink:href="#block" x="0" y="3" />
<use xlink:href="#block" x="58" y="3" />
<use xlink:href="#block" x="116" y="3" />
</g>
We chose
to give the upper board a thickness of 3 and the blocks a width
of 16 and a height of 12. With this all other coordinates could
be calculated. As the pallet is made out of wood, tan seems to be an appropriate colour for the
rectangles.
You might
ask now: "Where did I define a coordinate system in this code?".
Indeed we didn't, at least not explicitly. But as I mentioned
above, we do refer to a implicit coordinate system's origin, when
we write x="58" and y="3". Now that we finished our pallet's
definition, we can imagine the coordinate system to be rigidly
coupled to the pallet. With this in mind, we will use the term
"local coordinate system
of the group". Corresponding to that term we will also refer to a
"reference coordinate
system". The
reference coordinate system is defined by the innermost container
element (usually the parent group) or the innermost
viewport.
Now we
need some criteria to qualify the arbitrary choice of our local
coordinate system as advantageous or disadvantageous. For this we
want a scene, in which we can insert some instances of the
pallet.
Figure
68. Rack for pallets
We decide
to use a rack similar to that we defined in the previous chapter
??. With regard to the red reference coordinate system there are
three positions defined. These positions are the destined
midpoint of each load. Since your boss has told you, to store
three empty pallets at those locations, you start immediately
with the lower left one, as it is the most easiest to
handle.
<use xlink:href="#pallet"
x="71" y="300" />
Figure
69. Put pallet in place …
Ok, it
would have been nice if it were so easy. Simply using the
destination point coordinates causes our pallet sinking into the
floor. But that might be no problem. We just have to calculate a
little.
x_pallet = 71  width_of_pallet/2
y_pallet = 300  height_of_pallet
This
should work now.
<use xlink:href="#pallet" x="5" y="285"
/>
Figure
610. It's better
Fine, but
you do not appear very happy, as there are a lot of different
sized pallets in this storage and you are in no mood to always
calculate a lot. A way out of this could be to change the pallets
specifications, so that its local origin lies in the lower
midpoint. Sounds good, so you upgrade your pallet.
<g
id="pallet" stroke="black" fill="tan" >
<defs>
<rect id="block" width="16" height="12"
/>
</defs>
<rect x="66" y="15" width="132" height="3" />
<use xlink:href="#block" x="66"
y="12" />
<use xlink:href="#block" x="8"
y="12" />
<use xlink:href="#block" x="50"
y="12" />
</g>
With this
the positioning of any pallet without calculation work should be
possible.
<use
xlink:href="#pallet" x="71" y="300" />
Figure 611. Better viewport for
pallet
With the help of this illustrative example we
realize, that the local origin is of great importance for
handling group instances. We can imagine it as a grip point, with
which we clutch the pallet and move it to the location we want it
to be. It is now easy to place the other pallets as well 
provided that you are tall enough to reach the height. As you do
not like to store empty pallets, you even put some load onto
it.
<use
xlink:href="#pallet" x="71" y="300"
/>
<use xlink:href="#pallet" x="371" y="0" />
<use xlink:href="#pallet"
x="513" y="200" />
Figure 612. Some pallets in
rack
Select the local
origin of a group deliberately. Comparing it with a grip point,
with which to handle the group instances, might be of
help.
Here is the complete code of the rack's final
document.
<?xml
version="1.0" encoding="UTF8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg width="100%" height="100%"
xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="postBore" height="20"
patternUnits="userSpaceOnUse">
<rect width="12" height="20" stroke="none"
fill="steelblue" />
<circle cx="6" cy="10" r="2" stroke="none"
fill="lightgray" />
</pattern>
<g id="pallet" stroke="black" strokewidth="0.2"
fill="tan" >
<defs>
<rect id="block" width="16" height="12"
/>
</defs>
<rect x="66" y="15"
width="132" height="3" />
<use xlink:href="#block" x="66" y="12"
/>
<use xlink:href="#block" x="8" y="12"
/>
<use xlink:href="#block" x="50" y="12"
/>
</g>
<g id="load">
<use xlink:href="#pallet" />
<rect x="66" y="85" width="132" height="70"
stroke="black" strokewidth="1" />
</g>
<g id="uprightPost" stroke="black"
strokewidth="0.2" >
<rect width="12" height="300"
fill="url(#postBore)" />
</g>
<g id="beam" stroke="black" strokewidth="0.2"
fill="steelblue" >
<rect x="3" y="0" width="278" height="10"
/>
<rect x="0" y="0" width="3" height="20" />
<rect x="281" y="0" width="3" height="20"
/>
</g>
<g id="column">
<use xlink:href="#uprightPost" />
<use xlink:href="#beam" x="14" y="0" />
<use xlink:href="#beam" x="14"
y="100" />
<use xlink:href="#beam" x="14" y="200"
/>
</g>
<g id="rack">
<use xlink:href="#column" x="0" y="0" />
<use xlink:href="#column" x="300" y="0"
/>
<use xlink:href="#uprightPost" x="600" y="0"
/>
<line x1="50" y1="301" x2="650" y2="301"
stroke="black" fill="none" />
<rect x="50" y="302" width="700" height="8"
stroke="none" fill="lightgray" />
</g>
</defs>
<g transform="translate(100,150)" >
<use xlink:href="#rack" x="14" y="0" />
<use xlink:href="#load" x="71" y="300"
fill="tomato" />
<use xlink:href="#load" x="371" y="0" fill="tomato"
/>
<use xlink:href="#load" x="513" y="200"
fill="tomato" />
</g>
</svg>
We have
learned now how to displace <use>elements by their x and yattributes and
how to design the corresponding groups in order to do that quite
comfortable. But a simple element displacement from one location
to another can't be the end of the road.
What if
we want to rotate, mirror or change the size of an element? For
this task the SVG specification gives us a very common powerful
feature  the transformattribute. That transformattribute can be applied to most
graphics elements, i.e. elements, that cause graphics to be drawn
onto the canvas. It can be applied as well to the <g>element. Here is the transformattribute's
syntax:
transform = "translate(tx [ ty])
rotate(angle [cx cy])
scale(sx
[sy])
skewX(angle)
skewY(angle)"
We will
discuss the individual components of the transform attribute
separately now. I prefer to illustrate the transformations with a
more complex element, i.e. an <use> element referring to a group. But
we do bear in mind that the transform attribute is applicable to the other
graphics elements too.
We will
use the binding screw here for the subsequent examples. Assume
that we defined the screw's geometry regarding its red local
coordinate system.
Figure 613. The screw and its coordinate system
Here is
the code for our screw
<defs>
<linearGradient
id="zylinderShade" x2="0%" y2="50%"
spreadMethod="reflect">
<stop offset="0%"
stopcolor="darkslategray"/>
<stop offset="100%" stopcolor="white"/>
</linearGradient>
<g id="screw" stroke="black"
strokelinejoin="round" >
<path fill="url(#zylinderShade)" d="M100,64 96,60
96,140 100,136 z M68,64 72,60 72,140 68,136 z M96,60 72,60 72,140
96,140 z" />
<path fill="url(#zylinderShade)" d="M100,84
220,84 220,116 100,116 z M220,84 220,116 224,112 224,88 220,84 z"
/>
<path strokewidth="0.4" fill="none"
strokelinecap="round" strokedasharray="24 12 4 12" d="M60,100
232,100" />
<path strokewidth="0.4" fill="none"
strokelinecap="round" d="M100,88 224,88 M100,112 224,112"
/>
</g>
</defs>
If we
simply instantiate the screw's group via
<use xlink:href="#screw" />
the screw's local coordinate system will
coincide with the blue reference coordinate system. With this
starting situation we will analyse now the effects of the
individual components of the transform attribute. With that model
it might become somewhat clearer, that we rather transform the
coordinate system including the affiliated 2Dspace  with the
screw here accidentally in it  than simple graphics
elements.
The
translation is an elementary displacement
transformation.
Syntax:
translate(tx, ty)
tx xcoordinate of displacement
ty ycoordinate of displacement
Here we
apply the translate transformation
to the screw group's instance,
exactly to the local origin of this group.
<use xlink:href="#screw"
transform="translate(100,130)" />
Figure 614. x and y attributes in use element
Easy, isn't it? So
you might ask now what the difference of the translatetransformation to the use of the xand
yattributes is. Obviously seems
<use xlink:href="#screw"
transform="translate(100,130)" />
to be
identical to
<use xlink:href="#screw" x="100" y="130"
/>
Yes, you are right.
So why do we need something complex like transforms? We will
understand that later, when we had a look at the other elementary
transforms and need to combine them. So please be patient and
simply accept the necessity of the translatetransformation for now.
But we should also
understand the effect of a combination of the xand
yattributes and the translatetransformation.
<use xlink:href="#screw"
transform="translate(100,130)" x="130" y="160" />
Figure 615. Translate the use
element
The effect we have
now, is that the screw is translated first to the
coordinates (100,130)and subsequently
displaced by (130,160)to the end
location (230,30). To yield the end
position of the elements local coordinate system formally we can
simply add the coordinates as in
X_{local} = t_{x} + x
y_{local} = t_{y} +
y
In this formulas we can exchange the addends without
harm, i.e.
X_{local} =x +
t_{x}
y_{local} =y + t_{y}
will give us
the same result. But this means we can also exchange the
coordinate values to
<use xlink:href="#screw"
transform="translate(130,160)" x="100" y="130" />
Figure 616. Put the screw at
(230,30)
As you see we yield
the expected end position of (230,30)here too. So we are
not surprised to hear that the following elements produce
identical output.
<use xlink:href="#screw"
transform="translate(130,160) translate(100,130)" />
<use xlink:href="#screw"
transform="translate(100,130) translate(130,160)" />
<use xlink:href="#screw"
transform="translate(230,30)" />
<use xlink:href="#screw" x="230" y="30"
/>
It is quite simple
but also somewhat confusing to use both the xand
yattributes and the translatetransformation. So
it is best to avoid this generally, but there may be situations
to use this effect deliberately as a benefit.
Although the
sequence of these both displacements is of no importance
here, the SVG specification
states:
The transform attribute is applied to an
element before processing any other coordinate or length values
supplied for that element.
With that we have a predefined sequence of
perform
the translatetransformation.
apply the xand yattributes.
This was
also taken into account with the examples above.
The rotatetransformation is used to rotate an element by a certain
angle.
Syntax:
rotate(angle [, cx, cy])
angle rotation angle in
degrees, can be positive and negative
cx xcenter of rotation, optional
cy y center of rotation, optional
We apply that
rotatetransformation to our screw in its simplest
form.
<use xlink:href="#screw"
transform="rotate(25)" />
Figure 617. Rotate the screw
This results in a
rotation around the blue origin  yes it is the origin of the
reference coordinate system the screw is rotated about, as we'll
see later. The angle's value 25is
given in degrees. The rotation occurs clockwise since we have a
positive angle's value. The
mathematicians among you may cry out loud, because this is the
mathematical negative direction. They are right when referring to
a Cartesian coordinate system with the yaxis up. Since we have a
coordinate system with its yaxis down, the positive rotation
angle is directed clockwise.
Now we want
to explore the effect of using the other two parameters of the
rotate transform. We write
<use xlink:href="#screw" transform="rotate(25,
100,100)" />
Figure 618. Rotate with center at
(100,100)
With this we forced
the screw to be rotated about the point (100,100)marked
with the little blue pin. The coordinates of this pivot are in
the reference coordinate system with the blue axes.
So we learned about
the rotatetransformation:
the rotation
occurs about the reference system's origin by default
if an additional pivot point (cx,cy) is given in
reference system's coordinates, the rotation occurs about that
point.
the rotation angle's value has to be provided in degrees.
the rotation angle can be positive or negative. A positive
value results in a clockwise rotation.
With the rotatetransformation the
pivot coordinates only remain unchanged. This is also the
transformation centre.
a rotation angle of zero results in the identity
transformation, i.e. the transformation, that leaves the element
unaffected.
The inverse rotation transformation to rotate(angle)is rotate(angle),i.e.
these transformations applied subsequently to an element leaves the element unaffected
(identity transformation).
The scaletransformation is used to change the size of an
element.
Syntax:
scale(sx
[,sy])
sx xscale
factor
sy yscale factor, optional
If we omit
the yscale factor, its value will be set equal to the value of
the xscale factor.
<use xlink:href="#screw"
transform="scale(2)" />
As a result
we now have a screw twice as large as the original. But it is not
only enlarged, it has also changed its position. Instead of
wondering about this effect any longer let us look at the screw's
local coordinate system. We see now that the underlying grid as
well as the coordinate axes are scaled also by a factor of 2. And
with the recognition of the fact, that obviously every local
coordinate's value has been doubled we do understand now that the
scaled screw also must seem to be translated. It isn't, it is
simply scaled with respect to the origin  the blue
one.
Figure 619. Scale(2) the screw
Of course we
can also reduce the size of our screw with
<use xlink:href="#screw"
transform="scale(0.5)" />
For this we
simply have to use values less than one. Here we also multiply
every coordinate with the scale factor, so that those get smaller
values.If we make the scale value smaller and smaller the screw
will vanish into thin air, exactly into the world's origin.
Finally a scale factor of zero will reduce the screw to a single,
dimensionless point. Thus preventing it from being
rendered.
Figure 620. Scale(0.5) the screw
Until now we used
only one scale factor. With this we speak about an uniform
scaling, as we remember, that
sy is set to the value of sx when omitted. What if we
use two different scale factors now? We should consequently call
this a nonuniform
scaling.
<use xlink:href="#screw"
transform="scale(1,0.5)" />
Figure 621. Scale(1,0.5) the screw
With this
we reduced the screw's dimensions in ydirection only. We can
vary this of course with
<use
xlink:href="#screw" transform="scale(0.5,1)"
/>
Figure 622. Scale(0.5,1) the screw
by shrinking
the screw in xdirection only. As we understood this so far, you
may ask what about negative values. No problem with
this.
<use xlink:href="#screw"
transform="scale(1,1)" />
Figure 623. Scale(1,1) the screw
With that we
mirrored our screw at the yaxis. Or to explain it geometrically,
we changed the sign of every xcoordinate by multiplying it by
1, so that we now have negative xvalues for the screw. Of
course we can also mirror about the xaxis by simply
using
<use xlink:href="#screw"
transform="scale(1,1)" />
With the
special case of
<use xlink:href="#screw"
transform="scale(1,1)" />
we yield a
reflection about the origin by a series of two consecutive
mirroring transformations at the xaxis and the yaxis. This is
identical to the result of a rotation about the origin by
180°.
<use xlink:href="#screw"
transform="rotate(180)" />
So we learned about
the scaletransformation:
an element is
scaled uniformly using the scale transformation with
one single argument or two arguments with equal values.
a scale factor greater than 1 will enlarge the element.
a scale factor with a magnitude of less than 1 will reduce
the size of the element.
a scale factor of zero will prevent the element from being
displayed.
an element is scaled nonuniformly using the scaletransformation with two different argument values.
a scale factor sx
= 1 results in mirroring at the
yaxis.
a scale factor sy
= 1 results in mirroring at the
xaxis.
a scale transformation does usually not preserve lengths.
a uniform scale transformation preserves angles, while a
nonuniform doesn't.
the centre of the scale transformation is exclusively the
reference coordinate system's origin.
Scale factors sx =
1 and sy = 1 results in the
identity transformation.
The inverse scale transformation to scale(sx,sy)is
the transformation scale(1/sx,1/sy),
i.e. scaling with inverse scaling factors.
The skewing
transformation  frequently called shearing  consists of two
elementary transformations skewX and skewY.
Syntax:
skewX(angle])
angle angular displacement of yaxis
skewY(angle])
angle angular displacement of
xaxis
We want to look at
the skewY transformation first.
<use xlink:href="#screw" transform="skewY(25)"
/>
Figure 624. SkewY(25) the
screw
We can interpret
the skewY transformation as a rotation of the xaxis towards
the positive yaxis, while leaving the yaxis' direction as it
is. It is also obvious now, that a skewing angle of 90° and
greater is not allowed. The results for doing so are not defined.
A negative angle results in a rotation in the opposite direction.
Here also the angle must be greater than 90°. The only line
of the plane that remains unaffected by that transformation is
the xaxis.
The skewXtransformation behaves similar with regard to the
yaxis.
Figure 625. SkewX(25) the screw
Here we can
understand the skewX transformation as a
rotation of the yaxis towards the positive xaxis, while leaving
the xaxis intact.
But beware. The
association of a rotation with the skewXand skewYtransformation is somewhat misleading, since the
resulting effect is a true shearing or skewing. As you can easily
see above
the skewXtransformation results in a displacement of the
xcoordinates only, not
ycoordinates.
the skewYtransformation
displaces ycoordinates only, xcoordinates remain
unaffected.
With this we can
also understand the skewX transformation as a
displacement of any point in the plane in the
xdirection.
This displacement depends on the ycoordinate,
i.e. points with greater yvalues are displaced more than points
with smaller yvalues. To describe that mathematically, we can
define the ratio
Finally
there is a relation between those ratios and the
angles
As we also remember, that the tangent of
90° is infinite, we now understand, that a skew angle of
90° is not defined.
Here we
learned about the skewing transformation:
an element can
be skewed in x and ydirection independently by the skewXand skewYtransformation.
the skew angle can be positive and negative, its value must
be specified in degrees.
the skew angle's magnitude must not be greater or equal to
90°.
the skewXtransformation only
preserves lengths in xdirection.
the skewYtransformation only
preserves lengths in ydirection.
the skewing transformation does not preserve angles.
the skewXtransformation's centre
is the xaxis.
the skewYtransformation's centre
is the yaxis.
the skew angle angle = 0 results in the
identity transformation for skewX and skewY.
The inverse transformation to skewX(angle) is the
transformation skewX(angle)(the same holds
for skewY).
Now that
you know how the elementary transformations work you want to
start transforming your elements. Translating, scaling, rotating
and skewing them to your heart's content. You can do this quite
comfortably, as the transformattribute is capable of holding more than one
elementary transform.
Syntax:
transform="trfN ... trf2 trf1"
trf1 first elementary transform
trf2 second elementary transform
.......
trfN
N'th elementary transform
Fine, but
we have to be cautious, because the sequence of the elementary
transformations is of great importance.
The
elementary transformations of the transform attribute are evaluated from right to left.
To
introduce you into this we start with a small parts
library.
Figure 626. Objects in library
Beside
our screwwe also have a
nut group now. Both parts are geometrically
described with respect to their red local coordinate systems. We
must use these parts right now to bolt two plates
together.
Figure 627. Two plates to bolt together
Here is
the library's code
<defs>
<linearGradient
id="zylinderShade" x2="0%" y2="50%"
spreadMethod="reflect">
<stop offset="0%"
stopcolor="darkslategray"/>
<stop offset="100%" stopcolor="white"/>
</linearGradient>
<pattern id="hatch" width="10" height="10"
patternUnits="userSpaceOnUse">
<rect width="10" height="10" stroke="none"
fill="silver" />
<polyline points="0,10 10,0" stroke="gray"
strokewidth="0.25"/>
</pattern>
<g id="screw" stroke="black"
strokelinejoin="round" >
<path fill="url(#zylinderShade)" d="M0,36 4,40
4,40 0,36 z
M32,36 28,40
28,40 32,36 z
M4,40 28,40
28,40 4,40 z" />
<path fill="url(#zylinderShade)" d="M0,16
120,16 120,16 0,16 z M120,16 120,16 124,12 124,12 120,16 z"
/>
<path strokewidth="0.4" fill="none"
strokelinecap="round" strokedasharray="24 12 4 12"
d="M40,0 132,0" />
<path strokewidth="0.4" fill="none"
strokelinecap="round" d="M0,12 124,12 M0,12 124,12" />
</g>
<g id="nut" stroke="black"
strokelinejoin="round">
<path fill="url(#zylinderShade)" d="M0,36
4,40 4,40 0,36 z M32,36 28,40 28,40 32,36 z
M4,40 28,40 28,40 4,40 z" />
<path fill="gray" d="M4,40 28,40 28,16
4,16 z" />
<path fill="silver" d="M4,16 28,16 28,16
4,16 z" />
<path fill="gray" d="M4,16 28,16 28,40 4,40
z" />
</g>
<g id="plates">
<path id="upperPlate" fill="url(#hatch)"
stroke="black"
d="M10,100
81,100 81,120 10,120 12,116 9,113 11,111 9,107 12,104 8,101
z
M81,100 119,100
M81,120 119,120
M119,100 290,100 290,120 119,120 z
M290,100 310,100
M290,120 310,120
M310,100 380,100 380,120 310,120
z"/>
<path
id="lowerPlate" fill="url(#hatch)" stroke="black"
d="M30,122
81,122 81,142 30,142 z
M81,122 119,122
M81,142 119,142
M119,122 290,122 290,142 119,142 z
M290,122 310,122
M290,142 310,142
M310,122 410,122 412,126 408,131 411,135
409,138 412,142 310,142 z"/>
<path
stroke="black" strokewidth="0.4" fill="none"
strokedasharray="12 6 2 6"
d="M100,90 100,150 M300,90 300,150" />
<path stroke="black" strokewidth="0.5"
fill="none"
d="M100,100 120,80 M300,100 320,80" />
<text x="120" y="80" textanchor="start">(100,
100)</text>
<text x="320" y="80" textanchor="start">(300,
100)</text>
</g>
</defs>
The
plates' geometry is defined with respect to the blue reference
coordinate system. We start with the first screw in order to
stick it into the first hole. To bring it into the drawing, we
simply instance it by
<use xlink:href="#screw"
/>
Figure 628. The plates and the screw
Without a transformation the screw is
positioned so, that its red local coordinate system coincides
with the blue reference coordinate system. Fine, all we have to
do now, is simply
· rotate thescrewby 90 degrees.
· translate the screwat its destination coordinates (100,100).
Ok, we
remember that the elementary transformations are performed right
to left, so we write
<use xlink:href="#screw"
transform="translate(100,100) rotate(90)" />
Figure 629. The screw in place
It works.
Despite the fact, that it seems to be so easy, we should have a
look under the transformation's hood. Here is a stop motion
picture of the transforming procedure.
Figure 630. Transformations to
put in place the screw
The first
transformation process was to rotate the screw by 90°.
Hereafter we translated the screw's local origin onto the
point (100,100).
As I told
you, that the transformations order is important, let's simply
interchange that order as an experiment. Now we translate first
and rotate the screw subsequently.
<use xlink:href="#screw"
transform="rotate(90) translate(100,100)" />
Figure 631. Steps for transformation
With
this we miss the hole. Maybe we implied a rotation about the
screw's local red origin, but the rotation definitely occurred
about the reference coordinate system's blue origin. That wrong
presumption is in fact a popular beginners fault, so I need to stress here again:
The
default centre of an element's rotate transformation is the origin of the parent
element's reference coordinate system.
You might
remember, that there were two more default arguments with the
rotate transformation. We can use these to set the pivot point of
the rotation explicitly. Then we can leave the transformations
order as it is.
<use xlink:href="#screw"
transform="rotate(90, 100,100)
translate(100,100)" />
Now we can
practice what we learned with the assembly of our nut.
Rotate about the
coinciding local and reference origin by 90°
Translate onto the desired location at point (100,142).
Figure 632. Plates are
bolted
<use xlink:href="#femaleScrew"
transform="translate(100,142)
rotate(90)"
/>
or again
alternatively
<use xlink:href="#femaleScrew"
transform="rotate(90, 100,142) translate(100,142)"
/>
works
fantastically, although the screw looks somewhat
oversized.
You want to
start to assemble the other screw immediately, as you suddenly
realise, that the hole's diameter is half as wide as the previous
one's. After looking quite helplessly at our screw and nut groups
for a short period of time you prudently suggest to make some
smaller ones.
Yes, of
course. We do not have only one screw of a given size. We have an
infinite number of screws with different sizes each  thanks to
SVG's powerful scale transformation.
So how should we
start? To make a screw of half the size simply means to scale it
down uniformly via scale(0.5). But we still
have to rotate and translate also. So what transformation order
should we apply here  we are really extremely sensible with this
now.
As a precaution we
reread the chapter about scaling above. Here it is. The centre of
the scaling transformation is the reference coordinate system's
origin. Since the screw's local origin coincides with the
reference system's initially, we can start blindly with
the scale transformation followed by the now familiar pair
of rotateand translatetransform.
<use xlink:href="#screw" transform="translate(300,100)
rotate(90) scale(0.5)" />
Figure 633. Another screw in
place
It
obviously works as expected. With this success you decide
lionhearted to permute the order of scaling and rotation with the
nut.
<use xlink:href="#nut"
transform="translate(300,142) scale(0.5) rotate(90) "
/>
Figure 634. The two screws in
place
Well, this way also
works fine. Though it is no surprise, as both the scaleand
the rotatetransformation have an identical centre. But you
should also note:
You can arbitrarily
change the subsequent order of uniform scale and rotate
transformations with respect to the origin. But you cannot do
that with the subsequent order of nonuniform scale and rotate
transforms.
Just to show
you the correctness of the last sentence regarding the
nonuniform scale, we will simply proof that by
example.
<use
xlink:href="#screw" transform="scale(0.5,1) rotate(90)"
/>
<use
xlink:href="#screw" transform="rotate(90) scale(0.5,1)"
/>
Figure 635. Order for transformations
Still
heavily impressed by the enormous number of different screw sizes
available, you are meditating, if it would be possible to have
two screws of the same size but different thread lengths. Here I
must tell you that we can't do this with SVG 1.0 based on a
single group. But the wise W3C is busy all the time and I'm quite
sure we get a basketfull of surprises with SVG 2.0.
I don't
withhold the SVG code of the final screw document from
you.
<?xml version="1.0" encoding="UTF8" standalone="no"
?>
<!DOCTYPE svg PUBLIC "//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg width="100%" height="100%"
xmlns="http://www.w3.org/2000/svg">
<defs>
<! screw, nut and plates group go here
>
</defs>
<g transform="translate(60,60)">
<use xlink:href="#plates" />
<use xlink:href="#screw"
transform="translate(100,100) rotate(90)" />
<use xlink:href="#nut"
transform="translate(100,142) rotate(90)" />
<use xlink:href="#screw"
transform="translate(300,100) rotate(90) scale(0.5)" />
<use xlink:href="#nut"
transform="translate(300,142) scale(0.5) rotate(90) " />
</g>
</svg>
Until here
we discussed the application of multiple transforms to a single
element. So we now need to analyse, how the transformation of
container elements influence the contained elements. Furthermore
we will consider transformed instances of transformed elements or
groups.
Figure 636. Two squares
and one circle
<?xml version="1.0" encoding="UTF8"
standalone="no" ?>
<!DOCTYPE svg PUBLIC "//W3C//DTD SVG
1.0//EN""http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg width="100%" height="100%"
xmlns="http://www.w3.org/2000/svg">
<defs>
<rect id="inner" width="200" height="200"
fill="seagreen"
transform="rotate(45) translate(100,100)"
/>
</defs>
<g transform="translate(300,200)">
<rect width="100" height="100" fill="navy"
strokewidth="2"
transform="scale(2)
translate(50,50)" />
<circle r="100"
stroke="none" fill="tomato" />
<use xlink:href="#inner" transform="scale(0.707)"
/>
</g>
</svg>
We have
three elements in this SVG document (coordinates' text omitted) 
two squares and one circle.
The navysquare is defined with its upper left corner being
(0,0) and then translated so that its centre point coincides
with the origin. After that it is uniformly enlarged by the
factor 2.
The tomatocoloured circle has a centre point of (0,0)so that
it also coincides with the origin.
The seagreensquare is defined in the defssection. It has
originally twice the size as the navy one. It is also then
translated so that its centre point coincides with the origin.
Then it is rotated about the origin by 45°. At least it is
reused by an use element and scaled down
by 0.707 so that it fits into the circle.
All three elements
are finally placed into a group by which they are transformed to
the centre point of the document (300,200).
We will focus on the
sequence of these different transforms and analyse, how we can
create the same image with a simple flat element structure, i.e.
not using.<defs>,
<g> and <use> elements. We
start with the tomato coloured circle. To pull it out of its
parent group, we simply have to apply the group's transformation
directly to the circle element instead.
<circle
r="100" stroke="none" fill="tomato" transform="translate(300,200)" />
Figure 637. Translated circle
That was
quite easy. We advance to the navy square. This rectangle has its
own transformation defined. In order to extract it from its
parent group, we have to apply the group's transformation also to
the rectangle. And we have to apply the group's transformation
after the element's own.
<rect width="100" height="100"
fill="navy" strokewidth="2" transform="translate(300,200)
scale(2) translate(50,50)" />
Figure 638. Circle in a square
With that we
can generalise for nested transformed groups.
<g transform="t3">
<g transform="t2">
<g transform="t1">
<element
transform="t0" />
</g>
</g>
</g>
The overall
resulting element transformation can be composed of the
individual group transformations as follows
<element
transform="t3 t2 t1 t0" />
We'll
compose the parent groups'
transformations from inside out. That is, these are applied after
(appended from the left to) the element's transformation. We can
also think here in terms of inheritance (similar to CSS style
inheritance) and state as a general rule:
An element
inherits the transformation of its parent element so that the
inherited transform is applied after the element's
transform.
Now let's consider
the seagreen
square finally. In a first step we want to
eliminate the <use> element. In order to
preserve the <rect> element's visual
appearance (overall transformation), we need to apply the
<use>element's transformation to the <rect>element
itself.
<rect
id="inner" width="200" height="200" fill="seagreen" transform="
scale(0.707) rotate(45) translate(100,100)"
/>
Please
note, that we have to apply the
<use>element's transformation after the element's
transformation. Now we need to extract the seagreensquare
from its enclosing group. We are quite familiar with that, since
we did exactly that with the circle and the navysquare
before.
<rect
id="inner" width="200" height="200" fill="seagreen" transform="
translate(300,200) scale(0.707) rotate(45)
translate(100,100)" />
Figure 639. The three objects
With that we
can also generalise for instances of elements (and
groups).
<element id="e0"
transform="t0" />
<use id="e1" xlink:href="#e0" transform="t1" />
<use id="e2" xlink:href="#e1" transform="t2" />
<use id="e3" xlink:href="#e2" transform="t3"
/>
This is
identical to
<element id="e3" transform="t3 t2 t1 t0"
/>
where the
use's transformation is applied after (appended left to) the
reused element's transformation. We can here too think in terms
of inheritance and state as another general rule:
The particular
instance of a reused element inherits the transformation of its
corresponding useelement so that the inherited
transform is applied after the element's transform.
The
"flat" version of our document produces the same graphical
output and reads finally.
<?xml version="1.0" encoding="UTF8"
standalone="no" ?>
<!DOCTYPE svg PUBLIC "//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg width="100%" height="100%"
xmlns="http://www.w3.org/2000/svg">
<rect width="100" height="100" fill="navy"
strokewidth="2"
transform="translate(300,200) scale(2)
translate(50,50)"/>
<circle r="100" stroke="none" fill="tomato"
transform="translate(300,200)" />
<rect id="inner" width="200" height="200"
fill="seagreen"
transform="translate(300,200) scale(0.707) rotate(45)
translate(100,100)" />
</svg>
Now you
might be surprised that we can use this quite theoretically stuff
elegantly for a refreshing piece of computer art. For this we
start with a simple triangle.
<?xml
version="1.0" ?>
<svg width="600" height="400">
<defs>
<polygon id="e0" fill="navy" points="0,0 200,0
100,200" />
</defs>
<use xlink:href="#e0"
transform="translate(200,300)" />
</svg>
Figure 640. A triangle
Now we reuse
this triangle three times, while we scale it down by the factor
0.5 and position it at the corners of the  now unused  original
triangle.
<?xml version="1.0"
?>
<svg width="600" height="400">
<defs>
<polygon id="e0" fill="navy" points="0,0 200,0
100,200" />
<g
id="e1">
<use
xlink:href="#e0" transform="translate(0,0) scale(0.5)" />
<use xlink:href="#e0"
transform="translate(100,0) scale(0.5)"
/>
<use xlink:href="#e0"
transform="translate(50,100) scale(0.5)" />
</g>
</defs>
<use xlink:href="#e1"
transform="translate(200,300)" />
</svg>
Figure 641. First step
We repeat this
transformation by simply copying the group e1 multiple times and
incrementing the id names in each.
<?xml version="1.0" ?>
<svg width="600" height="400">
<defs>
<polygon id="e0" fill="navy" points="0,0 200,0
100,200" />
<g
id="e1">
<use
xlink:href="#e0" transform="translate(0,0) scale(0.5)" />
<use xlink:href="#e0"
transform="translate(100,0) scale(0.5)"
/>
<use
xlink:href="#e0" transform="translate(50,100) scale(0.5)"
/>
</g>
<g id="e2">
<use
xlink:href="#e1" transform="translate(0,0) scale(0.5)" />
<use xlink:href="#e1"
transform="translate(100,0) scale(0.5)"
/>
<use xlink:href="#e1"
transform="translate(50,100) scale(0.5)" />
</g>
<g id="e3">
<use
xlink:href="#e2" transform="translate(0,0) scale(0.5)" />
<use xlink:href="#e2"
transform="translate(100,0) scale(0.5)"
/>
<use xlink:href="#e2"
transform="translate(50,100) scale(0.5)" />
</g>
<g id="e4">
<use
xlink:href="#e3" transform="translate(0,0) scale(0.5)" />
<use xlink:href="#e3"
transform="translate(100,0) scale(0.5)"
/>
<use xlink:href="#e3"
transform="translate(50,100) scale(0.5)" />
</g>
<g id="e5">
<use
xlink:href="#e4" transform="translate(0,0) scale(0.5)" />
<use xlink:href="#e4"
transform="translate(100,0) scale(0.5)"
/>
<use xlink:href="#e4"
transform="translate(50,100) scale(0.5)" />
</g>
</defs>
<use xlink:href="#e5"
transform="translate(200,300)" />
</svg>
Figure 642. More steps
With these
boring transformations we have created a well known fractal  the
Sierpinski Triangle. If you want to learn more
about these beautiful selfsimilar fractals, you can surf the web
and type "iterated function systems", "IFS" or simply "fractals" in
your favourite search engine.
Before you
start reading this chapter, you should know that you not
necessarily need to use this kind of transform. But if you want
to use the matrix transform with scripting for performance
reasons or simply want to understand how it works, this chapter
is for you.
So you might have
noticed that we omitted a particular transformation type 
the matrixtransform.
Syntax:
matrix(a, b,
c, d, e, f)
a, b, c, d, e,
f matrix components as real numbers
Let's refer
to the screw example again and do the transformations this time
with matrices.
Figure 643. The plates and the screws
<?xml
version="1.0" encoding="UTF8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/SVG/DTD/svg10.dtd">
<svg width="100%" height="100%"
xmlns="http://www.w3.org/2000/svg">
<defs>
<! screw, nut and plates group go here
>
</defs>
<g transform="translate(60,60)">
<use xlink:href="#plates" />
<use xlink:href="#screw"
transform="matrix(0,1,1,0,100,100)" />
<use xlink:href="#nut"
transform="matrix(0,1,1,0,100,142)" />
<use xlink:href="#screw"
transform="matrix(0,0.5,0.5,0,300,100)" />
<use xlink:href="#nut"
transform="matrix(0,0.5,0.5,0,300,142)" />
</g>
</svg>
In order to fully understand this, we need to
do some vector mathematics here. A matrix is a rectangular array
of real numbers. The matrices here in SVG context are 3x3
matrices suited to transform coordinates. The 2dimensional
coordinates are expanded by an additional 1 to three vector
components.
With this concept of
the so called homogenous coordinates we are allowed to perform
the translation  which is additive by nature  also per
matrix/vector multiplication. The components in the first two
rows of the matrix are the components of the matrix transform. Since the last row is always (0 0 1),
we can omit it. Before discussing these components and compare
them to the elementary transformations we already know, let us
have a quick look at how the matrix/vector multiplication is
performed.
With this a new point (x',y')results from
another point (x,y)by the
calculation (ax+cy+e,
bx+dy+f), where a,b,c,d,e,fare
the constant matrix elements. With this we have linear functions
in the coordinates. Transformations that have this linear
characteristic are called affine
transformations.
The
translation matrix looks like this
where txand ty., the displacement in x
and ydirection, are exactly the same values we have to supply to
the translate(tx,ty)transform.
The rotation
matrix needs some trigonometric functions
where alfa is the rotation
angle. Please note, that the angle's value must be supplied in
radians here. This is contrary to the rotatetransform, where
we need the angle in degrees. We can simply convert an angle from
degrees into radians via
Scaling
Matrix
The scale
factors sxand sy.are identical to
the factors we will use with scale(sx,sy)transform
.
Shearing
Matrix
The skew factor tan(alfa)for the transformation matrices must be calculated from
the skewing angle of the skewX(angle)and skewY(angle)transform via the tangent function.
Transformations can be concatenated by multiplying their
matrices. Since matrix multiplication is not commutative, i.e.
the matrices cannot be interchanged, it is important that the
matrices be composed in the correct order. The matrix
multiplication has to be performed as follows.
Multiplying a vector with multiple matrices must be
performed from right to left.
This means that the rightmost transformation
matrix represents the first transformation to be applied, while
the leftmost matrix represents the last transformation to be
applied. This is also the reason for the righttoleft evaluation
of the elementary transformations of the transform attribute. That means also, if we
want to transform an element that still has some transformations
applied to it, we must postmultiply the new transformation
matrix, that is append it to the left of the previously applied
transformation matrices.
Now let's practice that theoretical stuff
with the small screw of our screw example.
Figure 644. Using matrix to put screw in place
We transformed the screw with
<use xlink:href="#screw"
transform="translate(300,100) rotate(90) scale(0.5)"
/>
We create
and compose the corresponding matrices in exactly the same
order
As we
remember, that sin 90° = 1 and cos 90° = 0, we can
simplify the middle matrix somewhat.
Now we can
start to multiply the matrices. You might be astonished, that I
tell you that it doesn't matter, if we start from left or from
right. So please note, only the order of the matrices is
important, not the evaluation order. We choose to start from the
left, leaving the multiplication from the right for your
exercise J. Multiplication of the two left
matrices yields
Now we
multiply the remaining matrices
and get the resulting matrix. We remember
SVG's matrix notation
in combination with the transform
attribute
matrix(a, b, c, d, e, f)
With this
we can now write the transform attribute in matrix
notation
<use xlink:href="#screw"
transform="matrix(0,0.5,0.5,0,300,100)" />
Easy isn't
it? You might ask now, why you should do such a lot calculation.
Here is the answer:
SVG
renderers usually convert transformation attributes into internal
transformation matrices. So the use of the matrix transform
attribute results in the best possible performance.
I recommend,
to consider using the matrix transform attribute, if you
automatically generate svg documents (serverside or clientside)
or manipulate the transform attribute by script
animation.