You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JavaScript中绘制tan(x)、ln(x)等含渐近行为的函数?

处理含渐近线数学函数的JavaScript绘图优化

问题描述

我正在开发一个项目,需要在JavaScript中绘制数学函数图像,尤其是具有渐近行为或在某些点趋近于无穷的函数,例如tan(x)、ln(x)和log(x)。我难以准确呈现这些函数,尤其是在它们趋近于无穷的点附近(比如tan(x)的垂直渐近线附近)。

绘制y = x+2这类普通函数没有问题,但绘制y = tan(x)时出现异常——图像会在渐近线附近错误地连接两端。计算值是正确的,问题出在绘制逻辑上。

以下是我的函数绘制代码:

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
}

优化方案

1. 通用化渐近线检测,摆脱特定函数依赖

当前代码通过isTan标记单独处理正切函数,无法适配其他含渐近线的函数。改为通过函数值本身判断:

  • 当y为Infinity、-Infinity,或超出画布可显示的Y范围时,判定为渐近线区域,需要断开路径。
  • 删除原有的isTan分支逻辑,统一用无穷值/越界检测处理所有函数。

2. 正确处理渐近线前后的路径断开

函数在渐近线附近会出现正负无穷的跳变,此时不能直接用lineTo连接前后点,正确流程是:

  1. 若之前有有效绘制点,先将当前段绘制到画布边缘(对应无穷方向);
  2. 结束当前路径,重新开启新路径;
  3. 在渐近线另一侧,从画布边缘开始绘制下一段曲线。

修改后的核心逻辑示例:

export function drawFunction(latexStr) {
  // Draw function
  const maxY = canvas.height / scaleFactor;
  let prevY;
  let startY = true;

  ctx.beginPath();
  ctx.strokeStyle = "blue";
  ctx.lineWidth = 2;

  for (let x = -canvas.width / 2; x <= canvas.width / 2; x += stepSize) {
    const [y] = parser(latexStr, x);
    const isInfinite = !isFinite(y) || Math.abs(y) > maxY;
    const adjustedX = canvas.width / 2 + x * scaleFactor;

    if (isInfinite) {
      // 绘制当前段到画布边缘并断开路径
      if (!startY) {
        const edgeY = prevY > 0 
          ? canvas.height / 2 - maxY * scaleFactor 
          : canvas.height / 2 + maxY * scaleFactor;
        ctx.lineTo(adjustedX, edgeY);
        ctx.stroke();
        ctx.beginPath();
      }
      startY = true;
      prevY = undefined;
    } else {
      const adjustedY = canvas.height / 2 - y * scaleFactor;
      if (startY) {
        ctx.moveTo(adjustedX, adjustedY);
        startY = false;
      } else {
        // 检测是否是跨渐近线的正负跳变
        const isJump = prevY !== undefined 
          && Math.sign(y) !== Math.sign(prevY) 
          && Math.abs(y - prevY) > maxY * 0.5;
        
        if (isJump) {
          // 绘制前一段到边缘,新开路径
          const prevEdgeY = prevY > 0 
            ? canvas.height / 2 - maxY * scaleFactor 
            : canvas.height / 2 + maxY * scaleFactor;
          ctx.lineTo(adjustedX, prevEdgeY);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(adjustedX, adjustedY);
        } else {
          ctx.lineTo(adjustedX, adjustedY);
        }
      }
      prevY = y;
    }
  }

  // 绘制最后一段未完成的路径
  ctx.stroke();
}

3. 优化采样策略

  • 提前规避渐近线:对于tan(x)这类有周期性渐近线的函数,可提前计算渐近线位置(x = π/2 + kπ,k为整数),采样时跳过渐近线附近的极小区间,避免无效计算。
  • 动态步长调整:在函数变化平缓区域用大步长提升性能,在渐近线附近用小步长保证绘制精度。

4. 清理冗余代码

移除原代码中依赖isTan的特殊逻辑,让代码更简洁,同时支持ln(x)、1/x等更多含渐近线的函数。

内容的提问来源于stack exchange,提问作者Simone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:44:52