Experiment about making graphs of route trips
[busui.git] / js / flotr / flotr.debug-0.2.0-alpha_radar1.js
blob:a/js/flotr/flotr.debug-0.2.0-alpha_radar1.js -> blob:b/js/flotr/flotr.debug-0.2.0-alpha_radar1.js
--- a/js/flotr/flotr.debug-0.2.0-alpha_radar1.js
+++ b/js/flotr/flotr.debug-0.2.0-alpha_radar1.js
@@ -1,1 +1,3349 @@
+//Flotr 0.2.0-alpha Copyright (c) 2009 Bas Wenneker, <http://solutoire.com>, MIT License.

+//

+//Radar chart added by Ryan Simmons

+//
+/* $Id: flotr.js 82 2009-01-12 19:19:31Z fabien.menager $ */

+

+var Flotr = {

+	version: '0.2.0-alpha',

+	author: 'Bas Wenneker',

+	website: 'http://www.solutoire.com',

+	/**

+	 * An object of the default registered graph types. Use Flotr.register(type, functionName)

+	 * to add your own type.

+	 */

+	_registeredTypes:{

+		'lines': 'drawSeriesLines',

+		'points': 'drawSeriesPoints',

+		'bars': 'drawSeriesBars',

+		'candles': 'drawSeriesCandles',

+		'pie': 'drawSeriesPie',

+		'radar':'drawSeriesRadar'

+	},

+	/**

+	 * Can be used to register your own chart type. Default types are 'lines', 'points' and 'bars'.

+	 * This is still experimental.

+	 * @todo Test and confirm.

+	 * @param {String} type - type of chart, like 'pies', 'bars' etc.

+	 * @param {String} functionName - Name of the draw function, like 'drawSeriesPies', 'drawSeriesBars' etc.

+	 */

+	register: function(type, functionName){

+		Flotr._registeredTypes[type] = functionName+'';	

+	},

+	/**

+	 * 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 {Class} 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){

+			var i, serie = (serie.data) ? Object.clone(serie) : {'data': serie};

+			for (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 result = dest || {};

+		for(var i in src){

+			result[i] = (src[i] != null && typeof(src[i]) == 'object' && !(src[i].constructor == Array || src[i].constructor == RegExp) && !Object.isElement(src[i])) ? Flotr.merge(src[i], dest[i]) : result[i] = src[i];		

+		}

+		return result;

+	},	

+	/**

+	 * 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;	

+		var magn = Flotr.getMagnitude(delta);

+		

+		// Norm is between 1.0 and 10.0.

+		var norm = delta / magn;

+		

+		var tickSize = 10;

+		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+')';

+	}, 

+	defaultPieLabelFormatter: function(slice) {

+	  return (slice.fraction*100).toFixed(2)+'%';

+	},

+	/**

+	 * 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);

+	},

+	/**

+	 * Parses a color string and returns a corresponding Color.

+	 * @param {String} str - string thats representing a color

+	 * @return {Color} returns a Color object or false

+	 */

+	parseColor: function(str){

+		if (str instanceof Flotr.Color) return str;

+		

+		var result, Color = Flotr.Color;

+

+		// 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(str)))

+			return new Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));

+	

+		// 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(str)))

+			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(str)))

+			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(str)))

+			return new Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));

+			

+		// #a0b1c2

+		if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)))

+			return new Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));

+	

+		// #fff

+		if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)))

+			return new Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));

+

+		// Otherwise, we're most likely dealing with a named color.

+		var name = str.strip().toLowerCase();

+		if(name == 'transparent'){

+			return new Color(255, 255, 255, 0);

+		}

+		return ((result = Color.lookupColors[name])) ? new Color(result[0], result[1], result[2]) : false;

+	},

+	/**

+	 * Extracts the background-color of the passed element.

+	 * @param {Element} element

+	 * @return {String} color string

+	 */

+	extractColor: 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(0);

+		} while(!element.nodeName.match(/^body$/i));

+

+		// Catch Safari's way of signaling transparent.

+		return (color == 'rgba(0, 0, 0, 0)') ? 'transparent' : color;

+	}

+};

+/**

+ * 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){

+		this.el = $(el);

+		

+		if (!this.el) throw 'The target container doesn\'t exist';

+		

+		this.data = data;

+		this.series = Flotr.getSeries(data);

+		this.setOptions(options);

+		

+		// Initialize some variables

+		this.lastMousePos = { pageX: null, pageY: null };

+		this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };

+		this.prevSelection = null;

+		this.selectionInterval = null;

+		this.ignoreClick = false;   

+		this.prevHit = null;

+    

+		// Create and prepare canvas.

+		this.constructCanvas();

+		

+		// 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.draw();

+		this.insertLegend();

+    

+		// Graph and Data tabs

+		if (this.options.spreadsheet.show) 

+		this.constructTabs();

+	},

+	/**

+	 * Sets options and initializes some variables and color specific values, used by the constructor. 

+	 * @param {Object} opts - options object

+	 */

