Sunday 20 August 2017

Integrals in math with Javascript

This article will describe a demo how to calculate and display definite integrals in a web browser using the HTML 5 <Canvas> element and demonstrate an approximation of integrals using the method of summing up the rectangles between a function curve and the x-axis, that is the equation under consideration to do the calculation of the definite integral on. The integral is defined as the area between the function curve and the x-axis.
Note that we in this demo will consider equations on the form of polynomial curves.
The picture reads integration, it should be integrals! Lets first look some math of how to do the integral of our example, we consider polynomials here, which will be defined as:
$$ y = f(x) = ax^3 + bx^2 + cx + d $$ The integral will be the areal between the function f(x) between startx and endx. Consider the following definition of the indefinite integral: $$\int f(x)\ dx $$ The definite area is then between startx s and endx e is then: $$\int_s^e f(x)dx $$ Some math ensues of to check the calculation of the integral (area), consider this following example :
$$ a = 0.05, b = 0.05, c = 0.05, d = 1 $$. We consider x to be between 1 and 4. Manually calculating the area results in the calculated area of 7.614 (the image shows 7.62, but a check showed it to be 7.614).
Calculation of the integral uses in the hand written calculation the start and end x-values and subtracting the end value of the integral formula with the start value. We consider x here to be between [1..4]. As the screen shot of the demo displays, we approximate this integral to the value 7.60 by calculating the integral as the sum of the midpoint rectangles.

The difference in the specific example between the hand written calculation of 7.62 (exact integral) and approximation of 7.60 is because of the fact that we approximate the integral by summing up the rectangles below the curve. Actually in some cases part of the rectangles are above the curve, there is some discrepancy between the approximate rectangle shape and the true shape of the integral. To get an even more exact value, reducing the increment from 0.5 to 0.25 will get a closer value to the exact one. Reducing the increments will result in smaller rectangles (thinner) following the curve more exact.



Now that we see that our math is sound, we can look at a Plunk with a demo displaying this.
Math integral demo - Plunk
In the sample the graph is displayed with Canvas in HTML 5. The following function in Javascript is used to display the integral and the midpoint rectangles:



   Graph.prototype.drawPolynomial = function(polynomialequation, a, b, c, d,
 increment, isIntegral, color, thickness, startx, endx){


        var totalArea = 0.0;

        var context = this.context;
        context.save();
        context.save();
        this.transformContext();

        context.beginPath();
        context.moveTo(this.minX, polynomialequation(a, b, c, d, this.minX));

        for(var x = this.minX + this.iteration; x <= this.maxX; x += this.iteration) {
          context.lineTo(x, polynomialequation(a, b, c, d, x));
        }

        context.restore();
        context.lineJoin = 'round';
        context.lineWidth = thickness;
        context.strokeStyle = color;
        context.stroke();
        context.restore();

        if (isIntegral){
            var context = this.context;
            context.save();
            this.transformContext();

            var currentY = 0.0;

            context.lineWidth = 0.1;
            context.strokeStyle = 'red';

            for(var x = startx; x < endx; x += increment) {
              context.beginPath();
              currentY = polynomialequation(a, b, c, d, (x+increment/2.0));
              context.rect(x, 0, increment, currentY);
              totalArea += increment * currentY;
              context.stroke();
            }           
      
        }

        return totalArea.toFixed(2);

      }


The following code is used to get the values for the coefficients of the polynomial equation and draw the polynomial function and the corresponding approximation rectangles for the integral.


  
     $(document).ready(function(){

      $("#btnDemo").click(function(){

        $("#a").val("0.05");
        $("#b").val("0.05");
        $("#c").val("0.05");
        $("#d").val("1");
        $("#increment").val("0.25"); 
        $("#startx").val("1.0"); 
        $("#endx").val("4.0"); 


        $("#btnGraph").click();

      });
     
      $("#btnGraph").click(function(){

        var a,b,c,d,increment,startx,endx = 0;

        a = parseFloat($("#a").val());
        b = parseFloat($("#b").val());
        c = parseFloat($("#c").val());
        d = parseFloat($("#d").val());
        increment = parseFloat($("#increment").val());
        startx = parseFloat($("#startx").val());
        endx = parseFloat($("#endx").val());
       
        var myGraph = new Graph({
         canvasId: 'Graph',
         minX: -10,
         minY: -10,
         maxX: 10,
         maxY: 10,
         unitsPerTick: 1
        });  

          var totalArea = myGraph.drawPolynomial(function(a,b,c,d,x){ 
           return ((a*x*x*x) + (b * x*x) +  c*x + d * 1.0).toFixed(2); 
           }, a, b, c, d, increment, true, 'blue', 1.0,  startx, endx);   

         $("#detailsInfo").append("Total area under graph: " + totalArea);

      

     
      });
     
     });
     


There is a lot of code to digest here, see through the code in the Plunk the article points to. The key points here is that we pass in a function as a delegate to be our Polynomial equation. We also approximate the integral (area) by summing up the rectangles below it.

Note that this demo also supports calculating the integral when it dips below the positive y axis!


Fix of reloading functionality

One issue with Canvas and a central one is the fact that the Canvas in HTML 5 is a rasterized grid. It is more like a single layer Photoshop image than a vectorized canvas like Illustrator. With that fact in mind, clearing the Canvas for a new redrawing functionality proved hard. In addition, I also use a transformation here from View-Coordinates to Object-Coordinates. A brute force redrawing is therefore preferred. The following ReloadCanvas method will fix this!

     function ReloadCanvas(canvasId){
        //debugger;

        var oldCanvas = document.getElementById(canvasId);
        console.log("Old canvas: " + oldCanvas);
        var newCanvas = document.createElement('canvas');
        var oldWidth = 500;
        var oldHeight = 500;
        if (oldCanvas){
          oldWidth = oldCanvas.width;
          oldHeight = oldCanvas.height;
          oldCanvas.parentNode.removeChild(oldCanvas);
        }
        newCanvas.id = 'Graph';
        newCanvas.width = oldHeight;
        newCanvas.height = oldWidth;
        document.body.appendChild(newCanvas);
      }



We detach the old canvas and insert a new one into the DOM (Document Object Model). I use here old api methods, you could also resort to more use of jQuery of course. Note that I try to get the old Canvas width and height and copy this to the new blank Canvas that I insert. You must use parentNode here to to the appendChild and removeChild methods to do the replacement of Canvas. This way, we can paint the Canvas again with a fresh new Canvas and have a simpler demo! I have updated the Plunk with a fixed Fork below:
Plunk - Integral math demo app

This demo also supports not only a convenient reload method to make the demo easier to test out, but also supports "negative integrals", i.e. sections where the integral dips below the x-axis as shown in the following image:

Note about accuracy

The total area calculated uses some rounding here. I used the .toFixed(2) to stick to a precision of 0.01. You can try out .toFixed(3) to test out a better precision. That will calculate even more precise the calculated area. The increments should of course be small and you will also see that some small values are still imprecise. We calculate the y value to be the midpoint of the polynomial f(x) and the midpoint should follow the curve as good as possible.

Actually this integral demo app can support other equations that just polynomials. We could support for example trigonometric functions and so on also. I will look at this in a future demo+article!
So there you have it, Canvas and Js can tutor kids and students math concepts in Calculus quite elegant in a web browser. We are seeing that Javascript and HTML 5 are now just as powerful as Java applets in the good old days to describe different concepts and prove that once again Math is fun, especially when computers also come into play!

Note for students


What we have used in this demo is the calculation of definite integrals using the Midpoint Rule and Riemann sum. You can read more about it here. This is standard 1st year Calculus syllabus.
Riemann Sums (Wikipedia)

Monday 7 August 2017

Finding intersection between two lines with HTML5 and Javascript

This article will look at detecting intersection of two lines using Javascript and using HTML 5 Canvas to display the two lines.

A plunk is available here:
Plunk - Intersection of two lines

















