YAHOO.namespace('Smb.Graph');

YAHOO.Smb.Graph = function(elementId, attr, seriesInfo, dataSource) {
        
	// Ensure that the needed versions of Dom and Event are available.

	var domInfo = YAHOO.env.getVersion('dom');
	var eventInfo = YAHOO.env.getVersion('event');

	if(typeof YAHOO.env.ua !== 'object') {
		throw new Error("YAHOO.Smb.Graph: can't find YAHOO.env.ua. Include version 2.3.0 or above of the global YAHOO object.");
	}
	if(YAHOO.lang.isNull(domInfo) || !YAHOO.lang.isObject(domInfo) || parseInt(domInfo.build, 10) < YAHOO.Smb.Graph.reqBuild) {
		throw new Error("YAHOO.Smb.Graph: can't find needed version of YAHOO.util.Dom. Include version 2.2.0 or above.");
	}
	if(YAHOO.lang.isNull(eventInfo) || !YAHOO.lang.isObject(eventInfo) || parseInt(eventInfo.build, 10) < YAHOO.Smb.Graph.reqBuild) {
		throw new Error("YAHOO.Smb.Graph: can't find needed version of YAHOO.util.Event. Include version 2.2.0 or above.");
	}

	// Ensure that the arguments to the constructor are of the correct type.

	if(typeof elementId !== 'object' && typeof elementId !== 'string') {
		throw new Error("YAHOO.Smb.Graph: the first argument to the constructor must be a string corresponding to an element ID or a reference to an element.");
	}
	var useDataSource = (typeof seriesInfo === 'object' && typeof seriesInfo.length === 'number') && (typeof dataSource === 'object' && typeof dataSource.sendRequest === 'function');

	// Initialize arrays.

	this.data = [];
	this.graphColor = [];
	this.dataMarkers = [];
	this.enableData = [];
	this.diffScale = [];
	this.axisRight = [];

	// Browser sniffing (Safari 2.0.4 doesn't respond to having height/width set on the canvas element).
	
	this.browserDetect();

	// Create the canvas element if it doesn't exist in the document. 

	if(YAHOO.util.Dom.inDocument(elementId)) {
		this.canvas = YAHOO.util.Dom.get(elementId);
	}
	else {
		this.canvas = document.createElement('canvas');
		this.canvas.id = elementId;
		document.getElementsByTagName('body')[0].appendChild(this.canvas);
		if(typeof G_vmlCanvasManager !== 'undefined') { // IE only.
			this.queueProperty('width', attr.width); // Set the dimensions before the behavior is applied.        
			this.queueProperty('height', attr.height);
			G_vmlCanvasManager.initElement(this.canvas); // Apply the behavior with excanvas.js.
			this.canvas = YAHOO.util.Dom.get(elementId); // Get a new reference to the canvas.
		}
	}
	YAHOO.util.Dom.addClass(this.canvas, YAHOO.Smb.Graph.classNames.prefix + YAHOO.Smb.Graph.classNames.canvas);
	this.ctx = this.canvas.getContext('2d');

        // Configuration settings.
        
	attr = attr || {};

	this.queueProperty('width', attr.width);
	this.queueProperty('height', attr.height);
	this.queueProperty('paddingTop', attr.paddingTop);
	this.queueProperty('paddingRight', attr.paddingRight);
	this.queueProperty('paddingBottom', attr.paddingBottom);
	this.queueProperty('paddingLeft', attr.paddingLeft);
	this.queueProperty('showXLabels', attr.showXLabels);
	this.queueProperty('showYLabels', attr.showYLabels);
	this.queueProperty('showBarLabels', attr.showBarLabels);
	this.queueProperty('labelFont', attr.labelFont);
	this.queueProperty('labelFontScale', attr.labelFontScale);
	this.queueProperty('xLabelColor', attr.xLabelColor);
	this.queueProperty('yLabelColor', attr.yLabelColor);
	this.queueProperty('yRightLabelColor', attr.yRightLabelColor);
	this.queueProperty('barLabelColor', attr.barLabelColor);
	this.queueProperty('xLabelPre', attr.xLabelPre);
	this.queueProperty('xLabelPost', attr.xLabelPost);
	this.queueProperty('xLabelFunction', attr.xLabelFunction);
	this.queueProperty('yLabelPre', attr.yLabelPre);
	this.queueProperty('yLabelPost', attr.yLabelPost);
	this.queueProperty('yLabelFunction', attr.yLabelFunction);
	this.queueProperty('yRightLabelPre', attr.yRightLabelPre);
	this.queueProperty('yRightLabelPost', attr.yRightLabelPost);
	this.queueProperty('barLabelPre', attr.batLabelPre);
	this.queueProperty('barLabelPost', attr.batLabelPost);
	this.queueProperty('xLabelSpacing', attr.xLabelSpacing);
	this.queueProperty('yLabelSpacing', attr.yLabelSpacing);
	this.queueProperty('typeLib', attr.typeLib);
	this.queueProperty('background', attr.background);
	this.queueProperty('graphBackground', attr.graphBackground);
	this.queueProperty('xGridColor', attr.xGridColor);
	this.queueProperty('yGridColor', attr.yGridColor);
	this.queueProperty('xGridSpacing', attr.xGridSpacing);
	this.queueProperty('yGridSpacing', attr.yGridSpacing);
	this.queueProperty('xAxisColor', attr.xAxisColor);
	this.queueProperty('yAxisColor', attr.yAxisColor);
	this.queueProperty('hideXAxis', attr.hideXAxis);
	this.queueProperty('hideYAxis', attr.hideYAxis);
	this.queueProperty('enableHoverInfo', attr.enableHoverInfo);
	this.queueProperty('hoverInfoFunction', attr.hoverInfoFunction);
	this.queueProperty('dontForceZero', attr.dontForceZero);
	this.queueProperty('type', attr.type);
	this.queueProperty('start', attr.start);
	this.queueProperty('end', attr.end);
	this.queueProperty('yScale', attr.yScale);
	this.queueProperty('xKey', attr.xKey);
	this.queueProperty('xLabel', attr.xLabel);

	// Create the custom events.

	this.mousemove = new YAHOO.util.CustomEvent('mousemove', this, false, YAHOO.util.CustomEvent.FLAT);
	this.mouseover = new YAHOO.util.CustomEvent('mouseover', this, false, YAHOO.util.CustomEvent.FLAT);
	this.mouseout = new YAHOO.util.CustomEvent('mouseout', this, false, YAHOO.util.CustomEvent.FLAT);
	this.dataLoaded = new YAHOO.util.CustomEvent('dataLoaded', this, false, YAHOO.util.CustomEvent.FLAT);

	// Create the hoverInfo.

	this.hoverInfo = document.createElement('div');
	this.hoverInfo.id = elementId + '-hover-info';
	this.hoverInfoText = document.createElement('div');
	this.canvas.parentNode.appendChild(this.hoverInfo);
	this.hoverInfo.appendChild(this.hoverInfoText);

	YAHOO.util.Dom.addClass(this.hoverInfo, YAHOO.Smb.Graph.classNames.prefix + YAHOO.Smb.Graph.classNames.hoverInfo);
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'position', 'absolute');
	var parentPos = YAHOO.util.Dom.getStyle(this.canvas.parentNode, 'position');
	this.parentStatic = (parentPos !== 'relative' && parentPos !== 'fixed');
	this.parentPosition = (!this.parentStatic) ?
		{ x: YAHOO.util.Dom.getX(this.canvas.parentNode), y: YAHOO.util.Dom.getY(this.canvas.parentNode) } :
		{ x: 0, y: 0 };
	this.canvasPosition = { 
		x: YAHOO.util.Dom.getX(this.canvas) - this.parentPosition.x, 
		y: YAHOO.util.Dom.getY(this.canvas) - this.parentPosition.y 
	};
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'top', (this.canvasPosition.y + this.paddingTop) + 'px');
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'left', this.canvasPosition.x + 'px');
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'width', '1px');    
	if(this.type === 'pie') {
		YAHOO.util.Dom.setStyle(this.hoverInfo, 'height', '1px');                
	}
	else {
		YAHOO.util.Dom.setStyle(this.hoverInfo, 'height', (this.height - this.paddingBottom - this.paddingTop) + 'px');
		YAHOO.util.Dom.setStyle(this.hoverInfo, 'background', '#666');
	}
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'z-index', 100);
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'overflow', 'visible');
	YAHOO.util.Dom.setStyle(this.hoverInfo, 'display', 'none');

	YAHOO.util.Dom.addClass(this.hoverInfoText, YAHOO.Smb.Graph.classNames.prefix + YAHOO.Smb.Graph.classNames.hoverInfoText);
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'position', 'absolute');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'top', '10px');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'left', '10px');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'width', 'auto');    
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'height', 'auto');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'padding', '.2em .4em .3em');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'background', '#fff');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'border', '1px solid #888');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'opacity', '0.85');
	YAHOO.util.Dom.setStyle(this.hoverInfoText, 'color', '#000');   

	// Create and position the canvasProxy.

	this.canvasProxy = document.createElement('div');
	this.canvasProxy.id = elementId + '-proxy';
	this.canvas.parentNode.appendChild(this.canvasProxy);
	YAHOO.util.Dom.addClass(this.canvasProxy, YAHOO.Smb.Graph.classNames.prefix + YAHOO.Smb.Graph.classNames.proxy);
	YAHOO.util.Dom.setStyle(this.canvasProxy, 'width', this.width + 'px');
	YAHOO.util.Dom.setStyle(this.canvasProxy, 'height', this.height + 'px');
	YAHOO.util.Dom.setStyle(this.canvasProxy, 'z-index', 200);
	YAHOO.util.Dom.setStyle(this.canvasProxy, 'position', 'absolute');
	YAHOO.util.Dom.setStyle(this.canvasProxy, 'top', this.canvasPosition.y + 'px');
	YAHOO.util.Dom.setStyle(this.canvasProxy, 'left', this.canvasPosition.x + 'px');

	// Request the data from the DataSource instance.

	if(useDataSource) {
		this.seriesInfo = seriesInfo;
		this.dataSource = dataSource;
		this.refreshData();
	}

	// Attach events.

	YAHOO.util.Event.on(this.canvasProxy, 'mouseover', this.showHoverInfo, this, true);
	YAHOO.util.Event.on(this.canvasProxy, 'mouseout', this.hideHoverInfo, this, true);
	YAHOO.util.Event.on(this.canvasProxy, 'mousemove', this.updateHoverInfo, this, true);
};
YAHOO.Smb.Graph.reqBuild = 127;
YAHOO.Smb.Graph.version = 1.03;

YAHOO.Smb.Graph.defaults = {
	width: 300,
	height: 180,

	paddingTop: 0,
	paddingRight: 0,
	paddingBottom: 0,
	paddingLeft: 0,
	typeLib: undefined,
	labelFont: undefined,
	labelFontScale: 1,
	showXLabels: false,
	showYLabels: false,
	showBarLabels: false,
	xLabelSpacing: 0,
	yLabelSpacing: 0,
	xLabelColor: '#000',
	yLabelColor: '#000',
	yRightLabelColor: '#000',
	barLabelColor: '#000',
	xLabelPre: '',
	xLabelPost: '',
	xLabelFunction: undefined,
	yLabelPre: '',
	yLabelPost: '',
	yLabelFunction: undefined,
	yRightLabelPre: '',
	yRightLabelPost: '',
	barLabelPre: '',
	barLabelPost: '',

	background: '#fff',
	graphBackground: '#fff',	
	xGridColor: '#e8e8e8',
	yGridColor: '#e8e8e8',
	xGridSpacing: -1,
	yGridSpacing: -1,
	xAxisColor: '#000',
	yAxisColor: '#000', 
	hideXAxis: false,
	hideYAxis: false,
	enableHoverInfo: false,
	hoverInfoFunction: undefined,
	dontForceZero: false,
	type: 'line',
	start: 0,
	end: 0,
	yScale: 'linear',
	xKey: undefined,
	xLabel: undefined
};
YAHOO.Smb.Graph.classNames = {
	prefix: 'ysmb-graph-',
	canvas: 'canvas',
	hoverInfo: 'hover-info',
	hoverInfoText: 'hover-info-text',
	proxy: 'proxy'
};


