/*----------------------------------------------------------------------------\
|                                  Chart 1.0                                  |
|                            Canvas Chart Painter                             |
|-----------------------------------------------------------------------------|
|                          Created by Emil A Eklund                           |
|                        (http://eae.net/contact/emil)                        |
|-----------------------------------------------------------------------------|
| Canvas implementation of the chart painter API. A canvas element is used to |
| draw the chart,  html elements are used for the legend and  axis labels as, |
| at the time being, there is no text support in canvas.                      |
|-----------------------------------------------------------------------------|
|                      Copyright (c) 2006 Emil A Eklund                       |
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
| This program is  free software;  you can redistribute  it and/or  modify it |
| under the terms of the MIT License.                                         |
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
| Permission  is hereby granted,  free of charge, to  any person  obtaining a |
| copy of this software and associated documentation files (the "Software"),  |
| to deal in the  Software without restriction,  including without limitation |
| the  rights to use, copy, modify,  merge, publish, distribute,  sublicense, |
| and/or  sell copies  of the  Software, and to  permit persons to  whom  the |
| Software is  furnished  to do  so, subject  to  the  following  conditions: |
| The above copyright notice and this  permission notice shall be included in |
| all copies or substantial portions of the Software.                         |
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
| THE SOFTWARE IS PROVIDED "AS IS",  WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| IMPLIED,  INCLUDING BUT NOT LIMITED TO  THE WARRANTIES  OF MERCHANTABILITY, |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| AUTHORS OR  COPYRIGHT  HOLDERS BE  LIABLE FOR  ANY CLAIM,  DAMAGES OR OTHER |
| LIABILITY, WHETHER  IN AN  ACTION OF CONTRACT, TORT OR  OTHERWISE,  ARISING |
| FROM,  OUT OF OR  IN  CONNECTION  WITH  THE  SOFTWARE OR THE  USE OR  OTHER |
| DEALINGS IN THE SOFTWARE.                                                   |
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
|                         http://eae.net/license/mit                          |
|-----------------------------------------------------------------------------|
| 2006-01-03 | Work started.                                                  |
| 2006-01-05 | Added legend and axis labels. Changed the painter api slightly |
|            | to allow two-stage initialization (required for ie/canvas) and |
|            | added legend/axis related methods. Also updated bar chart type |
|            | and added a few options, mostly related to bar charts.         |
| 2006-01-07 | Updated chart size calculations to take legend and axis labels |
|            | into consideration.  Split painter implementations to separate |
|            | files.                                                         |
| 2006-04-16 | Updated to use the  ExplorerCanvas ie emulation  layer instead |
|            | of the, now deprecated, IECanvas one.                          |
|-----------------------------------------------------------------------------|
| Created 2006-01-03 | All changes are in the log above. | Updated 2006-04-16 |
\----------------------------------------------------------------------------*/

function CanvasChartPainterFactory() {
	return new CanvasChartPainter();
}


function CanvasChartPainter() {
	this.base = AbstractChartPainter;
}


CanvasChartPainter.prototype = new AbstractChartPainter;

CanvasChartPainter.prototype.create = function(el) {
	while (el.firstChild) { el.removeChild(el.lastChild); }

	this.el = el;
	this.w = el.clientWidth;
	this.h = el.clientHeight;

	this.canvas = document.createElement('canvas');
	this.canvas.width  = this.w;
	this.canvas.height = this.h;
	this.canvas.style.width  = this.w + 'px';
	this.canvas.style.height = this.h + 'px';

	el.appendChild(this.canvas);
	
	/* Init explorercanvas emulation for IE */
	if ((!this.canvas.getContext) && (typeof G_vmlCanvasManager != "undefined")) {
		this.canvas = G_vmlCanvasManager.initElement(this.canvas);
	}
};


CanvasChartPainter.prototype.init = function(xlen, ymin, ymax, xgd, ygd, bLegendLabels) {
	this.ctx = this.canvas.getContext('2d');

	this.chartx = 0;
	this.chartw	= this.w;
	this.charth	= this.h;
	this.charty = 0;
	
	this.xlen = xlen;
	this.ymin = ymin;
	this.ymax = ymax;
	this.xgd  = xgd;
	this.ygd  = ygd;

	this.calc(this.chartw, this.charth, xlen, ymin, ymax, xgd, ygd);
};