+  setOptions: function(opts){

+    var options = {

+      colors: ['#00A8F0', '#C0D800', '#CB4B4B', '#4DA74D', '#9440ED'], //=> The default colorscheme. When there are > 5 series, additional colors are generated.

+      title: null,

+      subtitle: null,

+      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: Prototype.K, // => 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

+      },

+      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

+      },

+      y2axis: {

+      	titleAngle: 270

+      },

+      points: {

+        show: false,           // => setting to true will show points, false will hide

+        radius: 3,             // => point radius (pixels)

+        lineWidth: 2,          // => line width in pixels

+        fill: true,            // => true to fill the points with a color, false for (transparent) no fill

+        fillColor: '#FFFFFF',  // => fill color

+        fillOpacity: 0.4

+      },

+      lines: {

+        show: false,           // => setting to true will show lines, false will hide

+        lineWidth: 2,          // => line width in pixels

+        fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+      },

+      radar: {

+        show: false,           // => setting to true will show radar chart, false will hide

+        lineWidth: 2,          // => line width in pixels

+        fill: false,           // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4       // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+      },

+      bars: {

+        show: false,           // => setting to true will show bars, false will hide

+        lineWidth: 2,          // => in pixels

+        barWidth: 1,           // => in units of the x axis

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.4,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        horizontal: false,

+        stacked: false

+      },

+      candles: {

+        show: false,           // => setting to true will show candle sticks, false will hide

+        lineWidth: 1,          // => in pixels

+        wickLineWidth: 1,      // => in pixels

+        candleWidth: 0.6,      // => in units of the x axis

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        upFillColor: '#00A8F0',// => up sticks fill color

+        downFillColor: '#CB4B4B',// => down sticks fill color

+        fillOpacity: 0.5,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        barcharts: false       // => draw as barcharts (not standard bars but financial barcharts)

+      },

+      pie: {

+        show: false,           // => setting to true will show bars, false will hide

+        lineWidth: 1,          // => in pixels

+        fill: true,            // => true to fill the area from the line to the x axis, false for (transparent) no fill

+        fillColor: null,       // => fill color

+        fillOpacity: 0.6,      // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill

+        explode: 6,

+        sizeRatio: 0.6,

+        startAngle: Math.PI/4,

+        labelFormatter: Flotr.defaultPieLabelFormatter,

+        pie3D: false,

+        pie3DviewAngle: (Math.PI/2 * 0.8),

+        pie3DspliceThickness: 20

+      },

+      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

+      },

+      selection: {

+        mode: null,            // => one of null, 'x', 'y' or 'xy'

+        color: '#B6D9FF',      // => selection box color

+        fps: 20                // => frames-per-second

+      },

+      mouse: {

+        track: false,          // => true to track the mouse, no tracking otherwise

+        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

+      },

+      radarChartMode: false, // => true to render radar grid / and setup scaling for radar chart

+      shadowSize: 4,           // => size of the 'fake' shadow

+      defaultType: 'lines',    // => default series type

+      HtmlText: true,          // => wether to draw the text using HTML or on the canvas

+      fontSize: 7.5,             // => canvas' text font size

+      spreadsheet: {

+      	show: false,           // => show the data grid using two tabs

+      	tabGraphLabel: 'Graph',

+      	tabDataLabel: 'Data',

+      	toolbarDownload: 'Download CSV', // @todo: add language support

+      	toolbarSelectAll: 'Select all'

+      }

+    }

+    

+    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);

+    

+    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, tooClose;

+

+		// Collect user-defined colors from series.

