Topic 0.5
Your First Picture: a Smiley in SVG and in Canvas
In one line
Draw a smiling face twice: once with SVG tags (a circle, two eyes and a curved path) and once with Canvas commands. Along the way you meet the coordinate grid, fill and stroke, and the mistakes everyone makes first.
Think of it like this
Drawing on squared paper. A teacher says "put a dot 70 squares from the left and 80 down". Everyone's dot lands in the same place, because everyone counts from the same corner. SVG and Canvas work exactly like that: every shape is placed by counting from the top-left corner.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- coordinate
- A pair of numbers (x, y) that says where a point is: x across from the left, y down from the top.
- radius
- The distance from the centre of a circle to its edge. The circle is twice the radius wide.
- fill
- The colour that paints the inside of a shape.
- stroke
- The colour that paints the outline of a shape. Its thickness is the stroke width.
- path
- An outline made of commands (move, line, curve) that can be filled or stroked. Line charts are paths.
- control point
- A point that a curve bends towards without touching it. Moving it changes the curve's shape.
- radian
- The unit Canvas uses for angles. A full circle is 2π radians, about 6.28; half a circle is π.
Step by step
01The face: one circle
Start with the face: a yellow circle centred at (100, 100) with radius 80, inside an SVG 200 × 200. It reaches from x = 20 to x = 180, so it fits with a margin. The outline is 4 pixels of dark grey.
02Add two eyes
Two small dark circles at (70, 80) and (130, 80). They are written after the face, so they are painted on top of it. Swap the order and the face would cover them.
Both have y = 80, which is 20 above the centre: remember, smaller y means higher up.
03Add the smile
The mouth is a <path>: start at (60, 120), bend towards the control point (100, 160), end at (140, 120). The control point is below the ends, so the curve sags downwards into a smile.
fill="none" matters: a path is a shape, and without it the browser fills the area between the curve and the straight line joining its ends, in black (the "Break it" below shows it).
04Paint the same face on a Canvas
The second example draws the same face with commands, in the same order: the face (arc, fill, stroke), the eyes (a loop over the two x positions), the mouth (moveTo + quadraticCurveTo + stroke).
Each shape starts with beginPath(), so each fill() or stroke() paints only that shape.
05Meet the canvas size trap
The third example draws one circle on two canvases. The first has width="90" height="90" attributes: its drawing grid is 90 × 90. The second is sized only with CSS: its drawing grid stays at the default 300 × 150 and is squeezed into 90 × 90 on screen, so the circle comes out as a thin, tall oval.
Canvas has two sizes: the drawing grid (the width/height attributes) and the size on screen (CSS). When they differ, the drawing is stretched. Topic 11.4 uses this on purpose to make canvases sharp on high-resolution screens.
Try it yourself
- 1
Turn the smile into a frown
In the first example, change the mouth to
d="M 60 120 Q 100 60 140 120". The control point moves above the ends, so the curve bulges upwards: a frown. Same command, one number changed. - 2
Move an eye off the face
Change the right eye to
cx="190". It now floats outside the face (the face ends at x = 180, plus half its stroke) and touches the SVG's right edge: 190 + 10 = 200. Change it tocx="230": the eye is now outside the 200-wide SVG and disappears, with no error. - 3
Forget a beginPath
In the second example, delete the
ctx.beginPath();line just before the mouth. The right eye grows larger than the left one: the last eye's circle was still in the current path, so the mouth'sstroke()outlined it too, adding a 6-pixel dark outline around it. No error is printed.
Code & diagrams
Preview
Console
Nothing logged yet.Expected console output
Shapes in the smiley: 4
circle
circle
circle
pathrole and aria-label give a screen reader one sentence for the whole canvas, because it can't see the shapes inside (Topic 15.1).
Preview
Console
Nothing logged yet.Expected console output
Done. Elements inside the canvas: 0Neither mistake prints an error. Look at the preview: a black circle, a correct one, and a squashed oval.
Preview
Console
Nothing logged yet.Expected console output
SVG circle fill: rgb(0, 0, 0)
right: drawing grid 90x90 shown at 90x90
css-only: drawing grid 300x150 shown at 90x90Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Leave out fill="none" on the mouth
In the first example, delete fill="none" from the <path>.
Myth vs fact
Myth
y = 0 is at the bottom, like in maths class.
Fact
In SVG and Canvas, y = 0 is the top and y grows downwards. Charts flip it so that bigger values go up (Topic 3.2).
Myth
Setting a canvas's size in CSS is the same as setting its width and height.
Fact
CSS only changes the size on screen. The drawing grid stays at its attribute size (300 × 150 by default) and is stretched to fit, which distorts and blurs what you draw.
Myth
A curve passes through its control point.
Fact
It only bends towards it. The quadratic curve from (60, 120) to (140, 120) with control point (100, 160) reaches only y = 140 at its lowest, halfway to the control point.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
A quadratic Bézier from P0 to P2 with control P1 is B(t) = (1−t)²·P0 + 2(1−t)t·P1 + t²·P2. At t = 0.5 this is (P0 + 2·P1 + P2) ÷ 4, so for the smile the lowest point is (60 + 200 + 140) ÷ 4 = 100 across and (120 + 320 + 120) ÷ 4 = 140 down: halfway between the ends' y (120) and the control's y (160).
- ▸
SVG shapes paint in document order (the painter's model); there is no
z-indexfor SVG elements. To bring a mark to the front on hover, you move the element to the end of its parent (D3'sselection.raise()does exactly that). - ▸
A canvas 2D context keeps one current path until
beginPath().fill()andstroke()don't clear it. In a redraw loop that never callsbeginPath(), the path grows every frame and eachstroke()repaints everything drawn so far: a classic memory and speed leak (Topic 11.2).
Remember this
- 1
The problem this solves: every chart is made of simple shapes placed at exact coordinates. Before you can place 365 points from data, you need to place four shapes by hand and know why each one ends up where it does.
- 2
The grid starts at the top-left and y grows downwards. The point (0, 0) is the top-left corner. x grows to the right, like on a ruler. y grows down, unlike the graphs at school. So the eyes at y = 80 are above the mouth at y = 120. Forgetting this is the most common reason a first chart comes out upside down (Topic 3.2 fixes it for good).
- 3
A circle needs a centre and a radius. In SVG:
<circle cx="100" cy="100" r="80">: centre x, centre y, radius. In Canvas:ctx.arc(100, 100, 80, 0, Math.PI * 2): the same centre and radius, plus the angles to draw from and to. A full circle goes from angle 0 to 2π (Math.PI * 2) because Canvas measures angles in radians, where a full turn is 2π (about 6.28). - 4
Fill and stroke. Fill paints the inside of a shape; stroke paints its outline,
stroke-widthpixels thick. In SVG they are attributes (fill="#f4d35e",stroke="#333"). In Canvas you setctx.fillStyleorctx.strokeStylefirst, then callfill()orstroke(). SVG's default fill is black and default stroke is none, so a shape you forget to colour comes out black, and a line with no stroke is invisible. - 5
The mouth is a curve with a control point.
M 60 120 Q 100 160 140 120means: Move the pen to (60, 120); draw a Quadratic curve towards the control point (100, 160), ending at (140, 120). The curve doesn't touch the control point; it bends towards it, like a rope pulled by a hook. Move the control point up to y = 60 and the smile becomes a frown. Canvas has the same curve:moveTo(60, 120)thenquadraticCurveTo(100, 160, 140, 120). Topic 1.3 covers every path command. - 6
Canvas draws in order, with a "current path".
beginPath()starts a new outline,arc()andquadraticCurveTo()add to it, andfill()orstroke()paints everything in the current path. If you forgetbeginPath(), the old shapes are still in the path and get painted again. Shapes painted later cover earlier ones in both SVG and Canvas: draw the face before the eyes.
Explain it without notes
Where is the point (0, 0), and which way does y grow? Why does it matter for charts?
What is the difference between fill and stroke? What happens if you set neither on an SVG circle?
What does the Q in M 60 120 Q 100 160 140 120 do, and how would you make the smile deeper?
Why did the CSS-only canvas draw an oval?
Practice
Add a nose to the SVG smiley: a small circle (radius 6) halfway between the eyes and the mouth, in the middle of the face.
Make the Canvas smiley wink: replace the right eye with a short curved line instead of a dot.
Trade-offs
- ↔
Writing SVG by hand is clear for a few shapes, but placing hundreds needs code. That is why Topic 3.3 creates shapes in a loop and Phase 5 hands the loop to D3.
Done when you can
I can place a circle at a given centre and radius in SVG and in Canvas.
I can explain why y grows downwards and what that means for charts.
I can draw a curve with M and Q and change its shape with the control point.
I know the three silent mistakes: default black fill, a missing beginPath, and a canvas sized only in CSS.