Window :

Xmin Ymin

Xmax Ymax


Viewport :

Xmin Ymin

Xmax Ymax



The HTML file

<body>
<script>
   var width = 500;
   var height = 500;
   var lastvalue = 0;

   canvas = new Canvas(width,height);

function DoDisplay(value) {

   if (value == undefined) {
      value = lastvalue;
   }

   lastvalue = value;

   canvas.Clear();
   switch (value) {
      case '0':
         LineTest(canvas); 
	 break;
      case '1':
         DrawIt(canvas);
	 break;
      case '2':
         Circle(canvas);
	 break;
      case '3':
         Dragon(canvas);
	 break;
      default:
   }

   canvas.Redisplay();
   return;
}

canvas.Clear();
DoDisplay('0');
canvas.Redisplay();


</script>
<p>
<select onchange="DoDisplay(this.value)">
    <option value="0">Line Test</option>
    <option value="1">Data Test</option>
    <option value="2">Circle</option>
    <option value="3">Dragon</option>
</select>
<hr>
Window : 
<p>
X<sub>min</sub> <input type="text" id="wxn" length = "4" size="4">
Y<sub>min</sub> <input type="text" id="wyn" length = "4" size="4">
<p>
X<sub>max</sub> <input type="text" id="wxx" length = "4" size="4">
Y<sub>max</sub> <input type="text" id="wyx" length = "4" size="4">
<p>

<button type="button" id="Redraw" 
        onclick="canvas.SetWindow();DoDisplay()">
    Set
</button>

<hr>
Viewport : 
<p>
X<sub>min</sub> <input type="text" id="vxn" length = "4" size="4">
Y<sub>min</sub> <input type="text" id="vyn" length = "4" size="4">
<p>
X<sub>max</sub> <input type="text" id="vxx" length = "4" size="4">
Y<sub>max</sub> <input type="text" id="vyx" length = "4" size="4">
<p>
<button type="button" id="Redraw" 
        onclick="canvas.SetViewport();DoDisplay()">
   Set
</button>

<script>
canvas.FixInput();
</script>

<hr>
<hr>

Canvas.js

function Canvas(width, height, locID) {

    if (width == undefined || width < 0) {
       width = 300;
    }

    if (height == undefined || height < 0) {
       height = 300;
    }

    var canvas = document.createElement('canvas')
        canvas.height = height;
        canvas.width = width;
	canvas.style = "border:1px solid #000000;";

    if(locID == undefined) {
        document.body.appendChild(canvas);
    } else {
        div = document.getElementById(locID);
        if (null == div) {
            document.body.appendChild(canvas);
        } else {
            div.appendChild(canvas);
        }
    }

    document.body.appendChild(canvas);

    this.width = width;
    this.height = height;
    this.clearColor = {"R":255, "G": 255, "B": 255}
    this.ctx =  canvas.getContext("2d");

    this.fastLines = true;

    this.frameBuffer = new Array(this.width);
    for(var x = 0; x < this.width; x++) {
       this.frameBuffer[x] = new Array(this.height);
       for(var y=0; y < this.height; y++) {
           this.frameBuffer[x][y] = {"R": this.clearColor.R,
	            "G": this.clearColor.G, "B": this.clearColor.B};
       }
    }

    this.windowXMin = 0;
    this.windowXMax = Math.round(this.width);
    this.windowYMin = 0;
    this.windowYMax = Math.round(this.height);

    this.viewportXMin = 0;
    this.viewportXMax = this.width;
    this.viewportYMin = 0;
    this.viewportYMax = this.height;

    return this;
}

