javascripthtmlhtml5-canvasdrawing2d

Animate rectangle on canvas


I am trying to animate horizontal bar chart drawing. It will draw slowly and once finished draw the second one. Just like chart.js. It doesn't have to be that advanced. I am just trying to learn canvas drawing + animation. I am using ctx.fillRect and I am not sure if this can be animated.

Update: In my second snippet I have added a timeout around fillRect. It animates the bar but now position.Y doesn't seem to get updated in the right time. The bars are drawn on top of each other.

/**
*  Javascript Carousel
*  Author: Yasin Yaqoobi
*  Project Goal: Build a really simple slider using javascript timer and css transition. 
*  Date: 07/09/16
**/

var Charts = (function(){

var ctx; 
var canvas; 
var legendsHeight = 50; 
var yLabelsWidth = 100;
var scaleRatio; 

function init(canvas, chart){
	setupCanvas(canvas); 
	setScaleRatio(chart);
	if (chart.type.localeCompare('HorizontalBar') != -1){
		drawHorizontalChart(chart);
	}
}

function drawHorizontalChart(chart){
	var canvasHeight = $(canvas).outerHeight();
	var canvaswidth = $(canvas).outerWidth(); 
	var marginRatio = (canvasHeight - 2 * legendsHeight) / chart.data.labels.length * 0.2; 
	var barHeight = ((canvasHeight - 2 * legendsHeight) / chart.data.labels.length) - marginRatio; 
		
	ctx.beginPath();
	ctx.moveTo(yLabelsWidth, legendsHeight);   // (30, 15)
	ctx.lineTo(yLabelsWidth, canvasHeight - legendsHeight); // (30,385)
	ctx.lineTo(canvaswidth, canvasHeight - legendsHeight); // (1000,385)
	ctx.stroke();

	ctx.font = "16px serif";
	ctx.fillText(chart.data.datasets[0].label, (canvaswidth - yLabelsWidth)/2, legendsHeight / 2 );
	var position = {x:yLabelsWidth,y:legendsHeight};

	for (var i = chart.data.labels.length-1; i >= 0; i--){
		ctx.fillStyle = chart.data.datasets[0].backgroundColor[i]; 
		ctx.fillRect(position.x,position.y, scaleRatio * chart.data.datasets[0].data[i], barHeight);
		position.y += marginRatio + barHeight; 
		console.log('this is i ' + i);
	}
}

function setScaleRatio(chart){
	scaleRatio = chart.data.datasets[0].data.reduce(function(prev,curr){
		if (prev > curr){
			return prev; 
		}
		return curr; 
	});

	scaleRatio = $(canvas).outerWidth() / (scaleRatio + 10); 
}

function setupCanvas(canv){
	canvas = canv;
	if (canvas.getContext){
		ctx = canvas.getContext('2d');
	}
	console.log(ctx);
}

var publicApi = {
	init: init
}; 

return publicApi; 

})();


$(document).ready(function(){
	var canvas = document.getElementById("myChart");
	Charts.init(canvas, {
		type: 'HorizontalBar',
		data: {
			labels: ['USA', 'Russia', 'China'], 
			datasets: [
				{
					label: 'Progress Chart',
					backgroundColor: [
						'rgba(255, 99, 132, 0.2)',
		                'rgba(54, 162, 235, 0.2)',
		                'rgba(255, 206, 86, 0.2)'
	                ], 
	                 borderColor: [
		                'rgba(255,99,132,1)',
		                'rgba(54, 162, 235, 1)',
		                'rgba(255, 206, 86, 1)'
	                ], 
	                borderWidth: 1,
	                data: [60, 30, 80]
	            }
			]
		}

	});
});
.container{
	width: 1200px;
	box-shadow: 5px 5px 35px 0px rgba(0,0,0,0.4);
	-webkit-box-shadow: 5px 5px 35px 0px rgba(0,0,0,0.4);
	-moz-box-shadow: 5px 5px 35px 0px rgba(0,0,0,0.4);
	overflow: hidden;
	margin: 0 auto; 
	padding: 5%;
}

