/**
 * --------------------------------------------------------------------
 * jQuery-Plugin "visualize"
 * by Scott Jehl, scott@filamentgroup.com
 * http://www.filamentgroup.com
 * Copyright (c) 2009 Filament Group 
 * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
 * 	
 * --------------------------------------------------------------------
 */
(function($) { 
$.fn.visualize = function(options, container){
	return $(this).each(function(){
		//configuration
		var o = $.extend({
			type: 'bar', //also available: area, pie, line
			width: $(this).width(), //height of canvas - defaults to table height
			height: $(this).height(), //height of canvas - defaults to table height
			appendTitle: true, //table caption text is added to chart
			title: null, //grabs from table caption if null
			appendKey: true, //color key is added to chart
			colors: ['#be1e2d','#666699','#92d5ea','#ee8310','#8d10ee','#5a3b16','#26a4ed','#f45a90','#e9e744'],
			textColors: [], //corresponds with colors array. null/undefined items will fall back to CSS
			parseDirection: 'x', //which direction to parse the table data
			pieMargin: 10, //pie charts only - spacing around pie
			pieLabelsAsPercent: true,
			pieLabelPos: 'inside',
			lineWeight: 4, //for line and area - stroke weight
			lineDots: false, //also available: 'single', 'double'
			dotInnerColor: "#ffffff", // only used for lineDots:'double'
			lineMargin: (options.lineDots?15:0), //for line and area - spacing around lines
			barGroupMargin: 10,
			chartId: '',
			xLabelParser: null, // function to parse labels as values
			valueParser: null, // function to parse values. must return a Number
			chartId: '',
			chartClass: '',
			barMargin: 1, //space around bars in bar chart (added to both sides of bar)
			yLabelInterval: 30, //distance between y labels
			interaction: false // only used for lineDots != false -- triggers mouseover and mouseout on original table
		},options);
		
		//reset width, height to numbers
		o.width = parseFloat(o.width);
		o.height = parseFloat(o.height);
		
		// reset padding if graph is not lines
		if(o.type != 'line' && o.type != 'area' ) {
			o.lineMargin = 0;
		}
		
		var self = $(this);
		
		// scrape data from html table
		var tableData = {};
		var colors = o.colors;
		var textColors = o.textColors;
		
		
		var parseLabels = function(direction){
			var labels = [];
			if(direction == 'x'){
				self.find('thead tr').each(function(i){
					$(this).find('th').each(function(j){
						if(!labels[j]) {
							labels[j] = [];
						}
						labels[j][i] = $(this).text()
					})
				});
			}
			else {
				self.find('tbody tr').each(function(i){
					$(this).find('th').each(function(j) {
						if(!labels[i]) {
							labels[i] = [];
						}
						labels[i][j] = $(this).text()
					});
				});
			}
			return labels;
		};
		
		var fnParse = o.valueParser || parseFloat;
		var dataGroups = tableData.dataGroups = [];
		if(o.parseDirection == 'x'){
			self.find('tbody tr').each(function(i,tr){
				dataGroups[i] = {};
				dataGroups[i].points = [];
				dataGroups[i].color = colors[i];
				if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
				$(tr).find('td').each(function(j,td){
					dataGroups[i].points.push( {
						value: fnParse($(td).text()),
						elem: td,
						tableCords: [i,j]
					} );
				});
			});
		} else {
			var cols = self.find('tbody tr:eq(0) td').size();
			for(var i=0; i<cols; i++){
				dataGroups[i] = {};
				dataGroups[i].points = [];
				dataGroups[i].color = colors[i];
				if(textColors[i]){ dataGroups[i].textColor = textColors[i]; }
				self.find('tbody tr').each(function(j){
					dataGroups[i].points.push( {
						value:  $(this).find('td').eq(i).text()*1,
						elem: this,
						tableCords: [i,j]
					} );
				});
			};
		}
		
	
		var allItems = tableData.allItems = [];
		$(dataGroups).each(function(i,row){
			var count = 0;
			$.each(row.points,function(j,point){
				allItems.push(point);
				count += point.value;
			});
			row.groupTotal = count;
		});
		
		tableData.dataSum = 0;
		tableData.topValue = 0;
		tableData.bottomValue = Infinity;
		$.each(allItems,function(i,item){
			tableData.dataSum += fnParse(item.value);
			if(fnParse(item.value,10)>tableData.topValue) {
				tableData.topValue = fnParse(item.value,10);
			}
			if(item.value<tableData.bottomValue) {
				tableData.bottomValue = fnParse(item.value);	
			}
		});
		var dataSum = tableData.dataSum;
		var topValue = tableData.topValue;
		var bottomValue = tableData.bottomValue;
		
		var xAllLabels = tableData.xAllLabels = parseLabels(o.parseDirection);
		var yAllLabels = tableData.yAllLabels = parseLabels(o.parseDirection==='x'?'y':'x');
	
		var xLabels = tableData.xLabels = [];
		$.each(tableData.xAllLabels,function(i,labels) {
			tableData.xLabels.push(labels[0]);
		});
		
		var totalYRange = tableData.totalYRange = tableData.topValue - tableData.bottomValue;
		
		var zeroLocX = tableData.zeroLocX = 0;
		
		if($.isFunction(o.xLabelParser)) {
			
			var xTopValue = null;
			var xBottomValue = null;

			$.each(xLabels,function(i,label) {
				label = xLabels[i] = o.xLabelParser(label);
				if(i === 0) {
					xTopValue = label;
					xBottomValue = label;
				}
				if(label>xTopValue) {
					xTopValue = label;
				}
				if(label<xBottomValue) {
					xBottomValue = label;
				}
			});

			var totalXRange = tableData.totalXRange = xTopValue - xBottomValue;

			
			var	xScale = tableData.xScale = (o.width -2*o.lineMargin) / totalXRange;
			var marginDiffX = 0;
			if(o.lineMargin) {
				var marginDiffX = -2*xScale-o.lineMargin;
			}
			zeroLocX = tableData.zeroLocX = xBottomValue + o.lineMargin;
			
			tableData.xBottomValue = xBottomValue;
			tableData.xTopValue = xTopValue;
			tableData.totalXRange = totalXRange;
		}
		
		var	yScale = tableData.yScale = (o.height - 2*o.lineMargin) / totalYRange;
		var zeroLocY = tableData.zeroLocY = (o.height-2*o.lineMargin) * (tableData.topValue/tableData.totalYRange) + o.lineMargin;
		
		var yLabels = tableData.yLabels = [];

		var numLabels = Math.floor((o.height - 2*o.lineMargin) / 30);
		var loopInterval = tableData.totalYRange / numLabels; //fix provided from lab
		loopInterval = Math.round(parseFloat(loopInterval)/5)*5;
		loopInterval = Math.max(loopInterval, 1);
		// var start = 
		for(var j=Math.round(parseInt(tableData.bottomValue)/5)*5; j<=tableData.topValue+loopInterval; j+=loopInterval){
			yLabels.push(j); 
		}
		if(yLabels[yLabels.length-1] > tableData.topValue+loopInterval) {
			yLabels.pop();
		} else if (yLabels[yLabels.length-1] <= tableData.topValue-10) {
			yLabels.push(tableData.topValue);
		}
				
		// populate some data
		$.each(dataGroups,function(i,row){
			row.yLabels = tableData.yAllLabels[i];
			$.each(row.points, function(j,point){
				point.zeroLocY = tableData.zeroLocY;
				point.zeroLocX = tableData.zeroLocX;
				point.xLabels = tableData.xAllLabels[j];
				point.yLabels = tableData.yAllLabels[i];
				point.color = row.color;
			});
		});
		
		try{console.log(tableData);}catch(e){}
		
		var charts = {};
		
		charts.pie = {
			interactionPoints: dataGroups,
			
			setup: function() {
				charts.pie.draw(true);
			},
			draw: function(drawHtml){	

				var centerx = Math.round(canvas.width()/2);
				var centery = Math.round(canvas.height()/2);
				var radius = centery - o.pieMargin;				
				var counter = 0.0;

				if(drawHtml) {
					canvasContain.addClass('visualize-pie');

					if(o.pieLabelPos == 'outside'){ canvasContain.addClass('visualize-pie-outside'); }	

					var toRad = function(integer){ return (Math.PI/180)*integer; };
					var labels = $('<ul class="visualize-labels"></ul>')
						.insertAfter(canvas);
				}


				//draw the pie pieces
				$.each(dataGroups, function(i,row){
					var fraction = row.groupTotal / dataSum;
                    if (fraction <= 0 || isNaN(fraction))
                        return;
					ctx.beginPath();
					ctx.moveTo(centerx, centery);
					ctx.arc(centerx, centery, radius, 
						counter * Math.PI * 2 - Math.PI * 0.5,
						(counter + fraction) * Math.PI * 2 - Math.PI * 0.5,
		                false);
			        ctx.lineTo(centerx, centery);
			        ctx.closePath();
			        ctx.fillStyle = dataGroups[i].color;
			        ctx.fill();
			        // draw labels
					if(drawHtml) {
				       	var sliceMiddle = (counter + fraction/2);
				       	var distance = o.pieLabelPos == 'inside' ? radius/1.5 : radius +  radius / 5;
				        var labelx = Math.round(centerx + Math.sin(sliceMiddle * Math.PI * 2) * (distance));
				        var labely = Math.round(centery - Math.cos(sliceMiddle * Math.PI * 2) * (distance));
				        var leftRight = (labelx > centerx) ? 'right' : 'left';
				        var topBottom = (labely > centery) ? 'bottom' : 'top';
				        var percentage = parseFloat((fraction*100).toFixed(2));

						// interaction variables
						row.canvasCords = [labelx,labely];
						row.zeroLocY = tableData.zeroLocY = 0; // related to zeroLocY and plugin API
						row.zeroLocX = tableData.zeroLocX = 0; // related to zeroLocX and plugin API
						row.value = row.groupTotal;


				        if(percentage){
				        	var labelval = (o.pieLabelsAsPercent) ? percentage + '%' : row.groupTotal;
					        var labeltext = $('<span class="visualize-label">' + labelval +'</span>')
					        	.css(leftRight, 0)
					        	.css(topBottom, 0);
					        	if(labeltext)
				        var label = $('<li class="visualize-label-pos"></li>')
				       			.appendTo(labels)
				        		.css({left: labelx, top: labely})
				        		.append(labeltext);	
				        labeltext
				        	.css('font-size', radius / 8)
				        	.css('margin-'+leftRight, -labeltext.width()/2)
				        	.css('margin-'+topBottom, -labeltext.outerHeight()/2);

				        if(dataGroups[i].textColor){ labeltext.css('color', dataGroups[i].textColor); }

				        }
					}
			      	counter+=fraction;
				});
			}
		};
		
		(function(){
			
			var xInterval;

			var drawPoint = function (ctx,x,y,color,size) {
				ctx.moveTo(x,y);
				ctx.beginPath();
				ctx.arc(x,y,size/2,0,2*Math.PI,false);
				ctx.closePath();
				ctx.fillStyle = color;
				ctx.fill();
			};

			charts.line = {
				
				interactionPoints: allItems,

				setup: function(area){

					if(area){ canvasContain.addClass('visualize-area'); }
					else{ canvasContain.addClass('visualize-line'); }

					//write X labels
					var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
						.width(canvas.width())
						.height(canvas.height())
						.insertBefore(canvas);

					if(!o.customXLabels) {
						xInterval = (canvas.width() - 2*o.lineMargin) / (xLabels.length -1);
						$.each(xLabels, function(i){ 
							var thisLi = $('<li><span>'+this+'</span></li>')
								.prepend('<span class="line" />')
								.css('left', o.lineMargin + xInterval * i)
								.appendTo(xlabelsUL);						
							var label = thisLi.find('span:not(.line)');
							var leftOffset = label.width()/-2;
							if(i == 0){ leftOffset = 0; }
							else if(i== xLabels.length-1){ leftOffset = -label.width(); }
							label
								.css('margin-left', leftOffset)
								.addClass('label');
						});
					} else {
						o.customXLabels(tableData,xlabelsUL);
					}

					//write Y labels
					var liBottom = (canvas.height() - 2*o.lineMargin) / (yLabels.length-1);
					var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
						.width(canvas.width())
						.height(canvas.height())
						// .css('margin-top',-o.lineMargin)
						.insertBefore(scroller);

					$.each(yLabels, function(i){
						var value = Math.floor(this);
						var posB = (value-bottomValue)*yScale + o.lineMargin;
						if(posB >= o.height-1 || posB < 0) {
							return;
						}
						var thisLi = $('<li><span>'+value+'</span></li>')
							.css('bottom', posB);
						if(Math.abs(posB) < o.height-1) {
							thisLi.prepend('<span class="line"  />');
						}
						thisLi.prependTo(ylabelsUL);
						
						var label = thisLi.find('span:not(.line)');
						var topOffset = label.height()/-2;
						if(!o.lineMargin) {
							if(i == 0){ topOffset = -label.height(); }
							else if(i== yLabels.length-1){ topOffset = 0; }
						}
						label
							.css('margin-top', topOffset)
							.addClass('label');
					});
					
					//start from the bottom left
					ctx.translate(zeroLocX,zeroLocY);
					
					charts.line.draw(area);

				},
				
				draw: function(area) {
					// prevent drawing on top of previous draw
					ctx.clearRect(-zeroLocX,-zeroLocY,o.width,o.height);
					// Calculate each point properties before hand
					var integer;
					$.each(dataGroups,function(i,row){
						integer = o.lineMargin; // the current offset
						$.each(row.points, function(j,point){
							if(o.xLabelParser) {
								point.canvasCords = [(xLabels[j]-zeroLocX)*xScale - xBottomValue,-(point.value*yScale)];
							} else {
								point.canvasCords = [integer,-(point.value*yScale)];
							}
							
							if(o.lineDots) {
								point.dotSize = o.dotSize||o.lineWeight*Math.PI;
								point.dotInnerSize = o.dotInnerSize||o.lineWeight*Math.PI/2;
								if(o.lineDots == 'double') {
									point.innerColor = o.dotInnerColor;
								}
							}
							integer+=xInterval;
						});
					});
					// fire custom event so we can enable rich interaction
					self.trigger('vizualizeBeforeDraw',{options:o,table:self,canvasContain:canvasContain,tableData:tableData});
					// draw lines and areas
					$.each(dataGroups,function(h){
						// Draw lines
						ctx.beginPath();
						ctx.lineWidth = o.lineWeight;
						ctx.lineJoin = 'round';
						$.each(this.points, function(g){
							var loc = this.canvasCords;
							if(g == 0) {
								ctx.moveTo(loc[0],loc[1]);
							}
							ctx.lineTo(loc[0],loc[1]);
						});
						ctx.strokeStyle = this.color;
						ctx.stroke();
						// Draw fills
						if(area){
							var integer = this.points[this.points.length-1].canvasCords[0];
							if (isFinite(integer))
								ctx.lineTo(integer,0);
							ctx.lineTo(o.lineMargin,0);
							ctx.closePath();
							ctx.fillStyle = this.color;
							ctx.globalAlpha = .3;
							ctx.fill();
							ctx.globalAlpha = 1.0;
						}
						else {ctx.closePath();}
					});
					// draw points
					if(o.lineDots) {
						$.each(dataGroups,function(h){
							$.each(this.points, function(g){
								drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.color,this.dotSize);
								if(o.lineDots === 'double') {
									drawPoint(ctx,this.canvasCords[0],this.canvasCords[1],this.innerColor,this.dotInnerSize);
								}
							});
						});
					}
					
				}
			};
		
		})();
		
		charts.area = {
			setup: function() {
				charts.line.setup(true);
			},
			draw: charts.line.draw
		};
		
		(function(){

			var horizontal,bottomLabels;

			charts.bar = {
				setup:function(){
					/**
					 * We can draw horizontal or vertical bars depending on the
					 * value of the 'barDirection' option (which may be 'vertical' or
					 * 'horizontal').
					 */

					horizontal = (o.barDirection == 'horizontal');

					canvasContain.addClass('visualize-bar');

					/**
					 * Write labels along the bottom of the chart.	If we're drawing
					 * horizontal bars, these will be the yLabels, otherwise they
					 * will be the xLabels.	The positioning also varies slightly:
					 * yLabels are values, hence they will span the whole width of
					 * the canvas, whereas xLabels are supposed to line up with the
					 * bars.
					 */
					bottomLabels = horizontal ? yLabels : xLabels;

					var xInterval = canvas.width() / (bottomLabels.length - (horizontal ? 1 : 0));

					var xlabelsUL = $('<ul class="visualize-labels-x"></ul>')
						.width(canvas.width())
						.height(canvas.height())
						.insertBefore(canvas);

					$.each(bottomLabels, function(i){
						var thisLi = $('<li><span class="label">'+this+'</span></li>')
							.prepend('<span class="line" />')
							.css('left', xInterval * i)
							.width(xInterval)
							.appendTo(xlabelsUL);

						if (horizontal)	{
							var label = thisLi.find('span.label');
							label.css("margin-left", -label.width() / 2);
						}
					});

					/**
					 * Write labels along the left of the chart.	Follows the same idea
					 * as the bottom labels.
					 */
					var leftLabels = horizontal ? xLabels : yLabels;
					var liBottom = canvas.height() / (leftLabels.length - (horizontal ? 0 : 1));

					var ylabelsUL = $('<ul class="visualize-labels-y"></ul>')
						.width(canvas.width())
						.height(canvas.height())
						.insertBefore(canvas);

					$.each(leftLabels, function(i){
						var thisLi = $('<li><span>'+this+'</span></li>').prependTo(ylabelsUL);

						var label = thisLi.find('span:not(.line)').addClass('label');

						if (horizontal) {
							/**
							 * For left labels, we want to vertically align the text
							 * to the middle of its container, but we don't know how
							 * many lines of text we will have, since the labels could
							 * be very long.
							 *
							 * So we set a min-height of liBottom, and a max-height
							 * of liBottom + 1, so we can then check the label's actual
							 * height to determine if it spans one line or more lines.
							 */
							label.css({
								'min-height': liBottom,
								'max-height': liBottom + 1,
								'vertical-align': 'middle'
							});
							thisLi.css({'top': liBottom * i, 'min-height': liBottom});

							var r = label[0].getClientRects()[0];
							if (r.bottom - r.top == liBottom) {
								/* This means we have only one line of text; hence
								 * we can centre the text vertically by setting the line-height,
								 * as described at:
								 *   http://www.ampsoft.net/webdesign-l/vertical-aligned-nav-list.html
								 *
								 * (Although firefox has .height on the rectangle, IE doesn't,
								 * so we use r.bottom - r.top rather than r.height.)
								 */
								label.css('line-height', parseInt(liBottom) + 'px');
							}
							else {
								/*
								 * If there is more than one line of text, then we shouldn't
								 * touch the line height, but we should make sure the text
								 * doesn't overflow the container.
								 */
								label.css("overflow", "hidden");
							}
						}
						else {
							thisLi.css('bottom', liBottom * i).prepend('<span class="line" />');
							label.css('margin-top', -label.height() / 2)
						}
					});

					charts.bar.draw();

				},

				draw: function() {
					// Draw bars

					if (horizontal) {
						// for horizontal, keep the same code, but rotate everything 90 degrees
						// clockwise.
						ctx.rotate(Math.PI / 2);
					}
					else {
						// for vertical, translate to the top left corner.
						ctx.translate(0, zeroLocY);
					}

					// Don't attempt to draw anything if all the values are zero,
					// otherwise we will get weird exceptions from the canvas methods.
					if (totalYRange <= 0)
						return;

					var yScale = (horizontal ? canvas.width() : canvas.height()) / totalYRange;
					var barWidth = horizontal ? (canvas.height() / xLabels.length) : (canvas.width() / (bottomLabels.length));
					var linewidth = (barWidth - o.barGroupMargin*2) / dataGroups.length;

					for(var h=0; h<dataGroups.length; h++){
						ctx.beginPath();

						var strokeWidth = linewidth - (o.barMargin*2);
						ctx.lineWidth = strokeWidth;
						var points = dataGroups[h].points;
						var integer = 0;
						for(var i=0; i<points.length; i++){
							// If the last value is zero, IE will go nuts and not draw anything,
							// so don't try to draw zero values at all.
							if (points[i].value != 0) {
								var xVal = (integer-o.barGroupMargin)+(h*linewidth)+linewidth/2;
								xVal += o.barGroupMargin*2;

								ctx.moveTo(xVal, 0);
								ctx.lineTo(xVal, Math.round(-points[i].value*yScale));
	                        }
							integer+=barWidth;
						}
						ctx.strokeStyle = dataGroups[h].color;
						ctx.stroke();
						ctx.closePath();
					}

				}
			};
			
		})();
	
		//create new canvas, set w&h attrs (not inline styles)
		var canvasNode = document.createElement("canvas"); 
		var canvas = $(canvasNode)
			.attr({
				'height': o.height,
				'width': o.width
			});
		
		//get title for chart
		var title = o.title || self.find('caption').text();
		
		//create canvas wrapper div, set inline w&h, append
		var canvasContain = (container || $('<div '+(o.chartId?'id="'+o.chartId+'" ':'')+'class="visualize '+o.chartClass+'" role="img" aria-label="Chart representing data from the table: '+ title +'" />'))
			.height(o.height)
			.width(o.width);
		
		var scroller = $('<div class="visualize-scroller"></div>')
			.appendTo(canvasContain)
			.append(canvas);

		//title/key container
		if(o.appendTitle || o.appendKey){
			var infoContain = $('<div class="visualize-info"></div>')
				.appendTo(canvasContain);
		}
		
		//append title
		if(o.appendTitle){
			$('<div class="visualize-title">'+ title +'</div>').appendTo(infoContain);
		}
		
		
		//append key
		if(o.appendKey){
			var newKey = $('<ul class="visualize-key"></ul>');
			$.each(yAllLabels, function(i,label){
				$('<li><span class="visualize-key-color" style="background: '+dataGroups[i].color+'"></span><span class="visualize-key-label">'+ label +'</span></li>')
					.appendTo(newKey);
			});
			newKey.appendTo(infoContain);
		};		
		
		// init interaction
		if(o.interaction) {
			// sets the canvas to track interaction
			// IE needs one div on top of the canvas since the VML shapes prevent mousemove from triggering correctly.
			// Pie charts needs tracker because labels goes on top of the canvas and also messes up with mousemove
			var tracker = $('<div class="visualize-interaction-tracker"/>')
				.css({
					'height': o.height + 'px',
					'width': o.width + 'px',
					'position':'relative',
					'z-index': 200
				})
				.insertAfter(canvas);

			var triggerInteraction = function(overOut,data) {
				var data = $.extend({
					canvasContain:canvasContain,
					tableData:tableData
				},data);
				self.trigger('vizualize'+overOut,data);
			};

			var over=false, last=false, started=false;
			tracker.mousemove(function(e){
				var x,y,x1,y1,data,dist,i,current,selector,zLabel,elem,color,minDist,found,ev=e.originalEvent;

				// get mouse position relative to the tracker/canvas
				x = ev.layerX || ev.offsetX || 0;
				y = ev.layerY || ev.offsetY || 0;

				found = false;
				minDist = started?30000:(o.type=='pie'?(Math.round(canvas.height()/2)-o.pieMargin)/3:o.lineWeight*4);
				// iterate datagroups to find points with matching
				$.each(charts[o.type].interactionPoints,function(i,current){
					x1 = current.canvasCords[0] + zeroLocX;
					y1 = current.canvasCords[1] + (o.type=="pie"?0:zeroLocY);
					dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) );
					if(dist < minDist) {
						found = current;
						minDist = dist;
					}
				});
				
				if(o.multiHover && found) {
					x = found.canvasCords[0] + zeroLocX;
					y = found.canvasCords[1] + (o.type=="pie"?0:zeroLocY);
					found = [found];
					$.each(charts[o.type].interactionPoints,function(i,current){
						if(current == found[0]) {return;}
						x1 = current.canvasCords[0] + zeroLocX;
						y1 = current.canvasCords[1] + zeroLocY;
						dist = Math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y) );
						if(dist <= o.multiHover) {
							found.push(current);
						}
					});
				}
				// trigger over and out only when state changes, instead of on every mousemove
				over = found;
				if(over != last) {
					if(over) {
						if(last) {
							triggerInteraction('Out',{point:last});
						}
						triggerInteraction('Over',{point:over});
						last = over;
					}
					if(last && !over) {
						triggerInteraction('Out',{point:last});
						last=false;
					}
					started=true;
				}
			});
			tracker.mouseleave(function(){
				triggerInteraction('Out',{
					point:last,
					mouseOutGraph:true
				});
				over = (last = false);
			});
		}
		
		//append new canvas to page
		if(!container){canvasContain.insertAfter(this); }
		if( typeof(G_vmlCanvasManager) != 'undefined' ){ G_vmlCanvasManager.init(); G_vmlCanvasManager.initElement(canvas[0]); }	
		
		//set up the drawing board
		var ctx = canvas[0].getContext('2d');
		
		// Scroll graphs
		scroller.scrollLeft(o.width-scroller.width());

		// init plugins
		$.each($.visualizePlugins,function(i,plugin){
			plugin.call(self,o,tableData);
		});

		//create chart
		charts[o.type].setup();
		
		if(!container){
			//add event for updating
			self.bind('visualizeRefresh', function(){
				self.visualize(o, $(this).empty()); 
			});
			//add event for redraw
			self.bind('visualizeRedraw', function(){
				charts[o.type].draw();
			});
		}
	}).next(); //returns canvas(es)
};
// create array for plugins. if you wish to make a plugin,
// just push your init funcion into this array
$.visualizePlugins = [];

})(jQuery);