/*=========================================================
  Filename: cvsGraphLib2v06.js
  Rev: 2
  By: arc
  Description: A basic graphics interface for the canvas
  element.
  License: Released into the public domain
  latest version at
  <http://www/arc.id.au/CanvasGraphics.html>
  Requires:
  - IE canvas emulator 'excanvas.js' from
  <http://sourceforge.net/projects/excanvas>.
  - Text support 'canvastext.js' from
  <http://www.federated.com/~jim/canvastext/>
  - color parser 'rgbcolor.js' from
  <http://www.phpied.com/rgb-color-parser-in-javascript/>

  Date   |Description                                  |By
  ---------------------------------------------------------
  03Jul08 Rev1.00 First release                         ARC
  05Jul08 drawImg returns image object not string       ARC
  07Jul08 changed clearPanel to clearCanvas             ARC
  11Jul08 Added arrow method                            ARC
  18Jul08 Mods to support flipped axes                  ARC
  20Jul08 Improved axes labeling method                 ARC
  21Jul08 Added drawXYAxes with auto tick maks          ARC
  22Jul08 Added polyLine to speed up plots              ARC
  22Jul08 Moved Axis object in from GraphicsUtils.js    ARC
  22Jul08 Released as Rev2                              ARC
  23Jul08 bugfix: drawXYAxes lorg values fixed          ARC
  25Jul08 bugfix: yLabel in drawBoxAxes bad x coord     ARC
  01Aug08 Add dashed and doted line styles
          Add grid to boxAxes
          remove xPosRt, yPosUp - unnesseccary          ARC
  ========================================================= */

  var _resized = new Array();   // keep track of which canvases are initialised
  var _busy = new Array();      // index by canvas id each element is a busy flag

  function cvsGraphCtx(canvasId)
  {
    this.cId = canvasId;
    this.cnvs = document.getElementById(this.cId);
    this.rawWidth = this.cnvs.offsetWidth;
    this.rawHeight = this.cnvs.offsetHeight;
    this.aRatio = this.rawWidth/this.rawHeight;

    if (!(this.cId in _resized))
    {
    /* make canvas native aspect ratio equal style box aspect ratio.
       only do this once for each canvas as it clears the canvas.
       A second graph to be drawn will earase the firt graph if the canvas
       width and height attributes are reset */
      /* Note: rawWidth and rawHeight are floats, assignment to ints will truncate */
      var ua = navigator.userAgent.toLowerCase();
      var isIE = (/msie/.test(ua)) && !(/opera/.test(ua)) && (/win/.test(ua));
      if (isIE)
      {
         this.cnvs.style.width = this.rawWidth+'px';
         this.cnvs.style.height = this.rawHeight+'px';
      }
      else
      {
        this.cnvs.setAttribute('width', this.rawWidth);     // this actually reset the number of graphics pixels
        this.cnvs.setAttribute('height', this.rawHeight);   // use this instead of style to match emulator
      }

      /* create an element in associative array for this canvas
         element's existance is the test that resize has been done.
         Coukld have used the existance of the busy flag but lets use
         separate code for separate function */
      _resized[this.cId]= true;
    }

    if (!(this.cId in _busy))
    {
      /* this code is only executed for the first graph on each canvas
         create a busy flag for the graphics engine to prvent asynchronous
         drawing when ctx may be distorted during another call. All asynchronous
         drawinf functions must be recallabel with builtin timeout and only execute
         when ctx !busy.
       */
      _busy[this.cId] = false;
    }
    /* HV: Fix this - if the getContext() is undef(), define it! */
    if( this.cnvs.getContext==undefined ) {
          this.cnvs.getContext = function () {
            if (this.context_) {
               return this.context_;
             }
             return this.context_ = new CanvasRenderingContext2D_(this);
          };
    }
    this.ctx = this.cnvs.getContext('2d');
    this.ctx.save();

    this.vpW = this.rawWidth;         // vp width in pixels (default to full canvas size)
    this.vpH = this.rawHeight;        // vp height in pixels
    this.vpLLx = 0;                   // vp lower left from canvas left in pixels
    this.vpLLy = this.rawHeight;      // vp lower left from canvas top
    this.xscl = this.rawWidth/100;    // world x axis scale factor, default canvas width = 100 native units
    this.yscl = -this.rawWidth/100;   // world y axis scale factor, default +ve up and canavs height =100*aspect ratio (square pixels)
    this.xoffset = 0;                 // world x origin offset from viewport left in pixels
    this.yoffset = 0;                 // world y origin offset from viewport bottom in pixels
                                      // *** to move to world coord x ***
                                      // 1. from pixel x origin (canvas left) add vpLLx (gets to viewport left)
                                      // 2. add xoffset to get to pixel location of world x origin
                                      // 3. add x*xscl pixels to get to world x location.
                                      // ==> x (in world coords) == vpLLx + xoffset + x*xscl (pixels location of canvas)
                                      // ==> y (in world coords) == vpLLy + yoffset + y*xscl (pixels location of canvas)
    this.penCol = "#000000";     // black
    this.penWid = 1;             // pixels
    this.bkgCol = "#ffffff";     // white
    this.fontSize = 10;          // 10pt

    this.penX = 0;
    this.penY = 0;
  }

  cvsGraphCtx.prototype._setCtx = function()
  {
    this.ctx.fillStyle = this.penCol;
    this.ctx.lineWidth = this.penWid;
    this.ctx.strokeStyle = this.penCol;
  }

  cvsGraphCtx.prototype.clearCanvas = function()
  {
    this.ctx.clearRect(0, 0, this.rawWidth, this.rawWidth);
    // all drawing erased
    // but all global graphics contexts remain intact
  }

  cvsGraphCtx.prototype.setViewport = function(lowerLeftX, lowerLeftY, w, h)
  {
    if (h != undefined)
    {
      this.vpW = 0.01*w*this.rawWidth;
      this.vpH = 0.01*h*this.rawWidth;
      this.vpLLx = 0.01*lowerLeftX*this.rawWidth;
      this.vpLLy = this.rawHeight-0.01*lowerLeftY*this.rawWidth;
    }
    else
    {
      this.vpW = this.rawWidth;
      this.vpH = this.rawHeight;
      this.vpLLx = 0;
      this.vpLLy = this.rawHeight;
    }
    this.setWorldCoords();     // if new viewport, world coords are garbage, so reset to defaults
  }

  cvsGraphCtx.prototype.fillViewport = function(fillColor)
  {
    /* fillViewport() sets the background color and fills the viewport to that color */
    var color;

    this.ctx.save();   // we are going to change pencolor save the clean ctx
    this._setCtx();   // set up the stroke and fill styles
    if (fillColor != undefined)
    {
      color = new RGBColor(fillColor);

      if (color.ok)
        this.bkgCol = color.toHex();
 //     else
 //       alert("Invalid Color: "+fillColor+" Syntax: #rrggbb");
    }
    this.ctx.fillStyle = this.bkgCol;
    this.ctx.fillRect(this.vpLLx, (this.vpLLy-this.vpH), this.vpW, this.vpH); // fill referenced from top right corner
    this.ctx.fillStyle = this.penCol;    // restore fill color to pen color
    this.ctx.restore();
  }

  cvsGraphCtx.prototype.setWorldCoords = function(leftX, rightX, lowerY, upperY)
  {
    if (upperY != undefined)
    {
      this.xscl = this.vpW/(rightX-leftX);
      this.yscl = -(this.vpH/(upperY-lowerY));
      this.xoffset = -leftX*this.xscl;
      this.yoffset = -lowerY*this.yscl;
    }
    else
    {
      this.xscl = this.rawWidth/100;    // makes xaxis = 100 native units
      this.yscl = -this.rawWidth/100;   // makes yaxis = 100*aspect ratio ie. square pixels
      this.xoffset = 0;
      this.yoffset = 0;
    }
  }

  cvsGraphCtx.prototype.setPenColor = function(color)
  {
    var newCol = new RGBColor(color);

    if (newCol.ok)
      this.penCol = newCol.toHex();    // if no color passed then just restore this graph's pen color
//      else
//        alert("Invalid Color: "+color+" Syntax: #rrggbb");
  }

  cvsGraphCtx.prototype.setPenWidth = function(w)    // w in screen px
  {
    if (typeof w != "undefined")
      this.penWid = w;
  }

  cvsGraphCtx.prototype.setFontSize = function(s)    // s in points
  {
    if (typeof s != "undefined")
      this.fontSize = s;
  }

  cvsGraphCtx.prototype.move = function(x, y) /* from current pen to x,y */
  {
    this.ctx.beginPath();    // reset current path to null and move to 0,0
    this.ctx.moveTo(this.vpLLx+this.xoffset+x*this.xscl, this.vpLLy+this.yoffset+y*this.yscl);
		this.penX = this.vpLLx+this.xoffset+x*this.xscl;
		this.penY = this.vpLLy+this.yoffset+y*this.yscl;
  }

  cvsGraphCtx.prototype.line = function(x1, y1, style)  /* from current penX, PenY to x1,y1 */
  {
    this.ctx.save();   // we are going to change pencolor save the clean ctx
    this._setCtx();   // set up the stroke and fill styles
		if (style == "dashed")
		{
			this.dashTo(this.vpLLx+this.xoffset+x1*this.xscl, this.vpLLy+this.yoffset+y1*this.yscl, 6, 8)
      this.ctx.stroke();
		} else if (style == "dotted")
		{
			this.dashTo(this.vpLLx+this.xoffset+x1*this.xscl, this.vpLLy+this.yoffset+y1*this.yscl, 2, 6)
      this.ctx.stroke();
		} else
		{
      this.ctx.lineTo(this.vpLLx+this.xoffset+x1*this.xscl, this.vpLLy+this.yoffset+y1*this.yscl);
      this.ctx.stroke();
		}
    this.ctx.moveTo(this.vpLLx+this.xoffset+x1*this.xscl, this.vpLLy+this.yoffset+y1*this.yscl); /* update pen position for excanvas */
    this.ctx.restore();
  }

