// ***************** COPYRIGHT (c) 2008 STEFAN WANER ******************
// *********************** ALL RIGHTS RESERVED ************************



var theActions = new Array();
var canvas, theCanvas, ctx; // for manipulating the current canvas

var numberTesting = 0;
var numTesting2 = 0;
var jsDraggingBar = false;
var jsDraggingBarGraphNumber = false;
var jsDraggingBarNumber = false;
var jsPuttingXLabels = false; // special flag that allows user labels under the graph area.
var jsRefreshingGraph = false; // if true skip setup  stage
var jsxLabelMinSpace = 12, jsyLabelMinSpace = 40; // minimum space for axis labels
var jsXGridAuto = true; // auto gridlines
var jsYGridAuto = true;
var jsTheCurveNum = 0; // the curve selected for tracing or other pruposes

// error messages
var undefinedMg = "Y is not defined for the given range of X.";
try{
if (theLanguage == "es") undefinedMg ="Y no es definido por el rango especificado de X."
}
catch(error) {}

// various math functions

function sin(x) { return Math.sin(x) }
function cos(x) { return Math.cos(x) }
function tan(x) { return Math.sin(x)/Math.cos(x) }
function cot(x) { return Math.cos(x)/Math.sin(x) }
function sec(x) { return 1/Math.cos(x) }
function csc(x) { return 1/Math.sin(x) }


function grapher(name, width, height, bColor) {
// bColor = border color
// format: "graphFunction",function_string, color
// format "plotPoints",number,x1,y1,...,xn,yn,color


this.canvaslabel = name + "Canvas"; // name of the canvas tag
this.id = name;

this.numX = width;
this.numY = height;
this.showCoords = 1;
this.tracing = 0; // tracing takes place on curves only
this.traceColor = "red";
this.traceStep = 0; // will set on insertGraph()
this.decPlacesX = 2; // rounding of x-coordinates
this.decPlacesY = 2; // rounding of y-coordinates
this.draggableBars = 0; // if 1 you can drag bars from the top
this.edgeBuffer = 8; // minimum overflow for labels, arrows, etc
this.borderColor = bColor; // make it the empty string for no border
this.backgroundColor = "white"; // else in format [rrr, ggg, bbb]
this.surroundColor = ""; // same format; not specifiied means does nothing
this.gridColor = "aqua";
this.coordsColor = "blue"; // color of coordinates appearing below 
this.xAxis = "on";
this.yAxis = "on";
this.DeltaYAxisPosition = 0;
this.xGrid = "on";
this.yGrid = "on";
this.xLabels = []; // should match gridline positions
this.xLabelPosition = "onLines"; // otherwise "midLines"
this.xLabelsBelow = false;
this.yLabelsLeft = false;
this.nudgeDownxValues = 0; // pixels down (or up if neg) to shift x-axis labels 
this.yLabelMargin = 2; 
this.xLabelMargin = 2;
// the above two may be increased to get in the labels later
// set it to at least the total space you want under the graph
// to write, say, a title unde the graph
this.topMargin = 2; // for names etc
this.rightMargin = 2;
this.title = '';
this.pointsColor = "purple";
this.pointsText = '&nbsp; &nbsp; '; // the text where the coordinates are normally shown
this.action = doActions;
this.clicked = false;
this.slope = ""; // will be the line created by the user 
this.intercept = "";
this.window = [-10, 10, -10, 10];
this.xActual = ""; // coordinates of cursor on graph
this.yActual = "";
this.xScreen = ""; // screen coordinates of cursor on graph
this.yScreen = "";
this.xClicked = ""; // screen coordinates of cursor on graph when clicked
this.yClicked = "";
this.xTrace = ""; // actual coordinates of cursor on graph when clicked
this.yTrace = "";
this.xPrev = ""; 
this.yPrev = "";
this.actionsList = new Array(); // will be an array of actions described below
this.xGridStep = 1;
this.yGridStep = 1;
this.plottedPointsX = [0];
this.plottedPointsY = [0];
this.plottedCurves = []; 
	// a plotted curve is an array [string, xMin, xMax,"dot/circle/arrow","dot/circle/arrow", [shade (t/f), color, opacity, shade-to-curve], [detltaLeftArrow, deltaRightArrow]
	// detltaLeftArrow etc adjust angle of arrows.
this.curvesColors = ["teal","magenta","navyblue","darkviolet","coral", "purple","aqua","blue","red","grey", "firebrick", "black" ];
this.pointsPerCurve = 1.5*(this.numY + this.numX); 
this.minDetlaXFactor = .00001; 
	// fraction of window for smallest Æx used in adaptiveSegment
this.adaptive = false; // permits adaptiveSegment
this.bars = [];
	// a bar is an array xCoords, yCoords, delta1, delta2, color, opacity, border opacity
	// x coords of the bar are [x-detla1, x+delta2]
	// opacity = -1 means pure white;
	// if color = "[ [r1, g1, b1] ,[r2, g2, b2]]" then it will scale them;
this.barLabels = "";  // either "y", or "xy" for both coords at present

this.showScalex = true;
this.showScaley = true;
this.showEveryx = 1; // allows a tick mark on every 1 grid line
this.showEveryy = 1;
this.startTicksx = 1; // start counting at first
this.startTicksy = 1;
this.switchShownLablesY = false // set to true to reverse which lables shown
this.xTicsRange = [];
this.yTicsRange = [];
this.xTics = []; // for user defined gridlines
this.yTics = []; // format [n, x1, x2, x3, ... xn]
this.gridType = ["line"]; // set to ["tic",length in px] for tickmarks
this.arrowLength = 9; // dimensions of arrowheads
this.arrowFlare = 0.3 // radians
this.LRShadedBWidth = 2; // thickness of lines on left and right of shaded areas
this.Misc = [0, 0, 0, 0] // store miscellaneous properties here
this.MiscVal = [0, 0, 0, 0];
this.MiscLogic = [false, false, false, false];
this.onMouseMove = "";
this.onMouseDown = "";
this.onMouseUp = "";
this.onMouseOver = "makeGraph2(" + this.id + "); jsRefreshingGraph = true; capturePointsText(" + this.id + "); "
this.onMouseOut = "jsRefreshingGraph = false; restorePointsText(" + this.id + "); ";

}

function insertGraphWMyMath(theGraph) {
with(theGraph) {
	// preliminaries:
	onMouseMove = "alertCoord(arguments[0], " + id + ", " + decPlacesX + ", " + decPlacesY + "); ";
	if (draggableBars == 1) {
		onMouseDown = "dealWithMouseDownBars(" + id + "); ";
		onMouseMove += "dealWithMouseMoveBars(" + id + "); ";
		onMouseUp = "dealWithMouseUpBars(" + id + "); ";
		} // ifDragableBars
	else if (tracing == 1) {
		onMouseDown = "dealWithMouseDownTrace(" + id + "); ";
				} // if tracing
	var theString = ' <div id = '+ canvaslabel + 'Div style = "position:relative;"><canvas id='+ canvaslabel + ' width="' + parseInt(numX + edgeBuffer + yLabelMargin + rightMargin) +  '" height="' + parseInt(numY + edgeBuffer + xLabelMargin + topMargin)+ '" style = "{ cursor: crosshair;}" onmousemove = "' + onMouseMove + '" onmousedown = "' + onMouseDown + '" onmouseup = "' + onMouseUp + '" onmouseover = "' + onMouseOver + '" onmouseout = "' + onMouseOut + '"></canvas><div id = '+ canvaslabel + 'Span' + ' width=' + parseInt(numX + edgeBuffer + yLabelMargin + rightMargin) +  'px height=' + parseInt(numY + edgeBuffer + xLabelMargin + topMargin) + 'px style = " position:absolute; top:0px; left:0px; z-index:3"></div><div id = '+ canvaslabel + 'Text style = "position:relative; top: -' + parseInt(numY + edgeBuffer + xLabelMargin + topMargin)+ 'px; right: 0px; z-index:5"> </div></div><div><span id = underGraphL' + id + '></span><span id = traceCoords' + id + ' style = "color: ' + coordsColor + '"> &nbsp; &nbsp; </span> <span id = underGraphR' + id + '></span></div>';
// alert(theString);
	document.writeln(theString);
	return(theString);
	}
} // insertGraph

function insertGraph(theGraph) {
with(theGraph) {
	// preliminaries:
	
	onMouseMove = "alertCoord(arguments[0], " + id + ", " + decPlacesX + ", " + decPlacesY + "); ";
	if (draggableBars == 1) {
		onMouseDown = "dealWithMouseDownBars(" + id + "); ";
		onMouseMove += "dealWithMouseMoveBars(" + id + "); ";
		onMouseUp = "dealWithMouseUpBars(" + id + "); ";
		} // ifDragableBars
	else if (tracing == 1) {
		if (traceStep == 0) traceStep = (window[1]-window[0])/numX;
		onMouseDown = "dealWithMouseDownTrace(" + id + "); ";
// alert(traceStep);
		} // if tracing
	var theString = '<div id = '+ canvaslabel + 'Div style = "position:relative;"><canvas id='+ canvaslabel + ' width="' + parseInt(numX + edgeBuffer + yLabelMargin + rightMargin) +  '" height="' + parseInt(numY + edgeBuffer + xLabelMargin + topMargin)+ '" style = "{ cursor: crosshair;}" onmousemove = "' + onMouseMove + '" onmousedown = "' + onMouseDown + '" onmouseup = "' + onMouseUp + '" onmouseover = "' + onMouseOver + '" onmouseout = "' + onMouseOut + '" ></canvas><div id = '+ canvaslabel + 'Span' + ' width=' + parseInt(numX + edgeBuffer + yLabelMargin + rightMargin) +  'px height=' + parseInt(numY + edgeBuffer + xLabelMargin + topMargin) + 'px style = " position:absolute; top:0px; left:0px; z-index:3"></div></div><div><span id = traceCoords' + id + ' style = "color: ' + coordsColor + '"> &nbsp; &nbsp; </span><span id = underGraphL' + id + '></span><span id = underGraphR' + id + '></span></div>'; 
//alert(theString);
	document.writeln(theString);
	return(theString);
	}
} // insertGraph

function doActions() {

}//


function putBars(theGraph) {
var labelOffsetPosition = 7; 
var numBarGraphs = theGraph.bars.length;
// alert(numBarGraphs);
for (var n = 0; n<= numBarGraphs-1; n++) {
	var h1 = theGraph.bars[n][2];
	var h2 = theGraph.bars[n][3];
	var theColor = theGraph.bars[n][4];
	var theOpacity = theGraph.bars[n][5];
	var theBarBorderOpacity = 1;
	try {theBarBorderOpacity = theGraph.bars[n][6]} 
	catch(error) {}

	if (isNaN(theBarBorderOpacity)) theBarBorderOpacity = 1;
	var theXBarCoords = theGraph.bars[n][0];
// alert(theGraph.bars[n][1]);
	var theYBarCoords = theGraph.bars[n][1];
// alert(theYBarCoords);
	var numBars = theXBarCoords.length
	var colorVarying = false;
// alert(theColor[1].length);
	try {var theL = theColor[1].length}
	catch (error) { var theL = 1}
	
	if (theL > 1) {
		colorVarying = true;
		var colorArray = new Array();
		colorArray[1] = theColor[0];
		colorArray[numBars] = theColor[1];
		var deltaCol = [(theColor[1][0] - theColor[0][0])/numBars, (theColor[1][1] - theColor[0][1])/numBars, (theColor[1][2] - theColor[0][2])/numBars ];
		for (var i = 2; i <= numBars-1; i++) {
			colorArray[i] = [0,0,0];
			for (var j = 0; j <= 2; j++) {
				colorArray[i][j] = Math.round(theColor[0][j] + i*deltaCol[j] );
				} // j
			} // i
// alert(colorArray);
		} // if varying color


	var x1, y1, x2, yLower;
//alert(theXBarCoords);
	for (var i = 0; i <= numBars -1; i++) { 
//alert(i);
		labelOffsetPosition = 7; // reset it
		x1 = theXBarCoords[i];
		y1 = theYBarCoords[i];
		yLower = theGraph.window[2];
		if (colorVarying) putBar(theGraph , x1- h1, x1+h2, 0, y1, colorArray[i+1], theOpacity, true, theBarBorderOpacity);
		else putBar(theGraph , x1- h1, x1+h2, 0, y1, theColor, theOpacity, false, theBarBorderOpacity);
	
		var labelType = theGraph.barLabels;
		if (labelType != "") {
// alert(x1 + "," + y1);
			var theStr = '';
			var xPlacement = x1 + (h2-h1)/2; // center of bar
			if (labelType == "y") theStr = y1.toString();
			else theStr = "(" + x1.toString() + ", " + y1.toString() + ")";
			if (y1 < 0) labelOffsetPosition = - labelOffsetPosition - 8;
			putText(theGraph, theStr, xPlacement,y1, 0, labelOffsetPosition)
			} // showLables
		} // i
	} // n

// now put in axes in case the bars overwrite them
var xAxisPlease = false, yAxisPlease = false;
if ((xAxisPosition != -1) &&(theGraph.xAxis == "on")) xAxisPlease = true;
if ((yAxisPosition != -1)&&(theGraph.yAxis == "on")) yAxisPlease = true;
if ((xAxisPlease) || (yAxisPlease)) drawAxes(theGraph, xAxisPlease, yAxisPlease);
}


function putBar(theGraph, x1, x2, y1, y2, col, opac, isRGB, borderOpac) {
// if (isRGB) alert(col);
// alert(x1 + ", " + x2 + ", " + y1 + ", " + y2);
if (y2 > y1) {var yt = y1; y1 = y2; y2 = yt}
var yMin = theGraph.window[2];
if (y2 < yMin) y2 = yMin;
if (y1 < yMin) y1 = yMin;
var realCoords1 = screenCoords(theGraph, x1, y1);
var realCoords2 = screenCoords(theGraph, x2, y2);
var x1b = realCoords1[0];
var y1b = realCoords1[1];
var x2b = realCoords2[0];
var y2b = realCoords2[1];


theCanvas = document.getElementById(theGraph.canvaslabel);
ctx = theCanvas.getContext("2d");
if (opac == -1) ctx.fillStyle = lineStyler("white"); // white solid
else if (!isRGB) ctx.fillStyle = lineStyler(col, true, opac);
else {
	var theBStyle = "rgba(" + col[0] + "," + col[1] + "," + col[2] + "," + opac + ")"; 
// alert(theBStyle);
	ctx.fillStyle = theBStyle;

	}
// alert(x1b + ",  " + y2b + ",  " + (x2b-x1b) + ",  " + (y1b-y2b) + ",  ");
ctx.fillRect (x1b,y1b, x2b-x1b, y2b-y1b);
if (!isRGB) ctx.strokeStyle = lineStyler(col, true, borderOpac);
else ctx.strokeStyle = "rgba(" + col[0] + "," + col[1] + "," + col[2] +  "," + borderOpac + ")";
ctx.strokeRect(x1b,y1b, x2b-x1b, y2b-y1b); // boundary
 
	} // putBar


function plotPoints(theGraph, type, showLabels, labelType) { 
// labelType = '' gives (x, y) (default)
// labelType = 'y' gives y coord only
// types:   "x"    "."    "o"     "-"    "-x-"  "-.-"  '-o-" 
var labelOffsetPosition = 7; 
var theColor = theGraph.pointsColor;
if (type == "") type = "x"; // default is just xs
if ( (type.indexOf(".")!= -1) || (type.indexOf("x")!= -1) || (type.indexOf("o")!= -1)) {
	
	// plot x's
	for (var i = 1; i <= theGraph.plottedPointsX.length-1; i++) {
		if (type.indexOf("x")!= -1) putX(theGraph, parseFloat(theGraph.plottedPointsX [i]), parseFloat(theGraph.plottedPointsY [i]), theGraph.pointsColor);
		else if (type.indexOf(".")!= -1) putDot(theGraph, parseFloat(theGraph.plottedPointsX [i]), parseFloat(theGraph.plottedPointsY [i]), theGraph.pointsColor, false);
		else putDot(theGraph, parseFloat(theGraph.plottedPointsX [i]), parseFloat(theGraph.plottedPointsY [i]), theGraph.pointsColor, true); // open dot
		} // i points
	} // has x's 
	
	var x1, x2, y1, y2;
	x1 = theGraph.plottedPointsX [1]; y1 = theGraph.plottedPointsY [1];
	var numPts = theGraph.plottedPointsX.length-1;
	for (var i = 2; i <= numPts+1; i++) {
		if (i <= numPts) {x2 = theGraph.plottedPointsX [i]; y2 = theGraph.plottedPointsY [i];}
// alert(x1 + "," + y1 + "," + x2 + "," + y2 + "," + theColor);
		if (type.indexOf("-")!= -1) {
			if (i <= numPts) segment(theGraph, x1,y1,x2,y2, theColor);
			}
		if (showLabels) {
			var theStr = '';
			if (labelType == "y") theStr = y1.toString();
			else theStr = "(" + x1.toString() + ", " + y1.toString() + ")";
			if (screenCoords(theGraph, x1, y1)[1] < 20) labelOffsetPosition = - labelOffsetPosition - 8;
// alert(x1 + "," + y1);
			putText(theGraph, theStr, x1,y1, 0, labelOffsetPosition)
			} // showLables
		x1 = x2; y1 = y2;
		} // i points


} // plotPoints



var currentGraphjs = "";
var directionjs = 0;
// * Now rotation function definitions

var moveX = (function(){
  var isRunning;
  var action;
  return {
    start : function(){
		try{
			traceGraph(currentGraphjs, directionjs);
    			isRunning = setTimeout('moveX.start();', 200);
			}
		catch(error){}
    },
    stop : function (){
		try{
      	if (isRunning) clearTimeout(isRunning); 
			}
		catch(error){}
    }
  }

})();


function traceGraph(theGraph, direction) {
// moves a single pixel left or right on the current or nearest graph.
// 0 = left; 1 = right
var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
var deltaX = theGraph.traceStep;


if (direction == 0) {
	if (theGraph.xTrace > xMin) theGraph.xTrace -= deltaX;
	}
else if (theGraph.xTrace < xMax) theGraph.xTrace += deltaX;
// refreshGraph(theGraph);
try{refreshGraph(theGraph)} catch(error) {}
// find which curve is closest to this x-coord
var numCurves = theGraph.plottedCurves.length - 1;
var theCol = theGraph.traceColor;
var canPlot = true;

if ( (theGraph.plottedCurves[jsTheCurveNum][1] > theGraph.xTrace) || (theGraph.plottedCurves[jsTheCurveNum][2] < theGraph.xTrace) ) {
	canPlot = false;	
	var theDist = 2*(yMax-yMin); // bigger than possible
// must find a suitable curve that joins on to it

	for (var i = 0; i <= numCurves; i++) {
		if ( (theGraph.plottedCurves[i][1] <= theGraph.xTrace) && (theGraph.plottedCurves[i][2] >= theGraph.xTrace) ) {
			canPlot = true;
			x = parseFloat(theGraph.xTrace); y = myEval(theGraph.plottedCurves[i][0]);
			if (Math.abs(y- theGraph.yTrace) < theDist) {
				theDist = Math.abs(y- theGraph.yTrace);
				jsTheCurveNum = i;
				}
			} //in range
		} // i
// alert(x + " , " + y);
	} //looked for another graph
if (canPlot) {
	x = parseFloat(theGraph.xTrace);
	y = myEval(theGraph.plottedCurves[jsTheCurveNum][0]);
	putX(theGraph, x, y, theCol);
	document.getElementById("traceCoords" + theGraph.id).innerHTML =  '('+ x.toFixed(theGraph.decPlacesX) +', '+ y.toFixed(theGraph.decPlacesY) +')';
	theGraph.xTrace = x;
	theGraph.yTrace = y;
	}
} // traceGraph



function dealWithMouseDownTrace(theGraph){
// select and mark a point on the graph by clicking
// first refresh the graph to erase old plotted points
// this needs a function in the document that specifies how to refresh the particular graph (features. etc to show).
//name of this function is to be refreshGraph(theGraph);
try{refreshGraph(theGraph)} catch(error) {}
// find which curve is closest to this x-coord
var numCurves = theGraph.plottedCurves.length - 1;
var theCol = theGraph.traceColor;
var canPlot = false;

var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
var theDist = 2*(yMax-yMin); // bigger than possible
for (var i = 0; i <= numCurves; i++) {
	if ( (theGraph.plottedCurves[i][1] <= theGraph.xActual) && (theGraph.plottedCurves[i][2] >= theGraph.xActual) ) {
		canPlot = true;

		x = parseFloat(theGraph.xActual); y = myEval(theGraph.plottedCurves[i][0]);
		if (Math.abs(y- theGraph.yActual) < theDist) {
			theDist = Math.abs(y- theGraph.yActual);
			jsTheCurveNum = i;
			}
		} //in range
	} // i
// alert(x + " , " + y);
if (canPlot) {
	y = myEval(theGraph.plottedCurves[jsTheCurveNum][0]);
	putX(theGraph, x, y, theCol);
	document.getElementById("traceCoords" + theGraph.id).innerHTML =  '('+ roundDec(x,theGraph.decPlacesX) +', '+ roundDec(y,theGraph.decPlacesY) +')';
	theGraph.xTrace = x;
	theGraph.yTrace = y;
	theGraph.pointsText = document.getElementById("traceCoords" + theGraph.id).innerHTML;
	}

} // dealWithMouseDownTrace

function interactiveLine(theGraph) {
// alert(theGraph);
var theCol = "red";
theGraph.clicked = ! theGraph.clicked;
//alert(theGraph.id + "   ,  " + theGraph.clicked);
if(theGraph.clicked) {
// alert(theGraph.pointsColor);
	setUpGraph(theGraph); 
	plotPoints(theGraph); // just puts up points with no coordinates
	theGraph.clicked = true;
	putX(theGraph, parseFloat(theGraph.xActual), parseFloat(theGraph.yActual), theCol);
	theGraph.xPrev = theGraph.xActual;
	theGraph.yPrev = theGraph.yActual;
	} // if clicked once
else {
	putX(theGraph, theGraph.xActual, theGraph.yActual, theCol);
	// segment(theGraph, theGraph.xPrev, theGraph.yPrev, theGraph.xActual, theGraph.yActual, theCol);
	// now calculate the line 
	var theSlope = (theGraph.yActual- theGraph.yPrev)/ (theGraph.xActual- theGraph.xPrev);
	var theIntercept = theGraph.yActual - theSlope*theGraph.xActual;
	var endX = theGraph.window[1];
	var endY = theIntercept + theSlope*endX;
	var beginX = theGraph.window[0];
	var beginY = theIntercept + theSlope*beginX;
	segment(theGraph, beginX, beginY, endX, endY, theCol);
	theGraph.slope = roundSigDig(theSlope ,2);
	theGraph.intercept = roundSigDig(theIntercept,2);
// alert("y = "+ theGraph.slope + "x + " + theGraph.intercept);
	}
}// interactiveLine
 

function interactiveCurve(theGraph,type,theShift) {
// alert(theGraph);

var theCol = "red"; 
if ((type == "exponential")|| (type == "exponentialShifted")) {
	theGraph.clicked = ! theGraph.clicked;
//alert(theGraph.id + "   ,  " + theGraph.clicked);
	if(theGraph.clicked) {
// alert(theGraph.pointsColor);
		setUpGraph(theGraph); 
		plotPoints(theGraph); // just puts up points with no coordinates
		putBars(theGraph, true, 'y');
		theGraph.clicked = true;
		putX(theGraph, parseFloat(theGraph.xActual), parseFloat(theGraph.yActual), theCol);
		theGraph.xPrev = theGraph.xActual;
		theGraph.yPrev = theGraph.yActual;
		} // if clicked once
	else {
		putX(theGraph, theGraph.xActual, theGraph.yActual, theCol);
		// segment(theGraph, theGraph.xPrev, theGraph.yPrev, theGraph.xActual, theGraph.yActual, theCol);
		// now calculate the curve 
		if (type == "exponentialShifted") {
			var newX1 = theGraph.xPrev - theShift;
			var newX2 = theGraph.xActual - theShift;
			var theSlope = Math.log((theGraph.yActual)/(theGraph.yPrev))/ (newX2 - newX1);
			var theIntercept = Math.log(theGraph.yActual) - theSlope*newX2;
			}
		else {
			var theSlope = Math.log((theGraph.yActual)/(theGraph.yPrev))/ (theGraph.xActual- theGraph.xPrev);
		var theIntercept = Math.log(theGraph.yActual) - theSlope*theGraph.xActual;
			}
	var theN = theGraph.plottedCurves.length; 
	var beginX = theGraph.window[0];
	var endX = theGraph.window[1];
	var AStr = roundSigDig(Math.exp(theIntercept),6);
	var bStr = roundSigDig(Math.exp(theSlope),6);
	var theNewGrStr = AStr + "*" + bStr +"^x";
	if (type == "exponentialShifted") {
		theNewGrStr = AStr + "*" + bStr +"^(x-"+ theShift.toString() + ")";
// alert(theNewGrStr);
		}
	theGraph.plottedCurves[theN] = [theNewGrStr, beginX,endX];
	theGraph.curvesColors [theN %9] = "red";
	plotAllCurves(theGraph);
	// putBars(gbeerQ, true, 'y');
	theGraph.plottedCurves.length = theN; // truncate the array back to original
	theGraph.slope = roundSigDig(Math.exp(theSlope),4);
	theGraph.intercept = roundSigDig(Math.exp(theIntercept),4);
// alert("y = "+ theGraph.slope + "x + " + theGraph.intercept);
	}
	} // if exponential
}// interactiveCurve


function setUpGraph(theGraph) {
theCanvas = document.getElementById(theGraph.canvaslabel);
ctx = theCanvas.getContext("2d");
if (!jsRefreshingGraph ) makeGraph2(theGraph); 
		// determines axis & gridline positions
drawDecorations(theGraph);
// plotPoints(theGraph);
theGraph.clicked = false;
} // saveGraph

function eraseGraph(theGraph) {
setUpGraph(theGraph);
} // restoreGraph

// ***  IT ALSO REMAINS TO DO THE TOPMATTER

// *** file loader
// * Javascript loaders **
// * These are due to
// http://www.phpied.com/javascript-include/
function include(script_filename) {
    document.write('<' + 'script');
    document.write(' language="javascript"');
    document.write(' type="text/javascript"');
    document.write(' src="' + script_filename + '">');
    document.write('</' + 'script' + '>');
}
// writedoc loader

function include_dom(script_filename) {
    var html_doc = document.getElementsByTagName('head').item(0);
    var js = document.createElement('script');
    js.setAttribute('language', 'javascript');
    js.setAttribute('type', 'text/javascript');
    js.setAttribute('src', script_filename);
    html_doc.appendChild(js);
    return false;
}

// * Now one that includes new JS only if already included

var included_files = new Array();

function include_once(script_filename) {
    if (!in_array(script_filename, included_files)) {
        included_files[included_files.length] = script_filename;
        include_dom(script_filename);
    }
}

