<div class="donutchart make-inview">
<table class="table table-borderless table-sm w-auto">
<thead class="sr-only">
<tr>
<th scope="col">Percent</th>
<th scope="col">Area of Interest</th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">40%</span</td>
<td class="align-middle">Mathematics and Natural Sciences</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">24%</span></td>
<td class="align-middle">Arts and Humanities</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">11%</span></td>
<td class="align-middle">Engineering</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">20%</span></td>
<td class="align-middle">Social Sciences</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">5%</span></td>
<td class="align-middle">Performing Arts</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">5%</span></td>
<td class="align-middle">Undecided</td>
</tr>
</tbody>
</table>
</div>
<div class="donutchart make-inview">
<table class="table table-borderless table-sm w-auto">
<thead class="sr-only">
<tr>
<th scope="col">{{donut_th_value}}</th>
<th scope="col">{{donut_th_label}}</th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">40%</span</td>
<td class="align-middle">Mathematics and Natural Sciences</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">24%</span></td>
<td class="align-middle">Arts and Humanities</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">11%</span></td>
<td class="align-middle">Engineering</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">20%</span></td>
<td class="align-middle">Social Sciences</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">5%</span></td>
<td class="align-middle">Performing Arts</td>
</tr>
<tr>
<td class="align-middle"><span class="datum d-block text-center px-2 py-1 rounded">5%</span></td>
<td class="align-middle">Undecided</td>
</tr>
</tbody>
</table>
</div>
{
"donut_th_value": "Percent",
"donut_th_label": "Area of Interest"
}
document.addEventListener("DOMContentLoaded", function() {
// this script takes information from the HTML to make charts.
// there can be up to 5 slices in each chart.
// each data value must be a string that can be parsed into an integer.
var charts = document.querySelectorAll(".donutchart");
Array.prototype.forEach.call(charts, function (chartnode) {
var dataNodes = chartnode.querySelectorAll(".datum");
var slices = new Array();
var data = new Array();
var total = 0;
// create the svg for the chart
// the size of the svg doesn't really matter as long as it's a square
// this is because we are using percentages for everything
var chart = document.createElementNS('http://www.w3.org/2000/svg','svg');
chart.setAttribute('class','chart');
chart.setAttribute('viewbox','0 0 100 100');
chart.setAttribute('preserveAspectRatio','xMidYMid meet');
chart.setAttribute('width',100);
chart.setAttribute('height',100);
// convert the text of each .datum element to an integer
// also get a total
// also, for each .datum, add a slice element to the chart
for (var i=0; i<dataNodes.length; i++) {
data[i] = parseInt(dataNodes[i].innerText);
total += data[i];
slices[i] = document.createElementNS('http://www.w3.org/2000/svg','circle');
slices[i].className = "slice";
slices[i].setAttribute('r','50%');
slices[i].setAttribute('cx','50%');
slices[i].setAttribute('cy','50%');
chart.append(slices[i]);
}
// insert the chart
chartnode.querySelector('table').before(chart);
// normalize each value using the total to get the percentage
// of the whole represented by each .datum
for (var i=0; i<data.length; i++) {
data[i] = data[i]/total;
}
// using the dash length and dash offset trick
// create each slice of the chart
// we are doing everything here in percentages so the chart size will scale nicely
var offset = 0;
for (var i=0; i<data.length; i++) {
// start in the middle
var j = Math.round(data.length/2) - 1 + i;
j = (j > (data.length - 1)) ? j - data.length : j;
// slice as percent of circumference
var sliceLength = data[j] * 314.159;
slices[j].style.strokeDasharray = sliceLength + '% 314.159%';
slices[j].style.strokeDashoffset = offset + '%';
offset -= sliceLength;
}
});
// references:
// https://css-tricks.com/how-to-make-charts-with-svg/
// https://css-tricks.com/a-trick-that-makes-drawing-svg-lines-way-easier/
// https://math.stackexchange.com/questions/260096/find-the-coordinates-of-a-point-on-a-circle
// https://stackoverflow.com/questions/3642035/jquerys-append-not-working-with-svg-element
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio
});
$mathPI:3.14159;
// color scheme from https://coolors.co/6610f2-20c997-db1a74-ffc107-60b5ff
$color1:#{var(--indigo)} !default;
$color2:#{var(--teal)} !default;
$color3:#{var(--pink)} !default;
$color4:#{var(--yellow)} !default;
$color5:#60B5FF !default;
$color6:#ff794c !default;
$donutchart-break:640px !default;
body {
margin:0;
padding:2rem;
}
.donutchart {
align-items:center;
position:relative;
@media (min-width:$donutchart-break) {
display:flex;
}
.chart {
border-radius:50%;
height:auto;
max-width:250px;
width:100%;
@media (min-width:$donutchart-break) {
width:50%;
}
circle {
fill: transparent;
stroke-width:50%;
stroke-dasharray:1 ($mathPI * 100%);
stroke-dashoffset:1;
transition:all 1s;
&:nth-child(1) {
stroke: $color1;
}
&:nth-child(2) {
stroke: $color2;
}
&:nth-child(3) {
stroke: $color3;
}
&:nth-child(4) {
stroke: $color4;
}
&:nth-child(5) {
stroke: $color5;
}
&:nth-child(6) {
stroke: $color6;
}
}
}
.chart + table {
margin-top:1rem;
@media (min-width:$donutchart-break) {
margin-left:1rem;
margin-top:0;
}
}
table {
margin-top:0;
margin-bottom:0;
tbody tr {
.datum {
background-color:black;
color:white;
font-weight:bold;
}
.datum + td {
padding-left:0.25rem;
}
&:nth-child(1) .datum {
background-color:$color1;
}
&:nth-child(2) .datum {
background-color:$color2;
color:black;
}
&:nth-child(3) .datum {
background-color:$color3;
}
&:nth-child(4) .datum {
background-color:$color4;
color:black;
}
&:nth-child(5) .datum {
background-color:$color5;
color:black;
}
&:nth-child(6) .datum {
background-color:$color6;
color:black;
}
}
}
&.not-inview {
.chart circle {
@media (prefers-reduced-motion: no-preference) {
stroke-dasharray:1% ($mathPI * 100%) !important;
stroke-dashoffset:1% !important;
}
}
}
}
The donut chart pattern is a stylistic variation on a pie chart. The chart shows a small number of data points as percentage of a whole. The chart is build on values in the HTML that are indicated by a specific class name. This makes the chart data quickly editable and very accessible.
Below are custom class names for implementation of the donut chart. The pattern uses additional classes from Bootstrap for styling.
Name | Usage |
---|---|
.donutchart | apply to the block level container for the chart and data |
.datum | apply to each span that wraps (represents) a data point for the chart |
.make-inview | apply to the block level container if you want the chart to animate in |
The following articles were used as references for this pattern: