Cara menggunakan css donut chart animation

Often Creating charts with pure css is not the best way. it's better to use canvas or external libraries.

Table of Contents

  • Start Drawing With the Canvas
  • Adding a Few Helper Functions
  • Drawing the Pie Chart
  • The Pie Chart Data Model
  • The Pie Chart Graphical Representation
  • Drawing the Doughnut Chart
  • Adding Labels and a Chart Legend
  • Congratulations

Here is a pie chart without using external libraries, using html5 canvas (fiddle) :

Cara menggunakan css donut chart animation

var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
var lastend = 0;
var data = [60,210,90];
var myTotal = 0;
var myColor = ['#afcc4c', '#95b524','#c1dd54'];
var labels = ['B', 'A', 'C'];

for(var e = 0; e < data.length; e++)
{
  myTotal += data[e];
}

// make the chart 10 px smaller to fit on canvas
var off = 10
var w = (canvas.width - off) / 2
var h = (canvas.height - off) / 2
for (var i = 0; i < data.length; i++) {
  ctx.fillStyle = myColor[i];
  ctx.strokeStyle ='white';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(w,h);
  var len =  (data[i]/myTotal) * 2 * Math.PI
  var r = h - off / 2
  ctx.arc(w , h, r, lastend,lastend + len,false);
  ctx.lineTo(w,h);
  ctx.fill();
  ctx.stroke();
  ctx.fillStyle ='white';
  ctx.font = "20px Arial";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  var mid = lastend + len / 2
  ctx.fillText(labels[i],w + Math.cos(mid) * (r/2) , h + Math.sin(mid) * (r/2));
  lastend += Math.PI*2*(data[i]/myTotal);
}
html, body{
  background: #c4c9e9
}
  <canvas id="can" width="200" height="200" />

fiddle (code is written based on this solution)

But it's better to use libraries for drawing charts. in apex-charts there is an option called sparkline, which helps you to remove the extra stuffs and draw a minimal and clean chart.

Here is a clean donut chart using apex-charts library. (Extra stuffs are removed with sparkline option):

var options = {
  series: [620, 40],
  labels: ['Finished', 'Unfinished'],
  chart: {
    type: 'donut',
    sparkline: {
      enabled: true,
    }
  },
  plotOptions: {
    pie: {
      donut: {
        labels: {
          show: true,
          total: {
            showAlways: false,
            show: true,
            label: 'Total'
          }
        }
      }
    }
  },
};
var chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();

See it on codepen

In this tutorial, I will show you how to use JavaScript and the canvas to display numerical information in the form of pie charts and doughnut charts.

What Is a Pie Chart?

A chart is a statistical tool used to graphically represent numerical data. A pie chart displays that numerical data as a circle divided into slices. The size of each slice is proportional to the numeric value that it stands for.

What Is a Doughnut Chart?

To put it simply, a doughnut chart is a variation on a pie chart. The difference is that the slices are cut towards the center of the pie such that only the rim is visible. In this way, the chart looks like a doughnut, hence the name.

Start Drawing With the Canvas

Before drawing the pie chart, we will take a look at drawing its parts. We will see how we can use the canvas component and JavaScript to draw:

  • a line
  • an arc (a part of a circle)
  • a color-filled shape

To start drawing using the HTML5 canvas, we'll need to create a few things:

  • One folder to hold the project files; let's call this folder piechart-tutorial.
  • One HTML file index.html inside the piechart-tutorial folder. This file will contain the HTML code.
  • One JS file script.js inside the piechart-tutorial folder. This file will contain our JavaScript code.

We'll keep things very simple and add the following code inside index.html:

<html>
<body>
    <canvas id="myCanvas"></canvas>
	<script type="text/javascript" src="script.js"></script>
</body>
</html>

We have the <canvas> element with the ID myCanvas so that we can reference it in our JS code. We then load the JS code via the <script> tag.

Inside script.js, the JS code will first get a reference to the canvas and then set its width and height. To draw on the canvas, we only need a reference to its 2D context, which contains all the drawing methods.

