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



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


// Globals
// window.onerror = myErrorTrap;
var e = 2.718281828459045;
var pi = 3.141592653589793;

var canvas, ctx; // for excanvas
var canvasWidth = 400;
var canvasHeight = 400;

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();
var counter = 0; // for debugging
	theDigit[0] = new Image();
	theDigit[0].src = "digits/0.gif"
	theDigit[1] = new Image();
	theDigit[1].src = "digits/1.gif"; 
	theDigit[2] = new Image();
	theDigit[2].src = "digits/2.gif"; 
	theDigit[3] = new Image();
	theDigit[3].src = "digits/3.gif";
	theDigit[4] = new Image();
	theDigit[4].src = "digits/4.gif";
	theDigit[5] = new Image();
	theDigit[5].src = "digits/5.gif";
	theDigit[6] = new Image();
	theDigit[6].src = "digits/6.gif";
	theDigit[7] = new Image();
	theDigit[7].src = "digits/7.gif";
	theDigit[8] = new Image();
	theDigit[8].src = "digits/8.gif"; 
	theDigit[9] = new Image();
	theDigit[9].src = "digits/9.gif"; 
	theDigit[10] = new Image();
	theDigit[10].src = "digits/point.gif";
	theDigit[11] = new Image();
	theDigit[11].src = "digits/plus.gif";
	theDigit[12] = new Image();
	theDigit[12].src = "digits/minus.gif";
	theDigit[13] = new Image();
	theDigit[13].src = "digits/infinity.gif";
	theDigit[14] = new Image();
	theDigit[14].src = "digits/e.gif";
var digitWidth = [9, 6, 9, 8, 9, 8, 8, 9, 8, 8, 3, 9, 9, 12, 8];
var X = 0;
var x = 0;
var y = 0;
var x1 = 0;
var x2 = 0;
var y1 = 0;
var y2 = 0;
var h = 0;
var xh = 0;
var hx = 0;
var t = 0;
var th = 0;
var ht = 0;
var a = 0;
var b = 0; // end points of graph
var c = 0;
var d = 0;
var A = 0;
var p1 = 0;
var numDivisions = 400; // for each curve
var quoteMark = unescape( '%22' );
var singlequoteMark = unescape( '%27' );

var numX = canvasWidth; // picure width in pixels -- old canvas
var numY = canvasHeight;; // picture height
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 theCanvas = new makeArray2(numX,numY);

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 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 = 4;			// default accuracy
var sigDigMode = false;		// significant digits vs decimal places rounding
var haveProbabilities = false; // guesses if the user has already given probabilities
var showRelFreq = true, showEValue = false, showStDev = false, showHistogram = true;
var maxRelFreq = 0;
var maxDenom = 99999;
var theLabels = new Array(); // x-axis lables
var exampleNumber = 1;
var exampleStrings = ["", "1,2\n2,4\n3,5\n4,2\n5,3", "Apples,2\nOranges,4\nLimes,7\nPeaches,3","10,2\n20,5\n30,10\n40,70\n50,80\n60,100\n70,120\n80,150\n90,180\n100,200"]
var theAlerts = ["You must enter some data first.", "The X-values must be numbers to compute expected value and standard deviation."];
if (theLanguage == "es") {
	theAlerts = ["Debe ingresar primero algunos datos." , "Los valores de X deben ser n&uacute;meros para calcular el valor esperado y deviaci&oacute;n estand&aacute;r."];
	}

// *** end globals


// *** Error Handler ******
function myErrorTrap(message,url,linenumber) {
alert("It looks like you have entered something wrongly (or perhaps need to use an older version). Press 'Show Examples' to see examples of how to format functions.");
return (true);
} // end of on error

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

