Topic 7.3
Arcs, Pies and Donuts
In one line
d3.pie turns numbers into angles and d3.arc turns angles into path text, which makes pies and donuts. They show a part of one whole well for two to five slices, and badly beyond that: the honest alternative is a sorted bar chart.
Think of it like this
Cutting a pizza for friends. With two or three slices, anyone sees who got the bigger piece. Cut it into 25 thin slivers and nobody can tell a sliver of 13.87 degrees from one of 13.68. You would line the slivers up in a row to compare them. That row is a bar chart.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- radian
- A way of measuring angles. A full circle is 2π radians (about 6.283), a half is π, a quarter is π/2.
- slice
- One piece of a pie: an object with a start angle, an end angle and your data item.
- donut
- A pie with a hole in the middle, made by an inner radius above zero.
- centroid
- The middle point of a slice, where its label is usually placed.
- padAngle
- A small gap, in radians, kept between neighbouring slices.
Step by step
01See what pie and arc return
The first example prints the angles for four values. With 50, 0, 30 and 20, the large slice spans 0 to 180 degrees, the 30 spans 180 to 288 and the 20 spans 288 to 360. The zero is placed last: its start and end are both 360.
d3.pie().sort(null) keeps the input order, so the zero slice stays second and sits at 180 to 180. The index field is now the position in your array.
02Read the arc strings
A quarter pie is M0,-80A80,80,0,0,1,80,0L0,0Z: go to the top, draw an arc to the right, line to the centre, close. A full circle needs two arcs. An angle of NaN writes NaN into the text and the browser rejects it.
03Build a donut with labels
The donut shows the six districts: Harbour 27%, Old Town 25%, University 16%, Riverside 15%, Hillcrest 12% and Westpark 5%. Labels sit on a second, invisible arc a little further out, and the text is anchored to the start or the end depending on which side of the circle the slice is on.
The total (934k rides) in the hole answers the question "a share of what?".
04Give it 25 slices
The second example draws the 25 stations twice: as a pie, and as bars sorted from the biggest. In the pie, 18 slices are wide enough for a label and 7 are not. S25, the smallest, spans 4.0 degrees.
S12 and S17 differ by 477 rides, which is 0.2 degrees of angle in the pie (13.68 and 13.87) and 2 pixels in the bars. The bars put the stations on one scale and in order, so the ranking is readable.
Try it yourself
- 1
Keep the order
In the first example, remove
.sort(null)from the donut'sd3.pie(). Nothing changes here because the data is already largest first. Now swap two rows ofdistrictsand run again: withoutsort(null)the pie reorders them by size, so the slices no longer follow the order of your table (the labels still name the right slices, because each slice carries its own data). - 2
Make a pie from a donut
Change
innerRadius(45)toinnerRadius(0). The hole disappears and the934k ridestext sits on top of the slices. A donut leaves room for a total or a title in the middle.
Code & diagrams
The six totals are copied from `station-month.csv` (summed by district). The slices keep their input order (`sort(null)`), which here is already largest first.
Preview
Console
Nothing logged yet.Expected console output
default sort a #0 0.0-180.0 | b #3 360.0-360.0 | c #1 180.0-288.0 | d #2 288.0-360.0
sort(null) a #0 0.0-180.0 | b #1 180.0-180.0 | c #2 180.0-288.0 | d #3 288.0-360.0
negative value 0.0-200.0 | 360.0-360.0 | 200.0-360.0
empty array []
all zero 0.0 | 0.0
one slice 0.0-360.0
padAngle(0.05) 0.0-224.3 | 224.3-360.0
quarter pie M0,-80A80,80,0,0,1,80,0L0,0Z
donut quarter M0,-80A80,80,0,0,1,80,0L40,0A40,40,0,0,0,0,-40Z
full circle M0,-80A80,80,0,1,1,0,80A80,80,0,1,1,0,-80Z
NaN angle M0,-80LNaN,NaNZ
slices: 6 | total: 933651Same numbers, two charts. The pie needs a label rule (only slices over 12 degrees) and still leaves the small ones unnamed. The bars label every station.
Preview
Console
Nothing logged yet.Expected console output
slices: 25 labelled: 18
S01: 22.6 degrees S25: 4.0 degrees
slices narrower than 10 degrees: 5
S17 vs S12 rides: 35963 vs 35486 | bar width in pixels: 147.33 vs 145.37
S17 vs S12 angle: 13.87 vs 13.68 degreesBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
A number written as text with a comma
In the first example change ["Westpark", 45543] to ["Westpark", "45,543"].
Myth vs fact
Myth
A pie chart is fine for any part-of-a-whole data.
Fact
It is fine for two to five slices that differ clearly. With 25 stations, five slices are under 10 degrees and two stations that differ by 477 rides look identical.
Myth
Donuts are more accurate than pies because they look modern.
Fact
A donut shows the same angles with less area, so it is not more accurate. Its advantage is the hole, where you can put the total.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
d3.piestorespadAngleon each slice object. The pie first sets aside one gap per slice, shares the rest of the circle by value, and then each slice'sendAngleincludes its own gap, whichd3.arctrims off when it draws. Withd3.pie().padAngle(0.05)the printed angles of 50 and 30 are 0 to 224.3 and 224.3 to 360 degrees, not 225 and 135 as without a pad. - ▸
d3.arcis also used for gauges, progress rings and radial bar charts. You can write your own{ startAngle, endAngle }objects, which is why a progress ring is one arc, not a pie.
Remember this
- 1
The problem this solves: Priya asks what share of all rides each district carries. That is a part-of-a-whole question, and a donut answers it at a glance when there are few parts. This topic shows how to build one and where it stops working.
- 2
Two generators work together.
d3.pie()does the maths: it reads your values and returns one object per item withstartAngleandendAnglein radians (a full circle is 2π, which is 360 degrees).d3.arc()does the drawing: it takes one such object and returns thedtext of a slice. Neither touches the page. - 3
The pie sorts by default.
d3.pie()orders the slices from the largest to the smallest, and each object'sindexis its rank, not its position in your array. The first example showsb #3for a zero slice. If your data has a natural order (months, ages), call.sort(null)to keep it. - 4
A zero, negative or missing value is a slice of no width.
d3.pietreats values that are not above zero as zero, so the slice hasstartAngleequal toendAngle. No error appears, which is dangerous with text:"45,543"isNaNas a number, the slice silently vanishes, and the label saysNaN%(see Break it). An empty array returns[]. When every value is zero, all slices have no width (the chart is blank, not an error). A single slice is a full circle,0to360degrees. - 5
**
d3.archas the shape knobs.**innerRadius(0)makes a pie andinnerRadius(45)makes a donut.outerRadiussets the size,padAngleleaves a gap between slices,cornerRadiusrounds the corners.arc.centroid(d)returns the middle of a slice as[x, y], which is where a label goes. The origin of an arc is its centre, so move the group withtranslateor use aviewBoxcentred on zero. - 6
Why pies stop working. The eye judges angles and areas much worse than lengths (Topic 2.2). With six districts, a donut with percentages works. With 25 stations, the second example shows what happens: five slices are narrower than 10 degrees and cannot hold a label, and two stations that differ by 1.3% look the same. Sorted bars show the same numbers on one scale.
Explain it without notes
What do d3.pie and d3.arc each do, and why are there two?
Why does d3.pie give the zero slice index 3 in the first example?
Why is a pie of 25 stations a poor chart, and what do you use instead?
Practice
Make shareLabel(slices) that returns, for each slice, name + " " + percent and returns "" for a slice narrower than 5 degrees.
Turn the district donut into a half circle gauge: use startAngle(-Math.PI / 2) and endAngle(Math.PI / 2) on the pie.
Trade-offs
- ↔
A donut with 3 to 5 slices tells the share story fast. A bar chart needs more space but handles any number of items and shows the exact ranking.
Done when you can
I can say what
d3.pieandd3.arceach return.I can draw a donut with labels at the slice centres.
I can say what a zero or text value does to a pie.
I can explain why 25 slices do not work and draw sorted bars instead.