
/* GraphPaper.js
 *
 *	Michael Holder,  Sept 2009
 *
 *	$Id: GraphPaper.js,v 1.36 2011/10/23 22:55:01 michaelholder Exp $
 *
 *  Copyright (C) 2007-2011 TRD Associates, LLC
 *
 *    - All rights reserved.
 */

//========================================================================

var lineColor = ["#0000FF", "#FF0000", "#3A7728", "#A00054", "#FF00FF", "#000000"];
var pointColor = ["#0000FF", "#FF0000", "#3A7728", "#A00054", "#FF00FF"];
var pntPath = [ "z", "m-3,3l6,0l0,-6l-6,0l0,6", "m0,4l4,-4l-4,-4l-4,4z", "m-4,3l4,-7l4,7z"];
var fillColor = ["#6689CC", "#F4ED7C", "#84E2A8", "#EAAAC4", "#AA72BF"];

var Color = {yellow:"#FFFF00"};

//========================================================================

function winSize() {
	var myWidth = 0, myHeight = 0;
	if( typeof( window.innerWidth ) == 'number' ) {
		//Non-IE
		myWidth = window.innerWidth;
		myHeight = window.innerHeight;
	} else if( document.documentElement && 
			  ( document.documentElement.clientWidth || 
			   document.documentElement.clientHeight ) ) {
		//IE 6+ in 'standards compliant mode'
		myWidth = document.documentElement.clientWidth;
		myHeight = document.documentElement.clientHeight;
	} else if( document.body && 
			  ( document.body.clientWidth || 
			   document.body.clientHeight ) ) {
		//IE 4 compatible
		myWidth = document.body.clientWidth;
		myHeight = document.body.clientHeight;
	}
	return new Array(myWidth, myHeight);
}

//========================================================================

function Point( xX, yY ) {
	
	this.x = xX - 0;
	this.xText = xX + "";
	this.y = yY - 0;
	this.yText = yY + "";
	
	
	this.X = function() {
		return this.x;
	}
	
	
	this.Y = function() {
		return this.y;
	}
	
}

//========================================================================

function Data( typ, lab, Col, dat ) {

	this.representation = typ;
	this.label = lab;
	this.lineColor = Col;
	this.points = dat;
	
	
//	public enum Type { hBar, vBar, line, points, spcPoints, gasGauge, stopLight };
	
	//------------------------------------------------
	
	this.paintLine = function( lineNumber, gppr) {
		
		
		if ( this.representation == "LINE" ) {
			
			gppr.plotLineData( this.lineColor, "#0000FF", this.points, -1 );
			
		} else if ( this.representation == "RUN" ) {
			
			var lin = lineNumber % 6;
			var pnt = lineNumber % 5;
			
			gppr.plotLineData( lineColor[lineNumber], pointColor[pnt], this.points, lineNumber );
			
		} else if ( this.representation == "STAT" ) {
			
			gppr.plotLineData( "#FF0000", "#0000FF", this.points, true );
			
		} else if ( this.representation == "HBAR" ) {
			
			gppr.plotHBarData( this.lineColor, this.points );
			
		} else if ( this.representation == "PARETO" ) {
			
			var lin = lineNumber % 6;
			var pnt = lineNumber % 5;
			
			gppr.plotParetoData( lineColor[lineNumber], fillColor[pnt], this.points );
			
		} else if ( this.representation == "VBAR" ) {
			
			var lin = lineNumber % 6;
			var pnt = lineNumber % 5;
			
			gppr.plotVBarData( lineColor[lineNumber], fillColor[pnt], this.points, lineNumber );
			
		} else if ( this.representation == "GASGAUGE" ) {
			
			gppr.plotGasGauge( this.label, this.points[this.points.length-1] );
			
		} else if ( this.representation == "WINDVANE" ) {
			
			gppr.plotWindVane( this.label, this.points[this.points.length-1] );
			
		} else if ( this.representation == "STOPLIGHT" ) {
			
			gppr.plotStopLight( this.label, this.points );
		}
	}
	
	//------------------------------------------------
	
	this.paintLegend = function( lineNumber, gppr) {
		
		
		if ( this.representation == "LINE" ) {
			
			gppr.drawLabel( lineNumber, this.label, false );
			
		} else if ( this.representation == "RUN" ) {
			
			var lin = lineNumber % 6;
			var pnt = lineNumber % 5;
			
			gppr.drawLabel( lineNumber, this.label, true );
			
		} else if ( this.representation == "STAT" ) {
			
			gppr.drawLabel( lineNumber, this.label, false );
			
		} else if ( this.representation == "HBAR" ) {
			
			gppr.drawLabel( lineNumber, this.label, false );
			
		} else if ( this.representation == "PARETO" ) {
			
			var lin = lineNumber % 6;
			var pnt = lineNumber % 5;
			
			gppr.drawLabel( lineNumber, this.label, "BAR" );
			
		} else if ( this.representation == "VBAR" ) {
			
			var lin = lineNumber % 6;
			var pnt = lineNumber % 5;
			
			gppr.drawLabel( lineNumber, this.label, "BAR" );
			
		}
	}
}


//========================================================================

function TickMark( qQ, QLabel, tType ) {
	
	this.q = qQ - 0;
	this.qLabel = QLabel+"";
	this.TickType = tType;
}

//========================================================================

var monthStr = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

//-------------------------------------------------------//

function timeStr(hours, minutes, seconds) {
	var h = hours;
	if (h < 10) {
		h = "0"+ h;
	}
	var m = minutes;
	if (m < 10) {
		m = "0"+ m;
	}
	
	var t = h+":"+m;
	
	if (seconds != null) {
		var s = seconds;
		if (s < 10) {
			s = "0" + s;
		}
		t = t+":"+s;
	}
	
	return t;
}

//-------------------------------------------------------//

function fYear(dt, inc) {
	var t = dt.getTime();
	dt.setMonth(0);
	dt.setDate(1);
	dt.setHours(0);
	dt.setMinutes(0);
	dt.setSeconds(0);
	dt.setMilliseconds(0);
	var year = dt.getFullYear();
	year = year - year % inc;
	dt.setFullYear(year);
	if (dt.getTime() < t) {
		year += inc;
		dt.setFullYear(year);
	}
	return year;
}

//-------------------------------------------------------//

