By Vivek Kumar Bansal on February 26th, 2017

As an exercise on learning how SVG works, I decided to create a pie/donut-chart using nothing but vanilla JS (Yes, not even d3!).

For creating any charts, we need to understand few things about SVG first.

Unlike, normal graph coordinate system, the SVG coordinate has its Y-axis reversed. As you increase y-coordinates, the point moves down instead of up and vice-cersa. (You can read more about it here).

Next, to draw an arc, we will be using `path`

element of SVG, and we need a starting-point, radius and an end-point for it. A typical arc path looks something like this:

```
<path d="M40,20 A30,30 0 0,1 70,70"
style="stroke: #0000ff; stroke-width:2; fill:none;"/>
```

We will focus mainly on `d`

attribute. The two parameters after `M`

indicate a starting point. The two parameters after `A`

indicate the `x-radius`

and `y-radius`

of the arc respectively. The third parameter after `A`

indicates `x-axis-rotation`

and can be ignored for now.

The fourth and fifth parameters after `A`

indicate `large-arc-flag`

and `sweep-flag`

respectively. Given any two points, only two arcs can be drawn through them with give radii and it controlled using `large-arc-flag`

. As shown below, the blue arc is the smaller one (denoted by `0`

) and the the red arc is the the larger one (denoted by `1`

).

The `sweep-flag`

controls whether the arc is be to drawn in counter-clockwise direction (denoted by `0`

and shown in blue) or clock-wise-direction (denoted by `1`

and shown in red).

For more detailed info, you can read this

The following is a diagram of a circle in the SVG coordinate-system. We assume that `r`

is the radius of the the circle.

Now suppose we want the coordinates of a point $A$ on the circle which has an angle $\theta$ from vertical axis, we will make projections of $A$ on horizontal and vertical axis as $\vec{X}$ and $\vec{Y}$ respectively. By using basic trigonometry we get:

$$ \begin{align} & X = r + r sin\theta = r (1 + sin\theta) \newline & Y = r - r cos\theta = r (1 - cos\theta) \newline \newline & \text{Thus, } A = [r (1+ sin\theta), r(1 - cos\theta)] \end{align} $$

For a donut chart, we will need to make another smaller circle with radius $r'$ where $r' < r$. And similar to above example, we get coordinates of a point on this smaller circle as

$$ \left[r' (1+ sin\theta), r'(1 - cos\theta)\right] $$

To make a donut-chart, we need to make these circles concentric (have same centers), thus, we need to shift the smaller circle by $r - r'$ in both directions. Thus the updated coordinates for smaller circle are:

$$ \begin{align} &[(r'(1 + sin\theta)) + (r - r'), (r'(1 - cos\theta)) + (r - r')] \newline & = [r + r'sin\theta, r - r'cos\theta] \end{align} $$

Now we will learn how to draw an arc $(\widehat{PQRS})$ as shown in picture below. We assume that the start angle of the arc is $\alpha$ and the end angle is $\beta$. We also assume that the inner-radius $(\overline{OS})$ is $r_1$ the outer-radius $(\overline{OP})$ is $r_2$.

From the above section, we can concluded that
$$
\begin{align}
& P = [r_2 + r_2 sin\alpha, r_2 - r_2 cos\alpha]
\newline
& Q = [r_2 + r_2 sin\beta, r_2 - r_2 cos\beta]
\newline
& R =[r_2 + r_1 sin\beta, r_2 - r_1 cos\beta]
\newline
& S = [r_2 + r_1 sin\alpha, r_2 - r_1 cos\alpha]
\end{align}
$$
For drawing the arc, we will start from point `P(x, y)`

. Thus, we can write:

```
<path d="M P.x,P.y" />
```

Next we move **clock-wise** to point `Q(x, y)`

with radius `r2`

. Thus, we can write:

```
<path d="M P.x,P.y A r2,r2 0 0,1 Q.x,Q.y" />
```

Next we move to point `R(x, y)`

. Thus we can write:

```
<path d="M P.x,P.y A r2,r2 0 0,1 Q.x,Q.y L R.x,R.y" />
```

Next we move **counterclock-wise** to point `S(x, y)`

with radius `r1`

. Thus, we can write:

```
<path d="M P.x,P.y A r2,r2 0 0,1 Q.x,Q.y L R.x,R.y A r1,r1 0 0,0 S.x,S.y Z" />
```

The final `Z`

is to close the path.

There is one last bit remaining. We need to decide whether to use large-arc or small-arc (flip `large-arc-flag`

) based on the angle of the arc $(\beta - \alpha )$. If it is greater than $\pi$, we must use large-arc else small-arc.

The final javascript code will look like this:

```
function arc(startAngle, endAngle, outerRadius, innerRadius = 0) {
const sinAlpha = Math.sin(startAngle);
const cosAlpha = Math.cos(startAngle);
const sinBeta = Math.sin(endAngle);
const cosBeta = Math.cos(endAngle);
const largeArc = endAngle - startAngle > Math.PI;
const P = {
x: outerRadius + (outerRadius * sinAlpha),
y: outerRadius - (outerRadius * cosAlpha)
};
const Q = {
x: outerRadius + (outerRadius * sinBeta),
y: outerRadius - (outerRadius * cosBeta)
};
const R = {
x: outerRadius + (innerRadius * sinBeta),
y: outerRadius - (innerRadius * cosBeta)
};
const S = {
x: outerRadius + (innerRadius * sinAlpha),
y: outerRadius - (innerRadius* cosAlpha)
}
return `M${P.x},${P.y} A${outerRadius},${outerRadius} 0 ${largeArc ? '1,1' : '0,1'} ${Q.x},${Q.y} L${R.x},${R.y} A${innerRadius},${innerRadius} 0 ${largeArc ? '1,0' : '0,0'} ${S.x},${S.y} Z`;
}
```

Suppose you have data in the the following format:

```
// random data from https://www.mockaroo.com/
const data = [
{
"label": "web-enabled",
"value": 717,
"color": "#460898"
}
/* ... */
];
```

Total can be easily calculated using `Array.proptotype.reduce`

:

```
const total = data.reduce((p, c) => p + c.value, 0);
```

Now that we have raw data and total, we have to scale the data linearly between `0`

and `2π`

```
function scale (value) {
return value * Math.PI * 2 / total;
}
```

Now we can loop over the data and plot the chart. You can use any of your favourite frameworks like React, Vue, etc., or not and simply use vanilla JS as follows:

```
const fragment = document.createDocumentFragment();
const outerRadius = 300;
const innerRadius = 150;
let startAngle = 0;
data.forEach((slice) => {
const { value, color, label } = slice;
const sectorAngle = scale(value);
const d = arc(startAngle, startAngle + sectorAngle, outerRadius, innerRadius);
startAngle += sectorAngle;
const path = document.createElementNS('http://www.w3.org/2000/svg','path');
const title = document.createElementNS('http://www.w3.org/2000/svg','title');
title.innerHTML = label;
path.setAttribute('d', d);
path.setAttribute('fill', color);
path.appendChild(title);
fragment.appendChild(path);
});
document.getElementById('pie').appendChild(fragment); // assuming there is an svg with #pie available
```

And we are done. Now you'll a functional pie/donught chart without the need of d3!

You can have a look at the final result in the codepen demo below.