function in_array(needle, haystack) {
    for (var i = 0; i < haystack.length; i++) {
        if (haystack[i] == needle) {
            return true;
        }
    }
    return false;

}

// * end of on-the-fly javascript includers


// ** end of file loaders

// Globals
// window.onerror = myErrorTrap;

var cmr10 = new Array();
// load font information
var path1 = pathPrefix + "graphObject/100/chars100.js";
var path2 = pathPrefix + "graphObject/graphWriter.js";
include_once(path1);
include_once(path2);

// alert("width of A = " +cmr10[0x41][0]);


var e = 2.718281828459045;
var pi = 3.141592653589793;



var theColor = 0; // the color of a pixel
var numColors = 7;
var lineColor = new Array();
	lineColor[0] = 	"red";
	lineColor[1] = 	"blue";
	lineColor[2] = 	"purple";
	lineColor[3] = 	"green";
	lineColor[4] = 	"magenta";
	lineColor[5] = 	"grey";
	lineColor[6] = 	"orange";
	lineColor[7] = 	"yellow";


	
	var theDigit = new Array();

for (var i = 0; i <= 9; i++) {
	theDigit [i] = new Image;
	theDigit [i].src = pathPrefix + "graphObject/digits/char3" + i.toString(16) + ".png";
	} // i
 
	theDigit[10] = new Image();
	theDigit[10].src = pathPrefix + "digits/point.gif";
	theDigit[11] = new Image();
	theDigit[11].src = pathPrefix + "digits/plus.gif";
	theDigit[12] = new Image();
	theDigit[12].src = pathPrefix + "digits/minus.gif";
	theDigit[13] = new Image();
	theDigit[13].src = pathPrefix + "digits/infinity.gif";
	theDigit[14] = new Image();
	theDigit[14].src = pathPrefix + "digits/e.gif";
var digitWidth = [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 7, 6, 12, 8];
var counter = 0; // for debugging
var jsGSqueezeFactor = .9; 
// The larger jsGSqueezeFactor is, the more spread out the tick mark labels
// if the tickmark labels crash into each other, increase it to at least 1
var X = 0;
var x = 0;
var y = 0;

var h = 0;
var xh = 0;
var hx = 0;
var t = 0;
var th = 0;
var ht = 0;

var A = 0;
var p1 = 0;

var quoteMark = unescape( '%22' );
var singlequoteMark = unescape( '%27' );


var infinity = 10000000000000  // 10^13;
var windowcropTally = 20; 
	// will not cut a window in half if more than this number of 
     // pixels pop out of range as a result



var autoY = true;
var autoGridline = true;

var okToRoll = true;

var theString = "";

var theFunction = ""; // the function


var xGridLines = new Array(); // these are screen coordinates
var yGridLines = new Array();
var xMinorGridLines = new Array(); // these are screen coordinates
var yMinorGridLines = new Array();
var xGridLinesActual = new Array(); // these are actual coordinates
var yGridLinesActual = new Array();

// var lineColor = 2; // red
// var lineColorLite = lineColor + 7;

var xAxisPosition = 50; // position of axes = -1 if not visible
var yAxisPosition = 100;
var xGridStep = 10;
var yGridStep = 10;
var sigDigNumx = 2;  // for rounding of tick marks
var sigDigNumy = 2;
var xVals = new Array(); // to store the values of x for evlauator
var yVals = new Array();  // to store the functions
var tminVals = new Array(); 
var tmaxVals = new Array();

var arraysize = 0; // number of functions
var xArraysize = 0; // number of x-values in evalautor
var maxnum = 5;  // max number of functions allowed
var maxnumX = 12; // max number of x-values allowed
var theValues = new makeArray2(maxnumX, maxnum); // copy of evalauator data

var fracMode = false; // fraction mode off default
var numSigDigs = 5;  // rounding of y-values default
var maxDenom = 99999;

// *** end globals


// *** Error Handler ******
function myErrorTrap(message,url,linenumber) {
alert("A lo mejor ha ingresadao algo equivocado o tal vez bede probar una version mas viejo. Pulse 'Muestra Ejemplos' para ver ejemplos de funciones correctamente formateadas.");
return (true);
} // end of on error

// ********************



// **************** Plot All Curves ********************

function plotAllCurves(theGraph){
var theCurve;
for (var i = 0; i <= theGraph.plottedCurves.length-1; i++) {
	theCurve = theGraph.plottedCurves [i];
// alert (theCurve);
	var p = drawCurve(theGraph, theCurve, theGraph.curvesColors [i%12]);
	} // i
} // end of plot

// ******************End of Plot All Curves ***********

// **************** DRAWCURVE ******************************
// *** Draws a graph of a given funtion

function drawCurve(theGraph, theCurve, theCurveColor) {
okToRoll = true;
var thexMin = theCurve [1];
var thexMax = theCurve [2];
var theyString = theCurve [0];
var theLeftDecoration = theCurve[3];
var theRightDecoration = theCurve[4];
var mustShade = false; 
var theShadeTermination = "0";
try {mustShade = theCurve[5][0];
	theShadeCol = theCurve[5][1];
	var theShadeOpacity = theCurve[5][2];
	var theShadeBorderCol = theCurve[5][4];
	var theShadeBorderOpacity = theCurve[5][5];
	
//alert(theShadeBorderCol + "," + theShadeBorderOpacity);
	}

catch(error) {};
// for shading to a curve
try { theShadeTermination = theCurve[5][3];
	}
catch(error) {};
var theStrg = myParse(theyString);
var numDivisions = theGraph.pointsPerCurve; // for each curve
if (okToRoll) {
	
	// Search for a place to start the graph 
	// (in case initial y-coords are undefined or infinite.) 
	var deltax = (thexMax - thexMin)/numDivisions;
	var LRBwidth = theGraph.LRShadedBWidth*(theGraph.window[1]-theGraph.window[2])/theGraph.numX;
// alert(deltax);
// deltax = .25; // testing
	x1 = thexMin;
	x = x1;
	y1 = checkEval(theStrg);
// alert(y1);
// alert("x1 = " + x1 + "y1 = "+ y1 + (y1 > infinity));
	if ((isNaN(y1)) || (y1 == Infinity) || (y1 == -Infinity)) {
		for (var k = thexMin; k <= thexMax; k+=deltax) {
			x = k;
			x1 = k;
			y1 = checkEval(theStrg);
// if (k == thexMin) alert(theStrg + " " + y1);
			if(!isBad(y1)) break;
			} // end count
// alert(x1);
		if (k >= thexMax) {alert(undefinedMg); okToRoll = false}
		if (y1 > infinity) y1 = infinity;
		else if (y1 < -infinity) y1 = -infinity;
		} // y1 is not a number
			

	} // if OKtoRoll

if (okToRoll) {
		// now plot the points
	// at this point (x1, y1) is the acceptable starting point
	// first put in left-end-points
	var xStartC = x1, yStartC = y1 // remember these for later
	if (theLeftDecoration == "dot") putDot(theGraph, x1,y1, theCurveColor,false);
	else if (theLeftDecoration == "circle") putDot(theGraph, x1,y1, theCurveColor,true);
	else if (theLeftDecoration == "x") putX(theGraph, x1,y1, theCurveColor);

// alert(x);	
		var isFirstSegment = true, isLastSegment = false; // for arrowheads
		var thecount = 0; /// testing
		var startingX = x + deltax;
		for (var k = startingX; k <= thexMax; k+=deltax) {
			thecount++;
// if (thecount == 35) alert("thcount = " + thecount + "k = "+k);
// if (thecount == 41) alert("thcount = " + thecount + "k = "+k);
			x2 = k;
			x = x2;
			y2 = checkEval(theStrg);
			if (!isNaN(y2)) {
				if (y2 > infinity) y2 = infinity;
				else if (y2 < -infinity) y2 = -infinity; 
// alert(x1 + "," + y1 + "," + x2 + "," + y2);
				if(!mustShade){
					if (theGraph.adaptive) var p = adaptiveSegment(theGraph,x1,y1,x2,y2,theStrg, theCurveColor);
					else var p = segment(theGraph,x1,y1,x2,y2,theCurveColor);
					}
				else {
					if (!(theShadeBorderCol == undefined) && ((k <= startingX+ LRBwidth)||(k >= thexMax- LRBwidth))) var p = shadedSegment(theGraph,x1,y1,x2,y2,theCurveColor, theShadeBorderCol, theShadeBorderOpacity, theShadeTermination);
					else var p = shadedSegment(theGraph,x1,y1,x2,y2,theCurveColor,theShadeCol,theShadeOpacity, theShadeTermination);
					} // shading
				// now put in arrows if necessary
				if ((theLeftDecoration == "arrow") && (isFirstSegment) ) putArrow(theGraph, theCurve, x1, y1, x2, y2, theCurveColor, "left");
			isFirstSegment = false;
			if (k + deltax > thexMax) isLastSegment = true;
			if ((theRightDecoration == "arrow") && (isLastSegment) ) putArrow(theGraph, theCurve, x1, y1, x2, y2, theCurveColor, "right");
				x1 = x2;
				y1 = y2;
				} // If second point is defined
			else {
				for (var p = x2+deltax; p <= thexMax; p+=deltax) {
					x = p;
					y1 = checkEval(theStrg);
					if(!isNaN(y1)) break;
					} // end count
				k = p; // reset k further along the loop
				x1 = p;  // y1 is already what it is supposed to be
				} // second point was undefined
				
			
			if(thecount >= numDivisions + 2) k = thexMax+1;

			} // k
// put in right end-points
	if (theRightDecoration == "dot") putDot(theGraph, x1,y1, theCurveColor,false);
	else if (theRightDecoration == "circle") putDot(theGraph, x1,y1, theCurveColor,true);
	else if (theRightDecoration == "x") putX(theGraph, x1,y1, theCurveColor);
// redraw the left end-circles to prevent cures originating inside circles 
	if (theLeftDecoration == "circle") putDot(theGraph, xStartC, yStartC, theCurveColor,true);


// alert("k over");
	} // if okToRoll
} // function drawCurve


// ************** end of draw Curve *************