CanvasChartPainter.prototype.drawLegend=function(series){ //Don't add spaces on this line. IE8 breaks?
	var legend, list, item, label, secondAxisLabel;

	var makeimage = function(series, i, serieson, colourstring) //This creates the image fort the legend
	{
		item = document.createElement("li");
		item.style.color = '#' + colourstring;//series[i].color;
		//item.id = "chart" + colourstring;
		colorblock = document.createElement("img");
		if(serieson)
		{
		colorblock.src = "img/dot_" + colourstring + ".gif";
		}
		else
		{
		colorblock.src = "img/dot_" + colourstring + "off.gif";
		}
		colorblock.id = "chart" + colourstring;
		//Added in, total hack to id image for next chart draw - MV
		
		
		item.appendChild(colorblock);
		item.appendChild(document.createTextNode(" " + series[i].label));
		list.appendChild(item);
		
	}

	secondAxisLabel = document.createElement("div");
	secondAxisLabel.className="secondAxisLabel";
	secondAxisLabel.style.position = "absolute";


//	label = document.createElement('p');
//	label.appendChild(document.createTextNode('Watts, % Efficiency'));
//	label.style.color = 'black';
//	label.style.left = '20px';
//	label.style.top = '0px';
//	secondAxisLabel.appendChild(label);

	
	legend = document.createElement("div");
	legend.className = "legend";
	legend.style.position = "absolute";
	list = document.createElement("ul");	

//I'm not entirely sure how to make this more portable... Unfortunately this functionality gets way too specific - MV
// Adding functionality to turn legend dot images into radio buttons!

/*
torque1num = findchart(series, torqueLabel);
power1num = findchart(series, "Power A");
eff1num = findchart(series, "Efficiency A");
if(document.getElementById('gradeloadflag').getElementsByTagName('input')[1].checked)
{
	gradeload1num = findchart(series, "Grade A");
}
else
{
	gradeload1num = findchart(series, "Load A");
}
if(comparisonchart)
{
	torque2num = findchart(series, torqueLabel2);
	power2num = findchart(series, "Power B");
	eff2num = findchart(series, "Efficiency B");

	if(document.getElementById('gradeloadflag').getElementsByTagName('input')[1].checked)
	{
		gradeload2num = findchart(series, "Grade B");
	}
	else
	{
		gradeload2num = findchart(series, "Load B");
	}
}
*/
makeimage(series, torque1num, drawtorqueA, '0000FF');
makeimage(series, power1num, drawpowerA, 'FF0000');
makeimage(series, gradeload1num, drawgradeloadA, '000000');
makeimage(series, eff1num, drawefficiencyA, '008000');
if(comparisonchart)
{
item = document.createElement("br");
list.appendChild(item);
makeimage(series, torque2num, drawtorqueB, '6F6FFF');
makeimage(series, power2num, drawpowerB, 'FF6F6F');
makeimage(series, gradeload2num, drawgradeloadB, '888888');
makeimage(series, eff2num, drawefficiencyB, '6FFF6F');
}

	legend.appendChild(list);
	var lh = document.getElementById("legendHolder");
	if (lh.getElementsByTagName("div")[0]) 
	    lh.removeChild(lh.getElementsByTagName("div")[0]);
	lh.appendChild(legend);
	legend.style.top = "0px";
	// legend.style.top  = this.charty + (this.charth / 2) - (legend.offsetHeight / 2) + 'px';
	this.legend = legend;
	

	// Recalculate chart width and position based on labels and legend 
	this.el.appendChild(secondAxisLabel);
	secondAxisLabel.style.top = this.charty + (this.charth / 2) - legend.offsetHeight - 100 + 'px';
	secondAxisLabel.style.right = '0px';
	this.chartw	= this.w - 130;// - (this.legend.offsetWidth + 5);
	this.calc(this.chartw, this.charth, this.xlen, this.ymin, this.ymax, this.xgd, this.ygd);
	chartdrawdone = true;
	
};