+		for(i = neededColors - 1; i > -1; --i){

+			c = this.series[i].color;

+			if(c != null){

+				--neededColors;

+				if(Object.isNumber(c)) assignedColors.push(c);

+				else usedColors.push(Flotr.parseColor(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.parseColor(oc[i]);

+			

+			// Make sure each serie gets a different color.

+			var sign = variation % 2 == 1 ? -1 : 1;

+			var 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();

+			}

+			

+      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.

+			s.lines   = Object.extend(Object.clone(this.options.lines), s.lines);

+			s.points  = Object.extend(Object.clone(this.options.points), s.points);

+			s.bars    = Object.extend(Object.clone(this.options.bars), s.bars);

+			s.candles = Object.extend(Object.clone(this.options.candles), s.candles);

+			s.pie     = Object.extend(Object.clone(this.options.pie), s.pie);

+			s.radar   = Object.extend(Object.clone(this.options.radar), s.radar);

+			s.mouse   = Object.extend(Object.clone(this.options.mouse), s.mouse);

+			

+			if(s.shadowSize == null) s.shadowSize = this.options.shadowSize;

+		}

+	},

+	/**

+	 * 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;

+		

+  	this.canvas = el.select('.flotr-canvas')[0];

+		this.overlay = el.select('.flotr-overlay')[0];

+		

+		el.childElements().invoke('remove');

+

+		// For positioning labels and overlay.

+		el.setStyle({position:'relative', cursor:'default'});

+

+		this.canvasWidth = el.getWidth();

+		this.canvasHeight = el.getHeight();

+		size = {'width': this.canvasWidth, 'height': this.canvasHeight};

+

+		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 = new Element('canvas', size);

+			c.className = 'flotr-canvas';

+			c = c.writeAttribute('style', 'position:absolute;left:0px;top:0px;');

+		} else {

+			c = this.canvas.writeAttribute(size);

+		}

+		el.insert(c);

+		

+		if(Prototype.Browser.IE){

+			c = window.G_vmlCanvasManager.initElement(c);

+		}

+		this.ctx = c.getContext('2d');

+    

+		// Insert overlay canvas for interactive features.

+		if (!this.overlay) {

+			oc = this.overlay = new Element('canvas', size);

+			oc.className = 'flotr-overlay';

+			oc = oc.writeAttribute('style', 'position:absolute;left:0px;top:0px;');

+		} else {

+			oc = this.overlay.writeAttribute(size);

+		}

+		el.insert(oc);

+		

+		if(Prototype.Browser.IE){

+			oc = window.G_vmlCanvasManager.initElement(oc);

+		}

+		this.octx = oc.getContext('2d');

+

+		// Enable text functions

+		if (window.CanvasText) {

+		  CanvasText.enable(this.ctx);

+		  CanvasText.enable(this.octx);

+		  this.textEnabled = true;

+		}

+	},

+  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;

+    }

+  },

+	loadDataGrid: function(){

+    if (this.seriesData) return this.seriesData;

+

+		var s = this.series;

+		var 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];

+				if (r = dg.find(function(row) {return row[0] == x})) {

+					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

+		dg = dg.sortBy(function(v) {

+			return v[0];

+		});

+		return this.seriesData = dg;

+	},

+	

+	// @todo: make a tab manager (Flotr.Tabs)

+  showTab: function(tabName, onComplete){

+    var elementsClassNames = 'canvas, .flotr-labels, .flotr-legend, .flotr-legend-bg, .flotr-title, .flotr-subtitle';

+    switch(tabName) {

+      case 'graph':

+        this.datagrid.up().hide();

+        this.el.select(elementsClassNames).invoke('show');

+        this.tabs.data.removeClassName('selected');

+        this.tabs.graph.addClassName('selected');

+      break;

+      case 'data':

+        this.constructDataGrid();

+        this.datagrid.up().show();

+        this.el.select(elementsClassNames).invoke('hide');

+        this.tabs.data.addClassName('selected');

+        this.tabs.graph.removeClassName('selected');

+      break;

+    }

+  },

+  constructTabs: function(){

+    var tabsContainer = new Element('div', {className:'flotr-tabs-group', style:'position:absolute;left:0px;top:'+this.canvasHeight+'px;width:'+this.canvasWidth+'px;'});

+    this.el.insert({bottom: tabsContainer});

+    this.tabs = {

+    	graph: new Element('div', {className:'flotr-tab selected', style:'float:left;'}).update(this.options.spreadsheet.tabGraphLabel),

+    	data: new Element('div', {className:'flotr-tab', style:'float:left;'}).update(this.options.spreadsheet.tabDataLabel)

+    }

+    

+    tabsContainer.insert(this.tabs.graph).insert(this.tabs.data);

+    

+    this.el.setStyle({height: this.canvasHeight+this.tabs.data.getHeight()+2+'px'});

+

+    this.tabs.graph.observe('click', (function() {this.showTab('graph')}).bind(this));

+    this.tabs.data.observe('click', (function() {this.showTab('data')}).bind(this));

+  },

+  

+  // @todo: make a spreadsheet manager (Flotr.Spreadsheet)

+	constructDataGrid: function(){

+    // If the data grid has already been built, nothing to do here

+    if (this.datagrid) return this.datagrid;

+    

+		var i, j, 

+        s = this.series,

+        datagrid = this.loadDataGrid();

+

+		var t = this.datagrid = new Element('table', {className:'flotr-datagrid', style:'height:100px;'});

+		var colgroup = ['<colgroup><col />'];

+		

+		// First row : series' labels

+		var html = ['<tr class="first-row">'];

+		html.push('<th>&nbsp;</th>');

+		for (i = 0; i < s.length; ++i) {

+			html.push('<th scope="col">'+(s[i].label || String.fromCharCode(65+i))+'</th>');

+			colgroup.push('<col />');

+		}

+		html.push('</tr>');

+		

+		// Data rows

+		for (j = 0; j < datagrid.length; ++j) {

+			html.push('<tr>');

+			for (i = 0; i < s.length+1; ++i) {

+        var tag = 'td';

+        var content = (datagrid[j][i] != null ? Math.round(datagrid[j][i]*100000)/100000 : '');

+        

+        if (i == 0) {

+          tag = 'th';

+          var label;

+          if(this.options.xaxis.ticks) {

+            var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == datagrid[j][i] });

+            if (tick) label = tick[1];

+          } 

+          else {

+            label = this.options.xaxis.tickFormatter(content);

+          }

+          

+          if (label) content = label;

+        }

+

+				html.push('<'+tag+(tag=='th'?' scope="row"':'')+'>'+content+'</'+tag+'>');

+			}

+			html.push('</tr>');

+		}

+		colgroup.push('</colgroup>');

+    t.update(colgroup.join('')+html.join(''));

+    

+    if (!Prototype.Browser.IE) {

+      t.select('td').each(function(td) {

+      	td.observe('mouseover', function(e){

+      		td = e.element();

+      		var siblings = td.previousSiblings();

+      		

+      		t.select('th[scope=col]')[siblings.length-1].addClassName('hover');

+      		t.select('colgroup col')[siblings.length].addClassName('hover');

+      	});

+      	

+      	td.observe('mouseout', function(){

+      		t.select('colgroup col.hover, th.hover').each(function(e){e.removeClassName('hover')});

+      	});

+      });

+    }

+    

+		var toolbar = new Element('div', {className: 'flotr-datagrid-toolbar'}).

+	    insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarDownload).observe('click', this.downloadCSV.bind(this))).

+	    insert(new Element('button', {type:'button', className:'flotr-datagrid-toolbar-button'}).update(this.options.spreadsheet.toolbarSelectAll).observe('click', this.selectAllData.bind(this)));

+		

+		var container = new Element('div', {className:'flotr-datagrid-container', style:'left:0px;top:0px;width:'+this.canvasWidth+'px;height:'+this.canvasHeight+'px;overflow:auto;'});

+		container.insert(toolbar);

+		t.wrap(container.hide());

+		

+		this.el.insert(container);

+    return t;

+  },

+  selectAllData: function(){

+    if (this.tabs) {

+      var selection, range, doc, win, node = this.constructDataGrid();

+  

+      this.showTab('data');

+      

+      // deferred to be able to select the table

+      (function () {

+        if ((doc = node.ownerDocument) && (win = doc.defaultView) && 

+          win.getSelection && doc.createRange && 

+          (selection = window.getSelection()) && 

+          selection.removeAllRanges) {

+           range = doc.createRange();

+           range.selectNode(node);

+           selection.removeAllRanges();

+           selection.addRange(range);

+        }

+        else if (document.body && document.body.createTextRange && 

+          (range = document.body.createTextRange())) {

+           range.moveToElementText(node);

+           range.select();

+        }

+      }).defer();

+      return true;

+    }

+    else return false;

+  },

+  downloadCSV: function(){

+    var i, csv = '"x"',

+        series = this.series,

+        dg = this.loadDataGrid();

+    

+    for (i = 0; i < series.length; ++i) {

+      csv += '%09"'+(series[i].label || String.fromCharCode(65+i))+'"'; // \t

+    }

+    csv += "%0D%0A"; // \r\n

+    

+    for (i = 0; i < dg.length; ++i) {

+      if (this.options.xaxis.ticks) {

+        var tick = this.options.xaxis.ticks.find(function (x) { return x[0] == dg[i][0] });

+        if (tick) dg[i][0] = tick[1];

+      } else {

+        dg[i][0] = this.options.xaxis.tickFormatter(dg[i][0]);

+      }

+      csv += dg[i].join('%09')+"%0D%0A"; // \t and \r\n

+    }

+    if (Prototype.Browser.IE) {

+      csv = csv.gsub('%09', '\t').gsub('%0A', '\n').gsub('%0D', '\r');

+      window.open().document.write(csv);

+    }

+    else {

+      window.open('data:text/csv,'+csv);

+    }

+  },

+	/**

+	 * Initializes event some handlers.

+	 */

+	initEvents: function () {

+  	//@todo: maybe stopObserving with only flotr functions

+  	this.overlay.stopObserving();

+  	this.overlay.observe('mousedown', this.mouseDownHandler.bind(this));

+		this.overlay.observe('mousemove', this.mouseMoveHandler.bind(this));

+		this.overlay.observe('click', this.clickHandler.bind(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 = 0;  a.x.datamax  = 0;

+		a.x2.datamin = 0; a.x2.datamax = 0;

+		a.y.datamin = 0;  a.y.datamax  = 0;

+		a.y2.datamin = 0; a.y2.datamax = 0;

+		

+		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){
<