Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~30 min· topic 3 of 7

Topic 1.3

Paths: the Pen Commands

In one line

A path is a shape drawn by pen commands written in one d string: move, line, curve, arc, close. Every line chart is one path, and a bad number in the string stops the drawing at that point with a console error.

Think of it like this

Giving directions to someone drawing with a pen. "Put the pen down at the corner. Draw a straight line to the lamp post. Now curve towards the bakery. Lift the pen. Move to the park gate and draw to the pond." A path is exactly such a list of directions, written as short letters followed by numbers.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

path
A shape made by pen commands. Written as <path d="...">.
d attribute
The list of commands that describes the path, such as M 10 80 Q 95 10 180 80.
absolute / relative
A capital letter measures from the SVG's corner (absolute). A small letter measures from where the pen is now (relative).
control point
A point that pulls a curve towards it. The curve does not touch it.
flag
A setting that is only 0 or 1. Arcs have two: large-arc and sweep.
subpath
One continuous piece of a path. Every M starts a new one.
NaN
"Not a Number": what a failed calculation gives, such as "n/a" / 3992. Writing it into d is an error.
length (of a path)
How far the pen travels along the outline. getTotalLength() asks the browser for it, in user units.

Step by step

01Put the pen down and draw a line

Every path starts with M. Then L draws a straight line to the next point. The picture on the right is a single <path> with two commands, written M 20 100 L 100 20.

step 1whole filehtml
<path d="M 20 100 L 100 20" fill="none" stroke="black"></path>

02Add a second line: a corner

Each new L continues from where the last command ended. One more point and the line has a corner. A polyline would do the same so far.

step 2whole filehtml
<path d="M 20 100 L 100 20 L 180 100" fill="none" stroke="black"></path>

03Bend it with a curve

Now add Q 230 140 280 100. The curve starts where the tent ended, at (180, 100), and ends at (280, 100). It is pulled towards the control point (230, 140), shown as a hollow circle. The curve dips down but does not reach it.

step 3whole filehtml
<path d="M 20 100 L 100 20 L 180 100 Q 230 140 280 100" fill="none" stroke="black"></path>

04Close it, and fill it

Z draws a straight line back to the start, closing the shape, so the inside can be filled. Without Z, a fill still happens (as in Topic 1.2) but the stroke has no closing edge. The new last edge runs from (280, 100) straight back to (20, 100).

step 4whole filehtml
<path d="M 20 100 L 100 20 L 180 100 Q 230 140 280 100 Z" fill="#8ab17d" stroke="black"></path>

05Read the commands: straight, relative, closed

The first example below draws four paths and measures them. The closed triangle has length 305.9 (three sides). The same triangle without Z is 205.9 long, one side less. A rectangle from M 250 30 H 320 V 120 H 250 Z has length 320.

The fourth shape is the first triangle written with small letters: m 340 120 l 50 -90 l 50 90 z. Its box is the same size, 100 by 90. Relative paths are easy to move: change only the first m.

06Pick one of four arcs

The second example draws curves and arcs. Between the same two points, with radius 50, the four combinations of large-arc and sweep give four different arcs: small and large, each bulging up or down. The Console prints each arc's bounding box.

Pick one of four arcsdiagram
Rendering diagram…

07Turn data into a path, and see it break

The third example turns the week's rides into a d string: M for the first point, L for each next one. With Thursday missing, the pen lifts: the string has two Ms and the line has a gap. With "n/a" on Wednesday, the string contains NaN, the browser prints the error, and the line is drawn only up to Tuesday.

Try it yourself

  1. 1

    Pull the curve harder

    In the path editor, drag the control point of the curve higher, or in the second example change the first path to M 10 120 Q 100 -60 190 120. The curve rises more but still does not reach the control point: it only gets about halfway there. Notice that the control point can sit outside the picture.

  2. 2

    Close the open triangle

    In the first example, add Z to the end of the d of #open. The Console then says Closed with Z: 305.9 | open, no Z: 305.9, because both are the same shape now. The length of an open path grows by one side when you close it.

  3. 3

    Lift the pen

    In the third example, change null back to 3742 in the gap line: the gap disappears and the Console says 1 piece(s). Then change Saturday's 3013 to null instead: the Console says 2 piece(s) (Monday to Friday, then Sunday). Put Thursday's null back as well and it says 3, because each separate gap starts a new piece.