CanvasChartPainter.prototype.drawVerticalAxis = function(ygd, precision, series) {
	var axis, item, step, y, ty, n, yoffset, value, multiplier, w, items, pos;

	/* Calculate step size and rounding precision */
	// It's possible that we should keep the precision higher during calculations and round it right as the text node is being created.
	multiplier = Math.pow(10, precision);
	step       = series.range / (ygd - 1);

	/* Create container */
	axis = document.createElement('div');
	axis.style.position = 'absolute';
	axis.style.left  = '0px';
	axis.style.top   = '0px';
	axis.style.textAlign = 'right';
	this.el.appendChild(axis);

	/* Draw labels and points */
	this.ctx.fillStyle = 'black';
	w = 0;
	items = new Array();
	for (n = 0, i = series.ymax; (i > series.ymin) && (n < ygd - 1); i -= step, n++) {
		item = document.createElement('span');
		item.setAttribute("style", "color:blue");
		value = parseInt(i * multiplier) / multiplier;
		item.appendChild(document.createTextNode(value.toFixed(precision)));
		axis.appendChild(item);
		items.push([i, item]);
		if (item.offsetWidth > w) { w = item.offsetWidth; }
	}

	/* Draw last label and point (lower left corner of chart) */
	item = document.createElement('span');
	item.setAttribute("style", "color:blue");
	item.appendChild(document.createTextNode(series.ymin.toFixed(precision)));
	axis.appendChild(item);
	items.push([series.ymin, item]);
	if (item.offsetWidth > w) { w = item.offsetWidth; }
	
	/* Set width of container to width of widest label */
	axis.style.width = w + 'px';
	
	/* Recalculate chart width and position based on labels and legend */
	this.chartx = w + 5;
	this.charty = item.offsetHeight / 2;
	this.charth = this.h - ((item.offsetHeight * 1.5) + 5);
	this.chartw	= this.w - 130;// - (((this.legend)?this.legend.offsetWidth:0) + w + 10);
//	alert("drawing first vertical axis: " + this.chartw);
	this.calc(this.chartw, this.charth, this.xlen, this.ymin, this.ymax, this.xgd, this.ygd);
	
	/* Position labels on the axis */
	n          = series.range / this.charth;
	yoffset    = (series.ymin / n);
	for (i = 0; i < items.length; i++) {
		item = items[i][1];
		pos = items[i][0];
		if (pos == series.ymin) { y = this.charty + this.charth - 1; }
		else { y = this.charty + (this.charth - (pos / n) + yoffset); }
		this.ctx.fillRect(this.chartx - 5, y, 5, 1);
		ty = y - (item.offsetHeight/2);
		item.style.position = 'absolute';
		item.style.right = '0px';
		item.style.top   = ty + 'px';
	}	
};

