/**
  * @name: FD.graph
  * @package: Fairfax Digital Javascript Framework
  * @version: 0.1
  * @author: Dave Elkan
  * @copyright: Copyright 2007. Fairfax Digital.
  * * * * * * * * * * * * * * * * * * * * * * * * * *
  *             DO NOT ALTER THIS FILE.
  * If your requirements are not met by this widget
  * or this widget does not work please ask
  * Dave Elkan <delkan@fairfaxdigital.com.au>.
  * This widget is designed to to be used across
  * the Fairfax Digital Network and contains no
  * site specific code.
  * Altering this file will render it unsupportable.
  * * * * * * * * * * * * * * * * * * * * * * * * * *
  **/


/**
    * @package FDJSF
    * @name Graph
    * @version  0.1
    * @requires mootools (v1.0)
    * @author Dave Elkan
    * @notes This package is based on the Plotr graphing system developed by Alastair Tse <http://www.liquidx.net/plotkit>.
    **/
/*
	Plotr V0.1.4
	============
	This is a uncompressed version of Plotr.{Base,Chart,Canvas,LineChart,BarChart},
	packed by Dean Edwards's Packer.

	For license/info/documentation: http://www.solutoire.com/plotr/

	Credits
	-------
	Plotr is partially based on PlotKit (BSD license) by
	Alastair Tse <http://www.liquidx.net/plotkit>.

	Copyright
	---------
 	Copyright 2007 (c) Bas Wenneker <sabmann[a]gmail[d]com>
 	For use under the BSD license. <http://www.solutoire.com/plotr>
*/

if (typeof(Plotr) == 'undefined') {
	Plotr = {};
};

Plotr.name = 'Plotr';
Plotr.version = '0.1.4';
Plotr.author = 'Bas Wenneker';

if (typeof(Plotr.Base) == 'undefined') {
	Plotr.Base = {};
};

/**
 * Plotr.Base.items puts all (non Function) items of lst into
 * an Array and returns the Array.
 *
 * @alias items
 * @param {Object} lst
 * @return {Array} result - Array that contains all nonFunction items of lst.
 */
Plotr.Base.items = function(lst) {
	var result = [];
	for(var item in lst){
		if (typeof(lst[item]) == 'function') continue;
		result.push(lst[item]);
	}
	return result;
};

/**
 * Check if obj is null or undefined.
 *
 * @alias isNil
 * @param {Object} obj
 * @return {Boolean} true if null or undefined.
 */
Plotr.Base.isNil = function(obj) {
	return (obj == null || typeof(obj) == 'undefined');
};

/**
 * Check if canvas simulation by ExCanvas is supported by the browser.
 *
 * @alias excanvasSupported
 * @return {Boolean} true if userAgent is IE
 */
Plotr.Base.excanvasSupported = function() {
     if (/MSIE/.test(navigator.userAgent) && !window.opera) {
         return true;
     }
     return false;
};

/**
 * Check whether or not canvas is supported by the browser.
 *
 * @alias isSupported
 * @param {String} canvasName - ID of the canvas element.
 * @return {Boolean} true if browser has canvas support supported.
 */
Plotr.Base.isSupported = function(canvasName) {
    var canvas = null;
    try {
        if (Plotr.Base.isNil(canvasName))
            canvas = new Element('canvas');
        else
            canvas = $(canvasName);
        var context = canvas.getContext('2d');
    }
    catch(e) {
        var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
        var opera = (navigator.userAgent.toLowerCase().indexOf('opera') != -1);
        if ((!ie) || (ie[1] < 6) || (opera))
            return false;
        return true;
    }
    return true;
};

/**
 * This function checks lst for the element with the largest length and
 * then returns an array with element 0 .. length.
 *
 * @alias uniqueIndices
 * @param {Array} lst
 * @return {Array} result - Returns an array with unique numbers.
 */
Plotr.Base.uniqueIndices = function(lst) {
	var result = [];
  var max = lst.max(function(item){
		return item.length;
	});
  for(var a = 0; a < max; a++) {
    result.push(a);
  }
	return result;
};

Plotr.Base.sum = function(lst){
	lst = lst.flatten();
	var result = 0;
	for(var i = lst.length-1; i >= 0; i--)
		result += lst[i];
	return result;
};

/**
 * Convert an string with an hexadecimal color code {'#ffffff','ffffff'}
 * to an RGB object {r: int, g: int, b: int}.
 *
 * @alias hexToRGB
 * @param {String} hex - String with hexadecimal color code like '#ffffff' or 'ffffff'.
 * @return {Object} rgbObj - Returns an object {r: int, g: int, b: int}.
 */
Plotr.Base.hexToRGB = function(hex) {
	hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
	return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
};

/**
 * Returns a String representation of rgbObj.
 *
 * @alias toRGBString
 * @param {Object} rgbObj - Object with RGB values {r: int, g: int, b: int}.
 * @return {String} rgb - Returns a String representation of the rgbObj.
 */
Plotr.Base.toRGBString = function(rgbObj) {
	return [rgbObj.r, rgbObj.g, rgbObj.b].rgbToHex();
};

/**
 * Convert an string with an hexadecimal color code {'#ffffff','ffffff'}
 * to an HSB object {h: int, s: int, b: int}.
 *
 * @alias hexToHSB
 * @param {String} hex - String with hexadecimal color code like '#ffffff' or 'ffffff'.
 * @return {Object} rgbObj - Returns an object {r: int, g: int, b: int}.
 */
Plotr.Base.hexToHSB = function(hex) {
	return Plotr.Base.rgbToHSB(Plotr.Base.hexToRGB(hex));
};

/**
 * Modification of Promeths' script. Converts a rgbObj {r: int, g: int, b: int}
 * to a hsbObj {h: int, s: int, b: int}.
 *
 * @alias rgbToHSB
 * @param {Object} rgbObj - Object with RGB values {r: int, g: int, b: int}.
 * @return {Object} hsbObj - Returns an object with HSB values {h: int, s: int, b: int}.
 * @author Prometh - http://proto.layer51.com/d.aspx?f=1136
 */
Plotr.Base.rgbToHSB = function(rgbObj) {
	var r = rgbObj.r;
	var g = rgbObj.g;
	var b = rgbObj.b;
	var hsb = {};

	hsb.b = Math.max(Math.max(r,g),b);
	hsb.s = (hsb.b <= 0) ? 0 : Math.round(100*(hsb.b - Math.min(Math.min(r,g),b))/hsb.b);
	hsb.b = Math.round((hsb.b /255)*100);
	if((r==g) && (g==b)) hsb.h = 0;
	else if(r>=g && g>=b) hsb.h = 60*(g-b)/(r-b);
	else if(g>=r && r>=b) hsb.h = 60  + 60*(g-r)/(g-b);
	else if(g>=b && b>=r) hsb.h = 120 + 60*(b-r)/(g-r);
	else if(b>=g && g>=r) hsb.h = 180 + 60*(b-g)/(b-r);
	else if(b>=r && r>=g) hsb.h = 240 + 60*(r-g)/(b-g);
	else if(r>=b && b>=g) hsb.h = 300 + 60*(r-b)/(r-g);
	else hsb.h = 0;
	hsb.h = Math.round(hsb.h);
	return hsb;
};

