<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"
}
  • Content:
    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
    });
  • URL: /components/raw/donut/donut.js
  • Filesystem Path: source/patterns/organisms/infographics/donut/donut.js
  • Size: 2.9 KB
  • Content:
    $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;
          }
        }
      }
    }
  • URL: /components/raw/donut/donut.scss
  • Filesystem Path: source/patterns/organisms/infographics/donut/donut.scss
  • Size: 2.2 KB

Donut Chart

Overview

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.

Limitations

  • data values will be parsed as integers for the purpose of calculating the chart slice size
  • data is normalized based on the sum of all the integers parsed
  • the CSS supports no more than 5 data points

Class Names

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

References

The following articles were used as references for this pattern: