M. Al Jumaily
M. Al Jumaily

Reputation: 973

Plotting functions with asymptotes

I am trying to plot functions with asymptotes, such as f(x) = tan(x) and f(x) = 1 / x using SVG. With the help of enxaneta's answer, I was able to generate the points and connect them through quadratic and cubic Bézier curves.

There are two problems in the implementation:

let smooth = 0.1;
let container = document.getElementById("svg");
let width = container.getAttribute("width");
let height = container.getAttribute("height");

let xmin = -5 * 2 * Math.PI;
let xmax = 5 * 2 * Math.PI;
let ymin = -10;
let ymax = 10;

function f(x) { //use tan(x) or 1 / x
  return Math.tan(x);
  //return 1 / x;
}

let points = generatePoints(xmin, xmax, 150);
let thePath = document.getElementById("thePath");

thePath.setAttribute("d", drawCurve(points));
thePath.setAttribute("stroke-width", 2);
thePath.setAttribute("opacity", 1);
thePath.setAttribute("stroke-linecap", "circle");
thePath.setAttribute("stroke-linejoin", "miter");
thePath.setAttribute("shape-rendering", "auto"); 

points.forEach((point) => { // magenta points
  plotSinglePoint(point.x, point.y);
});

////////////////////////////////////////////////////////////////////////////////
//                      Implementation below                                  //
////////////////////////////////////////////////////////////////////////////////
function generatePoints(xMin, xMax, samples = 50) {
  result = [];
  let xInc = (xMax - xMin) / samples;
  for (let x = xMin; x < xMax; x += xInc) {
    let xVal = coordX(x);
    let yVal = coordY(f(x));
    let point = { x: xVal, y: yVal };
    if (isValidPoint(point)) {
      result.push(point);
    }
  }
  result.forEach((p) => {
    if (p.x == NaN || p.y == NaN) {
      console.log(p);
    }
  });
  return result;
}

function isValidPoint(point) {
  return 0 <= point.x && point.x <= width && 0 <= point.y && point.y <= height;
}

function plotSinglePoint(x, y, r = 3) {
  let svgns = "http://www.w3.org/2000/svg";
  let circle = document.createElementNS(svgns, "circle");
  circle.setAttributeNS(null, "cx", x);
  circle.setAttributeNS(null, "cy", y);
  circle.setAttributeNS(null, "r", r);
  circle.setAttributeNS(
    null,
    "style",
    "fill: magenta; stroke: red; stroke-width: 0px;"
  );
  container.appendChild(circle);
}

function controlPoints(p) {
  // given the points array p calculate the control points for the cubic Bezier curves

  var pc = [];
  for (var i = 1; i < p.length - 1; i++) {
    var dx = p[i - 1].x - p[i + 1].x; // difference x
    var dy = p[i - 1].y - p[i + 1].y; // difference y
    // the first control point
    var x1 = p[i].x - dx * smooth;
    var y1 = p[i].y - dy * smooth;
    var o1 = {
      x: x1,
      y: y1,
    };

    // the second control point
    var x2 = p[i].x + dx * smooth;
    var y2 = p[i].y + dy * smooth;
    var o2 = {
      x: x2,
      y: y2,
    };

    // building the control points array
    pc[i] = [];
    pc[i].push(o1);
    pc[i].push(o2);
  }
  return pc;
}

function drawCurve(p) {
  var pc = controlPoints(p); // the control points array

  let d = `M${p[0].x},${p[0].y}
Q${pc[1][1].x},${pc[1][1].y}, ${p[1].x},${p[1].y}
`;
  if (p.length > 2) {
    // central curves are cubic Bezier
    for (var i = 1; i < p.length - 2; i++) {
      d += `C${pc[i][0].x}, ${pc[i][0].y}, ${pc[i + 1][1].x}, ${
        pc[i + 1][1].y
      }, ${p[i + 1].x},${p[i + 1].y}`;
    }

    // the first & the last curve are quadratic Bezier
    var n = p.length - 1;
    d += `Q${pc[n - 1][0].x}, ${pc[n - 1][0].y}, ${p[n].x}, ${p[n].y}`;
  }
  return d;
}

function coordX(x) {
  return (width * (x - xmin)) / (xmax - xmin);
}
function coordY(y) {
  return height * (1 - (y - ymin) / (ymax - ymin));
}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title></title>
    <meta name="description" content="" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="" />
  </head>
  <body>
    <svg
      id="svg"
      width="500"
      height="500"
      style="border: solid; fill: none; stroke: black"
    >
     

      <path id="thePath" />
      <!-- stroke="url(#left-to-right)"  -->
    </svg>
    <script src="script.js" async defer></script>
  </body>
</html>

Upvotes: 1

Views: 69

Answers (0)

Related Questions