CanvasChartPainter.prototype.drawOtherVerticalAxis = function(ygd, precision, series) {
	var axis, item, step, y, ty, n, yoffset, value, multiplier, w, items, pos;

	/* Calculate step size and rounding precision */
	// It's possible that we should keep the precision higher during calculations and round it right as the text node is being created.
	multiplier = Math.pow(10, precision);
	step       = series.range / (ygd - 1);

	w = 0;
	/* Create container */
	axis = document.createElement('div');
	axis.setAttribute('id', 'powerunits');	
	axis.style.position = 'absolute';
	// This should be width minus legend minus width of labels.
	// ZEV - HACK HACK HACK
	axis.style.top   = '0px';
	axis.style.textAlign = 'left';
	this.el.appendChild(axis);

	/* Draw labels and points */
	this.ctx.fillStyle = 'black';
	items = new Array();
	for (n = 0, i = series.ymax; (i > series.ymin) && (n < ygd - 1); i -= step, n++) {
		item = document.createElement('span');
		item.setAttribute("style", "color:red");
		value = parseInt(i * multiplier) / multiplier;
		item.appendChild(document.createTextNode(value.toFixed(precision)));
		axis.appendChild(item);
		items.push([i, item]);
		if (item.offsetWidth > w) { w = item.offsetWidth; }
	}
	axis.style.left  = (this.chartw - 40) + 'px'; // guess

	/* Draw last label and point (lower right corner of chart) */
	item = document.createElement('span');
	item.setAttribute("style", "color:red");
	item.appendChild(document.createTextNode(series.ymin.toFixed(precision)));
	axis.appendChild(item);
	items.push([series.ymin, item]);
	if (item.offsetWidth > w) { w = item.offsetWidth; }
	
	/* Set width of container to width of widest label */
	axis.style.width = w + 'px';
	
	/* Recalculate chart width and position based on labels and legend */
//	this.chartx = w - 5;
//	this.charty = item.offsetHeight / 2;
//	this.charth = this.h - ((item.offsetHeight * 1.5) + 5);
//	alert("drawing other vertical axis: " + this.chartw);
	this.chartw	= this.w - w - 152;
//	if (this.legend)
//	{
		this.chartw = this.chartw;// - this.legend.offsetWidth;
//	}
//	alert("but now: " + this.chartw);
	this.chartw = this.chartw - w;
//	alert("finally: " + this.chartw);
	this.calc(this.chartw, this.charth, this.xlen, this.ymin, this.ymax, this.xgd, this.ygd);
	
	/* Position labels on the axis */
	n          = series.range / this.charth;
	yoffset    = (series.ymin / n);
	this.ctx.fillStyle = 'black';
	for (i = 0; i < items.length; i++) {
		item = items[i][1];
		pos = items[i][0];
		if (pos == series.ymin) { y = this.charty + this.charth - 1; }
		else { y = this.charty + (this.charth - (pos / n) + yoffset); }
		this.ctx.fillRect(this.chartw + this.chartx, y, 5, 1);
		ty = y - (item.offsetHeight/2);
		item.style.position = 'absolute';
		item.style.right = '0px';
		item.style.top   = ty + 'px';
	}	
};

CanvasChartPainter.prototype.drawthirdVerticalAxis = function(ygd, precision, series) {
	var axis, item, step, y, ty, n, yoffset, value, multiplier, w, items, pos;

	/* Calculate step size and rounding precision */
	// It's possible that we should keep the precision higher during calculations and round it right as the text node is being created.
	multiplier = Math.pow(10, precision);
	step       = series.range*100 / (ygd - 1);

	w = 0;
	/* Create container */
	axis = document.createElement('div');
	axis.style.position = 'absolute';
	// This should be width minus legend minus width of labels.
	// ZEV - HACK HACK HACK
	axis.style.top   = '0px';
	axis.style.textAlign = 'left';
	this.el.appendChild(axis);

	/* Draw labels and points */
	this.ctx.fillStyle = 'black';
	items = new Array();
	for (n = 0, i = series.ymax*100; (i > series.ymin*100) && (n < ygd - 1); i -= step, n++) {
		item = document.createElement('span');
		item.setAttribute("style", "color:green");
		value = parseInt(i * multiplier) / multiplier;
		item.appendChild(document.createTextNode(value.toFixed(precision)));
		axis.appendChild(item);
		items.push([i, item]);
		if (item.offsetWidth > w) { w = item.offsetWidth; }
	}
	//axis.style.left  = (this.chartw+20) + 'px'; // guess
	axis.style.left = ($('#powerunits').position().left + 40) + 'px';
	/* Draw last label and point (lower right corner of chart) */
	item = document.createElement('span');
	item.setAttribute("style", "color:green");
	item.appendChild(document.createTextNode((series.ymin*100).toFixed(precision)));
	axis.appendChild(item);
	items.push([series.ymin*100, item]);
	if (item.offsetWidth > w) { w = item.offsetWidth; }
	
	/* Set width of container to width of widest label */
	axis.style.width = w + 'px';
	
	/* Recalculate chart width and position based on labels and legend */
//	this.chartx = w - 5;
//	this.charty = item.offsetHeight / 2;
//	this.charth = this.h - ((item.offsetHeight * 1.5) + 5);
//	alert("drawing other vertical axis: " + this.chartw);
	this.chartw	= this.w - w - 165;
//	if (this.legend)
//	{
		this.chartw = this.chartw;// - this.legend.offsetWidth;
//	}
//	alert("but now: " + this.chartw);
	this.chartw = this.chartw - w;
//	alert("finally: " + this.chartw);
	this.calc(this.chartw, this.charth, this.xlen, this.ymin, this.ymax, this.xgd, this.ygd);
	
	/* Position labels on the axis */
	n          = series.range*100 / this.charth;
	yoffset    = (series.ymin*100 / n);
	this.ctx.fillStyle = 'black';
	for (i = 0; i < items.length; i++) {
		item = items[i][1];
		pos = items[i][0];
		if (pos == series.ymin*100) { y = this.charty + this.charth - 1; }
		else { y = this.charty + (this.charth - (pos / n) + yoffset); }
		//this.ctx.fillRect(this.chartw + this.chartx, y, 5, 1);
		ty = y - (item.offsetHeight/2);
		item.style.position = 'absolute';
		item.style.right = '0px';
		item.style.top   = ty + 'px';
	}	
};