We continue from the last article and add a LineIntersection function and add prototype functions. This "class" will help us find the intersection points. The code is as follows:

   function LineIntersection (config){
        this.m1 = config.m1;
        this.b1 = config.b1;
        this.m2 = config.m2;    
        this.b2 = config.b2;

        this.error = 0;
        this.marginOfError = 0.01;
        this.iterations = 0;
        this.maxIterations = 200;
   
      }

      LineIntersection.prototype.PrintData = function() {
        console.clear();
        console.log("m1: " + this.m1 + " b1: " + this.b1 + " m2: " + this.m2 + " b2: " + this.b2);
      }

      LineIntersection.prototype.GetGuess = function(guess){
        var newguess = ((this.b2 - this.b1) / (this.m1 - this.m2) + guess) / 2;
        return newguess;
      }

      LineIntersection.prototype.Y1 = function (guess){
        return this.m1 * guess + this.b1;
      }

      LineIntersection.prototype.Y2 = function (guess){
        return this.m2 * guess + this.b2;
      }

      LineIntersection.prototype.DeltaY = function (guess){
        return Math.abs(this.Y1(guess) - this.Y2(guess));
      }

      LineIntersection.prototype.FindIntersection = function(){

        this.iterations = 0;
       
        if (this.m1 == this.m2)
        {
          alert("The two lines are parallel!");
          return { x: "Infinity", y: "Infinity"};
        }

        guess = Math.floor(Math.random() * 10 + 1);

        do {
         guess = this.GetGuess(guess);

         this.error = this.DeltaY(guess);

         if (this.iterations > this.maxIterations){
          break;

          this.iterations = this.iterations + 1;
         }

        }
        while (this.error > this.marginOfError);

        return { x: guess.toFixed(2), y: this.Y1(guess).toFixed(2) }

      } //function LineIntersection 