function nYear(dt, inc, end) {
	var year = dt.getFullYear() + inc;
	dt.setFullYear(year);
	if (dt.getTime() < end) {
		return year;
	} else {
		return null;
	}	
}

//-------------------------------------------------------//

function fMonth(dt, inc) {
	var t = dt.getTime();
	dt.setDate(1);
	dt.setHours(0);
	dt.setMinutes(0);
	dt.setSeconds(0);
	dt.setMilliseconds(0);
	var mo = dt.getMonth();
	mo = mo - mo % inc;
	dt.setMonth(mo);	
	if (dt.getTime() < t) {
		mo += inc;
		dt.setMonth(mo);
	}
	return monthStr[dt.getMonth()];
}

//-------------------------------------------------------//

function nMonth(dt, inc, end) {
	var mo = dt.getMonth();
	dt.setMonth(mo + inc);
	if (dt.getTime() < end) {
		return monthStr[dt.getMonth()];
	} else {
		return null;
	}	
}

//-------------------------------------------------------//

function fWeek(dt, inc) {
 	var t = dt.getTime();
 	dt.setHours(0);
 	dt.setMinutes(0);
 	dt.setSeconds(0);
 	dt.setMilliseconds(0);
	var day = dt.getDay();
	var date = dt.getDate();
	if (day > 0) {
 		dt.setDate(date + 7 - day);
 	}
 	return dt.getMonth()+1+"/"+dt.getDate();
}

//-------------------------------------------------------//
 
 function nWeek(dt, inc, end) {
 	var date = dt.getDate();
 	dt.setDate(date + inc);
 	if (dt.getTime() < end) {
 		return dt.getMonth()+1+"/"+dt.getDate();
 	} else {
		return null;
	}	
}
 
//-------------------------------------------------------//

function fDay(dt, inc) {
	var t = dt.getTime();
	dt.setHours(0);
	dt.setMinutes(0);
	dt.setSeconds(0);
	dt.setMilliseconds(0);
	var date = dt.getDate();
	if (dt.getTime() < t) {
		date += 1;
		dt.setDate(date);
	}
	return dt.getMonth()+1+"/"+dt.getDate();
}

//-------------------------------------------------------//

function nDay(dt, inc, end) {
	var date = dt.getDate();
	dt.setDate(date + inc);
	if (dt.getTime() < end) {
		return dt.getMonth()+1+"/"+dt.getDate();
	} else {
		return null;
	}	
}

//-------------------------------------------------------//

function fHour(dt, inc) {
	var t = dt.getTime();
	dt.setMinutes(0);
	dt.setSeconds(0);
	dt.setMilliseconds(0);
	var hours = dt.getHours();
	hours = hours - hours % inc;
	dt.setHours(hours);
	if (dt.getTime() < t) {
		hours += inc;
		dt.setHours(hours);
	}
	return dt.getHours()+":";
}

//-------------------------------------------------------//

function nHour(dt, inc, end) {
	var hours = dt.getHours();
	dt.setHours(hours + inc);
	if (dt.getTime() < end) {
		return dt.getHours()+":";
	} else {
		return null;
	}	
}

//-------------------------------------------------------//

function fMinute(dt, inc) {
	var t = dt.getTime();
	dt.setSeconds(0);
	dt.setMilliseconds(0);
	var minutes = dt.getMinutes();
	minutes = minutes - minutes % inc;
	dt.setMinutes(minutes);
	if (dt.getTime() < t) {
		minutes += inc;
		dt.setMinutes(minutes);
	}
	return timeStr(dt.getHours(),dt.getMinutes());
}

//-------------------------------------------------------//

function nMinute(dt, inc, end) {
	var minutes = dt.getMinutes();
	minutes += inc;
	dt.setMinutes(minutes);
	if (dt.getTime() < end) {
		return timeStr(dt.getHours(), dt.getMinutes());
	} else {
		return null;
	}	
}

//-------------------------------------------------------//

function fSecond(dt, inc) {
	var t = dt.getTime();
	dt.setMilliseconds(0);
	var seconds = dt.getSeconds();
	seconds =  seconds - seconds % inc;
	dt.setSeconds(seconds);
	if (dt.getTime() < t) {
		seconds += inc;
		dt.setSeconds(seconds);
		}
	return timeStr(dt.getHours(),dt.getMinutes(), dt.getSeconds());
}

//-------------------------------------------------------//

function nSecond(dt, inc, end) {
	var seconds = dt.getSeconds();
	seconds += inc;
	dt.setSeconds(seconds);
	if (dt.getTime() < end) {
		return timeStr(dt.getHours(), dt.getMinutes(), dt.getSeconds());
	} else {
		return null;
	}	
}

//-------------------------------------------------------//

var units = [
			 ["century", 3155760000, fYear, nYear, 100],
			 ["decades", 1577880000, fYear, nYear, 50],
			 ["decades", 631152000, fYear, nYear, 20],
			 ["decade", 315576000, fYear, nYear, 10],
			 ["years", 157788000, fYear, nYear, 5],
			 ["years", 63115200, fYear, nYear, 2],
			 ["year", 31557600, fYear, nYear, 1],
			 ["quarters", 7862400, fMonth, nMonth, 3],
			 ["month", 2620800, fMonth, nMonth, 1],
			 ["week", 604800, fWeek, nWeek, 7],
			 ["days", 259200, fDay, nDay, 3],
			 ["day", 86400, fDay, nDay, 1],
			 ["hours", 10800, fHour, nHour, 3],
			 ["hour", 3600, fHour, nHour, 1],
			 ["minutes", 900, fMinute, nMinute, 15],
			 ["minutes", 600, fMinute, nMinute, 10],
			 ["minutes", 300, fMinute, nMinute, 5],
			 ["minutes", 120, fMinute, nMinute, 2],
			 ["minute", 60, fMinute, nMinute, 1],
			 ["seconds", 15, fSecond, nSecond, 15],
			 ["seconds", 10, fSecond, nSecond, 10],
			 ["seconds", 5, fSecond, nSecond, 5],
			 ["seconds", 2, fSecond, nSecond, 2],
			 ["second", 1, fSecond, nSecond, 1]
];

