Search code examples
javascriptjquerycanvasjquery-animatedonut-chart

Animate lines in a donut chart using a canvas


Basically I try to animate the lines in a donut chart (see example), so that each of them is counted up with document.ready. I tried to adopt the solution from here and here

As you can see, the countUp solution is already working well, but it needs to be connected to the chart, so that it is drawn on page load.

My knowledge regarding canvas isn´t amazing. I would be very happy, if anybody could help. Here is the code: https://jsfiddle.net/9oyoh67x/10/

$(document).ready(function () {        
var canvas = document.getElementById('canvas');
            var ctx = canvas.getContext('2d');
            console.log(ctx);
            ctx.lineWidth = 19;
            ctx.lineCap = 'round';
            ctx.shadowBlur = 10;
            donutChart(); 
            function degtoRad(degree) {
                var factor = Math.PI / 180; // = 1 deg = 0.01745 rad
                return degree * factor; // for 360 = 6.28 = 360°
            }

            /*function move() {
                var elem = document.getElementById("canvas");
                var width = 0;
                var id = setInterval(countUp, 1000); }
            */

            $(function () {
                var countUp = setInterval(function () { donutChart }, 40);
                function count($this) {
                    var current = parseInt($this.html(), 10);
                    $this.html(++current);
                    if (current !== $this.data('count')) {
                        setTimeout(function () { count($this) }, 50);
                    }
                }
                $(".testspan1").each(function () {
                    $(this).data('count', parseInt($(this).html(), 10));
                    $(this).html('0');
                    count($(this));
                });   
                 $(".testspan2").each(function () {
                    $(this).data('count', parseInt($(this).html(), 10));
                    $(this).html('0');
                    count($(this));
                });   
                 $(".testspan3").each(function () {
                    $(this).data('count', parseInt($(this).html(), 10));
                    $(this).html('0');
                    count($(this));
                });   
                 $(".testspan4").each(function () {
                    $(this).data('count', parseInt($(this).html(), 10));
                    $(this).html('0');
                    count($(this));
                });   
            });            

            function donutChart() {
                var factor_calc = Math.PI / 180;
                var record = $('.testspan1').text(); // equal to 100% or 360°
                var average = $('.testspan2').text();
                var income = $('.testspan2').text();
                var target = $('.testspan3').text();


                // Record
                ctx.strokeStyle = 'rgba(115, 100, 164, 1)';
                ctx.beginPath();
                ctx.arc(250, 250, 200, degtoRad(270), degtoRad(270 + (record / record) * 360));
                ctx.stroke();

                // Average
                ctx.strokeStyle = 'rgba(125, 131, 164, 1)';
                ctx.beginPath();
                ctx.arc(250, 250, 170, degtoRad(270), degtoRad(270 + (average / record) * 360));
                ctx.stroke();

                //Income
                ctx.strokeStyle = 'rgba(75, 181, 164, 1)';
                ctx.beginPath();
                ctx.arc(250, 250, 140, degtoRad(270), degtoRad(270 + (income / record) * 360));
                ctx.stroke();

                // Target
                ctx.strokeStyle = 'rgba(26, 221, 164, 1)';
                ctx.beginPath();
                ctx.arc(250, 250, 110, degtoRad(270), degtoRad(270 + (target / record) * 360));
                ctx.stroke();

                // Record
                ctx.font = "25px Philosopher";
                ctx.fillStyle = 'rgba(115, 100, 164, 1)';
                ctx.fillText(record, 220, 200);

                //Average
                ctx.font = "25px Philosopher";
                ctx.fillStyle = 'rgba(125, 131, 164, 1)';
                ctx.fillText(average, 220, 230);

                //Income
                ctx.font = "30px Philosopher";
                ctx.fillStyle = 'rgba(75, 181, 164, 1)';
                ctx.fillText(income, 220, 260);

                // Target
                ctx.font = "35px Philosopher";
                ctx.fillStyle = 'rgba(26, 221, 164, 1)';
                ctx.fillText(target, 220, 290);

                // Euro
                ctx.font = "60px Philosopher";
                ctx.fillStyle = 'rgba(26, 221, 164, 1)';
                ctx.fillText('€', 280, 255);
            }

            var options = {
                useEasing: true,
                useGrouping: true,
                separator: ',',
                decimal: '',
                prefix: '',
                suffix: '€'

            };
        });

HTML:

        <div class="canvasdiv">
        <canvas id="canvas" width="500" height="500"></canvas>
        <img id="myImage" />
        <span class='testspan1'>1250</span>
        <span class='testspan2'>250</span>
        <span class='testspan3'>450</span>
        <span class='testspan4'>130</span>
    </div>

For people with similar problems, here are some further related links (using no canvas):


