Skip to content


Lesson 1 - Basic 2D Shapes

In this lesson, we are going to understand the basic 2D shape as graphic element, as well as how to draw them with HTML5 and PureScript.

Why basic shapes?

Well, there are two reasons: First, when we are still children learning how to draw, drawing the basic shapes is first step; Second, it requires little math knowledge, but still an excellent example of how to apply math in engineering task.

We are going to talk about 3 basic shapes:

  • Rectangle
  • Triangle
  • Circle


In 2d world, we can use a vector from a given point (reference point) to represent the position of any point. But for simplicity, we define coordinate to be a tuple of real numbers, and assume the origin to be $(0, 0)$, so a point can be represented in form of $(x, y)$. Note that the vector form is equivalent to the coordinate form.

So next is line, or more exactly, segment. One segment has two endpoints. And reversely, any two different points can detemine a segment.

Next is rectangle. One rectangle has four segments, or four endpoints. And reversely, any four points or four segments, when satisfying some properties, can make up a rectangle. For example, given different points $p1, p2, p3, p4$, consider vectors formed $v_{12}, v_{23},v_{34},v_{41}$. Apparently, only when $v_{12} \cdot v_{41} = v_{12} \cdot v_{23} = 0$, they form a rectangle.

or, $(x_2 - x_1)(x_4 - x_1) + (y_2 - y_1)(y_4 - y_1) = (x_2 - x_1)(x_4 - x_1) + (x_3 - x_2)(y_3 - y_2) = 0$

Next is triangle. One triangle defines three segments or three points.So reversely, any three different points can make up one triangle.

Next is circle. Circle should be defined in term of "set of points". That is different from segments. The set can be infinitely large due to the real number's property. But definition can be concise. Thus, circle is ${(x, y) \,\vert\, (x - x_0)^2 + (y - y_0)^2 = r^2 }$. So there comes another more succinct representation by its center $(x_0, y_0)$ and radius $r$.



Yes, we will not use JavaScript. PureScript is more "pure" as a functional language which can be compiled to JavaScript. PS's syntax is cleaner which can make us focus on the problem; PS's functional features are powerful which can solve problems in a amazingly simple way; PS's type system is safer so we don't have to waste time debugging non-sense.

Let's hack

Phil Freeman, the author of PureScript, wrote a book about it. But if it is too lengthy, you just need to follow me.

First, we need to get npm. For OS X, it is simply brew install npm. After that, we can install the compiler et cetera by npm install -g bower purescript pulp.

Then we can enter the code directory lesson1 and pulp dep install and wait for it to finish. After that, we will compile the source to js, with pulp browserify -O -t index.js. Then, open the index.html and see the amazement.

That is the typical workflow.

Let's see what is inside src/Main.purs.

module Main where

import Prelude
import Control.Monad.Eff.Console
import Graphics.Canvas (getCanvasElementById, getContext2D)
import Graphics.Canvas.Free
import Math hiding (log)
import Data.Maybe

main = do
    mcanvas <- getCanvasElementById "canvas" -- Maybe Canvas
    case mcanvas of
        Nothing -> log "failed to load canvas element" -- console.log
        Just canvas -> do
            context <- getContext2D canvas -- get the context

            runGraphics context $ do -- draw within the context
                setFillStyle "#00FFFF" -- set the fill-in color
                rect { x: 0.0, y: 0.0, w: 40.0, h: 60.0 } -- put a rectangle on screen
                fill -- fill it

                setStrokeStyle "#000000" -- set the stroke, or line color
                at 100.0 100.0 $ triangle -- draw a triangle at certain place

                setFillStyle "#FFFF00" -- change color
                at 150.0 150.0 $ circle 50.0 -- draw circle

triangle = do
    beginPath -- begin track a path of segments
    moveTo 0.0 (-100.0) -- start point
    lineTo 75.0 100.0 -- first segment
    lineTo (-75.0) 100.0 -- second segment
    closePath -- close up
    stroke -- draw the path

circle size = do
  arc { x: 0.0, y: 0.0, r: size, start: 0.0, end: Math.pi * 2.0 }
  -- start and end are in unit radian

at x y gfx = do
  save -- save current context (including position of pen point)
  translate x y -- moving
  gfx -- do something
  restore -- restore the context

The details of code is self-documented. But we need to cover some high-level points here, they reflect the programming model of 2D drawing with canvas API.

Canvas Model

Yes, image a canvas. You have a pen. You also have some models, like rectangles. But to draw arbitrary polygon, you have to draw line by line, without lifting the pen. And you can change color in the color box.

And two important things: "fill" and "stroke". Fill draws a solid shape by filling the path's content area. Stroke draws the shape by stroking its outline.

And due to the power of computer, we can save and restore contexts! It is due to the fact that, we just want to do something temporally.