YAHOO.Smb.Graph.prototype = {

	// Setup methods.

	render: function() { // This is a convenience method that calls all of the methods needed to draw the graph.
		return this.clearGraph().calculateScale().drawGrid().drawGraph().drawScale().drawLabels();
	},    

	clearGraph: function() {
		this.ctx.clearRect(0, 0, this.width, this.height);
		this.ctx.fillStyle = this.graphBackground;
		this.ctx.fillRect(this.paddingLeft, this.paddingTop, this.width - this.paddingLeft - this.paddingRight, this.height - this.paddingTop - this.paddingBottom);
		return this;
	},

	calculateScale: function() { // Scale the graph dynamically based on the data.
		this.scale = {};

		if(this.type === 'pie') {
			this.scale.center = { x: this.width / 2, y: this.height / 2 };
			this.scale.radius = (this.width < this.height) ? this.width * 0.44 : this.height * 0.44;

			this.total = 0;
			for(var n = this.start, len = this.data[0].length; n < this.end && n < len; n++) {
				this.total += this.data[0][n].y.value;
			}                  
			this.scale.radians = 2 * Math.PI / this.total; // Convert to radians and scale to the total amount.                        
		}
		else {
			this.max = 0;
			this.min = (this.dontForceZero) ? 9e30 : 0;
			this.scale = {vert: 9e30, horiz: 9e30, origin: 9e30};
			for(var m = 0, mLen = this.data.length; m < mLen; m++) {
				if(!this.enableData[m]) {
					continue;
				}
				for(var n = 0, len = this.data[m].length; n < len; n++) {
					if(YAHOO.lang.isNull(this.data[m][n].y.value)) {
						continue;
					}
					if(this.data[m][n].y.value > this.max) {
						this.max = this.data[m][n].y.value;
					}
					if(this.data[m][n].y.value < this.min) {
						this.min = this.data[m][n].y.value;
					}
				}
				
				this.min *= 0.95;
				this.max *= 1.005;

				this.scale[m] = {};
				this.scale[m].vert = (this.height - this.paddingBottom - this.paddingTop) / (this.max - this.min);
				this.scale[m].horiz = (this.width - this.paddingLeft - this.paddingRight) / (this.end - this.start - ((this.type.match(/bar/)) ? 0 : 1));
				this.scale[m].origin = Math.round(this.height - this.paddingBottom + this.min * this.scale[m].vert) - 1;
			
				if(this.type === 'horizontal-bar') {
					this.scale[m].vert = (this.height - this.paddingBottom - this.paddingTop) / (this.end - this.start);
					this.scale[m].horiz = (this.width - this.paddingLeft - this.paddingRight) / (this.max - this.min);
				}				

				if(this.scale[m].vert < this.scale.vert)
					this.scale.vert = this.scale[m].vert;
				if(this.scale[m].horiz < this.scale.horiz)
					this.scale.horiz = this.scale[m].horiz;
				if(this.scale[m].origin < this.scale.origin)
					this.scale.origin = this.scale[m].origin;
			}   
		}

		return this;
	},

	refreshData: function() { // Fetch the data from the DataSource instance.
		if(typeof this.dataSource !== 'object' || typeof this.dataSource.sendRequest !== 'function') {
			return this;
		}
		
		this.data = [];
		this.graphColor = [];
		for(var i=0; i<this.dataMarkers.length; i++)
			this.dataMarkers[i].parentNode.removeChild(this.dataMarkers[i]);
		this.dataMarkers = [];
		this.enableData = [];
		
		this.dataSource.sendRequest({}, this._handleDataResponse, this);

		return this;
	},
	
	browserDetect: function() { // Some browsers handle the canvas tag slightly differently. Use YAHOO.env.ua to determine if one of those browsers is being used.
		this.MSIE = (YAHOO.env.ua.ie > 0);
		this.SAFARI_2 = (YAHOO.env.ua.webkit >= 412 && YAHOO.env.ua.webkit < 522);
		this.SAFARI_3 = (YAHOO.env.ua.webkit >= 522);
	},

	// Accessor and mutator methods.

	queueProperty: function(prop, value) {

		// Pre-processing.

		switch(prop) {
			case 'type':
				if(value !== undefined && value !== 'line' && value !== 'bar' && value !== 'horizontal-bar' && value !== 'area' && value !== 'pie') {
					throw new Error('Graph: You can only set type to be line, bar, area or pie.');
				}
				break;
			case 'start':
				if(value < 0) {
					throw new Error('Graph: start must be greater than zero.');
				}
				break;
			case 'end':
				if(value < this.start) {
					throw new Error('Graph: end must be greater than start.');
				}
				break;
		}

		// Set the attribute.

		this[prop] = value || YAHOO.Smb.Graph.defaults[prop];

		// Post-processing.

		switch(prop) {
			case 'width':
				if(this.SAFARI_2) {
					this.canvas.style.width = this.width + 'px';
				}
				else {
					this.canvas.width = this.width;
				}
				break;
			case 'height':
				if(this.SAFARI_2) {
					this.canvas.style.height = this.height + 'px';
				}
				else {
					this.canvas.height = this.height;
				}
				break;
			case 'background':
				YAHOO.util.Dom.setStyle(this.canvas, 'background', this.background);
				break;  
			case 'paddingBottom':
				if(this.type !== 'pie') {
					YAHOO.util.Dom.setStyle(this.hoverInfo, 'height', (this.height - this.paddingBottom) + 'px');
				}
				break;
		}

		return this;
	},

	setProperty: function(prop, value) {
		return this.queueProperty(prop, value).render();
	},

	getProperty: function(prop) {
		return this[prop];
	},

	addDataSet: function(data, layout) {
		var xLabel = layout.xLabel || layout.x;
		var yLabel = layout.yLabel || layout.y;   
		var color = layout.color || '#cc0000';
		var diffScale = layout.newScale || false;
		var axisRight = layout.axisRight || false;

		if(this.type === 'pie') {
			color = layout.colors || ['#cc0000', '#c36000', '#8c8d9b', '#46495e', '#ff9900', '#666666'];
			this.graphOutline = layout.outline || '#000';
		}

		this.data.push([]);
		this.graphColor.push(color);
		this.enableData.push(true);
		this.diffScale.push(diffScale);
		this.axisRight.push(axisRight)
		var m = this.data.length - 1;
		for(var n = 0, len = data.length; n < len; n++) {
			this.data[m][n] = { x: {}, y: {} };
			this.data[m][n].x.value = parseFloat(data[n][layout.x]) || n;
			this.data[m][n].y.value = (data[n][layout.y] === null) ? null : parseFloat(data[n][layout.y]);
			this.data[m][n].x.label = data[n][xLabel] || n;
			this.data[m][n].y.label = data[n][yLabel];
			if(this.yScale === 'log') {
				this.data[m][n].y.value = Math.log(this.data[m][n].y.value) * Math.LOG10E;
			}
		}

		if(this.end === 0) {
			this.end = this.data[m].length;
		}

		if(this.type !== 'pie') {
			this.dataMarkers.push(document.createElement('div'));
			var len = this.dataMarkers.length - 1;
			this.hoverInfo.appendChild(this.dataMarkers[len]);
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'position', 'absolute');
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'overflow', 'hidden');
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'top', '2px');
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'left', '-2px');
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'width', '5px');    
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'height', '5px');
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'margin-top', '-2px');        
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'background-color', color);
			YAHOO.util.Dom.setStyle(this.dataMarkers[len], 'border', '1px solid ' + this.background);                
		}

		return m;
	},

	_handleDataResponse: function(requestObj, responseData) { // Callback method for the DataSource sendRequest method.
		for(var n = 0, len = this.seriesInfo.length; n < len; n++) {
			this.addDataSet(responseData.results, {
				y: this.seriesInfo[n].key, 
				xLabel: this.getProperty('xKey'),
				color: this.seriesInfo[n].color,
				newScale: this.seriesInfo[n].newScale,
				axisRight: this.seriesInfo[n].axisRight
			});
		}
		var that = this;
		setTimeout(function() { that.dataLoaded.fire();	}, 0);
	},

	getNumDataSets: function() {
		return this.data.length;
	},
	toggleData: function(index, show) { // Toggle a dataset from hidden to shown (or vise versa).
		return (show) ? this.showData(index) : this.hideData(index);
	},
	hideData: function(index) {
		this.enableData[index] = false;
		return this;
	},
	showData: function(index) {
		this.enableData[index] = true;
		return this;
	},  
	setDataColor: function(index, color) {
		this.graphColor[index] = color;
		return this;
	},  

	getContext: function() { // Get the canvas context. This is used for drawing to the canvas directly.
		return this.ctx;
	},

	// Hover info functions.

	showHoverInfo: function(e) { // Fire the mouseover event and show the hover info, if it is enabled.
		this.mouseover.fire();
		if(this.enableHoverInfo && this.type !== 'pie') {
			YAHOO.util.Dom.setStyle(this.hoverInfo, 'display', 'block');
		}
		return this;
	},

	hideHoverInfo: function(e) { // Fire the mouseout event and hide the hover info, if it is enabled.
		this.mouseout.fire();
		if(this.enableHoverInfo && this.type !== 'pie') {
			YAHOO.util.Dom.setStyle(this.hoverInfo, 'display', 'none');
		}
		return this;
	},

	updateHoverInfo: function(e) { // Fire the mousemove event and update the hover info, if it is enabled.
		var x = (e.clientX - this.canvasPosition.x - this.parentPosition.x - this.paddingLeft) / this.scale.horiz;
		if(x < 0)
			x = Math.floor(x);
		else if(x >= this.end - this.start - ((this.type.match(/bar/)) ? 0 : 1))
			x = Math.ceil(x);
		else
			x = Math.round(x);

		x += this.start;
		var y;
		if(x >= this.data[0].length) {
			this.hideHoverInfo();
			return this;
		}

		// Calculate the position data.
		
		var inGraph = false;
		var args = [];
		if(this.type === 'pie') {
			x = Math.round(e.clientX - YAHOO.util.Dom.getX(this.canvas));
			y = Math.round(e.clientY - YAHOO.util.Dom.getY(this.canvas));

			var deltaX = Math.abs(x - this.scale.center.x);
			var deltaY = Math.abs(y - this.scale.center.y);
			var distanceFromCenter = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
			inGraph = distanceFromCenter <= this.scale.radius;
			if(inGraph) {

				// Calculate the angle in radians.

				var angle = Math.atan(deltaY/deltaX);
				if(x < this.scale.center.x && y < this.scale.center.y) {        // Quadrant IV
					angle += 3 * Math.PI / 2;
				}
				else if(x < this.scale.center.x && y >= this.scale.center.y) {  // Quadrant III
					angle = (Math.PI / 2 - angle) + Math.PI;
				}
				else if(x >= this.scale.center.x && y >= this.scale.center.y) { // Quadrant II
					angle += Math.PI / 2;
				}
				else {                                                          // Quadrant I
					angle = (Math.PI / 2) - angle;	
				}

				// Use the angle to determine which piece the cursor is over.

				for(var n = this.start, len = this.data[0].length, last = 0; n < this.end && n < len; n++) {
					if(angle >= last && angle < this.scale.radians * this.data[0][n].y.value + last) {
						args = [this.data[0][n].x.label.replace(/ /g, '&nbsp;'), (this.data[0][n].y.value / this.total * 100) + '%', this.data[0][n].y.value];
						break;
					}
					last += this.scale.radians * this.data[0][n].y.value;
				}        
			}
			else {
				args = [];
			}
		}
		else if(x >= this.start)  {
			args.push(this.data[0][x].x.label);
			for(var n = 0, len = this.data.length; n < len; n++) {
				if(!this.enableData[n]) {
					continue;
				}
				args.push(this.data[n][x].y.label);
			}
		}

		// Fire the mousemove event only if position data exists.

		if(args.length > 0) {
			this.mousemove.fire(args);
		}

		// Update the hover info.

		if(this.enableHoverInfo) {
			if(this.type === 'pie') {
				if(inGraph) {
					YAHOO.util.Dom.setStyle(this.hoverInfo, 'display', 'block');                                        
					YAHOO.util.Dom.setStyle(this.hoverInfo, 'left', e.clientX + 'px');
					YAHOO.util.Dom.setStyle(this.hoverInfo, 'top', e.clientY + 'px');
					this.hoverInfoText.innerHTML = args[0] + '&nbsp;-&nbsp;' + args[1] + '&nbsp;(' + args[2] + ')';
				}
				else {
					YAHOO.util.Dom.setStyle(this.hoverInfo, 'display', 'none');                		        
				}
			}
			else {
				if(x >= this.start && x < this.end) {
					this.showHoverInfo();
				}
				else {
					this.hideHoverInfo();
				}
				
				YAHOO.util.Dom.setStyle(this.hoverInfo, 'left', (e.clientX - this.parentPosition.x) + 'px');
				if(!this.data[0][x]) {
					return this;
				}
				var output = [];
				var ohtml = this.data[0][x].x.label;
				for(var n = 0, len = this.data.length; n < len; n++) {
					if(!this.enableData[n]) {
						YAHOO.util.Dom.setStyle(this.dataMarkers[n], 'display', 'none');
						continue;
					}
					output.push({
						color: this.graphColor[n],
						data: this.data[n][x].y.label
					});
					ohtml += ',&nbsp;<em style="color:' + this.graphColor[n] + '">' + this.data[n][x].y.label + '</em>';
					y = Math.round(this.height - ((this.data[n][x].y.value - this.min) * (this.diffScale[n]?this.scale[n].vert:this.scale.vert)) - this.paddingBottom - this.paddingTop);
					YAHOO.util.Dom.setStyle(this.dataMarkers[n], 'display', 'block');
					YAHOO.util.Dom.setStyle(this.dataMarkers[n], 'top', y + 'px');
				}
				if(typeof this.hoverInfoFunction !== 'undefined') {
					this.hoverInfoText.innerHTML = this.hoverInfoFunction(x, this.data[0][x].x.label, output);
				} else {
					this.hoverInfoText.innerHTML = ohtml;
				}

				if(e.clientX - this.parentPosition.x + this.hoverInfoText.offsetWidth + 10 > this.width) {
					YAHOO.util.Dom.setStyle(this.hoverInfoText, 'left', '' + ( this.parentPosition.x + this.width - (e.clientX + this.hoverInfoText.offsetWidth) ) + 'px');
				} else {
					YAHOO.util.Dom.setStyle(this.hoverInfoText, 'left', '10px');
				}
			}
		}
		
		return this;
	},

	// Drawing functions.

	drawGrid: function() { // Draws the grid lines.
		if(this.type === 'pie') {
			return this;
		}

		// Vertical lines.

		var inc = (this.xGridSpacing < 0) ? this.scale[0].horiz * 4 : this.xGridSpacing;

		this.ctx.fillStyle = this.yGridColor;
		for(var n = this.paddingLeft; n < (this.width - this.paddingRight); n += inc) {
			this.ctx.fillRect(n, 0, 1, this.height - this.paddingBottom);
		}
		this.ctx.fillRect(this.width - 1 - this.paddingRight, 0, 1, this.height - this.paddingBottom);


		// Horizontal lines.

		inc = (this.yGridSpacing < 0) ? (this.height - this.paddingBottom - this.paddingTop) / 16 : this.yGridSpacing;
		var dataHeight;

		this.ctx.fillStyle = this.xGridColor;

		for(var n = this.paddingTop; n < (this.height - this.paddingBottom); n += inc) {
			this.ctx.fillRect(this.paddingLeft, n, this.width - this.paddingLeft - this.paddingRight, 1);
		}			
		if(n < this.scale.origin) {
			this.ctx.fillRect(this.paddingLeft, this.height - this.paddingBottom, this.width - this.paddingLeft - this.paddingRight, 1);
		}

		return this;
	},
        
	drawScale: function() { // Draws the axes and the tick marks.
		if((this.hideXAxis && this.hideYAxis) || this.type === 'pie') {
			return this;
		}

		// Axes.

		if(!this.hideYAxis) {
			this.ctx.fillStyle = this.yAxisColor;
			this.ctx.fillRect(this.paddingLeft, this.paddingTop, 1, this.height - this.paddingBottom - this.paddingTop);
			for(var n = 1, len = this.data.length; n < len; n++) {
				if(this.axisRight[n]) {
					this.ctx.fillRect(this.width - this.paddingRight, this.paddingTop, 1, this.height - this.paddingBottom - this.paddingTop);
					break;
				}
			}
		}

		if(!this.hideXAxis) {
			this.ctx.fillStyle = this.xAxisColor;
			this.ctx.fillRect(this.paddingLeft, this.scale.origin, this.width - this.paddingLeft - this.paddingRight, 1);
		}

		// Horizontal ticks on the y-axis.

		var inc = (this.yGridSpacing < 0) ? (this.height - this.paddingBottom - this.paddingTop) / 16 : this.yGridSpacing;

		if(!this.hideYAxis) {
			this.ctx.fillStyle = this.yAxisColor;
			for(var n = this.paddingTop, m = 0; n < (this.height - this.paddingBottom); n += inc, m++) {
				var width = (this.yGridSpacing < 0) ? ((m % 2 === 0) ? 3 : ((m % 4 === 3) ? 5 : 9)) : 7;
				this.ctx.fillRect(this.paddingLeft - Math.floor(width / 2), Math.round(n), width, 1);
			}

			for(var m = 1, len = this.data.length; m < len; m++) {
				if(this.axisRight[m]) {
					for(var n = this.paddingTop, m = 0; n < (this.height - this.paddingBottom); n += inc, m++) {
						var width = (this.yGridSpacing < 0) ? ((m % 2 === 0) ? 3 : ((m % 4 === 3) ? 5 : 9)) : 7;
						this.ctx.fillRect(this.width - this.paddingRight - Math.floor(width / 2), Math.round(n), width, 1);
					}
					break;
				}
			}
		}

		// Vertical ticks on the x-axis.

		inc = (this.xGridSpacing < 0) ? this.scale.horiz * 4 : this.xGridSpacing;

		if(!this.hideXAxis) {
			this.ctx.fillStyle = this.xAxisColor;
			for(var n = this.paddingLeft, m = 0; n < this.width; n += inc, m++) {
				if(m === 0) {
					continue;
				}
				var height = (this.xGridSpacing < 0) ? ((m % 2 === 0) ? 3 : ((m % 4 === 3) ? 5 : 8)) : 7;
				this.ctx.fillRect(Math.round(n), this.scale.origin - height + Math.floor(height / 2), 1, height);
			}
		}

		return this;
	},
        
	drawLabels: function() { // Draws the labels that appear on the axes and the bars.

		if(this.typeLib === undefined || this.labelFont === undefined) {
			return this;
		}
		
		var label, length, x, y;

		// y-axis labels.

		var inc = (this.yGridSpacing < 0) ? (this.height - this.paddingBottom - this.paddingTop) / 16 : this.yGridSpacing;

		if(this.showYLabels) {
			this.ctx.fillStyle = this.yAxisColor;
			var precision = Math.log(this.scale.vert) * Math.LOG10E;
			precision = (precision < 0) ? 0 : Math.floor(precision);

			for(var n = this.paddingTop, m = 0; n < (this.height - this.paddingBottom); n += inc, m++) {
				if(m === 0 || (this.yGridSpacing < 0 && (m % 2 === 0 || m % 4 === 3)) || ((m - 1) % (this.yLabelSpacing + 1) !== 0)) { // Determine when the label should and should not be drawn.
					continue;
				}

				label = ((this.height - this.paddingBottom - n) / this.scale.vert) + this.min;
				if(this.yScale === 'log') {
					label = Math.exp(label / Math.LOG10E);
				}
				label = label.toFixed(precision);
				if(typeof this.yLabelFunction !== 'undefined') {
					label = this.yLabelFunction(label);
				} else {
				label = this.yLabelPre + label + this.yLabelPost;
				}

				length = this.typeLib.calculateLength(this.typeLib.tokenize(label), this.labelFont, this.labelFontScale, 0);
				if(length.totalWidth > this.paddingLeft - 8) { // Don't draw the label at all if it is going to be truncated.
					continue;
				}

				this.typeLib.addStringToCanvas(label, this.ctx, { x: 0, y: Math.round(n - (length.lineHeight / 2)), width: this.paddingLeft - 8, font: this.labelFont, color: this.yLabelColor, scale: this.labelFontScale, textAlign: 'right' });
			}
			
			for(var p = 0, len = this.data.length; p < len; p++) {
				if(this.axisRight[p]) {
					for(var n = this.paddingTop, m = 0; n < (this.height - this.paddingBottom); n += inc, m++) {
						if(m === 0 || (this.yGridSpacing < 0 && (m % 2 === 0 || m % 4 === 3)) || ((m - 1) % (this.yLabelSpacing + 1) !== 0)) { // Determine when the label should and should not be drawn.
							continue;
						}

						label = ((this.height - this.paddingBottom - n) / this.scale[p].vert) + this.min;
						if(this.yScale === 'log') {
							label = Math.exp(label / Math.LOG10E);
						}
						label = label.toFixed(precision);
						label = this.yRightLabelPre + label + this.yRightLabelPost;
						length = this.typeLib.calculateLength(this.typeLib.tokenize(label), this.labelFont, this.labelFontScale, 0);
						if(length.totalWidth > this.paddingRight - 10) { // Don't draw the label at all if it is going to be truncated.
							continue;
						}

						this.typeLib.addStringToCanvas(label, this.ctx, { x: this.width - this.paddingRight + 10, y: Math.round(n - (length.lineHeight / 2)), width: this.paddingRight - 10, font: this.labelFont, color: this.yRightLabelColor, scale: this.labelFontScale, textAlign: 'left' });
					}
					break;
				}
			}
		}

		// x-axis labels.

		inc = (this.xGridSpacing < 0) ? this.scale.horiz * 4 : this.xGridSpacing;

		if(this.showXLabels) {
			this.ctx.fillStyle = this.xAxisColor;
			var dataIndex;
			for(var n = this.paddingLeft, m = 0; n < this.width; n += inc, m++) {
				dataIndex = Math.round((n - this.paddingLeft) / this.scale.horiz) + this.start;
				if(dataIndex >= this.data[0].length || (this.xGridSpacing < 0 && (m % 2 === 0 || m % 4 === 3)) || ((m - 1) % (this.xLabelSpacing + 1) !== 0)) { // Determine when the label should and should not be drawn.
					continue;
				}

				label = this.data[0][dataIndex].x.label;
				if(typeof this.xLabelFunction !== 'undefined') {
					label = this.xLabelFunction(label);
				} else {
				label = this.xLabelPre + label + this.xLabelPost;
				}

				length = this.typeLib.calculateLength(this.typeLib.tokenize(label), this.labelFont, this.labelFontScale, 0);
				x = Math.round((this.type.match(/bar/)) ? n : n - (length.totalWidth / 2));
				y = this.height - this.paddingBottom + 8;

				this.typeLib.addStringToCanvas(label, this.ctx, { x: x, y: y, font: this.labelFont, color: this.xLabelColor, scale: this.labelFontScale });
			}
		}  

		// Bar labels.

		if(this.type.match(/bar/) && this.showBarLabels) {
			this.ctx.fillStyle = '#cc0000';
			var tokens;
			for(var n = 0, len = this.data.length; n < len; n++) {
				if(!this.enableData[n]) {
					continue;
				}
				for(var m = this.start, len = this.data[n].length; m < this.end && m < len; m++) {

					label = this.data[n][m].y.label;
					label = this.barLabelPre + label + this.barLabelPost;

					tokens = this.typeLib.tokenize(label);
					length = this.typeLib.calculateLength(tokens, this.labelFont, this.labelFontScale, 1);

					if(this.type === 'bar') {
						x = Math.round(this.data[n][m].x.value * this.scale[0].horiz - (this.start * this.scale[0].horiz) + this.paddingLeft + (this.scale[0].horiz * 0.4) - (length.lineHeight / 2));
						y = Math.round(this.height - ((this.data[n][m].y.value - this.min) * this.scale[0].vert + length.totalWidth + 4 + this.paddingBottom));
						if(this.data[n][m].y.value <= 0) {
							y = Math.round(this.scale[0].origin - length.totalWidth - 4);
						}

						if(y < 0) {
							continue;
						}
					}
					else { // Horizontal bar graph labels.
						x = Math.round(this.data[n][m].y.value * this.scale[0].horiz + this.paddingLeft + 4);
						y = Math.round((this.data[n][m].x.value - this.start) * this.scale[0].vert + this.paddingTop + (this.scale[0].vert * 0.4) - 3); 
						if(this.data[n][m].y.value <= 0) {
							x = Math.round(this.paddingLeft + 4);
						}

						if(x < 0) {
							continue;
						}						
					}
					this.typeLib.addStringToCanvas(label, this.ctx, { x: x, y: y, font: this.labelFont, color: this.barLabelColor, scale: this.labelFontScale, angle: (this.type === 'bar') ? 270 : 0 });
				}        
			}
		}             

		return this;
	},
    
	drawGraph: function() { // Draw the actual graph.
		if(this.data.length === 0) {
			return this;
		}
		var func;
		switch(this.type) {
			case 'bar': 
				func = this.drawBar;
				break;
			case 'horizontal-bar':
				func = this.drawHorizontalBar;
				break;
			case 'area': 
				func = this.drawArea;
				break;
			case 'pie':
				func = this.drawPie;
				break;
			case 'line': 
			default:
				func = this.drawLine;
		}

		for(var n = 0, len = this.data.length; n < len; n++) {
			if(!this.enableData[n]) {
				continue;
			}
			func.call(this, this.data[n], this.graphColor[n], (this.type === 'pie') ? this.scale : ((this.diffScale[n]) ? this.scale[n] : this.scale));
		}

		return this;
	},

	// Drawing functions for each graph type.

	drawBar: function(dataSet, color, scale) {
		this.ctx.fillStyle = color;
		for(var n = this.start, len = dataSet.length; n < this.end && n < len; n++) {
			this.ctx.fillRect(dataSet[n].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, this.height - ((dataSet[n].y.value - this.min) * scale.vert) - this.paddingBottom, scale.horiz * 0.8, dataSet[n].y.value * scale.vert);
		}

		return this;
	},

	drawHorizontalBar: function(dataSet, color, scale) {
		this.ctx.fillStyle = color;
		var x, y, w, h;
		for(var n = this.start, len = dataSet.length; n < this.end && n < len; n++) {
			x = this.paddingLeft;
			y = (dataSet[n].x.value - this.start) * scale.vert + this.paddingTop;
			w = Math.round(dataSet[n].y.value * scale.horiz);
			h = scale.vert * 0.8;

			this.ctx.fillRect(x, y, w, h);
		}

		return this;
	},

	drawArea: function(dataSet, color, scale) {
		this.ctx.fillStyle = color;
		this.ctx.beginPath();
		this.ctx.moveTo(this.paddingLeft, scale.origin);
		for(var n = this.start, len = dataSet.length; n < this.end && n < len; n++) {
			if(dataSet[n].y.value === null) {
				this.ctx.lineTo(dataSet[n - 1].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, this.height - ((dataSet[n].y.value - this.min) * scale.vert) - this.paddingBottom);
				if(dataSet[n + 1]) {
					this.ctx.moveTo(dataSet[n + 1].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, this.height - ((dataSet[n].y.value - this.min) * scale.vert) - this.paddingBottom);
				}
			}
			else {
				this.ctx.lineTo(dataSet[n].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, this.height - ((dataSet[n].y.value - this.min) * scale.vert) - this.paddingBottom);
			}
		}   
		this.ctx.lineTo(dataSet[--n].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, scale.origin);
		this.ctx.fill();

		return this;
	},
    
	drawLine: function(dataSet, color, scale) {
		this.ctx.strokeStyle = color;
		this.ctx.beginPath();
		this.ctx.moveTo(this.paddingLeft, this.height - ((dataSet[this.start].y.value - this.min) * scale.vert) - this.paddingBottom);
		for(var n = this.start, len = dataSet.length; n < this.end && n < len; n++) {
			if(dataSet[n].y.value === null) {
				this.ctx.stroke();				
				if(dataSet[n + 1]) {
					this.ctx.beginPath();
					this.ctx.moveTo(dataSet[n + 1].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, this.height - ((dataSet[n + 1].y.value - this.min) * scale.vert) - this.paddingBottom);
				}
			}
			else {
				this.ctx.lineTo(dataSet[n].x.value * scale.horiz - (this.start * scale.horiz) + this.paddingLeft, this.height - ((dataSet[n].y.value - this.min) * scale.vert) - this.paddingBottom);
			}
		}   
		this.ctx.stroke();

		return this;
	},

	drawPie: function(dataSet, color, scale) {
		this.ctx.strokeStyle = this.graphOutline;

		for(var n = this.start, len = dataSet.length, last = Math.PI / -2; n < this.end && n < len; n++) {
			this.ctx.fillStyle = (n < color.length) ? color[n] : color[n % color.length];
			this.ctx.beginPath();

			this.ctx.moveTo(scale.center.x, scale.center.y);
			this.ctx.arc(scale.center.x, scale.center.y, scale.radius, last, scale.radians * dataSet[n].y.value + last, false);
			this.ctx.lineTo(scale.center.x, scale.center.y);
			this.ctx.fill();

			this.ctx.beginPath();
			this.ctx.moveTo(scale.center.x, scale.center.y);
			if(n === 0) {
				this.ctx.lineTo(scale.center.x, scale.center.y - scale.radius);
			}
			this.ctx.arc(scale.center.x, scale.center.y, scale.radius, last, scale.radians * dataSet[n].y.value + last, false);
			this.ctx.lineTo(scale.center.x, scale.center.y);
			this.ctx.stroke();
			
			last += scale.radians * dataSet[n].y.value;
		}

		return this;
	}
};