The intersection point is then calculated using the code in the following jQuery button click event handler:

   $("#btnIntercept").click(function(){
          var m1,b1,m2,b2 = 0;

        m1 = parseFloat($("#m1").val());
        b1 = parseFloat($("#b1").val());
        m2 = parseFloat($("#m2").val());
        b2 = parseFloat($("#b2").val());
       
        var myGraph = new Graph({
         canvasId: 'Graph',
         minX: -10,
         minY: -10,
         maxX: 10,
         maxY: 10,
         unitsPerTick: 1
        });      

        myGraph.drawLine(m1, b1, 'blue', 3);

        myGraph.drawLine(m2, b2, 'red', 4);

        var lineIntersect = new LineIntersection({
          m1: m1,
          b1: b1,
          m2: m2,
          b2: b2
        });

        lineIntersect.PrintData();

        var intersect = lineIntersect.FindIntersection();
        console.log(intersect);
        console.log(intersect.x);

        myGraph.drawRect(intersect.x, intersect.y, 0.5, 0.5);

        $("#detailsInfo").append("Intersection point: " + " X: " + intersect.x + " Y: " + intersect.y);


To plot a dot where the calculated intersection point the following code is used:
        Graph.prototype.drawRect = function (x, y, width, height){
        var context = this.context; 
        this.transformContext();
        context.strokeStyle = 'green';
        context.fillRect(x - (width/2), y - (height/2), width, height);
      }
To move from object space coordinates to display coordinates we make use of the following helper function:

        Graph.prototype.transformContext = function() {
        var context = this.context;

        // move context to center of canvas
        this.context.translate(this.centerX, this.centerY);

        /*
         * stretch grid to fit the canvas window, and
         * invert the y scale so that that increments
         * as you move upwards
         */
        context.scale(this.scaleX, -this.scaleY);
      };

Sunday 6 August 2017

Drawing intercepting lines with HTML 5 and Js - Part One

I am working with a sample demo that will draw two lines and calculate the intersection. The demo will use Js and HTML 5 Canvas. I have made a Plunk available here:

Canvas line drawing Plunk

The image belows shows a rendering of two lines on the form : y = f(x) = ax + b, y1 = x + 1, y2 = -x + 3

The HTML and Js code is below. I will in the later articles focus on calculating the interception with an iterative approximation method for the intercept. In a future article I will clean up the code and I will look on the code bits and look at Canvas functionality. Here is the code:




















<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8" />
    <script data-require="jquery@*" data-semver="3.1.1" src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <link data-require="bootstrap-css@*" data-semver="4.0.0-alpha.4" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.4/css/bootstrap.min.css" />
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Intersection of two lines with Canvas and Js </h1>
    
    <fieldset>
      <label>y = mx + b</label> <br /><br />
      
      <table>
        <tr>
        
          <td> <label for="m1">m1:</label> <input id="m1" type="text" style="width:50px" /></td>
          <td> <label for="b1">b1:</label> <input id="b1" type="text" style="width:50px" /></td>

          <td> <label for="slope2">m2:</label> <input id="m2" type="text" style="width:50px" /></td>
          <td> <label for="b2">b2:</label> <input id="b2" type="text" style="width:50px" /></td>

        </tr>
        <tr>
          <td><input id="btnGraph" type="button" value="Draw line" /></td>
        </tr>
      </table>
      
      <br />
      <h3>Graph</h3>
      
      <canvas id="Graph" style="background:aliceblue;border:1px solid #AFAFAF" width="600" height="600"></canvas>
     
    </fieldset>
    
      <script>
      function Graph(config) {
        // user defined properties
        this.canvas = document.getElementById(config.canvasId);
        this.minX = config.minX;
        this.minY = config.minY;
        this.maxX = config.maxX;
        this.maxY = config.maxY;
        this.unitsPerTick = config.unitsPerTick;

        // constants
        this.axisColor = '#aaa';
        this.font = '8pt Calibri';
        this.tickSize = 20;

        // relationships
        this.context = this.canvas.getContext('2d');
        this.rangeX = this.maxX - this.minX;
        this.rangeY = this.maxY - this.minY;
        this.unitX = this.canvas.width / this.rangeX;
        this.unitY = this.canvas.height / this.rangeY;
        this.centerY = Math.round(Math.abs(this.minY / this.rangeY) * this.canvas.height);
        this.centerX = Math.round(Math.abs(this.minX / this.rangeX) * this.canvas.width);
        this.iteration = (this.maxX - this.minX) / 1000;
        this.scaleX = this.canvas.width / this.rangeX;
        this.scaleY = this.canvas.height / this.rangeY;

        // draw x and y axis
        this.drawXAxis();
        this.drawYAxis();
      }

      Graph.prototype.drawXAxis = function() {
        var context = this.context;
        context.save();
        context.beginPath();
        context.moveTo(0, this.centerY);
        context.lineTo(this.canvas.width, this.centerY);
        context.strokeStyle = this.axisColor;
        context.lineWidth = 2;
        context.stroke();

        // draw tick marks
        var xPosIncrement = this.unitsPerTick * this.unitX;
        var xPos, unit;
        context.font = this.font;
        context.textAlign = 'center';
        context.textBaseline = 'top';

        // draw left tick marks
        xPos = this.centerX - xPosIncrement;
        unit = -1 * this.unitsPerTick;
        while(xPos > 0) {
          context.moveTo(xPos, this.centerY - this.tickSize / 2);
          context.lineTo(xPos, this.centerY + this.tickSize / 2);
          context.stroke();
          context.fillText(unit, xPos, this.centerY + this.tickSize / 2 + 3);
          unit -= this.unitsPerTick;
          xPos = Math.round(xPos - xPosIncrement);
        }

        // draw right tick marks
        xPos = this.centerX + xPosIncrement;
        unit = this.unitsPerTick;
        while(xPos < this.canvas.width) {
          context.moveTo(xPos, this.centerY - this.tickSize / 2);
          context.lineTo(xPos, this.centerY + this.tickSize / 2);
          context.stroke();
          context.fillText(unit, xPos, this.centerY + this.tickSize / 2 + 3);
          unit += this.unitsPerTick;
          xPos = Math.round(xPos + xPosIncrement);
        }
        context.restore();
      };

      Graph.prototype.drawYAxis = function() {
        var context = this.context;
        context.save();
        context.beginPath();
        context.moveTo(this.centerX, 0);
        context.lineTo(this.centerX, this.canvas.height);
        context.strokeStyle = this.axisColor;
        context.lineWidth = 2;
        context.stroke();

        // draw tick marks
        var yPosIncrement = this.unitsPerTick * this.unitY;
        var yPos, unit;
        context.font = this.font;
        context.textAlign = 'right';
        context.textBaseline = 'middle';

        // draw top tick marks
        yPos = this.centerY - yPosIncrement;
        unit = this.unitsPerTick;
        while(yPos > 0) {
          context.moveTo(this.centerX - this.tickSize / 2, yPos);
          context.lineTo(this.centerX + this.tickSize / 2, yPos);
          context.stroke();
          context.fillText(unit, this.centerX - this.tickSize / 2 - 3, yPos);
          unit += this.unitsPerTick;
          yPos = Math.round(yPos - yPosIncrement);
        }

        // draw bottom tick marks
        yPos = this.centerY + yPosIncrement;
        unit = -1 * this.unitsPerTick;
        while(yPos < this.canvas.height) {
          context.moveTo(this.centerX - this.tickSize / 2, yPos);
          context.lineTo(this.centerX + this.tickSize / 2, yPos);
          context.stroke();
          context.fillText(unit, this.centerX - this.tickSize / 2 - 3, yPos);
          unit -= this.unitsPerTick;
          yPos = Math.round(yPos + yPosIncrement);
        }
        context.restore();
      };

      Graph.prototype.drawEquation = function(equation, color, thickness) {


        var context = this.context;
        context.save();
        context.save();
        this.transformContext();

        context.beginPath();
        context.moveTo(this.minX, equation(this.minX));

        for(var x = this.minX + this.iteration; x <= this.maxX; x += this.iteration) {
          context.lineTo(x, equation(x));
        }

        context.restore();
        context.lineJoin = 'round';
        context.lineWidth = thickness;
        context.strokeStyle = color;
        context.stroke();
        context.restore();
      };

       Graph.prototype.drawLine = function(slope, yintercept, color, thickness) {

        console.log("Inside drawline");

        console.log("this.maxX: " + this.maxX + " this.maxY: " + this.maxY);

        var context = this.context;

        // draw x and y axis
        this.drawXAxis();
        this.drawYAxis();

        //context.clearRect(0, 0, this.canvas.width, this.canvas.height);

        context.save();
        context.save();
        this.transformContext();

        console.log("this.minX: " + this.minX);
        console.log("this.iteration: " + this.iteration);
        console.log("yintercept: " + yintercept);
        console.log("slope:" + slope);

        context.beginPath();
        context.moveTo(this.minX, slope * this.minX + yintercept);

        for(var x = this.minX + this.iteration; x <= this.maxX; x += this.iteration) {
          if (this.iteration % 200 == 0){
           console.log("x: " + x + " y: " + (slope * x + yintercept));
          }
          context.lineTo(x, slope * x + yintercept);
        }

        context.restore();
        context.lineJoin = 'round';
        context.lineWidth = thickness;
        context.strokeStyle = color;
        context.stroke();
        context.restore();
      };

      Graph.prototype.transformContext = function() {
        var context = this.context;

        // move context to center of canvas
        this.context.translate(this.centerX, this.centerY);

        /*
         * stretch grid to fit the canvas window, and
         * invert the y scale so that that increments
         * as you move upwards
         */
        context.scale(this.scaleX, -this.scaleY);
      };
    
    </script>
    
    <script>
     $(document).ready(function(){
     
      $("#btnGraph").click(function(){

        var m1,b1,m2,b2 = 0;

        m1 = parseFloat($("#m1").val());
        b1 = parseFloat($("#b1").val());
        m2 = parseFloat($("#m2").val());
        b2 = parseFloat($("#b2").val());



       
        var myGraph = new Graph({
         canvasId: 'Graph',
         minX: -10,
         minY: -10,
         maxX: 10,
         maxY: 10,
         unitsPerTick: 1
        });      

        myGraph.drawLine(m1, b1, 'blue', 3);

        myGraph.drawLine(m2, b2, 'red', 4);

        //myGraph.drawEquation(function(x) {
         //return 1 * x;
        //}, 'red', 3);

     
      });
     
     });
     
     </script>
    
  </body>

</html>