function dateTicks(t1, t2) {
	
	var Ticks = new Array();

	var range = (t2 - t1) / 1000;
	
	for (var i in units) {
		
		var z = Math.floor(range / units[i][1]);
		
		if (z > 1) {
			
			var period = units[i][0];
			var first = units[i][2];
			var next = units[i][3];
			var increment = units[i][4];
			
			var d = new Date();
			d.setTime(t1);
			
			for(tick = first(d, increment);
				tick != null;
				tick = next(d, increment, t2)) {
				var t = d.getTime();
				Ticks.push(new TickMark(t, tick, 'MAJOR'));
			
			}
			
			if (units[i][1] > 1) {
				
				var j = i - 0 + 1;
				
				if ((increment == 7) ||
					(increment == 50) ||
					(increment == 15) ||
					(increment == 5)) {
					j += 1;
				}
				
				first = units[j][2];
				next = units[j][3];
				increment = units[j][4];
				
				d.setTime(t1);
				
				for(tick = first(d, increment);
					tick != null;
					tick = next(d, increment, t2)) {
					var t = d.getTime();
					Ticks.push(new TickMark(t, tick, 'MINOR'));
					
				}
			}
			
			return [period, Ticks];
		}
	}
	
}

//========================================================================

// *** This class is the canvas used for the graph ***

