perfectxml.com
 Basic Search  Advanced Search   
Topics Resources Free Library Software XML News About Us
home » info bank » articles » Chapter 6 - Coordinate system, can't live without it Sat, Feb 23, 2008
Chapter 6 - Coordinate system, can't live without it
Excerpt published here with permission from the book: Learn SVG
    by Jon Frost, Stefan Goessner and Michel Hirtzler (revisited by Robert DiBlasi)

For more information on this book, or to purchase this book, visit www.LearnSVG.com.




Coordinate system, can't live without it

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 6-1. User coordinate System

  Contents
Coordinate system, can't live without it
Multiple Viewports

Locale Coordinate System

Elementary Transformations

Translate

Rotate

Scale

Skewing

Concatenation of Transforms

Nesting of Transformations

Transformation Matrices

Translation Matrix

Rotation Matrix

Scaling Matrix

Shearing Matrix

Concatenate Transform Matrices

Matrix Example

 With this user coordinate system comes along a plane 2D-space. 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 so-called Viewport..


Figure 6-2. 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 x-axis is pointing towards the right, the positive y-axis 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 y-axis up. Though we need to get used to this coordinate system with the y-axis 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 6-3. 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.

Multiple Viewports

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 6-4. 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 y-attribute 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 6-5. 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 x-coordinate corresponding to the viewports upper left corner
     
ymin       
minimal y-coordinate corresponding to the viewports upper left corner
     
width      
width of the viewport
     
height           
height of the viewport

Locale Coordinate System

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 6-6. 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 6-7. 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 6-8. 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 6-9. 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 6-10. 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 6-11. 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 6-12. 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="UTF-8" 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" stroke-width="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" stroke-width="1" />
    
</g>
    
<g id="uprightPost" stroke="black" stroke-width="0.2" >
       
<rect width="12" height="300" fill="url(#postBore)" />
    
</g>
    
<g id="beam" stroke="black" stroke-width="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>

Elementary Transformations

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 6-13. 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%" stop-color="darkslategray"/>
     
<stop offset="100%" stop-color="white"/>
   
</linearGradient>
   
<g id="screw" stroke="black" stroke-linejoin="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 stroke-width="0.4" fill="none" stroke-linecap="round" stroke-dasharray="24 12 4 12" d="M60,100 232,100" />
     
<path stroke-width="0.4" fill="none" stroke-linecap="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 2D-space - with the screw here accidentally in it - than simple graphics elements.

Translate

The translation is an elementary displacement transformation.

Syntax:

            translate(tx, ty)
     
tx   
x-coordinate of displacement
     
ty   
y-coordinate 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 6-14. 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 x-and y-attributes 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 x-and y-attributes and the translatetransformation.

    <use xlink:href="#screw" transform="translate(100,130)" x="130" y="-160" />

 


Figure 6-15.  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

             Xlocal = tx + x

            ylocal = ty + y

 In this formulas we can exchange the addends without harm, i.e.

             Xlocal =x +  tx

            ylocal =y +  ty

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 6-16.  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 x-and y-attributes 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
x-and y-attributes.

This was also taken into account with the examples above.

Rotate

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   
x-center 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 6-17. 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 y-axis up. Since we have a coordinate system with its y-axis 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 6-18.  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).

Scale

The scaletransformation is used to change the size of an element.

Syntax:

            scale(sx [,sy])
     
sx    x-scale factor
     
sy   
y-scale factor, optional

If we omit the y-scale factor, its value will be set equal to the value of the x-scale 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 6-19. 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 6-20. 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 non-uniform scaling.

   <use xlink:href="#screw" transform="scale(1,0.5)" />

 


Figure 6-21. Scale(1,0.5) the screw

 With this we reduced the screw's dimensions in y-direction only. We can vary this of course with

   <use xlink:href="#screw" transform="scale(0.5,1)" />

 


Figure 6-22. Scale(0.5,1) the screw

by shrinking the screw in x-direction 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 6-23.  Scale(1,-1) the  screw

With that we mirrored our screw at the y-axis. Or to explain it geometrically, we changed the sign of every x-coordinate by multiplying it by -1, so that we now have negative x-values for the screw. Of course we can also mirror about the x-axis 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 x-axis and the y-axis. 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 non-uniformly using the
scaletransformation with two different argument values.
    a scale factor
sx = -1 results in mirroring at the y-axis.
    a scale factor
sy = -1 results in mirroring at the x-axis.
    a scale transformation does usually not preserve lengths.
    a uniform scale transformation preserves angles, while a non-uniform 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.

Skewing

The skewing transformation - frequently called shearing - consists of two elementary transformations skewX and skewY.

 Syntax:

            skewX(angle])
     
angle       angular displacement of y-axis

            skewY(angle])
     
angle      
angular displacement of x-axis