/**
 * Modification of Promeths' script. Converts a hsbObj {h: int, s: int, b: int}
 * to a rgbObj {r: int, g: int, b: int}.
 *
 * @alias hsbToRGB
 * @param {Object} hsbObj - Object with HSB value {h: int, s: int, b: int}.
 * @return {Object} rgbObj - Returns an Object with RGB values {r: int, g: int, b: int}.
 * @author Prometh - http://proto.layer51.com/d.aspx?f=1135
 */
Plotr.Base.hsbToRGB = function(hsbObj) {
	var r,g,b;
	var h = Math.round(hsbObj.h);
	var s = Math.round(hsbObj.s*255/100);
	var v = Math.round(hsbObj.b*255/100);
	if(s == 0) {
		r = g = b = v;
	} else {
		var t1 = v;
		var t2 = (255-s)*v/255;
		var t3 = (t1-t2)*(h%60)/60;
		if(h==360) h = 0;
		if(h<60) {r=t1;	b=t2; g=t2+t3}
		else if(h<120) {g=t1; b=t2;	r=t1-t3}
		else if(h<180) {g=t1; r=t2;	b=t2+t3}
		else if(h<240) {b=t1; r=t2;	g=t1-t3}
		else if(h<300) {b=t1; g=t2;	r=t2+t3}
		else if(h<360) {r=t1; g=t2;	b=t1-t3}
		else {r=0; g=0;	b=0}
	}
	return {r:Math.round(r), g:Math.round(g), b:Math.round(b)};
};

/**
 * Plotr.Base.levelHSB takes an hsbObj alters the brightness of it.
 * The brightness will be the max(initial brightness + level, 100).
 *
 * @alias levelHSB
 * @param {Object} hsbObj - Object with HSB values {h: int, s: int, b: int}.
 * @param {Object} level - Increase Brightness with level.
 * @return {String} rgbString - Returns a RGB string representation of the leveled hsbObj.
 */
Plotr.Base.levelHSB = function(hsbObj, level) {
	var hsb = {
		h: hsbObj.h,
		s: hsbObj.s,
		b: Math.min(hsbObj.b + level, 100)
	};
	return Plotr.Base.toRGBString(Plotr.Base.hsbToRGB(hsb));
};

/**
 * Plotr.Base.generateColorscheme returns an Array with string representations of
 * colors. The colors start from 'hex' and every color after hex has the same Hue
 * and Saturation but a leveled Brightness. So colors go from dark to light.
 * If reverse is set to true, the colors go from light to dark.
 *
 * @alias generateColorscheme
 * @param {String} hex - String with hexadecimal color code like '#ffffff' or 'ffffff'.
 * @param {Integer} size - Size of the colorScheme.
 * @param {Boolean} reverse - True if you want the colorScheme to be reversed.
 * @return {Array} result - Returns a colorScheme Array of length 'size'.
 */
Plotr.Base.generateColorscheme = function(hex, size, reverse) {
	var hsb = Plotr.Base.hexToHSB(hex);
	var result = [];
	var level = (100 - hsb.b)/size;

  var x = 0;
  while(x < size) {
    result.push(Plotr.Base.levelHSB(hsb, level*x));
    x++;
  }
	return (reverse) ? result.reverse() : result;
};

/**
 * Returns the default (green) colorScheme.
 *
 * @alias defaultScheme
 * @param {Integer} size - Size of the colorScheme to return.
 * @return {Array} colorScheme - Returns an Array of colors of length 'size'.
 */
Plotr.Base.defaultScheme = function(size) {
	return Plotr.Base.generateColorscheme('#3c581a',size)
};

/**
 * Function returns a colorScheme based on 'color' of length 'size'.
 *
 * @alias getColorscheme
 * @see Plotr.Base.colorSchemes
 * @param {String} color
 * @param {Object} size - Size the colorScheme that's returned should have.
 * @return {Array} colorScheme - Returns an Array of colors of length 'size'.
 */
Plotr.Base.getColorscheme = function(color, size) {
	var scheme = Plotr.Base.colorSchemes[color];
	if(Plotr.Base.isNil(scheme)){
		return Plotr.Base.generateColorscheme(color,(size) ? size : 3);
	}
	return Plotr.Base.generateColorscheme(scheme,(size) ? size : 3);
};

/**
 * Storage of colors for predefined colorSchemes.
 *
 * @alias colorSchemes
 */
Plotr.Base.colorSchemes = {
	red: '#6d1d1d',
	green: '#3c581a',
	blue: '#224565',
	grey: '#444444',
	black: '#000000'
};

/**
 * Plotr.Chart
 *
 * @alias Plotr.Chart
 * @namespace Plotr
 */
