Doughnut time
Tags: charting, css, data
Published: 2023-11-22 12:41:17

---
title: Doughnut time
published: true
description: A CSS technique for creating simple doughnut charts from semi-semantic markup
tags: Charting, CSS, Data
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66tinqls3z14ekd5pojn.jpg
# Use a ratio of 100:42 for best results.
---

<small>Image by [Andres Ayrton](https://www.pexels.com/photo/crop-unrecognizable-person-demonstrating-box-with-delicious-doughnuts-6550773/)</small>

I was asked to build some doughnut charts the other day and it reminded me of a technique I'd read about years ago which would be *perfect*. This is often what it's like for developers: you read a blog post about something and think "that sounds useful. I'll need to remember that, for some hypothetical project in the future." With experience, a developer's mind resembles that drawer you have in your house full of cables which somehow allow a VHS recorder to connect to your microwave.

The technique in question is called [conic gradients](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient) and can be used to do stuff like this:

{% codepen https://codepen.io/ross-angus/pen/LYmqKWx default-tab=result %}

One word you'll hear bandied about if you hang around long enough with developers is "elegance". Just to be clear - you're extremely unlikely to hear this applied *to* developers. But you'll hear them mutter it under their breath, in relation to some code they approve of.

When we say "elegant", we generally mean simple, clean code which achieves its function with great economy. Imagine an artist standing in front of a canvas and, using a single brush stroke, describes the form of a horse galloping full pelt. That, but with the confusing mess of punctuation symbols which make up computer languages.

There are few places where elegant code counts more than on the web. There's no end of statistics reminding you of how many customers you can shed, if you ask them to watch your website load for a fraction of a second longer than your rival.

Developers strive to achieve this in many ways, but when building a single component, we sometimes aim to do so in a single html "tag". Going back to the example of our doughnut chart, we might ideally represent this in the HTML as this:

```html
<p>75%</p>
```

That `<p>` is short for "paragraph", by the way.

HTML (broadly speaking) is used to store the *data* of a single web page. The language we're going to use to describe how the doughnut chart looks is called CSS or Cascading Style Sheets. So the first thing we need to do is pass the value `75%` to the CSS.

## A quick diversion down semantics avenue

Semantics is a term which is often flashed around in conversation with much of the same attitude as Mulder and Scully produced their FBI badges. In the context of web development, it means ensuring that the data which is displayed is put in the correct context.

[Charts.css](https://chartscss.org/) is a much more comprehensive approach to charting using CSS. As such, their starting point is often the `table`. Tables are great for displaying data which has both a vertical and a horizontal relationship. For example:

### Doughnuts around the World
| Name              | Place of origin                     | Notable features                |
|-------------------|-------------------------------------|---------------------------------|
| Bomboloni         | Italy                               | Fillings                        |
| Berliner          | Germany                             | Sometimes filled with champagne |
| Jelebi            | Middle Eastern/Indian/North African | Made from maida flour                     |
| Churros           | Spain and Portugal                  | Rod-shaped, not ring-shaped     |
| Sufganiyot        | Israeli                             | Cooked in schmaltz              |
| Youtiao Doughnuts | China                               | Twisted stick                   |
| Beignets          | France                              | Made from choux or yeast pastry    |
| An Doughnut       | Japan                               | Can be filled with red bean paste                  |
| Oliebollen        | Holland                             | Can be filled with raisins      |

<small>Content shamelessly stolen from [Crazy & Co.](https://www.crazyandco.uk/2021/02/02/12-doughnuts-from-around-the-world/)</small>

The data I'm representing here is much simpler - a single category and a figure.

HTML has a tag for that! It's called `figure` and it's not just for pretty pictures. Inside a `figure` tag, you can add a `figcaption` tag which should explain what the figure means. For example:

```html
<figure>
  <p>100%</p>
  <figcaption>This is how much I love CSS</figcaption>
</figure>
``` 

Anyway, back to how we're going to make these doughnuts.

## *From this point on, it's no longer for the layperson*

It's not possible to extract the contents of a tag using CSS, but we *can* pass values to the CSS as CSS variables on a `style` attribute:

```html
<p style="--value: 75%;">75%</p>
```

A basic conic gradient which represents 75% would be as follows:

```css
p {
  background: conic-gradient(red 270deg, transparent 270deg);
}
```

This is the shortened form of `conic-gradient`, where the browser makes the assumption that the gradient is centred within the element, starts at the 12 noon position, the first colour starts at 0 degrees and the second colour ends at 360 degrees. The repetition of `270deg` means that both the start and end colour change at this point, which leaves a hard line.

Note how the size of the doughnut is defined in degrees. We *could* do some math(s) here to convert our 75% figure to degrees, but what's the point, when `conic-gradient` is just as happy with a percentage?

```css
p {
  background: conic-gradient(red var(--value), transparent var(--value));
}
```

## Adding the doughnut hole

A conic gradient is just a background image, but we're going to exploit where this image *starts*, in order to create the hole. We can use the `background-origin` property to tweak where the background image starts from:

{% codepen https://codepen.io/ross-angus/pen/vYrEYzv default-tab=result %}

Note how in the first example, the background image appears *under* the border, then in the second example, the image starts from *inside* the border and in the final example, the background image stats from some distance *away* from the border. This gap is determined by the padding inside the element.

We can use this property in combination with adding *multiple* background on the same elements in order to create the doughnut hole. Here's how it works:

{% codepen https://codepen.io/ross-angus/pen/KKRLVdL default-tab=result %}

Each one of those charts uses the following style rule:

```css
/* --barColour and --value are passed in from the HTML */
p {
  background:
   conic-gradient(var(--bg) 0, var(--bg) 0) padding-box,
   conic-gradient(var(--barColour) var(--value), transparent var(--value)) border-box;
}
```
That first background represents the hole in the middle and note that it starts from the `padding-box` position. Unfortunately, this needs to include the background colour as a colour value (I've used a CSS variable above, to keep it DRY). So the hole in the centre is not truly transparent, it's instead a circle which sits on top.

This means this technique is not ideal for doughnut charts which need to sit on top of complicated background graphics.

The second background is the doughnut itself and starts from the `border-box` element (inside the border). This means the thickness of the doughnut ring can be controlled by adjusting the transparent border on the doughnut element. Specifically, this property:

```css
p {
   border: solid 1em transparent;
}
```

## Future work

The original design I was working from held no more than one figure per doughnut chart. It's possible to add more than one band of colour to the doughnut chart, but doing so breaks the tight relationship between the data in the HTML and the doughnut chart itself. This is because each band in the doughnut chart is passed as a pair of CSS variables, one for colour, the other for the value.

As the conic-gradient rule needs to expect these variables in advance, you'd need to decided what the maximum number of these, when writing the original rule. For example:

```css
p {
  background:
    conic-gradient(var(--bg) 0, var(--bg) 0) padding-box,
    conic-gradient(
      var(--barColour) var(--value),
      var(--barColour2, transparent) var(--value) var(--value2, 100%),
      var(--barColour3, transparent) var(--value2, 100%) var(--value3, 100%),
      var(--barColour4, transparent) var(--value3, 100%) var(--value4, 100%),
      var(--barColour5, transparent) var(--value4, 100%) var(--value5, 100%),
      var(--barColour6, transparent) var(--value5, 100%) var(--value6, 100%),
      transparent var(--value6, 100%) 100%
    ) border-box;
}
```

Note that the `var()` function can provide a default value, if none is supplied by the markup. This allows the doughnut chart to work with as few as one value.