Sharing a small page I cooked up to help people explore transformations - both the linear and the non-linear kind by drawing pictures and modifying them using transformations.

This hack is in the spirit of the theme of this year's Infinity festival at Pramati - where we have "Engaging the senses" as a theme.  So, engage your senses to grasp the math of transformations.

For the impatient, here is the web page. However, you can also try out a few examples right here.

WARNING: Don't write production code like the HTML+JS in that page. It is > just a quick hack for illustrative purposes.

You'll see a canvas area and a matrix display at the bottom right. Open up the dev console and you can start putting in your own drawing by setting the global draw() function and using some provided drawing primitives like - line, point, circle, grid and curve. All changes update live.

To dive in ...

Yoooos the soource Luuuke!


A basic one first -

draw = () => { line(P(10,10),P(50,50)); }
tx = Scale(2)

A more fun non-linear one -

tx = (p) => P(p.x + 0.003 * Math.cos(Math.PI*t) * p.y * p.y,
              p.y + 0.003 * Math.sin(Math.PI*t) * p.x * p.x)
draw = () => {
    circle(P(0,0), 140);


A "transformation" is modelled as a function that maps a given point to another point. You can write your own functions and use them. The inverse transformation of built-in transformations can be accessed with the .inverse property.

  • To create a point, use P(x,y). If p is a variable holding a point, then p.x and p.y are x and y coordinates respectively.


draw is a zero-argument function that you can set to draw whatever figure you want to .. well, at least ones using the offered primitives.

tx is a global variable holding the transformation to be applied to your drawing.

In the JS console, just do draw = () => { ... } to set the drawing function.

Also, you can just do tx = Scale(10), for example, to scale your drawing by a factor of 10.

Drawing primitives

  • line(P(x1,y1),P(x2,y2)) draws a line from $(x_1,y_1)$ to $(x_2,y_2)$
  • circle(P(cx,cy),r) draws a circle centered at $(x,y)$ with radius $r$.
  • point(x,y) draws a point at $(x,y)$.
  • grid(dx,dy) draws a grid with spacing given by $dx$ and $dy$.
  • lattice(xmin,ymin,xmax,ymax,func) calls func over a lattice of points spanning $(x_{min},y_{min})-(x_{max},y_{max})$.
  • You can set the pen color for the primitives that follow by setting pen.color = 'blue'
  • You can set the pen thickness using pen.thickness = 5.

Built-in transformations

  • Linear(mxx,mxy,myx,myy) is a 2D matrix linear transformation.
    Linear(mxx,mxy,myx,myy).inverse will be the inverse transformation of the
    first one.
  • Translate(dx,dy) shifts the drawing by $(dx,dy)$.
  • Rotate(Deg(degrees)) or Rotate(radians) is a rotation about the origin -
    positive angle = anti-clockwise.
  • Scale(s) scales by the factor $s$.
  • mul(T1,T2,...) creates the product of the transformations as though they
    were applied back to front on a point. The product transform's inverse is
    also computed.


You can create dynamic parameters using Param like below -

angle = Param("angle", Param.linear(-Math.PI, Math.PI))
tx = Rotate(angle)
draw = () => { line(P(10,10),P(50,50)); }

You'll see a slider pop up at the top right part of the window, which you can use to dynamically rotate the figure.


You can create simple animations by writing your draw() function to make use of the variable t which gives the current time in seconds relative to when the page started. You can also create your own custom transform function which depends on t to make the global transformation time dependent.

Some of the primitive drawing functions have additional parameters set as properties.  For example, line.divisions = 10 will change the number of divisions used to draw a line. Why should a line be broken up into smaller line segments? That will become clear once you start playing around with non-linear transformations.

Have fun!

This is the stairway to go up at the lighthouse in Pointcabrillo at the South of California.
Photo by Benjamin Lizardo / Unsplash