// ******************Draw Gridlines and Axes ***********
function drawDecorations(theGraph) {
var canvasWidth = theGraph.numX - theGraph.yLabelMargin - theGraph.rightMargin; 
var canvasHeight = theGraph.numY - theGraph.xLabelMargin - theGraph.topMargin;
// alert(canvasHeight);
with(theGraph) {
	if (surroundColor != "") {
		if (surroundColor == "white") ctx.clearRect(theGraph.yLabelMargin-5, theGraph.topMargin-2, canvasWidth, canvasHeight+5);
		else {
// alert("hello");
			ctx.fillStyle =  "rgb(" + surroundColor [0] + "," + surroundColor [1] + "," + surroundColor [2] + ")";
	ctx.fillRect(theGraph.yLabelMargin-5, theGraph.topMargin-2, canvasWidth+ edgeBuffer, canvasHeight+15+theGraph.nudgeDownxValues);
		}
	}
	if(backgroundColor == "white") ctx.clearRect(theGraph.yLabelMargin, theGraph.topMargin, canvasWidth, canvasHeight);
	else {
//alert("hello");
	ctx.fillStyle =  "rgb(" + backgroundColor [0] + "," + backgroundColor [1] + "," + backgroundColor [2] + ")";
	ctx.fillRect(theGraph.yLabelMargin, theGraph.topMargin, canvasWidth, canvasHeight);
		}
	}
if(theGraph.borderColor != "") {
	ctx.strokeStyle = lineStyler(theGraph.borderColor);
	ctx.strokeRect (theGraph.yLabelMargin, theGraph.topMargin, canvasWidth-1, canvasHeight-1);
	}
ctx.strokeStyle = lineStyler(theGraph.gridColor);
// ***HERE
// First determine the vertical position of the x-tick marks
// and horiz position of y-tick marks
var xMarksPosition = canvasHeight-15;
if (!theGraph.xLabelsBelow) {
	if (xAxisPosition != -1) xMarksPosition = xAxisPosition + 6;
	if (xMarksPosition > canvasHeight - 5) xMarksPosition -= 23;
	xMarksPosition += theGraph.nudgeDownxValues;
	}
else xMarksPosition = theGraph.topMargin + canvasHeight + 2 + theGraph.nudgeDownxValues;

// alert(xMarksPosition);
var yMarksPosition = 5;
var a = theGraph.window[0];
var b = theGraph.window[1];
var c = theGraph.window[2];
var d = theGraph.window[3];
if ((theGraph.xTicsRange).length <= 1) theGraph.xTicsRange  = theGraph.window.slice(0,2);
if ((theGraph.yTicsRange).length <= 1) theGraph.yTicsRange = theGraph.window.slice(2,4);


// First the gridlines themselves
if (theGraph.gridType[0] == "line") {
if ((theGraph.xGridStep > 0)&&(theGraph.xGrid == "on")) {
	for (var i = 1; i <= xGridLines[0]; i++) 
		{
// alert("i = " + i + " : " + xGridLines[i] + "of " + xGridLines[0]);
		var p = xGridLines[i];
		if ( (p > 0) && (p <= canvasWidth + theGraph.yLabelMargin) ) {
			ctx.beginPath();
			ctx.moveTo(xGridLines[i], theGraph.topMargin + canvasHeight);
			ctx.lineTo(xGridLines[i], theGraph.topMargin);
			ctx.stroke();
			ctx.closePath();
			} // if p is positive
		} // i
	} // if xGridStep
// alert("here");
if ((theGraph.yGridStep > 0) &&( theGraph.yGrid == "on")) {
// alert(yGridLines);
	for (var i = 1; i <= yGridLines[0]; i++) 
		{
		var p = yGridLines[i];
		if ((p > 0) && (p <= canvasHeight + theGraph.topMargin)) {
			ctx.beginPath();
			ctx.moveTo(theGraph.yLabelMargin, yGridLines[i]);
			ctx.lineTo(canvasWidth + theGraph.yLabelMargin, yGridLines[i]);
			ctx.stroke();
			ctx.closePath();
			} // IF P IS POSITIVE
		} // i
	} // if yGridStep
} // if we are drawing lines rather than tics

else if (theGraph.gridType[0] == "tic") { 
var theTicLength = theGraph.gridType[1];
if (xAxisPosition != -1) {
	
	if ((theGraph.xGridStep > 0)&&(theGraph.xGrid == "on")) {
		for (var i = 1; i <= xGridLines[0]; i++) 
			{
// alert("i = " + i + " : " + xGridLines[i] + "of " + xGridLines[0]);
			var p = xGridLines[i];
			if ( (p > 0) && (p <= canvasWidth + theGraph.yLabelMargin) ) {
				ctx.beginPath();
				ctx.moveTo(xGridLines[i], xAxisPosition - theTicLength/2);
				ctx.lineTo(xGridLines[i], xAxisPosition + theTicLength/2);
				ctx.stroke();
				ctx.closePath();
				} // if p is positive
			} // i
		} // if xGridStep

	} // if there is an x axis

if (yAxisPosition != -1) {
	if ((theGraph.yGridStep > 0) &&( theGraph.yGrid == "on")) {
	for (var i = 1; i <= yGridLines[0]; i++) 
		{
		var p = yGridLines[i];
		if ((p > 0) && (p <= canvasHeight + theGraph.topMargin)) {
			ctx.beginPath();
			ctx.moveTo(yAxisPosition - theTicLength/2, yGridLines[i]);
			ctx.lineTo(yAxisPosition + theTicLength/2, yGridLines[i]);
			ctx.stroke();
			ctx.closePath();
			} // IF P IS POSITIVE
		} // i
	} // if yGridStep

	} // if y axis
} // tics 
// alert("here");




// *** First the x-tick marks or x-axis labels ***
if (theGraph.xLabels.length > 0) {
	// first order the array xGridLines and eliminate repetitions etc
	// must add a and b as gridlines also in case they are not there
	// later repititions are eliminated anyway
// ***TESTING
// var st = '';
// for (var k = 0; k <= xGridLines[0]; k++) st += xGridLines[k].toString() + ", ";
// alert(st);
//***END TESTING
	xGridLines[0] += 2;
	xGridLines[xGridLines[0]-1] = theGraph.yLabelMargin;
	xGridLines[xGridLines[0]] = theGraph.yLabelMargin+ theGraph.canvasWidth;
	var xGTemp = xGridLines.slice(1, xGridLines[0]);
// alert(xGTemp);
	xGTemp.sort(function(a,b){return a - b}); // ascending
// alert(xGTemp);
	// eliminate repitions
	var xGTempN = [xGTemp[0]];
	var theIndex = 1;
	for (var i = 1; i <= xGTemp.length-1; i++) {
		if (xGTemp[i] > xGTemp[i-1]) {
			xGTempN[theIndex] = xGTemp[i];
			theIndex += 1;
			} // different
		} // i
// alert(xGTempN);
	xGridLines[0] = xGTempN.length;
	for (var i = 1; i <= xGridLines[0]; i++) {
		xGridLines[i] = xGTempN[i-1];
		} // i
// alert(xGridLines);
// ***TESTING
//  var st = '';
// for (var k = 0; k <= xGridLines[0]; k++) st += xGridLines[k].toString() + ", ";
// alert(st);
//***END TESTING
// alert(theGraph.xLabelPosition);
	jsPuttingXLabels = true;
	var halfGLine = (xGridLines[2] - xGridLines[1])/2;
	for (var i = 1; i <= xGridLines[0]; i++) {
		if (theGraph.xLabelPosition == "onLines") putText(theGraph, theGraph.xLabels[i-1], xGridLines[i], xMarksPosition, 0, -10, true);
		else {
	if (theGraph.xLabelPosition == "midLines") putText(theGraph, theGraph.xLabels[i-1], xGridLines[i] + halfGLine, xMarksPosition, 0, -10, true);
	}

		} // i
	jsPuttingXLabels = false;
// alert(xGridLines);
// place the labels at (xMarksPosition, centering automatic);


	} // putting labels


else if (theGraph.showScalex) {
// alert(xGridLinesActual);


// ***TESTING
var st = '';
//for (var k = 0; k <= xGridLines[0]; k++) st += xGridLines[k].toString() + ", ";
//alert(st);
//***END TESTING
	// set up array of ones to skip
	var skipArrayx = new Array();
	// first figure out how many significant digits to display
	sigDigNumx = 2;// minimum
	var mustIncrease = true;
	for (var i = sigDigNumx; i <= 12; i++) { 
		sigDigNumx = i;
	 if(!mustIncrease) break;
	 else {
		 for (var j = 1; j <= xGridLines[0]-1; j++) {
	 		if ((roundSigDig(xGridLinesActual[j],i) == roundSigDig(xGridLinesActual[j+1],i)) && (xGridLinesActual[j] != xGridLinesActual[j+1])) break;
		 		mustIncrease = false;
				} // j
			} // if mustIncrease
		} // i

	// need max length of the lables first -- roughly 6 pts/char
	var maxLength = 0;
// alert("a = " + a);
	for (var i = theGraph.startTicksx; i <= xGridLines[0]; i++) {

		if ( ( (i- theGraph.startTicksx) % theGraph.showEveryx == 0)&& (i >= theGraph.startTicksx) ) skipArrayx[i] = false; else skipArrayx[i] = true;
		var theStr = (roundSigDig(xGridLinesActual[i], sigDigNumx)).toString();
		var theLen = theStr.length*6;
		if(theLen > maxLength) maxLength = theLen;
		}// i
	// alert(theLen);
	var thexMultiple = 6666;
	if (theGraph.xGridStep > 0) thexMultiple  = Math.round((b-a)* maxLength*jsGSqueezeFactor/(canvasWidth * theGraph.xGridStep) + .5);
// alert(thexMultiple);
	if (thexMultiple < 6666) {
		// now actually put in the tick marks
			var theCharPosn = 0; 
			var deltaCharPos =0;
			var skipThis = false;
		for (var i = theGraph.startTicksx; i <= xGridLines[0]; i+= thexMultiple) {
			skipThis = false;
			if ((i > theGraph.Misc[0] ) && (i - thexMultiple < theGraph.Misc[0])) i = theGraph.Misc[0];
// if (theGraph.id == "Q16g") alert("i = " + i + " the actual coord of the gridline is " + xGridLinesActual[i] + " and the screen coord is " + xGridLines[i]);
			if (!theGraph.xLabelsBelow) {
				if ((xGridLines[i] <= theGraph.yLabelMargin) || (xGridLines[i] >= canvasWidth + theGraph.yLabelMargin + theGraph.edgeBuffer) || (xGridLinesActual [i] > theGraph.xTicsRange[1]) || (xGridLinesActual [i] < theGraph.xTicsRange[0])) skipThis = true;
				}
			else {
				if ((xGridLines[i] < theGraph.yLabelMargin) || (xGridLines[i] > canvasWidth + theGraph.yLabelMargin) || (xGridLinesActual [i] > theGraph.xTicsRange[1]) || (xGridLinesActual [i] < theGraph.xTicsRange[0])) skipThis = true;
				}
// if (theGraph.id == "Q16g") alert(theGraph.xTicsRange);
// if (theGraph.id == "Q16g") alert("i = " + i + " the actual coord of the gridline is " + xGridLinesActual[i] + " and the screen coord is " + xGridLines[i] + ", and skipThis = " + skipThis);
			if ((!skipThis) && (!skipArrayx[i])) {
// alert("i = " + i + " the actual coord of the gridline is " + xGridLinesActual[i] + " and the screen coord is " + xGridLines[i]);
				var theStr = (roundSigDig(xGridLinesActual[i], sigDigNumx)).toString();
				var theLen2 = theStr.length;
				var theDig = '';
				var theCmd = '';
				theCharPosn = Math.round(-theLen2*3); // centering
				var hitEdge = false;
				if(xGridLines[i] + theCharPosn < -theGraph.edgeBuffer) {
					// check if it can be shifted to fit in
					if (9*theLen2 >= maxLength) hitEdge = true;
					else theCharPosn = theGraph.edgeBuffer;
// alert(9*theLen2 + "' maxLength = " + maxLength);
					} 
				else if (xGridLines[i] + theCharPosn + theLen2*6 > canvasWidth +  theGraph.yLabelMargin + theGraph.edgeBuffer) {
					if (9*theLen2 >= maxLength) hitEdge = true;
					else theCharPosn = Math.round(-theLen2*7);
					} 
				if (!hitEdge) {
					deltaCharPos =0;
					for (var j = 0; j < theLen2; j++) {
						theCmd = 'ctx.drawImage(theDigit[';
						theDig = theStr.charAt(j);
						if (isNumberChar(theDig)) {theCmd += theDig; deltaCharPos = digitWidth[parseInt(theDig)]}
						else if (theDig == ".") {theCmd += "10"; deltaCharPos = 2}
						else if (theDig == "e") {theCmd += "14"; deltaCharPos = 7}
						else if (theDig == "-") {theCmd += "12"; deltaCharPos = 6}
						else if (theDig == "+") {theCmd += "11"; deltaCharPos = 9}
						else {theCmd += "13"; deltaCharPos = 8}; // should not happen
			
						theCmd += "]," + (xGridLines[i] + theCharPosn) + "," + xMarksPosition + ")";

						eval(theCmd);
						theCharPosn += deltaCharPos;
						} // j
					} // if not hitEdge
				} // if not skipThis
			} // i
		} // if thexMultiple < 6666
	} // if showScalex
// *** End of the x-tick marks ***
// alert("here");

// *** Now the origin if it is in bottom left corner
if((theGraph.showScalex)&& (theGraph.showScaley)) {
	if ((theGraph.window[0] == 0)&& (theGraph.window[2] == 0))
		{
		if ((theGraph.yLabelsLeft)&&( theGraph.xLabelsBelow)) ctx.drawImage(theDigit[0], yAxisPosition - 10, xMarksPosition);
		}



	}



// *** Now the y-tick marks ***
if (theGraph.showScaley){
// alert("here")
// alert(yGridLinesActual);
	var skipArrayy = new Array();
	// first figure out how many significant digits to display
	sigDigNumy = 2;// minimum
	var mustIncrease = true;
	for (var i = sigDigNumy; i <= 12; i++) { 
		sigDigNumy = i;
		if(!mustIncrease) break;
		else {
			for (var j = 1; j <= yGridLines[0]-1; j++) {
				if ((roundSigDig(yGridLinesActual[j],i) == roundSigDig(yGridLinesActual[j+1],i)) && (yGridLinesActual[j] != yGridLinesActual[j+1])) break;
				mustIncrease = false;
				} // j
			} // if mustIncrease
		} // i
	for (var i = 1; i <= yGridLines[0]; i++) {
		if ( ((i- theGraph.startTicksy) % theGraph.showEveryy == 0)&& (i >= theGraph.startTicksy) ) skipArrayy[i] = false; else skipArrayy[i] = true;
		} // i
		if (theGraph.switchShownLablesY) {
//alert(yGridLines);
			for (var i = 1; i <= yGridLines[0]; i++) {
				if(skipArrayy[i] ) skipArrayy[i] = false;
				else skipArrayy[i] = true}
			}
// if the origin is bottom left, it should not also appear in the y-coordinates:
// (in this case it appears last in the gridLines array....)
if((theGraph.showScalex)&&(theGraph.showScaley)) {
	if ((theGraph.window[0] == 0)&& (theGraph.window[2] == 0))
		{
		if (yGridLinesActual[yGridLines[0]] == 0) skipArrayy[yGridLines[0]] = true;
		}
	}




	var theHeight = 10; // rough height of a digit
	var theyMultiple = 6666;
	if (yGridStep > 0) theyMultiple  = Math.round((d-c)*theHeight*1.5/(canvasWidth *yGridStep*(theGraph.showEveryy)) + .5);
// alert(theGraph.showEveryy  + "'" + theyMultiple);

	if (theyMultiple < 6666) {
		// now actually put in the tick marks
		// first estimate longest label
		var maxLen = 0;
		for (var i = theyMultiple; i <= yGridLines[0]; i++) {
			var theStr = (roundSigDig(yGridLinesActual[i], sigDigNumy)).toString();
			var theLen = theStr.length;
			if (theLen > maxLen) maxLen = theLen;
			} // i
			var theCharPosn = 0; 
			var deltaCharPos =0;
			var skipThis = false;
		for (var i = theGraph.startTicksy; i <= yGridLines[0]; i+= theyMultiple) {
			skipThis = false;
// alert("i = " + i + " the actual coord of the gridline is " + yGridLinesActual[i] + " and the screen coord is " + yGridLines[i]);
			if ((yGridLines[i] <= theHeight/2) || (yGridLines[i] >= canvasHeight + theGraph.topMargin+theHeight/2) || (yGridLinesActual [i] > theGraph.yTicsRange[1]) || (yGridLinesActual [i] < theGraph.yTicsRange[0])) skipThis = true;
// Following is a fix for the fact that the y gridlines are not ordered if the x-axis is there
if ((i > 1) && (c*d < 0) && (yGridLinesActual[i]* yGridLinesActual[i-1] < 0)){
		i+= theyMultiple-1;
		}
		if ((!skipThis) && (!skipArrayy[(1+i- theyMultiple)/theyMultiple]))  {


			var theStr = (roundSigDig(yGridLinesActual[i], sigDigNumy)).toString();
			var theLen = theStr.length;
			var theDig = '';
			var theCmd = '';
			theCharPosn = 5; // no clue where this came from but watch out for it
			if (yAxisPosition != -1) {
				if (yAxisPosition < theGraph.yLabelMargin + canvasWidth/2) yMarksPosition = yAxisPosition +2 - theCharPosn;
				else yMarksPosition = yAxisPosition - 4- theLen*6.5 - theCharPosn;
				}
			// compute the length of the string on the canvas
			var wordLen = 0;
			for (var j = 0; j < theLen; j++) {
				theDig = theStr.charAt(j);
				if (isNumberChar(theDig))wordLen += digitWidth[parseInt(theDig)];
				else if (theDig == ".") { wordLen += 2}
				else if (theDig == "e") { wordLen += 8}
				else if (theDig == "-") { wordLen += 6}
				else if (theDig == "+") { wordLen += 9}
				} // j
			if (theGraph.yLabelsLeft) yMarksPosition = theGraph.yLabelMargin- wordLen - 3 - theCharPosn;
			deltaCharPos =0;
			for (var j = 0; j < theLen; j++) {
				theCmd = 'ctx.drawImage(theDigit[';
				theDig = theStr.charAt(j);
				if (isNumberChar(theDig)) {theCmd += theDig; deltaCharPos = digitWidth[parseInt(theDig)]}
				else if (theDig == ".") {theCmd += "10"; deltaCharPos = 2}
				else if (theDig == "e") {theCmd += "14"; deltaCharPos = 8}
				else if (theDig == "-") {theCmd += "12"; deltaCharPos = 6}
				else if (theDig == "+") {theCmd += "11"; deltaCharPos = 9}
				else {theCmd += "13"; deltaCharPos = 8}; // should not happen
			
				theCmd += "]," + (yMarksPosition + theCharPosn) + "," + (yGridLines[i] -5) + ")";

				eval(theCmd);
				theCharPosn += deltaCharPos;
				} // j
			} // if not skipThis
		} // i
	} // if theyMultiple < 6666

// *** End of the y-tick marks ***
} // if showScaley



// Next the axes
// alert(yAxisPosition);
var xAxisPlease = false, yAxisPlease = false;

if ((yAxisPosition != -1)&&(theGraph.yAxis == "on")) yAxisPlease = true;
if ((xAxisPosition != -1) &&(theGraph.xAxis == "on")) xAxisPlease = true;

drawAxes(theGraph, xAxisPlease, yAxisPlease)



} // drawDecorations


