Search code examples
javascripthtmlcanvaschartsarea

draw on html canvas chart negative and positive areas


I'm creating an individual chart based on HTML canvas. I want to draw negative and positive values in other colors but can't achieve it so far.

Here is my actual chart:

enter image description here

Here is what I want to achieve:

enter image description here

I can think of two solutions. Either I can draw it with a gradient that only goes down to zero.

Or each positive and each negative surface is drawn separately. But then I still need the intersections.

Here are my original values:

[
  { "x": 0, "y": 750 },
  { "x": 1, "y": -200 },
  { "x": 2, "y": 60 },
  { "x": 3, "y": 60 },
  { "x": 4, "y": 120 },
  { "x": 5, "y": 505 }
]

Here are my transformed pixel values:

[
  { "x": 0, "y": 236}, // to draw the area
  { "x": 0, "y": 0},
  { "x": 173, "y": 300}, 
  { "x": 346, "y": 217}, 
  { "x": 519, "y": 217}, 
  { "x": 692, "y": 198}, 
  { "x": 865, "y": 77}, 
  { "x": 865, "y": 236} // to draw the area
]

Do you have an idea of how the goal can be realized? Thank you!


Solution

  • I had some fun making a basic chart renderer using the canvas. I hope you find the following code useful. (if something is unclear don't hesitate to ask)

    const c = document.getElementById("mycanvas");
    const cc = c.getContext("2d");
    
    const points = [
      750, -200, 60, 60, 120, 505
    ];
    
    const maxY = Math.max(...points);
    const minY = Math.min(...points);
    
    //used to scale and fit the graph into the canvas
    const xaxis = (maxY / (maxY - minY)) * c.height;
    const yscale = -c.height / (maxY - minY);
    const xscale = c.width / (points.length - 1);
    
    const poscolor = "cornflowerblue";
    const negcolor = "crimson";
    cc.fillStyle = points[0] >= 0 ? poscolor : negcolor;
    
    //start at (0, 0) and line to first point
    cc.beginPath();
    cc.moveTo(0, xaxis);
    cc.lineTo(0, points[0] * yscale + xaxis);
    for (let i = 1; i < points.length; i++) {
      const a = {
        x: i - 1,
        y: points[i - 1]
      };
      const b = {
        x: i,
        y: points[i]
      };
      //if a.y and b.y have different sign, the line will intersect the x-axis
      if (a.y * b.y < 0) {
        //calculate intersection (point on x-axis)
        const intersection = -a.y / (b.y - a.y);
        const intersectionPoint = (a.x + intersection) * xscale;
        //complete the current shape
        cc.lineTo(intersectionPoint, xaxis);
        cc.fill();
        //start a new shape for the other side of the x-axis
        cc.fillStyle = b.y >= 0 ? poscolor : negcolor;
        cc.beginPath();
        cc.moveTo(intersectionPoint, xaxis);
      }
      //continue the shape to point b
      cc.lineTo(b.x * xscale, b.y * yscale + xaxis);
    }
    //draw a line back to the x-axis and finish the shape
    cc.lineTo((points.length - 1) * xscale, xaxis);
    cc.fill();
    canvas {
      background-color: ivory;
      border: 2px solid darkgray;
    }
    <!DOCTYPE html>
    <html>
    
    <body>
      <canvas id="mycanvas" width=500 height=200></canvas>
    </body>
    
    </html>