Canvas.prototype = {

    // ui elements.
    SetClearColor: function(r,g,b) {
        this.clearColor.R = r; 
        this.clearColor.G = g; 
        this.clearColor.B = b; 
        return;
    },

    
    SetViewport: function() {
         node = document.getElementById("vxn");
	 vxn = parseInt(node.value);
         node = document.getElementById("vxx");
	 vxx = parseInt(node.value);

	 if (vxn == vxx) {
	    this.FixInput();
	 } else if (vxn > vxx) {
	    tmp = vxx;
	    vxx = vxn;
	    vxn = tmp;
	 }

         node = document.getElementById("vyn");
	 vyn = parseInt(node.value);
         node = document.getElementById("vyx");
	 vyx = parseInt(node.value);

	 if (vyn == vyx) {
	    this.FixInput();
	 } else if (vyn > vyx) {
	    tmp = vyx;
	    vyx = vyn;
	    vyn = tmp;
	 }

	 this.viewportXMin = vxn;
	 this.viewportXMax = vxx;
	 this.viewportYMin = vyn;
	 this.viewportYMax = vyx;

	 return;
    },

    SetWindow: function() {
         node = document.getElementById("wxn");
	 wxn = parseInt(node.value);
         node = document.getElementById("wxx");
	 wxx = parseInt(node.value);

	 if (wxn == wxx) {
	    this.FixInput();
	 } else if (wxn > wxx) {
	    tmp = wxx;
	    wxx = wxn;
	    wxn = tmp;
	 }

         node = document.getElementById("wyn");
	 wyn = parseInt(node.value);
         node = document.getElementById("wyx");
	 wyx = parseInt(node.value);

	 if (wyn == wyx) {
	    this.FixInput();
	 } else if (wyn > wyx) {
	    tmp = wyx;
	    wyx = wyn;
	    wyn = tmp;
	 }

	 this.windowXMin = wxn;
	 this.windowXMax = wxx;
	 this.windowYMin = wyn;
	 this.windowYMax = wyx;

	 return;
    },

    FixInput: function() {
         document.getElementById("wxn").value = this.windowXMin;
         document.getElementById("wxx").value = this.windowXMax;
         document.getElementById("wyn").value = this.windowYMin;
         document.getElementById("wyx").value = this.windowYMax;
         document.getElementById("vxn").value = this.viewportXMin;
         document.getElementById("vxx").value = this.viewportXMax;
         document.getElementById("vyn").value = this.viewportYMin;
         document.getElementById("vyx").value = this.viewportYMax;
	 return;
    },


    Width: function() {
       return this.width;
    },

    Height: function() {
       return this.height;
    },

    Clear: function() {
        for(var x = 0; x < this.width; x++) {
            for(var y=0; y < this.height; y++) {
                this.frameBuffer[x][y].R = this.clearColor.R;
                this.frameBuffer[x][y].G = this.clearColor.G;
                this.frameBuffer[x][y].B = this.clearColor.B;
            }
	}
        return;
    },

    Redisplay: function() {
        for(var x = 0; x < this.width; x++) {
           for(var y=0; y < this.height; y++) {
	       color = "rgb(" + this.frameBuffer[x][y].R
	                      + ", " + this.frameBuffer[x][y].G
	                      + ", " + this.frameBuffer[x][y].B +")";
	       this.ctx.fillStyle = color;
	       this.ctx.fillRect(x,y,1,1);
           }
        }	
        return;
    },

    SetPoint: function(x,y,r,g,b) {
       if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
          // this if is a very bad form of clipping
          if (x >= this.viewportXMin && x <= this.viewportXMax
	          && y>=this.viewportYMin && y <= this.viewportYMax) {
	       this.frameBuffer[x][y].R = Clamp(0,255,r);
	       this.frameBuffer[x][y].G = Clamp(0,255,g);
	       this.frameBuffer[x][y].B = Clamp(0,255,b);
          }
       }
       return;
    },

    TransformX: function (x) {
        var xNDC
	var xDC

	xNDC = (x-this.windowXMin)* 2/(this.windowXMax-this.windowXMin) -1;

        // note this SHOULD be simplified
	xDC = (xNDC+1) * (this.viewportXMax-this.viewportXMin)/2
	              + this.viewportXMin;
	return Math.round(xDC);
    },

    TransformY: function(y) {
        var yNDC,
	    yDC;

        // note this SHOULD be simplified
	yNDC = (y-this.windowYMin) * 2 / (this.windowYMax-this.windowYMin)-1;
	yDC = (-1* yNDC +1) *(this.viewportYMax-this.viewportYMin)/2 
	     + this.viewportYMin;

	return Math.round(yDC);
    },

    Point: function(x,y, r,g,b) {
        xs = this.TransformX(x);
	ys = this.TransformY(y);
        this.SetPoint(xs,ys,r,g,b);
    },

    Line: function(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2) {

          console.log("orig", x1, y1, x2, y2);
          var xs = this.TransformX(x1)
	  var xe = this.TransformX(x2);
	  var ys = this.TransformY(y1);
	  var ye = this.TransformY(y2);

	  console.log('transformed ' , xs,xe, ys, ye);

          this.BruteLine(xs, ys, r1, g1, b1, xe, ye, r2, g2, b2);
    },

    BruteLine: function(x1, y1, r1, g1, b1, x2, y2, r2, g2, b2) {
        var dx = x2-x1;
	var dy = y2-y1;
	var m = dy/dx; // may be NAN but will not be used.

	var x,y,dir;

	if (dx == 0 && dy == 0)  {
	    // attempt to draw a degenerate line (a point)
	    this.SetPoint(x1, y1, r1, g1,b1)
	} else if (dx == 0) {
	    // no change in x, so it is a vertical line
	    var sy = Math.min(y1,y2);
	    var ey = Math.max(y1, y2);
	    for(y=sy; y <= ey; y++) {
	       this.SetPoint(x1,y,r1,g1,b1);
	    }
	} else if (dy == 0) {
	    // no change in y so it is a horizontal line.
	    var sx = Math.min(x1,x2);
	    var ex = Math.max(x1, x2);
	    for(x=sx; x <= ex; x++) {
	       this.SetPoint(x,y1,r1,g1,b1);
	    }
	} else if (Math.abs(dx) > Math.abs(dy) ) {
	    if (dx < 0) {
	       dir  = -1;
	    } else {
	       dir = 1;
	    }
	    for(x=x1; x!= x2; x+= dir) {
	       y = Math.round(y1+m*(x-x1));
	       this.SetPoint(x,y,r1,g1,b1);
	    }
	} else {
	    if (dy < 0) {
	        dir = -1;
	    } else {
	        dir = 1;
	    }
	    for(y=y1; y != y2; y+= dir) {
	        x = Math.round(x1 + 1/m*(y-y1));
	        this.SetPoint(x,y,r1,g1,b1);
	    }
	}
        return;
    },
};