// **********Utilities to read in the functions *********
// ********************************************************************
function doTheHistogram (){
// canvas = document.getElementById("cv");
// ctx = canvas.getContext("2d");
okToRoll = true;

if  (!haveData) {alert(theAlerts[0]); okToRoll = false}
var theBarsX = new Array(), theBarsY = new Array();

for (var i = 0; i <= 2*numPoints+2; i++) theLabels[i] = "";
theLabels[0] = "";
var labelIndex = 1;
for (var i = 0; i <= numPoints-1; i++) {
	theBarsX[i] = i;
	if (sigDigMode)theBarsY[i] = roundSigDig(freqDist [i+1][2],numSigDigs);
	else theBarsY[i] = roundDec(freqDist [i+1][2],numSigDigs);
	theLabels[labelIndex] = freqDist[i+1][1];
	theLabels[labelIndex+1] = "";
	labelIndex += 2;
	} // i
theLabels[labelIndex] = "";

var theWindowY = 1, yStep = .2;
if (maxRelFreq <= .5) {theWindowY = .5; yStep = .1}
if (maxRelFreq <= .25) {theWindowY = .25; yStep = .05}
if (maxRelFreq <= .2) {theWindowY = .2; yStep = .04}
if (maxRelFreq <= .1) {theWindowY = .1; yStep = .02}
if (maxRelFreq <= .05) {theWindowY = .05; yStep = .01}

cleanUp(myGraph)

with(myGraph) {

	showCoords = 0;
	xAxis = "off";
	yAxis = "off"
	yGrid = "on";
	yAxis = "off";
	yGridStep = yStep;
	xGridStep = .5;
	xLabelsBelow = true;
	yLabelsLeft = true;
	
	xLabels = theLabels.slice();
	window = [0,i,0, theWindowY];
	// showScaley = false;
var theBarColor = pickOne("mediumseagreen","magenta","hotpink","gold","slateblue","steelblue","coral","teal","blueviolet","springgreen","olive","limegreen","goldenrod");
	bars = [ [theBarsX, theBarsY, 0,1, theBarColor, .5]]; 
	draggableBars = 1;
	barLabels = "y";
	} // with myGraph

setUpGraph(myGraph);
putBars(myGraph);

} // doTheHistogram




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

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)  {
// ************************
// returns an array 0th entry = number n of blocks (-1) if the character Sep does not occur
// subsequent n entries are the blocks themselves
// Here are the blocks
// ***block 1 *** SEP *** block 2 *** SEP *** block 3 ***
// (one more block than number of occurrences of SEP)
// ************************
	var NumSeps=0; var Count = 0;
	var location = new Array;
	location[0] = -1;
	var len = InString.length;
	for (Count=0; Count < len; Count++)  {
		if (InString.charAt(Count)==Sep)
			{
			NumSeps++;
			location[NumSeps] = Count;
			}
		}
	
	var parse = new makeArray (NumSeps+2);
	if (NumSeps == 0) {parse[0] = 1; parse[1] = InString; return(parse);}
	parse[0] = NumSeps + 1;  
	
	for (var i = 1; i <=NumSeps; i++)
		{
		parse[i] = InString.substring(location[i-1]+1, location[i]);
// alert("i = " + i + "  "  + parse[i]);
		}	
		parse[NumSeps+1] = InString.substring(location[NumSeps]+1, len);
// alert("i = " + i + "  "  + parse[i]);

	
	return (parse);
} // parser

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 PARSER ***********
function myEval(theString)
{
// if (isBad(theString)) return 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);
					outString = leftRest+"Math.pow(Math.pow("+Arg1+","+ theNumerator +"),1/" + theDenominator + ")" +rightRest;
					}

			
				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

//alert(powFix2("(x-1)^(2/3)"));

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,"cos","Math.cos");
		theString = replaceSubstring (theString,"sin","Math.sin");
		theString = replaceSubstring (theString,"tan","Math.tan");
		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
