Smooth Curves on Canvas

The Challenge

Draw a smooth line following a given set of points.

var p = [[72,56],[96,71],[128,38],[78,12],[14,62],[112,124],[176,24],[226,74],[187,111]];

Drawing these points and connecting them with simple line segments produces:

ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.moveTo(p[0][0],p[0][1]);
for(i = 1; i < p.length; i+=2){
	ctx.lineTo(p[i][0],p[i][1]);
}
ctx.stroke();

Connecting the dots with quadratic curves produces:

ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
ctx.moveTo(p[0][0],p[0][1]);
for(i = 1; i+1 < p.length; i+=2){
	ctx.quadraticCurveTo(p[i][0],p[i][1],p[i+1][0],p[i+1][1]);
}
ctx.stroke();

It is clear that sharp corners occur when one curve ends and another begins. These corners exist wherever there is a difference in the slopes between line's end and the next line's beginning.

The key to problem is noticing how at each corner, the slope of the curve is precisely equal to the slope of the straight green segment at the curves endpoints.

So we want the slope at the end of each curve to match the slope at the beginning of the next curve.

To pull this trick off, we can use the midpoints of the original point set as endpoints. Drawing the midpoints in red:

ctx.fillStyle = "blue";
for(i = 0; i < p.length; i++){	
	ctx.fillRect(p[i][0]-3,p[i][1]-3,6,6);
}

function avgPoint(p1,p2){
	var x = (p1[0] + p2[0]) / 2;
	var y = (p1[1] + p2[1]) / 2;
	return [x,y];
}

ctx.fillStyle = "red";
for(i = 0; i+1 < p.length; i++){
	var mid = avgPoint(p[i],p[i+1]);
	ctx.fillRect(mid[0]/2-3,mid[1]/2-3,6,6);
}

Using this new set of points, red & blue both, look at the slope of each half-line compared to the slope of its other half-line. They are equal!

By using the red midpoints as the endpoints for our curves, we guarantee that each curve's end will have the same slope as the next curve's beginning, forming a totally smooth path from start to finish.

The Solution

var mid = avgPoint(p[0],p[1]);
ctx.beginPath();
ctx.moveTo(p[0][0],p[0][1]);
ctx.lineTo(mid[0],mid[1]);
for(var i=1; i+1 < p.length; i++){
	mid = avgPoint(p[i], p[i+1]);
	ctx.quadraticCurveTo(p[i][0], p[i][1], mid[0], mid[1]);
}
ctx.lineTo(p[p.length-1][0], p[p.length-1][1]);
ctx.stroke();
// Joey Lemberg // Jan 31, 2014 // joey@yangcanvas.com //

Finally, drawing our smooth curve without anything else on the canvas.

Much better than we could have done only useing straight lines!



`