function Clamp(min, max, v) {
   return Math.max(min, Math.min(v,max));
}

LineTests.js

var X=0,
    Y = 1,
    R = 2,
    G = 3, 
    B = 4;

data = [
         [ "line", [ 10,10,255,0,0], [20,20,0,0,255], [30,30,0,255,0]],
	 [ "polygon", [100,100,0,255,255], [110,100,0,255,255], 
	              [110,110,0,255,255], [100,110,0,255,255]],
       ];



function DrawPolyLine(canvas, ary) {
    for(var p =1; p < ary.length-1; p++) {
        canvas.Line(
	  ary[p][X],ary[p][Y], ary[p][R],ary[p][G],ary[p][B], 
	  ary[p+1][X],ary[p+1][Y], ary[p+1][R],ary[p+1][G],ary[p+1][B]) ;
    }
    return;
}

function DrawPolygon(canvas, ary) {
    DrawPolyLine(canvas, ary);

        var a = 1, b = ary.length-1;
        canvas.Line(
	  ary[b][X],ary[b][Y], ary[b][R],ary[b][G],ary[b][B], 
	  ary[a][X],ary[a][Y], ary[a][R],ary[a][G],ary[a][B]) ;
    return;
}

function DrawIt(canvas) {
    var shape,point;

    for(shape=0; shape < data.length; shape ++) {
       console.log("drawing a ", data[shape][0]);
       if (data[shape][0] == "line") {
           DrawPolyLine(canvas, data[shape]);
       } else if (data[shape][0] == "polygon")  {
           DrawPolygon(canvas, data[shape]);
       }
    }
    return;
}

function Dragon(canvas) {
    DrawPolyLine(canvas, dragon[0]);
    return;
}

function  Circle(canvas) {
   var t;
   var x1,y1;
   var x2,y2;
   var r = Math.min(canvas.Width()/2, canvas.Height()/2)*.9;
   var xc, yc;
   var step = 30;

   xc = 0;
   yc = 0;

   t = 0;

   for (var R = 20; R <= r ; R+= 10) {
   x1 = xc + Math.round(R * Math.cos(t*Math.PI/180));
   y1 = yc + Math.round(R * Math.sin(t*Math.PI/180));
   for(t=step;t<360; t+= step) {
       x2 = xc + Math.round(R * Math.cos(t*Math.PI/180));
       y2 = yc + Math.round(R * Math.sin(t*Math.PI/180));
       canvas.Line(x1, y1, 0,0,0, x2, y2 , 255, 0,0);
       x1 = x2;
       y1 = y2;
   }

   t = 360;
   x2 = xc + Math.round(R * Math.cos(t*Math.PI/180));
   y2 = yc + Math.round(R * Math.sin(t*Math.PI/180));
   canvas.Line(x1, y1, 255,0,0, x2, y2 , 255, 0,0);
   }

   canvas.Point(xc, yc, 255,0,0);
}

function LineTest(canvas) {

   var xm =  -200;
   var ym =  -200;
   var xx = -100;
   var yx =  -100;

   // draw a horizontal line left to right.
   canvas.Line(xm,ym, 0,0,0, xx,ym , 0, 0, 0 );
   // draw a horizontal line right to left
   canvas.Line(xx,yx, 0,0,0, xm, yx, 0, 0, 0 );

   // draw a vertical line top to bottom
   canvas.Line (xm, yx, 0, 0, 0, xm, ym, 0,0,0);
   // draw a vertical line bottom to top.
   canvas.Line (xx, ym, 0, 0, 0, xx, yx, 0,0,0);

   // four diagionals.
   canvas.Line(xm,ym,0,0,0, xx,yx,0,0,0);
   canvas.Line(xx,yx,0,0,0, xm,ym,0,0,0);

   canvas.Line(xx,ym,0,0,0, xm,yx,0,0,0);
   canvas.Line(xm,yx,0,0,0, xx,ym,0,0,0);

   var xc = Math.round((xx+xm)/2);
   var yc = Math.round((yx+ym)/2);;
   var r = Math.min(Math.abs(xx-xm),Math.abs(yx,ym))*.7;
   var x,y;

   var t;
   for(t=0;t<360;t+=10) {
       x = xc + Math.round(r*Math.cos(t*Math.PI/180));
       y = yc + Math.round(r*Math.sin(t*Math.PI/180));
       canvas.Line(xc, yc,255,0,0, x,y,155,42,0);
   }
}