// ***********************************************
var basicsRead = false;
function readBasics() {
// alert("here");
var theStrg = "";  // dummy string for evaluating functions
autoY = document.theFormB.autoYButton.checked;
autoGridline = document.theFormB.autoGridButton.checked;
// get the graph window information
	for (var k = 1; k <= 1; k++)
	{
	var aa = document.theFormB.a.value; 
// alert(aa);
	if (aa == "") { alert("You have not entered a number for xMin."); okToRoll = false; break;}
//alert(aa);
	a = myEval(aa);
	if (isNaN(a) ) { alert("You have not entered a number for xMin."); okToRoll = false; break;}
	var bb = document.theFormB.b.value; 
	if (bb == "") { alert("You have not entered a number for xMax."); okToRoll = false; break}
	b = myEval(bb); 
	if (isNaN(b) ) { alert("You have not entered a number for xMax."); okToRoll = false; break;}
	if ( (okToRoll) && (a >= b)) { alert("xMax should be larger than xMin"); okToRoll = false; break;}
	if(!autoY) {
// alert("Here autoY is off");
		var cc = document.theFormB.c.value; 
		if (cc == "") { alert("You have not entered a number for yMin. (Otherwise click on 'Auto'.)"); okToRoll = false; break}
		c = myEval(cc); 
		if (isNaN(c) ) { alert("You have not entered a number for yMin. (Otherwise click on 'Auto'.)"); okToRoll = false; break;}
		var dd = document.theFormB.d.value; 
		if (dd == "") { alert("You have not entered a number for yMax. (Otherwise click on 'Auto'.)"); okToRoll = false; break}
		d = myEval(dd); 
		if (isNaN(d) ) { alert("You have not entered a number for yMax. (Otherwise click on 'Auto'.)"); okToRoll = false; break;}
		if ( (okToRoll) && (c >= d)) { alert("yMax should be larger than yMin"); okToRoll = false; break;}
// alert(a + " , " + b + " , " + c + " , " + d);
		} // end of if autoY
	else {
		document.theFormB.c.value = ''; // cannnot have it both ways
		document.theFormB.d.value = ''; 
		// Must compute ymin and ymax here
		var deltax = (b-a)/canvasWidth;
// alert(deltax);
		var maxy = 0; var miny = 0;
		var firstCheck = true;
		for (var j = 1; j <= arraysize; j++) {
			theStrg = myParse(yVals[j]);
			for (var i = 0; i <= canvasWidth; i++)
				{
				x = a + i*deltax;
// alert("x = " + x);
				y  = checkEval(theStrg);
				if (!isNaN(y) &&( y < infinity) && (y > -infinity))
					{
					// alert ("y = " + y);
					if (firstCheck)						{
						firstCheck = false; 
						maxy = y; 
						miny = y
						}
					if (y > maxy) maxy = y;
					else if (y < miny) miny = y;
					} // end of if y is a legit number
				} // i
			} // j

// alert("miny = "+miny + "maxy =" + maxy);
			// Now cut down the size of the window if necessary
			// in the case of graphs shooting off to infinity
			// the tequnique is to eliminate "outliers" by cropping 
			// the window.


// *** Why don't we just compute the st deviation and crop to +- 3s
			var cutting_down = true;
			var invisible_tally = 0;
			var maxSteps = 10, steppes = 0;
			while ((cutting_down) && (steppes < maxSteps))
				{
				steppes ++;
				maxy = maxy/2;
				miny = miny/2;
				invisible_tally = 0;
				for (var j = 1; j <= arraysize; j++) {
					theStrg = myParse(yVals[j]);

					for (var i = 0; i <= numX; i++)
						{
						x = a + i*deltax;
						y  = checkEval(theStrg);
						if (!isNaN(y) &&( y < infinity) && (y > -infinity))
							{
							if ((y > maxy) || (y < miny))
								{
								invisible_tally++;
								if (invisible_tally > windowcropTally)
									{
									cutting_down = false;
									maxy = 2*maxy;
									miny = 2*miny;
									i = numX;
									break;
									} // too many invisible;
								} // if y > ymax
						} // if is a number
					} // i
				} // j
			// by the end of this cutting_down had better be false;
			} // while cutting_down
// alert("miny = "+miny + "maxy =" + maxy);
		
		if (miny == maxy) {miny  = miny-1; maxy += 1}

		// ** following two lines added for plotting points
		if (haveData) {
			if (yMaxPoints > maxy) maxy = yMaxPoints;
			if (yMinPoints < miny) miny = yMinPoints;
			}
		var scalefactor = 150/(maxy - miny); 
// ************
// end of y min and ymax window coords
// ************

		c = eval(roundSigDigUpDown (miny,4, false)); // sets the globals
		d = eval(roundSigDigUpDown (maxy,4, true));
		} // end of else for autoY
// alert("c = " + c + " d = " + d);
// At this point we have the globals a, b, c, d we need
	if(!autoGridline) {
		var xk = document.theFormB.xg.value;
		if (xk == "") xGridStep = 0;
		else xGridStep = eval(xk);
		var yk = document.theFormB.yg.value;
		if (yk == "") yGridStep = 0;
		else yGridStep = eval(yk);
		} // end of if not autoGridline
	else {
		document.theFormB.xg.value = ''; 
		document.theFormB.yg.value = ''; // cannot have it both ways.
		var pq = (b-a)/10;
		xGridStep = pq;
		pq = pq = (d-c)/10;
		yGridStep = pq;
		} // end of else for autoGridline
	} // end of single loop (k)
if (okToRoll) basicsRead = true;
} // readBasics

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




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



function refreshGraph()  {
// nothing here
} // End of refreshGraph