Code & diagrams

Pen commands: lines, closing, relative New tab

The numbers are measured by the browser (getTotalLength and getBBox), not calculated by hand.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Closed with Z: 305.9 | open, no Z: 205.9
Absolute and relative triangle, box: 100 x 90 and 100 x 90
Rectangle from H and V, length: 320.0
Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "L 50 50 L 90 10".
A path that starts with L has length 0
Curves and the four arcs New tab

The four coloured arcs start at the same point and end at the same point. The grey dashed lines show where the control points pull.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

large=0 sweep=0: top 250, bottom 270, width 80
large=0 sweep=1: top 230, bottom 250, width 80
large=1 sweep=0: top 250, bottom 330, width 100
large=1 sweep=1: top 170, bottom 250, width 100
Radius 10 between points 80 apart still draws: width 80, height 40
A line chart is one path, with a gap and with NaN New tab

The last two lines show the failure: the string contains NaN, the browser prints its error, and the chart is drawn only as far as Tuesday.

Preview

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

Full week: M5,33L65,10L125,15L185,16L245,18L305,32L365,22
Thursday missing: 2 piece(s)
With n/a: M5,33L65,10L125,NaNL185,16L245,18L305,32L365,22
Error: <path> attribute d: Expected number, "M5,33L65,10L125,NaNL185,16L245,1…".

Try it: interactive

Drag the points, or edit the path

Drag the points to see how the d text changes, then type in the d text and watch the points move. Try changing the Q to a C.

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Divide by a maximum of zero

In the third example, change const most = 3992; to const most = 0; (as if the data had no busy day yet).

terminal
$ Console
── what you'll see ──
Full week: M5,-InfinityL65,-InfinityL125,-InfinityL185,-InfinityL245,-InfinityL305,-InfinityL365,-Infinity
Error: <path> attribute d: Expected number, "M5,-InfinityL65,-Inf…".
Thursday missing: 2 piece(s)
Error: <path> attribute d: Expected number, "M5,-InfinityL65,-Inf…".
With n/a: M5,-InfinityL65,-InfinityL125,NaNL185,-InfinityL245,-InfinityL305,-InfinityL365,-Infinity
Error: <path> attribute d: Expected number, "M5,-InfinityL65,-Inf…".

Break #2

Start a path with L instead of M

In the third example, change let penDown = false; in pathFor to let penDown = true;.

terminal
$ Preview and Console
── what you'll see ──
Full week: L5,33L65,10L125,15L185,16L245,18L305,32L365,22
Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "L5,33L65,10L125,…".
Thursday missing: 1 piece(s)
Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "L5,33L65,10L125,…".
With n/a: L5,33L65,10L125,NaNL185,16L245,18L305,32L365,22
Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "L5,33L65,10L125,…".
All three charts are empty. Even the NaN chart reports the missing M first: the browser stops at the first problem it finds.

Myth vs fact

Myth

A curve passes through its control points.

Fact

It only leans towards them. In the second example the control point of Q sits at y = 10 and the curve's top is at y = 65.

Myth

Paths are only for fancy shapes and icons.

Fact

Every line chart, area chart and map outline is one path. D3 builds the d string for you, but it is the same string you write by hand here.

Myth

If the data has a bad value, the whole line disappears.

Fact

The line is drawn up to the bad value and stops. A chart that ends early is a sign to look for NaN in the path string.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Numbers in d can be separated by spaces or commas, and a minus sign or a second decimal point also separates them: M10 10L50 50.5-20 10 is valid and means M 10 10 L 50 50.5 L -20 10. Minified path strings from design tools look like this. A minus sign is why L50-20 is two numbers.

  • ▸

    D3 7's line() rounds numbers to 3 decimals by default, which keeps strings short: it turns 0.123456789 into 0.123 and 10.55555 into 10.556. Hundreds of points then cost only a few kilobytes of attribute text. d3.line().digits(n) changes the precision.

  • ▸

    getTotalLength() and getPointAtLength() give a path's length and any point along it. They power the "line drawing itself" animation (Phase 9) and are also a cheap way to find where a given x lies on a curve. They need the path to be in the document and rendered.

  • ▸

    A single path with 100,000 points is one DOM node, so it is far cheaper than 100,000 circles, but every frame the browser still re-rasterises the whole outline. For very long lines, simplify the points or move to Canvas (Phase 11, 12).