var myCanvas = document.getElementById("myCanvas");
myCanvas.width = 300;
myCanvas.height = 300;

var ctx = myCanvas.getContext("2d");

Adding a Few Helper Functions

Now that we have the canvas set up and also a reference to the drawing canvas, let's define a few JavaScript functions that we will be able to reuse when drawing the pie chart. We will add the functions in our script.js file.

function drawLine(ctx, startX, startY, endX, endY, color){
    ctx.save();
    ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.moveTo(startX,startY);
    ctx.lineTo(endX,endY);
    ctx.stroke();
    ctx.restore();
}

The drawLine function takes six parameters:

  1. ctx: reference to the drawing context
  2. startX: the X coordinate of the line's starting point
  3. startY: the Y coordinate of the line's starting point
  4. endX: the X coordinate of the line's end point
  5. endY: the Y coordinate of the line's end point
  6. color: the color of the line

The first thing we do inside the function is save the current context. After that, we start drawing the line by calling beginPath(). This informs the drawing context that we are starting to draw something new on the canvas. We use moveTo() to set the starting point, call lineTo() to indicate the end point, and then do the actual drawing by calling stroke().

Let's now see how we can draw a part of a circle, also called an arc.

function drawArc(ctx, centerX, centerY, radius, startAngle, endAngle, color){
    ctx.save();
    ctx.strokeStyle = color;
    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, startAngle, endAngle);
    ctx.stroke();
    ctx.restore();
}

The drawArc function takes seven parameters:

  1. ctx: reference to the drawing context
  2. centerX: the X coordinate of the circle's center
  3. centerY: the Y coordinate of the circle's center
  4. radius: the X coordinate of the line's end point
  5. startAngle: the start angle in radians where the portion of the circle starts
  6. endAngle: the end angle in radians where the portion of the circle ends
  7. color: the color of the arc

We've seen how to draw a line and how to draw an arc, so now let's see how to draw a colored shape. Since our goal is to draw a pie chart that is made up of slices, let's create a function that draws a pie slice.

function drawPieSlice(ctx,centerX, centerY, radius, startAngle, endAngle, fillColor, strokeColor) {
    ctx.save();
    ctx.fillStyle = fillColor;
    ctx.strokeStyle = strokeColor;
    ctx.beginPath();
    ctx.moveTo(centerX, centerY);
    ctx.arc(centerX, centerY, radius, startAngle, endAngle, strokeColor);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
}

The drawPieSlice function takes seven parameters:

  1. ctx: reference to the drawing context
  2. centerX: the X coordinate of the circle's center
  3. centerY: the Y coordinate of the circle's center
  4. radius: the X coordinate of the line's end point
  5. startAngle: the start angle in radians where the portion of the circle starts
  6. endAngle: the end angle in radians where the portion of the circle ends
  7. fillColor: the color used to fill the slice
  8. strokeColor: the color used to draw the perimeter of the slice

Here is an example for calling three functions:

drawLine(ctx, 200, 200, 300, 300, "#000");
drawArc(ctx, 250, 250, 150, 0, Math.PI/3, "#000");
drawPieSlice(ctx, 250, 250, 150, Math.PI/2, Math.PI/2 + Math.PI/3, "#F00", "#000");

It will produce this result:

Now we have all the tools necessary to draw a pie chart, so let's see how we use them together.

Drawing the Pie Chart

Conceptually, any chart has two main parts:

  • The data model contains the numerical data to be represented. This is structured in a format specific to the type of chart.
  • The graphical representation is how the numerical data in the data model gets represented by visual elements according to some rules in the form of math formulas.

The Pie Chart Data Model

The most common way to structure the data model for pie charts is a series of categories and corresponding values, where each category and value is associated with a slice of the pie.

As an example, the data model of a pie chart displaying the number of vinyls I have grouped by genre would look something like:

  • Classical music: 16
  • Alternative rock: 12
  • Pop: 18
  • Jazz: 32

This data can be passed to our pie chart drawing class in the form of an object. We will be passing it to our PieChart object later:

