CSS is amazing — I’m regularly surprised at how far it has come in the years I’ve been using it (~2005 – present). One such surprise came when I noticed this tweet by Shruti Balasa which demonstrated how to create a pie Chart using conic-gradient()
.
It’s fairly straightforward. Here’s a code snippet:
div {
background: conic-gradient(red 36deg, orange 36deg 170deg, yellow 170deg);
border-radius: 50%;
}
Using this tiny amount of CSS, you can create gradients that start and stop at specific angles and define a color for each ‘segment’ of the pie chart.
Happy Days!Brills, I thought I could use this instead of a charting library for a data dashboard project I’m working on for the new CockroachDB Cloud API, but I had a problem. I didn’t know the values for my chart ahead of time, and the values I was receiving from the API weren’t in degrees!
Here’s a preview link and Open-source repo of how I worked around those two problems, and in the rest of this post, I’ll explain how it all works.
- 🚀 Preview: https://css-conic-gradient-charts.vercel.app/
- ⚙️ Repo: https://github.com/PaulieScanlon/css-conic-gradient-charts
Here’s some sample data from a typical API response which I’ve sorted by value
.
const data = [
{
name: 'Cluster 1',
value: 210,
},
{
name: 'Cluster 2',
value: 30,
},
{
name: 'Cluster 3',
value: 180,
},
{
name: 'Cluster 4',
value: 260,
},
{
name: 'Cluster 5',
value: 60,
},
].sort((a, b) => a.value - b.value);
You can see that each item in the array has a name
and a value
.
In order to convert the value
from a number into a deg
value to use with CSS, there are a few things you need to do:
- Calculate the total amount of all the values.
- Use the total amount to calculate the percentage that each value represents.
- Convert the percentage into degrees.
Note: The code I’ll be referring to in the steps below can be found in the repo here: /components/donut-1.js.
Calculate The Total Amount
Using JavaScript, you can use this little one-liner to sum up each value from the data array, which results in a single total.
const total_value = data.reduce((a, b) => a + b.value, 0);
// => 740
Calculate The Percentage
Now that you have a total_value
, you can convert each of the values from the data array to a percentage using a JavaScript function. I’ve called this function covertToPercent
.
Note: I’ve used the value of 210 from Cluster 1 in this example.
const convertToPercent = (num) => Math.round((num / total_value) * 100);
// convertToPercent(210) => 28
Convert Percentage to Degrees
Once you have a percentage, you can convert the percentage into degrees using another JavaScript function. I’ve called this function convertToDegrees
.
const convertToDegrees = (num) => Math.round((num / 100) * 360);
// convertToDegrees(28) => 101
The Result
As a temporary test, if I were to map over the items in the sorted data array, using the two functions explained above, you’d end up with the following output:
const test_output = data.map((item) => {
const percentage = convertToPercent(item.value);
const degrees = convertToDegrees(percentage);
return `${degrees}deg`;
});
// => ['14deg', '29deg', '86deg', '101deg', '126deg']
The return value of test_output
is an array of the value
(in degrees) + the string deg
.
This solves one of a two-part problem. I’ll now explain the other part of the problem.
To create a Pie chart using conic-gradient()
, you need two deg
values. The first is the angle from where the gradient should start, and the second is the angle where the gradient should stop. You’ll also need a color for each segment, but I’ll come to that in a moment.
['red 🤷 14deg', 'blue 🤷 29deg', 'green 🤷 86deg', 'orange 🤷 101deg', 'pink 🤷 126deg']
Using the values from the test_output
, I only have the end value (where the gradient should stop). The start angle for each segment is actually the end angle from the previous item in the array, and the end angle is the cumulative value of all previous end values plus the current end value. And to make matters worse, the start value for the first angle needs to be manually set to 0
🥴.
Here’s a diagram to better explain what that means:
If that sounds confusing, it’s because it is, but if you look at the output of a function that can do all this, it might make more sense.
"#...", 0, 14,
"#...",, 14, 43,
"#...",, 43, 130,
"#...",, 130, 234,
"#...",, 234, 360,
The Function That Can Do All This
And here’s the function that can indeed do all of this. It uses reduce()
to iterate over the data array, performs the necessary addition to calculate the angles, and returns a new set of numbers that can be used to create the correct start and end angles for use in a Chart.
const total_value = data.reduce((a, b) => a + b.value, 0);
const convertToPercent = (num) => Math.round((num / total_value) * 100);
const convertToDegrees = (num) => Math.round((num / 100) * 360);
const css_string = data
.reduce((items, item, index, array) => {
items.push(item);
item.count = item.count || 0;
item.count += array[index - 1]?.count || item.count;
item.start_value = array[index - 1]?.count ? array[index - 1].count : 0;
item.end_value = item.count += item.value;
item.start_percent = convertToPercent(item.start_value);
item.end_percent = convertToPercent(item.end_value);
item.start_degrees = convertToDegrees(item.start_percent);
item.end_degrees = convertToDegrees(item.end_percent);
return items;
}, [])
.map((chart) => {
const { color, start_degrees, end_degrees } = chart;
return ${color} ${start_degrees}deg ${end_degrees}deg
;
})
.join();
I’ve purposefully left this pretty verbose, so it’s easier to add in console.log()
. I found this to be quite helpful when I was developing this function.
You might notice the additional map
chained to the end of the reduce
. By using a map
I’m able to modify the returned values and tack on deg
, then return them all together as an array of strings.
Using join
right at the end converts the array back to a single css_string
, which can be used with conic-gradient()
😅.
"#..." 0deg 14deg,
"#..." 14deg 43deg,
"#..." 43deg 130deg,
"#..." 130deg 234deg,
"#..." 234deg 360deg
Using The css_string
With An SVG foreignObject
Now, unfortunately, you can’t use conic-gradient()
with SVG. But you can wrap an HTML element inside a foreignObject
and style the background
using a conic-gradient()
.
This post first appeared on How Copywriting Can Benefit From User Research —, please read the originial post: here