canvas{
	margin: 0 auto;
	display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>

<div class="container">
	<h1 class="page-title underline-text">Charts</h1> 
		<div class="charts-area">
		<h3>Progress Chart</h3>
		<canvas id="myChart" width="1000" height="400"></canvas>
	</div>
<script src="jquery-3.1.0.js"></script>
<script src="npo.js"></script>
<script src="index.js"></script>

</body>

/**
*  Javascript Carousel
*  Author: Yasin Yaqoobi
*  Project Goal: Build a really simple slider using javascript timer and css transition. 
*  Date: 07/09/16
**/

var Charts = (function(){

var ctx; 
var canvas; 
var legendsHeight = 50; 
var yLabelsWidth = 100;
var scaleRatio; 

function init(canvas, chart){
	setupCanvas(canvas); 
	setScaleRatio(chart);
	if (chart.type.localeCompare('HorizontalBar') != -1){
		drawHorizontalChart(chart);
	}
}

function drawHorizontalChart(chart){
	var canvasHeight = $(canvas).outerHeight();
	var canvaswidth = $(canvas).outerWidth(); 
	var marginRatio = (canvasHeight - 2 * legendsHeight) / chart.data.labels.length * 0.2; 
	var barHeight = ((canvasHeight - 2 * legendsHeight) / chart.data.labels.length) - marginRatio; 
		
	ctx.beginPath();
	ctx.moveTo(yLabelsWidth, legendsHeight);   // (30, 15)
	ctx.lineTo(yLabelsWidth, canvasHeight - legendsHeight); // (30,385)
	ctx.lineTo(canvaswidth, canvasHeight - legendsHeight); // (1000,385)
	ctx.stroke();

	ctx.font = "16px serif";
	ctx.fillText(chart.data.datasets[0].label, (canvaswidth - yLabelsWidth)/2, legendsHeight / 2 );
	var position = {x:yLabelsWidth,y:legendsHeight};

	for (var i = chart.data.labels.length-1; i >= 0; i--){
		ctx.fillStyle = chart.data.datasets[0].backgroundColor[i]; 
		for (var n = 20; n < scaleRatio * chart.data.datasets[0].data[i]; n+=1){
		(function (ratio){
			setTimeout(function(){
				console.log(ratio);
				ctx.fillRect(position.x,position.y, ratio, barHeight);
			}, 1000);
		})(n);
	}
		position.y += marginRatio + barHeight; 
		console.log('this is positionY ' + position.y);
	}
}

function setScaleRatio(chart){
	scaleRatio = chart.data.datasets[0].data.reduce(function(prev,curr){
		if (prev > curr){
			return prev; 
		}
		return curr; 
	});

	scaleRatio = $(canvas).outerWidth() / (scaleRatio + 10); 
}

function setupCanvas(canv){
	canvas = canv;
	if (canvas.getContext){
		ctx = canvas.getContext('2d');
	}
	console.log(ctx);
}

var publicApi = {
	init: init
}; 

return publicApi; 

})();


$(document).ready(function(){
	var canvas = document.getElementById("myChart");
	Charts.init(canvas, {
		type: 'HorizontalBar',
		data: {
			labels: ['USA', 'Russia', 'China'], 
			datasets: [
				{
					label: 'Progress Chart',
					backgroundColor: [
						'rgba(255, 99, 132, 0.2)',
		                'rgba(54, 162, 235, 0.2)',
		                'rgba(255, 206, 86, 0.2)'
	                ], 
	                 borderColor: [
		                'rgba(255,99,132,1)',
		                'rgba(54, 162, 235, 1)',
		                'rgba(255, 206, 86, 1)'
	                ], 
	                borderWidth: 1,
	                data: [60, 30, 80]
	            }
			]
		}

	});
});
.container{
	width: 1200px;
	box-shadow: 5px 5px 35px 0px rgba(0,0,0,0.4);
	-webkit-box-shadow: 5px 5px 35px 0px rgba(0,0,0,0.4);
	-moz-box-shadow: 5px 5px 35px 0px rgba(0,0,0,0.4);
	overflow: hidden;
	margin: 0 auto; 
	padding: 5%;
}

canvas{
	margin: 0 auto;
	display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>

<div class="container">
	<h1 class="page-title underline-text">Charts</h1> 
		<div class="charts-area">
		<h3>Progress Chart</h3>
		<canvas id="myChart" width="1000" height="400"></canvas>
	</div>
<script src="jquery-3.1.0.js"></script>
<script src="npo.js"></script>
<script src="index.js"></script>

</body>


Solution

  • Here's a small sample using setTimeout to draw a horizontal bar one after another. I have a sample timing setup for you that waits for the previous bar to finish and redraws every 10 milliseconds. Run it to see.

    <!DOCTYPE html>
    <html>
    
    <body>
    
    <canvas id="canvas" height="300" width="600"></canvas>
    
    <script>
    
    var canvas = document.getElementById('canvas'),
        ctx = canvas.getContext('2d');
    
    var bars = [
        { name: 'bar1', value: 567 },
        { name: 'bar2', value: 394 }
    ];
    
    var delay = 0,
        speed = 10;
    
    for(var i = 0; i < bars.length; ++i){
        for(var l = 0; l < bars[i].value; ++l) setTimeout(ctx.fillRect.bind(ctx,0,50 + 100 * i, l, 75),(i > 0 ? delay+(bars[i-1].value*speed) : 0) + delay+l*speed);
    }
    
    </script>
    
    </body>
    
    </html>

    EDIT: Cleaner, sums delay for multiple bars

    <!DOCTYPE html>
    <html>
    
    <body>
    
    <canvas id="canvas" height="300" width="675"></canvas>
    
    <script>
    
    var canvas = document.getElementById('canvas'),
        ctx = canvas.getContext('2d');
    
    var bars = [
        { name: 'bar1', value: 567 },
        { name: 'bar2', value: 394 },
        { name: 'bar3', value: 217 }
    ];
    
    var delay = 0, // accrued delay
        speed = 3; // drawing speed (milliseconds per render)
    
    for(var i = 0; i < bars.length; ++i){
        for(var l = 0; l < bars[i].value; ++l){
            setTimeout(
                ctx.fillRect.bind(ctx,0,50 + 100 * i, l, 75),
                (i == 0 ? 0 : delay) + l*speed
            );
        }
        delay += bars[i].value * speed;
    }
    
    </script>
    
    </body>
    
    </html>