function GraphPaper( view, xLab, col, w, h ) {
	
	this.xLabel = xLab;
	this.plotColor = col;
	this.prefSize = new Point( w, h );
	this.plotData = new Array();

	this.paper = Raphael(view, w, h);
	
	//------------------------------------------------
	
	this.TextTicks = function(points) {
		
		var Ticks = new Array();
		
		var i = 0;
		for (var p in points) {
			Ticks.push(new TickMark(i++, points[p].xText,'MAJOR'));
		}
		return Ticks;
	}
	
	//------------------------------------------------
	
	this.TickMarks = function(Max, Min, MaxTicks) {
		
		var Ticks = new Array();
		
		Factors = new Array( 1, 2, 5, 10, 20, 50 );
		
		var z, q, Tick;
		
		var Range = Max - Min;
		var Magnitude = Math.pow(10, Math.round(Math.log(Range)/Math.LN10));
		
		Tick = 1.0;
		for (var i in Factors) {
			z = Range / Magnitude * Factors[i];
			if ((z < MaxTicks) && (z > 4)) {
				Tick = Magnitude / Factors[i];
				if (Min < 0) {
					q = Tick * Math.ceil(Min / Tick);
				} else {
					q = Tick * Math.floor(Min / Tick);
				}
				if (Min > 0) {
					q += Tick;
				}
				var tLog = Math.log(Tick)/Math.LN10;
				var places = 0;
				if (tLog < 0) {
					places = Math.ceil(Math.abs(tLog));
				}
				
				while (q <= Max) {
					Ticks.push(new TickMark(q, q.toFixed(places), 'MAJOR'));
					q += Tick;
				}
				return Ticks;
			}
		}
		return Ticks;
	}
	
	//------------------------------------------------
	
	this.init = function() {
		
		this.xMax = -1e99;
		this.yMax = -1e99;
		this.yMin = 1e99;
		this.xMin = 1e99;
		
		this.plotData = new Array();
		
		this.spc = false;
		this.UCL = null;
		this.LCL = null;
		this.addXmargin = false;
		this.xMargin = 20.0;
		this.drawPlotAxis = false;
		this.xAxisType = 'N';

//		this.paper.rect( 0, 0, this.prefSize.X(), this.prefSize.Y())
//						.attr({fill:"#FFFFFF","stroke-width":0});
		
	}
	
	//------------------------------------------------
	
	this.setYlimits = function( max, min ) {
		this.yMax = max;
		this.yMin = min;
	}	
	
	//------------------------------------------------
	
	this.checkX = function( x ) {
		if ( x < this.xMin )
			this.xMin = x;
		if ( x > this.xMax )
			this.xMax = x;
	}	
	
	//------------------------------------------------
	
	this.checkY = function( y ) {
		if ( y < this.yMin )
			this.yMin = y;
		if ( y > this.yMax )
			this.yMax = y;
	}	
	
	//------------------------------------------------
	
	this.addData = function( type, lab, col, points ) {

		for (var p in points) {
			this.checkX( points[p].X() );
			this.checkY( points[p].Y() );
		}

		if ( type == "HBAR" ) {
			this.addXmargin = true;
			this.drawPlotAxis = true;
		} else if ( type == "VBAR" ) {
			this.addXmargin = true;
			this.drawPlotAxis = true;
		} else if ( type == "GASGAUGE" ) {
			this.drawPlotAxis = false;
		} else if ( type == "WINDVANE" ) {
			this.drawPlotAxis = false;
		} else if ( type == "PARETO" ) {
			this.addXmargin = true;
			this.drawPlotAxis = true;
			this.xMin = 0.0;
			this.xMax = points.length - 1.0;
		} else if ( type == "STOPLIGHT" ) {
			this.drawPlotAxis = false;
		} else {
			this.drawPlotAxis = true;
		}
		
		this.plotData.push( new Data( type, lab, col, points) );
		
	}
	
	//------------------------------------------------
	
	this.specLimits = function( usl, lsl, units ) {
		
		this.addXmargin = true;
		
		if (usl != null) {
			this.USL = usl;
			this.checkY( this.USL );
		}
		
		if (lsl != null) {
			this.LSL = lsl;
			this.checkY( this.LSL );
		}
		if (units != null) {
			this.units = units;
		}
		
	}
	
	//------------------------------------------------
	
	this.spcLimits = function( xBar, label, bars, ucl, lcl ) {
		
		this.spc = true;
		this.addXmargin = true;
		
		this.Xbar = xBar;
		this.XbarLabel = label;
		this.numBars = bars;
		this.UCL = ucl;
		this.LCL = lcl;
		
		this.checkY( this.Xbar );
		this.checkY( this.UCL );
		this.checkY( this.LCL );
		
	}

	//------------------------------------------------
	
	this.scaleX = function( x ) {
		return Math.floor(( x - this.xMinM )/( this.xMaxM - this.xMinM ) * this.iRangeX ) + this.iOriginX;
	}
	
	//------------------------------------------------
	
	this.scaleY = function( y ) {
		return Math.floor(this.iOriginY - Math.floor(( y - this.yMinM )/
										   ( this.yMaxM - this.yMinM ) * this.iRangeY ));
	}
	
	//------------------------------------------------
	
	this.setMargin = function() {
		
		if ( this.addXmargin ) {
			var xMargin = (this.xMax - this.xMin) / this.xMargin;
			this.xMaxM = this.xMax + xMargin;
			this.xMinM = this.xMin - xMargin;
		} else {
			this.xMaxM = this.xMax;
			this.xMinM = this.xMin;
		}
		var yMargin = (this.yMax - this.yMin) / 20.0;
		this.yMaxM = this.yMax + yMargin;
		this.yMinM = this.yMin - yMargin;
	}
	
	//------------------------------------------------
	
	this.xType = function( label ) {
		if (label == "date") {
			this.xAxisType = 'D';
		} else if (label == "text") {
			this.xAxisType = 'T';
		}
	}	
	
	//------------------------------------------------
	
	this.drawAxis = function() {
		
		var XTICKMARGIN = 35;
		var YTICKMARGIN = 50;
		var TOPMARGIN = 15;
		var RIGHTMARGIN = 15;
		var MAJORTICK = 5;
		var MINORTICK = 3;
		var iLCL = 0;
		var iUCL = 0;
		var iXbar = 0;
		var iUSL = 0;
		var iLSL = 0;
		
		var w = this.prefSize.X();
		var h = this.prefSize.Y();
		
//		this.paper.setClip( 0, 0, w, h );
		
		if ( this.spc ) {
			RIGHTMARGIN = 35;
		}
		if (this.xAxisType == 'T') {
			XTICKMARGIN = 75;
		}
		
		this.setMargin();
		
		this.iOriginX = YTICKMARGIN;
		this.iOriginY = h - XTICKMARGIN;
		this.iRangeX = w - RIGHTMARGIN - YTICKMARGIN;
		this.iRangeY = h - TOPMARGIN - XTICKMARGIN;
		
		if ( ! this.drawPlotAxis ) {
			return;
		}
		
		this.paper.rect( this.iOriginX, this.iOriginY - this.iRangeY,
						this.iRangeX, this.iRangeY ).attr({"fill":this.plotColor, "stroke-width":0});
		
		if ( this.spc ) {
			iLCL = this.scaleY( this.LCL );
			iUCL = this.scaleY( this.UCL );
			iXbar = this.scaleY( this.Xbar );
			
			this.paper.rect( this.iOriginX, iUCL, 
							this.iRangeX, iLCL - iUCL ).attr({fill:"#DCFFDC", "stroke-width":0});
		}
		
		
		
		
		if (this.USL !=null) {
			iUSL = this.scaleY(this.USL);
			var iYTop = this.iOriginY - this.iRangeY;
			var iShade = iUSL - iYTop;
			this.paper.rect( this.iOriginX, iYTop, 
							this.iRangeX,  iShade ).attr({fill:"#F9BFC1", "stroke-width":0});
		}

		if (this.LSL !=null) {
			iLSL = this.scaleY(this.LSL);
			this.paper.rect( this.iOriginX, iLSL, 
							this.iRangeX,  this.iOriginY - iLSL ).attr({fill:"#F9BFC1", "stroke-width":0});
		}
		
		
		
		var xticks;
		if (this.xAxisType == 'D') {
			xticks = dateTicks( this.xMinM, this.xMaxM );
			this.xLabel = this.xLabel+" ["+xticks[0]+"]";
			xticks = xticks[1];
		} else if (this.xAxisType == 'T') {
			xticks = this.TextTicks(this.plotData[0].points);
		} else {
			xticks = this.TickMarks( this.xMaxM, this.xMinM, 11 );
		}
		
		for ( var i in xticks ) {
			var xT = xticks[i];
			var ix = this.scaleX( xT.q );
			if (xT.TickType == 'MAJOR') {
				this.drawLine( ix, this.iOriginY, ix, this.iOriginY - this.iRangeY, "#FFFFFF" );
				this.drawLine( ix, this.iOriginY, ix,this.iOriginY + MAJORTICK );
				if (this.xAxisType == 'T') {
					var tickLabel = this.paper.text( ix, this.iOriginY + 12, xT.qLabel ).attr("font-size",12).attr("text-anchor","start");
					tickLabel.rotate(45, ix, this.iOriginY + 12);
				} else {
					this.paper.text( ix, this.iOriginY + 12, xT.qLabel ).attr("font-size",12);
				}
			} else {
				this.drawLine( ix, this.iOriginY, ix,this.iOriginY + MINORTICK );
			}
		}

		var yticks = this.TickMarks( this.yMaxM, this.yMinM, 21 );
		
		for ( var j in yticks ) { 
			var yT = yticks[j];
			var iy = this.scaleY( yT.q ); 
			this.drawLine( this.iOriginX, iy, this.iOriginX - MAJORTICK, iy );
			this.paper.text( this.iOriginX - MAJORTICK - 4, iy, yT.qLabel ).attr("text-anchor","end").attr("font-size",12);
			this.drawLine( this.iOriginX, iy, this.iOriginX + this.iRangeX, iy, "#FFFFFF" );				
		}
		
		var iXtop = this.iOriginX + this.iRangeX + 1;
		
		if ( this.spc ) {
			
			var iXtick = iXtop + 6;
			var iXmid = iXtop + 4;
			
			this.drawLine( this.iOriginX, iLCL, iXtop, iLCL, "#A53F0F" );
			this.drawLine( this.iOriginX, iUCL, iXtop, iUCL, "#A53F0F" );
			
			this.drawLine( this.iOriginX, iXbar, iXtop, iXbar, "#777777" );
			
			
			if (iXbar - iUCL < 18) {
				iy = iUCL;
				iUCL = iXbar - 18;
				this.drawLine(iXtop, iy, iXmid, iUCL, "#777777");
				this.drawLine(iXmid, iUCL, iXtick, iUCL, "#777777");
			} else {
				this.drawLine(iXtop, iUCL, iXmid, iUCL, "#777777");
			}
			
			if (iLCL - iXbar < 15) {
				iy = iLCL;
				iLCL = iXbar + 15;
				this.drawLine(iXtop, iy, iXmid, iLCL, "#777777");
				this.drawLine(iXmid, iLCL, iXtick, iLCL, "#777777");
			} else {
				this.drawLine(iXtop, iLCL, iXmid, iLCL, "#777777");
			}
			
			this.drawLine(iXtop, iXbar, iXmid, iXbar, "#777777");
			
			this.paper.text( iXtick, iLCL, "LCL" ).attr("text-anchor","start").attr("font-size",12);
			this.paper.text( iXtick, iUCL, "UCL" ).attr("text-anchor","start").attr("font-size",12);
			this.paper.text( iXtick, iXbar, this.XbarLabel ).attr("text-anchor","start").attr("font-size",12);
			if ( this.numBars > 0 ) {
				this.drawLine( this.iOriginX + this.iRangeX + 6,  iXbar - 8, 
							  this.iOriginX + this.iRangeX + 16, iXbar - 8);
			}
			if ( this.numBars > 1 ) {
				this.drawLine( this.iOriginX + this.iRangeX + 6,  iXbar - 11,  
							  this.iOriginX + this.iRangeX + 16, iXbar - 11);
			}
		}
		
		if ( ( this.yMin < 0.0 ) && ( this.yMax > 0 ) ) {
			var iy = this.scaleY( 0.0 );
			this.drawLine( this.iOriginX, iy, this.iOriginX + this.iRangeX, iy, "#777777" );
		}

		if (this.USL != null) {
			this.drawLine( this.iOriginX, iUSL, iXtop, iUSL, "#FF0000" );
		}
		
		if (this.LSL != null) {
			this.drawLine( this.iOriginX, iLSL, iXtop, iLSL, "#FF0000" );
		}
		
		this.paper.rect( this.iOriginX, this.iOriginY - this.iRangeY, 
						this.iRangeX, this.iRangeY ).attr("stroke-width",2); 
		
		this.paper.text( this.iOriginX + this.iRangeX / 2,
						h - 9, this.xLabel ).attr("font-size",14);
	}
		
	//------------------------------------------------
	
	this.paint = function(margin) {
		
		if (margin) {
			this.xMargin = margin;
		}
		
		this.drawAxis();
		
		for( var d in this.plotData ) {
			this.plotData[d].paintLine(d, this );
		}

		for( var d in this.plotData ) {
			this.plotData[d].paintLegend(d, this );
		}
	}
	
	//------------------------------------------------
	
	this.drawPoint = function(ix, iy, num, col) {
		num = num % 4;
		if (num > 0) {
			this.paper.path("M"+ix+","+iy+pntPath[num]).attr({fill:col,"stroke-width":0});
		} else {
			this.paper.circle( ix, iy, 3, 3 ).attr({fill:col,"stroke-width":0});
		}
	}
	
	//------------------------------------------------
	
	this.drawLine = function(x1,y1,x2,y2,color,width) {
		var c = color || "#000000";
		var w = width || 1;
		this.paper.path("M"+x1+","+y1+"L"+x2+","+y2).attr({stroke:c, "stroke-width":w});
	}
	
	//------------------------------------------------
	
	this.drawLabel = function( lineNum, label, legend ) {
		
		var lin = lineNum % 6;
		var pnt = lineNum % 5;
		
		var iy = this.iOriginY - this.iRangeY + lineNum * 17 + 17;
		var ix = this.iOriginX + this.iRangeX - 10;
		
		if (legend == "BAR") {
			
			var lin = lineNum % 6;
			var pnt = lineNum % 5;
			
			this.paper.rect( ix - 10, iy - 6, 10, 10 ).attr({fill:fillColor[pnt],stroke:lineColor[lin]});			
			ix -= 12;
		} else if (legend) {
			this.paper.path("M"+ix+","+iy+"l-10,0").attr({stroke:lineColor[lin], "stroke-width":1});
			this.drawPoint(ix, iy, lineNum, pointColor[pnt]);
			ix -= 12;
		}
		
		this.paper.text(ix+1, iy+1,label).attr({fill:"#FFFFFF","text-anchor":"end","font-size":12});
		this.paper.text(ix+1, iy-1,label).attr({fill:"#FFFFFF","text-anchor":"end","font-size":12});
		this.paper.text(ix-1, iy+1,label).attr({fill:"#FFFFFF","text-anchor":"end","font-size":12});
		this.paper.text(ix-1, iy-1,label).attr({fill:"#FFFFFF","text-anchor":"end","font-size":12});
		this.paper.text(ix, iy,label).attr({"text-anchor":"end","font-size":12});
	}

	//------------------------------------------------
	
	this.plotLineData = function( lineColor, pointColor, points, drawPoints ) {
		
		var ix, iy, iLastX, iLastY;
		
		iLastX = 0xFFFFFF;
		iLastY = 0xFFFFFF;
		
		for ( var p in points ) {
			
//			alert("Points: x,y = ["+points[p].X()+", "+points[p].Y()+"]");
			
			ix = this.scaleX( points[p].X() );
			iy = this.scaleY( points[p].Y() );
			
			if ( iLastX == 0xFFFFFF ) {
				iLastX = ix;
				iLastY = iy;
			}
			
			this.drawLine(iLastX, iLastY, ix, iy, lineColor);			
			
			iLastX = ix;
			iLastY = iy;
		}
	
		if (drawPoints > -1) {
			for ( var p in points ) {
			
				ix = this.scaleX( points[p].X() );
				iy = this.scaleY( points[p].Y() );
					
				if ( this.spc ) { 
					if (( points[p].Y() > this.UCL) || (points[p].Y() < this.LCL)) {
						this.paper.circle( ix, iy, 3, 3 ).attr({fill:"#FF0000","stroke-width":0});
					} else {
						this.paper.circle( ix, iy, 3, 3 ).attr({fill:pointColor,"stroke-width":0});
					}
				} else {
					this.drawPoint(ix, iy, drawPoints, pointColor);
				}
			}
		}
	}

	//------------------------------------------------
	
	this.plotHBarData = function( lineColor, points ) {
		
		//		g.setClip( iOriginX, iOriginY - iRangeY, iRangeX, iRangeY );
		
		var tick = points[1].Y() - points[0].Y();
		var height = Math.floor(( tick * 0.80) /( this.yMaxM - this.yMinM ) * this.iRangeY);
		var offset = Math.floor(( tick * 0.40 ) /( this.yMaxM - this.yMinM ) * this.iRangeY);
		var barLeft = this.scaleX( 0.0 );
		
		for ( var p in points ) {
			
			var ix = this.scaleX( points[p].X() );
			var iy = this.scaleY( points[p].Y() );
			
			var width = ix - barLeft;
			
			this.paper.rect( barLeft, iy - offset, width, height ).attr({fill:"#FFFF7F",stroke:"#FF0000"});			
		}
		
	}
	
	//------------------------------------------------
	
	this.plotParetoData = function( lineColor, fillColor, points) {
		
		var zero = this.scaleY( 0.0 );
		var width = this.iRangeX / ( points.length + 1) * 0.8;
		var offset = width / 2 - 1;
		var barWidth = width - 2;
		if (barWidth < 1) {
			barWidth = 1;
		}
		
		for ( var p in points ) {
			
			var ix = this.scaleX(p) - offset;
			var iy = this.scaleY(points[p].Y());
			
			if (iy == zero) {
				iy = zero - 1;
			}
			
			var height = Math.abs(iy - zero);
			var barBottom = Math.min(zero, iy);
			
			this.paper.rect( ix, barBottom, barWidth, height ).attr({fill:fillColor,stroke:lineColor});			
		}
		
	}
	
	//------------------------------------------------
	
	this.plotVBarData = function( lineColor, fillColor, points, obsNumber) {
		
		var zero = this.scaleY( 0.0 );
		var space = this.iRangeX / ( points.length + 1) * 0.8;
		var width = space / this.plotData.length;
		var offset = space / 2;
		var barWidth = width - 2;
		if (barWidth < 1) {
			barWidth = 1;
		}
		
		for ( var p in points ) {
			
			var ix = this.scaleX(points[p].X()) - offset + obsNumber * width;
			var iy = this.scaleY(points[p].Y());
			
			if (iy == zero) {
				iy = zero - 1;
			}
			
			var height = Math.abs(iy - zero);
			var barBottom = Math.min(zero, iy);
			
			this.paper.rect( ix, barBottom, barWidth, height ).attr({fill:fillColor,stroke:lineColor});			
		}
		
	}
	
	//------------------------------------------------
	
	this.fillDial = function(cx, cy, r, v1, v2, color, width ) {
		
		var c = color || "#FFFFFF";
		var w = width || 1;
		
		var a1 = this.angle(v1);
		var a2 = this.angle(v2);
		
		largeArc = 0;
		if (Math.abs(a2-a1) > Math.PI) {
			largeArc = 1;
		}
		
		a1 = 3.926990817 - a1;
		a2 = 3.926990817 - a2;
		
		var x1 = cx + r * Math.cos(a1);
		var y1 = cy - r * Math.sin(a1);
		var x2 = cx + r * Math.cos(a2);
		var y2 = cy - r * Math.sin(a2);
		
		this.paper.path("M"+x1+","+y1+" A"+r+","+r+" 0 "+largeArc+",1 "+x2+","+y2).attr({stroke:c, "stroke-width":w});
	}
	
	//------------------------------------------------
	
	this.drawDial = function( x, y, r, color, width ) {
		
		var c = color || "#FFFFFF";
		var w = width || 1;
		
		var delta = r * 0.707106781;
		var x1 = x - delta;
		var yy = y + delta;
		var x2 = x + delta;
		
		this.paper.path("M"+x1+","+yy+" A"+r+","+r+" 0 1,1 "+x2+","+yy).attr({stroke:c, "stroke-width":w});
	}
	
	//------------------------------------------------
	
	this.drawRtick = function( y, r, label, color, size ) {
				
		var theta = 3.926990817 - this.angle( y );
		
		var cx = Math.floor( Math.cos( theta ) * r ) + this.iCenterX;
		var cy = this.iCenterY - Math.floor( Math.sin( theta ) * r );
		
		this.paper.text(cx , cy, label).attr("fill", color).attr("color", color).attr("font-size", size);
	}

	//------------------------------------------------
	
	this.drawRline = function( y, r1, r2, lineColor, width) {
		
		var c = lineColor || "#000000";
		var w = width || 1;
		
		var theta = 3.926990817 - this.angle( y );
		
		var ix1 = Math.floor( Math.cos( theta ) * r1 ) + this.iCenterX;
		var iy1 = this.iCenterY - Math.floor( Math.sin( theta ) * r1 );
		
		var ix2 = Math.floor( Math.cos( theta ) * r2 ) + this.iCenterX;
		var iy2 = this.iCenterY - Math.floor( Math.sin( theta ) * r2 );
		
		this.drawLine( ix1, iy1, ix2, iy2, c, w );
	}

	//------------------------------------------------
	
	this.angle = function( y ) {
		var theta = ( y - this.yMinM ) / ( this.yMaxM - this.yMinM ) * 4.71238898;
		return theta;
	}
	
	//------------------------------------------------
	
	this.plotGasGauge = function( label, current ) {

		var w = this.prefSize.X();
		var h = this.prefSize.Y();
		
		if (this.USL != null) {
			this.yMaxM = this.USL;
		} else {
			this.yMaxM = this.yMax;
		}
		if (this.LSL != null) {
			this.yMinM = this.LSL;
		} else {
			this.yMinM = this.yMin;
		}
		
		var yMargin = (this.yMaxM - this.yMinM) / 20.0;
		this.yMaxM = this.yMaxM + yMargin;
		this.yMinM = this.yMinM - yMargin;		
		
		var value = current.Y();
		if (value > this.yMaxM) {
			value = this.yMaxM;
		} else if (value < this.yMinM) {
			value = this.yMinM;
		}
		
		var dimension = Math.min( w, h ) * 18 / 20;
		
		var dial = dimension / 2.3;
		
		var tick = dimension / 50;
		var fontSize = dimension / 12.5;
		
		this.iCenterX = w / 2;
		this.iCenterY = (h + fontSize) / 2;
		
		var outerRange = dial * 18 / 20;
		var innerRange = dial * 12 / 20;
		
		var bandCenter = (innerRange + outerRange) / 2;
		var dialBand = (outerRange - innerRange) * 7 / 10;
		
		var dialWidth = dimension / 75;
		var tickWidth = dimension / 125;
		
		var innerTick = innerRange - tick;
		var outerTick = outerRange + tick;
		var tickR = (innerRange + outerRange) / 2;
		
		var needleLength = dial * 17 / 20;
		var needleWidth = dimension / 47;
		
		var labelY =  fontSize;
		var unitsY = this.iCenterY + dimension / 10;
		var valueY = this.iCenterY + innerTick;
		
		this.paper.circle( this.iCenterX, this.iCenterY, dial + 2 ).attr({"stroke":"#B7B7B7","stroke-width":3});
		this.paper.circle( this.iCenterX, this.iCenterY, dial ).attr("fill","#C8E1FF");
		
		
		if (this.USL != null) {
			this.fillDial( this.iCenterX, this.iCenterY, bandCenter, this.USL, this.yMaxM, "#FF5555", dialBand );
			if ((this.UCL < this.USL) && (this.LSL < this.UCL)){
				this.fillDial( this.iCenterX, this.iCenterY, bandCenter, this.UCL, this.USL, "#FFFF55", dialBand );
			}
		} else if (this.spc) {
			this.fillDial( this.iCenterX, this.iCenterY, bandCenter, this.UCL, this.yMaxM, "#FFFF55", dialBand );
		}

		if (this.LSL != null) {
			this.fillDial( this.iCenterX, this.iCenterY, bandCenter, this.yMinM, this.LSL, "#FF5555", dialBand );
			if ( this.spc ) {
				if ((this.LSL < this.LCL) && (this.LCL < this.USL)){
					this.fillDial( this.iCenterX, this.iCenterY, bandCenter, this.LSL, this.LCL, "#FFFF55", dialBand );
				}
			}
		} else if ( this.spc ) {
			this.fillDial( this.iCenterX, this.iCenterY, bandCenter, this.yMinM, this.LCL, "#FFFF55", dialBand );
		}
	
		this.drawDial( this.iCenterX, this.iCenterY, outerRange, "#6689CC", dialWidth );
		this.drawDial( this.iCenterX, this.iCenterY, innerRange, "#6689CC", dialWidth );
		
		if (this.USL != null) {
			this.drawRline( this.USL, innerTick, outerTick, "#FF5555", tickWidth );
		}
		if (this.LSL != null) {
			this.drawRline( this.LSL, innerTick, outerTick, "#FF5555", tickWidth );
		}
		
		if ( this.spc ) {
			if (( this.yMinM < this.LCL ) && ( this.LCL < this.yMaxM )) {
				this.drawRline( this.LCL, innerTick, outerTick, "#FFFF55", tickWidth );
			}
			if (( this.yMinM < this.UCL ) && ( this.UCL < this.yMaxM )) {
				this.drawRline( this.UCL, innerTick, outerTick, "#FFFF55", tickWidth );
			}
		}
		
		if ( ( this.yMinM < 0.0 ) && ( 0.0 < this.yMaxM )) {
			this.drawRline( 0.0, innerTick, outerTick, "#CCCCCC", 1 );
		}
		
		var dialTicks = this.TickMarks(this.yMaxM, this.yMinM, 15);
		for ( var j in dialTicks ) { 
			var yT = dialTicks[j];
			this.drawRtick(yT.q, tickR, yT.qLabel, '#000000', fontSize);
		}

//		this.paper.text( this.iCenterX , unitsY, this.units ).attr("fill","#FFFFFF").attr("font-size", fontSize);
		this.paper.text( this.iCenterX , labelY, label ).attr("fill","#000000").attr("font-size", fontSize);
		this.paper.text( this.iCenterX , valueY, current.Y().toFixed(1) ).attr("fill","#000000").attr("font-size", fontSize);
		
		this.drawRline( value, 0, needleLength, "#F00C0C", needleWidth );
		
		this.paper.circle( this.iCenterX, this.iCenterY, dial / 10 ).attr("fill","#333333");
	}
	
	//------------------------------------------------
	
	this.plotStopLight = function(label, dataPoints) {
		
		var current = dataPoints[dataPoints.length-1];
		var value = current.Y().toPrecision(4);
		
		var state = "GREEN";
		
		if ( this.spc ) {
		
			for ( p in dataPoints ) {
				var y = dataPoints[p].Y();
				if ( y > this.UCL ) {
					state = "YELLOW";
					break;
				} else if ( y < this.LCL ) {
					state = "YELLOW";
					break;
				}
			}
		}
		
		var y = dataPoints[dataPoints.length-1].Y();
		if (( this.USL != null) && (y > this.USL)){
				state = "RED";
		} else if ((this.LSL != null) && ( y < this.LSL )) {
				state = "RED";
		}
		
		this.setMargin();
		
		var w = this.prefSize.X();
		var h = this.prefSize.Y();
		
		var value = current.Y().toPrecision(4);
		
		var dimension = Math.min( w, h );
		var fontSize = dimension / 15;
		var lampFontSize = dimension / 19;

		var margin = dimension / 10;
		
		var iCenterX = w / 2;
		
		var spacing = (h - margin * 3) / 3.5;
		var radius = spacing * 9 / 20;
		var outline = spacing * 7 / 12;
		
		var lampCenter = iCenterX - outline;
		var alertCenter = iCenterX + outline;

		var yellowCenter = h / 2 - dimension / 50;
		var greenCenter = yellowCenter + spacing;
		var redCenter = yellowCenter - spacing;
		
		this.paper.rect( lampCenter - outline, redCenter - outline,
						outline * 2, spacing * 2 + outline * 2 ).attr({"fill":"#C8E1FF", "stroke-width":2});
		
		this.paper.text( iCenterX , margin, label ).attr("fill","#000000").attr("font-size", fontSize);

		if (state == "RED") {
			this.paper.circle( lampCenter, redCenter, radius ).attr("fill","#FF0000");
			this.paper.circle( lampCenter, redCenter, radius + 3 ).attr("fill","none");
			this.paper.circle( lampCenter, yellowCenter, radius ).attr("fill","#3F3F00");
			this.paper.circle( lampCenter, greenCenter, radius ).attr("fill","#003F00");
			this.paper.text( lampCenter, redCenter, value ).attr("fill","#3F0000").attr("font-size", lampFontSize);
			this.paper.rect( alertCenter - outline, redCenter - outline, 
							outline * 2, outline * 2 ).attr({"fill":this.plotColor, "stroke-width":0});
			this.paper.text( alertCenter , redCenter, "Out\nof\nSpec!" ).attr("fill","#3F0000").attr("font-size", fontSize);			
		} else if (state == "YELLOW") {
			this.paper.circle( lampCenter, redCenter, radius ).attr("fill","#3F0000");
			this.paper.circle( lampCenter, yellowCenter, radius ).attr("fill","#FFFF00");
			this.paper.circle( lampCenter, yellowCenter, radius + 3 ).attr("fill","none");
			this.paper.circle( lampCenter, greenCenter, radius ).attr("fill","#003F00");
			this.paper.text( lampCenter , yellowCenter, value ).attr("fill","#3F3F00").attr("font-size", lampFontSize);
			this.paper.rect( alertCenter - outline, yellowCenter - outline, 
							outline * 2, outline * 2 ).attr({"fill":this.plotColor, "stroke-width":0});
			this.paper.text( alertCenter , yellowCenter, "Out\nof\nControl" ).attr("fill","#3F3F00").attr("font-size", fontSize);			
		} else if (state == "GREEN") {
			this.paper.circle( lampCenter, redCenter, radius ).attr("fill","#3F0000");
			this.paper.circle( lampCenter, yellowCenter, radius ).attr("fill","#3F3F00");
			this.paper.circle( lampCenter, greenCenter, radius ).attr("fill","#00FF00");
			this.paper.circle( lampCenter, greenCenter, radius + 3 ).attr("fill","none");
			this.paper.text( lampCenter , greenCenter, value ).attr("fill","#003F00").attr("font-size", lampFontSize);
			this.paper.rect( alertCenter - outline, greenCenter - outline, 
							outline * 2, outline * 2 ).attr({"fill":this.plotColor, "stroke-width":0});
			this.paper.text( alertCenter , greenCenter, "All\nOK" ).attr("fill","#003F00").attr("font-size", fontSize);			
		} else {
			this.paper.text( iCenterX , greenCenter, value ).attr("fill","#FFFFFF").attr("font-size", lampFontSize);			
		}
		
	}
	
	//------------------------------------------------
	
	this.toRect = function( degrees, r) {
		
		if (degrees < 0) {
			degrees = 360 + degrees;
		} else if (degrees > 359.999) {
			degrees -= 360.0;
		}
				
		var theta = 3.141592654 * degrees / 180.0;
		
		var cx = Math.floor( Math.sin( theta ) * r ) + this.iCenterX;
		var cy = this.iCenterY - Math.floor( Math.cos( theta ) * r );
		
		var pt = new Point(cx, cy);
		
		return pt;
	}
	
	//------------------------------------------------
	
	this.drawCompassPoint = function( bearing, r, label, color, size ) {
		
		var p = this.toRect(bearing, r);
		
		this.paper.text(p.x , p.y, label).attr({"fill":color,"font-size":size});
	}
	
	//------------------------------------------------
	
	this.plotWindVane = function( label, current ) {
		
		var w = this.prefSize.X();
		var h = this.prefSize.Y();
		
		var yMargin = (this.yMaxM - this.yMinM) / 20.0;
		this.yMaxM = this.yMaxM + yMargin;
		this.yMinM = this.yMinM - yMargin;		
		
		var value = current.Y();
		if (value > 360) {
			value = value - 360;
		} else if (value < this.yMinM) {
			value = this.yMinM;
		}
		
		var dimension = Math.min( w, h );
		
		var dial = dimension / 2.2;
		
		var tick = dimension / 50;
		
		this.iCenterX = w / 2;
		this.iCenterY = h / 2 - tick;
		
		var outerRange = dial * 18 / 20;
		var innerRange = dial * 12 / 20;
		
		var bandCenter = (innerRange + outerRange) / 2;
		var dialBand = (outerRange - innerRange) * 7 / 10;
		
		var dialWidth = dimension / 75;
		var tickWidth = dimension / 125;
		
		var innerTick = innerRange - tick;
		var outerTick = outerRange + tick;
		var tickR = (innerRange + outerRange) / 2;
		
		var needleLength = dial * 13 / 20;
		
		var fontSize = dimension / 12.5;
		var labelY = this.iCenterY - fontSize;
		var valueY = this.iCenterY + 2 * fontSize;
		
		this.paper.circle( this.iCenterX, this.iCenterY, dial + 2 ).attr({"stroke":"#BBCCBB","stroke-width":3});
		this.paper.circle( this.iCenterX, this.iCenterY, dial ).attr("fill","#002A22");
		
		this.paper.circle( this.iCenterX, this.iCenterY, outerRange ).attr({"stroke":"#FFFFFF", "stroke-width":dialWidth});
		this.paper.circle( this.iCenterX, this.iCenterY, innerRange ).attr({"stroke":"#FFFFFF", "stroke-width":dialWidth});
				
		this.drawCompassPoint(0.0, tickR, 'N', "#FFFFFF", fontSize);
		this.drawCompassPoint(45.0, tickR, '•', "#FFFFFF", fontSize);
		this.drawCompassPoint(90.0, tickR, 'E', "#FFFFFF", fontSize);
		this.drawCompassPoint(135.0, tickR, '•', "#FFFFFF", fontSize);
		this.drawCompassPoint(180.0, tickR, 'S', "#FFFFFF", fontSize);
		this.drawCompassPoint(225.0, tickR, '•', "#FFFFFF", fontSize);
		this.drawCompassPoint(270.0, tickR, 'W', "#FFFFFF", fontSize);
		this.drawCompassPoint(315.0, tickR, '•', "#FFFFFF", fontSize);
		
		this.paper.text( this.iCenterX , labelY, label ).attr({"fill":"#FFFFFF","font-size":fontSize});
		this.paper.text( this.iCenterX , valueY, current.Y().toFixed(1)+"°" ).attr({"fill":"#FFFFFF","font-size":fontSize});
		
		var tip = this.toRect(value, needleLength);
		var lTail = this.toRect(value - 175, needleLength);
		var rTail = this.toRect(value - 185, needleLength);
		
		this.paper.path("M "+tip.x+" "+tip.y+" L "+lTail.x+" "+lTail.y+" L "+rTail.x+" "+rTail.y+" L "+tip.x+" "+tip.y).attr({stroke:"#FF0000", "fill":"#FF0000"});

		this.paper.circle( this.iCenterX, this.iCenterY, dial / 10 ).attr("fill","#333333");
	}
	
	
	//----------------------------------------------------------------------//
}

