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

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 points
var tension = 1;

drawCurve(ctx, myPoints); //default tension=0.5
drawCurve(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]);
}

_x000D_
_x000D_
var ctx = document.getElementById("c").getContext("2d");_x000D_
_x000D_
_x000D_
function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) {_x000D_
_x000D_
  ctx.beginPath();_x000D_
_x000D_
  drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments));_x000D_
  _x000D_
  if (showPoints) {_x000D_
    ctx.beginPath();_x000D_
    for(var i=0;i<ptsa.length-1;i+=2) _x000D_
      ctx.rect(ptsa[i] - 2, ptsa[i+1] - 2, 4, 4);_x000D_
  }_x000D_
_x000D_
  ctx.stroke();_x000D_
}_x000D_
_x000D_
_x000D_
var myPoints = [10,10, 40,30, 100,10, 200, 100, 200, 50, 250, 120]; //minimum two points_x000D_
var tension = 1;_x000D_
_x000D_
drawCurve(ctx, myPoints); //default tension=0.5_x000D_
drawCurve(ctx, myPoints, tension);_x000D_
_x000D_
_x000D_
function getCurvePoints(pts, tension, isClosed, numOfSegments) {_x000D_
_x000D_
  // use input value if provided, or use a default value  _x000D_
  tension = (typeof tension != 'undefined') ? tension : 0.5;_x000D_
  isClosed = isClosed ? isClosed : false;_x000D_
  numOfSegments = numOfSegments ? numOfSegments : 16;_x000D_
_x000D_
  var _pts = [], res = [], // clone array_x000D_
      x, y,   // our x,y coords_x000D_
      t1x, t2x, t1y, t2y, // tension vectors_x000D_
      c1, c2, c3, c4,  // cardinal points_x000D_
      st, t, i;  // steps based on num. of segments_x000D_
_x000D_
  // clone array so we don't change the original_x000D_
  //_x000D_
  _pts = pts.slice(0);_x000D_
_x000D_
  // The algorithm require a previous and next point to the actual point array._x000D_
  // Check if we will draw closed or open curve._x000D_
  // If closed, copy end points to beginning and first points to end_x000D_
  // If open, duplicate first points to befinning, end points to end_x000D_
  if (isClosed) {_x000D_
    _pts.unshift(pts[pts.length - 1]);_x000D_
    _pts.unshift(pts[pts.length - 2]);_x000D_
    _pts.unshift(pts[pts.length - 1]);_x000D_
    _pts.unshift(pts[pts.length - 2]);_x000D_
    _pts.push(pts[0]);_x000D_
    _pts.push(pts[1]);_x000D_
  }_x000D_
  else {_x000D_
    _pts.unshift(pts[1]); //copy 1. point and insert at beginning_x000D_
    _pts.unshift(pts[0]);_x000D_
    _pts.push(pts[pts.length - 2]); //copy last point and append_x000D_
    _pts.push(pts[pts.length - 1]);_x000D_
  }_x000D_
_x000D_
  // ok, lets start.._x000D_
_x000D_
  // 1. loop goes through point array_x000D_
  // 2. loop goes through each segment between the 2 pts + 1e point before and after_x000D_
  for (i=2; i < (_pts.length - 4); i+=2) {_x000D_
    for (t=0; t <= numOfSegments; t++) {_x000D_
_x000D_
      // calc tension vectors_x000D_
      t1x = (_pts[i+2] - _pts[i-2]) * tension;_x000D_
      t2x = (_pts[i+4] - _pts[i]) * tension;_x000D_
_x000D_
      t1y = (_pts[i+3] - _pts[i-1]) * tension;_x000D_
      t2y = (_pts[i+5] - _pts[i+1]) * tension;_x000D_
_x000D_
      // calc step_x000D_
      st = t / numOfSegments;_x000D_
_x000D_
      // calc cardinals_x000D_
      c1 =   2 * Math.pow(st, 3)  - 3 * Math.pow(st, 2) + 1; _x000D_
      c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); _x000D_
      c3 =     Math.pow(st, 3) - 2 * Math.pow(st, 2) + st; _x000D_
      c4 =     Math.pow(st, 3) -    Math.pow(st, 2);_x000D_
_x000D_
      // calc x and y cords with common control vectors_x000D_
      x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x;_x000D_
      y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y;_x000D_
_x000D_
      //store points in array_x000D_
      res.push(x);_x000D_
      res.push(y);_x000D_
_x000D_
    }_x000D_
  }_x000D_
_x000D_
  return res;_x000D_
}_x000D_
_x000D_
function drawLines(ctx, pts) {_x000D_
  ctx.moveTo(pts[0], pts[1]);_x000D_
  for(i=2;i<pts.length-1;i+=2) ctx.lineTo(pts[i], pts[i+1]);_x000D_
}
_x000D_
canvas { border: 1px solid red; }
_x000D_
<canvas id="c"><canvas>
_x000D_
_x000D_
_x000D_

This results in this:

Example pix

You can easily extend the canvas so you can call it like this instead:

ctx.drawCurve(myPoints);

Add the following to the javascript:

if (CanvasRenderingContext2D != 'undefined') {
    CanvasRenderingContext2D.prototype.drawCurve = 
        function(pts, tension, isClosed, numOfSegments, showPoints) {
       drawCurve(this, pts, tension, isClosed, numOfSegments, showPoints)}
}

You can find a more optimized version of this on NPM (npm i cardinal-spline-js) or on GitLab.

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to canvas

How to make canvas responsive How to fill the whole canvas with specific color? Use HTML5 to resize an image before upload Convert canvas to PDF Scaling an image to fit on canvas Split string in JavaScript and detect line break Get distance between two points in canvas canvas.toDataURL() SecurityError Converting Chart.js canvas chart to image using .toDataUrl() results in blank image Chart.js canvas resize

Examples related to html5-canvas

Scaling an image to fit on canvas Tainted canvases may not be exported How to fix getImageData() error The canvas has been tainted by cross-origin data? JS Client-Side Exif Orientation: Rotate and Mirror JPEG Images Resize image with javascript canvas (smoothly) How to show a running progress bar while page is loading HTML5 Canvas Resize (Downscale) Image High Quality? HTML5 Canvas Rotate Image Real mouse position in canvas Changing three.js background to transparent or other color

Examples related to bezier

how to draw smooth curve through N points using javascript HTML5 canvas? How to convert a 3D point into 2D perspective projection?

Examples related to spline

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