how to draw smooth curve through N points using javascript HTML5 canvas? how to draw smooth curve through N points using javascript HTML5 canvas? javascript javascript

how to draw smooth curve through N points using javascript HTML5 canvas?


The problem with joining subsequent sample points together with disjoint "curveTo" type functions, is that where the curves meet is not smooth. This is because the two curves share an end point but are influenced by completely disjoint control points. One solution is to "curve to" the midpoints between the next 2 subsequent sample points. Joining the curves using these new interpolated points gives a smooth transition at the end points (what is an end point for one iteration becomes a control point for the next iteration.) In other words the two disjointed curves have much more in common now.

This solution was extracted out of the book "Foundation ActionScript 3.0 Animation: Making things move". p.95 - rendering techniques: creating multiple curves.

Note: this solution does not actually draw through each of the points, which was the title of my question (rather it approximates the curve through the sample points but never goes through the sample points), but for my purposes (a drawing application), it's good enough for me and visually you can't tell the difference. There is a solution to go through all the sample points, but it is much more complicated (see http://www.cartogrammar.com/blog/actionscript-curves-update/)

Here is the the drawing code for the approximation method:

// move to the first point   ctx.moveTo(points[0].x, points[0].y);   for (i = 1; i < points.length - 2; i ++)   {      var xc = (points[i].x + points[i + 1].x) / 2;      var yc = (points[i].y + points[i + 1].y) / 2;      ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);   } // curve through the last two points ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y);


A bit late, but for the record.

You can achieve smooth lines by using cardinal splines (aka canonical spline) to draw smooth curves that goes through the points.

I made this function for canvas - it's split into three function to increase versatility. The main wrapper function looks like this:

function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {    showPoints  = showPoints ? showPoints : false;    ctx.beginPath();    drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));    if (showPoints) {        ctx.stroke();        ctx.beginPath();        for(var i=0;i<ptsa.length-1;i+=2)                 ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);    }}

To draw a curve have an array with x, y points in the order: x1,y1, x2,y2, ...xn,yn.

Use it like this:

var myPoints = [10,10, 40,30, 100,10]; //minimum two pointsvar tension = 1;drawCurve(ctx, myPoints); //default tension=0.5drawCurve(ctx, myPoints, tension);

The function above calls two sub-functions, one to calculate the smoothed points. This returns an array with new points - this is the core function which calculates the smoothed points:

function getCurvePoints(pts, tension, isClosed, numOfSegments) {    // use input value if provided, or use a default value       tension = (typeof tension != 'undefined') ? tension : 0.5;    isClosed = isClosed ? isClosed : false;    numOfSegments = numOfSegments ? numOfSegments : 16;    var _pts = [], res = [],    // clone array        x, y,           // our x,y coords        t1x, t2x, t1y, t2y, // tension vectors        c1, c2, c3, c4,     // cardinal points        st, t, i;       // steps based on num. of segments    // clone array so we don't change the original    //    _pts = pts.slice(0);    // The algorithm require a previous and next point to the actual point array.    // Check if we will draw closed or open curve.    // If closed, copy end points to beginning and first points to end    // If open, duplicate first points to befinning, end points to end    if (isClosed) {        _pts.unshift(pts[pts.length - 1]);        _pts.unshift(pts[pts.length - 2]);        _pts.unshift(pts[pts.length - 1]);        _pts.unshift(pts[pts.length - 2]);        _pts.push(pts[0]);        _pts.push(pts[1]);    }    else {        _pts.unshift(pts[1]);   //copy 1. point and insert at beginning        _pts.unshift(pts[0]);        _pts.push(pts[pts.length - 2]); //copy last point and append        _pts.push(pts[pts.length - 1]);    }    // ok, lets start..    // 1. loop goes through point array    // 2. loop goes through each segment between the 2 pts + 1e point before and after    for (i=2; i < (_pts.length - 4); i+=2) {        for (t=0; t <= numOfSegments; t++) {            // calc tension vectors            t1x = (_pts[i+2] - _pts[i-2]) * tension;            t2x = (_pts[i+4] - _pts[i]) * tension;            t1y = (_pts[i+3] - _pts[i-1]) * tension;            t2y = (_pts[i+5] - _pts[i+1]) * tension;            // calc step            st = t / numOfSegments;            // calc cardinals            c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1;             c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2);             c3 =       Math.pow(st, 3)  - 2 * Math.pow(st, 2) + st;             c4 =       Math.pow(st, 3)  -     Math.pow(st, 2);            // calc x and y cords with common control vectors            x = c1 * _pts[i]    + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;            y = c1 * _pts[i+1]  + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;            //store points in array            res.push(x);            res.push(y);        }    }    return res;}

And to actually draw the points as a smoothed curve (or any other segmented lines as long as you have an x,y array):

function drawLines(ctx, pts) {    ctx.moveTo(pts[0], pts[1]);    for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);}