Solution

  • I made it. Here is my solution (https://jsfiddle.net/9oyoh67x/12/):

    // requestAnimationFrame Shim
    (function start() {
        var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
            window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
        window.requestAnimationFrame = requestAnimationFrame;
    })();
    
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    console.log(ctx);
    ctx.lineWidth = 19;
    ctx.lineCap = 'round';
    ctx.shadowBlur = 10;
    var record = 820; // equal to 100% or 360°
    var record_count = (record / record * 100)
    var average = (229 / record * 100);
    var income = (490 / record * 100);
    var target = (750 / record * 100);
    var counterClockwise = true;
    var circ = Math.PI * 2;
    var quart = Math.PI / 2;
    var startTime1 = null;
    var startTime2 = null;
    var startTime3 = null;
    var startTime4 = null;
    var duration = null;
    var duration1 = null;
    var duration2 = null;
    var duration3 = null;
    var duration4 = null;
    var options = {
        useEasing: true,
        useGrouping: true,
        separator: ',',
        decimal: '0',
        prefix: '',
        suffix: ''
    };
    
    
    function animate_rec(time) {
    
        if (!startTime1) {
            startTime1 = time;
        }
    
        var delta1 = Math.min(1, (time - startTime1) / duration1);
        var curPerc = ((2 * Math.PI) / 100) * (record_count * delta1);
        console.log(curPerc)
        var innerNumRec = (record * delta1);
        //console.log(innerNumRec);
    
        ctx.strokeStyle = 'rgba(115, 100, 164, 1)';
        ctx.beginPath();
        ctx.arc(250, 250, 200, -quart, curPerc - quart);
        ctx.stroke();
    
        if (delta1 < 1) {
            requestAnimationFrame(animate_rec);
            ctx.clearRect(215, 180, 50, 30);
        } else {
            startTime1 = null;
            ctx.clearRect(215, 180, 50, 30);
        }
    
    
    
        // Record
        ctx.font = "25px Philosopher";
        ctx.fillStyle = 'rgba(115, 100, 164, 1)';
        ctx.fillText(Math.round(innerNumRec), 220, 200);
    }
    
    var startAnim_rec = function () {
        duration1 = 3000;
        requestAnimationFrame(animate_rec);
    };
    
    function animate_avg(time) {
    
        if (!startTime2) {
            startTime2 = time;
        }
    
        var delta2 = Math.min(1, (time - startTime2) / duration2);
        var curPerc = ((2 * Math.PI) / 100) * (average * delta2);
        var innerNumAvg = ((average * record / 100) * delta2);
        console.log(innerNumAvg);
    
        ctx.strokeStyle = 'rgba(125, 131, 164, 1)';
        ctx.beginPath();
        ctx.arc(250, 250, 170, -quart, curPerc - quart);
        ctx.stroke();
    
        if (delta2 < 1) {
            requestAnimationFrame(animate_avg);
            ctx.clearRect(215, 210, 70, 50);
        } else {
            startTime2 = null;
            ctx.clearRect(215, 210, 70, 50);
        }
    
        // Average
        ctx.font = "30px Philosopher";
        ctx.fillStyle = 'rgba(125, 131, 164, 1)';
        ctx.fillText(Math.round(innerNumAvg), 220, 230);
    
    }
    
    var startAnim_avg = function () {
        duration2 = 3200;
        requestAnimationFrame(animate_avg);
    };
    
    function animate_inc(time) {
    
        if (!startTime3) {
            startTime3 = time;
        }
    
        var delta3 = Math.min(1, (time - startTime3) / duration3);
        var curPerc = ((2 * Math.PI) / 100) * (income * delta3);
        var innerNumInc = ((income * record / 100) * delta3);
        console.log(innerNumInc);
    
        ctx.strokeStyle = 'rgba(75, 181, 164, 1)';
        ctx.beginPath();
        ctx.arc(250, 250, 140, -quart, curPerc - quart);
        ctx.stroke();
    
        if (delta3 < 1) {
            requestAnimationFrame(animate_inc);
            ctx.clearRect(215, 230, 70, 50);
        } else {
            startTime3 = null;
            ctx.clearRect(215, 230, 70, 50);
        }
    
        // Income
        ctx.font = "35px Philosopher";
        ctx.fillStyle = 'rgba(75, 181, 164, 1)';
        ctx.fillText(Math.round(innerNumInc), 220, 260);
    }
    
    var startAnim_inc = function () {
        duration3 = 3400;
        requestAnimationFrame(animate_inc);
    };
    
    function animate_tar(time) {
        if (!startTime4) {
            startTime4 = time;
        }
    
        var delta4 = Math.min(1, (time - startTime4) / duration4);
        var curPerc = ((2 * Math.PI) / 100) * (target * delta4);
        var innerNumTar = ((target * record / 100) * delta4);
        console.log(innerNumTar);
    
        ctx.strokeStyle = 'rgba(26, 221, 164, 1)';
        ctx.beginPath();
        ctx.arc(250, 250, 110, -quart, curPerc - quart);
        ctx.stroke();
    
        if (delta4 < 1) {
            requestAnimationFrame(animate_tar);
            ctx.clearRect(220, 260, 70, 50);
        } else {
            startTime4 = null;
            ctx.clearRect(220, 260, 70, 50);
        }
    
        // Target
        ctx.font = "40px Philosopher";
        ctx.fillStyle = 'rgba(26, 221, 164, 1)';
        ctx.fillText(Math.round(innerNumTar), 220, 290);
    }
    
    var startAnim_tar = function () {
        duration4 = 3600;
        requestAnimationFrame(animate_tar);
    };
    
    startAnim_rec();
    startAnim_avg();
    startAnim_inc();
    startAnim_tar();
    
    // Euro
    ctx.font = "60px Philosopher";
    ctx.fillStyle = 'rgba(26, 221, 164, 1)';
    ctx.fillText('€', 280, 255);});