The drawing context has a transformation matrix associated with it and every pair of co-ordinates is multiplied by this matrix before drawing occurs.
When the context is created the matrix is set to the identity which means you are drawing using the default pixel co-ordinates. However, there are a set of methods that can be used to set the transform to anything you like.
A general transformation takes the form:
x'= ax + cy + e y'= bx + dy + f
The values of a,c,b, d specify a rotation, a scaling or a skew depending on their values. The values e and f specify a shift of the origin to the new location e,f.
This is all you need to know but to understand the way that these transformations are presented is it worth knowing about homogeneous co-ordinates.
The transformation can be written in matrix form as:
where in terms of the previous tranformation values we have:
A = (a c) (b d)
p'=(x') p=(x) t=(e) (y') (y) (f)
Notice that the rotation/scale/skew part of the transformation can be written as a matrix multiplication, but the translation is an untidy part that we have to add.
The whole transformation can be written as a matrix multiplication if we add an extra dummy dimension, set to 1, that we simply ignore when actually drawing.
That is the transformation can be written:
T = (a c e) (b d f) (0 0 1)
p'=(x') p=(x) (y') (y) (1 ) (1)
So now you know that homogenous co-ordinates are just a trick that let us treat translation, along with rotation etc, as part of a matrix multiplication.
This is how the Canvas transformation works - you specify a 3x3 matrix in homogenous co-ordinates - which is used to multiple the co-ordinates you specify before any drawing operation.
Now to return to the details of the programming. We have a method:
which sets the transformation to the matrix specified and a method
which multiplies the existing transformation matrix by the one specified.
Setting the transformation in this general way is powerful but also a bit abstract and difficult.
To make things easier we also have:
scale(x,y) which applies a scaling in the x and y direction to the transformation matrix
rotate(angle) which applies a rotation angle in the clockwise direction; the angle is measured in radians
translate(x,y) which performs a translation by x,y
Active and passive view
The transformation operations are all very easy but it is worth recalling that transformations have two completely different interpretations and uses.
The first is often called the active view which is that the transformation moves and modifies what is being drawn.
The second is often called the passive view and corresponds to thinking of the transformation as a change in the co-ordinate system.
Both views are useful when programming - lets take a look at an application of each in turn.
The active view of transformation uses them to draw an graphics object at another location.
For example to draw a rectangle at a different angle we could use:
This produces a reasonable sine wave but a much cleaner method of plotting graphs is to make use of the transformation to provide the co-ordinates that we would really like to use.
We would like a co-ordinate system that varied between 0 and 12 in the x direction and +1 and -1 in the y direction. If we assume that the canvas is 400x400 then the change in the co-ordinate system can be created using:
First we scale so that the x axis runs from 0 to 12 and the y axis from 0 to 2. Then we translate so the the y axis runs from -1 to 1.
The only complication is that that the transformation affects every measured aspect of the drawing, including line width. So we have to reduce the line width back to roughly one pixel in expressed in the new co-ordinate system.
In general to create a co-ordinate system that runs from xmin to xmax and ymin to ymax with a canvas of size width by height you need to first perform a scaling :