function drawAxes(theGraph, xAxisPlease, yAxisPlease) {
// x, y = 0 for no axis, 1 for yes

// alert(yAxisPosition);

theCanvas = document.getElementById(theGraph.canvaslabel);
ctx = theCanvas.getContext("2d");
var canvasHeight = theGraph.numY - theGraph.xLabelMargin - theGraph.topMargin;
var canvasWidth = theGraph.numX - theGraph.yLabelMargin - theGraph.rightMargin;
if (yAxisPlease) {
	ctx.strokeStyle = "rgb(0,0,0)";
	ctx.beginPath();
	ctx.moveTo(yAxisPosition, canvasHeight + theGraph.topMargin);
	ctx.lineTo(yAxisPosition, theGraph.topMargin);
	ctx.lineTo(yAxisPosition+3, theGraph.arrowLength +theGraph.topMargin);
	ctx.moveTo(yAxisPosition, theGraph.topMargin);
	ctx.lineTo(yAxisPosition-3, theGraph.arrowLength +theGraph.topMargin);
	ctx.stroke();
	ctx.closePath();
	} // y axis

if (xAxisPlease) {
	ctx.strokeStyle = "rgb(0,0,0)";
	ctx.beginPath();
	ctx.moveTo(theGraph.yLabelMargin, xAxisPosition);
	ctx.lineTo(canvasWidth+ theGraph.yLabelMargin, xAxisPosition);
	ctx.lineTo(canvasWidth+ theGraph.yLabelMargin - theGraph.arrowLength, xAxisPosition+3);
	ctx.moveTo(canvasWidth+ theGraph.yLabelMargin, xAxisPosition);
	ctx.lineTo(canvasWidth+ theGraph.yLabelMargin - theGraph.arrowLength, xAxisPosition-3);
	ctx.stroke();
	ctx.closePath();
	} // x axis

} // drawAxes


// ******************Enf of Draw Gridlines and Axes ***********

// *************** Utilities follow ***************************

function screenCoords(theGraph, x1, y1) {
// returns the screen coordinates as an array [0] = x [1] = y
var a = theGraph.window[0];
var b = theGraph.window[1];
var c = theGraph.window[2];
var d = theGraph.window[3]; 
var theWidth = theGraph.numX - theGraph.yLabelMargin - theGraph.rightMargin; 
var theHeight = theGraph.numY - theGraph.xLabelMargin - theGraph.topMargin;
var x1bar = theGraph.yLabelMargin + Math.round(theWidth*(x1-a)/(b-a) );
var y1bar = Math.round(theGraph.topMargin  + theHeight*(d-y1)/(d-c) );
return([x1bar, y1bar]);
}


function sesame(url,hsize,vsize){ 
// Default size is 550 x 400
        var tb="toolbar=0,directories=0,status=0,menubar=0"
        tb+=",scrollbars=1,resizable=1,"
    var tbend="width="+hsize+",height="+vsize;
    if(tbend.indexOf("<undefined>")!=-1){tbend="width=550,height=400"}
        tb+=tbend
        Win_2 = window.open("","win2",tb);
        Win_2 = window.open(url,"win2",tb);
	Win_2.focus();
    }


function stripSpaces (InString)  {
	OutString=""; 
	if (InString == "") return (OutString);
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=" ")
			OutString=OutString+TempChar;
	}
	return (OutString);
}

function stripChar (InString,symbol)  {
	OutString="";
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=symbol)
			OutString=OutString+TempChar;
	}
	return (OutString);
}

function replaceChar (InString,oldSymbol,newSymbol)  {
	OutString="";
	for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar!=oldSymbol)
			OutString=OutString+TempChar
		else OutString=OutString+newSymbol;
	}
	return (OutString);
}


function makeArray2 (X,Y)
	{
	var count;
	this.length = X+1;
	for (var count = 1; count <= X+1; count++)
		// to allow starting at 1
		this[count] = new makeArray(Y);
	} // makeArray2

function makeArray (Y)
	{
	var count;
	this.length = Y+1;
	for (var count = 1; count <= Y+1; count++)
		this[count] = 0;
	} // makeArray


function rightString (InString, num)  {
	OutString=InString.substring (InString.length-num, InString.length);
	return (OutString);
}

function rightTrim (InString)  {
	var length = InString.length;
	OutString=InString.substring(0,length-1);
	return (OutString);
}



function checkString(InString,subString,backtrack)
// check for subString
// if backtrack = false, returns -1 if not found, and left-most location in string if found
// if backtrack = true, returns -1 if not found, and right-most location in string if found
// note that location is to the left of the substring in both cases
{
var found = -1;
var theString = InString;
var Length = theString.length;
var symbLength = subString.length;
for (var i = Length- symbLength; i >-1; i--)
	{	
	TempChar=theString.substring (i, i+ symbLength);
	if (TempChar == subString) 
			{
			found = i;
			if (backtrack) i = -1
			}
	} // i
return(found);
} // check

function replaceSubstring (InString,oldSubstring,newSubstring)  {
	OutString="";
	var sublength = oldSubstring.length;
	for (Count=0; Count < InString.length; Count++)  {
		TempStr=InString.substring (Count, Count+sublength);
		TempChar=InString.substring (Count, Count+1);
		if (TempStr!= oldSubstring)
			OutString=OutString+TempChar
		else 
			{
			OutString=OutString+ newSubstring;
			Count +=sublength-1
			}

	}
	return (OutString);
} 

function parser (InString, Sep)  {
	NumSeps=1;
	for (Count=1; Count < InString.length; Count++)  {
		if (InString.charAt(Count)==Sep)
			NumSeps++;
	}
	parse = new makeArray (NumSeps+4, "");		// my adjustments
	Start=0; Count=1; ParseMark=0;
	LoopCtrl=1;
	while (LoopCtrl==1)  {
		ParseMark = InString.indexOf(Sep, ParseMark);
		TestMark=ParseMark+0;
		if ((TestMark==0) || (TestMark==-1)){
			parse[Count]= InString.substring (Start, InString.length);
			LoopCtrl=0;
			break;
		}
		parse[Count] = InString.substring (Start, ParseMark);
		Start=ParseMark+1;
		ParseMark=Start;
		Count++;
	}
	parse[0]=Count;
	return (parse);
}

function winopen(){
// opens a window in the bottom frame
	var str = winopen.arguments[0];
	var loc = winopen.arguments[1];
	if (navigator.appName == "Microsoft Internet Explorer") this.parent.bottom.window.location = str;
	else window.open(str,loc);
} // winopen

function looksLikeANumber(theString) {
// returns true if theString looks like it can be evaluated
var result = true;
var length = theString.length;
var x = ""
var y = "1234567890-+/*. "
var yLength = y.length;
for (var i = 0; i <= length; i++)
	{ 
	x = theString.charAt(i);
		result = false;
		for (var j = 0; j <= yLength; j++) 
			{
			if (x == y.charAt(j)) {result = true; break}
			} // j
	if (result == false) return(false);
	} // i
return(result);
} // looks like a number

function shiftRight(theNumber, k) {
	if (k == 0) return (theNumber)
	else
		{
		var k2 = 1;
		var num = k;
		if (num < 0) num = -num;
		for (var i = 1; i <= num; i++)
			{
			k2 = k2*10
			}
		}
	if (k>0) 
		{return(k2*theNumber)}
	else 
		{return(theNumber/k2)}
	}


function roundSigDig(theNumber, numDigits) {
	with (Math)
		{
// alert(theNumber);
		if (isBad(theNumber)) return theNumber;
		else if (theNumber == 0) return(0);
		else if(abs(theNumber) < 0.000000000001) return(0);
// WARNING: ignores numbers less than 10^(-12)
		else
			{
			var k = floor(log(abs(theNumber))/log(10))-numDigits+1
			var k2 = shiftRight(round(shiftRight(abs(theNumber),-k)),k)
			if (theNumber > 0) return(k2);
			else return(-k2)
			} // end else
		}
} // roundSigDig



function roundSigDigUpDown(theNumber, numDigits, up) {
	with (Math)
		{
// alert(theNumber);
		if (isBad(theNumber)) return theNumber;
		else if (theNumber == 0) return(0);
		else if(abs(theNumber) < 0.000000000001) return(0);
// WARNING: ignores numbers less than 10^(-12)
		else
			{
			var k = floor(log(abs(theNumber))/log(10))-numDigits+1
			var k2 = 0;
			if (up) k2 = shiftRight(1+floor(shiftRight(abs(theNumber),-k)),k);
			else k2 = shiftRight(floor(shiftRight(abs(theNumber),-k)),k);
			if (theNumber > 0) return(k2);
			else return(-k2)
			} // end else
		}
} // roundSigDigUpDown

function roundDec(theNumber, numPlaces) {
with (Math)
	{
	var x =shiftRight(round(shiftRight(theNumber,numPlaces)),-numPlaces);
	return x;
	} // with math
} // roundDec


// ********* FUNCTION PARSER ***********
function myEval(theString)
{
return(eval(myParse(theString)))
}



function checkEval(theString) {
		try
		{
 		return(eval(theString));
		}
	catch (error)
		{
 		 return("not a number");
		}
}


// The above function requires all the following routines
function breakApart(InString) {
	// ******* Input: any string such as aaa*bbb or (aa*aa)*bbb
	// *** This Routine Retuns two pieces
	// *** bbb starts at the left-most operation *, /,  - or +
	// *** if it sees a paren, it stops after closure.

	theString = InString;
	var Length = theString.length;
	var outArray = new Array();
	outArray[1] = theString;
	outArray[2] = "";
	var parenCount = 0;
	var parenWatch = false;
	var looking = true;
	
	
	if (theString.substring (0,1) == "(")
		{
		parenCount++;
		parenWatch = true;
		looking = false;
		}
	// Look for operators
	
	for (Count=1; Count < Length; Count++)  
		{
		TempChar=theString.substring (Count, Count+1);
		if (TempChar == "(") 
			{parenCount ++;}
		else if (TempChar == ")") {parenCount = parenCount-1};

		if ((parenCount == 0) && (parenWatch == true))
			 {
			parenWatch = false;
			outArray[1] = theString.substring (0, Count+1);
			outArray[2] = theString.substring (Count+1, Length);
			}

		if (looking == true)
			{
		if ( ( (TempChar == "*") || (TempChar == "/") || (TempChar == "-")  ) || ( (TempChar == "+") || (TempChar == ')' )  )  )
				{ 
				
					{
					// alert(Count);
					looking = false;
					outArray[1] = theString.substring (0, Count);
					outArray[2] = theString.substring (Count, Length);
					// alert (outArray[1]);
					// alert(outArray[2]);
					} // end if hit one
				} 

			} // end if looking

		} // end of loop


	return (outArray);

} // end of breakApart

function isNumberChar (InString)  {
	if(InString.length!=1) 
		return (false);
	RefString="1234567890)";
	if (RefString.indexOf (InString, 0)==-1) 
		return (false);
	return (true);
}

function isCharHere (InString, RefString)  {
	if(InString.length!=1) 
		return (false);
	if (RefString.indexOf (InString, 0)==-1) 
		return (false);
	return (true);
}