Plotr.Chart = new Class({

	/**
	 * The constructor of Plotr.Chart.
	 *
	 * @alias initialize
	 * @see Plotr.Canvas.setOptions
	 * @param {String} type - Choose from {'bar'}.
	 * @param {Object} options - Object with options.
	 */
	initialize: function(element, options) {
		this.setOptions(options);
		this.sets = 0;
		this.dataStores = [];

		if (!Plotr.Base.isNil(this.options.xAxis)) {
	        this.minxval = this.options.xAxis[0];
	        this.maxxval = this.options.xAxis[1];
	        this.xscale = this.maxxval - this.minxval;
	    } else {
	        this.minxval = 0;
	        this.maxxval = this.xscale = null;
	    }
	    if (!Plotr.Base.isNil(this.options.yAxis)) {
	        this.minyval = this.options.yAxis[0];
	        this.maxyval = this.options.yAxis[1];
	        this.yscale = this.maxyval - this.minyval;
	    } else {
	        this.minyval = 0;
	        this.maxyval = this.yscale = null;
	    }

	    this.xticks = [];
      this.yticks = [];
	    this.minxdelta = 0;
	    this.xrange = 1;
      this.yrange = 1;
      this._initCanvas(element);
	},

	/**
	 * Function adds the array to the dataStores object. Argument must be in
	 * the form of: {['<setName>': [[0,1],[1,2]...<data>], ..}. The function also
	 * keeps track of how many sets are added.
	 *
	 * @alias addDataset
	 * @param {Object} arguments - Object with data
	 */
	addDataset: function(store) {
		for(var name in store) {
			this.dataStores[name] = store[name];
			this.sets++;
		}
	},

	/**
	 * This function makes it easy to parse a table and show it's
	 * data in a chart. The upper left corner has coordinates (x=0,y=0).
	 *
	 * @alias addTable
	 * @param {String|Element} table - table id or element;
	 * @param {Integer} x - xcoordinate to start with data parsing
	 * @param {Integer} y - y coordinate to start with data parsing
	 * @param {Integer} xticks - row number of row with labels for xticks
	 */
	addTable: function(table, x, y, xticks){
		table = $(table);

		if(Plotr.Base.isNil(x))	x = 0;
		if(Plotr.Base.isNil(y)) y = 1;
		if(Plotr.Base.isNil(xticks)) xticks = -1;

		var tr = table.tBodies[0].rows;
		var store = {};
		var labels = [];

		for(var i = y, ln = tr.length; i < ln; i++){
			var j = 0;
			store['row_'+i] = $A(tr[i].cells).reject(function(cell,index){
				return index < x;
			}).collect(function(cell){
				return [j++, parseFloat(cell.innerHTML)];
			});
		}
		if(xticks >= 0){
			var tickIndex = 0;
			this.options.xTicks = $A(tr[xticks].cells).reject(function(cell,index){
				return index < x;
			}).collect(function(cell){
				return {v: tickIndex++, label: cell.innerHTML};
			});
		}
		this.addDataset(store);
	},

	/**
	 * This function does all the math. It'll process all the data that has to do
	 * with canvas measures.
	 *
	 * @alias _eval
	 * @param {Object} options - (optional) evaluate the chart with the given options.
	 */
	_eval: function(options) {
		if(!Plotr.Base.isNil(options)) {
			Object.extend(options,{});
			this.setOptions(options);
		}
		this.stores = Plotr.Base.items(this.dataStores);
		this._evalXY();
		this.setColorscheme();
	},

	/**
	 * Processes measures of the bars(/lines/pies).
	 *
	 * @alias _evalXY
	 */
	_evalXY: function() {
    //var xdata = this.stores.collect(0);
    var xdata = this.stores.collect(function(item) {return item.pluck(0)}).flatten();
		if (Plotr.Base.isNil(this.options.xAxis)) {
			this.minxval = (this.options.xOriginIsZero) ? 0 : parseFloat(xdata.min());
			this.maxxval = parseFloat(xdata.max());
		} else {
			this.minxval = this.options.xAxis[0];
      this.maxxval = this.options.xAxis[1];
			this.xscale = this.maxxval - this.minxval;
		}
		this.xrange = this.maxxval - this.minxval;
		this.xscale = (this.xrange == 0) ? 1.0 : 1/this.xrange;

    //var ydata = this.stores.collect(1);
    var ydata = this.stores.collect(function(item) {return item.pluck(1)}).flatten();
		if (Plotr.Base.isNil(this.options.yAxis)) {
			this.minyval = (this.options.yOriginIsZero) ? 0 : parseFloat(ydata.min());
			this.maxyval = parseFloat(ydata.max());
		} else {
			this.minyval = this.options.yAxis[0];
	        this.maxyval = this.options.yAxis[1];
			this.yscale = this.maxyval - this.minyval;
		}
	    this.yrange = this.maxyval - this.minyval;
		this.yscale = (this.yrange == 0) ? 1.0 : 1/this.yrange;
	},

	/**
	 * Evaluates ticks for X and Y axis.
	 *
	 * @alias _evalLineTicks
	 */
	_evalLineTicks: function() {
		this._evalLineTicksForXAxis();
		this._evalLineTicksForYAxis();
	},

	/**
	 * Evaluates ticks for X axis.
	 *
	 * @alias _evalLineTicksForXAxis
	 */
	_evalLineTicksForXAxis: function() {
	    if (this.options.xTicks) {
			this.xticks = this.options.xTicks.collect(function (tick) {
				var label = tick.label;
	            if (Plotr.Base.isNil(label))
	                label = tick.v.toString();
	            var pos = this.xscale * (tick.v - this.minxval);
	            if ((pos >= 0.0) && (pos <= 1.0)) {
	                return [pos, label];
	            }
			}.bind(this));
	    } else if (this.options.xNumberOfTicks) {
	        var uniqx = Plotr.Base.uniqueIndices(this.stores);
	        var roughSeparation = this.xrange / this.options.xNumberOfTicks;
	        var tickCount = 0;

	        this.xticks = [];
	        for (var i = 0; i <= uniqx.length; i++) {
	            if ((uniqx[i] - this.minxval) >= (tickCount * roughSeparation)) {
	                var pos = this.xscale * (uniqx[i] - this.minxval);
	                if ((pos > 1.0) || (pos < 0.0))
	                    continue;
	                this.xticks.push([pos, uniqx[i]]);
	                tickCount++;
	            }
	            if (tickCount > this.options.xNumberOfTicks)
	                break;
	        }
    	}
	},

	/**
	 * Evaluates ticks for Y axis.
	 *
	 * @alias _evalLineTicksForYAxis
	 */
	_evalLineTicksForYAxis: function() {
	    if (this.options.yTicks) {
			this.yticks = this.options.yTicks.collect(function (tick) {
				var label = tick.label;
	            if (Plotr.Base.isNil(label))
	                label = tick.v.toString();
	            var pos = 1.0 - (this.yscale * (tick.v - this.minyval));
	            if ((pos >= 0.0) && (pos <= 1.0)) {
	                return [pos, label];
	            }
			}.bind(this));
	    }else if (this.options.yNumberOfTicks) {
	        this.yticks = [];
			var prec = this.options.yTickPrecision;
            var num = this.yrange/this.options.yNumberOfTicks;
            if(this.options.fixYScale)
                num = this.options.yScale;

	        var roughSeparation = num.toFixed(this.options.yTickPrecision);

            for (var i = 0; i <= this.options.yNumberOfTicks; i++) { //TODO:marker
                var yval = this.minyval + (i * roughSeparation);
                var pos = 1.0 - ((yval - this.minyval) * this.yscale);
                if ((pos > 1.0) || (pos < 0.0))
                    continue;
                this.yticks.push([pos, yval.toFixed(prec)]);
            }
        }
    }
});
Plotr.Chart.implement(new Options);

/**
 * Plotr.Canvas
 *
 * @alias Plotr.Canvas
 * @namespace Plotr
 */
