javascriptcurvelineto

Javascript canvas line graph smooth curve


I have the following graph draw from array data. I want to use plain javascript, no libraries.

  1. How could I curve this so its not straight lines? Basically I am trying to replicate youtube video heatmap graph (most viewable times through video). Nothing fancy, just so its curved.

  2. I am not sure if getMaxY function is accurate.

var canvas = document.getElementById("myCanvas");
var c = canvas.getContext("2d");
var values = [10, 22, 64, 9, 97, 5, 11, 45, 33, 15, 55, 19];
var width = 600;
var height = 50;

c.strokeRect(0, 0, width, height)
c.beginPath();
c.moveTo(0, height);

c.lineTo(getXPixel(0), getYPixel(values[0]));

for(var i = 1; i < values.length; i ++) {
    c.lineTo(getXPixel(i), getYPixel(values[i]));
}

c.lineTo(width, height);
c.lineTo(0, height);

c.fillStyle = "rgba(44, 44, 44, 0.2)";
c.fill();



function getMaxY() {
    var max = 0;
    for(var i = 0; i < values.length; i ++) {
        if(values[i] > max) {
            max = values[i];
        }
    }
    max += 10 - max % 10;
    return max;
}

function getXPixel(val) {
    return (width / (values.length-1)) * val;
}

function getYPixel(val) {
    return height - ((height / getMaxY()) * val);
}
<canvas id="myCanvas" width="600" height="50" ></canvas>


Solution

  • You can use ctx.quadraticCurveTo function with a "look forward" control point to make the line curvy.

    UPDATE: I added the previous line for comparison, we can see it's not very precise. I'll experiment with another function. Maybe bezierCurveTo will be better.

    UPDATE 2: My lazy solution for a better precision is to use more points (by adding the averages between each points)

    var canvas = document.getElementById("myCanvas");
    var c = canvas.getContext("2d");
    var values = [10, 22, 64, 9, 97, 5, 11, 45, 33, 15, 55, 19];
    var width = canvas.width;
    var height = canvas.height;
    
    // add more points for better precision
    var newValues = [values[0]]
    for (var i = 1; i < values.length; i++) {
      newValues.push((values[i - 1] + values[i]) / 2)
      newValues.push(values[i])
    }
    values = newValues
    
    
    c.strokeRect(0, 0, width, height)
    c.beginPath();
    c.moveTo(0, height);
    
    c.lineTo(getXPixel(0), getYPixel(values[0]));
    
    for (var i = 1; i < values.length - 2; i++) {
      const xc = (getXPixel(i + 1) + getXPixel(i)) / 2;
      const yc = (getYPixel(values[i + 1]) + getYPixel(values[i])) / 2;
      c.quadraticCurveTo(getXPixel(i), getYPixel(values[i]), xc, yc);
    }
    
    // Draw the last segment
    c.quadraticCurveTo(
      getXPixel(values.length - 2),
      getYPixel(values[values.length - 2]),
      getXPixel(values.length - 1),
      getYPixel(values[values.length - 1])
    );
    
    
    c.lineTo(width, height);
    c.lineTo(0, height);
    
    c.fillStyle = "rgba(244, 0, 0, 0.82)";
    c.fill();
    c.closePath()
    
    // old line code, for comparison
    c.beginPath();
    
    c.moveTo(getXPixel(0), getYPixel(values[0]));
    
    for (var i = 1; i < values.length; i++) {
      c.lineTo(getXPixel(i), getYPixel(values[i]));
    }
    c.stroke()
    c.closePath()
    
    
    function getMaxY() {
      var max = 0;
      for (var i = 0; i < values.length; i++) {
        if (values[i] > max) {
          max = values[i];
        }
      }
      max += 10 - max % 10;
      return max;
    }
    
    function getXPixel(val) {
      return (width / (values.length - 1)) * val;
    }
    
    function getYPixel(val) {
      return height - ((height / getMaxY()) * val);
    }
    #myCanvas {
      background: gray;
    }
    <canvas id="myCanvas" width="600" height="150"></canvas>