// ************** DISPLAY GRAPH *************
// For old browsers
function displayTheGraph()  {
// puts up the graph in the appropriate window, and extra things as needed

	// *** Open a window
	var tb="toolbar=0,directories=0,status=0,menubar=0"
	tb+=",scrollbars=1,resizable=1,"
	var tbend="width=350,height=300"
	tb+=tbend
	Win_1 = window.open("","win1",tb);
	Win_1.document.open();
	// ** Window is opened

var theString  = '<html><title>Your Graph</title><body bgcolor = "FFFFFF"><p><center><table><tr>'
theString += '<td></td><td align = center>' + roundSigDig(d,4) + '</td></tr><tr><td valign = center>'+ roundSigDig(a,4)+'</td><td>';

theString += '<table border = 1><tr><td>';
theString += formatGraph2();			// the actual graph
theString += '</td></tr></table>';
theString += '</td><td valign = center>' + roundSigDig(b,4) + '</td></tr><tr><td><td align = center>' + roundSigDig(c,4) + '</td></tr></table></center></body></html>';
Win_1.document.write(theString);
Win_1.document.close();
Win_1.focus();

} // displayTheGraph
// ********** END DISPLAY GRAPH *************






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 alertCoord(e) {
if(basicsRead) {
	var canvPos = getElementPosition('cv');
	var xActual = "", yActual = "";
	  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;
	    var ycoord = e.clientY;
	    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
	xActual = roundSigDig(a + xcoord*(b-a)/canvasWidth,4);
	yActual = roundSigDig((canvasWidth-ycoord)*(d-c)/canvasWidth + c, 4);
	document.getElementById("traceCoords").innerHTML =  '('+ xActual +', '+ yActual +')'
	} // if basicsRead
} // alertCoord


// ********* Handling input and relative frequency output
function checkSettings() {
fractionMode = document.theFormB.fracModeButton.checked;
showRelFreq = document.theFormB.showRelFreqButton.checked; 
showEValue = document.theFormB.showEValueButton.checked;
showHistogram = document.theFormB.showHistogramButton.checked;

var accuracydig = document.theFormB.acc.value;
if ( (accuracydig == "") || (!looksLikeANumber(accuracydig)) ) { 
	var theMessage = "Enter a value for the accuracy (Rounding) in the range 1-13.";
	if (theLanguage == "es") theMessage = "Ingrese un valor de exactitud por redondeando en el rango 1-13."
	alert(theMessage);
	okToRoll = false
	}
numSigDigs = accuracydig;
} // checkSettings



var haveData = false;
var tab = unescape( "%09" );	
var cr = unescape( "%0D" );	
var lf = unescape( "%0A" );
var semicolon = unescape( '%3B' );
var comma = ",";
var textareaText = '';
var maxNumPoints = 200; 
var numPoints = 0;
var thePlottedPoints = new makeArray2(maxNumPoints,2);
var freqDist = new makeArray2(maxNumPoints,2);