Plotr.Canvas = new Class({

	/**
	 * Sets options of this chart. Current options are:
	 * - sweetRender: using more advanced renderering techniques the chart will look much sweeter. {Boolean}
	 * - drawBackground: whether a background should be drawn. {Boolean}
	 * - backgroundLineColor: color of backgroundlines. {String}
	 * - backgroundColor: background color. {String}
	 * - padding: padding. {Object}
	 * - colorScheme: Array of colors used for chart. {Array}
	 * - strokeColor: color of a stroke. {String}
	 * - strokeWidth: width of a stroke. {Number}
	 * - shouldFill: whether bars/lines/pies should be filled. {Boolean}
	 * - shouldStroke: whether strokes should be drawn. {Boolean}
	 * - drawXAxis: whether the X axis should be drawn. {Boolean}
	 * - drawYAxis: whether the Y axis should be drawn. {Boolean}
	 * - axisTickSize: size of a tick in pixels. {Number}
	 * - axisLineColor: color of axis lines. {String}
	 * - axisLineWidth: line width of axis. {Number}
	 * - axisLabelColor: axis label color. {String}
	 * - axisLabelFont: font familily used for labels. {String}
	 * - axisLabelFontSize: font size used for labels. {String}
	 * - axisLabelWidth: axis label width. {Number}
	 * - barWidthFillFraction: sets the bar width (>1 will cause bars to overlap eachother). {Integer}
	 * - barOrientation: whether bars are horizontal. {'horizontal','vertical'}
	 * - xOriginIsZero: whether or not the origin of the X axis starts at zero. {Boolean}
	 * - yOriginIsZero: whether or not the origin of the Y axis starts at zero. {Boolean}
	 * - xAxis: values of xAxis {[xmin,xmax]}
	 * - yAxis: values of yAxis {[ymin,ymax]}
	 * - xTicks: labels for the X axis. {[{label: "somelabel", v: value}, ..]} (label = optional)
	 * - yTicks: labels for the Y axis. {[{label: "somelabel", v: value}, ..]} (label = optional)
	 * - xNumberOfTicks: number of ticks on X axis when xTicks is null. {Integer}
	 * - yNumberOfTicks: number of ticks on Y axis when yTicks is null. {Integer}
	 * - xTickPrecision: decimals for the labels on the X axis. {Integer}
	 * - yTickPrecision: decimals for the labels on the Y axis. {Integer}
	 *
	 * @alias Chart.setOptions
	 * @param {Object} options - Object with options.
	 */
	options: {
    sweetRender:		true,
    drawBackground: 	true,
    backgroundLineColor:'#ffffff',
    backgroundColor: 	'#f5f5f5',
    backgroundLineSize: 0.5,
    padding: 			{left: 30, right: 30, top: 5, bottom: 10},
    colorScheme: 		Plotr.Base.defaultScheme(Math.max(this.sets,3)),
    strokeColor: 		'#ffffff',
    strokeWidth: 		0.5,
    shouldFill: 		true,
    shouldStroke: 		true,
    drawXAxis: 			true,
    drawYAxis: 			true,
    axisTickSize: 		3,
    axisLineColor: 		'#000000',
    axisLineWidth: 		0.5,
    axisLabelColor: 	'#666666',
    axisLabelFont: 		'Arial',
    axisLabelFontSize: 	9,
    axisLabelWidth: 	50,
    barWidthFillFraction: 0.75,
    barOrientation: 'vertical',
    xOriginIsZero: true,
    yOriginIsZero: true,
    xAxis: null,
    yAxis: null,
    xTicks: null,
    yTicks: null,
    showIngraphLabels:  true,
    fixYScale: false,
    yScale: 5000,
    xNumberOfTicks: 10,
    yNumberOfTicks: 10,
    xTickPrecision: 1,
    yTickPrecision: 1,
    pieRadius: 0.4
	},

	/**
	 * Resets options and datasets of this chart.
	 *
	 * @alias reset
	 */
	reset: function() {
		this.setOptions(this.options);
		this.dataStores = [];
		if(!Plotr.Base.isNil(this.renderDelay)){
			this.renderDelay.stop();
			this.renderDelay = null;
		}
	},

	/**
	 * The constructor of Plotr.Canvas.
	 *
	 * @alias initialize
	 * @see setOptions
	 * @param {String} element  - Canvas element ID.
	 * @param {Plotr.Chart} chart - Chart object to render.
	 * @param {Object} options - Options.
	 */
	_initCanvas: function(element) {
		this.canvasNode = $(element);
		this.containerNode = $(this.canvasNode.parentNode);
		this.containerNode.setStyles({position: 'relative',width: this.canvasNode.width + 'px'});
		//window.ie = Plotr.Base.excanvasSupported();

    if (window.ie && !Plotr.Base.isNil(G_vmlCanvasManager)) {
      this.IEDelay = 0.5;
      this.maxTries = 10;
      this.renderDelay = null;
      this.renderStack = [];
      this.canvasNode = G_vmlCanvasManager.initElement(this.canvasNode);
    }
		if(Plotr.Base.isNil(this.canvasNode))
			throw 'Plotr.Canvas(): Could\'nt find canvas.';
		if(Plotr.Base.isNil(this.containerNode) || this.containerNode.nodeName.toLowerCase() != 'div')
			throw 'Plotr.Canvas(): Canvas element is not enclosed by a <div> element.';
		if (!window.ie && !(Plotr.Base.isSupported(element)))
      throw "Plotr.Canvas(): Canvas is not supported.";

		this.xlabels = [];
    this.ylabels = [];

		this.area = {
      x: this.options.padding.left,
      y: this.options.padding.top,
      w: this.canvasNode.width - this.options.padding.left - this.options.padding.right,
      h: this.canvasNode.height - this.options.padding.top - this.options.padding.bottom
    };
	},

	/**
	 * This function renders the background in the canvas element.
	 *
	 * @alias render
	 * @param {String} [element] - (optional) ID of the canvas element to render in.
	 */
	_render: function(element) {
		if(!Plotr.Base.isNil(element)) this._initCanvas(element);

		if (this.options.drawBackground) {
			this._renderBackground();
		}
	},

	_ieWaitForVML: function(element, options){
		var isNil = Plotr.Base.isNil;

		if(!isNil(element)) {
			this.renderStack[element] = options;
		}
		try{
			if(!isNil(this.canvasNode)) {
				this.canvasNode.getContext("2d");
			} else {
				$(element).getContext("2d");
			}
		} catch(e) {
			if(isNil(this.renderDelay)){
				this.renderDelay = new PeriodicalExecuter(function(pe){
					if(!isNil(this.canvasNode)) {
						this.render(this.canvasNode,options);
					} else {
						this.render(element,options);
					}
				}.bind(this), this.IEDelay);
			}else if(this.maxTries-- <= 0) {
				this.renderDelay.stop();
			}
			return true;
		}
		if(!isNil(this.renderDelay)){
			this.renderDelay.stop();
			delete this.renderStack[element || this.canvasNode];
		}

		return false;
	},

	/**
	 * Sets the colorScheme used for the chart.
	 *
	 * @alias setColorScheme
	 */
	setColorscheme: function() {
		var scheme = this.options.colorScheme;

		if(scheme instanceof Array) {
			return;
		} else if(typeof(scheme) == 'string') {
			if(this.type == 'pie'){
				this.options.colorScheme = Plotr.Base.getColorscheme(scheme, Math.max(this.stores[0].length,3));
			}else{
				this.options.colorScheme = Plotr.Base.getColorscheme(scheme, Math.max(this.sets,3));
			}
		} else {
			throw 'Plotr.Canvas.setColorscheme(): colorScheme is invalid!';
		}
	},

	/**
	 * Renders the background of the chart.
	 *
	 * @alias _renderBackground
	 */
	_renderBackground: function() {
		var cx = this.canvasNode.getContext('2d');
		cx.save();
    cx.fillStyle = this.options.backgroundColor;

		if(this.options.sweetRender) {


        cx.fillRect(this.area.x, this.area.y, this.area.w, this.area.h);
        cx.strokeStyle = this.options.backgroundLineColor;
        cx.lineWidth = this.options.backgroundLineSize;

        var ticks = this.yticks;
        var horiz = false;
        if (this.type == 'bar' && this.options.barOrientation == 'horizontal') {
          ticks = this.xticks;
          horiz = true;
        }

			var drawBackgroundLines = function(tick) {
				var x1 = x2 = y1 = y2 = 0;
				if(horiz) {
					x1 = x2 = tick[0] * this.area.w + this.area.x;
          y1 = this.area.y;
          y2 = y1 + this.area.h;
				} else {
					x1 = this.area.x;
          y1 = tick[0] * this.area.h + this.area.y;
          x2 = x1 + this.area.w;
          y2 = y1 = y1.toFixed(0);
        }

				cx.beginPath();
        cx.moveTo(x1, y1);
        cx.lineTo(x2, y2);
        cx.closePath();
        cx.stroke();
			}.bind(this);
			ticks.each(drawBackgroundLines);
		} else {
			cx.fillRect(0, 0, this.canvasNode.width, this.canvasNode.height);

		}

		cx.restore();

	},

	/**
	 * Renders the axis for line charts.
	 *
	 * @alias _renderLineAxis
	 */
	_renderLineAxis: function() {
		this._renderAxis();
	},

	/**
	 * Renders axis.
	 *
	 * @alias _renderAxis
	 */
	_renderAxis: function() {
	    if (!this.options.drawXAxis && !this.options.drawYAxis)
	        return;

	    var cx = this.canvasNode.getContext('2d');

	    var labelStyle = {
			position: 'absolute',
	        fontSize: this.options.axisLabelFontSize + 'px',
			fontFamily: this.options.axisLabelFont,
	        zIndex: 10,
	        color: this.options.axisLabelColor,
	        width: this.options.axisLabelWidth + 'px',
	        overflow: 'hidden'
		};

	    cx.save();
	    cx.strokeStyle = this.options.axisLineColor;
	    cx.lineWidth = this.options.axisLineWidth;

	    if (this.options.drawYAxis) {
	        if (this.yticks) {
				var collectYLabels = function(tick) {
					if(typeof(tick) == 'function') return;

	                var x = this.area.x;
	                var y = this.area.y + tick[0] * this.area.h;

	                cx.beginPath();
	                cx.moveTo(x, y);
	                cx.lineTo(x - this.options.axisTickSize, y);
	                cx.closePath();
	                cx.stroke();

					var label = new Element('div');
					label.appendChild(document.createTextNode(tick[1]));
					label.setStyles(Object.extend(labelStyle, {
						top: (y - this.options.axisLabelFontSize) + 'px',
						left: (x - this.options.padding.left - this.options.axisTickSize) + 'px',
						width: (this.options.padding.left - this.options.axisTickSize * 2) + 'px',
						textAlign: 'right'
					}));
          if(this.containerNode = $(this.containerNode)) {
            this.containerNode.appendChild(label);
          }
          return label;
				}.bind(this);
				this.ylabels = this.yticks.collect(collectYLabels);
	        }

	        cx.beginPath();
	        cx.moveTo(this.area.x, this.area.y);
	        cx.lineTo(this.area.x, this.area.y + this.area.h);
	        cx.closePath();
	        cx.stroke();
	    }

		if(this.options.drawXAxis) {
	        if(this.xticks) {
				var collectXLabels = function(tick) {
					if (typeof(tick) == 'function') return;

	                var x = this.area.x + tick[0] * this.area.w;
                	var y = this.area.y + this.area.h;

	                cx.beginPath();
	                cx.moveTo(x, y);
	                cx.lineTo(x, y + this.options.axisTickSize);
	                cx.closePath();
	                cx.stroke();

	                var label = new Element('div');
	                label.appendChild(document.createTextNode(tick[1]));

	                label.setStyles(Object.extend(labelStyle, {
        						top: (y + this.options.axisTickSize) + 'px',
        						left: (x - this.options.axisLabelWidth/2) + 'px',
        						width: this.options.axisLabelWidth + 'px',
        						textAlign: 'center'
        					}));
	                this.containerNode.appendChild(label);
	                return label;
				}.bind(this);
				this.xlabels = this.xticks.collect(collectXLabels);
	        }

	        cx.beginPath();
	        cx.moveTo(this.area.x, this.area.y + this.area.h);
	        cx.lineTo(this.area.x + this.area.w, this.area.y + this.area.h);
	        cx.closePath();
	        cx.stroke();
	    }
		cx.restore();
	}
});
Plotr.Canvas.implement(new Options);