{
    "Classical Music": 16, 
    "Alternative Rock": 12, 
    "Pop": 18, 
    "Jazz": 32,
}

The Pie Chart Graphical Representation

The pie chart uses a circle to display the information in the data model by dividing it into slices. Each slice corresponds to a category from the data model, and the size of the slice is proportional to the category value.

My small collection of 88 vinyls has four categories. Each category will get a slice of the pie chart proportional to the number of vinyls in that category.

But how do we measure the size of a slice? That's easy—we do that by the angle at the tip of the slice. All we have to know is that the full circle corresponds to an angle of 360 degrees or 2 * PI. So half a circle would be 180 deg or PI, a quarter 90 deg or PI/2, and so on.

To determine the angle for each category slice, we use the formula:

slice angle = 2 * PI * category value / total value

According to this formula, the 16 classical music vinyls will get a slice angle of approximately 0.36 * PI or 64.8 deg.

Let's get to drawing. For this, we will use a JavaScript class which we will name Piechart. The constructor will receive one options argument, an object containing the following:

  • canvas: reference to the canvas where we want to draw the pie chart
  • data: reference to an object holding the data model
  • colors: an array holding the colors we want to use for each slice
  • seriesName: a string that holds the chart title
  • padding: to tell us how much padding should be added to the chart
  • donutHoleSize: specifies the fraction that should be cut out of the pie chart.
  • titleOptions: an object that contains information about the title's font, size, alignment, color, etc.

The Piechart class also contains one method, draw(), which does the actual drawing of the chart.

var Piechart = function(options){
  constructor(options) {
    this.options = options;
    this.canvas = options.canvas;
    this.ctx = this.canvas.getContext("2d");
    this.colors = options.colors;
    this.titleOptions = options.titleOptions;
    this.totalValue = [...Object.values(this.options.data)].reduce((a, b) => a + b, 0);
    this.radius = Math.min(this.canvas.width / 2, this.canvas.height / 2) - options.padding;
  }

  drawSlices() {
    var colorIndex = 0;
    var startAngle = -Math.PI / 2;

    for (var categ in this.options.data) {
      var val = this.options.data[categ];
      var sliceAngle = (2 * Math.PI * val) / this.totalValue;

      drawPieSlice(
        this.ctx,
        this.canvas.width / 2,
        this.canvas.height / 2,
        this.radius,
        startAngle,
        startAngle + sliceAngle,
        this.colors[colorIndex % this.colors.length]
      );

      startAngle += sliceAngle;
      colorIndex++;
    }
  }
}

The class starts by storing the passed options in different properties inside the constructor() method. It stores the canvas reference and creates a drawing context also stored as a class member. Then it stores the colors array passed as options.

We convert our object values into an array inside the constructor and then use the reduce() method to get the total of all data points. This value is later used to calculate the angle of different slices. We also calculate the radius of our pie chart within the constructor method by taking the minimum value between half of the canvas width and half of the canvas height and then subtracting the padding. Subtracting the padding to calculate the radius helps introduce some space between different chart elements.

The drawSlices() method does all the actual drawing of the pie chart. For each category in the data model, we apply the formula mentioned above to calculate the pie slice angle. Finally, we use the drawPieSlice() function, using the center of the canvas as the center of the slice. We also offset the start and end angle of the slices each time we draw a category, otherwise the slices would overlap.

To use the class, we have to create an instance and then call the drawSlices() method on the created object.

var myPiechart = new Piechart(
    {
    	canvas: myCanvas,
    	data: {
            "Classical Music": 16,
            "Alternative Rock": 12,
            "Pop": 18,
            "Jazz": 32
        },
        colors: ["#80DEEA", "#FFE082", "#FFAB91", "#CE93D8"]
    }
);
myPiechart.drawSlices();

And the result looks like this:

Drawing the Doughnut Chart

We've seen how to draw the pie chart. We also know that a doughnut chart differs only by having a hole in the middle of the chart. How do we draw the hole? We can draw a white circle over the pie chart.

Let's modify the drawSlices() method of the Piechart class to do that.