function putProduct(InString) {
OutString="";
for (Count=0; Count < InString.length; Count++)  {
		TempChar=InString.substring (Count, Count+1);
		if (!isCharHere(TempChar,"xXeslcapdDuUhHtT(") || (Count == 0) )
			{OutString=OutString+TempChar}
		else 
			{
			if (isNumberChar(InString.substring(Count-1,Count)))
				{OutString=OutString+"*"+TempChar}
			else OutString=OutString+TempChar
			}
	}

	return (OutString);
}

function reverse (InString)  {
	OutString="";
	var Length = InString.length;
	for (Count=Length; Count > -1; Count--)  {
		TempChar=InString.substring (Count, Count+1);
		if (TempChar == "(") {TempChar = ")"}
		else if  (TempChar == ")") {TempChar = "("}
		OutString=OutString+TempChar;
		}
	return (OutString);
	}

function powFix2(InString) {
	// ****Replaces one "^" by "pow"

	theString = InString;
	var Length = theString.length;
	outString = theString; 
		// in case nothing happens
	
		// Look for wedge
	var looking = true;
	for (Count=0; Count < Length; Count++)  
		{
		if (looking)
			{
			TempChar=theString.substring (Count, Count+1);
			if (TempChar == "^")
				{
				looking = false;
				rightStr = theString.substring (Count+1,Length);
				leftStr = theString.substring (0,Count);
				// deal with right-hand string
				Aray = breakApart(rightStr);
				Arg2 = Aray[1];
				rightRest = Aray[2];
			
				backString = reverse(leftStr);
				Aray = breakApart(backString);
				Arg1 = reverse(Aray[1]);
				leftRest = reverse(Aray[2]);
				// now deal with some fractional exponents
				var slashIndex = Arg2.indexOf("/")
				if (slashIndex >1) {
					var theNumerator = Arg2.substring(1, slashIndex);
					var theDenominator = Arg2.substring(slashIndex+1,Arg2.length-1);
					if ((parseInt(theNumerator)%2 != 0)&& (parseInt(theDenominator)%2 != 0)) {
						outString = leftRest+"(Math.pow(Math.pow(Math.abs("+Arg1+"),"+ theNumerator +"),1/" + theDenominator + ")*Math.abs("+Arg1+")/"+Arg1+")" +rightRest;
						} // odd radical
					else {
					outString = leftRest+"Math.pow(Math.pow("+Arg1+","+ theNumerator +"),1/" + theDenominator + ")" +rightRest;

//alert(outString);
						}
					}

			
				else outString = leftRest+"Math.pow("+Arg1+","+Arg2+")"+rightRest;
				
				} // end hif hit a wedge

			} // end of looking for a wedge
		} // end of loop

// ****** testing *******
// document.Extra.diagnostics.value += outString + cr;
// ***** end testing *****

return (outString);

} // end of powFix2

function powCheck(InString) {
	// ****checks for ^
	
	theString = InString;
	var Length = theString.length;
	
	// Look for wedge
	var found = false;
	for (Count=0; Count < Length; Count++)  
		{
		TempChar=theString.substring (Count, Count+1);
		if (TempChar == "^")
			{
			found = true;
			} // end if hit a wedge
		} // end of looking for a wedge
	return(found);


} // end of powCheck

function isBad(theNum) {
if ((isNaN(theNum)) || (theNum == Infinity) || (theNum == -Infinity) || (theNum > infinity) || (theNum < -infinity)) return(true);
else return(false)
}

function myParse(expression)
{
// alert('here')
		var theString = stripSpaces(expression);		
		with (Math)
			{
		// now convert formatting from GC formatting		
		theString = putProduct(theString);
		theString = replaceSubstring(theString,"log","(1/Math.log(10))*Math.log");
		theString = replaceSubstring(theString,"ln","Math.log");
		theString = replaceSubstring(theString,"abs","Math.abs");
			while (powCheck(theString))
				{
				theString = powFix2(theString);
				// alert(theString);
				}
		theString = theString.toLowerCase();
		theString = replaceSubstring (theString,"math.","Math."); 
		// in case the user put in a math. something
// alert(theString);
			} // with Math
		theString = replaceSubstring (theString,"exp(","Math.exp(");
		theString = replaceSubstring (theString,")(",")*(");
		theString = replaceSubstring (theString,")ln",")*ln");
		theString = replaceSubstring (theString,"sqrt","Math.sqrt");
		theString = replaceSubstring (theString,"x(","x*(");
		theString = replaceSubstring (theString,")x",")*x");
		return(theString);
} // myParse
// ******** END FUNCTION PARSER **************

function toFrac(x, maxDenom) {
	if (isBad(x)) return x;
	var theFrac = new Array();
	theFrac[1] = 0;
	theFrac[2] = 0;
	var p1 = 1;
	var p2 = 0;
	var q1 = 0;	
	var q2 = 1;	
	var u =0;
	var t = 0;
	var flag = true;
	var negflag = false;
	var a = 0;
	var xIn = x; // variable for later
	var n = 0;
	var d = 0;
	var p = 0;
	var q = 0;

	if (x >10000000000) return(theFrac);
while (flag)
	{
	if (x<0) {x = -x; negflag = true; p1 = -p1}
	var intPart = Math.floor(x);
	var decimalPart = roundSigDig((x - intPart),15);

	x = decimalPart;
	a = intPart;
	
	t = a*p1 + p2;
	u = a*q1 + q2;
	if  ( (Math.abs(t) > 10000000000 ) || (u > maxDenom ) ) 
		{
			n = p1;
			d = q1;
			break;
		}

		p = t;
		q = u;
			
//		cout << "cf coeff: " << a << endl; // for debugging
//		cout << p << "/" << q << endl;	// for debugging
		
	if ( x == 0 )
		{
		n = p;
		d = q;
		break;
		}

		p2 = p1;
		p1 = p;
		q2 = q1;
		q1 = q;
		x = 1/x;
	
	} // while ( true );
	
	theFrac[1] = n;
	theFrac[2] = d;

	if (theFrac[2] == 1) return (theFrac[1].toString());
	else return (theFrac[1] + "/" + theFrac[2]);

} // toFrac




// ***********************************************
// **** Graphing Routine
// ***********************************************

// ********************************************************************
// *********** Initilaize the Canvas & Set up the Axes and Gridlines ****
// ********************************************************************

function makeGraph2(theGraph)
{
if (okToRoll) {
var a = theGraph.window[0];
var b = theGraph.window[1];
var c = theGraph.window[2];
var d = theGraph.window[3];

var canvasWidth = theGraph.numX;
var canvasHeight = theGraph.numY;
if ((theGraph.yLabelsLeft) && (theGraph.yLabelMargin < jsyLabelMinSpace) ) {theGraph.yLabelMargin = jsyLabelMinSpace }
canvasWidth-= theGraph.yLabelMargin + theGraph.rightMargin;
if ((theGraph.xLabelsBelow) && (theGraph.xLabelMargin < jsxLabelMinSpace) ) {theGraph.xLabelMargin = jsxLabelMinSpace } 
canvasHeight -= theGraph.xLabelMargin + theGraph.topMargin;

// initialize the canvas
// now set up the axes & gridlines
	xGridStep = theGraph.xGridStep;
	yGridStep = theGraph.yGridStep;
	
	// gridlines first
	// setup position of gridlines
if ((theGraph.xTics).length > 1) jsXGridAuto = false;
if ((theGraph.yTics).length > 1) jsYGridAuto = false;

if (!jsXGridAuto) {
xGridLinesActual = theGraph.xTics.slice(0);
xGridLines[0] = xGridLinesActual[0];
for (var i = 1; i <= xGridLinesActual[0]; i++) {
	xGridLines[i] = screenCoords(theGraph, xGridLinesActual[i], 0)[0];
	}
}

if (!jsYGridAuto) {
yGridLinesActual = theGraph.yTics.slice(0);
yGridLines[0] = yGridLinesActual[0];
for (var i = 1; i <= yGridLinesActual[0]; i++) {
	yGridLines[i] = screenCoords(theGraph, 0, yGridLinesActual[i])[1];
	}
//alert(yGridLines);
}

// auto x gridlines begin
if (jsXGridAuto) {
	theGraph.Misc[0] = 6660000000;
	if (xGridStep > 0) xGridLines[0] = 1+Math.round((b-a)/xGridStep );
	else xGridLines[0] = 0; // number of gridlines
// alert(xGridLines[0]);
	// initialize these arrays to zero 
	for (var i = 1; i <= xGridLines[0]; i++) { xGridLines[i] = 0; xGridLinesActual[i] = a}
	
	var pq = 0; // just a dummy variable
// alert(theGraph.yLabelMargin);
	if (a*b >= 0) {
		// no y-axis appears in the interior in this case
		for (var i = 1; i <= xGridLines[0]; i++)
			{
			xGridLines[i] = Math.round(theGraph.yLabelMargin + i*xGridStep*canvasWidth/(b-a)  );
			xGridLinesActual[i] = a + i*xGridStep;
// alert("i = " + i + " : " + xGridLines[i] + cr);
			} // x Grid Line Positions
// ***TESTING
//  var st = '';
// for (var k = 0; k <= xGridLines[0]; k++) st += xGridLines[k].toString() + ", ";
// alert(st);
//***END TESTING
		} // if a and b have the same sign
	else 
		{ // want the y-axis to be one of the gridlines

		for (var i = 1; i <= xGridLines[0]; i++)
			{	
			pq = Math.round(theGraph.yLabelMargin + (i*xGridStep - a)*canvasWidth/(b-a));

			if ((pq <= canvasWidth + theGraph.yLabelMargin) && (pq >= 0)) {
				xGridLines[i] = pq;
				xGridLinesActual[i] = i*xGridStep;
				}
			else break;
			} // loop i
		i -= 1; //overstepped i
		theGraph.Misc[0] = xGridLines[0] - i; // for labels below
		// this just records the index of the first negative gridline
		for (var j = 1; j <= xGridLines[0] - i; j++)
			{
			pq = Math.round(theGraph.yLabelMargin  + (-j*xGridStep - a)*canvasWidth/(b-a));
// alert("j = " + j + "pq = " + pq);
			if ((pq <= canvasWidth + theGraph.yLabelMargin) && (pq >= theGraph.yLabelMargin)) 
				{
				xGridLines[j+i] = pq;
				xGridLinesActual[j+i] = -j*xGridStep;
				}
			else {
				xGridLines[0] = j-1+i; // cut off excess repeats
				}
			} // loop j
			// add an extra gridline in 1st posn if labels are below
			if (theGraph.xLabelsBelow) { 
				// include y-axis as a gridline
				xGridLines[0] += 1; 
				for (var k = xGridLines[0]+1; k >=2; k-- ) {
					xGridLines[k] = xGridLines[k-1];
					xGridLinesActual[k] = xGridLinesActual[k-1];
					} // k
				xGridLines[1] = screenCoords(theGraph,0,0)[0];
				xGridLinesActual[1] = 0;
				} // labels below
		} // a and b have opposite sign

// *** end of auto x gridlines	
} // if jsXGridAuto

if (jsYGridAuto) {
	if (yGridStep > 0) yGridLines[0] = 1+Math.round(  (d-c)/yGridStep );
	else yGridLines[0] = 0;
	for (var i = 1; i <= yGridLines[0]; i++) { yGridLines[i] = 0; yGridLinesActual[i] = d}
	if (c*d >= 0) {
//alert(yGridLines);
		for (var i = 1; i <= yGridLines[0]; i++)
			{
			yGridLines[i] = Math.round(theGraph.topMargin + (i-1)*yGridStep*canvasHeight/(d-c)  );
			yGridLinesActual[i] = d - (i-1)*yGridStep;
// alert(yGridLinesActual[i] + ",   " + yGridLines[i]);
			} // y Grid Line Positions
		} // if c and d have the same sign
	else {
		for (var i = 1; i <= yGridLines[0]; i++)
			{
			pq = Math.round((i*yGridStep - c)*canvasHeight/(d-c));
			if ((pq <= canvasHeight) && (pq >= 0)) {
				yGridLines[i] = canvasHeight - pq;
				yGridLinesActual[i] = i*yGridStep;
				}
			else break;
			} // loop i
		for (var j = 1; j <= yGridLines[0] - i; j++)
			{
			pq = Math.round((-j*yGridStep - c)*canvasHeight/(d-c));
// alert("j = " + j + "pq = " + pq);
			if ((pq <= canvasHeight) && (pq >= 0)) {
				yGridLines[j+i] = canvasHeight - pq;
				yGridLinesActual[j+i] = -j*yGridStep;
				}
			else {
				yGridLines[j+i] = 0;
				yGridLinesActual[j+i] = d;
				}
			} // loop j
			if (theGraph.yLabelsLeft) { 
				// include x-axis as a gridline
				yGridLines[0] += 1; 
				for (var k = yGridLines[0]+1; k >=2; k-- ) {
					yGridLines[k] = yGridLines[k-1];
					yGridLinesActual[k] = yGridLinesActual[k-1];
					} // k
				yGridLines[1] = screenCoords(theGraph,0,0)[1];
				yGridLinesActual[1] = 0;
				} // labels below
		} // if c and d have opposite sign
} // if jsYGridAuto

	// now the axes
	if ((a <= theGraph.DeltaYAxisPosition) && (b >= theGraph.DeltaYAxisPosition)) yAxisPosition = theGraph.yLabelMargin + Math.round((theGraph.DeltaYAxisPosition -a)*canvasWidth/(b-a));
else yAxisPosition = -1;
	if ((c <= 0) && (d >= 0)) xAxisPosition = screenCoords(theGraph,0,0)[1];
else xAxisPosition = -1;

// alert("here");


	} // if okToRoll
} // maheGraph2



// ************** GRAPH in EXCANVAS *************