We want to look at the skewY transformation first.

   <use xlink:href="#screw" transform="skewY(25)" />



Figure 6-24. SkewY(25) the screw

We can interpret the skewY transformation as a rotation of the x-axis towards the positive y-axis, while leaving the y-axis' 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 x-axis.

The skewXtransformation behaves similar with regard to the y-axis.

 
Figure 6-25. SkewX(25) the screw

Here we can understand the skewX transformation as a rotation of the y-axis towards the positive x-axis, while leaving the x-axis 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 x-coordinates only, not  y-coordinates.
    the
skewYtransformation displaces y-coordinates only, x-coordinates remain unaffected.

With this we can also understand the skewX transformation as a displacement of any point in the plane in the x-direction.


This displacement depends on the y-coordinate, i.e. points with greater y-values are displaced more than points with smaller y-values. 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 y-direction 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 x-direction.
    the
skewYtransformation only preserves lengths in y-direction.
    the skewing transformation does not preserve angles.
    the
skewXtransformation's centre is the x-axis.
    the
skewYtransformation's centre is the y-axis.
    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).

Concatenation of Transforms

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 6-26. 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 6-27. Two plates to bolt together

Here is the library's code

   <defs>
   
<linearGradient  id="zylinderShade" x2="0%" y2="50%" spreadMethod="reflect">
     
<stop offset="0%" stop-color="darkslategray"/>
     
<stop offset="100%" stop-color="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" stroke-width="0.25"/>
   
</pattern>
   
<g id="screw" stroke="black" stroke-linejoin="round" >
     
<path fill="url(#zylinderShade)" d="M0,-36 -4,-40 -4,40 0,36 z 
       
                                  M-32,-36 -28,-40 -28,40 -32,36 z 
                                         
M-4,-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 stroke-width="0.4" fill="none" stroke-linecap="round" stroke-dasharray="24 12 4 12" 
                                                                    d="M-40,0 132,0" />
     
<path stroke-width="0.4" fill="none" stroke-linecap="round" d="M0,-12 124,-12 M0,12 124,12" />
   
</g>
    
<g id="nut" stroke="black" stroke-linejoin="round">
       
<path fill="url(#zylinderShade)" d="M0,-36 -4,-40 -4,40 0,36 z M-32,-36 -28,-40 -28,40 -32,36 z                                              M-4,-40 -28,-40 -28,40 -4,40 z" />
       
<path fill="gray" d="M-4,-40 -28,-40 -28,-16 -4,-16 z" />
       
<path fill="silver" d="M-4,-16 -28,-16 -28,16 -4,16 z" />
       
<path fill="gray" d="M-4,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" stroke-width="0.4" fill="none" stroke-dasharray="12 6 2 6" 
           
d="M100,90 100,150 M300,90 300,150" />
     
<path stroke="black" stroke-width="0.5" fill="none" 
           
d="M100,100 120,80 M300,100 320,80" />
     
<text x="120" y="80" text-anchor="start">(100, 100)</text>
     
<text x="320" y="80" text-anchor="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 6-28. 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 6-29. 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 6-30. 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 6-31. 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 6-32. 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 6-33. 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 6-34. 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 non-uniform scale and rotate transforms.

Just to show you the correctness of the last sentence regarding the non-uniform 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 6-35. 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="UTF-8" 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>

Nesting of Transformations

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 6-36. Two squares and one circle

<?xml version="1.0" encoding="UTF-8" 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" stroke-width="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 6-37. 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" stroke-width="2" transform="translate(300,200) scale(2) translate(-50,-50)" />

 


Figure 6-38. 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 6-39. The three objects

With that we can also generalise for instances of elements (and groups).

 <element id="e0" transform="t0" />
<use id="e1" xl
ink: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="UTF-8" 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" stroke-width="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 6-40. 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 6-41. 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 6-42. 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 self-similar fractals, you can surf the web and type "iterated function systems", "IFS" or simply "fractals" in your favourite search engine.

Transformation Matrices

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 6-43. The plates and the screws

<?xml version="1.0" encoding="UTF-8" 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 2-dimensional 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.

Translation Matrix

 The translation matrix looks like this 


where txand ty., the displacement in x- and y-direction, are exactly the same values we have to supply to the translate(tx,ty)transform.

Rotation Matrix

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.

Concatenate Transform Matrices

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 right-to-left 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 post-multiply the new transformation matrix, that is append it to the left of the previously applied transformation matrices.

Matrix Example

 Now let's practice that theoretical stuff with the small screw of our screw example.


Figure 6-44. 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 (server-side or client-side) or manipulate the transform attribute by script animation.

 

  Contact Us | E-mail Us | Site Guide | About PerfectXML | Advertise ©2004 perfectxml.com. All rights reserved. | Privacy