CanvasChartPainter.prototype.drawHorizontalAxis = function(xlen, labels, xgd, precision) {
	var axis, item, step, x, tx, n, multiplier;
	
	/* Calculate offset, step size and rounding precision */
	multiplier = Math.pow(10, precision);
	var maxX = labels[NUMBER_OF_POINTS - 1];
		 maxX = Math.round(maxX);
		 
	//document.getElementById('speedUnitFlag').getElementsByTagName('input')[2].checked ? xgd = parseInt((maxX / 100) + 1) : xgd = Math.floor(maxX / 5) + 1;
	if($("#speedUnitFlag").val() == "rpm") { xgd = parseInt((maxX / 100) + 1); }
	else { xgd = Math.floor(maxX / 5) + 1 }

	newLabels = new Array(xgd + 1);
	for (i = 0; i < xgd + 1; i++)
	{
		newLabels[i] = (maxX * (i / (xgd - 1) ));
	}
	n = this.chartw / (xgd - 1);
	
	/* Create container */
	axis = document.createElement('div');
	axis.style.position = 'absolute';
	axis.style.left   = '0px';
	axis.style.top    = (this.charty + this.charth + 5) + 'px';
	axis.style.width  = this.w + 'px';
	this.el.appendChild(axis);

	/* Draw labels and points */
	this.ctx.fillStyle = 'black';
	for (i = 0; i < xgd; i++) {
		item = document.createElement('span');
		// ZEV EDIT: on the next line, the precision is the same for horizontal and vertical.
		// This could be fixed to make them independent.
		item.appendChild(document.createTextNode(newLabels[i].toFixed(precision)));
		axis.appendChild(item);
		x = this.chartx + (n * i);
		tx = x - (item.offsetWidth/2)
		item.style.position = 'absolute';
		item.style.left = tx + 'px';
		item.style.top  = '0px';
		this.ctx.fillRect(x, this.charty + this.charth, 1, 5);
	}	
	// recalculate with new xgd
	this.calc(this.chartw, this.charth, xlen, this.ymin, this.ymax, xgd, this.ygd);
};


CanvasChartPainter.prototype.drawAxes = function(doubleAxis) {
	// We could put the zero line here, but then it would be drawn over. Maybe this is no big deal. Or, the anti-aliasing might be modified to alpha-blend...
	this.ctx.fillStyle = 'black';
	this.ctx.fillRect(this.chartx, this.charty, 1, this.charth);
	this.ctx.fillRect(this.chartx, this.charty + this.charth - 1, this.chartw+1, 1);
	if (doubleAxis) {	
		this.ctx.fillRect(this.chartw+this.chartx, this.charty, 1, this.charth);
	}
//	(x, y, length, height)
};


CanvasChartPainter.prototype.drawBackground = function() {
	this.ctx.fillStyle = 'white';
	this.ctx.fillRect(0, 0, this.w, this.h);
};