drawSlices() {
    var colorIndex = 0;
    var startAngle = -Math.PI / 2;
    
    for (var categ in this.options.data) {
      var val = this.options.data[categ];
      var sliceAngle = (2 * Math.PI * val) / this.totalValue;
    
      drawPieSlice(
        this.ctx,
        this.canvas.width / 2,
        this.canvas.height / 2,
        this.radius,
        startAngle,
        startAngle + sliceAngle,
        this.colors[colorIndex % this.colors.length]
      );
    
      startAngle += sliceAngle;
      colorIndex++;
    }
    
    if (this.options.doughnutHoleSize) {
      drawPieSlice(
        this.ctx,
        this.canvas.width / 2,
        this.canvas.height / 2,
        this.options.doughnutHoleSize * this.radius,
        0,
        2 * Math.PI,
        "#FFF",
        "#FFF"
      );
    
      drawArc(
        this.ctx,
        this.canvas.width / 2,
        this.canvas.height / 2,
        this.options.doughnutHoleSize * this.radius,
        0,
        2 * Math.PI,
        "#000"
      );
    }
}

The added code looks in the options parameter for a member variable doughnutHoleSize. If this doesn't exist in the options, then the code will draw the pie chart as before, but if it does exist, then a white circle is drawn with the same center as the pie chart.

The radius of the circle is determined by multiplying the pie chart radius and the value of doughnutHoleSize. This should be a number between 0 and 1, where 0 will result in a pie chart, and any values higher than 0 would result in a doughnut with the hole larger and larger, 1 making the chart invisible.

To draw a doughnut chart with a hole half the size of the chart, we would need to use a doughnutHoleSize of 0.5 and make the following calls:

var myDougnutChart = new Piechart(
    {
    	canvas:myCanvas,
    	data:myVinyls,
    	colors:["#fde23e","#f16e23", "#57d9ff","#937e88"],
    	doughnutHoleSize:0.5
    }
);

myDougnutChart.drawSlices();

And here is the result:

Adding Labels and a Chart Legend

Our pie chart and doughnut chart look pretty good, but we can make them even better by adding two things:

  • value labels: showing the percentage corresponding to each slice
  • a chart legend: showing the categories and their corresponding colors in the chart

Usually, values associated with the slices are represented as percentage values calculated as 100 * value associated with a slice / total value, with the whole circle representing 100%.

For example, in the case of our sample data, vinyls with classical music would represent approximately 26%. It would be nice to be able to write that value right on the corresponding slice. To do that, we will use the fillText(text,x,y) function of the drawing context. This function takes three parameters: the text and the x and y coordinates.

How do we calculate the x and y coordinates at which to place the text? We have to make use of some geometry knowledge and something called polar coordinates. Basically, polar coordinates use a radius and an angle to define the position of a point. The two formulas we will use are:

x = R * cos(angle)

y = R * sin(angle)

We will apply these two formulas to place the text halfway along the pie chart radius and halfway around the angle for each pie slice. To do this, we need to add another method called drawLabels() to our Piechart class:

drawLabels() {
    var colorIndex = 0;
    var startAngle = -Math.PI / 2;
    for (var categ in this.options.data) {
      var val = this.options.data[categ];
      var sliceAngle = (2 * Math.PI * val) / this.totalValue;
      var labelX =
        this.canvas.width / 2 +
        (this.radius / 2) * Math.cos(startAngle + sliceAngle / 2);
      var labelY =
        this.canvas.height / 2 +
        (this.radius / 2) * Math.sin(startAngle + sliceAngle / 2);
    
      if (this.options.doughnutHoleSize) {
        var offset = (this.radius * this.options.doughnutHoleSize) / 2;
        labelX =
          this.canvas.width / 2 +
          (offset + this.radius / 2) * Math.cos(startAngle + sliceAngle / 2);
        labelY =
          this.canvas.height / 2 +
          (offset + this.radius / 2) * Math.sin(startAngle + sliceAngle / 2);
      }
    
      var labelText = Math.round((100 * val) / this.totalValue);
      this.ctx.fillStyle = "black";
      this.ctx.font = "32px Khand";
      this.ctx.fillText(labelText + "%", labelX, labelY);
      startAngle += sliceAngle;
    }
}

