SVG pie chart, donut chart and progress ring: the code
Two ways to draw a pie chart in SVG: a path with an arc for each slice, or circles with stroke-dasharray. Code for pies, donuts, progress rings and gauges.
Published
SVG has no pie chart element. There are two ways to build one. Draw each slice as a <path>: a line out from the centre, an arc round the rim and a line back. Or draw each slice as a <circle> whose thick, dashed stroke shows only part of the way round. The first gives real wedge shapes, and the second needs no trigonometry.
A pie with a quarter slice and a three-quarter slice, written by hand:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<path d="M 50 50 L 50 10 A 40 40 0 0 1 90 50 Z" fill="teal"/>
<path d="M 50 50 L 90 50 A 40 40 0 1 1 50 10 Z" fill="gold"/>
</svg>
The teal quarter runs from 12 o’clock to 3 o’clock. The gold slice carries on from 3 o’clock all the way round to 12.
Which method
| Path with an arc | Circle with a dashed stroke | |
|---|---|---|
| Sums needed | Sine and cosine for each slice end | None: values are percentages |
| Best for | Pies drawn from data in script, slices that pull out, exact wedge shapes | Donuts, progress rings, gauges, charts written by hand or in a template |
| Each slice is | A closed shape that can be filled, outlined and clicked exactly | A stroke: it cannot have its own outline |
| Animating a value | Rewrite the path | Change one number, and CSS can do the transition |
Method 1: a path for each slice
One slice is three commands. With the centre at cx,cy, radius r, and the slice running from point 1 to point 2 on the rim:
M cx cy L x1 y1 A r r 0 large 1 x2 y2 Z
M goes to the centre. L draws out to the start of the slice. A draws the arc to the end of the slice: the two radii, a rotation of 0, the large-arc flag, the sweep flag and the end point. Z closes back to the centre.
The two flags matter:
- large-arc is 1 when the slice is more than half the pie, over 180 degrees, and 0 otherwise. Get this wrong and a big slice is drawn as the small one on the other side.
- sweep is 1 to go clockwise.
The points on the rim come from the angle. Measuring from 12 o’clock and going clockwise, the point at angle degrees is:
x = cx + r × sin(angle)
y = cy − r × cos(angle)
The y term is subtracted because y runs downwards in SVG. For the quarter slice above: at 0 degrees the point is 50, 10 and at 90 degrees it is 90, 50. The SVG path guide explains the arc command in full.
From data, in JavaScript
<svg id="chart" viewBox="0 0 100 100" width="300" role="img" aria-label="Materials: wood 40%, acrylic 30%, card 20%, leather 10%"></svg>
<script>
function slicePath(cx, cy, r, startAngle, endAngle) {
const point = (angle) => {
const rad = (angle - 90) * Math.PI / 180;
return [cx + r * Math.cos(rad), cy + r * Math.sin(rad)];
};
const [x1, y1] = point(startAngle);
const [x2, y2] = point(endAngle);
const large = endAngle - startAngle > 180 ? 1 : 0;
return `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`;
}
const data = [
{ label: 'Wood', value: 40, colour: 'teal' },
{ label: 'Acrylic', value: 30, colour: 'gold' },
{ label: 'Card', value: 20, colour: 'crimson' },
{ label: 'Leather', value: 10, colour: 'slategray' },
];
const total = data.reduce((sum, d) => sum + d.value, 0);
const svg = document.querySelector('#chart');
let angle = 0;
for (const d of data) {
const sweep = d.value / total * 360;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', slicePath(50, 50, 40, angle, angle + sweep));
path.setAttribute('fill', d.colour);
svg.append(path);
angle += sweep;
}
</script>
Four slices, starting at the top and going clockwise: teal for 40%, gold for 30%, red for 20% and grey for 10%. Subtracting 90 degrees inside point turns the usual maths convention, which starts at 3 o’clock, into one that starts at 12.
One case breaks it. A slice of 100% starts and ends at the same point, and an arc between two identical points draws nothing: in Chrome the whole pie vanished. When one value is the whole total, draw a <circle> instead.
Method 2: circles with a dashed stroke
A stroke can be dashed, and a dash can be made as long as a share of the circle. Give a circle a stroke as wide as the pie’s radius and the dash becomes a wedge.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g fill="none" stroke-width="40" transform="rotate(-90 50 50)">
<circle cx="50" cy="50" r="20" pathLength="100" stroke="teal" stroke-dasharray="40 100"/>
<circle cx="50" cy="50" r="20" pathLength="100" stroke="gold" stroke-dasharray="30 100" stroke-dashoffset="-40"/>
<circle cx="50" cy="50" r="20" pathLength="100" stroke="crimson" stroke-dasharray="20 100" stroke-dashoffset="-70"/>
<circle cx="50" cy="50" r="20" pathLength="100" stroke="slategray" stroke-dasharray="10 100" stroke-dashoffset="-90"/>
</g>
</svg>
The same four-slice pie as the script drew, with no script. How it works:
- The radius is half the pie’s. A stroke is centred on the circle’s edge. With
r="20"andstroke-width="40"it reaches from the centre out to 40. pathLength="100"tells the browser to treat the circle’s outline as 100 units long, so that dash lengths can be written as percentages. Without it the dashes would have to be worked out from the circumference, 2 × π × r.stroke-dasharray="40 100"is a dash of 40 followed by a gap of 100: one dash covering 40% of the way round, and a gap long enough that it never repeats.stroke-dashoffsetslides the dash along. Each slice is moved on by the sum of the slices before it, written as a negative number: 0, then -40, then -70, then -90.rotate(-90 50 50)turns the lot a quarter turn back. The SVG specification starts a circle’s outline at 3 o’clock and runs it clockwise. The rotation puts the start at 12 o’clock.
Dashed lines covers stroke-dasharray and stroke-dashoffset.
A donut chart
Make the stroke thinner than the radius and the middle opens up. Only two numbers change from the pie: r and stroke-width.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<g fill="none" stroke-width="16" transform="rotate(-90 50 50)">
<circle cx="50" cy="50" r="32" pathLength="100" stroke="teal" stroke-dasharray="40 100"/>
<circle cx="50" cy="50" r="32" pathLength="100" stroke="gold" stroke-dasharray="30 100" stroke-dashoffset="-40"/>
<circle cx="50" cy="50" r="32" pathLength="100" stroke="crimson" stroke-dasharray="20 100" stroke-dashoffset="-70"/>
<circle cx="50" cy="50" r="32" pathLength="100" stroke="slategray" stroke-dasharray="10 100" stroke-dashoffset="-90"/>
</g>
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" font-size="10">100</text>
</svg>
A ring from radius 24 to radius 40, with a number in the hole. The text sits outside the rotated group so that it stays upright.
A progress ring
A progress circle is a donut with one slice, drawn over a pale full ring:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke="gainsboro" stroke-width="10"/>
<circle cx="50" cy="50" r="40" fill="none" stroke="teal" stroke-width="10"
pathLength="100" stroke-dasharray="75 100" transform="rotate(-90 50 50)"/>
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" font-size="16">75%</text>
</svg>
To show another value, change the 75 in stroke-dasharray and in the text.
In a web page the change can be animated with one line of CSS, transition: stroke-dasharray 0.5s on the coloured circle. Setting the attribute to a new value then slides the bar there: read half way through the transition in Chrome, the computed dash was 63 on its way from 25 to 75.
stroke-linecap="round" gives the bar round ends, with two side effects. Each cap adds half the stroke width to the end of the bar, so it reads a little high. And at 0 the caps still show, as a dot. Leave the bar out, or hide it, when the value is 0.
A gauge
A gauge is the same idea on an open arc. pathLength works on any path:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 60">
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="gainsboro" stroke-width="10"/>
<path d="M 10 50 A 40 40 0 0 1 90 50" fill="none" stroke="teal" stroke-width="10"
pathLength="100" stroke-dasharray="60 100"/>
</svg>
A half ring from left to right over the top, filled to 60%. No rotation is needed, because the path already starts where the gauge should.
Labels on the slices
A label goes at the middle angle of its slice, part of the way out from the centre. For the quarter slice, the middle angle is 45 degrees. At 25 units from the centre, the formulas above give x = 50 + 25 × sin(45°) = 67.7 and y = 50 − 25 × cos(45°) = 32.3:
<text x="67.7" y="32.3" text-anchor="middle" dominant-baseline="central"
font-size="6" fill="white">25%</text>
text-anchor="middle" and dominant-baseline="central" centre the text on that point both ways. Thin slices have no room for a label: put those in a legend beside the chart. SVG text covers text placement.
For people who cannot see the chart
A pie chart built from shapes says nothing to a screen reader. Give the <svg> role="img" and an aria-label that states the figures, as the script example does, or put the same numbers in a table near the chart. Accessible SVG has the patterns.
Common problems
| What you see | Cause | Fix |
|---|---|---|
| A big slice is drawn as a small one on the wrong side | The large-arc flag is 0 for a slice over 180 degrees | Set it to 1 |
| The slice bulges inwards | The sweep flag | Use 1 for clockwise |
| A 100% slice draws nothing | The arc starts and ends at the same point | Draw a <circle> |
| The pie starts at 3 o’clock | That is where a circle’s outline and the usual angle maths both start | Subtract 90 degrees, or rotate by -90 |
| The dashed-stroke pie has a hole or overlaps itself | r is not half of stroke-width | For a full pie, stroke-width is twice r |
| Dash lengths do not match the percentages | No pathLength, so dashes are in drawing units | pathLength="100" |
| The slices do not line up | An offset is positive, or is not the running total | Each stroke-dashoffset is minus the sum of the earlier slices |
| The label in the middle is on its side | It is inside the rotated group | Move the <text> out of the group |
Questions
Which method is better for a cutting machine? The path method. Each slice is a real closed outline that a cutter can follow. A dashed stroke is only a way of painting: software that follows the outline itself would cut the whole circle.
Can CSS draw a pie chart without SVG? A conic-gradient background on a round element does. SVG is the better choice when the slices need labels, links or hover effects, or when the chart must be saved as a file.
Do I need a chart library? Not for one pie. For many charts, axes and tooltips, a library such as D3 generates the same paths for you.
How do I check my arcs? Paste the SVG into the SVG viewer and change one number at a time.