function lineStyler(col,isAlpha, percent) {
// http://users.rcn.com/giant.interport/COLOR/1ColorSpecifier.html
var colr, colg, colb;
if ((col == "white")||(col == "")) {colr = 255; colg = 255; colb = 255}
else if (col == "red")  {colr = 255; colg = 0; colb = 0}
else if (col == "green")  {colr = 0; colg = 255; colb = 0}
else if (col == "blue")  {colr = 0; colg = 0; colb = 255}
else if (col == "black")  {colr = 0; colg = 0; colb = 0} 
else if (col == "grey")  {colr = 100; colg = 100; colb = 100}
else if (col == "yellow")  {colr = 255; colg = 255; colb = 0}
else if (col == "magenta") {colr = 255; colg = 0; colb = 255}
else if (col == "teal")  {colr = 0; colg = 128; colb = 128}
else if (col == "maroon")  {colr = 128; colg = 0; colb = 0}

else if (col == "limegreen")  {colr = 50; colg = 205; colb = 50}
else if (col == "mediumseagreen")  {colr = 60; colg = 179; colb = 113}
else if (col == "springgreen")  {colr = 0; colg = 255; colb = 127} 
else if (col == "olive")  {colr = 128; colg = 128; colb = 0}
else if (col == "olivedrab")  {colr = 107; colg = 142; colb = 35}
else if (col == "aqua")  {colr = 53; colg = 243; colb = 195}

else if (col == "royalblue")  {colr = 65; colg = 105; colb = 255}
else if (col == "lightblue")  {colr = 176; colg = 216; colb = 230}
else if (col == "slateblue")  {colr = 106; colg = 90; colb = 205}
else if (col == "navyblue")  {colr = 0; colg = 0; colb = 128}
else if (col == "slateblue")  {colr = 106; colg = 90; colb = 205}
else if (col == "steelblue")  {colr = 70; colg = 130; colb = 180}

else if (col == "purple")  {colr = 128; colg = 0; colb = 128}
else if (col == "mediumpurple")  {colr = 147; colg = 112; colb = 214} // lighter
else if (col == "darkviolet")  {colr = 148; colg = 0; colb = 211}
else if (col == "blueviolet")  {colr = 138; colg = 43; colb = 228}

else if (col == "pink")  {colr = 255; colg = 192; colb = 203}
else if (col == "hotpink")  {colr = 255; colg = 105; colb = 180}
else if (col == "deeppink")  {colr = 255; colg = 20; colb = 147}


else if (col == "orange")  {colr = 255; colg = 165; colb = 0}
else if (col == "orangered")  {colr = 255; colg = 69; colb = 0}
else if (col == "coral")  {colr = 255; colg = 127; colb = 80}

else if (col == "tomato")  {colr = 255; colg = 99; colb = 71}
else if (col == "crimson")  {colr = 220; colg = 20; colb = 60}
else if (col == "firebrick")  {colr = 178; colg = 34; colb = 34}

else if (col == "silver")  {colr = 192; colg = 192; colb = 192}
else if (col == "gold")  {colr = 255; colg = 215; colb = 80}
else if (col == "brass")  {colr = 181; colg = 174; colb = 69} // nonstandard
else if (col == "copper")  {colr = 182; colg = 103; colb = 44} // nonstandard
else if (col == "goldenrod")  {colr = 218; colg = 165; colb = 32}


else {colr = 0; colg = 0; colb = 0} // black default
var style = "rgb(" + colr + "," + colg + "," + colb + ")";
if(isAlpha) style = "rgba(" + colr + "," + colg + "," + colb + "," + percent + ")";
return(style);
} // lineColor

// ************** SEGMENT *************
// creates a clipped segment in the window
// note that the segment is automatically clipped by excanvas 
// so there is no work for us here
function segment(theGraph, x1,y1,x2,y2, col) {
var screen1 = screenCoords(theGraph, x1, y1);
var screen2 = screenCoords(theGraph, x2, y2);

// do some clipping for Windoze
var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
var noGo = false;
if ((y1 > yMax) && (y2 > yMax)) noGo = true;
else if ((y1 < yMin) && (y2 < yMin)) noGo = true;
else if  ((x1 > xMax) && (x2 > xMax)) noGo = true;
else if  ((x1 < xMin) && (x2 < xMin)) noGo = true;
if (!noGo) {
	
	var x1bar = screen1[0];
	var x2bar = screen2[0];
	var y1bar = screen1[1];
	var y2bar = screen2[1];
	var style = [lineStyler(col)].join("");
	theCanvas = document.getElementById(theGraph.canvaslabel);
	ctx = theCanvas.getContext("2d");
	ctx.strokeStyle = style;

	ctx.beginPath();
	ctx.moveTo(x1bar, y1bar);
	ctx.lineTo(x2bar, y2bar);
	ctx.stroke();
	ctx.closePath();
	}// if not noGo

} // segment

function shadedSegment(theGraph,x1,y1,x2,y2,col,theShadeCol,theShadeOpacity, theShadeTermination) {
// if (x1 == 0) alert("here");
var screen1 = screenCoords(theGraph, x1, y1);
var screen2 = screenCoords(theGraph, x2, y2);
var c = theGraph.window[2];
var d = theGraph.window[3];

var baseY1 = 0;
if (0 < c) baseY1 = c;
else if (0 > d) baseY1 = d; 
var baseY2 = baseY1;
if (!(theShadeTermination == undefined) && !(theShadeTermination == "0")) {
// alert("HERE");
x = x1; baseY1 = myEval(theShadeTermination);
x = x2; baseY2 = myEval(theShadeTermination);
if (baseY1 < c) baseY1 = c; 
else if (baseY1 > d) baseY1 = d;
if (baseY2 < c) baseY2= c;
else if (baseY2 > d) baseY2 = d;
	}

var screen3 = screenCoords(theGraph, x1, baseY1);
var screen4 = screenCoords(theGraph, x1, baseY2);

// do some clipping for Windoze
var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
var noGo = false;
if ((y1 > yMax) && (y2 > yMax)) noGo = true;
else if ((y1 < yMin) && (y2 < yMin)) noGo = true;
else if  ((x1 > xMax) && (x2 > xMax)) noGo = true;
else if  ((x1 < xMin) && (x2 < xMin)) noGo = true;
if (!noGo) {
	
	var x1bar = screen1[0];
	var x2bar = screen2[0];
	var y1bar = screen1[1];
	var y2bar = screen2[1];
	var style = [lineStyler(col)].join("");
	theCanvas = document.getElementById(theGraph.canvaslabel);
	ctx = theCanvas.getContext("2d");
	ctx.strokeStyle = style;

	ctx.beginPath();
	ctx.moveTo(x1bar, y1bar);
	ctx.lineTo(x2bar, y2bar);
	ctx.stroke();
	ctx.closePath();




	var y1Base = screen3[1];
	var y2Base = screen4[1];

	ctx.fillStyle = lineStyler(theShadeCol, true, theShadeOpacity);
	ctx.beginPath();
	ctx.moveTo(x1bar, y1Base);
	ctx.lineTo(x1bar, y1bar);
	ctx.lineTo(x2bar, y2bar);
	ctx.lineTo(x2bar, y2Base);


	ctx.fill();
	
	}// if not noGo

} // shadedSegment

// ************** Adaptive segment *************
// recursive version for pathalogical functions
function adaptiveSegment(theGraph, x1,y1,x2,y2, theFunction, col) {
// theFunction needs to be parsed
// the slope of the segment last drawn is stored in theGraph.MiscVal[0];
// the one before that is in [1]
var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
var noGo = false;
if ((y1 > yMax) && (y2 > yMax)) noGo = true;
else if ((y1 < yMin) && (y2 < yMin)) noGo = true;
else if  ((x1 > xMax) && (x2 > xMax)) noGo = true;
else if  ((x1 < xMin) && (x2 < xMin)) noGo = true;
if (!noGo) {
	var theSlope = (y2-y1)/(x2-x1);
	var mustSubdivide = false;
	if ((Math.abs(theSlope-theGraph.MiscVal[0]) > .5) || (Math.abs(theSlope-	theGraph.MiscVal[1]) > .5)) mustSubdivide = true; // abrupt slope change 
	var screen1 = screenCoords(theGraph, x1, y1);
	var screen2 = screenCoords(theGraph, x2, y2);
	var x1bar = screen1[0];
	var x2bar = screen2[0];
	var y1bar = screen1[1];
	var y2bar = screen2[1];
	var midPointBad = false; var hitBottom = false;
	var epsilon = theGraph.minDetlaXFactor*(theGraph.window[1]- theGraph.window[0]);
	var x3 = .7*x1 + .3*x2;
	x = x3; 
	var y3 = checkEval(theFunction);
	var x4 = .3*x1 + .7*x2;
	x = x4;
	var y4 = checkEval(theFunction);
	var xm = (x1+x2)/2; 
	x = xm;
	var ym = checkEval(theFunction);
	
	if  (  isNaN(y3) || isNaN(y4) ) midPointBad = true;
	else {
		var screen3 = screenCoords(theGraph, x3, y3);
		var x3bar = screen3[0];
		var y3bar = screen3[1];
		var delta3 = Math.abs(.7*y1bar+.3*y2bar - y3bar);
		var screen4 = screenCoords(theGraph, x4, y4);
		var x4bar = screen4[0];
		var y4bar = screen4[1];
		var delta4 = Math.abs(.3*y1bar+.7*y2bar - y4bar);
	
		
		} 
	if ( ( (!mustSubdivide) && (delta3 < 1)&& (delta4 < 1)&& (Math.abs(y2bar - y1bar) < 3) && (!midPointBad) ) || ((x2bar-x1bar)*(x2bar-x1bar)  < epsilon*epsilon) ) {
	if ((x2bar-x1bar)*(x2bar-x1bar)  < epsilon*epsilon) hitBottom = true;
	screen1 = screenCoords(theGraph, x1, y1);
	screen2 = screenCoords(theGraph, x2, y2);
	x1bar = screen1[0];
	x2bar = screen2[0];
	y1bar = screen1[1];
	y2bar = screen2[1];
	var style = [lineStyler(col)].join("");
	theCanvas = document.getElementById(theGraph.canvaslabel);
	ctx = theCanvas.getContext("2d");
	ctx.strokeStyle = style;
	ctx.beginPath();
	ctx.moveTo(x1bar, y1bar);
	ctx.lineTo(x2bar, y2bar);
	ctx.stroke();
	ctx.closePath();
	theGraph.MiscVal[1] = theGraph.MiscVal[0];
	theGraph.MiscVal[0] = (y2-y1)/(x2-x1);
	}
else {

	if (numberTesting >= 1000) { 
		numberTesting = 0;
		segment(theGraph,x1,y1,x2,y2, col) 
		}
	else {
		numberTesting += 1;
	
// document.getElementById("traceCoords" + theGraph.id).innerHTML = numberTesting.toString() + ",  " + hitBottom;
		adaptiveSegment(theGraph, x1,y1,x3,y3, theFunction, col);
		adaptiveSegment(theGraph, x3,y3,x4,y4, theFunction, col);
		adaptiveSegment(theGraph, x4,y4,x2,y2, theFunction, col);
		} // if numberTesting not that big;
	}

} // if not noGo
} // adaptiveSegment




function rememberValues() {
if (document.theFormC.y1x1.value != "") {
	var theInstruction = ""; // a javascript instuction
	var doIt = 0; // a dummy variable
	for (var i = 1; i <= maxnumX; i++) {
		for (var j = 0; j <= maxnum; j++) {
		if (j > 0) theInstruction = " theValues[i][j] = document.theFormC.y"+j+"x"+i+".value";
		else theInstruction = " theValues[i][j] = document.theFormC.x"+i+".value";
		doIt = eval(theInstruction);
		} // j
	} // i
} // IF THERE IS SOMETHING IN THE FIRST CELL
} // remember values

function bringBackValues() {
	if (document.theFormC.y1x1.value == "") {
		var theInstruction = ""; // a javascript instuction
		var doIt = 0; // a dummy variable
		for (var i = 1; i <= maxnumX; i++) {
			for (var j = 0; j <= maxnum; j++) {
			if (j > 0) theInstruction = " document.theFormC.y"+j+"x"+i+".value = theValues[i][j]";
			else theInstruction = " document.theFormC.x"+i+".value = theValues[i][j]";
			doIt = eval(theInstruction);
			} // j
		} // i
	} // if there is nothing in the first cell
} // remember values


// ********* Added Routines and globals for Plotting Points
var plottingPoints = false;
var tab = unescape( "%09" );	
var cr = unescape( "%0D" );	
var lf = unescape( "%0A" );
var semicolon = unescape( '%3B' );
var comma = ",";
var textareaText = '';
var maxNumPoints = 100; 
var numPoints = 0;
var thePlottedPoints = new makeArray2(maxNumPoints,2);
var yMaxPoints = 0;
var yMinPoints = 0;
var xHeight = 4; // half-height of the plotted Xs
var dotRadius = 4; // radius of end-point dots

function rememberValuesPoints() {
if (stripSpaces(document.theFormP.thePoints.value) != '')
textareaText = document.theFormP.thePoints.value;
} // remember values

function restoreValuesPoints() {
if (stripSpaces(document.theFormP.thePoints.value) == '')
document.theFormP.thePoints.value = textareaText;
} // restore values