var yMaxPoints = 0; 
var yMinPoints = 0; // maximum & minimum y-value of plotted points
var xMaxPoints = 0; 
var xMinPoints = 0; // maximum & minimum x-value of plotted points
var sumFreq = 0; // sum of frequencies
var areNumbersX = false;  // true if the category names are numbers
var xHeight = 5; // height of the plotted Xs

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 readInDataArea() { 
// 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) {
	haveData = true;
	areNumbersX = true;
	for (var i = 1; i <= numPoints; i++) {
		thePoint = parser(xyPoints[i], tab);
		thePlottedPoints[i][1] = thePoint[1];
		if (isNaN(thePlottedPoints[i][1])) areNumbersX = false;
		thePlottedPoints[i][2] = parseFloat(thePoint[2])
		if (isNaN(thePlottedPoints[i][2])) {
			alert(thePoint[2] + " does not look like a number to me. Each data point must have the form A,B where B is a nonnegative number.");
			haveData = false; // stop the process before hell breaks loose
			}
			else if (thePlottedPoints[i][2] < 0) {
			alert(thePoint[2] + " is negative. Each data point must have the form A,B where B is a nonnegative number.");
			haveData = false; // stop the process before hell breaks loose
			}

		} // i

	// now compute y max and min for these (useful for later)
	// and also the sum of the frequencies
	yMaxPoints = thePlottedPoints[1][2];
	yMinPoints = yMaxPoints;
	sumFreq = 0;
	for (var i = 1; i <= numPoints; i++) {
		try
			{
			sumFreq += thePlottedPoints[i][2];
 			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 haveData = false;
} // readInDataArea

function generateRelativeFrequencies() {
if (sumFreq == 0) {
		alert("Your frequencies are all zero....")
		okToRoll = false;
		}

if (Math.abs(sumFreq - 1) < .0001) haveProbabilities = true;
else haveProbabilities = false;


for (var i = 1; i <= numPoints; i++) {
	freqDist[i][1] = thePlottedPoints[i][1];
	freqDist[i][2] = thePlottedPoints[i][2]/sumFreq;
	} // i
// alert(freqDist[1][2] + "," + freqDist[4][2]);

maxRelFreq = yMaxPoints/sumFreq;

} // generateRelativeFrequencies


function displayResults(theResults) {
// var theScriptString = '<STYLE TYPE=' + quoteMark + 'text/css' + quoteMark + '>BODY, LAYER, P, DIV, TABLE, TR, TD, TH {font-size: 12px; font-family: times; color: 000000}</STYLE>';

var theString = '<center><table><tr><td><TABLE BORDER = 1 cellspacing = 0 cellpadding = 2 noshade border = 0 bordercolor = #FF7272 STYLE="background-color: FFE9EE; border-collapse: collapse">';

if (haveProbabilities ) theString += '<th colspan = 2><b>Relative Frequency Distribution</b></th><tr><td align = center><i>X</i></td><td align = center><i>P</i>(<i>X</i> = <i>x</i>)</td>';
else theString += '<th colspan = 3><b>Relative Frequency Distribution</b></th><tr><td align = center><i>X</i></td><td align = center><i>fr</i>(<i>X</i>)</td><td align = center><i>P</i>(<i>X</i> = <i>x</i>)</td>';

theString += '</tr><tr>';
var theValue;
for (var i = 1; i <= numPoints; i++) {
		if ( (!fractionMode) && (sigDigMode)) theValue = roundSigDig(freqDist[i][2], numSigDigs);
		else if ( (!fractionMode) && (!sigDigMode)) theValue = roundDec(freqDist[i][2], numSigDigs);
		else theValue = toFrac(freqDist[i][2]);
		theString+= '<td>' + freqDist[i][1] + '</td>';
		if (!haveProbabilities) theString+= '<td>' + thePlottedPoints[i][2] + '</td>';
		theString+= '<td>' + theValue + '</td>';

	theString += '</tr>'
	} // i
theString += '</table></td><td width = 20></td><td valign = top>';
if (theResults == "showStats") {
	if (!areNumbersX) alert(theAlerts[1]);
	else {
		var sumProd = 0;
		for (var i = 1; i <= numPoints; i++) {
			sumProd += freqDist[i][1]*freqDist[i][2];
			}
		var EX = sumProd;
		sumProd = 0;
		for (var i = 1; i <= numPoints; i++) {
			sumProd += (freqDist[i][1]-EX)*(freqDist[i][1]-EX) *freqDist[i][2];
			}	
		var theVariance = sumProd;
		var theStDev = Math.pow(theVariance,.5);
		
		theString += '<p>&nbsp;<p><i>E</i>(<i>X</i>) = ' + roundSigDig(EX,numSigDigs);
		theString += '<p><img src = "../tutstats/gf/sigma.gif"><sup>2</sup>(<i>X</i>) = ' + roundSigDig(theVariance,numSigDigs);
		theString += '<p><img src = "../tutstats/gf/sigma.gif"> (<i>X</i>) = ' + roundSigDig(theStDev,numSigDigs);
		} // have numbers to use
	} // if showing stats

theString += '</td></tr></table></center>';
document.getElementById("data").innerHTML = theString;



}

function cleanUp(theGraph) {
with(theGraph) {
	if (surroundColor == "white") ctx.fillStyle =  "rgb(255, 255, 255)";
	else ctx.fillStyle =  "rgb(" + surroundColor [0] + "," + surroundColor [1] + "," + surroundColor [2] + ")";
// alert(" " + theGraph.yLabelMargin + ", " + (theGraph.numY-1) + ", " + theGraph.numX+ theGraph.yLabelMargin+ ", " + 10);

ctx.fillRect(theGraph.yLabelMargin, theGraph.numY-10, theGraph.numX+ theGraph.yLabelMargin, 10);

ctx.fillRect(theGraph.yLabelMargin-40, 0, 40, theGraph.numY-10);

	} // with theGraph

} // cleanUp

function randomInt(a,b) {
// returns a random integer in the range [a, b]
return(a + Math.floor(Math.random()*(b-a+1)));
}

function pickOne() {
var argArr = pickOne.arguments;
var n = argArr.length-1;
return(pickOne.arguments[randomInt(0,n)]);
}