/*
 * @author Trevor McCauley, senocular.com
 * @version 1.0.2
 *
 * convert to simple function from class by ARC 26Mar07
 */

	function lineLength(sx, sy, ex, ey)
	{
		if (typeof ex == "undefined")
			return Math.sqrt(sx*sx + sy*sy);
		var dx = ex - sx;
		var dy = ey - sy;
		return Math.sqrt(dx*dx + dy*dy);
	}

	cvsGraphCtx.prototype.targetMoveTo = function(x, y)
	{
		this.penX = x;
		this.penY = y;
    this.ctx.moveTo(x, y);
	}

	cvsGraphCtx.prototype.targetLineTo = function(x, y)
	{
		if (x == this.penX && y == this.penY)
			return;
		this.penX = x;
		this.penY = y;
    this.ctx.lineTo(x, y);
	}

		/**
		 * Draws a dashed line in target using the current line style from the current drawing position
		 * to (x, y); the current drawing position is then set to (x, y).
		 */
	cvsGraphCtx.prototype.dashTo = function(x, y, onLength, offLength)
	{
		var overflow = 0;
		var isLine = true;
		var dashLength = onLength + offLength;
		var dx = x-this.penX,	dy = y-this.penY;
		var a = Math.atan2(dy, dx);
		var ca = Math.cos(a), sa = Math.sin(a);
		var segLength = lineLength(dx, dy);
		if (overflow)
		{
			if (overflow > segLength)
			{
				if (isLine)
					this.targetLineTo(x, y);
				else this.targetMoveTo(x, y);
				overflow -= segLength;
				return;
			}
			if (isLine)
				this.targetLineTo(this.penX + ca*overflow, this.penY + sa*overflow);
			else
				this.targetMoveTo(this.penX + ca*overflow, this.penY + sa*overflow);
			segLength -= overflow;
			overflow = 0;
			isLine = !isLine;
			if (!segLength)
				return;
		}

		var fullDashCount = Math.floor(segLength/dashLength);
		if (fullDashCount)
		{
			var onx = ca*onLength,	ony = sa*onLength;
			var offx = ca*offLength,	offy = sa*offLength;
			for (var i=0; i<fullDashCount; i++)
			{
				if (isLine)
				{
					this.targetLineTo(this.penX+onx, this.penY+ony);
					this.targetMoveTo(this.penX+offx, this.penY+offy);
				}
        else
				{
					this.targetMoveTo(this.penX+offx, this.penY+offy);
					this.targetLineTo(this.penX+onx, this.penY+ony);
				}
			}
			segLength -= dashLength*fullDashCount;
		}

		if (isLine)
		{
			if (segLength > onLength)
			{
				this.targetLineTo(this.penX+ca*onLength, this.penY+sa*onLength);
				this.targetMoveTo(x, y);
				overflow = offLength-(segLength-onLength);
				isLine = false;
			}
      else
			{
				this.targetLineTo(x, y);
				if (segLength == onLength)
				{
					overflow = 0;
					isLine = !isLine;
				}
        else
				{
					overflow = onLength-segLength;
					this.targetMoveTo(x, y);
				}
			}
		}
    else
		{
			if (segLength > offLength)
			{
				this.targetMoveTo(this.penX+ca*offLength, this.penY+sa*offLength);
				this.targetLineTo(x, y);
				overflow = onLength-(segLength-offLength);
				isLine = true;
			}
      else
			{
				this.targetMoveTo(x, y);
				if (segLength == offLength)
				{
					overflow = 0;
					isLine = !isLine;
				}
        else
					overflow = offLength-segLength;
			}
		}
	}

  cvsGraphCtx.prototype.polyLine = function(data) // data is either data[n] or data[n][2]
  {
    this.ctx.save();   // we are going to change pencolor save the clean ctx
    this._setCtx();   // set up the stroke and fill styles
    this.ctx.beginPath();    // reset current path to null and move to 0,0
    if (data[0][0] != undefined)    // data is 2D array (or 3D but just use first 2 values) x=data[n][0], y=data[n][1]
    {
      this.ctx.moveTo(this.vpLLx+this.xoffset+data[0][0]*this.xscl, this.vpLLy+this.yoffset+data[0][1]*this.yscl);
      for (var i=1; i<data.length; i++)
        this.ctx.lineTo(this.vpLLx+this.xoffset+data[i][0]*this.xscl, this.vpLLy+this.yoffset+data[i][1]*this.yscl);
      this.ctx.stroke();
      // now update pen position for excanvas
      this.ctx.moveTo(this.vpLLx+this.xoffset+data[data.length-1][0]*this.xscl, this.vpLLy+this.yoffset+data[data.length-1][1]*this.yscl);
    }
    else // data is a simple array x values are data[0], data[2] ... data[2*n], y values data[1] data[3].. data[2*n+1]
    {
      this.ctx.moveTo(this.vpLLx+this.xoffset+data[0]*this.xscl, this.vpLLy+this.yoffset+data[1]*this.yscl);
      for (var i=1; i<data.length/2; i++)
      {
        this.ctx.lineTo(this.vpLLx+this.xoffset+data[2*i]*this.xscl, this.vpLLy+this.yoffset+data[2*i+1]*this.yscl);
      }
      this.ctx.stroke();
      // now update pen position for excanvas
      this.ctx.moveTo(this.vpLLx+this.xoffset+data[data.length-2]*this.xscl, this.vpLLy+this.yoffset+data[data.length-1]*this.yscl);
    }
    this.ctx.restore();
  }

  cvsGraphCtx.prototype.arrow = function(x1, y1, x2, y2, size) /* from current pen to x,y */
  {
    var a = 20;   // half angle of arrow head in degrees
    var scale = 2;     // default size
    var dx = (x2-x1)*this.xscl;         // pixels
    var dy = (y2-y1)*this.yscl;
    var theta = -Math.atan2(dy, dx);
    var phiL = theta + Math.PI - (a*Math.PI/180.0);
    var phiR = theta + Math.PI + (a*Math.PI/180.0);
    var phiC = theta + Math.PI;

    if (size != undefined)
      scale = size;
    if (size<1)
      scale = 1;
    if (size>9)
      scale = 9;

    var r = scale*(3+4*Math.sqrt(this.penWid));     // size of arrow head, at least as wide as the line
    var x3 = x2 + r*Math.cos(phiL)/this.xscl;
    var y3 = y2 - r*Math.sin(phiL)/this.yscl;
    var x4 = x2 + r*Math.cos(phiR)/this.xscl;
    var y4 = y2 - r*Math.sin(phiR)/this.yscl;
    var xs = x2 + 0.9*r*Math.cos(phiC)/this.xscl;
    var ys = y2 - 0.9*r*Math.sin(phiC)/this.yscl;

    this.ctx.save();   // we are going to change pencolor save the clean ctx
    this._setCtx();   // set up the stroke and fill styles
    this.ctx.beginPath();    // reset current path to null and move to 0,0
    this.ctx.moveTo(this.vpLLx+this.xoffset+x1*this.xscl, this.vpLLy+this.yoffset+y1*this.yscl);
    // stroke the line just short of the end so we get the sharp end point
    this.ctx.lineTo(this.vpLLx+this.xoffset+xs*this.xscl, this.vpLLy+this.yoffset+ys*this.yscl);
    this.ctx.stroke();
    this.ctx.moveTo(this.vpLLx+this.xoffset+x2*this.xscl, this.vpLLy+this.yoffset+y2*this.yscl);
    this.ctx.lineTo(this.vpLLx+this.xoffset+x3*this.xscl, this.vpLLy+this.yoffset+y3*this.yscl);
    this.ctx.lineTo(this.vpLLx+this.xoffset+x4*this.xscl, this.vpLLy+this.yoffset+y4*this.yscl);
    this.ctx.lineTo(this.vpLLx+this.xoffset+x2*this.xscl, this.vpLLy+this.yoffset+y2*this.yscl);
    this.ctx.fill();

    this.ctx.moveTo(this.vpLLx+this.xoffset+x2*this.xscl, this.vpLLy+this.yoffset+y2*this.yscl); /* update pen position for excanvas */
    this.ctx.restore();
  }

  cvsGraphCtx.prototype.rect = function(x, y, w, h, fillColor)
  {
    this.ctx.save();   // we are going to change pencolor save the clean ctx
    this._setCtx();   // set up the stroke and fill styles

    var newCol = new RGBColor(fillColor);

    if (newCol.ok)
    {
      this.ctx.fillStyle = newCol.toHex();
      this.ctx.fillRect(this.vpLLx+this.xoffset+x*this.xscl, this.vpLLy+this.yoffset+y*this.yscl, w*this.xscl, h*this.yscl);
      this.ctx.fillStyle = this.penCol;        // restore fill color to pen color
    }
    else
    {
      // just stoke the rectangle
      this.ctx.strokeRect(this.vpLLx+this.xoffset+x*this.xscl, this.vpLLy+this.yoffset+y*this.yscl, w*this.xscl, h*this.yscl);
    }
    this.ctx.restore();
  }

  cvsGraphCtx.prototype.shape = function(shape, x, y, size, fillColor)
  {
    var xLofs, yLofs;  /* label origin offsets */
    var fill = false;

    var d = size*this.xscl;     // size in x axis units

    this.ctx.save();   // we are going to change pencolor save the clean ctx
    this._setCtx();   // set up the stroke and fill styles
    var newCol = new RGBColor(fillColor);
    if (newCol.ok)                          // else color doesn't change uses penCol
    {
      this.ctx.fillStyle = newCol.toHex();
      fill = true;
    }
    switch (shape)
    {
      case "square":
        xLofs = -0.5*d;
        yLofs = -0.5*d;
        if (fill)
          this.ctx.fillRect(this.vpLLx+this.xoffset+x*this.xscl+xLofs, this.vpLLy+this.yoffset+y*this.yscl+yLofs, d, d);
        else
          this.ctx.strokeRect(this.vpLLx+this.xoffset+x*this.xscl+xLofs, this.vpLLy+this.yoffset+y*this.yscl+yLofs, d, d);
        break;
      case "triangle":
        this.ctx.beginPath();                             // Begin a shape
        xLofs = 0;
        yLofs = -0.5747*d;
        this.ctx.moveTo(this.vpLLx+this.xoffset+x*this.xscl+xLofs, this.vpLLy+this.yoffset+y*this.yscl+yLofs);
        xLofs = 0.5*d;
        yLofs = 0.2887*d;
        this.ctx.lineTo(this.vpLLx+this.xoffset+x*this.xscl+xLofs, this.vpLLy+this.yoffset+y*this.yscl+yLofs);
        xLofs = -0.5*d;
        yLofs = 0.2887*d;
        this.ctx.lineTo(this.vpLLx+this.xoffset+x*this.xscl+xLofs, this.vpLLy+this.yoffset+y*this.yscl+yLofs);
        this.ctx.closePath();
        if (fill)
          this.ctx.fill();                                  // Fill the shape
        else
          this.ctx.stroke();
        break;
      case "circle":
      default:
        this.ctx.beginPath();                             // Begin a shape
        this.ctx.arc(this.vpLLx+this.xoffset+x*this.xscl, this.vpLLy+this.yoffset+y*this.yscl, 0.5*d, 0, 2*Math.PI, false);
        this.ctx.closePath();  // not required for fills
        if (fill)
          this.ctx.fill();
        else
        this.ctx.stroke();
    }                                  // Fill the shape
    this.ctx.restore();
  }

  cvsGraphCtx.prototype.drawImg = function(imgURL, x, y, w, lorg, degs)
  {
    var img = new Image();
    // load the image
    img.src = imgURL;

    this.updateImg(img, x, y, w, lorg, degs);

    return img;
  }

  cvsGraphCtx.prototype.updateImg = function(img, x, y, w, lorg, degs)
  {
    var savThis = this;               // form a closure for callback execution

    if ((img.complete)&&(!_busy[this.cId]))  // image loaded? engine busy?
    {
      _busy[this.cId] = true;
      modifyImg(savThis, img, x, y, w, lorg, degs);
      _busy[this.cId] = false;
    }
    else  // not loaded yet
    {
      setTimeout(function(){savThis.updateImg(img, x, y, w, lorg, degs)}, 50);
    }
  }

  function modifyImg(savThis, img, x, y, w, lorg, degs)
  {
      var reScale = w*savThis.xscl/img.width;   // canvas is to be scaled by this pixel ratio
      var xLofs, yLofs;  /* label origin offsets */
      var rot = 0;

      if (degs != undefined)
        rot = -degs*Math.PI/180.0;      // measure angles counter clockwise

      switch (lorg)
      {
        case 1:
          xLofs = 0;
          yLofs = 0;
          break;
        case 2:
          xLofs = 0.5*img.width;          // work in pixels assume image already scaled
          yLofs = 0;
          break;
        case 3:
          xLofs = img.width;
          yLofs = 0;
          break;
        case 4:
          xLofs = 0;
          yLofs = 0.5*img.height;
          break;
        case 5:
          xLofs = 0.5*img.width;
          yLofs = 0.5*img.height;
          break;
        case 6:
          xLofs = img.width;
          yLofs = 0.5*img.height;
          break;
        case 7:
          xLofs = 0;
          yLofs = img.height;
          break;
        case 8:
          xLofs = 0.5*img.width;
          yLofs = img.height;
          break;
        case 9:
          xLofs = img.width;
          yLofs = img.height;
          break;
        default:
          xLofs = 0;
          yLofs = 0;
      }

      savThis.ctx.save();   // save the clean ctx
      savThis.ctx.translate(savThis.vpLLx+x*savThis.xscl+savThis.xoffset, savThis.vpLLy+y*savThis.yscl+savThis.yoffset);        // move origin to image lorg point
      savThis.ctx.scale(reScale, reScale);
      savThis.ctx.rotate(rot);
      savThis.ctx.drawImage(img, -xLofs, -yLofs);   // bug in drawImage(img, x, y, w, h) so don't use it
      savThis.ctx.restore();
  }

  cvsGraphCtx.prototype.label = function(str, x, y, lorg, degs, ptSize)
  {
    var savThis = this;               // form a closure for callback execution

    if (!_busy[this.cId])  // engine busy?
    {
      _busy[this.cId] = true;
      modifyLabel(savThis, str, x, y, lorg, degs, ptSize);
      _busy[this.cId] = false;
    }
    else  // engine busy, comeback later
    {
      setTimeout(function(){savThis.label(str, x, y, lorg, degs, ptSize)}, 50);
    }
  }

  function modifyLabel(savThis, str, x, y, lorg, degs, ptSize)
  {
    var fSize = savThis.fontSize;
    var xLofs, yLofs;  /* label origin offsets */
    var rot = 0;

    if (degs != undefined)
      rot = -degs*Math.PI/180.0;      // measure angles counter clockwise

    if ((ptSize != undefined)&&(ptSize>4))    // ptSize is points
      fSize = ptSize;

    var strLen = CanvasTextFunctions.measure(0, fSize, str);

    /* Note: char cell is 33 pixels high, char size is 21 pixels (0 to 21), decenders go to -7 to 21.
       passing 'size' to text function scales char height by size/25.
       So reference height for vertically alignment is charHeight = 21/25 (=0.84) of the fontSize. */
    switch (lorg)
    {
      case 1:
      default:
        xLofs = 0;
        yLofs = 0.84*fSize;
        break;
      case 2:
        xLofs = 0.5*strLen;
        yLofs = 0.84*fSize;
        break;
      case 3:
        xLofs = strLen;
        yLofs = 0.84*fSize;
        break;
      case 4:
        xLofs = 0;
        yLofs = 0.42*fSize;
        break;
      case 5:
        xLofs = 0.5*strLen;
        yLofs = 0.42*fSize;
        break;
      case 6:
        xLofs = strLen;
        yLofs = 0.42*fSize;
        break;
      case 7:
        xLofs = 0;
        yLofs = 0;
        break;
      case 8:
        xLofs = 0.5*strLen;
        yLofs = 0;
        break;
      case 9:
        xLofs = strLen;
        yLofs = 0;
        break;
    }

    savThis.ctx.save();   // we are going to change pencolor save the clean ctx
    savThis._setCtx();   // set up the stroke and fill styles
    // setup for rotation by rot degrees
    savThis.ctx.translate(savThis.vpLLx+x*savThis.xscl+savThis.xoffset, savThis.vpLLy+y*savThis.yscl+savThis.yoffset);   // move origin to the lorg point of the text
    savThis.ctx.rotate(rot);
    CanvasTextFunctions.draw(savThis.ctx, 0, fSize, -xLofs, yLofs, str);
    savThis.ctx.restore();
  }

  cvsGraphCtx.prototype.drawAxes = function(xOrg, yOrg, xMin, xMax, yMin, yMax, xMinTic, yMinTic, xMajTic, yMajTic)
  {
    var x, y;
    // make the length of tick marks 1% of the screen width
    var xTicLen = -0.007*this.vpW/this.yscl;
    var yTicLen = 0.007*this.vpW/this.xscl;

    var xTics = new AxisTicsManual(xMin, xMax, xMinTic, xMajTic);
    var yTics = new AxisTicsManual(yMin, yMax, yMinTic, yMajTic);

    // draw axes
    this.move(xMin, yOrg);
    this.line(xMax, yOrg);   // X axis
    this.move(xOrg, yMin);
    this.line(xOrg, yMax);   // Y axis

    // X axis tick marks
    if (xTics.ticStep)
    {
      for(x=xTics.tic1; x<=xMax; x+=xTics.ticStep)
      {
        this.move(x, yOrg - xTicLen);
        this.line(x, yOrg + xTicLen);
      }
    }
    // Y axis tick marks
    if (yTics.ticStep)
    {
      for(y=yTics.tic1; y<=yMax; y+=yTics.ticStep)
      {
        this.move(xOrg - yTicLen, y);
        this.line(xOrg + yTicLen, y);
      }
    }

    // major ticks X axis
    if (xTics.lblStep)
    {
      for(x=xTics.lbl1; x<=xMax; x+=xTics.lblStep)
      {
        this.move(x, yOrg - 1.5*xTicLen);
        this.line(x, yOrg + 1.5*xTicLen);
      }
    }
    // major ticks Y axis
    if (yTics.lblStep)
    {
      for(y=yTics.lbl1; y<=yMax; y+=yTics.lblStep)
      {
        this.move(xOrg - 1.5*yTicLen, y);
        this.line(xOrg + 1.5*yTicLen, y);
      }
    }

    // now label the axes
    var xOfs = 4*xTicLen;
    var yOfs = 4*yTicLen;
  	var lorg;

		if (xTics.lblStep)
		{
    	// X axis, decide whether to label above or below X axis
  		if (((yOrg<yMin+0.55*(yMax-yMin)) && (this.yscl<0))||((yOrg>yMin+0.45*(yMax-yMin)) && !(this.yscl<0)))
    	{
  			// x axis on bottom half of screen
    		xOfs = -xOfs;
    		lorg = 2;
    	}
    	else
    	{
    		lorg = 8;
    	}
    	for (x = xTics.lbl1; x<=xMax; x += xTics.lblStep)
    	{
  			// skip label at the origin if it would be on the other axis
  			if ((x==xOrg)&&(yOrg>yMin)&&(yOrg<yMax))
  				continue;
    		this.label(engNotation(x), x, yOrg+xOfs, lorg);
    	}
    }

		if (yTics.lblStep)
		{
    	// Y axis, decide whether to label to right or left of Y axis
    	if (((xOrg<xMin+0.5*(xMax-xMin)) && (this.xscl>0))||((xOrg>xMin+0.5*(xMax-xMin)) && !(this.xscl>0)))
    	{
    		// y axis on left half of screen
    		yOfs = -yOfs;
    		lorg = 6;
    	}
    	else
    	{
    		lorg = 4;
    	}
    	for (y = yTics.lbl1; y<=yMax; y += yTics.lblStep)
    	{
  			// skip label at the origin if it would be on the other axis
  			if ((y==yOrg)&&(xOrg>xMin)&&(xOrg<xMax))
  				continue;
    		this.label(engNotation(y), xOrg+yOfs, y, lorg);
    	}
    }
  }

  cvsGraphCtx.prototype.drawBoxAxes = function(xMin, xMax, yMin, yMax, xMn, yMn, xUnits, yUnits, title)
  {
		var x, y;
		var lbl;
  	// make the length of tick marks 1% of the screen width
  	var xTicLen = -0.015*this.vpW/this.yscl;
  	var yTicLen = 0.015*this.vpW/this.xscl;
  	var xOfs = -0.02*this.vpW/this.yscl;
  	var yOfs = 0.02*this.vpW/this.xscl;
		var dx;		// tolerance to handle maths noise
		var dy;

		var xTics = new AxisTicsManual(xMin, xMax, xMn);
		var yTics = new AxisTicsManual(yMin, yMax, yMn);

		// Draw box axes
		this.move(xMin, yMin);
  	this.line(xMin, yMax);
  	this.line(xMax, yMax);
  	this.line(xMax, yMin);
  	this.line(xMin, yMin);

		if (xMn == undefined)
			return;
		dx = 0.01*xTics.ticStep;		// tolerance to handle maths noise
  	for (x=xTics.tic1; x<=xMax+dx; x += xTics.ticStep)
  	{
  		this.move(x, yMin-xTicLen);
 		  this.line(x, yMin);
      if ((x != xMin)&&(x != xMax))        // no dots on the box
      {
       	this.line(x, yMax, "dotted");      // grid lines
      }
  	}
		if (yMn == undefined)
			return;
		dy = 0.01*yTics.ticStep;
  	for (y=yTics.tic1; y<=yMax+dy; y += yTics.ticStep)
  	{
  		this.move(xMin-yTicLen, y);
    	this.line(xMin, y);
      if ((y != yMin)&&(y != yMax))
      {
  		  this.line(xMax, y, "dotted");      // grid lines
      }
  	}

		// Now labels
  	// X axis, label only first and last tic below X axis
		x = xTics.tic1;
		this.label(engNotation(x), x, yMin - xOfs, 1);  // label first tic (lorg=1)
  	while(x+xTics.ticStep <= xMax+dx)
  	{
  		x += xTics.ticStep;
  	}
		this.label(engNotation(x), x, yMin - xOfs, 3);	// label last tic (lorg=3)

		// Y axis, label bottom and top tics to left of Y axis
 		y = yTics.tic1;
  	this.label(engNotation(y), xMin - yOfs, y, 6);  // label bottom tic (lorg=6)
  	while (y + yTics.ticStep <= yMax+dy)
  	{
			y += yTics.ticStep;
  	}
		this.label(engNotation(y), xMin - yOfs, y, 6); // label top tic (lorg=6)

    // x axis label
    if (typeof xUnits == "string")
      lbl = engNotation(xTics.ticStep)+xUnits+"/div";
    else
      lbl = engNotation(xTics.ticStep)+"/div";
    x = xMin+(xMax-xMin)/2
		this.label(lbl, x, yMin - xOfs, 2);  // label in center of x axis (lorg 2)

    // y axis label
    if (typeof yUnits == "string")
      lbl = engNotation(yTics.ticStep)+yUnits;
    else
      lbl = engNotation(yTics.ticStep);
    y = yMin+(yMax-yMin)/2;
		this.label(lbl, xMin - yOfs, y, 9);     // label in center of y axis (lorg 9)
    y = yMin+(yMax-yMin)/2.15;
		this.label("/div", xMin - yOfs, y, 3);  // label just below center of y axis (lorg 3)

    // title
    if (typeof title == "string")
      this.label(title, xMin, yMax + xOfs, 7);
	}

  cvsGraphCtx.prototype.drawXYAxes = function(xOrg, yOrg, xMin, xMax, yMin, yMax, xUnits, yUnits, xLabel, yLabel)
  {
    var xTics = new AxisTicsAuto(xMin, xMax);
    var yTics = new AxisTicsAuto(yMin, yMax);

    var xOfs = -0.07*this.vpW/this.yscl;
    var yOfs = 0.14*this.vpW/this.xscl;
    var lorg;

    this.drawAxes(xOrg, yOrg, xMin, xMax, yMin, yMax, xTics.ticStep, yTics.ticStep, 2*xTics.ticStep, 2*yTics.ticStep);
    var xU = "";
    var yU = "";
    if ((xUnits != undefined)&&(xUnits.length>0))
      xU = "("+xUnits+")";
    if ((yUnits != undefined)&&(yUnits.length>0))
      yU = "("+yUnits+")";

    var xL = "";
    if ((xLabel != undefined)&&(xLabel.length>0))
      xL = xLabel;
    if (((yOrg<yMin+0.55*(yMax-yMin)) && (this.yscl<0))||((yOrg>yMin+0.45*(yMax-yMin)) && !(this.yscl<0)))
    {
      xOfs = -xOfs;
      lorg = (this.xscl>0)? 3: 1;
    }
    else
    {
      lorg = (this.xscl>0)? 9: 7;
    }
    this.label(xL+xU, xMax, yOrg+xOfs, lorg, 0, 13);

    var yL = "";
    if ((yLabel != undefined)&&(yLabel.length>0))
      yL = yLabel;
  	// Y axis, decide whether to label to right or left of Y axis
  	if (((xOrg<xMin+0.5*(xMax-xMin)) && (this.xscl>0))||((xOrg>xMin+0.5*(xMax-xMin)) && !(this.xscl>0)))
  	{
  		// y axis on left half of screen
  		yOfs = -yOfs;
      lorg = (this.yscl<0)? 9: 7;
  	}
  	else
  	{
      lorg = (this.yscl<0)? 3: 1;
  	}
    this.label(yL+yU, xOrg+yOfs, yMax, lorg, 90, 13);
  }

  function AxisTicsManual(xmin, xmax,	xMn, xMj)
	{
    this.tic1 = 0;
    this.ticStep = 0;
    this.lbl1 = 0;
    this.lblStep = 0;
    this.ticScl = 0;     // reserved for future use

		if ((xmin==undefined)||(xmax==undefined)||(xMn==undefined))
			return;

		var dx;		// tolerance for avoiding maths noise

		if (xMn!=0)
		{
			dx = 0.01*xMn;
			this.tic1 = xMn*Math.ceil((xmin-dx)/xMn);
      this.ticStep = xMn;
		}

		if ((xMj!=undefined)&&(xMj>0))
		{
			this.lblStep = this.ticStep*Math.round(xMj/xMn);
			dx = 0.01*xMn;
			this.lbl1 = this.lblStep*Math.ceil((xmin-dx)/this.lblStep);
		}

    // to make all labels have same scale factor calc lastTic and corresponding tag "m kMG" etc
    // calc engnotation for xTic1 exp=xTicScl, tag=xTicTag
    // plot x = xtic1 + n*xTicStep
    // label x/xTicScl+xTicTag
	}

  function AxisTicsAuto(min, max, numTics)   // optional 'numTics' forces the value of numSteps
  {
    this.tic1;
    this.ticStep;
    this.lbl1 = 0;
    this.lblStep = 0;
    this.ticScl = 0;     // reserved for future use

    if (numTics>1)
      this.calcNTics(min, max, numTics);
    else  // auto tick the axis
      this.calcTics(min, max);
  }