Plotr.BarChart = Plotr.Chart.extend({
	/**
	 * Type of chart we're dealing with.
	 */
	type: 'bar',

	/**
	 * Renders the chart with the specified options. The optional parameters
	 * can be used to render a barchart in a different canvas element with new options.
	 *
	 * @alias render
	 * @param {String} [element] - (optional) ID of a canvas element.
	 * @param {Object} [options] - (optional) Options for rendering.
	 */
	render: function(element, options) {
		var isNil = Plotr.Base.isNil;

		if(window.ie && this._ieWaitForVML(element,options)){
			return;
		}

		this._evaluate(options);
		this._render(element);
		this._renderBarChart();
		this._renderBarAxis();

		if(window.ie) {
			for(var el in this.renderStack){
				if(typeof(this.renderStack[el]) == 'function') break;
				this.render(el,this.renderStack[el]);
				break;
			}
		}
	},

	/**
	 * This function does all the math. This function evaluates all the data needed
	 * to plot the chart.
	 *
	 * @alias _evaluate
	 * @param {Object} [options] - (optional) Evaluate the chart with the given options.
	 */
	_evaluate: function(options) {
		this._eval(options);
		if(this.options.barOrientation == 'vertical') this._evalBarChart();
		else this._evalHorizBarChart();
		this._evalBarTicks();
	},

	/**
	 * Evaluates measures for vertical bars.
	 *
	 * @alias _evalBarChart
	 */
	_evalBarChart: function() {
		var uniqx = Plotr.Base.uniqueIndices(this.stores);
		var xdelta = 10000000;
	    for (var i = 1; i < uniqx.length; i++) {
	        xdelta = Math.min(Math.abs(uniqx[i] - uniqx[i-1]), xdelta);
	    }

		var barWidth = 0;
	    var barWidthForSet = 0;
	    var barMargin = 0;
	    if (uniqx.length == 1) {
	        xdelta = 1.0;
	        this.xscale = 1.0;
	        this.minxval = uniqx[0];
	        barWidth = 1.0 * this.options.barWidthFillFraction;
	        barWidthForSet = barWidth / this.stores.length;
	        barMargin = (1.0 - this.options.barWidthFillFraction)/2;
	    } else {
        this.xscale = (this.xrange == 1) ? 0.5 : (this.xrange == 2) ? 1/3.0 : (1.0 - 1/this.xrange)/this.xrange;
	        barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
	        barWidthForSet = barWidth / this.stores.length;
	        barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
	    }

		this.minxdelta = xdelta;
		this.bars = [];

	    var i = 0;
	    for (var name in this.dataStores) {
	        var store = this.dataStores[name];
			if(typeof(store) == 'function') continue;
	        for (var j = 0; j < store.length; j++) {
	            var item = store[j];
	            var rect = {
	                x: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
	                y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
	                w: barWidthForSet,
	                h: ((parseFloat(item[1]) - this.minyval) * this.yscale),
	                xval: parseFloat(item[0]),
	                yval: parseFloat(item[1]),
	                name: name
	            };
	            if ((rect.x >= 0.0) && (rect.x <= 1.0) &&
	                (rect.y >= 0.0) && (rect.y <= 1.0)) {
	                this.bars.push(rect);
	            }
	        }
			i++;
	    }
	},

	/**
	 * Evaluates measures for vertical bars.
	 *
	 * @alias _evalHorizBarChart
	 */
	_evalHorizBarChart: function() {
		var uniqx = Plotr.Base.uniqueIndices(this.stores);
		var xdelta = 10000000;
	    for (var i = 1; i < uniqx.length; i++) {
	        xdelta = Math.min(Math.abs(uniqx[i] - uniqx[i-1]), xdelta);
	    }

		var barWidth = 0;
	    var barWidthForSet = 0;
	    var barMargin = 0;
	    if (uniqx.length == 1) {
	        xdelta = 1.0;
	        this.xscale = 1.0;
	        this.minxval = uniqx[0];
	        barWidth = 1.0 * this.options.barWidthFillFraction;
	        barWidthForSet = barWidth / this.stores.length;
	        barMargin = (1.0 - this.options.barWidthFillFraction)/2;
	    } else {
       	 	this.xscale = (1.0 - xdelta/this.xrange)/this.xrange;
        	barWidth = xdelta * this.xscale * this.options.barWidthFillFraction;
        	barWidthForSet = barWidth / this.stores.length;
        	barMargin = xdelta * this.xscale * (1.0 - this.options.barWidthFillFraction)/2;
		}

		this.minxdelta = xdelta;
	    this.bars = [];
	    var i = 0;
	    for (var name in this.dataStores) {
	        var store = this.dataStores[name];
	        if (typeof(store) == 'function') continue;
	        for (var j = 0; j < store.length; j++) {
	            var item = store[j];
	            var rect = {
	                y: ((parseFloat(item[0]) - this.minxval) * this.xscale) + (i * barWidthForSet) + barMargin,
	                x: 0.0,
	                h: barWidthForSet,
	                w: ((parseFloat(item[1]) - this.minyval) * this.yscale),
	                xval: parseFloat(item[0]),
	                yval: parseFloat(item[1]),
	                name: name
	            };

				rect.y = (rect.y <= 0.0) ? 0.0 : (rect.y >= 1.0) ? 1.0 : rect.y;
	            if ((rect.x >= 0.0) && (rect.x <= 1.0)) {
	                this.bars.push(rect);
	            }
	        }
	        i++;
	    }
	},

	/**
	 * Evaluates bar ticks.
	 *
	 * @alias _evalBarTicks
	 */
	_evalBarTicks: function() {
		this._evalLineTicks();
		this.xticks = this.xticks.collect(function(tick) {
			return [tick[0] + (this.minxdelta * this.xscale)/2, tick[1]];
		}.bind(this));

		if (this.options.barOrientation == 'horizontal') {
			var tmp = this.xticks;
			this.xticks = this.yticks.collect(function(tick) {
				return [1.0 - tick[0], tick[1]];
			}.bind(this));
			this.yticks = tmp;
	    }
	},

	/**
	 * Renders a horizontal/vertical bar chart.
	 *
	 * @alias _renderBarChart
	 */
	_renderBarChart: function() {

		var cx = this.canvasNode.getContext('2d');
		var index = 0;

		for(var storeName in this.dataStores) {
			var drawBar = function(bar) {
				if(bar.name != storeName || typeof(bar) == 'function') return;
				cx.save();
				cx.lineWidth = this.options.strokeWidth;
				cx.fillStyle = this.options.colorScheme[index % this.options.colorScheme.length];
				cx.strokeStyle = this.options.strokeColor;
				var x = this.area.w * bar.x + this.area.x;
	 	    	var y = this.area.h * bar.y + this.area.y;
	 	        	var w = this.area.w * bar.w;
	 	        	var h = this.area.h * bar.h;

	 	       		if ((w < 1) || (h < 1)) return;
	 	        	if (this.options.shouldFill) cx.fillRect(x, y, w, h);
				if (this.options.shouldStroke) cx.strokeRect(x, y, w, h);
				cx.restore();
			}.bind(this);
			this.bars.each(drawBar);
			index++;
		}
	},

	/**
	 * Renders the axis for bar charts.
	 *
	 * @alias _renderBarAxis
	 */
	_renderBarAxis: function() {
		this._renderAxis();
	}
});
Plotr.BarChart.implement(new Plotr.Canvas);

