HTML5 Dynamic Charts – JavaScript and the Canvas 2D Context

Now that the HTML5 and Canvas 2D specifications are feature complete (December 2012) and most companies have pushed IE9 to their clients, I wanted to take advantage of the canvas element's 2D Context to create dynamic charts - without using Flash, ActiveX, Web Parts, browser add-ins or anything else extraneous to HTML and JavaScript. The first addition to my JavaScript chart library was a pieChart object.

Go right to the full featured demo and download the source from my example site. Or continue reading for a detailed explanation of how I created the chart objects.

To start creating my charts, I'll define the base object from which I'll derive all my chart objects including the pieChart.

<script type="text/javascript" src="jquery-1.9.1.min.js"></script>
<script type="text/javascript">
    function chart() { }
    chart.prototype = {
        canvas: null,
        data: null,
        colors: null,
        currentColor: null,
        currentColorIndex: null,
        width: null,
        height: null,
        initChart: function(canvasWidth, canvasHeight, chartData, chartColors) {
            this.data = chartData.slice();
            this.colors = chartColors.slice();
            this.canvas = document.createElement('canvas');
            this.canvas.width = canvasWidth;
            this.canvas.height = canvasHeight;
            this.width = canvasWidth;
            this.height = canvasHeight;
        },
        randomColor: function() {
            var r = ('0' + (Math.random() * 256 | 0).toString(16)).slice(-2),
            g = ('0' + (Math.random() * 256 | 0).toString(16)).slice(-2),
            b = ('0' + (Math.random() * 256 | 0).toString(16)).slice(-2);
            return '#' + r + g + b;
        },
        resetColor: function() { this.currentColorIndex = -1; },
        moveToNextColor: function() {
            if (!this.colors) {//check for 'null' or 'undefined' color array. if so, use a random color
                this.currentColor = this.randomColor();
                return this.currentColor;
            }
            else {
                if (this.currentColorIndex == null) {
                    this.currentColorIndex = 0;
                }
                else {
                    this.currentColorIndex = this.currentColorIndex + 1
                    if ((this.currentColorIndex >= this.colors.length) || (this.currentColorIndex < 0)) { this.currentColorIndex = 0; }
                }
                this.currentColor = this.colors[this.currentColorIndex];
                return this.currentColor;
            }
        }
    }
</script>

Although I don't use jQuery (line 1) in the base chart object, I'll use it later to extend the prototype of each chart object. Lines 13 and 14 initialize the data and color arrays for the chart. Notice the slice() array method. Since passing a JavaScript array object behaves like a "pass by reference", I use slice() to create new arrays with the same values as the parameter arrays. Now I won't accidentally change the originals while I'm manipulating the chart's data or colors.

The pieChart object is defined as follows:

<script type="text/javascript">
function pieChart(chartData, chartDiameter, chartColors) {
        this.diameter = chartDiameter;
        this.initChart(chartDiameter, chartDiameter, chartData, chartColors);
        this.draw();
}
pieChart.prototype = {

        draw: function() {

            this.resetColor();
            this.canvas.width = this.diameter;
            this.canvas.height = this.diameter;
            var canvas = this.canvas;
            var amounts = this.data.slice();

            var context = canvas.getContext('2d');
            var x = canvas.width / 2;
            var y = canvas.height / 2;
            var radius = Math.min(x, y);
            var counterClockwise = false;

            var percentDenominator = 0;
            for (var i = 0; i < amounts.length; i++) { percentDenominator += amounts[i]; }
            for (var i = 1; i < amounts.length; i++) { amounts[i] += amounts[i - 1]; }
            for (var i = 0; i < amounts.length; i++) { amounts[i] = amounts[i] / percentDenominator * 2 * Math.PI; }

            var points = new Array();
            points.push([0, amounts[0]]);
            for (var i = 1; i < amounts.length; i++) { points.push([amounts[i - 1], amounts[i]]); }

            for (var i = 0; i < amounts.length; i++) {
                context.lineWidth = 1;
                context.beginPath();
                context.moveTo(x, y);
                context.arc(x, y, radius, points[i][0], points[i][1], false);
                context.lineTo(x, y);
                context.closePath();
                context.fillStyle = this.moveToNextColor();
                context.fill();
                context.strokeStyle = "#ffffff";
                context.stroke();
            }
        }
}
$.extend(pieChart.prototype, chart.prototype);
</script>

The idea is to change each data item into a running percentage of the total circumference of the pie (no pun intended - lines 23-26). Then we create a multidimensional array such that the end point of the first arc is the starting point of the next one (lines 28-30). We now draw consecutive arcs along the circle shown in Figure 1. Each arc has a radius that is equal to the radius of the pie chart and a length that is the percentage of the circumference correlating to the value of the input data item. The last line (56) of the script extends the pieChart with the prototype of the base chart object.

For example, [10,20,20,50] is transformed to
[0.10, 0.30, 0.50, 1.00] and then we create the points array
[ [0, 0.10*2Pi], [0.10*2Pi, 0.30*2Pi], [0.30*2Pi, 0.50*2Pi], [0.50*2Pi, 1.00*2Pi ]

Figure 1

Here's some sample code to create the chart when the document is ready and a function to call when you need to update it:

var myPie = new pieChart([10,20,20,50],200,["#993333","#669966","#FFCC66","#336699"]);
   
$(document).ready(function() { 
   $("div#myPieChart").html(myPie.canvas);
});

function updateChart(newData){
     myPie.data = newData;
     myPie.draw();
}

to Change the chart Data to [120,140,75,90]. Several other properties of the chart object can be set to change the pie chart. For example, setting the color property to an empty string will cause the chart to be drawn using random colors. See a full featured demo and download the source from my examples site. The source download contains bar and line charts in addition to the pie chart. I'm adding new charts all the time, so check back often!

Tagged with:
Posted in HTML5, JavaScript and jQuery, Mathematics
Advertisement