function readInTextarea() {
// reads in points and calculates maximum and minimum y coords 
var theString = document.theFormP.thePoints.value;
theString = stripSpaces(theString);
theString = replaceChar(theString,comma,tab);
theString = replaceChar(theString,semicolon,cr);
theString = replaceChar(theString,lf,cr);
// now get rid of strings of more than one tab and one cr's in a row
var doubletab = true; var doublecr = true;
while ( (doubletab) || (doublecr) )
	{
	if (checkString(theString,tab+tab,false) == -1) doubletab = false;
	else theString = replaceSubstring(theString,tab+tab,tab);
	if (checkString(theString,cr+cr,false) == -1) doublecr = false;
	else theString = replaceSubstring(theString,cr+cr,cr);
	} // while
theString = replaceSubstring(theString,tab+cr,cr); // get rid of tab + crs
var xyPoints = parser(theString,cr);
var thePoint = new Array;
numPoints = xyPoints[0];
if (theString.indexOf(tab) == -1) numPoints = 0; 
if (numPoints > 0) {
	plottingPoints = true;
	for (var i = 1; i <= numPoints; i++) {
		thePoint = parser(xyPoints[i], tab);
		thePlottedPoints[i][1] = parseFloat(thePoint[1]);
		thePlottedPoints[i][2] = parseFloat(thePoint[2]);
		} // i

	// now compute y max and min for these
	yMaxPoints = thePlottedPoints[1][2];
	yMinPoints = yMaxPoints;
	for (var i = 1; i <= numPoints; i++) {
		try
			{
 			if (thePlottedPoints[i][2] > yMaxPoints) yMaxPoints = thePlottedPoints[i][2];
			else if (thePlottedPoints[i][2] < yMinPoints) yMinPoints = thePlottedPoints[i][2];
			}
			catch (error)
			{ 
			}
		} // i
//	alert("numPOints = " + numPoints + "  ; yMax = " + yMaxPoints + "  ; yMin = "+ yMinPoints);
	// now scale up a bit so that not on the edge
	yMinPoints -= .1*Math.abs(yMinPoints);
	yMaxPoints += .1*Math.abs(yMaxPoints);

	} // if more than 0 points
else plottingPoints = false;
} // readInTextarea

function putX(theGraph, x1,y1, color) {
var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
// alert(xMin +" " + xMax +" " +  yMin+" " +  yMax);

var theWidth = theGraph.numX; 
var theHeight = theGraph.numY;
screen1 = screenCoords(theGraph, x1, y1);
var x1bar = screen1[0];
var y1bar = screen1[1];
// alert(x1bar);
// alert(theGraph.canvaslabel);
theCanvas = document.getElementById(theGraph.canvaslabel);
// alert(theCanvas);
ctx = theCanvas.getContext("2d");
if (!(color == undefined)) ctx.strokeStyle = lineStyler(color);

else ctx.strokeStyle = lineStyler(theGraph.pointsColor);
ctx.beginPath();

ctx.moveTo(x1bar-xHeight, y1bar-xHeight);
ctx.lineTo(x1bar+xHeight, y1bar+xHeight);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(x1bar+xHeight, y1bar-xHeight);
ctx.lineTo(x1bar-xHeight, y1bar+xHeight);
ctx.stroke();
ctx.closePath();

} // putX

function putDot(theGraph, x1,y1, color,isOpen) {
var xMin = theGraph.window[0];
var xMax = theGraph.window[1];
var yMin = theGraph.window[2];
var yMax = theGraph.window[3];
// alert(xMin +" " + xMax +" " +  yMin+" " +  yMax);

var theWidth = theGraph.numX; 
var theHeight = theGraph.numY;
screen1 = screenCoords(theGraph, x1, y1);
var x1bar = screen1[0];
var y1bar = screen1[1];
// alert(x1bar);
// alert(theGraph.canvaslabel);
theCanvas = document.getElementById(theGraph.canvaslabel);
// alert(theCanvas);
ctx = theCanvas.getContext("2d");

ctx.beginPath();
if(!isOpen) {
	ctx.fillStyle = lineStyler(color);
	ctx.arc(x1bar, y1bar, dotRadius, 0, 2*Math.PI,true); 
	ctx.fill();
	}
else {
	ctx.fillStyle = "white";
	ctx.arc(x1bar, y1bar, dotRadius, 0, 2*Math.PI,true); 
	ctx.fill();
	ctx.strokeStyle = lineStyler(color);
	ctx.arc(x1bar, y1bar, dotRadius*1.2, 0, 2*Math.PI,true);
	ctx.stroke();
	}

ctx.closePath();

} // putDot



function putArrow(theGraph, theCurve, x1, y1, x2, y2, color, direction)
{
// alert("HERE at PutArrow");
// first get the angle
// alert(Math.atan2(-1,-1) + ", " + (-3*3.14159/4).toString());
var thetaCurve = Math.atan2(y2-y1, x2-x1);
try{
if (direction == "left") thetaCurve += theCurve[6][0];
else thetaCurve += theCurve[6][1];
}
catch(error) {

}
// alert(thetaCurve);
var theta1 = thetaCurve + theGraph.arrowFlare;
var theta2 = thetaCurve - theGraph.arrowFlare;
var theLength = theGraph.arrowLength;
// alert(direction + ", " + theta1 + ", " + theta2 + ", " +  theLength*Math.sin(theta1));
var theStartPoint = screenCoords(theGraph, x1, y1); 
var theEndPoint1, theEndPoint2;
if (direction == "left") {
	theEndPoint1 = [Math.round(theStartPoint[0]+theLength*Math.cos(theta1)), Math.round(theStartPoint[1]-theLength*Math.sin(theta1))];
	theEndPoint2 = [Math.round(theStartPoint[0]+theLength*Math.cos(theta2)), Math.round(theStartPoint[1]-theLength*Math.sin(theta2))];
	} // if left
else {
	theStartPoint = screenCoords(theGraph, x2, y2);
	theEndPoint1 = [Math.round(theStartPoint[0]-theLength*Math.cos(theta1)), Math.round(theStartPoint[1]+theLength*Math.sin(theta1))];
	theEndPoint2 = [Math.round(theStartPoint[0]-theLength*Math.cos(theta2)), Math.round(theStartPoint[1]+theLength*Math.sin(theta2))];
	} // if right

theCanvas = document.getElementById(theGraph.canvaslabel);
ctx = theCanvas.getContext("2d");
ctx.strokeStyle = lineStyler(color);
ctx.beginPath();
ctx.moveTo(theEndPoint1[0], theEndPoint1[1]);
ctx.lineTo(theStartPoint[0], theStartPoint[1]);
ctx.lineTo(theEndPoint2[0], theEndPoint2[1]);
ctx.stroke();
ctx.closePath();
} // putArrow



//** detecting mouse coordinates on the canvas

function getElementPosition(elemID){
var offsetTrail = document.getElementById(elemID);
var offsetLeft = 0;
var offsetTop = 0;
while (offsetTrail){
offsetLeft += offsetTrail.offsetLeft;
offsetTop += offsetTrail.offsetTop;
offsetTrail = offsetTrail.offsetParent;
}
if (navigator.userAgent.indexOf('Mac') != -1 && typeof document.body.leftMargin != "undefined"){
offsetLeft += document.body.leftMargin;
offsetTop += document.body.topMargin;
}
return {left:offsetLeft,top:offsetTop};
}

function restorePointsText(theGraph) {
// alert("HERE");
document.getElementById("traceCoords" + theGraph.id).innerHTML = theGraph.pointsText;
} // restorePointsText

function capturePointsText(theGraph) {
theGraph.pointsText = document.getElementById("traceCoords" + theGraph.id).innerHTML; 
}

function alertCoord(e,theGraph) {
// note need for draggable bars this even if not showing cords 

	var canvPos = getElementPosition(theGraph.canvaslabel);
//alert(canvPos);
	  if( !e ) {
	    if( window.event ) {
	      //Internet Explorer
	      e = window.event;
	    } else {
	      //total failure, we have no way of referencing the event
	      return;
	    }
	  }
	  if( typeof( e.pageX ) == 'number' ) {
	    //most browsers
	    var xcoord = e.pageX;
	    var ycoord = e.pageY;
	  } else if( typeof( e.clientX ) == 'number' ) {
	    //Internet Explorer and older browsers
	    //other browsers provide this, but follow the pageX/Y branch
	    var xcoord = e.clientX-1;
	    var ycoord = e.clientY-1;
	    var badOldBrowser = ( window.navigator.userAgent.indexOf( 'Opera' ) + 1 ) ||
	     ( window.ScriptEngine && ScriptEngine().indexOf( 'InScript' ) + 1 ) ||
	     ( navigator.vendor == 'KDE' );
	    if( !badOldBrowser ) {
	      if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
	        //IE 4, 5 & 6 (in non-standards compliant mode)
	        xcoord += document.body.scrollLeft;
	        ycoord += document.body.scrollTop;
	      } else if( document.documentElement && ( document.documentElement.scrollLeft 	|| document.documentElement.scrollTop ) ) {
	        //IE 6 (in standards compliant mode)
	        xcoord += document.documentElement.scrollLeft;
	        ycoord += document.documentElement.scrollTop;
	      }
	    }
	  } else {
	    //total failure, we have no way of obtaining the mouse coordinates
	    return;
	  }
	xcoord -= canvPos.left;
	ycoord -= canvPos.top;
	// window.alert('Mouse coordinates are ('+xcoord+','+ycoord+')');
	// now get actual coordinates
	var a = theGraph.window[0];
	var b = theGraph.window[1];
	var c = theGraph.window[2];
	var d = theGraph.window[3]; 
	var theWidth = theGraph.numX  - theGraph.yLabelMargin - theGraph.rightMargin;
	var theHeight = theGraph.numY - theGraph.xLabelMargin - theGraph.topMargin; 


	// var theInsta = "var a = " + name + ".a";
	// var theInstb = "var b = " + name + ".b";
	// var theInstc = "var c = " + name + ".c";
	// var theInstd = "var d = " + name + ".d";
	// var theInstCW = "var theWidth = " + name + ".numX";
	// var theInstCH = "var theHeight = " + name + ".numY";
	// eval(theInsta); eval(theInstb); eval(theInstc); eval(theInstd);
	// eval(theInstCW); eval(theInstCH);
// alert(a + "," + b + ","  + c + ","  + d  + ","  + theWidth );
	var xAdjusted = xcoord- theGraph.yLabelMargin;
	theGraph.xScreen = xcoord;
	theGraph.yScreen = ycoord;
	

		theGraph.xActual = (a + xAdjusted*(b-a)/theWidth).toFixed(theGraph.decPlacesX);
		theGraph.yActual = ((theHeight-ycoord)*(d-c)/theHeight + c).toFixed(theGraph.decPlacesY);
		if ((xAdjusted >= 0) && (xAdjusted <= theWidth)  && (ycoord <= theHeight) ) {
		if(theGraph.showCoords == 1) document.getElementById("traceCoords" + theGraph.id).innerHTML =  '('+ theGraph.xActual +', '+ theGraph.yActual +')';
		}

} // alertCoord


function canDragBars(theGraph) {
theGraph.onclick = dealWithClick(theGraph);
theGraph.onmousedown = dealWithMouseDownBars(theGraph)

theGraph.onmouseup = document.getElementById("traceCoords" + theGraph.id).innerHTML = "up";

}

function dealWithClick(theGraph) {
theGraph.xClicked = theGraph.xScreen;
theGraph.yClicked = theGraph.yScreen;


}

function dealWithMouseDownBars(theGraph) {
// check if the click occurred near the top of a specific bar
// alertCoord(arguments[0], theGraph,2,2);
var theXcoord = theGraph.xScreen;
var theYcoord = theGraph.yScreen;
// document.getElementById("traceCoords" + theGraph.id).innerHTML = "clicked";
// alert(theXcoord + "," + theYcoord);
var SCL, SCR;
var numBarGraphs = theGraph.bars.length;
for (var n = 0; n<= numBarGraphs-1; n++) {
	var h1 = theGraph.bars[n][2];
	var h2 = theGraph.bars[n][3];
	var theXBarCoords = theGraph.bars[n][0];
	var theYBarCoords = theGraph.bars[n][1]; 
	var xLeft, xRight, y1, x2, yLower;
//alert(theXBarCoords);
	for (var i = 0; i <= theXBarCoords.length-1; i++) { 
//alert(i);
		xLeft = theXBarCoords[i] - h1; // left edge
		xRight = theXBarCoords[i] + h2;
		y1 = theYBarCoords[i];
// if ((n == 0) && (i == 0)) alert(xLeft + "," +  xRight);
		SCL = screenCoords(theGraph, xLeft, y1);
		SCR = screenCoords(theGraph, xRight, y1);
// document.getElementById("traceCoords" + theGraph.id).innerHTML = theXcoord - SCL[0];
		if ( (theXcoord >= SCL[0]) && (theXcoord <= SCR[0]) && (theYcoord >= SCL[1] - 5) && (theYcoord <= SCL[1] + 5) ) {
			// now modify onMove;
// alert(theXcoord + "," + SCL[0]);
			jsDraggingBar = true;
			jsDraggingBarGraphNumber = n;
			jsDraggingBarNumber = i;
// document.getElementById("traceCoords" + theGraph.id).innerHTML = "success";

}
		
		} // i
	} // n


} // dealWithMouseDownBars


function dealWithMouseMoveBars(theGraph) {
if(jsDraggingBar) {
var yMin = theGraph.window[2];
if (theGraph.yActual >= yMin) { 
	theGraph.bars[jsDraggingBarGraphNumber][1][jsDraggingBarNumber] = theGraph.yActual;
	setUpGraph(theGraph);
	putBars(theGraph);
	} // if y is in range
	
	} // if dragging a bar
} // dealWithMouseMoveBars


function dealWithMouseUpBars(theGraph) {
//alert(theGraph.bars[jsDraggingBarGraphNumber][1][jsDraggingBarNumber] = theGraph.yActual);
jsDraggingBar = false;
} // dealWithMouseMoveBars