Plotr.LineChart = Plotr.Chart.extend({
	/**
     * Type of chart we're dealing with.
 	 */
	type: 'line',

	/**
	 * Renders the chart with the specified options. The optional parameters
	 * can be used to render a linechart in a different canvas element with new options.
	 *
	 * @alias render
	 * @param {String} [element] - (optional) ID of a canvas element.
	 * @param {Object} [options] - (optional) Options for rendering.
	 */
	render: function(element,options) {
		if(window.ie && this._ieWaitForVML(element,options)){
			return;
		}

		this._evaluate(options);
		this._render(element);
		this._renderLineChart();
		this._renderLineAxis();

		if(window.ie) {
			for(var el in this.renderStack){
				if(typeof(this.renderStack[el]) == 'function') break;
				this.render(el,this.renderStack[el]);
				break;
			}
		}
	},

	/**
	 * This function does all the math. It'll process all the data needed to
	 * plot the chart.
	 *
	 * @alias evaluate
	 * @param {Object} options - (optional) evaluate the chart with the given options.
	 */
	_evaluate: function(options) {
		this._eval(options);
		this._evalLineChart();
		this._evalLineTicksForXAxis();
		this._evalLineTicksForYAxis();
	},

	/**
	 * Processes specific measures for line charts.
	 *
	 * @alias _evalLineChart
	 */
	_evalLineChart: function() {
	    this.points = [];

	    for (var name in this.dataStores) {
	        var store = this.dataStores[name];
	        if (typeof(store) == 'function') continue;
	        for (var j = 0; j < store.length; j++) {
	            var item = store[j];
	            var point = {
	                x: ((parseFloat(item[0]) - this.minxval) * this.xscale),
	                y: 1.0 - ((parseFloat(item[1]) - this.minyval) * this.yscale),
	                xval: parseFloat(item[0]),
	                yval: parseFloat(item[1]),
	                name: name
	            };
				point.y = (point.y <= 0.0) ? 0.0 : (point.y >= 1.0) ? 1.0 : point.y;

	            if ((point.x >= 0.0) && (point.x <= 1.0)) {
	                this.points.push(point);
	            }
	        }
	    }
	},

	_renderLineChart: function() {
	    var cx = this.canvasNode.getContext("2d");
		var index = 0;

         var labelStyle = {
                position: 'absolute',
                fontSize: this.options.axisLabelFontSize + 'px',
                fontFamily: this.options.axisLabelFont,
                zIndex: 10,
                color: this.options.axisLabelColor,
                width: this.options.axisLabelWidth + 'px',
                overflow: 'hidden'
            };


        if(this.options.showIngraphLabels){
            for(var i = 0; i < this.points.length; i++){
                var currLabelPoint = this.points[i];
                var label = new Element('div');

                if(i > 0){ //Don't print the first label
                    label.appendChild(document.createTextNode(currLabelPoint.yval.toFixed(0)));
                    label.setStyles(Object.extend(labelStyle,{ //TODO: mark
                        top: (currLabelPoint.y * this.area.h) + 'px',
                        left: (currLabelPoint.x * this.area.w) + 'px',
                        width: (this.options.padding.left - this.options.axisTickSize * 2) + 'px',
                        textAlign: 'right'
                    }));
                    this.containerNode.appendChild(label);
                }
            }
        }


        for(var storeName in this.dataStores) {
			cx.save();
			cx.lineWidth = this.options.strokeWidth;
			cx.fillStyle = this.options.colorScheme[index % this.options.colorScheme.length];
		    cx.strokeStyle = this.options.strokeColor;



            var preparePath = function() {
                cx.beginPath();
                cx.moveTo(this.area.x, this.area.y + this.area.h);
                var addPoint = function(point) {

                }.bind(this);
                for(var point in this.points) {//for(var pointCount = 0; pointCount < this.points.size(); pointCount++){
                    var currPoint = this.points[point];
                    if (currPoint.name == storeName)
                        cx.lineTo(this.area.w * currPoint.x + this.area.x, this.area.h * currPoint.y + this.area.y);
                }
                cx.lineTo(this.area.w + this.area.x, this.area.h + this.area.y);
                cx.lineTo(this.area.x, this.area.y + this.area.h);
                cx.closePath();
            }.bind(this);




            if (this.options.shouldFill) {
	            preparePath(cx);
		        cx.fill();
		    }
	        if (this.options.shouldStroke) {
	            preparePath(cx);
	            cx.stroke();
	        }

	        cx.restore();

			index++;
		}
	}
});
Plotr.LineChart.implement(new Plotr.Canvas);

