Learning the trigonometry trio — sin cos and tan — is right near the top of my reverso bucket list (things I hope to die before I have to do). They are followed closely by setting up a network printer and running for a bus.
Whatever was I to do? I wanted a pie chart in my app, but I didn’t want to learn trigonometry. Could I make a pie, without π?
☑ Dumb play on words.
Have you heard of my app know-it-all? I have talked about it non-stop for the last month or so.
For those late to the party, the idea is this: I provide you with a list of everything that there is to know about web development. For each item, you record how well you know it. At the end, you will have a list of things that you don’t know about web development.
If you don’t understand why this is great, there is a reddit thread somewhere discussing how I could possibly be so stupid — you may wish to pile on.
Since there are 10,000 things to know, you will probably not do it in a single sitting. So I figured a little pie for each row would help you visualise where you’re up to.
No fancy hover effect, no tooltips, no animation, and just my two favorite dimensions.
Why not use a charting library to do this?
I feel the need to add that D3 is 74 KB of pure brilliance, and that the creator Mike Bostock is one of the great unicorn developers of our time.
Side note: yes, this is supposed to be a vanilla JS app and I am using
localforage. But browser storage is just such a mess (thanks to SafarIE) that I acquiesced in my no-library rule.
I don’t mind though. Because I got to use the word acquiesced.
Never one to shy away from intellectual theft, I googled “Pie chart SVG” as my first step. There were a few things that involved hacking CSS, but that strikes me as The Wrong Way To Do It. Especially when the reason is to avoid maths (or ‘math’, as you may call it in your weird country).
There was a post on CSS-Tricks. I read it for 12 seconds, then the whole header of the site moved and distracted me. I had to view the source to see what was going on. I never went back to finish the article. (If I was forced to offer an opinion, I would say this is what bad design looks like. Then I would wonder if Chris Coyer would ever read this. Then and I would feel mean and stutter something about great content. Then I would run from the room, tears streaming down my face.)
There were some articles that described using SVG (the correct way), but they quickly devolved into symbols and talk of hypotenuses and that cult-leader Pythagoras (never trust a man that’s afraid of beans).
The more I think about that stuff the more confused and troubled I get. Like the other day when I was in the lift and a bald guy got in that smelled like shampoo.
So I vowed that when my pie was finished, I would write a blog post about it that probably almost no people will be interested in because who makes pie charts without a library anyway?
Seriously, what are you even doing here? Unless you’re future David and you forgot how to do this. Hi there future David, sorry about the you-know-what.
I do not like self-indulgent intros on technical blog posts, but try as I might, I can’t seem to avoid them. At least this one is over now. If, by some miracle, you actually want to know how to do a pie chart in SVG, let the games … start.
The anatomy of a slice
There are three parts to drawing a slice of a pie.
This isn’t a complete slice, clearly. But SVG don’t care. SVG be all like yo dawg, I got this:
That’s the basics. Now, to put it into action we must know:
- How to work out the x/y coordinates of those points
- How to write the syntax that instructs SVG to draw the lines between those points.
#1 Working out where the points are
For this I googled “work out where a point is on a circle”. I found a lot of answers from people assuming I am significantly more cleverer than I actually am. While this is flattering, it isn’t helpful.
This is what the answers looked like to me:
Having spent some time translating professor into Mary Ann, I would like to tell you how to do it, and I will do you the favor of assuming you’re an idiot.
I, like many people, want to draw a pie slice that represents 12% of a pie. To do this I need to find two points on my circle:
- the start position (0% of the way around the circle)
- the end position (12% of the way around the circle).
(In a list of things, always put a period after the longest one.)
The real life working function is:
Uh oh, things just got real mathy up in here. But here’s the thing — I don’t know what
Math.sin() are, and you shouldn’t have to know either. I know that
Math.PI is 3 and a half, but I don’t know or care what this has to do with anything.
What I do know is that this works and you and I both have more important things to think about. Like, do ants ever sit down? If so, do they just sit on the ground? Or do they make little chairs out of twigs and leaves?
Back to the mathy stuff.
People that know trigonometry are probably going nuts right now because a) they’re a highly strung crowd. But also b) because I’m:
- not taking into account the radius of the circle
- ignoring the fact that these coordinates are relative to the center of the circle, not the top left like SVG is
- for a percent of ‘0’, this would put the coordinates at the 3 O’clock position, not at 12 where I would expect it.
So maybe I should go and complicate my pretty little function to handle these annoyances.
Moving the goalposts
Some of you probably just want to know the answer, so here it is:
viewBox="-1 -1 2 2"
That effectively aligns the SVG coordinate system with the cartesian coordinate system and addresses the three points above. Specifically:
- I don’t need to multiply the sin/cos results by the radius
- 0,0 is now in the center of the SVG, not the top left
- my slices will begin at the top of the circle, not the right.
If you don’t want to know how, skip straight to #2 The (nasty) <path> syntax.
Otherwise, buckle up, buckeroos.
Professor Nerdo will tell you I’m supposed to multiply the result of
Math.cos() by the radius of the circle to get the correct result. I’m not happy about this and to be perfectly honest, I’d rather have a bar chart than pass around radii all the time.
Oh life would be so much easier if my circle had a radius of exactly 1, because multiplying by one makes no difference. (That somehow makes loneliness even sadder.)
But there is a problem that threatens to derail the whole project. (I’ve been watching Megastructures — there’s always a problem that threatens to derail the whole project.)
If I have a circle radius of only 1, then when I scale it up my chart will be all pixelated.
[pauses for effect]
…my Graphic was a Vector that was Scalable. Then I could just define my SVG viewport as 2x2, so a 1px radius circle will always fill the width and height of the SVG. (Thus disconnecting the pixels that we use in the SVG from the pixels that are rendered on the screen.)
Next problem: SVG wants the top left to be the origin, but I want the center of the circle to be the origin. So, while I’m messing with the viewport, I might as well offset it, to put the origin right in the middle.
Perhaps some pictures will keep me occupied for a while so I don’t keep thinking about that loneliness thing.
Let’s say I want to draw a circle. By default, SVG will center a circle centered on 0,0.
But if shift the viewport up and to the left by 1, then I get this:
The solid blue square is what is rendered to the screen, the dotted line is in case you had forgotten what the viewport was in the previous picture.
This is great, I no longer need to dirty up my code by shifting everything right and down by the radius.
The final part to all this is that currently,
cos seem to think that the right side of the circle is the starting position:
But I want my pie slices to begin at the top (12 O’clock). Easy, I can just rotate the SVG so we have this:
Don’t be freaked out by the weird coordinate universe we are now a part of, this all just works (because we’ve aligned SVG and cartesian).
To recap, the
<svg> will look like this:
viewBox="-1 -1 2 2"
-0.25turn instead of
-90deg because it’s weirder.
Now I am fully equipped to use my function from above to feed in percentages, and get the position on the outside of the circle for that percent:
Now all I need to do is take those x/y coordinates and use SVG to render slices on the screen. For this, I will need…
#2 The (nasty) <path> syntax
One of my favourite pastimes is drawing birds by writing out the data for path elements.
Here’s a toucan I drew earlier:
M423.573,69.353c-3.646–2.637–5.666–10.937–4.447–8.912c1.215,2.025,0.801–2.025–0.213–5.466 c-1.006–3.446–1.205–5.875,0–4.05c1.223,1.818,0.611–1.618–0.192–6.078c-0.812–4.452–1.221–8.706–5.065–14.775 c-3.848–6.077–5.465–9.519–9.72–10.328c-4.25–0.813–4.454–2.026–2.427–2.435c2.021–0.405–0.412–1.213–4.253–1.011 c-3.851,0.204–3.851–0.405–2.431–1.416c1.418–1.014,2.031–2.021–0.61–1.419c-2.626,0.613–1.215–1.213–3.235,0 c-2.029,1.213–0.203–2.02–2.84–0.405c-2.632,1.618–1.615–0.809–2.828,0.612c-1.225,1.414–1.421–2.437–1.822–0.208 c-0.407,2.228–0.819,2.43–0.819,2.43s-39.121–8.232–71.295,10.487c-15.456,8.984–7.671,26.975–7.671,26.975 s1.832–6.996,20.252–6.482c21.669,0.609,49.809–0.405,49.809–0.405s3.197,4.77,2.232,10.333c-1.62,9.309–15.395,15.384–15.6,29.152 c-0.188,13.22,3.389,58.711,16.002,75.524c9.723,12.957,11.336,7.088,11.336,7.088s4.664,8.101,5.472,2.228 c0,0,3.235,2.228,2.427,6.472c-0.807,4.262–4.458,19.651–8.3,43.746c-3.845,24.098–1.8,37.665,5.371,25.823 c0,0–10.599,33.358,3.427,22.134c0,0,1.559,19.328,7.791,11.846c0,0,7.477,14.656,11.842–7.791c0,0,5.609,7.791,7.785–3.746 c0,0,5.609,5.613,4.678–10.901c-0.695–12.157,0.305–35.853–0.628–50.497c-0.936–14.656–0.305–29.938–0.305–29.938 s0.762,2.487,1.996,1.738c2.19–1.334,1.26–7.22,1.26–7.22s3.703,5.095,6.004–3.744c1.466–5.651,5.95–17.282,7.558–27.862 c4.369–28.676–4.535–45.114–12.281–60.295C428.165,73.382,427.22,71.979,423.573,69.353L423.573,69.353z
My point: this part is not going to be fun.
<path> element has an attribute
d (for ‘dinosaur’ probably), that can contain multiple commands in the form of a string. Different letters signify different commands, and the numbers define where the path goes on the screen. You can see with the toucan above I’ve used both big ‘C’ and little ‘c’ to capture the personality of these magnificent creatures.
For drawing a slice, I am interested in only three commands: M, A and L. I have faith that you can work out what they stand for all on your own.
If I wanted to draw the above slice (remember, I only need to draw two lines, SVG will fill it in and make it a slice) my path element would look like this:
<path d=”M 1 0 A 1 1 0 0 1 0.8 0.59 L 0 0"></path>
The first part,
M 1 0 is obvious, it means move to our start position. This doesn’t draw anything yet.
The second part:
A 1 1 0 0 1 0.8 0.59 follows the syntax for the ‘arc’ command. Which is this monstrosity:
rx ry x-axis-rotation large-arc-flag sweep-flag x y.
Fun fact: the movie se7en was originally about the seven parameters that define an SVG arc.
Only three of these are worth knowing, but it took me long time to get my head around them and I’ll feel better if I get to regurgitate it.
ry: this is the radius of the circle. Arcs can actually go around an ellipse, so potentially have two radiuses. Because my pie is a circle, I can consider them both the radius (1)
x-axis-rotation: this would only make sense if my arc was arcing around an ellipse, rather than a circle, so this will always be 0
large-arc-flag. Given two points on a circle, should the arc go the long way around (more than half a circle) or the short way (less than half a circle). This depends on whether the percent for our slice is greater than or less than 50%.
sweep-flag. For pies, it’s always 1. I’m not going to explain why
y. This is where we want to end up
Note that you don’t define a ‘start’ x and y, just the end. The start is defined by the previous command (
M 1 0).
The last part of the path data is the “line-to” command, which is simply
L 0 0. In other words, “draw a straight line (L) to the middle (0,0) of the circle”.
So all up we need five numbers to define our path.
endY. All together now:
Got that? Good.
How about I do a full example of a pie, where I take an array of slice data and render some slices.
(I will leave the pie on the windowsill of the Result tab.)
You can imagine that it isn’t hard to extend this to show tooltips (in SVG, you use a
<title> element, not a
title attribute) or respond to a click or hover event.
If you’re interested, you can check out
PieChart.js in the know-it-all repo.
I hope that you’ve enjoyed learning about… hey, that green in the pie chart looks familiar. Do you think … could it be … the same green as when you click on the little love heart in medium? Hmm, if only there was a way to find out.
Oh and the owl has nothing to do with pie charts.