The code goes over each slice, calculates the percentage, calculates the position, and uses the fillText() method to draw it on the chart. We have used the fillStyle property to set the text color to black and the font property to set the size and font family of the label. It's also important to note that if the chart is a doughnut chart and the doughnutHoleSize is set, then the label will be pushed towards the edge of the chart to make it centered on the doughnut slice.

And here's how the resulting charts look with the value labels:

To complete our chart, the last thing we will add is the chart legend. Our chart legend will display the categories of our data model and the color used for the corresponding slice. First, we have to make some modifications to our index.html file by adding a <div> tag that will store our legend element.

<html>
<body>
    <canvas id="myCanvas"></canvas>
    <div for="myCanvas"></div>
    <script type="text/javascript" src="script.js"></script>
</body>
</html>

Then in script.js we add the code that creates the content of the legend element. We do this inside the drawLegend() method of the Piechart class:

drawLegend() {
    let pIndex = 0;
    let legend = document.querySelector("div[for='myCanvas']");
    let ul = document.createElement("ul");
    legend.append(ul);

    for (let ctg of Object.keys(this.options.data)) {
      let li = document.createElement("li");
      li.style.listStyle = "none";
      li.style.borderLeft =
        "20px solid " + this.colors[pIndex % this.colors.length];
      li.style.padding = "5px";
      li.textContent = ctg;
      ul.append(li);
      pIndex++;
    }
}

The code looks for a legend element passed via the options parameter. If one is supplied, this element is filled in with the HTML code containing a colored box and the name of the data model category.

Our charts will also have a title to describe what the chart is supposed to show us. We can do that by adding another method called drawTitle() to our PieChart class. The configuration options for the chart title will be taken from the titleOptions object passed to the PieChart class.

drawTitle() {
    this.ctx.save();
    
    this.ctx.textBaseline = "bottom";
    this.ctx.textAlign = this.titleOptions.align;
    this.ctx.fillStyle = this.titleOptions.fill;
    this.ctx.font = `${this.titleOptions.font.weight} ${this.titleOptions.font.size} ${this.titleOptions.font.family}`;
    
    let xPos = this.canvas.width / 2;
    
    if (this.titleOptions.align == "left") {
      xPos = 10;
    }
    if (this.titleOptions.align == "right") {
      xPos = this.canvas.width - 10;
    }
    
    this.ctx.fillText(this.options.seriesName, xPos, this.canvas.height);
    
    this.ctx.restore();
}

One more thing that we can do for our own convenience is to define a draw() method that calls all other methods of the class to draw the whole chart.

draw() {
    this.drawSlices();
    this.drawLabels();
    this.drawTitle();
    this.drawLegend();
}

We also need to make a change to the way we call the drawing of our pie chart like this:

var myPiechart = new PieChart({
  canvas: myCanvas,
  seriesName: "Vinyl records",
  padding: 40,
  doughnutHoleSize: 0.4,
  data: {
    "Classical Music": 16,
    "Alternative Rock": 12,
    "Pop": 18,
    "Jazz": 32
  },
  colors: ["#80DEEA", "#FFE082", "#FFAB91", "#CE93D8"],
  titleOptions: {
    align: "center",
    fill: "black",
    font: {
      weight: "bold",
      size: "18px",
      family: "Lato"
    }
  }
});

myPiechart.draw();

And here is the resulting chart and chart legend:

Congratulations

We have seen that drawing charts using the HTML5 canvas is actually not that hard. It only requires a bit of math and a bit of JavaScript knowledge. You now have everything you need to draw your own pie charts and doughnut charts.

If you want a quick and easy solution for creating not only pie charts and doughnut charts but also loads of other types of charts, you can download the Infographic Charts and Graphics HTML Tags Library or its WordPress plugin counterpart Charts and Graphs WordPress Visual Designer.