/* calcTics(min, max)
 * Calculate the tic mark spacing for graph plotting
 * Given the minimum and maximum values of the axis
 * returns the first, last tic values, the tic spacing and number
 * of ticks.
 * The algorithm gives tic spacing of 1, 2, 5, 10, 20 etc
 * and a number of ticks from ~5 to ~11
 */
  AxisTicsAuto.prototype.calcTics = function(mn, mx)
  {
    var pwr, spanman, stepman;
    var spanexp, stepexp;

    if (mn>=mx)
    {
      alert("Max must be greater than Min!");
      return;
    }
    pwr = Math.log(mx-mn)/Math.LN10;
    if (pwr<0.0)
      spanexp = Math.floor(pwr) - 1;
    else
      spanexp = Math.floor(pwr);
    spanman = (mx-mn)/Math.pow(10.0, spanexp);
    if(spanman>=5.5)
    {
      spanexp += 1;
      spanman /= 10.0;
    }
    stepman = 0.5;
    if(spanman<2.2)
      stepman = 0.2;
    if(spanman<1.1)
      stepman = 0.1;
    stepexp = 3*Math.floor(spanexp/3);
    if((spanexp<0)&&(spanexp%3!=0))
      stepexp -= 3;
    stepman = stepman*Math.pow(10.0, (spanexp-stepexp));
    this.ticStep = stepman*Math.pow(10.0, stepexp);
    if(mn>=0.0)
      this.tic1 = (Math.floor((mn/this.ticStep)-0.01)+1)*this.ticStep;   // avoid math noise
    else
      this.tic1 = -Math.floor((-mn/this.ticStep)+0.01)*this.ticStep;   // avoid math noise

/*    var str = "";
    str += "tic1= "+this.tic1+ "\n";
    str += "lastTic= "+this.lastTic+ "\n";
    str += "ticStep= "+this.ticStep+ "\n";
    str += "numSteps= "+this.numSteps;
    alert(str);
*/
  }

  AxisTicsAuto.prototype.calcNTics = function(mn, mx, n)
  {
    if (mn>=mx)
    {
      alert("Max must be greater than Min!");
      return;
    }

    this.tic1 = mn;
    this.ticStep = (mx-mn)/n;
//    this.lastTic = mx;
//    this.numSteps = n+1;         // n steps, n+1 ticks

/*    var str = "";
    str += "ymin="+mn+"  ymax="+mx+"\n";
    str += "tic1= "+this.tic1+ "\n";
    str += "lastTic= "+this.lastTic+ "\n";
    str += "ticStep= "+this.ticStep+ "\n";
    str += "numSteps= "+this.numSteps;
    alert(str);
*/
  }

  function engNotation(val)        // rounds to 2 dec places and strips trailing 0s
  {
    var unit = "pnum kMG";
    var man, pwr;
    var expt = 0;
    var str = "";

    man = 0.0;
    if (Math.abs(val)>1.0E-12)
    {
      pwr = Math.log(Math.abs(val))/(3.0*Math.LN10);
      expt = Math.floor(pwr);
      man = val/Math.pow(1000.0, expt);
      expt *= 3;
    }
    // now force round to decPlaces
    str = man.toFixed(2);
    // now strip trailing 0s
    while (str.charAt(str.length-1)=='0')
      str = str.substring(0,str.length-1);
    if (str.charAt(str.length-1)=='.')
      str = str.substring(0,str.length-1);
    // now add the symbol for the exponent
    if (expt)
      return str+unit.charAt(expt/3+4);
    else
      return str;                   // dont add unnecessary space
  }