Plotr.PieChart = Plotr.Chart.extend({
	/**
     * Type of chart we're dealing with.
 	 */
	type: 'pie',

	/**
	 * Renders the chart with the specified options.
	 *
	 * @alias render
	 * @param {String} [element] - (optional) ID of a canvas element.
	 * @param {Object} [options] - (optional) Options for rendering.
	 */
	render: function(element,options) {
		var isNil = Plotr.Base.isNil;

		if(window.ie && this._ieWaitForVML(element,options)){
			return;
		}

		this._evaluate(options);
		this._render(element);
		this._renderPieChart();
		this._renderPieAxis();

		if(window.ie) {
			for(var el in this.renderStack){
				if(typeof(this.renderStack[el]) == 'function') break;
				this.render(el,this.renderStack[el]);
				break;
			}
		}
	},


	/**
	 * This function does all the math. This function evaluates all the data needed
	 * to plot the chart.
	 *
	 * @alias _evaluate
	 * @param {Object} [options] - (optional) Evaluate the chart with the given options.
	 */
	_evaluate: function(options) {
		this._eval(options);
		this._evalPieChart();
		this._evalPieTicks();
	},

	_evalPieChart: function(){
		var store = this.stores[0];
		var sum = Plotr.Base.sum(store.pluck(1));

		var angle = 0.0;
		this.slices = [];
		for(var i = 0, slice = null, fraction = null; i < store.length; i++){
			slice = store[i];
			if(slice[1] > 0){
				fraction = slice[1]/sum;
				this.slices.push({
					fraction: fraction,
					xval: slice[0],
					yval: slice[1],
					startAngle: 2 * angle * Math.PI,
					endAngle: 2 * (angle + fraction) * Math.PI
				});
				angle += fraction;
			}
		}
	},

	_renderPieChart: function(){
		var cx = this.canvasNode.getContext('2d');

		var centerx = this.area.x + this.area.w * 0.35;
    	var centery = this.area.y + this.area.h * 0.5;
		var radius = Math.min(this.area.w * this.options.pieRadius, this.area.h * this.options.pieRadius);

		if(window.ie) {
	        centerx = parseInt(centerx);
	        centery = parseInt(centery);
	        radius = parseInt(radius);
	    }

		for(var i = 0, ln = this.slices.length; i < ln; i++) {
			cx.save();
			cx.fillStyle = this.options.colorScheme[i % this.options.colorScheme.length];

			var drawPie = function(){
				cx.beginPath();
				cx.moveTo(centerx, centery);
				cx.arc(centerx, centery, radius,
                        this.slices[i].startAngle - Math.PI/2,
                        this.slices[i].endAngle - Math.PI/2,
                        false);
				cx.lineTo(centerx, centery);
           		cx.closePath();
			}.bind(this);

			if(Math.abs(this.slices[i].startAngle - this.slices[i].endAngle) > 0.001) {

				if(this.options.shouldFill) {
                	drawPie();
	                cx.fill();
	            }

	            if (this.options.shouldStroke) {
	                drawPie();
	                cx.lineWidth = this.options.strokeWidth;
	                if (this.options.strokeColor)
	                    cx.strokeStyle = this.options.strokeColor;
	                cx.stroke();
	            }
			}
			cx.restore();
		}
	},


    _evalPieTicks: function() {
		this.xticks = [];

		var toPercentage = function(n){
			n *= 100;
			return n.toFixed(1)+'%';
		};

		if(this.options.xTicks) {
			var lookup = [];
			for (var i = 0; i < this.slices.length; i++) {
				lookup[this.slices[i].xval] = this.slices[i];
			}

			for(var i = 0; i < this.options.xTicks.length; i++) {
				var tick = this.options.xTicks[i];
				var slice = lookup[tick.v];
	            var label = tick.label;
				if(slice) {
	                if (Plotr.Base.isNil(label))
	                    label = tick.v.toString();
					label += " (" + toPercentage(slice.fraction) + ")";
					this.xticks.push([tick.v, label]);
				}
			}
		}else{
			for(var i =0; i < this.slices.length; i++) {
				var slice = this.slices[i];
				var label = slice.xval + " (" + toPercentage(slice.fraction) + ")";
				this.xticks.push([slice.xval, label]);
			}
		}
	},

    _drawKey: function(x, y, d, c) {
        var cx = this.canvasNode.getContext('2d');
        cx.beginPath();
        cx.moveTo(x, y);
        cx.lineTo(x + d, y);
        cx.lineTo(x + d, y - d);
        cx.lineTo(x, y - d);
        cx.lineTo(x, y);
        cx.closePath();
        cx.fillStyle = c;
        cx.fill();
    },

    _renderPieAxis: function() {
        if (!this.options.drawXAxis)
            return;

        if(this.xticks){
            var lookup = new Array();
            for (var i = 0; i < this.slices.length; i++) {
                lookup[this.slices[i].xval] = this.slices[i];
            }
            var centerx = this.area.x + this.area.w * 0.5;
            var centery = this.area.y + this.area.h * 0.33;
            var radius = Math.min(this.area.w * this.options.pieRadius,
                    this.area.h * this.options.pieRadius);
            var labelWidth = 150;//this.options.axisLabelWidth;

            var startKeyY = centery - radius + 45;

            for(var i = 0; i < this.xticks.length; i++) {
                var slice = lookup[this.xticks[i][0]];
                if(Plotr.Base.isNil(slice))
                    continue;

                var angle = (slice.startAngle + slice.endAngle)/2;
                // normalize the angle
                var normalisedAngle = angle;
                if (normalisedAngle > Math.PI * 2)
                    normalisedAngle = normalisedAngle - Math.PI * 2;
                else if (normalisedAngle < 0)
                    normalisedAngle = normalisedAngle + Math.PI * 2;

                //var labelx = centerx + Math.sin(normalisedAngle) * (radius + 10);
                //var labely = centery - Math.cos(normalisedAngle) * (radius + 10);
                var labelx = 170;
                var labely = startKeyY - ((this.xticks.length - 20) * i) - 5;

                var c = this.options.colorScheme[i % this.options.colorScheme.length];
                this._drawKey(labelx, labely, 10, c);


                var labelStyle = {
                    position: 'absolute',
                    zIndex: 11,
                    width: labelWidth + 'px',
                    fontFamily: this.options.axisLabelFont,
                    fontSize: this.options.axisLabelFontSize + 'px',
                    overflow: 'hidden',
                    color: this.options.axisLabelColor,
                    left: labelx + 15 + 'px',
                    top: (labely - this.options.axisLabelFontSize) + 'px'
                };
                /*
                                if(normalisedAngle <= Math.PI * 0.5) {
                                    // text on top and align left
                                    Object.extend(labelStyle, {
                                        textAlign: 'left',
                                        verticalAlign: 'top',
                                        left: labelx + 'px',
                                        top: (labely - this.options.axisLabelFontSize) + 'px'
                                    });
                                }else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) {
                                    // text on bottom and align left
                                    Object.extend(labelStyle, {
                                        textAlign: 'left',
                                        verticalAlign: 'bottom',
                                        left: labelx + 'px',
                                        top: labely + 'px'
                                    });
                                }else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) {
                                    // text on bottom and align right
                                    Object.extend(labelStyle, {
                                        textAlign: 'right',
                                        verticalAlign: 'bottom',
                                        left: (labelx  - labelWidth) + 'px',
                                        top: labely + 'px'
                                    });
                                }else {
                                    // text on top and align right
                                    Object.extend(labelStyle, {
                                        textAlign: 'right',
                                        verticalAlign: 'bottom',
                                        left: (labelx  - labelWidth) + 'px',
                                        top: (labely - this.options.axisLabelFontSize) + 'px'
                                    });
                                }
                */

                var label = new Element('div');
                label.setHTML(this.xticks[i][1]);
                label.setStyles(labelStyle);
                this.containerNode.adopt(label);
                this.xlabels.push(label);
            }
        }
    }
});
Plotr.PieChart.implement(new Plotr.Canvas);

Array.prototype.min = function() {
  if(this.length > 0) {
    var min = this[0];
    for(var a = 1; a < this.length; a++) {
      if($type(this[a]) == "array") {
        var subMin = this[a].min();
        if(subMin < min) {
          min = subMin;
        }
      } else if(this[a] < min) {
        min = this[a];
      }
    }
    return min;
  }
};

Array.prototype.max = function(iterator) {
  var result;
  this.each(function(value, index) {
    value = (iterator || window.returnThis)(value, index);
    if (result == undefined || value >= result)
      result = value;
  });
  return result;
};

Array.prototype.collect = function(iterator) {
  var results = [];
  this.each(function(value, index) {
    results.push((iterator || Class.empty)(value, index));
  });
  return results;
};

Array.prototype.pluck = function(property) {
  var results = [];
  this.each(function(value, index) {
    results.push(value[property]);
  });
  return results;
};

Array.prototype.flatten = function() {
  return this.inject([], function(array, value) {
    return array.concat(value && value.constructor == Array ?
      value.flatten() : [value]);
  });
};

Array.prototype.inject = function(memo, iterator) {
  this.each(function(value, index) {
    memo = iterator(memo, value, index);
  });
  return memo;
};

window.returnThis = function(x) { return x };

