Search code examples
javascripthtmlplotcanvashtml5-canvas

How to plot functions with asymptotic behavior like tan(x), ln(x), and log(x) in JavaScript?


I'm working on a project where I need to plot graphs of mathematical functions in JavaScript, specifically functions that exhibit asymptotic behavior or approach infinity at certain points, such as tan(x), ln(x), and log(x). I'm struggling with how to accurately represent these functions, especially near the points where they approach infinity (for example, near vertical asymptotes for tan(x)).

This is my Code to draw functions:

export function drawFunction(latexStr) {
  let isTan;
  // Draw function
  console.log(canvas.height);
  console.log(canvas.width);

  let prevY; // Stores the previous Y value
  let startY = true; // To know when a new line is needed

  ctx.beginPath();
  ctx.strokeStyle = "blue";
  ctx.lineWidth = 2;
  // Draw the function in small steps
  for (let x = -canvas.width / 2; x <= canvas.width / 2; x += stepSize) {
    let y = parser(latexStr, x)[0]; // f(x)
    isTan = parser(latexStr, x)[1];
    console.log(isTan);
    // Skip drawing the graph if it's infinite or too large for the canvas
    if (Math.abs(y) > canvas.height / scaleFactor && !isTan) {
      startY = true;
      y = (y < 0 ? -1 : 1) * (canvas.height / scaleFactor - 1);
    }

    const adjustedX = canvas.width / 2 + x * scaleFactor; // Calculation of the small X line
    const adjustedY = canvas.height / 2 - y * scaleFactor; // Calculation of the small Y line

    /*     if (Math.round(x * 100) / 100 === stepSize)
      ctx.moveTo(adjustedX, adjustedY); */

    // Draw or move to position
    if (startY) {
      ctx.moveTo(adjustedX, adjustedY);
      startY = false;
    } else {
      if (prevY !== undefined && Math.abs(y - prevY) > 1 * scaleFactor) {
        if (isTan) {
          ctx.stroke(); // Draw the current line
          ctx.beginPath(); // Start a new line
          ctx.moveTo(adjustedX, y < 0 ? canvas.height : 0); // Move to the edge of the canvas
        } else {
          ctx.moveTo(adjustedX, adjustedY); // Move to the new starting position
        }
      } else {
        ctx.lineTo(adjustedX, adjustedY);
      }
    }

    prevY = y;
    //console.log(Math.round(x * 100) / 100);
  }

  ctx.stroke(); // Draw the function
}

Drawing a normal function like "y = x+2" is no problem, but issues arise with a function like "y = tan(x)" as seen in the image below:

Image of the tan(x) Function

The values are correct, so it seems not to be an issue with the calculations but rather with the way the drawing is handled. I've searched everywhere but couldn't find a solution. Does anyone have suggestions on how I could improve the drawing algorithm to better handle infinity points for functions like tan(x)?

Thank you in advance for your help!


Solution

  • Your problem is not quite clear, and you have not provided full code ...
    From your image I'm going to assume that what you are not happy with is some lines not making it all the way up to the edge of the canvas, like image below:

    enter image description here

    Problem

    Here is some simple code reproducing your problem:

    var canvas = document.getElementById('plotCanvas');
    var ctx = canvas.getContext('2d');
    
    var startX = -20;
    var endX = 20;
    var scaleFactor = 25;
    
    ctx.beginPath();
    for (var x = startX + 0.01; x <= endX; x += 0.01) {
      xP = (x - startX) * (canvas.width / (endX - startX));
      yP = canvas.height / 2 - Math.tan(x) * scaleFactor
      if (xP > 0 && yP > 0 && xP < canvas.width && yP < canvas.height) {
        ctx.lineTo(xP, yP);
      } else {
        ctx.moveTo(xP, yP)
      }
    }
    ctx.strokeStyle = 'blue';
    ctx.stroke();
    canvas {
      border: 1px solid black;
    }
    <canvas id="plotCanvas" width="400" height="300"></canvas>


    Solution

    We have a hard condition to only draw inside the canvas:
    if (xP > 0 && yP > 0 && xP < canvas.width && yP < canvas.height) {
    that is good to optimize what we draw and not waste time drawing things that are not visible, but...

    The condition for the boundaries needs to be a bit more flexible, an easi fix is to let it draw a few lines outside the canvas boundaries, instead of stopping at 0 like I had, I'm letting it go to:
    -canvas.width that is all

    var canvas = document.getElementById('plotCanvas');
    var ctx = canvas.getContext('2d');
    
    var startX = -20;
    var endX = 20;
    var scaleFactor = 25;
    
    ctx.beginPath();
    for (var x = startX + 0.01; x <= endX; x += 0.01) {
      xP = (x - startX) * (canvas.width / (endX - startX));
      yP = canvas.height / 2 - Math.tan(x) * scaleFactor
      if (xP > -canvas.width && yP > -canvas.height && xP < canvas.width && yP < canvas.height) {
        ctx.lineTo(xP, yP);
      } else {
        ctx.moveTo(xP, yP)
      }
    }
    ctx.strokeStyle = 'blue';
    ctx.stroke();
    canvas {
      border: 1px solid black;
    }
    <canvas id="plotCanvas" width="400" height="300"></canvas>