You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by er...@apache.org on 2010/05/17 11:29:38 UTC

svn commit: r945044 [2/3] - in /ofbiz/trunk: ./ framework/common/webcommon/WEB-INF/actions/includes/ framework/common/webcommon/includes/flotrCharts/ framework/common/widget/ framework/example/entitydef/ framework/example/webapp/example/WEB-INF/ framew...

Added: ofbiz/trunk/framework/images/webapp/images/flotr/flotr.js
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/images/webapp/images/flotr/flotr.js?rev=945044&view=auto
==============================================================================
--- ofbiz/trunk/framework/images/webapp/images/flotr/flotr.js (added)
+++ ofbiz/trunk/framework/images/webapp/images/flotr/flotr.js Mon May 17 09:29:38 2010
@@ -0,0 +1,4134 @@
+/* $Id: flotr.js 157 2010-04-22 12:45:35Z fabien.menager $ */
+
+/** 
+ * @projectDescription Flotr is a javascript plotting library based on the Prototype Javascript Framework.
+ * @author Bas Wenneker
+ * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
+ * @version 0.2.0
+ */
+var Flotr = {
+	version: '%version%',
+	author: 'Bas Wenneker',
+	website: 'http://www.solutoire.com',
+	isIphone: /iphone/i.test(navigator.userAgent),
+	
+	/**
+	 * An object of the registered graph types. Use Flotr.addType(type, object)
+	 * to add your own type.
+	 */
+	graphTypes: {},
+	
+	/**
+	 * The list of the registered plugins
+	 */
+	plugins: {},
+	
+	/**
+	 * Can be used to add your own chart type. 
+	 * @param {String} name - Type of chart, like 'pies', 'bars' etc.
+	 * @param {String} graphType - The object containing the basic drawing functions (draw, etc)
+	 */
+	addType: function(name, graphType){
+		Flotr.graphTypes[name] = graphType;
+		Flotr.defaultOptions[name] = graphType.options || {};
+		Flotr.defaultOptions.defaultType = Flotr.defaultOptions.defaultType || name;
+	},
+	
+	/**
+	 * Can be used to add a plugin
+	 * @param {String} name - The name of the plugin
+	 * @param {String} plugin - The object containing the plugin's data (callbacks, options, function1, function2, ...)
+	 */
+	addPlugin: function(name, plugin){
+		Flotr.plugins[name] = plugin;
+		Flotr.defaultOptions[name] = plugin.options || {};
+	},
+	
+	/**
+	 * Draws the graph. This function is here for backwards compatibility with Flotr version 0.1.0alpha.
+	 * You could also draw graphs by directly calling Flotr.Graph(element, data, options).
+	 * @param {Element} el - element to insert the graph into
+	 * @param {Object} data - an array or object of dataseries
+	 * @param {Object} options - an object containing options
+	 * @param {Class} _GraphKlass_ - (optional) Class to pass the arguments to, defaults to Flotr.Graph
+	 * @return {Object} returns a new graph object and of course draws the graph.
+	 */
+	draw: function(el, data, options, GraphKlass){	
+		GraphKlass = GraphKlass || Flotr.Graph;
+		return new GraphKlass(el, data, options);
+	},
+	
+	/**
+	 * Collects dataseries from input and parses the series into the right format. It returns an Array 
+	 * of Objects each having at least the 'data' key set.
+	 * @param {Array, Object} data - Object or array of dataseries
+	 * @return {Array} Array of Objects parsed into the right format ({(...,) data: [[x1,y1], [x2,y2], ...] (, ...)})
+	 */
+	getSeries: function(data){
+		return data.collect(function(serie){
+			serie = (serie.data) ? Object.clone(serie) : {data: serie};
+			for (var i = serie.data.length-1; i > -1; --i) {
+				serie.data[i][1] = (serie.data[i][1] === null ? null : parseFloat(serie.data[i][1])); 
+			}
+			return serie;
+		});
+	},
+	
+	/**
+	 * Recursively merges two objects.
+	 * @param {Object} src - source object (likely the object with the least properties)
+	 * @param {Object} dest - destination object (optional, object with the most properties)
+	 * @return {Object} recursively merged Object
+	 */
+	merge: function(src, dest){
+		var i, v, result = dest || {};
+		for(i in src){
+			v = src[i];
+			result[i] = (v && typeof(v) === 'object' && !(v.constructor === Array || v.constructor === RegExp) && !Object.isElement(v)) ? Flotr.merge(v, dest[i]) : result[i] = v;
+		}
+		return result;
+	},
+	
+	/**
+	 * Recursively clones an object.
+	 * @param {Object} object - The object to clone
+	 * @return {Object} the clone
+	 */
+	clone: function(object){
+		var i, v, clone = {};
+		for(i in object){
+			v = object[i];
+			clone[i] = (v && typeof(v) === 'object' && !(v.constructor === Array || v.constructor === RegExp) && !Object.isElement(v)) ? Flotr.clone(v) : v;
+		}
+		return clone;
+	},
+	
+	/**
+	 * Function calculates the ticksize and returns it.
+	 * @param {Integer} noTicks - number of ticks
+	 * @param {Integer} min - lower bound integer value for the current axis
+	 * @param {Integer} max - upper bound integer value for the current axis
+	 * @param {Integer} decimals - number of decimals for the ticks
+	 * @return {Integer} returns the ticksize in pixels
+	 */
+	getTickSize: function(noTicks, min, max, decimals){
+		var delta = (max - min) / noTicks,
+        magn = Flotr.getMagnitude(delta),
+        tickSize = 10,
+		    norm = delta / magn; // Norm is between 1.0 and 10.0.
+		    
+		if(norm < 1.5) tickSize = 1;
+		else if(norm < 2.25) tickSize = 2;
+		else if(norm < 3) tickSize = ((decimals == 0) ? 2 : 2.5);
+		else if(norm < 7.5) tickSize = 5;
+		
+		return tickSize * magn;
+	},
+	
+	/**
+	 * Default tick formatter.
+	 * @param {String, Integer} val - tick value integer
+	 * @return {String} formatted tick string
+	 */
+	defaultTickFormatter: function(val){
+		return val+'';
+	},
+	
+	/**
+	 * Formats the mouse tracker values.
+	 * @param {Object} obj - Track value Object {x:..,y:..}
+	 * @return {String} Formatted track string
+	 */
+	defaultTrackFormatter: function(obj){
+		return '('+obj.x+', '+obj.y+')';
+	}, 
+	
+	/**
+	 * Utility function to convert file size values in bytes to kB, MB, ...
+	 * @param value {Number} - The value to convert
+	 * @param precision {Number} - The number of digits after the comma (default: 2)
+	 * @param base {Number} - The base (default: 1000)
+	 */
+	engineeringNotation: function(value, precision, base){
+		var sizes =         ['Y','Z','E','P','T','G','M','k',''],
+		    fractionSizes = ['y','z','a','f','p','n','ยต','m',''],
+		    total = sizes.length;
+
+		base = base || 1000;
+		precision = Math.pow(10, precision || 2);
+
+		if (value == 0) return 0;
+
+		if (value > 1) {
+			while (total-- && (value >= base)) value /= base;
+		}
+		else {
+			sizes = fractionSizes;
+			total = sizes.length;
+			while (total-- && (value < 1)) value *= base;
+		}
+
+		return (Math.round(value * precision) / precision) + sizes[total];
+	},
+	
+	/**
+	 * Returns the magnitude of the input value.
+	 * @param {Integer, Float} x - integer or float value
+	 * @return {Integer, Float} returns the magnitude of the input value
+	 */
+	getMagnitude: function(x){
+		return Math.pow(10, Math.floor(Math.log(x) / Math.LN10));
+	},
+	toPixel: function(val){
+		return Math.floor(val)+0.5;//((val-Math.round(val) < 0.4) ? (Math.floor(val)-0.5) : val);
+	},
+	toRad: function(angle){
+		return -angle * (Math.PI/180);
+	},
+	floorInBase: function(n, base) {
+		return base * Math.floor(n / base);
+	},
+	drawText: function(ctx, text, x, y, style) {
+		if (!ctx.fillText || Flotr.isIphone) {
+			ctx.drawText(text, x, y, style);
+			return;
+		}
+		
+		style = Object.extend({
+			size: '10px',
+			color: '#000000',
+			textAlign: 'left',
+			textBaseline: 'bottom',
+			weight: 1,
+			angle: 0
+		}, style);
+		
+		ctx.save();
+		ctx.translate(x, y);
+		ctx.rotate(style.angle);
+		ctx.fillStyle = style.color;
+		ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+		ctx.textAlign = style.textAlign;
+		ctx.textBaseline = style.textBaseline;
+		ctx.fillText(text, 0, 0);
+		ctx.restore();
+	},
+	measureText: function(ctx, text, style) {
+		if (!ctx.fillText || Flotr.isIphone) {
+			return {width: ctx.measure(text, style)};
+		}
+		
+		style = Object.extend({
+			size: '10px',
+			weight: 1,
+			angle: 0
+		}, style);
+		
+		ctx.save();
+		ctx.rotate(style.angle);
+		ctx.font = (style.weight > 1 ? "bold " : "") + (style.size*1.3) + "px sans-serif";
+		var metrics = ctx.measureText(text);
+		ctx.restore();
+		return metrics;
+	},
+	getBestTextAlign: function(angle, style) {
+    style = style || {textAlign: 'center', textBaseline: 'middle'};
+    angle += Flotr.getTextAngleFromAlign(style);
+    
+    if (Math.abs(Math.cos(angle)) > 10e-3) 
+      style.textAlign    = (Math.cos(angle) > 0 ? 'right' : 'left');
+    
+    if (Math.abs(Math.sin(angle)) > 10e-3) 
+      style.textBaseline = (Math.sin(angle) > 0 ? 'top' : 'bottom');
+    
+    return style;
+  },
+  alignTable: {
+    'right middle' : 0,
+    'right top'    : Math.PI/4,
+    'center top'   : Math.PI/2,
+    'left top'     : 3*(Math.PI/4),
+    'left middle'  : Math.PI,
+    'left bottom'  : -3*(Math.PI/4),
+    'center bottom': -Math.PI/2,
+    'right bottom' : -Math.PI/4,
+    'center middle': 0
+  },
+  getTextAngleFromAlign: function(style) {
+    return Flotr.alignTable[style.textAlign+' '+style.textBaseline] || 0;
+  }
+};
+
+Flotr.defaultOptions = {
+	colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.
+	title: null,             // => The graph's title
+	subtitle: null,          // => The graph's subtitle
+	shadowSize: 4,           // => size of the 'fake' shadow
+	defaultType: null,       // => default series type
+	HtmlText: true,          // => wether to draw the text using HTML or on the canvas
+	fontSize: 7.5,           // => canvas' text font size
+	resolution: 1,           // => resolution of the graph, to have printer-friendly graphs !
+	legend: {
+		show: true,            // => setting to true will show the legend, hide otherwise
+		noColumns: 1,          // => number of colums in legend table // @todo: doesn't work for HtmlText = false
+		labelFormatter: function(v){return v}, // => fn: string -> string
+		labelBoxBorderColor: '#CCCCCC', // => border color for the little label boxes
+		labelBoxWidth: 14,
+		labelBoxHeight: 10,
+		labelBoxMargin: 5,
+		container: null,       // => container (as jQuery object) to put legend in, null means default on top of graph
+		position: 'nw',        // => position of default legend container within plot
+		margin: 5,             // => distance from grid edge to default legend container within plot
+		backgroundColor: null, // => null means auto-detect
+		backgroundOpacity: 0.85// => set to 0 to avoid background, set to 1 for a solid background
+	},
+	xaxis: {
+		ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
+		showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
+		labelsAngle: 0,        // => labels' angle, in degrees
+		title: null,           // => axis title
+		titleAngle: 0,         // => axis title's angle, in degrees
+		noTicks: 5,            // => number of ticks for automagically generated ticks
+		tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string
+		tickDecimals: null,    // => no. of decimals, null means auto
+		min: null,             // => min. value to show, null means set automatically
+		max: null,             // => max. value to show, null means set automatically
+		autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
+		color: null,           // => color of the ticks
+		mode: 'normal',        // => can be 'time' or 'normal'
+		timeFormat: null,
+		scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
+		base: Math.E
+	},
+	x2axis: {},
+	yaxis: {
+		ticks: null,           // => format: either [1, 3] or [[1, 'a'], 3]
+		showLabels: true,      // => setting to true will show the axis ticks labels, hide otherwise
+		labelsAngle: 0,        // => labels' angle, in degrees
+		title: null,           // => axis title
+		titleAngle: 90,        // => axis title's angle, in degrees
+		noTicks: 5,            // => number of ticks for automagically generated ticks
+		tickFormatter: Flotr.defaultTickFormatter, // => fn: number -> string
+		tickDecimals: null,    // => no. of decimals, null means auto
+		min: null,             // => min. value to show, null means set automatically
+		max: null,             // => max. value to show, null means set automatically
+		autoscaleMargin: 0,    // => margin in % to add if auto-setting min/max
+		color: null,           // => The color of the ticks
+		scaling: 'linear',     // => Scaling, can be 'linear' or 'logarithmic'
+		base: Math.E
+	},
+	y2axis: {
+		titleAngle: 270
+	},
+	grid: {
+		color: '#545454',      // => primary color used for outline and labels
+		backgroundColor: null, // => null for transparent, else color
+		tickColor: '#DDDDDD',  // => color used for the ticks
+		labelMargin: 3,        // => margin in pixels
+		verticalLines: true,   // => whether to show gridlines in vertical direction
+		horizontalLines: true, // => whether to show gridlines in horizontal direction
+		outlineWidth: 2,       // => width of the grid outline/border in pixels
+		circular: false        // => if set to true, the grid will be circular, must be used when radars are drawn
+	},
+	selection: {
+		mode: null,            // => one of null, 'x', 'y' or 'xy'
+		color: '#B6D9FF',      // => selection box color
+		fps: 20                // => frames-per-second
+	},
+	crosshair: {
+		mode: null,            // => one of null, 'x', 'y' or 'xy'
+		color: '#FF0000',      // => crosshair color
+		hideCursor: true       // => hide the cursor when the crosshair is shown
+	},
+	mouse: {
+		track: false,          // => true to track the mouse, no tracking otherwise
+		trackAll: false,
+		position: 'se',        // => position of the value box (default south-east)
+		relative: false,       // => next to the mouse cursor
+		trackFormatter: Flotr.defaultTrackFormatter, // => formats the values in the value box
+		margin: 5,             // => margin in pixels of the valuebox
+		lineColor: '#FF3F19',  // => line color of points that are drawn when mouse comes near a value of a series
+		trackDecimals: 1,      // => decimals for the track values
+		sensibility: 2,        // => the lower this number, the more precise you have to aim to show a value
+		radius: 3,             // => radius of the track point
+		fillColor: null,       // => color to fill our select bar with only applies to bar and similar graphs (only bars for now)
+		fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill 
+	}
+};
+
+/**
+ * Flotr Graph class that plots a graph on creation.
+ */
+Flotr.Graph = Class.create({
+	/**
+	 * Flotr Graph constructor.
+	 * @param {Element} el - element to insert the graph into
+	 * @param {Object} data - an array or object of dataseries
+ 	 * @param {Object} options - an object containing options
+	 */
+	initialize: function(el, data, options){
+    try {
+		this.el = $(el);
+    
+		if (!this.el) throw 'The target container doesn\'t exist';
+		if (!this.el.clientWidth) throw 'The target container must be visible';
+
+		this.registerPlugins();
+    
+		this.el.fire('flotr:beforeinit', [this]);
+    
+		// Initialize some variables
+		this.el.graph = this;
+		this.data = data;
+		this.lastMousePos = { pageX: null, pageY: null };
+		this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };
+		this.plotOffset = {left: 0, right: 0, top: 0, bottom: 0};
+		this.prevSelection = null;
+		this.selectionInterval = null;
+		this.ignoreClick = false;   
+		this.prevHit = null;
+		this.series = Flotr.getSeries(data);
+		this.setOptions(options);
+    
+		var type, p;
+		for (type in Flotr.graphTypes) {
+			this[type] = Object.clone(Flotr.graphTypes[type]);
+			for (p in this[type]) {
+				if (Object.isFunction(this[type][p]))
+					this[type][p] = this[type][p].bind(this);
+			}
+		}
+    
+		// Create and prepare canvas.
+		this.constructCanvas();
+    
+		this.el.fire('flotr:afterconstruct', [this]);
+		
+		// Add event handlers for mouse tracking, clicking and selection
+		this.initEvents();
+		
+		this.findDataRanges();
+		this.calculateTicks(this.axes.x);
+		this.calculateTicks(this.axes.x2);
+		this.calculateTicks(this.axes.y);
+		this.calculateTicks(this.axes.y2);
+		
+		this.calculateSpacing();
+		this.setupAxes();
+    
+		this.draw();
+		this.insertLegend();
+    
+		this.el.fire('flotr:afterinit', [this]);
+    } catch (e) {
+      try {
+        console.error(e);
+      } catch (e) {}
+    }
+	},
+	/**
+	 * Sets options and initializes some variables and color specific values, used by the constructor. 
+	 * @param {Object} opts - options object
+	 */
+	setOptions: function(opts){
+		var options = Flotr.clone(Flotr.defaultOptions);
+		options.x2axis = Object.extend(Object.clone(options.xaxis), options.x2axis);
+		options.y2axis = Object.extend(Object.clone(options.yaxis), options.y2axis);
+		this.options = Flotr.merge(opts || {}, options);
+		
+		// The 4 axes of the plot
+		this.axes = {
+			x:  {options: this.options.xaxis,  n: 1}, 
+			x2: {options: this.options.x2axis, n: 2}, 
+			y:  {options: this.options.yaxis,  n: 1}, 
+			y2: {options: this.options.y2axis, n: 2}
+		};
+		
+		// Initialize some variables used throughout this function.
+		var assignedColors = [],
+		    colors = [],
+		    ln = this.series.length,
+		    neededColors = this.series.length,
+		    oc = this.options.colors, 
+		    usedColors = [],
+		    variation = 0,
+		    c, i, j, s;
+
+		// Collect user-defined colors from series.
+		for(i = neededColors - 1; i > -1; --i){
+			c = this.series[i].color;
+			if(c){
+				--neededColors;
+				if(Object.isNumber(c)) assignedColors.push(c);
+				else usedColors.push(Flotr.Color.parse(c));
+			}
+		}
+		
+		// Calculate the number of colors that need to be generated.
+		for(i = assignedColors.length - 1; i > -1; --i)
+			neededColors = Math.max(neededColors, assignedColors[i] + 1);
+
+		// Generate needed number of colors.
+		for(i = 0; colors.length < neededColors;){
+			c = (oc.length == i) ? new Flotr.Color(100, 100, 100) : Flotr.Color.parse(oc[i]);
+			
+			// Make sure each serie gets a different color.
+			var sign = variation % 2 == 1 ? -1 : 1,
+          factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
+			c.scale(factor, factor, factor);
+
+			/**
+			 * @todo if we're getting too close to something else, we should probably skip this one
+			 */
+			colors.push(c);
+			
+			if(++i >= oc.length){
+				i = 0;
+				++variation;
+			}
+		}
+	
+		// Fill the options with the generated colors.
+		for(i = 0, j = 0; i < ln; ++i){
+			s = this.series[i];
+
+			// Assign the color.
+			if(s.color == null){
+				s.color = colors[j++].toString();
+			}else if(Object.isNumber(s.color)){
+				s.color = colors[s.color].toString();
+			}
+			
+			// Every series needs an axis
+			if (!s.xaxis) s.xaxis = this.axes.x;
+			     if (s.xaxis == 1) s.xaxis = this.axes.x;
+			else if (s.xaxis == 2) s.xaxis = this.axes.x2;
+			
+			if (!s.yaxis) s.yaxis = this.axes.y;
+			     if (s.yaxis == 1) s.yaxis = this.axes.y;
+			else if (s.yaxis == 2) s.yaxis = this.axes.y2;
+			
+			// Apply missing options to the series.
+			for (var t in Flotr.graphTypes){
+				s[t] = Object.extend(Object.clone(this.options[t]), s[t]);
+			}
+			s.mouse = Object.extend(Object.clone(this.options.mouse), s.mouse);
+			
+			if(s.shadowSize == null) s.shadowSize = this.options.shadowSize;
+		}
+	},
+	setupAxes: function(){
+		/**
+		 * Translates data number to pixel number
+		 * @param {Number} v - data number
+		 * @return {Number} translated pixel number
+		 */
+		function d2p(v, o){
+			if (o.scaling === 'logarithmic') {
+				v = Math.log(Math.max(v, Number.MIN_VALUE));
+				if (o.base !== Math.E) 
+					v /= Math.log(o.base);
+			}
+			return v;
+		}
+
+		/**
+		 * Translates pixel number to data number
+		 * @param {Number} v - pixel data
+		 * @return {Number} translated data number
+		 */
+		function p2d(v, o){
+			if (o.scaling === 'logarithmic')
+				v = (o.base === Math.E) ? Math.exp(v) : Math.pow(o.base, v);
+			return v;
+		}
+
+		this.axes.x.d2p = this.axes.x2.d2p = function(x){
+			return (d2p(x, this.options) - this.min) * this.scale;
+		};
+
+		this.axes.x.p2d = this.axes.x2.p2d = function(x){
+			return (p2d(x, this.options) / this.scale + this.min);
+		};
+
+		var ph = this.plotHeight;
+		this.axes.y.d2p = this.axes.y2.d2p = function(y){
+			return ph - (d2p(y, this.options) - this.min) * this.scale;
+		};
+
+		this.axes.y.p2d = this.axes.y2.p2d = function(y){
+			return p2d(this.max -y / this.scale, this.options);
+		};
+	},
+	/**
+	 * Initializes the canvas and it's overlay canvas element. When the browser is IE, this makes use 
+	 * of excanvas. The overlay canvas is inserted for displaying interactions. After the canvas elements
+	 * are created, the elements are inserted into the container element.
+	 */
+	constructCanvas: function(){
+		var el = this.el,
+			size, c, oc;
+		
+		// The old canvases are retrieved to avoid memory leaks ...
+		this.canvas = el.select('.flotr-canvas')[0];
+		this.overlay = el.select('.flotr-overlay')[0];
+		
+		// ... and all the child elements are removed
+		el.descendants().invoke('remove');
+
+		// For positioning labels and overlay.
+		el.style.position = 'relative';
+		el.style.cursor = el.style.cursor || 'default';
+
+		size = el.getDimensions();
+		this.canvasWidth = size.width;
+		this.canvasHeight = size.height;
+
+		var style = {
+			width: size.width+'px',
+			height: size.height+'px'
+		};
+
+		var o = this.options;
+		size.width *= o.resolution;
+		size.height *= o.resolution;
+
+		if(this.canvasWidth <= 0 || this.canvasHeight <= 0){
+			throw 'Invalid dimensions for plot, width = ' + this.canvasWidth + ', height = ' + this.canvasHeight;
+		}
+    
+		// Insert main canvas.
+		if (!this.canvas) {
+			c = this.canvas = $(document.createElement('canvas')); // Do NOT use new Element()
+			c.className = 'flotr-canvas';
+			c.style.cssText = 'position:absolute;left:0px;top:0px;';
+		}
+		c = this.canvas.writeAttribute(size).show().setStyle(style);
+		c.context_ = null; // Reset the ExCanvas context
+		el.insert(c);
+    
+		// Insert overlay canvas for interactive features.
+		if (!this.overlay) {
+			oc = this.overlay = $(document.createElement('canvas')); // Do NOT use new Element()
+			oc.className = 'flotr-overlay';
+			oc.style.cssText = 'position:absolute;left:0px;top:0px;';
+		}
+		oc = this.overlay.writeAttribute(size).show().setStyle(style);
+		oc.context_ = null; // Reset the ExCanvas context
+		el.insert(oc);
+		
+		if(Prototype.Browser.IE){
+			window.G_vmlCanvasManager.initElement(c);
+			window.G_vmlCanvasManager.initElement(oc);
+		}
+		this.ctx = c.getContext('2d');
+		this.octx = oc.getContext('2d');
+    
+		if(!Prototype.Browser.IE){
+			this.ctx.scale(o.resolution, o.resolution);
+			this.octx.scale(o.resolution, o.resolution);
+		}
+
+		// Enable text functions
+		this.textEnabled = !!this.ctx.drawText;
+	},
+  processColor: function(color, options){
+    if (!color) return 'rgba(0, 0, 0, 0)';
+    
+    options = Object.extend({
+      x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight, opacity: 1, ctx: this.ctx
+    }, options);
+    
+    if (color instanceof Flotr.Color) return color.adjust(null, null, null, options.opacity).toString();
+    if (Object.isString(color)) return Flotr.Color.parse(color).scale(null, null, null, options.opacity).toString();
+		
+    var grad = color.colors ? color : {colors: color};
+    
+    if (!options.ctx) {
+      if (!Object.isArray(grad.colors)) return 'rgba(0, 0, 0, 0)';
+      return Flotr.Color.parse(Object.isArray(grad.colors[0]) ? grad.colors[0][1] : grad.colors[0]).scale(null, null, null, options.opacity).toString();
+    }
+    grad = Object.extend({start: 'top', end: 'bottom'}, grad); 
+    
+    if (/top/i.test(grad.start))  options.x1 = 0;
+    if (/left/i.test(grad.start)) options.y1 = 0;
+    if (/bottom/i.test(grad.end)) options.x2 = 0;
+    if (/right/i.test(grad.end))  options.y2 = 0;
+
+    var i, c, stop, gradient = options.ctx.createLinearGradient(options.x1, options.y1, options.x2, options.y2);
+    for (i = 0; i < grad.colors.length; i++) {
+      c = grad.colors[i];
+      if (Object.isArray(c)) {
+        stop = c[0];
+        c = c[1];
+      }
+      else stop = i / (grad.colors.length-1);
+      gradient.addColorStop(stop, Flotr.Color.parse(c).scale(null, null, null, options.opacity));
+    }
+    return gradient;
+  },
+	registerPlugins: function(){
+		var name, plugin, c;
+		for (name in Flotr.plugins) {
+			plugin = Flotr.plugins[name];
+			for (c in plugin.callbacks) {
+				this.el.observe(c, plugin.callbacks[c].bindAsEventListener(this));
+			}
+			this[name] = Object.clone(plugin);
+			for (p in this[name]) {
+				if (Object.isFunction(this[name][p]))
+					this[name][p] = this[name][p].bind(this);
+			}
+		}
+	},
+	/**
+	 * Calculates a text box dimensions, wether it is drawn on the canvas or inserted into the DOM
+	 * @param {String} text - The text in the box
+	 * @param {Object} canvasStyle - An object containing the style for the text if drawn on the canvas
+	 * @param {String} HtmlStyle - A CSS style for the text if inserted into the DOM
+	 * @param {Object} className - A CSS className for the text if inserted into the DOM
+	 */
+	getTextDimensions: function(text, canvasStyle, HtmlStyle, className) {
+		if (!text) return {width:0, height:0};
+		
+		if (!this.options.HtmlText && this.textEnabled) {
+			var bounds = this.ctx.getTextBounds(text, canvasStyle);
+			return {
+				width: bounds.width+2, 
+				height: bounds.height+6
+			};
+		}
+		else {
+			var dummyDiv = this.el.insert('<div style="position:absolute;top:-10000px;'+HtmlStyle+'" class="'+className+' flotr-dummy-div">' + text + '</div>').select(".flotr-dummy-div")[0],
+			    dim = dummyDiv.getDimensions();
+			dummyDiv.remove();
+			return dim;
+		}
+	},
+	/**
+	 * Builds a matrix of the data to make the correspondance between the x values and the y values :
+	 * X value => Y values from the axes
+	 * @return {Array} The data grid
+	 */
+	loadDataGrid: function(){
+		if (this.seriesData) return this.seriesData;
+
+		var s = this.series,
+		    dg = [];
+
+    /* The data grid is a 2 dimensions array. There is a row for each X value.
+     * Each row contains the x value and the corresponding y value for each serie ('undefined' if there isn't one)
+    **/
+		for(i = 0; i < s.length; ++i){
+			s[i].data.each(function(v) {
+				var x = v[0],
+				    y = v[1], 
+					r = dg.find(function(row) {return row[0] == x});
+				if (r) r[i+1] = y;
+				else {
+					var newRow = [];
+					newRow[0] = x;
+					newRow[i+1] = y;
+					dg.push(newRow);
+				}
+			});
+		}
+		
+    // The data grid is sorted by x value
+		return this.seriesData = dg.sortBy(function(v){return v[0]});
+	},
+	/**
+	 * Initializes event some handlers.
+	 */
+	initEvents: function () {
+		//@todo: maybe stopObserving with only flotr functions
+		this.overlay.stopObserving()
+		    .observe('mousedown', this.mouseDownHandler.bindAsEventListener(this))
+		    .observe('mousemove', this.mouseMoveHandler.bindAsEventListener(this))
+		    .observe('mouseout', this.clearHit.bindAsEventListener(this))
+		    .observe('click', this.clickHandler.bindAsEventListener(this));
+	},
+	/**
+	 * Function determines the min and max values for the xaxis and yaxis.
+	 */
+	findDataRanges: function(){
+		var s = this.series, 
+		    a = this.axes;
+		
+		a.x.datamin = a.x2.datamin = a.y.datamin = a.y2.datamin = Number.MAX_VALUE;
+		a.x.datamax = a.x2.datamax = a.y.datamax = a.y2.datamax = -Number.MAX_VALUE;
+		
+		if(s.length > 0){
+			var i, j, h, x, y, data, xaxis, yaxis;
+		
+			// Get datamin, datamax start values 
+			for(i = 0; i < s.length; ++i) {
+				data = s[i].data, 
+				xaxis = s[i].xaxis, 
+				yaxis = s[i].yaxis;
+				
+				if (data.length > 0 && !s[i].hide) {
+					if (!xaxis.used) xaxis.datamin = xaxis.datamax = data[0][0];
+					if (!yaxis.used) yaxis.datamin = yaxis.datamax = data[0][1];
+					xaxis.used = true;
+					yaxis.used = true;
+
+					for(h = data.length - 1; h > -1; --h){
+						x = data[h][0];
+						     if(x < xaxis.datamin) xaxis.datamin = x;
+						else if(x > xaxis.datamax) xaxis.datamax = x;
+
+						for(j = 1; j < data[h].length; j++){
+							y = data[h][j];
+							     if(y < yaxis.datamin) yaxis.datamin = y;
+							else if(y > yaxis.datamax) yaxis.datamax = y;
+						}
+					}
+				}
+			}
+		}
+		
+		this.findXAxesValues();
+		
+		this.calculateRange(a.x, 'x');
+		
+		if (a.x2.used) {
+			this.calculateRange(a.x2, 'x');
+		}
+		
+		this.calculateRange(a.y, 'y');
+		
+		if (a.y2.used) {
+			this.calculateRange(a.y2, 'y');
+		}
+	},
+	extendRange: function(axis, type) {
+		var f = (type === 'y') ? 'extendYRange' : 'extendXRange'
+		for (var t in Flotr.graphTypes) {
+			if (this[t][f]) this[t][f](axis);
+		}
+	},
+	/**
+	 * Calculates the range of an axis to apply autoscaling.
+	 * @param {Object} axis - The axis for what the range will be calculated
+	 */
+	calculateRange: function(axis, type){
+		var o = axis.options,
+		    min = o.min != null ? o.min : axis.datamin,
+		    max = o.max != null ? o.max : axis.datamax,
+		    margin = o.autoscaleMargin;
+
+		if(max - min == 0.0){
+			var widen = (max == 0.0) ? 1.0 : 0.01;
+			min -= widen;
+			max += widen;
+		}
+		axis.tickSize = Flotr.getTickSize(o.noTicks, min, max, o.tickDecimals);
+
+		// Autoscaling.
+		if(o.min == null && margin != 0){
+			min -= axis.tickSize * margin;
+			// Make sure we don't go below zero if all values are positive.
+			if(min < 0 && axis.datamin >= 0) min = 0;
+			min = axis.tickSize * Math.floor(min / axis.tickSize);
+		}
+    
+		if(o.max == null && margin != 0){
+			max += axis.tickSize * margin;
+			if(max > 0 && axis.datamax <= 0 && axis.datamax != axis.datamin) max = 0;				
+			max = axis.tickSize * Math.ceil(max / axis.tickSize);
+		}
+
+		if (min == max) max = min + 1;
+
+		axis.min = min;
+		axis.max = max;
+		
+		this.extendRange(axis, type);
+	},
+	/** 
+	 * Find every values of the x axes
+	 */
+	findXAxesValues: function(){
+		var i, j, s;
+		for(i = this.series.length-1; i > -1 ; --i){
+			s = this.series[i];
+			s.xaxis.values = s.xaxis.values || {};
+			for (j = s.data.length-1; j > -1 ; --j){
+				s.xaxis.values[s.data[j][0]+''] = {};
+			}
+		}
+	},
+	/**
+	 * Calculate axis ticks.
+	 * @param {Object} axis - The axis for what the ticks will be calculated
+	 */
+	calculateTicks: function(axis){
+		var o = axis.options, i, v;
+		
+		axis.ticks = [];	
+		if(o.ticks){
+			var ticks = o.ticks, t, label;
+
+			if(Object.isFunction(ticks)){
+				ticks = ticks({min: axis.min, max: axis.max});
+			}
+			
+			// Clean up the user-supplied ticks, copy them over.
+			for(i = 0; i < ticks.length; ++i){
+				t = ticks[i];
+				if(typeof(t) == 'object'){
+					v = t[0];
+					label = (t.length > 1) ? t[1] : o.tickFormatter(v);
+				}else{
+					v = t;
+					label = o.tickFormatter(v);
+				}
+				axis.ticks[i] = { v: v, label: label };
+			}
+		}
+		else {
+			if (o.mode == 'time') {
+				var tu = Flotr.Date.timeUnits,
+				    spec = Flotr.Date.spec,
+						delta = (axis.max - axis.min) / axis.options.noTicks,
+						size, unit;
+
+				for (i = 0; i < spec.length - 1; ++i) {
+					var d = spec[i][0] * tu[spec[i][1]];
+					if (delta < (d + spec[i+1][0] * tu[spec[i+1][1]]) / 2 && d >= axis.tickSize)
+						break;
+				}
+				size = spec[i][0];
+				unit = spec[i][1];
+				
+				// special-case the possibility of several years
+				if (unit == "year") {
+					size = Flotr.getTickSize(axis.options.noTicks*tu.year, axis.min, axis.max, 0);
+				}
+				
+				axis.tickSize = size;
+				axis.tickUnit = unit;
+				axis.ticks = Flotr.Date.generator(axis);
+			}
+			else {
+				// Round to nearest multiple of tick size.
+				var start = axis.tickSize * Math.ceil(axis.min / axis.tickSize),
+				    decimals;
+				
+				// Then store all possible ticks.
+				for(i = 0; start + i * axis.tickSize <= axis.max; ++i){
+					v = start + i * axis.tickSize;
+					
+					// Round (this is always needed to fix numerical instability).
+					decimals = o.tickDecimals;
+					if(decimals == null) decimals = 1 - Math.floor(Math.log(axis.tickSize) / Math.LN10);
+					if(decimals < 0) decimals = 0;
+					
+					v = v.toFixed(decimals);
+					axis.ticks.push({ v: v, label: o.tickFormatter(v) });
+				}
+			}
+		}
+	},
+	/**
+	 * Calculates axis label sizes.
+	 */
+	calculateSpacing: function(){
+		var a = this.axes,
+  			options = this.options,
+  			series = this.series,
+  			margin = options.grid.labelMargin,
+  			x = a.x,
+  			x2 = a.x2,
+  			y = a.y,
+  			y2 = a.y2,
+  			maxOutset = 2,
+  			i, j, l, dim;
+		
+		// Labels width and height
+		[x, x2, y, y2].each(function(axis) {
+			var maxLabel = '';
+			
+			if (axis.options.showLabels) {
+				for(i = 0; i < axis.ticks.length; ++i){
+					l = axis.ticks[i].label.length;
+					if(l > maxLabel.length){
+						maxLabel = axis.ticks[i].label;
+					}
+				}
+			}
+			axis.maxLabel  = this.getTextDimensions(maxLabel, {size:options.fontSize, angle: Flotr.toRad(axis.options.labelsAngle)}, 'font-size:smaller;', 'flotr-grid-label');
+			axis.titleSize = this.getTextDimensions(axis.options.title, {size: options.fontSize*1.2, angle: Flotr.toRad(axis.options.titleAngle)}, 'font-weight:bold;', 'flotr-axis-title');
+		}, this);
+
+		// Title height
+		dim = this.getTextDimensions(options.title, {size: options.fontSize*1.5}, 'font-size:1em;font-weight:bold;', 'flotr-title');
+		this.titleHeight = dim.height;
+
+		// Subtitle height
+		dim = this.getTextDimensions(options.subtitle, {size: options.fontSize}, 'font-size:smaller;', 'flotr-subtitle');
+		this.subtitleHeight = dim.height;
+
+		// Grid outline line width.
+		if(options.show){
+			maxOutset = Math.max(maxOutset, options.points.radius + options.points.lineWidth/2);
+		}
+		for(j = 0; j < options.length; ++j){
+			if (series[j].points.show){
+				maxOutset = Math.max(maxOutset, series[j].points.radius + series[j].points.lineWidth/2);
+			}
+		}
+		
+		var p = this.plotOffset;
+		p.bottom += (options.grid.circular ? 0 : (x.options.showLabels ?  (x.maxLabel.height + margin) : 0)) + 
+		            (x.options.title ? (x.titleSize.height + margin) : 0) + maxOutset;
+		
+		p.top    += (options.grid.circular ? 0 : (x2.options.showLabels ? (x2.maxLabel.height + margin) : 0)) + 
+		            (x2.options.title ? (x2.titleSize.height + margin) : 0) + this.subtitleHeight + this.titleHeight + maxOutset;
+    
+		p.left   += (options.grid.circular ? 0 : (y.options.showLabels ?  (y.maxLabel.width + margin) : 0)) + 
+		            (y.options.title ? (y.titleSize.width + margin) : 0) + maxOutset;
+		
+		p.right  += (options.grid.circular ? 0 : (y2.options.showLabels ? (y2.maxLabel.width + margin) : 0)) + 
+		            (y2.options.title ? (y2.titleSize.width + margin) : 0) + maxOutset;
+    
+		p.top = Math.floor(p.top); // In order the outline not to be blured
+    
+		this.plotWidth  = this.canvasWidth - p.left - p.right;
+		this.plotHeight = this.canvasHeight - p.bottom - p.top;
+		
+		x.scale  = this.plotWidth / (x.max - x.min);
+		x2.scale = this.plotWidth / (x2.max - x2.min);
+		y.scale  = this.plotHeight / (y.max - y.min);
+		y2.scale = this.plotHeight / (y2.max - y2.min);
+	},
+	/**
+	 * Draws grid, labels, series and outline.
+	 */
+	draw: function() {
+		this.drawGrid();
+		this.drawLabels();
+		this.drawTitles();
+    
+		if(this.series.length){
+			this.el.fire('flotr:beforedraw', [this.series, this]);
+			for(var i = 0; i < this.series.length; i++){
+				if (!this.series[i].hide)
+					this.drawSeries(this.series[i]);
+			}
+		}
+		this.drawOutline();
+		this.el.fire('flotr:afterdraw', [this.series, this]);
+	},
+	/**
+	 * Draws a grid for the graph.
+	 */
+	drawGrid: function(){
+		var v, o = this.options,
+		    ctx = this.ctx, a;
+		    
+		if(o.grid.verticalLines || o.grid.horizontalLines){
+			this.el.fire('flotr:beforegrid', [this.axes.x, this.axes.y, o, this]);
+		}
+		ctx.save();
+		ctx.lineWidth = 1;
+		ctx.strokeStyle = o.grid.tickColor;
+		
+		if (o.grid.circular) {
+			ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2);
+			var radius = Math.min(this.plotHeight, this.plotWidth)*o.radar.radiusRatio/2,
+			    sides = this.axes.x.ticks.length,
+					coeff = 2*(Math.PI/sides),
+					angle = -Math.PI/2;
+			
+			// Draw grid lines in vertical direction.
+			ctx.beginPath();
+			
+			if(o.grid.horizontalLines){
+				a = this.axes.y;
+				for(var i = 0; i < a.ticks.length; ++i){
+					v = a.ticks[i].v;
+					var ratio = v / a.max;
+					
+					for(var j = 0; j <= sides; ++j){
+						ctx[j == 0 ? 'moveTo' : 'lineTo'](Math.cos(j*coeff+angle)*radius*ratio, Math.sin(j*coeff+angle)*radius*ratio);
+					}
+					//ctx.moveTo(radius*ratio, 0);
+					//ctx.arc(0, 0, radius*ratio, 0, Math.PI*2, true);
+				}
+			}
+			
+			if(o.grid.verticalLines){
+				for(var i = 0; i < sides; ++i){
+					ctx.moveTo(0, 0);
+					ctx.lineTo(Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
+				}
+			}
+			ctx.stroke();
+		}
+		else {
+			ctx.translate(this.plotOffset.left, this.plotOffset.top);
+	
+			// Draw grid background, if present in options.
+			if(o.grid.backgroundColor != null){
+				ctx.fillStyle = this.processColor(o.grid.backgroundColor, {x1: 0, y1: 0, x2: this.plotWidth, y2: this.plotHeight});
+				ctx.fillRect(0, 0, this.plotWidth, this.plotHeight);
+			}
+			
+			// Draw grid lines in vertical direction.
+			ctx.beginPath();
+			if(o.grid.verticalLines){
+				a = this.axes.x;
+				for(var i = 0; i < a.ticks.length; ++i){
+					v = a.ticks[i].v;
+					// Don't show lines on upper and lower bounds.
+					if ((v <= a.min || v >= a.max) || 
+					    (v == a.min || v == a.max) && o.grid.outlineWidth != 0)
+						continue;
+		
+					ctx.moveTo(Math.floor(a.d2p(v)) + ctx.lineWidth/2, 0);
+					ctx.lineTo(Math.floor(a.d2p(v)) + ctx.lineWidth/2, this.plotHeight);
+				}
+			}
+			
+			// Draw grid lines in horizontal direction.
+			if(o.grid.horizontalLines){
+				a = this.axes.y;
+				for(var j = 0; j < a.ticks.length; ++j){
+					v = a.ticks[j].v;
+					// Don't show lines on upper and lower bounds.
+					if ((v <= a.min || v >= a.max) || 
+					    (v == a.min || v == a.max) && o.grid.outlineWidth != 0)
+						continue;
+		
+					ctx.moveTo(0, Math.floor(a.d2p(v)) + ctx.lineWidth/2);
+					ctx.lineTo(this.plotWidth, Math.floor(a.d2p(v)) + ctx.lineWidth/2);
+				}
+			}
+			ctx.stroke();
+		}
+		
+		ctx.restore();
+		if(o.grid.verticalLines || o.grid.horizontalLines){
+			this.el.fire('flotr:aftergrid', [this.axes.x, this.axes.y, o, this]);
+		}
+	}, 
+	/**
+   * Draws a outline for the graph.
+   */
+	drawOutline: function(){
+    var v, o = this.options,
+        ctx = this.ctx;
+		
+    if (o.grid.outlineWidth == 0) return;
+		
+    ctx.save();
+		
+    if (o.grid.circular) {
+      ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2);
+      var radius = Math.min(this.plotHeight, this.plotWidth)*o.radar.radiusRatio/2,
+          sides = this.axes.x.ticks.length,
+          coeff = 2*(Math.PI/sides),
+          angle = -Math.PI/2;
+      
+      // Draw axis/grid border.
+      ctx.beginPath();
+      ctx.lineWidth = o.grid.outlineWidth;
+      ctx.strokeStyle = o.grid.color;
+      ctx.lineJoin = 'round';
+      
+      for(var i = 0; i <= sides; ++i){
+        ctx[i == 0 ? 'moveTo' : 'lineTo'](Math.cos(i*coeff+angle)*radius, Math.sin(i*coeff+angle)*radius);
+      }
+      //ctx.arc(0, 0, radius, 0, Math.PI*2, true);
+
+      ctx.stroke();
+    }
+    else {
+      ctx.translate(this.plotOffset.left, this.plotOffset.top);
+      
+      // Draw axis/grid border.
+      var lw = o.grid.outlineWidth,
+          orig = 0.5-lw+((lw+1)%2/2);
+      ctx.lineWidth = lw;
+      ctx.strokeStyle = o.grid.color;
+      ctx.lineJoin = 'miter';
+      ctx.strokeRect(orig, orig, this.plotWidth, this.plotHeight);
+    }
+    
+    ctx.restore();
+	},
+	/**
+	 * Draws labels for x and y axis.
+	 */   
+	drawLabels: function(){		
+		// Construct fixed width label boxes, which can be styled easily. 
+		var noLabels = 0, axis,
+		    xBoxWidth, i, html, tick, left, top,
+		    options = this.options,
+		    ctx = this.ctx,
+		    a = this.axes;
+		
+		for(i = 0; i < a.x.ticks.length; ++i){
+			if (a.x.ticks[i].label) {
+				++noLabels;
+			}
+		}
+		xBoxWidth = this.plotWidth / noLabels;
+		
+		if (options.grid.circular) {
+			ctx.save();
+			ctx.translate(this.plotOffset.left+this.plotWidth/2, this.plotOffset.top+this.plotHeight/2);
+			var radius = this.plotHeight*options.radar.radiusRatio/2 + options.fontSize,
+			    sides = this.axes.x.ticks.length,
+					coeff = 2*(Math.PI/sides),
+					angle = -Math.PI/2;
+			
+			var style = {
+				size: options.fontSize
+			};
+
+			// Add x labels.
+			axis = a.x;
+			style.color = axis.options.color || options.grid.color;
+			for(i = 0; i < axis.ticks.length && axis.options.showLabels; ++i){
+				tick = axis.ticks[i];
+				tick.label += '';
+				if(!tick.label || tick.label.length == 0) continue;
+				
+				var x = Math.cos(i*coeff+angle) * radius, 
+				    y = Math.sin(i*coeff+angle) * radius;
+						
+				style.angle = Flotr.toRad(axis.options.labelsAngle);
+				style.textBaseline = 'middle';
+				style.textAlign = (Math.abs(x) < 0.1 ? 'center' : (x < 0 ? 'right' : 'left'));
+
+				Flotr.drawText(ctx, tick.label, x, y, style);
+			}
+			
+			// Add y labels.
+			axis = a.y;
+			style.color = axis.options.color || options.grid.color;
+			for(i = 0; i < axis.ticks.length && axis.options.showLabels; ++i){
+				tick = axis.ticks[i];
+				tick.label += '';
+				if(!tick.label || tick.label.length == 0) continue;
+				
+				style.angle = Flotr.toRad(axis.options.labelsAngle);
+				style.textBaseline = 'middle';
+				style.textAlign = 'left';
+				
+				Flotr.drawText(ctx, tick.label, 3, -(axis.ticks[i].v / axis.max) * (radius - options.fontSize), style);
+			}
+			ctx.restore();
+			return;
+		}
+    
+		if (!options.HtmlText && this.textEnabled) {
+			var style = {
+				size: options.fontSize
+			};
+	
+			// Add x labels.
+			axis = a.x;
+			style.color = axis.options.color || options.grid.color;
+			for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){
+				tick = axis.ticks[i];
+				if(!tick.label || tick.label.length == 0) continue;
+				
+				left = axis.d2p(tick.v);
+				if (left < 0 || left > this.plotWidth) continue;
+        
+				style.angle = Flotr.toRad(axis.options.labelsAngle);
+				style.textAlign = 'center';
+				style.textBaseline = 'top';
+				style = Flotr.getBestTextAlign(style.angle, style);
+				
+				Flotr.drawText(
+					ctx, tick.label,
+					this.plotOffset.left + left, 
+					this.plotOffset.top + this.plotHeight + options.grid.labelMargin,
+					style
+				);
+			}
+			  
+			// Add x2 labels.
+			axis = a.x2;
+			style.color = axis.options.color || options.grid.color;
+			for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){
+				tick = axis.ticks[i];
+				if(!tick.label || tick.label.length == 0) continue;
+        
+				left = axis.d2p(tick.v);
+				if(left < 0 || left > this.plotWidth) continue;
+        
+				style.angle = Flotr.toRad(axis.options.labelsAngle);
+				style.textAlign = 'center';
+				style.textBaseline = 'bottom';
+				style = Flotr.getBestTextAlign(style.angle, style);
+				
+				Flotr.drawText(
+					ctx, tick.label,
+					this.plotOffset.left + left, 
+					this.plotOffset.top + options.grid.labelMargin,
+					style
+				);
+			}
+			  
+			// Add y labels.
+			axis = a.y;
+			style.color = axis.options.color || options.grid.color;
+			for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){
+				tick = axis.ticks[i];
+				if (!tick.label || tick.label.length == 0) continue;
+        
+				top = axis.d2p(tick.v);
+				if(top < 0 || top > this.plotHeight) continue;
+				
+				style.angle = Flotr.toRad(axis.options.labelsAngle);
+				style.textAlign = 'right';
+				style.textBaseline = 'middle';
+				style = Flotr.getBestTextAlign(style.angle, style);
+				
+				Flotr.drawText(
+					ctx, tick.label,
+					this.plotOffset.left - options.grid.labelMargin, 
+					this.plotOffset.top + top,
+					style
+				);
+			}
+			  
+			// Add y2 labels.
+			axis = a.y2;
+			style.color = axis.options.color || options.grid.color;
+			for(i = 0; i < axis.ticks.length && axis.options.showLabels && axis.used; ++i){
+				tick = axis.ticks[i];
+				if (!tick.label || tick.label.length == 0) continue;
+        
+				top = axis.d2p(tick.v);
+				if(top < 0 || top > this.plotHeight) continue;
+        
+				style.angle = Flotr.toRad(axis.options.labelsAngle);
+				style.textAlign = 'left';
+				style.textBaseline = 'middle';
+				style = Flotr.getBestTextAlign(style.angle, style);
+				
+				Flotr.drawText(
+					ctx, tick.label,
+					this.plotOffset.left + this.plotWidth + options.grid.labelMargin, 
+					this.plotOffset.top + top,
+					style
+				);
+				
+				ctx.save();
+				ctx.strokeStyle = style.color;
+				ctx.beginPath();
+				ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + axis.d2p(tick.v));
+				ctx.lineTo(this.plotOffset.left + this.plotWidth,     this.plotOffset.top + axis.d2p(tick.v));
+				ctx.stroke();
+				ctx.restore();
+			}
+		} 
+		else if (a.x.options.showLabels || a.x2.options.showLabels || a.y.options.showLabels || a.y2.options.showLabels) {
+			html = ['<div style="font-size:smaller;color:' + options.grid.color + ';" class="flotr-labels">'];
+			
+			// Add x labels.
+			axis = a.x;
+			if (axis.options.showLabels){
+				for(i = 0; i < axis.ticks.length; ++i){
+					tick = axis.ticks[i];
+					if(!tick.label || tick.label.length == 0 || 
+					    (this.plotOffset.left + axis.d2p(tick.v) < 0) || 
+					    (this.plotOffset.left + axis.d2p(tick.v) > this.canvasWidth)) continue;
+					
+					html.push(
+					  '<div style="position:absolute;top:', 
+					  (this.plotOffset.top + this.plotHeight + options.grid.labelMargin), 'px;left:', 
+					  (this.plotOffset.left +axis.d2p(tick.v) - xBoxWidth/2), 'px;width:', 
+					  xBoxWidth, 'px;text-align:center;', (axis.options.color?('color:'+axis.options.color+';'):''), 
+					  '" class="flotr-grid-label">', tick.label, '</div>'
+					);
+				}
+			}
+			
+			// Add x2 labels.
+			axis = a.x2;
+			if (axis.options.showLabels && axis.used){
+				for(i = 0; i < axis.ticks.length; ++i){
+					tick = axis.ticks[i];
+					if(!tick.label || tick.label.length == 0 || 
+					    (this.plotOffset.left + axis.d2p(tick.v) < 0) || 
+					    (this.plotOffset.left + axis.d2p(tick.v) > this.canvasWidth)) continue;
+					
+					html.push(
+					  '<div style="position:absolute;top:', 
+					  (this.plotOffset.top - options.grid.labelMargin - axis.maxLabel.height), 'px;left:', 
+					  (this.plotOffset.left + axis.d2p(tick.v) - xBoxWidth/2), 'px;width:', 
+					  xBoxWidth, 'px;text-align:center;', (axis.options.color?('color:'+axis.options.color+';'):''), 
+					  '" class="flotr-grid-label">', tick.label, '</div>'
+					);
+				}
+			}
+			
+			// Add y labels.
+			axis = a.y;
+			if (axis.options.showLabels){
+				for(i = 0; i < axis.ticks.length; ++i){
+					tick = axis.ticks[i];
+					if (!tick.label || tick.label.length == 0 ||
+							 (this.plotOffset.top + axis.d2p(tick.v) < 0) || 
+							 (this.plotOffset.top + axis.d2p(tick.v) > this.canvasHeight)) continue;
+					
+					html.push(
+					  '<div style="position:absolute;top:', 
+					  (this.plotOffset.top + axis.d2p(tick.v) - axis.maxLabel.height/2), 'px;left:0;width:', 
+					  (this.plotOffset.left - options.grid.labelMargin), 'px;text-align:right;', 
+					  (axis.options.color?('color:'+axis.options.color+';'):''), 
+					  '" class="flotr-grid-label flotr-grid-label-y">', tick.label, '</div>'
+					);
+				}
+			}
+			
+			// Add y2 labels.
+			axis = a.y2;
+			if (axis.options.showLabels && axis.used){
+				ctx.save();
+				ctx.strokeStyle = axis.options.color || options.grid.color;
+				ctx.beginPath();
+				
+				for(i = 0; i < axis.ticks.length; ++i){
+					tick = axis.ticks[i];
+					if (!tick.label || tick.label.length == 0 ||
+							 (this.plotOffset.top + axis.d2p(tick.v) < 0) || 
+							 (this.plotOffset.top + axis.d2p(tick.v) > this.canvasHeight)) continue;
+					
+					html.push(
+					  '<div style="position:absolute;top:', 
+					  (this.plotOffset.top + axis.d2p(tick.v) - axis.maxLabel.height/2), 'px;right:0;width:', 
+					  (this.plotOffset.right - options.grid.labelMargin), 'px;text-align:left;', 
+					  (axis.options.color?('color:'+axis.options.color+';'):''), 
+					  '" class="flotr-grid-label flotr-grid-label-y">', tick.label, '</div>'
+					);
+
+					ctx.moveTo(this.plotOffset.left + this.plotWidth - 8, this.plotOffset.top + axis.d2p(tick.v));
+					ctx.lineTo(this.plotOffset.left + this.plotWidth,     this.plotOffset.top + axis.d2p(tick.v));
+				}
+				ctx.stroke();
+				ctx.restore();
+			}
+			
+			html.push('</div>');
+			this.el.insert(html.join(''));
+		}
+	},
+	/**
+	 * Draws the title and the subtitle
+	 */
+	drawTitles: function(){
+		var html,
+		    options = this.options,
+		    margin = options.grid.labelMargin,
+		    ctx = this.ctx,
+		    a = this.axes;
+		
+		if (!options.HtmlText && this.textEnabled) {
+			var style = {
+				size: options.fontSize,
+				color: options.grid.color,
+				textAlign: 'center'
+			};
+			
+			// Add subtitle
+			if (options.subtitle){
+				Flotr.drawText(
+					ctx, options.subtitle,
+					this.plotOffset.left + this.plotWidth/2, 
+					this.titleHeight + this.subtitleHeight - 2,
+					style
+				);
+			}
+			
+			style.weight = 1.5;
+			style.size *= 1.5;
+			
+			// Add title
+			if (options.title){
+				Flotr.drawText(
+					ctx, options.title,
+					this.plotOffset.left + this.plotWidth/2, 
+					this.titleHeight - 2,
+					style
+				);
+			}
+			
+			style.weight = 1.8;
+			style.size *= 0.8;
+			
+			// Add x axis title
+			if (a.x.options.title && a.x.used){
+				style.textAlign = 'center';
+				style.textBaseline = 'top';
+				style.angle = Flotr.toRad(a.x.options.titleAngle);
+				style = Flotr.getBestTextAlign(style.angle, style);
+				Flotr.drawText(
+					ctx, a.x.options.title,
+					this.plotOffset.left + this.plotWidth/2, 
+					this.plotOffset.top + a.x.maxLabel.height + this.plotHeight + 2 * margin,
+					style
+				);
+			}
+			
+			// Add x2 axis title
+			if (a.x2.options.title && a.x2.used){
+				style.textAlign = 'center';
+				style.textBaseline = 'bottom';
+				style.angle = Flotr.toRad(a.x2.options.titleAngle);
+				style = Flotr.getBestTextAlign(style.angle, style);
+				Flotr.drawText(
+					ctx, a.x2.options.title,
+					this.plotOffset.left + this.plotWidth/2, 
+					this.plotOffset.top - a.x2.maxLabel.height - 2 * margin,
+					style
+				);
+			}
+			
+			// Add y axis title
+			if (a.y.options.title && a.y.used){
+				style.textAlign = 'right';
+				style.textBaseline = 'middle';
+				style.angle = Flotr.toRad(a.y.options.titleAngle);
+				style = Flotr.getBestTextAlign(style.angle, style);
+				Flotr.drawText(
+					ctx, a.y.options.title,
+					this.plotOffset.left - a.y.maxLabel.width - 2 * margin, 
+					this.plotOffset.top + this.plotHeight / 2,
+					style
+				);
+			}
+			
+			// Add y2 axis title
+			if (a.y2.options.title && a.y2.used){
+				style.textAlign = 'left';
+				style.textBaseline = 'middle';
+				style.angle = Flotr.toRad(a.y2.options.titleAngle);
+				style = Flotr.getBestTextAlign(style.angle, style);
+				Flotr.drawText(
+					ctx, a.y2.options.title,
+					this.plotOffset.left + this.plotWidth + a.y2.maxLabel.width + 2 * margin, 
+					this.plotOffset.top + this.plotHeight / 2,
+					style
+				);
+			}
+		} 
+		else {
+			html = ['<div style="color:'+options.grid.color+';" class="flotr-titles">'];
+			
+			// Add title
+			if (options.title)
+				html.push(
+				  '<div style="position:absolute;top:0;left:', 
+				  this.plotOffset.left, 'px;font-size:1em;font-weight:bold;text-align:center;width:',
+				  this.plotWidth,'px;" class="flotr-title">', options.title, '</div>'
+				);
+			
+			// Add subtitle
+			if (options.subtitle)
+				html.push(
+				  '<div style="position:absolute;top:', this.titleHeight, 'px;left:', 
+				  this.plotOffset.left, 'px;font-size:smaller;text-align:center;width:',
+				  this.plotWidth, 'px;" class="flotr-subtitle">', options.subtitle, '</div>'
+				);
+
+			html.push('</div>');
+			
+			html.push('<div class="flotr-axis-title" style="font-weight:bold;">');
+			
+			// Add x axis title
+			if (a.x.options.title && a.x.used)
+				html.push(
+				  '<div style="position:absolute;top:', 
+				  (this.plotOffset.top + this.plotHeight + options.grid.labelMargin + a.x.titleSize.height), 
+				  'px;left:', this.plotOffset.left, 'px;width:', this.plotWidth, 
+				  'px;text-align:center;" class="flotr-axis-title">', a.x.options.title, '</div>'
+				);
+			
+			// Add x2 axis title
+			if (a.x2.options.title && a.x2.used)
+				html.push(
+				  '<div style="position:absolute;top:0;left:', this.plotOffset.left, 'px;width:', 
+				  this.plotWidth, 'px;text-align:center;" class="flotr-axis-title">', a.x2.options.title, '</div>'
+				);
+			
+			// Add y axis title
+			if (a.y.options.title && a.y.used)
+				html.push(
+				  '<div style="position:absolute;top:', 
+				  (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
+				  'px;left:0;text-align:right;" class="flotr-axis-title">', a.y.options.title, '</div>'
+				);
+			
+			// Add y2 axis title
+			if (a.y2.options.title && a.y2.used)
+				html.push(
+				  '<div style="position:absolute;top:', 
+				  (this.plotOffset.top + this.plotHeight/2 - a.y.titleSize.height/2), 
+				  'px;right:0;text-align:right;" class="flotr-axis-title">', a.y2.options.title, '</div>'
+				);
+			
+			html.push('</div>');
+			
+			this.el.insert(html.join(''));
+		}
+	},
+	/**
+	 * Actually draws the graph.
+	 * @param {Object} series - series to draw
+	 */
+	drawSeries: function(series){
+		series = series || this.series;
+		
+		var drawn = false;
+		for(type in Flotr.graphTypes){
+			if(series[type] && series[type].show){
+				drawn = true;
+				this[type].draw(series);
+			}
+		}
+		
+		if(!drawn){
+			this[this.options.defaultType].draw(series);
+		}
+	},
+	/**
+	 * Adds a legend div to the canvas container or draws it on the canvas.
+	 */
+	insertLegend: function(){
+		if(!this.options.legend.show)
+			return;
+			
+		var series = this.series,
+			plotOffset = this.plotOffset,
+			options = this.options,
+			legend = options.legend,
+			fragments = [],
+			rowStarted = false, 
+			ctx = this.ctx,
+			i;
+			
+		var noLegendItems = series.findAll(function(s) {return (s.label && !s.hide)}).length;
+
+		if (noLegendItems) {
+		    if (!options.HtmlText && this.textEnabled && !$(legend.container)) {
+				var style = {
+					size: options.fontSize*1.1,
+					color: options.grid.color
+				};
+
+				var p = legend.position, 
+				    m = legend.margin,
+				    lbw = legend.labelBoxWidth,
+				    lbh = legend.labelBoxHeight,
+				    lbm = legend.labelBoxMargin,
+				    offsetX = plotOffset.left + m,
+				    offsetY = plotOffset.top + m;
+				
+				// We calculate the labels' max width
+				var labelMaxWidth = 0;
+				for(i = series.length - 1; i > -1; --i){
+					if(!series[i].label || series[i].hide) continue;
+					var label = legend.labelFormatter(series[i].label);
+					labelMaxWidth = Math.max(labelMaxWidth, Flotr.measureText(ctx, label, style).width);
+				}
+				
+				var legendWidth  = Math.round(lbw + lbm*3 + labelMaxWidth),
+				    legendHeight = Math.round(noLegendItems*(lbm+lbh) + lbm);
+				
+				if(p.charAt(0) == 's') offsetY = plotOffset.top + this.plotHeight - (m + legendHeight);
+				if(p.charAt(1) == 'e') offsetX = plotOffset.left + this.plotWidth - (m + legendWidth);
+				
+				// Legend box
+				var color = this.processColor(options.legend.backgroundColor || 'rgb(240,240,240)', {opacity: options.legend.backgroundOpacity || 0.1});
+				
+				ctx.fillStyle = color;
+				ctx.fillRect(offsetX, offsetY, legendWidth, legendHeight);
+				ctx.strokeStyle = options.legend.labelBoxBorderColor;
+				ctx.strokeRect(Flotr.toPixel(offsetX), Flotr.toPixel(offsetY), legendWidth, legendHeight);
+				
+				// Legend labels
+				var x = offsetX + lbm;
+				var y = offsetY + lbm;
+				for(i = 0; i < series.length; i++){
+					if(!series[i].label || series[i].hide) continue;
+					var label = legend.labelFormatter(series[i].label);
+					
+					ctx.fillStyle = series[i].color;
+					ctx.fillRect(x, y, lbw-1, lbh-1);
+					
+					ctx.strokeStyle = legend.labelBoxBorderColor;
+					ctx.lineWidth = 1;
+					ctx.strokeRect(Math.ceil(x)-1.5, Math.ceil(y)-1.5, lbw+2, lbh+2);
+					
+					// Legend text
+					Flotr.drawText(ctx, label, x + lbw + lbm, y + (lbh + style.size - ctx.fontDescent(style))/2, style);
+					
+					y += lbh + lbm;
+				}
+		    }
+		    else {
+		  		for(i = 0; i < series.length; ++i){
+		  			if(!series[i].label || series[i].hide) continue;
+		  			
+		  			if(i % options.legend.noColumns == 0){
+		  				fragments.push(rowStarted ? '</tr><tr>' : '<tr>');
+		  				rowStarted = true;
+		  			}
+		  
+		  			var s = series[i],
+		  			    label = legend.labelFormatter(s.label),
+		  			    boxWidth = legend.labelBoxWidth,
+		  			    boxHeight = legend.labelBoxHeight,
+		  			    opacity = 'opacity:' + s.bars.fillOpacity + ';filter:alpha(opacity=' + s.bars.fillOpacity*100 + ');',
+		  			    color = 'background-color:' + ((s.bars.show && s.bars.fillColor && s.bars.fill) ? s.bars.fillColor : s.color) + ';';
+		  			
+					fragments.push(
+						'<td class="flotr-legend-color-box">',
+							'<div style="border:1px solid ', legend.labelBoxBorderColor, ';padding:1px">',
+								'<div style="width:', (boxWidth-1), 'px;height:', (boxHeight-1), 'px;border:1px solid ', series[i].color, '">', // Border
+									'<div style="width:', boxWidth, 'px;height:', boxHeight, 'px;', opacity, color, '"></div>', // Background
+								'</div>',
+							'</div>',
+						'</td>',
+						'<td class="flotr-legend-label">', label, '</td>'
+					);
+		  		}
+		  		if(rowStarted) fragments.push('</tr>');
+		  		
+		  		if(fragments.length > 0){
+		  			var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join('') + '</table>';
+		  			if(options.legend.container != null){
+		  				$(options.legend.container).innerHTML = table;
+		  			}
+				    else {
+		  				var pos = '', p = options.legend.position, m = options.legend.margin;
+		  				
+		  				     if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
+		  				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';					
+		  				     if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
+		  				else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
+		  				     
+		  				var div = this.el.insert('<div class="flotr-legend" style="position:absolute;z-index:2;' + pos +'">' + table + '</div>').select('div.flotr-legend')[0];
+		  				
+		  				if(options.legend.backgroundOpacity != 0.0){
+		  					/**
+		  					 * Put in the transparent background separately to avoid blended labels and
+		  					 * label boxes.
+		  					 */
+		  					var c = options.legend.backgroundColor;
+		  					if(c == null){
+		  						var tmp = (options.grid.backgroundColor != null) ? options.grid.backgroundColor : Flotr.Color.extract(div);
+		  						c = this.processColor(tmp, null, {opacity: 1});
+		  					}
+		  					this.el.insert(
+		  					  '<div class="flotr-legend-bg" style="position:absolute;width:' + div.getWidth() + 
+		  					  'px;height:' + div.getHeight() + 'px;' + pos +'background-color:' + c + ';"> </div>'
+		  					)
+		  					.select('div.flotr-legend-bg')[0].setOpacity(options.legend.backgroundOpacity);
+		  				}
+		  			}
+		  		}
+		    }
+	    }
+	},
+	/**
+	 * Calculates the coordinates from a mouse event object.
+	 * @param {Event} event - Mouse Event object.
+	 * @return {Object} Object with coordinates of the mouse.
+	 */
+	getEventPosition: function (event){
+		var offset = this.overlay.cumulativeOffset(),
+		    pointer = Event.pointer(event),
+		    rx = (pointer.x - offset.left - this.plotOffset.left),
+		    ry = (pointer.y - offset.top - this.plotOffset.top);
+    
+		return {
+			x:  this.axes.x.p2d(rx),
+			x2: this.axes.x2.p2d(rx),
+			y:  this.axes.y.p2d(ry),
+			y2: this.axes.y2.p2d(ry),
+			relX: rx,
+			relY: ry,
+			absX: pointer.x,
+			absY: pointer.y
+		};
+	},
+	/**
+	 * Observes the 'click' event and fires the 'flotr:click' event.
+	 * @param {Event} event - 'click' Event object.
+	 */
+	clickHandler: function(event){
+		if(this.ignoreClick){
+			return this.ignoreClick = false;
+		}
+		this.el.fire('flotr:click', [this.getEventPosition(event), this]);
+	},
+	/**
+	 * Observes mouse movement over the graph area. Fires the 'flotr:mousemove' event.
+	 * @param {Event} event - 'mousemove' Event object.
+	 */
+	mouseMoveHandler: function(event){
+ 		var pos = this.getEventPosition(event);
+    
+		this.lastMousePos.pageX = pos.absX;
+		this.lastMousePos.pageY = pos.absY;	
+    	
+    	//@todo Add another overlay for the crosshair
+		if (this.options.crosshair.mode)
+			this.clearCrosshair();
+			
+		if(this.selectionInterval == null && (this.options.mouse.track || this.series.any(function(s){return s.mouse && s.mouse.track;})))
+			this.hit(pos);
+			//this.newHit(pos);
+		
+		if (this.options.crosshair.mode)
+			this.drawCrosshair(pos);
+    
+		this.el.fire('flotr:mousemove', [event, pos, this]);
+	},
+	/**
+	 * Observes the 'mousedown' event.
+	 * @param {Event} event - 'mousedown' Event object.
+	 */
+	mouseDownHandler: function (event){
+		if(event.isRightClick()) {
+			event.stop();
+			var overlay = this.overlay;
+			
+			overlay.hide();
+			
+			function cancelContextMenu () {
+				overlay.show();
+				document.stopObserving('mousemove', cancelContextMenu);
+			}
+			document.observe('mousemove', cancelContextMenu);
+			return;
+		}
+    
+		if(!this.options.selection.mode || !event.isLeftClick()) return;
+		
+		this.setSelectionPos(this.selection.first, event);
+		if(this.selectionInterval != null){
+			clearInterval(this.selectionInterval);
+		}
+		this.lastMousePos.pageX = null;
+		this.selectionInterval = setInterval(this.updateSelection.bindAsEventListener(this), 1000/this.options.selection.fps);
+		
+		this.mouseUpHandler = this.mouseUpHandler.bindAsEventListener(this);
+		document.observe('mouseup', this.mouseUpHandler);
+	},
+	/**
+	 * Fires the 'flotr:select' event when the user made a selection.
+	 */
+	fireSelectEvent: function(){
+		var a = this.axes, s = this.selection,
+		    x1, x2, y1, y2;
+		
+		x1 = a.x.p2d(s.first.x);
+		x2 = a.x.p2d(s.second.x);
+		y1 = a.y.p2d(s.first.y);
+		y2 = a.y.p2d(s.second.y);
+
+		this.el.fire('flotr:select', [{
+			x1:Math.min(x1, x2), 
+			y1:Math.min(y1, y2), 
+			x2:Math.max(x1, x2), 
+			y2:Math.max(y1, y2),
+			xfirst:x1, xsecond:x2, yfirst:y1, ysecond:y2
+		}, this]);
+	},
+	/**
+	 * Observes the mouseup event for the document. 
+	 * @param {Event} event - 'mouseup' Event object.
+	 */
+	mouseUpHandler: function(event){
+		document.stopObserving('mouseup', this.mouseUpHandler);
+		event.stop();
+    
+		if(this.selectionInterval != null){
+			clearInterval(this.selectionInterval);
+			this.selectionInterval = null;
+		}
+
+		this.setSelectionPos(this.selection.second, event);
+		this.clearSelection();
+		
+		if(this.selectionIsSane()){
+			this.drawSelection();
+			this.fireSelectEvent();
+			this.ignoreClick = true;
+		}
+	},
+	/**
+	 * Calculates the position of the selection.
+	 * @param {Object} pos - Position object.
+	 * @param {Event} event - Event object.
+	 */
+	setSelectionPos: function(pos, event) {
+		var options = this.options,
+		    offset = this.overlay.cumulativeOffset();
+		
+		if(options.selection.mode.indexOf('x') == -1){
+			pos.x = (pos == this.selection.first) ? 0 : this.plotWidth;			   
+		}else{
+			pos.x = event.pageX - offset.left - this.plotOffset.left;
+			pos.x = Math.min(Math.max(0, pos.x), this.plotWidth);
+		}
+
+		if (options.selection.mode.indexOf('y') == -1){
+			pos.y = (pos == this.selection.first) ? 0 : this.plotHeight;
+		}else{
+			pos.y = event.pageY - offset.top - this.plotOffset.top;
+			pos.y = Math.min(Math.max(0, pos.y), this.plotHeight);
+		}
+	},
+	/**
+	 * Updates (draws) the selection box.
+	 */
+	updateSelection: function(){
+		if(this.lastMousePos.pageX == null) return;
+		
+		this.setSelectionPos(this.selection.second, this.lastMousePos);
+		this.clearSelection();
+		
+		if(this.selectionIsSane()) this.drawSelection();
+	},
+	/**
+	 * Removes the selection box from the overlay canvas.
+	 */
+	clearSelection: function() {
+		if(this.prevSelection == null) return;
+			
+		var prevSelection = this.prevSelection,
+			lw = this.octx.lineWidth,
+			plotOffset = this.plotOffset,
+			x = Math.min(prevSelection.first.x, prevSelection.second.x),
+			y = Math.min(prevSelection.first.y, prevSelection.second.y),
+			w = Math.abs(prevSelection.second.x - prevSelection.first.x),
+			h = Math.abs(prevSelection.second.y - prevSelection.first.y);
+		
+		this.octx.clearRect(x + plotOffset.left - lw/2+0.5,
+		                    y + plotOffset.top - lw/2+0.5,
+		                    w + lw,
+		                    h + lw);
+		
+		this.prevSelection = null;
+	},
+	/**
+	 * Allows the user the manually select an area.
+	 * @param {Object} area - Object with coordinates to select.
+	 */
+	setSelection: function(area, preventEvent){
+		var options = this.options,
+			xa = this.axes.x,
+			ya = this.axes.y,
+			vertScale = ya.scale,
+			hozScale = xa.scale,
+			selX = options.selection.mode.indexOf('x') != -1,
+			selY = options.selection.mode.indexOf('y') != -1;
+		
+		this.clearSelection();
+
+		this.selection.first.y  = (selX && !selY) ? 0 : (ya.max - area.y1) * vertScale;
+		this.selection.second.y = (selX && !selY) ? this.plotHeight : (ya.max - area.y2) * vertScale;			
+		this.selection.first.x  = (selY && !selX) ? 0 : (area.x1 - xa.min) * hozScale;
+		this.selection.second.x = (selY && !selX) ? this.plotWidth : (area.x2 - xa.min) * hozScale;
+		
+		this.drawSelection();
+		if (!preventEvent)
+			this.fireSelectEvent();
+	},
+	/**
+	 * Draws the selection box.
+	 */
+	drawSelection: function() {
+		var prevSelection = this.prevSelection,
+			s = this.selection,
+			octx = this.octx,
+			options = this.options,
+			plotOffset = this.plotOffset;
+		
+		if(prevSelection != null &&
+			s.first.x == prevSelection.first.x &&
+			s.first.y == prevSelection.first.y && 
+			s.second.x == prevSelection.second.x &&
+			s.second.y == prevSelection.second.y)
+			return;
+
+		octx.save();
+		octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8});
+		octx.lineWidth = 1;
+		octx.lineJoin = 'miter';
+		octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4});
+
+		this.prevSelection = {
+			first: { x: s.first.x, y: s.first.y },
+			second: { x: s.second.x, y: s.second.y }
+		};
+
+		var x = Math.min(s.first.x, s.second.x),
+		    y = Math.min(s.first.y, s.second.y),
+		    w = Math.abs(s.second.x - s.first.x),
+		    h = Math.abs(s.second.y - s.first.y);
+		
+		octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
+		octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h);
+		octx.restore();
+	},
+	/**	 
+	 * Draws the selection box.
+	 */
+	drawCrosshair: function(pos) {
+		var octx = this.octx,
+			options = this.options,
+			plotOffset = this.plotOffset,
+			x = plotOffset.left+pos.relX+0.5,
+			y = plotOffset.top+pos.relY+0.5;
+		
+		if (pos.relX < 0 || pos.relY < 0 || pos.relX > this.plotWidth || pos.relY > this.plotHeight) {
+			this.el.style.cursor = null;
+			this.el.removeClassName('flotr-crosshair');
+			return; 
+		}
+		
+		this.lastMousePos.relX = null;
+		this.lastMousePos.relY = null;
+		
+		if (options.crosshair.hideCursor) {
+			this.el.style.cursor = Prototype.Browser.Gecko ? 'none' :'url(blank.cur),crosshair';
+			this.el.addClassName('flotr-crosshair');
+		}
+		
+		octx.save();
+		octx.strokeStyle = options.crosshair.color;
+		octx.lineWidth = 1;
+		octx.beginPath();
+		
+		if (options.crosshair.mode.indexOf('x') != -1) {
+			octx.moveTo(x, plotOffset.top);
+			octx.lineTo(x, plotOffset.top + this.plotHeight);
+			this.lastMousePos.relX = x;
+		}
+		
+		if (options.crosshair.mode.indexOf('y') != -1) {
+			octx.moveTo(plotOffset.left, y);
+			octx.lineTo(plotOffset.left + this.plotWidth, y);
+			this.lastMousePos.relY = y;
+		}
+		
+		octx.stroke();
+		octx.restore();
+	},
+	/**
+	 * Removes the selection box from the overlay canvas.
+	 */
+	clearCrosshair: function() {
+		if (this.lastMousePos.relX != null)
+			this.octx.clearRect(this.lastMousePos.relX-0.5, this.plotOffset.top, 1,this.plotHeight+1);
+		
+		if (this.lastMousePos.relY != null)
+			this.octx.clearRect(this.plotOffset.left, this.lastMousePos.relY-0.5, this.plotWidth+1, 1);		
+	},
+	/**
+	 * Determines whether or not the selection is sane and should be drawn.
+	 * @return {Boolean} - True when sane, false otherwise.
+	 */
+	selectionIsSane: function(){
+		return Math.abs(this.selection.second.x - this.selection.first.x) >= 5 &&
+		       Math.abs(this.selection.second.y - this.selection.first.y) >= 5;
+	},
+	/**
+	 * Removes the mouse tracking point from the overlay.
+	 */
+	clearHit: function(){
+		if(!this.prevHit) return;
+    
+		var prevHit = this.prevHit,
+		    plotOffset = this.plotOffset,
+		    s = prevHit.series,
+		    lw = s.bars.lineWidth,
+		    xa = prevHit.xaxis,
+		    ya = prevHit.yaxis;
+				
+		if(!s.bars.show){
+			var r = s.points.radius;
+			this.octx.clearRect(
+				xa.d2p(prevHit.x) + plotOffset.left - r*2,
+				ya.d2p(prevHit.y) + plotOffset.top - r*2,
+				r*3 + s.points.lineWidth*3, 
+				r*3 + s.points.lineWidth*3
+			);
+		}
+
+		else {
+			var bw = s.bars.barWidth;
+			this.octx.clearRect(
+				xa.d2p(prevHit.x - bw/2) + plotOffset.left - lw, 
+				ya.d2p(prevHit.y >= 0 ? prevHit.y : 0) + plotOffset.top - lw, 
+				xa.d2p(bw) + lw * 2, 
+				ya.d2p(prevHit.y < 0 ? prevHit.y : 0) + lw * 2
+			);
+		}
+	},
+	/**
+	 * Updates the mouse tracking point on the overlay.
+	 */
+	drawHit: function(n){
+		var octx = this.octx,
+		    s = n.series,
+		    xa = n.xaxis,
+		    ya = n.yaxis;
+
+		if(s.mouse.lineColor != null){
+			octx.save();
+			octx.lineWidth = s.points.lineWidth;
+			octx.strokeStyle = s.mouse.lineColor;
+			octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
+      
+			if(!s.bars.show){
+				octx.translate(this.plotOffset.left, this.plotOffset.top);
+				octx.beginPath();
+  				octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.mouse.radius, 0, 2 * Math.PI, true);
+  				octx.fill();
+  				octx.stroke();
+				octx.closePath();
+			}
+
+			else {
+  				octx.save();
+  				octx.translate(this.plotOffset.left, this.plotOffset.top);
+  				octx.beginPath();
+        
+  				if (s.mouse.trackAll) {
+  					octx.moveTo(xa.d2p(n.x), ya.d2p(0));
+  					octx.lineTo(xa.d2p(n.x), ya.d2p(n.yaxis.max));
+				}
+				else {
+  					var bw = s.bars.barWidth;
+  					
+  					octx.moveTo(xa.d2p(n.x-(bw/2)), ya.d2p(0));
+					octx.lineTo(xa.d2p(n.x-(bw/2)), ya.d2p(n.y));
+  					octx.lineTo(xa.d2p(n.x+(bw/2)), ya.d2p(n.y));
+  					octx.lineTo(xa.d2p(n.x+(bw/2)), ya.d2p(0));
+  
+  					if(s.mouse.fillColor) octx.fill();
+				}
+
+  				octx.stroke();
+  				octx.closePath();
+  				octx.restore();
+			}
+			octx.restore();
+		}
+		this.prevHit = n;
+	},
+	newHit: function(mouse){
+		var series = this.series,
+			options = this.options,
+			decimals, label;
+
+		for(var i = series.length-1; i > -1; --i){
+			s = series[i];
+			if(!s.mouse.track) continue;
+
+			for(var type in Flotr.graphTypes){
+				if (!this[type].getHit) continue;
+				
+				var h = this[type].getHit(s, mouse);
+				if (h.index !== undefined) {
+					decimals = s.mouse.trackDecimals;
+					if(decimals == null || decimals < 0) decimals = 0;
+					
+					label = s.mouse.trackFormatter(h);
+					this.drawTooltip(label, h.x, h.y, s.mouse);
+					this.mouseTrack.fire('flotr:hit', [h, this]);
+				}
+			}
+		}
+	},
+	/**
+	 * Retrieves the nearest data point from the mouse cursor. If it's within
+	 * a certain range, draw a point on the overlay canvas and display the x and y
+	 * value of the data.
+	 * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor.
+	 */
+	hit: function(mouse){
+		var series = this.series,
+			options = this.options,
+			prevHit = this.prevHit,
+			plotOffset = this.plotOffset,
+			octx = this.octx, 
+			data, sens, xsens, ysens, x, y, xa, ya, mx, my, i,
+			/**
+			 * Nearest data element.
+			 */
+			n = {
+				dist:Number.MAX_VALUE,
+				x:null,
+				y:null,
+				relX:mouse.relX,
+				relY:mouse.relY,
+				absX:mouse.absX,
+				absY:mouse.absY,
+				mouse:null,
+				xaxis:null,
+				yaxis:null,
+				series:null,
+				index:null,
+				seriesIndex:null
+			};
+
+		if (options.mouse.trackAll) {
+			for(i = 0; i < series.length; i++){
+				s = series[0];
+				data = s.data;
+				xa = s.xaxis;
+				ya = s.yaxis;
+				xsens = (2*options.points.lineWidth)/xa.scale * s.mouse.sensibility;
+				mx = xa.p2d(mouse.relX);
+				my = ya.p2d(mouse.relY);
+		
+				for(var j = 0; j < data.length; j++){
+					x = data[j][0];
+					y = data[j][1];
+		
+					if (y === null ||
+							xa.min > x || xa.max < x ||
+							ya.min > y || ya.max < y ||
+							mx < xa.min || mx > xa.max ||
+							my < ya.min || my > ya.max) continue;
+		
+					var xdiff = Math.abs(x - mx);
+		
+					// Bars are not supported yet. Not sure how it should look with bars
+					if((!s.bars.show && xdiff < xsens) 
+							|| (s.bars.show && xdiff < s.bars.barWidth/2) 
+							|| (y < 0 && my < 0 && my > y)) {
+						
+						var distance = xdiff;
+						
+						if (distance < n.dist) {
+							n.dist = distance;
+							n.x = x;
+							n.y = y;
+							n.xaxis = xa;
+							n.yaxis = ya;
+							n.mouse = s.mouse;
+							n.series = s; 
+							n.allSeries = series; // include all series
+							n.index = j;
+						}
+					}
+				}
+			}
+		}
+	  else {
+	    for(i = 0; i < series.length; i++){
+  			s = series[i];
+  			if(!s.mouse.track) continue;
+  			
+  			data = s.data;
+  			xa = s.xaxis;
+  			ya = s.yaxis;
+  			sens = 2 * options.points.lineWidth * s.mouse.sensibility;
+  			xsens = sens/xa.scale;
+  			ysens = sens/ya.scale;
+  			mx = xa.p2d(mouse.relX);
+  			my = ya.p2d(mouse.relY);
+        
+  			//if (s.points) {
+  			//	var h = this.points.getHit(s, mouse);
+  			//	if (h.index !== undefined) console.log(h);
+  			//}
+  			
+  			for(var j = 0, xpow, ypow; j < data.length; j++){
+  				x = data[j][0];
+  				y = data[j][1];
+  				
+  				if (y === null || 
+  				    xa.min > x || xa.max < x || 
+  				    ya.min > y || ya.max < y) continue;
+  				
+  				var xdiff = Math.abs(x - mx),
+  				    ydiff = Math.abs(y - my);
+  				
+  				// we use a different set of criteria to determin if there has been a hit
+  				// depending on what type of graph we have
+  				if(((!s.bars.show) && xdiff < xsens && ydiff < ysens) || 
+  				    (s.bars.show && xdiff < s.bars.barWidth/2 && ((y > 0 && my > 0 && my < y) || (y < 0 && my < 0 && my > y)))){
+  					var distance = Math.sqrt(xdiff*xdiff + ydiff*ydiff);
+  					if(distance < n.dist){
+  						n.dist = distance;
+  						n.x = x;
+  						n.y = y;
+  						n.xaxis = xa;
+  						n.yaxis = ya;
+  						n.mouse = s.mouse;
+  						n.series = s;
+  						n.allSeries = series;
+  						n.index = j;
+  						n.seriesIndex = i;
+  					}
+  				}
+  			}
+  		}
+		}
+		
+		if(n.series && (n.mouse && n.mouse.track && !prevHit || (prevHit /*&& (n.x != prevHit.x || n.y != prevHit.y)*/))){
+			var mt = this.mouseTrack,
+			    pos = '', 
+			    s = n.series,
+			    p = n.mouse.position, 
+			    m = n.mouse.margin,
+			    elStyle = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';
+			
+			if (!n.mouse.relative) { // absolute to the canvas
+				     if(p.charAt(0) == 'n') pos += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
+				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';
+				     if(p.charAt(1) == 'e') pos += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
+				else if(p.charAt(1) == 'w') pos += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
+			}
+			else { // relative to the mouse or in the case of bar like graphs to the bar
+				if(!s.bars.show){
+					     if(p.charAt(0) == 'n') pos += 'bottom:' + (m - plotOffset.top - n.yaxis.d2p(n.y) + this.canvasHeight) + 'px;top:auto;';
+					else if(p.charAt(0) == 's') pos += 'top:' + (m + plotOffset.top + n.yaxis.d2p(n.y)) + 'px;bottom:auto;';
+					     if(p.charAt(1) == 'e') pos += 'left:' + (m + plotOffset.left + n.xaxis.d2p(n.x)) + 'px;right:auto;';
+					else if(p.charAt(1) == 'w') pos += 'right:' + (m - plotOffset.left - n.xaxis.d2p(n.x) + this.canvasWidth) + 'px;left:auto;';
+				}
+
+				else {
+					pos += 'bottom:' + (m - plotOffset.top - n.yaxis.d2p(n.y/2) + this.canvasHeight) + 'px;top:auto;';
+					pos += 'left:' + (m + plotOffset.left + n.xaxis.d2p(n.x - options.bars.barWidth/2)) + 'px;right:auto;';
+				}
+			}
+			elStyle += pos;
+				     
+			if(!mt){
+				this.el.insert('<div class="flotr-mouse-value" style="'+elStyle+'"></div>');
+				mt = this.mouseTrack = this.el.select('.flotr-mouse-value')[0];
+			}
+			else {
+				mt.style.cssText = elStyle;
+				this.mouseTrack = mt;
+			}
+			
+			if(n.x !== null && n.y !== null){
+				mt.show();
+				
+				this.clearHit();
+				this.drawHit(n);
+				
+				var decimals = n.mouse.trackDecimals;
+				if(decimals == null || decimals < 0) decimals = 0;
+				
+				mt.innerHTML = n.mouse.trackFormatter({
+					x: n.x.toFixed(decimals), 
+					y: n.y.toFixed(decimals), 
+					series: n.series, 
+					index: n.index,
+					nearest: n
+				});
+				mt.fire('flotr:hit', [n, this]);
+			}
+			else if(prevHit){
+				mt.hide();
+				this.clearHit();
+			}
+		}
+		else if(this.prevHit) {
+			this.mouseTrack.hide();
+			this.clearHit();
+		}
+	},
+	drawTooltip: function(content, x, y, options) {
+		var mt = this.mouseTrack,
+		    style = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;', 
+		    p = options.position, 
+		    m = options.margin,
+		    plotOffset = this.plotOffset;
+
+		if (!mt) {
+			this.el.insert('<div class="flotr-mouse-value"></div>');
+			mt = this.mouseTrack = this.el.select('.flotr-mouse-value')[0];
+		}
+
+		if(x !== null && y !== null){
+			if (!options.relative) { // absolute to the canvas
+				     if(p.charAt(0) == 'n') style += 'top:' + (m + plotOffset.top) + 'px;bottom:auto;';
+				else if(p.charAt(0) == 's') style += 'bottom:' + (m + plotOffset.bottom) + 'px;top:auto;';
+				     if(p.charAt(1) == 'e') style += 'right:' + (m + plotOffset.right) + 'px;left:auto;';
+				else if(p.charAt(1) == 'w') style += 'left:' + (m + plotOffset.left) + 'px;right:auto;';
+			}
+			else { // relative to the mouse
+				     if(p.charAt(0) == 'n') style += 'bottom:' + (m - plotOffset.top - y + this.canvasHeight) + 'px;top:auto;';
+				else if(p.charAt(0) == 's') style += 'top:' + (m + plotOffset.top + y) + 'px;bottom:auto;';
+				     if(p.charAt(1) == 'e') style += 'left:' + (m + plotOffset.left + x) + 'px;right:auto;';
+				else if(p.charAt(1) == 'w') style += 'right:' + (m - plotOffset.left - x + this.canvasWidth) + 'px;left:auto;';
+			}
+	
+			mt.style.cssText = style;
+			mt.update(content).show();
+		}
+		else {
+			mt.hide();
+		}
+	},
+	saveImage: function (type, width, height, replaceCanvas) {
+		var image = null;
+		if (Prototype.Browser.IE) {
+			image = '<html><body>'+this.canvas.firstChild.innerHTML+'</body></html>';
+			return window.open().document.write(image);
+		}
+			
+		switch (type) {
+			case 'jpeg':
+			case 'jpg': image = Canvas2Image.saveAsJPEG(this.canvas, replaceCanvas, width, height); break;
+			default:
+			case 'png': image = Canvas2Image.saveAsPNG(this.canvas, replaceCanvas, width, height); break;
+			case 'bmp': image = Canvas2Image.saveAsBMP(this.canvas, replaceCanvas, width, height); break;
+		}
+		if (Object.isElement(image) && replaceCanvas) {
+			this.restoreCanvas();
+			this.canvas.hide();
+			this.overlay.hide();
+			this.el.insert(image.setStyle({position: 'absolute'}));
+		}
+	},
+	restoreCanvas: function() {
+		this.canvas.show();
+		this.overlay.show();
+		this.el.select('img').invoke('remove');
+	}
+});
+
+Flotr.Color = Class.create({
+	initialize: function(r, g, b, a){
+		this.rgba = ['r','g','b','a'];
+		var x = 4;
+		while(-1<--x){
+			this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+		}
+		this.normalize();
+	},
+	adjust: function(rd, gd, bd, ad) {
+		var x = 4;
+		while(-1<--x){
+			if(arguments[x] != null)
+				this[this.rgba[x]] += arguments[x];
+		}
+		return this.normalize();
+	},
+	scale: function(rf, gf, bf, af){
+		var x = 4;
+		while(-1<--x){
+			if(arguments[x] != null)
+				this[this.rgba[x]] *= arguments[x];
+		}
+		return this.normalize();
+	},
+	clone: function(){
+		return new Flotr.Color(this.r, this.b, this.g, this.a);
+	},
+	limit: function(val,minVal,maxVal){
+		return Math.max(Math.min(val, maxVal), minVal);
+	},
+	normalize: function(){
+		var limit = this.limit;
+		this.r = limit(parseInt(this.r), 0, 255);
+		this.g = limit(parseInt(this.g), 0, 255);
+		this.b = limit(parseInt(this.b), 0, 255);
+		this.a = limit(this.a, 0, 1);
+		return this;
+	},
+	distance: function(color){
+		if (!color) return;
+		color = new Flotr.Color.parse(color);
+		var dist = 0, x = 3;
+		while(-1<--x){
+			dist += Math.abs(this[this.rgba[x]] - color[this.rgba[x]]);
+		}
+		return dist;
+	},
+	toString: function(){
+		return (this.a >= 1.0) ? 'rgb('+[this.r,this.g,this.b].join(',')+')' : 'rgba('+[this.r,this.g,this.b,this.a].join(',')+')';
+	}
+});
+
+Object.extend(Flotr.Color, {
+	/**
+	 * Parses a color string and returns a corresponding Color.
+	 * The different tests are in order of probability to improve speed.
+	 * @param {String, Color} str - string thats representing a color
+	 * @return {Color} returns a Color object or false
+	 */
+	parse: function(color){
+		if (color instanceof Flotr.Color) return color;
+
+		var result, Color = Flotr.Color;
+
+		// #a0b1c2
+		if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color)))
+			return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));
+
+		// rgb(num,num,num)
+		if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color)))
+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));
+	
+		// #fff
+		if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)))
+			return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
+	
+		// rgba(num,num,num,num)
+		if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color)))
+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));
+			
+		// rgb(num%,num%,num%)
+		if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)))
+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
+	
+		// rgba(num%,num%,num%,num)
+		if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(color)))
+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
+
+		// Otherwise, we're most likely dealing with a named color.
+		var name = (color+'').strip().toLowerCase();
+		if(name == 'transparent'){
+			return new Color(255, 255, 255, 0);
+		}
+		return (result = Color.names[name]) ? new Color(result[0], result[1], result[2]) : new Color(0, 0, 0, 0);
+	},
+  
+	/**
+	 * Extracts the background-color of the passed element.
+	 * @param {Element} element - The element from what the background color is extracted
+	 * @return {String} color string
+	 */
+	extract: function(element){
+		var color;
+		// Loop until we find an element with a background color and stop when we hit the body element. 
+		do {
+			color = element.getStyle('background-color').toLowerCase();
+			if(!(color == '' || color == 'transparent')) break;
+			element = element.up();
+		} while(!element.nodeName.match(/^body$/i));
+
+		// Catch Safari's way of signaling transparent.
+		return new Flotr.Color(color == 'rgba(0, 0, 0, 0)' ? 'transparent' : color);
+	},
+	
+	names: {
+		aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],
+		brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],
+		darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],
+		darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],
+		darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],
+		khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],
+		lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],
+		maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],
+		violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]
+	}
+});
+
+Flotr.Date = {
+	format: function(d, format) {
+		if (!d) return;
+		
+		// We should maybe use an "official" date format spec, like PHP date() or ColdFusion 
+		// http://fr.php.net/manual/en/function.date.php
+		// http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_c-d_29.html
+		var tokens = {
+			h: d.getUTCHours().toString(),
+			H: leftPad(d.getUTCHours()),
+			M: leftPad(d.getUTCMinutes()),
+			S: leftPad(d.getUTCSeconds()),
+			s: d.getUTCMilliseconds(),
+			d: d.getUTCDate().toString(),
+			m: (d.getUTCMonth() + 1).toString(),
+			y: d.getUTCFullYear().toString(),
+			b: Flotr.Date.monthNames[d.getUTCMonth()]
+		};
+
+		function leftPad(n){
+			n += '';
+			return n.length == 1 ? "0" + n : n;
+		}
+		
+		var r = [], c,
+		    escape = false;
+		
+		for (var i = 0; i < format.length; ++i) {
+			c = format.charAt(i);
+			
+			if (escape) {
+				r.push(tokens[c] || c);
+				escape = false;
+			}
+			else if (c == "%")
+				escape = true;
+			else
+				r.push(c);
+		}
+		return r.join('');
+	},
+	getFormat: function(time, span) {
+		var tu = Flotr.Date.timeUnits;
+		     if (time < tu.second) return "%h:%M:%S.%s";
+		else if (time < tu.minute) return "%h:%M:%S";

[... 1551 lines stripped ...]