Remember this

  1. 1

    The problem this solves: rectangles and circles cannot make a line chart with 365 points, a curve, or a custom icon. A polyline can follow many points but cannot bend or have gaps. A path can do all of these, in one element, with one d attribute. Every chart line, area and map outline in this course is a path.

  2. 2

    **The d attribute is a list of commands.** Each command is a letter followed by numbers. M x y (move to) lifts the pen and puts it down at a point. L x y draws a straight line to a point. H x draws a horizontal line to that x, and V y a vertical line to that y. Z closes the shape with a straight line back to where it started. A path must begin with M: if it does not, the browser draws nothing and prints Error: <path> attribute d: Expected moveto path command ('M' or 'm'), "L 50 50 L 90 10".

  3. 3

    Capital letters are absolute, small letters are relative. L 60 20 goes to the point (60, 20). l 60 20 goes 60 across and 20 down from wherever the pen is now. Relative commands let you describe a shape once and place it anywhere by changing only the first m. The first example draws the same triangle both ways, and both have the same box and the same length.

  4. 4

    Curves bend towards control points. Q cx cy x y draws a curve to (x, y), pulled towards one control point (cx, cy). C c1x c1y c2x c2y x y uses two control points, one for each end. The curve never passes through its control points; it leans towards them, like a rubber band. A larger distance to the control point means a stronger pull. The path editor below lets you drag them.

  5. 5

    Arcs take two flags. A rx ry rotation large-arc sweep x y draws a piece of an ellipse from the current point to (x, y). Between two points there are four such arcs. large-arc (0 or 1) picks the longer or the shorter one, and sweep (0 or 1) picks which side it bulges to (1 is clockwise). If the radii are too small to reach the end point, the browser scales them up until it can, so an arc never fails. Rotation is usually 0.

  6. 6

    A line chart is a path. Turn each data point into x and y, write M for the first and L for each next point. D3 writes exactly this, with no spaces and numbers rounded to 3 decimals: M0,1L2.5,3. A missing value is a gap: lift the pen (a new M) instead of drawing across it. Topic 3.4 and Phase 7 build this for 365 points.

  7. 7

    A bad number stops the path. If a calculation puts NaN into the string, the browser prints Error: <path> attribute d: Expected number, "…" and draws the path up to the last good command. The chart does not disappear; it ends early. This is why a line that stops partway is a hint to look for NaN. D3's line() writes the bad value into the string as it is (LNaN,3), so the error appears too. The quoted text is a window of about 32 characters around the bad spot, with … where the string was cut.

  8. 8

    Other mistakes, other messages. The browser names the problem. A lone X gives Expected path command, "M 1 1 L 5 5 X 3". A string that stops in the middle of a command gives Unexpected end of attribute. Expected number, "M 0 0 L 10 20 Q". An arc flag other than 0 or 1 gives Expected arc flag ('0' or '1'), "M 0 0 A 5 5 0 2 1 10 10". The shape is drawn up to the last good command each time.

Explain it without notes

01

What is the difference between L 60 20 and l 60 20?

02

Why does the browser print nothing when a path starts with L?

03

What do the two flags of an arc decide?

04

What happens to a chart line when one value is NaN?

Practice

01

Write a d string for a house outline: start at (20, 80), go to (20, 40), then (50, 10), then (80, 40), then (80, 80), then close. Use only M, L and Z.

02

Write a function pathFor(points) where points is a list of [x, y] pairs and returns the d string with M then L commands. What should it return for an empty list, and what would d="" draw?

Trade-offs

  • ↔

    A path is more flexible than a polyline (curves, gaps, arcs) but its string is harder to read by hand. Use a polyline for a few straight segments, a path for everything that bends or breaks.

  • ↔

    Absolute commands are easy to check against a grid. Relative commands make shapes easy to move and repeat. Hand-written icons often mix both.

Done when you can

  • I can read a d string: M, L, H, V, Z, Q, C and A.

  • I know what capital and small letters mean.

  • I can turn a list of points into a path string, with a gap for a missing value.

  • I recognise the NaN error and know the line is drawn only up to it.

  • I know a path that does not start with M draws nothing and prints no error.