CanvasChartPainter.prototype.drawChart = function(series, ygd) {
	step = series.range / (ygd - 1);
	this.ctx.fillStyle = 'silver';
	if (this.xgrid) {
		for (i = this.xgrid; i < this.chartw; i += this.xgrid) {
			this.ctx.fillRect(this.chartx + i, this.charty, 1, this.charth-1);
		}	
	}
	if (this.ygrid) {
		for (i = this.charth - this.ygrid, curVal = (series.ymin + step); i > 0; i -= this.ygrid, curVal += step) {
			if (curVal == 0)
			{
//				alert(series.ymax + " " + step + " " + curVal);
				this.ctx.fillStyle = 'black';
				this.ctx.fillRect(this.chartx + 1, this.charty + i, this.chartw, 1);
				this.ctx.fillStyle = 'silver';
			}
			this.ctx.fillRect(this.chartx + 1, this.charty + i, this.chartw, 1);
		}	
	}	
};


CanvasChartPainter.prototype.drawArea = function(color, values) {
	var i, len, x, y, n, yoffset;

	/* Determine distance between points and offset */
	n = this.range / this.charth;
	yoffset = (this.ymin / n);

	len = values.length;
	if (len) {
		this.ctx.fillStyle = color;

		/* Begin line in lower left corner */
		x = this.chartx + 1;
		this.ctx.beginPath();
		this.ctx.moveTo(x, this.charty + this.charth - 1);

		/* Determine position of first point and draw it */
		y = this.charty + this.charth - (values[0] / n) + yoffset;
		this.ctx.lineTo(x, y);

		/* Draw lines to succeeding points */
		for (i = 1; i < len; i++) {
			y = this.charty + this.charth - (values[i] / n) + yoffset;
			x += this.xstep;
			this.ctx.lineTo(x, y);
		}

		/* Close path and fill it */
		this.ctx.lineTo(x, this.charty + this.charth - 1);
		this.ctx.closePath();
		this.ctx.fill();
}	};


CanvasChartPainter.prototype.drawLine = function(series) {
	
	// i: loop counter, len: number of samples
	var i, len, x, y, n, yoffset;
	var color = series.color;
	var values = series.values;

	/* Determine distance between points and offset */
	n = series.range / this.charth;
	if(n == 0) n = 1;
	yoffset = (series.ymin / n);

	len = values.length;
	if (len) {
		this.ctx.lineWidth   = 1;
		this.ctx.strokeStyle = color;

		/* Determine position of first point and draw it */
		x = this.chartx + 1;
/*alert(this.charty);
alert(this.charth);
alert(n);
alert(yoffset);
alert(values[0]);	*/	
		y = this.charty + this.charth - (values[0] / n) + yoffset;

		
		this.ctx.beginPath();
		
		this.ctx.moveTo(x, y);

		/* Draw lines to succeeding points */
		for (i = 1; i < len; i++) {
			y = this.charty + this.charth - (values[i] / n) + yoffset;
			x += this.xstep;
			this.ctx.lineTo(x, y);
		}

		/* Stroke path */
		this.ctx.stroke();
}	};


CanvasChartPainter.prototype.drawBars = function(color, values, xlen, xoffset, width) {
	var i, len, x, y, n, yoffset;

	/* Determine distance between points and offset */
	n = this.range / this.charth;
	yoffset = (this.ymin / n);

	len = values.length;
	if (len > xlen) { len = xlen; }
	if (len) {
		this.ctx.fillStyle = color;

		/* Determine position of each bar and draw it */
		x = this.chartx + xoffset + 1;
		for (i = 0; i < len; i++) {
			y = this.charty + this.charth - (values[i] / n) + yoffset;

			this.ctx.beginPath();
			this.ctx.moveTo(x, this.charty + this.charth-1);
			this.ctx.lineTo(x, y );
			this.ctx.lineTo(x+width, y);
			this.ctx.lineTo(x+width, this.charty + this.charth-1);
			this.ctx.closePath();
			this.ctx.fill();

			x += this.xstep;
		}	
	}	
};

