(function($) {
	var debug = 1;

	function Axis(name) {
		this.name = name;
		this.show = false;
		// How much bigger or smaller to make the axis than the data range.
		this.scale = 1.2;
		this.numberTicks;
		this.tickInterval;
		this.renderer = new $.jqplot.lineAxisRenderer();
		this.tickFormatter = sprintf;
		this.label = {text:null, font:null, align:null};
		this.ticks = {labels:[], values:[], styles:[], formatString:'%.1f', fontFamily:'"Trebuchet MS", Arial, Helvetica, sans-serif', fontSize:'0.75em'};
		this.height = 0;
		this.width = 0;
		this.elem;
		// lowest/highest user data associated with this axis.
		this.dataBounds = {min:null, max:null};
		// lowest/highest values on axis.
		this.min=null;
		this.max=null;
		this.style;
		// axis will push the grid at position by value
		this.gridOffsets;
		// pixel offsets of min/max labels
		this.offsets = {min:null, max:null};
		this.canvasWidth;
		this.canvasHeight;
	};

	function Series() {
		this.show = true;
		this.xaxis = 'xaxis';
		this._xaxis = new Axis(this.xaxis);
		this.yaxis = 'yaxis';
		this._yaxis = new Axis(this.yaxis);
		this.renderer = new $.jqplot.lineRenderer();
		// raw user data points.  These should never be altered!!!
		this.data = [];
		// data in grid coordinates.  User data transformed for plotting on grid.
		this.gridData = [];
		// place holder, don't do anything with points yet.
		this.points = {show:true, renderer: 'circleRenderer'};
		this.color;
		this.lineWidth = 2.0;
		this.breakOnNull = false;
		this.label = '';
	};

	function Grid() {
		this.drawGridlines = true;
		this.background = '#ffffff';
		this.borderColor = '#c0c0c0';
		this.borderWidth = 2.0;
		this.width;
		this.height;
		this.top;
		this.bottom;
		this.left;
		this.right;
	};
    
	$._jqPlot = function() {
		// user's data.  Should be in the form of
		this.data = [];
		// the id of the dom element to render the plot into
		this.targetId = null;
		// the jquery object for the dom target.
		this.target = null;
		// default options object.
		// Fill in axes properties by default so don't throw an error.
		// Remind me that can set defaults for all points, axes, series at once.
		this.defaults = {
			pointsDefaults: {},
			axesDefaults: {},
			axes: {xaxis:{}, yaxis:{}},
			seriesDefaults: {},
			series:[]
		};

		// container for the individual data series
		this.series = [];
		// up to 2 axes are supported, each with it's own options.
		this.axes = {xaxis: new Axis('xaxis'), yaxis: new Axis('yaxis')};
		this.grid = new Grid();
		// this.title = {text:null, font:null};
		// handle to the grid canvas drawing context.  Holds the axes, grid, and labels.
		// Stuff that should be rendered only at initial plot drawing.
		this.gctx = null;
		// handle to the series  canvas drawing context.  Holds the rendered
		// rendered series which may be manipulated through user interaction.
		this.sctx = null;
		// handle to the overlay canvas drawing object.  Holds interactive content
		// like highlights that are rendered according to user interaction
		this.octx = null;
		// width and height of the canvas
		this.width = null;
		this.height = null;
		this.gridOffsets = {top:10, right:10, bottom:10, left:10};
		this.equalXTicks = true;
		this.equalYTicks = true;
		// borrowed colors from Flot.
		this.seriesColors = ["#000000"];
		// Default font characteristics which can be overriden by individual
		// plot elements.  All are css specs.
		this.textColor = '#000000';
		this.fontFamily = 'Trebuchet MS, Arial, Helvetica, sans-serif';
		this.fontSize = '1em';
		// container to hold all of the merged options.  Convienence for plugins.
		this.options = {};
            
		this.init = function(target, data, options) {
			this.targetId = target;
			this.target = $('#'+target);
			// make sure the target is positioned by some means
			if (!this.target) throw "No plot target specified";
			if (this.target.css('position') == 'static') this.target.css('position', 'relative');
			this.target.css('color', this.textColor);
			this.target.css('font-family', this.fontFamily);
			this.target.css('font-size', this.fontSize);
			this.height = parseFloat(this.target.css('height'));
			this.width = parseFloat(this.target.css('width'));
			if (this.height <=0 || this.width <=0) throw "Canvas dimensions <=0";
			// get a handle to the plot object from the target to help with events.
			$(target).data('jqplot', this);
			this.data = data;
			if (data.length < 1 || data[0].length < 2) throw "Invalid Plot data";
			this.parseOptions(options);
			// set the dataBounds (min and max) for each axis
			for (var i=0; i<this.series.length; i++) {
				this.series[i].renderer.processData.call(this.series[i]);
			}
		}
    
		// parse the user supplied options and override defaults, then
		// populate the instance properties
		this.parseOptions = function(options){
			this.options = $.extend(true, {}, this.defaults, options);
			for (var n in this.axes) {
				var axis = this.axes[n];
				$.extend(true, axis, this.options.axesDefaults, this.options.axes[n]);
				switch (n) {
					case 'xaxis':
						//axis.style = {position:'absolute', left:'0px', bottom:'0px'};
						axis.height = 0;
						axis.width = this.width;
						axis.gridOffset = 'bottom';
						break;
					case 'yaxis':
						//axis.style = {position:'absolute', left:'0px', top:'0px'};
						axis.height = this.height;
						axis.width = 0;
						axis.gridOffset = 'left';
						break;
					default:
						break;
				}
			}
			for (var i=0; i<this.data.length; i++) {
				var temp = $.extend(true, new Series(), this.seriesDefaults, this.options.series[i]);
				temp.data = this.data[i];
				switch (temp.xaxis) {
					case 'xaxis':
						temp._xaxis = this.axes.xaxis;
						break;
					default:
						break;
				}
				switch (temp.yaxis) {
					case 'yaxis':
						temp._yaxis = this.axes.yaxis;
						break;
					default:
						break;
				}
				if (temp.show) {
					temp._xaxis.show = true;
					temp._yaxis.show = true;
				}
				if (!temp.color) temp.color = this.seriesColors[i];
				this.series.push(temp);
			}
			// copy the grid and title options into this object.
			$.extend(true, this.grid, this.options.grid);
			for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
				$.jqplot.postParseOptionsHooks[i].call(this);
			}
		};
    
		// create the plot and add it do the dom
		this.draw = function(){
			this.drawAxes();
			this.pack();
			this.makeCanvas();
			this.drawGrid();
			this.drawSeries();
			for (var i=0; i<$.jqplot.postDrawHooks.length; i++) {
				$.jqplot.postDrawHooks[i].call(this);
			}
		};

		this.drawAxes = function(){
			for (var name in this.axes) {
				var axis = this.axes[name];
				if (axis.show) {
					// populate the axis label and value properties.
					this.setAxis(axis.name);
					// fill a div with axes labels in the right direction.
					// Need to pregenerate each axis to get it's bounds and
					// position it and the labels correctly on the plot.
					var h, w;
					axis.elem = $('<div class="jqplot-axis"></div>').appendTo(this.target).get(0);
					//for (var s in axis.style) $(axis.elem).css(s, axis.style[s]);
					for (var i=0; i<axis.ticks.labels.length; i++) {
						var elem = $('<div class="jqplot-axis-tick"></div>').appendTo(axis.elem).get(0);
						for (var s in axis.ticks.styles[i]) $(elem).css(s, axis.ticks.styles[i][s]);
						$(elem).html(axis.ticks.labels[i]);
						if (axis.ticks.fontFamily) elem.style.fontFamily = axis.ticks.fontFamily;
						if (axis.ticks.fontSize) elem.style.fontSize = axis.ticks.fontSize;
						h = $(elem).outerHeight(true);
						w = $(elem).outerWidth(true);
						if (axis.height < h) axis.height = h;
						if (axis.width < w) axis.width = w;
					}
					$(axis.elem).height(axis.height);
					$(axis.elem).width(axis.width);
				}
			}
		};

		// Populate the axis properties, giving a label and value
		// (corresponding to the user data coordinates, not plot coords.)
		// for each tick on the axis.
		this.setAxis = function(name) {
			// if a ticks array is specified, use it to fill in
			// the labels and values.
			var axis = this.axes[name];
			axis.canvasHeight = this.height;
			axis.canvasWidth = this.width;
			var db = axis.dataBounds;
			axis.renderer.fill.call(axis);
		};

		this.pack = function() {
			// calculate grid offsets
			var offsets = this.gridOffsets;
			var axes = this.axes;
			var temp
			if (temp) offsets.top = temp;
			if (axes.yaxis.width) offsets.left = axes.yaxis.width;
			if (axes.xaxis.height) offsets.bottom = axes.xaxis.height;
			this.grid.top = this.gridOffsets.top;
			this.grid.left = this.gridOffsets.left;
			this.grid.height = this.height - this.gridOffsets.top - this.gridOffsets.bottom;
			this.grid.width = this.width - this.gridOffsets.left - this.gridOffsets.right;
			this.grid.bottom = this.grid.top + this.grid.height;
			this.grid.right = this.grid.left + this.grid.width;
			for (var name in this.axes) {
				var axis = this.axes[name];
				axis.renderer.pack.call(axis, offsets);
			}
		};

		this.makeCanvas = function(){
			this.gridCanvas = document.createElement('canvas');
			this.gridCanvas.width = this.width;
			this.gridCanvas.height = this.height;
			if ($.browser.msie) // excanvas hack
				this.gridCanvas = window.G_vmlCanvasManager.initElement(this.gridCanvas);
			$(this.gridCanvas).css({ position: 'absolute', left: 0, top: 0 });
			this.target.append(this.gridCanvas);
			this.gctx = this.gridCanvas.getContext("2d");
			this.seriesCanvas = document.createElement('canvas');
			this.seriesCanvas.width = this.width;
			this.seriesCanvas.height = this.height;
			if ($.browser.msie) // excanvas hack
				this.seriesCanvas = window.G_vmlCanvasManager.initElement(this.seriesCanvas);
			$(this.seriesCanvas).css({ position: 'absolute', left: 0, top: 0 });
			this.target.append(this.seriesCanvas);
			this.sctx = this.seriesCanvas.getContext("2d");
			this.overlayCanvas = document.createElement('canvas');
			this.overlayCanvas.width = this.width;
			this.overlayCanvas.height = this.height;
			if ($.browser.msie) // excanvas hack
				this.overlayCanvas = window.G_vmlCanvasManager.initElement(this.overlayCanvas);
			$(this.overlayCanvas).css({ position: 'absolute', left: 0, top: 0 });
			this.target.append(this.overlayCanvas);
			this.octx = this.overlayCanvas.getContext("2d");
		};

		this.drawGrid = function(){
			// Add the grid onto the grid canvas.  This is the bottom most layer.
			var ctx = this.gctx;
			var grid = this.grid;
			ctx.save();
			ctx.fillStyle = grid.background;
			ctx.fillRect(grid.left, grid.top, grid.width, grid.height);
			if (grid.drawGridlines) {
				ctx.save();
				ctx.lineJoin = 'miter';
				ctx.lineCap = 'round';
				ctx.lineWidth = 1;
				ctx.strokeStyle = '#c0c0c0';
				for (var name in this.axes) {
					var axis = this.axes[name];
					if (axis.show) {
						var ticks = axis.ticks;
						switch (name) {
							case 'xaxis':
								for (var i=0; i<ticks.values.length; i++) {
									var pos = Math.round(axis.u2p(ticks.values[i])) + 0.5;
									ctx.beginPath(); ctx.moveTo(pos, grid.top); ctx.lineTo(pos, grid.bottom+4); ctx.stroke();
								}
								break;
							case 'yaxis':
								for (var i=0; i<ticks.values.length; i++) {
									var pos = Math.round(axis.u2p(ticks.values[i])) + 0.5;
									ctx.beginPath(); ctx.moveTo(grid.right, pos); ctx.lineTo(grid.left-4, pos); ctx.stroke();
								}
								break;
						}
					}
				}
				ctx.restore();
			}
			ctx.lineWidth = grid.borderWidth;
			ctx.strokeStyle = grid.borderColor;
			ctx.strokeRect(grid.left, grid.top, grid.width, grid.height);
			ctx.restore();
		};

		this.drawSeries = function(){
			for (var i=0; i<this.series.length; i++) {
				this.series[i].renderer.draw.call(this.series[i], this.grid, this.sctx);
				for (var j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
					$.jqplot.postDrawSeriesHooks[j].call(this.series[i], this.grid, this.sctx);
				}
			}
		};

	};
    
	$.jqplot = function(target, data, options) {
		options = options || {};
		var plot = new $._jqPlot();
		plot.init(target, data, options);
		plot.draw();
		return plot;
	};

	$.jqplot.postParseOptionsHooks = [];
	$.jqplot.postDrawHooks = [];
	$.jqplot.postDrawSeriesHooks = [];

	$.jqplot.lineAxisRenderer = function() {
	};
    
	$.jqplot.lineAxisRenderer.prototype.fill = function() {
		var name=this.name;
		var db=this.dataBounds;
		var dim,interval;
		var min,max;
		var pos1,pos2;
	  if (this.ticks.values.length==0) {
			if (name=='xaxis') dim=this.canvasWidth; else dim=this.canvasHeight;
			if (this.numberTicks == null) {
				if (dim > 100) this.numberTicks = parseInt(3+(dim-100)/75);	else this.numberTicks = 3;
			}
			min = ((this.min != null) ? this.min : db.min);
			max = ((this.max != null) ? this.max : db.max);
			var range = max - min;
			var rmin = min - (this.min != null ? 0 : range/2*(this.scale - 1));
			var rmax = max + (this.max != null ? 0 : range/2*(this.scale - 1));
			this.min = rmin;
			this.max = rmax;
			this.tickInterval = (rmax - rmin)/(this.numberTicks-1);
			for (var i=0; i<this.numberTicks; i++){
				var tt = rmin + i*this.tickInterval
				this.ticks.labels.push(this.tickFormatter(this.ticks.formatString, tt));
				this.ticks.values.push(rmin + i*this.tickInterval);
			}
	  }
		for (var i=0; i<this.numberTicks; i++){
			var pox = i*15+'px';
			switch (name) {
				case 'xaxis':
					this.ticks.styles.push({position:'absolute', top:'0px', left:pox, paddingTop:'10px'});
					break;
				case 'yaxis':
					this.ticks.styles.push({position:'absolute', right:'0px', top:pox, paddingRight:'10px'});
					break;
			}
		}
		if (name == 'yaxis') this.ticks.styles.reverse();
	};
    
	// Now we know offsets around the grid, we can define conversioning functions
	// and properly lay out the axes.
	$.jqplot.lineAxisRenderer.prototype.pack = function(offsets, grid) {
		var ticks = this.ticks;
		var tickdivs = $(this.elem).children('div');
		if (this.name == 'xaxis') {
			this.offsets = {min:offsets.left, max:offsets.right};
			this.p2u = function(p) {
				return (p - this.offsets.min)*(this.max - this.min)/(this.canvasWidth - this.offsets.max - this.offsets.min) + this.min;
			}
			this.u2p = function(u) {
				return (u - this.min) * (this.canvasWidth - this.offsets.max - this.offsets.min) / (this.max - this.min) + this.offsets.min;
			}
			if (this.show) {
				// set the position
				if (this.name == 'xaxis') {
					$(this.elem).css({position:'absolute', left:'0px', top:(this.canvasHeight-offsets.bottom)+'px'});
				}	else {
					$(this.elem).css({position:'absolute', left:'0px', bottom:(this.canvasHeight-offsets.top)+'px'});
				}
				for (i=0; i<tickdivs.length; i++) {
					var shim = $(tickdivs[i]).outerWidth(true)/2;
					//var t = this.u2p(ticks.values[i]);
					var val = this.u2p(ticks.values[i]) - shim + 'px';
					$(tickdivs[i]).css('left', val);
					// remember, could have done it this way
					//tickdivs[i].style.left = val;
				}
			}
		}	else {
			this.offsets = {min:offsets.bottom, max:offsets.top};
			this.p2u = function(p) {
				return (p - this.canvasHeight + this.offsets.min)*(this.max - this.min)/(this.canvasHeight - this.offsets.min - this.offsets.max) + this.min;
			}
			this.u2p = function(u) {
				return -(u - this.min) * (this.canvasHeight - this.offsets.min - this.offsets.max) / (this.max - this.min) + this.canvasHeight - this.offsets.min;
			}
			if (this.show) {
				// set the position
				if (this.name == 'yaxis') {
					$(this.elem).css({position:'absolute', right:(this.canvasWidth-offsets.left)+'px', top:'0px'});
				} else {
					$(this.elem).css({position:'absolute', left:(this.canvasWidth - offsets.right)+'px', top:'0px'});
				}
				for (i=0; i<tickdivs.length; i++) {
					var shim = $(tickdivs[i]).outerHeight(true)/2;
					var val = this.u2p(ticks.values[i]) - shim + 'px';
					$(tickdivs[i]).css('top', val);
				}
			}
		}
	};
    
  $.jqplot.lineRenderer=function(){
  };

	// called within scope of an individual series.
	$.jqplot.lineRenderer.prototype.draw = function(grid, ctx) {
		var i;
		ctx.save();
		ctx.beginPath();
		var xaxis = this.xaxis;
		var yaxis = this.yaxis;
		var d = this.data;
		var xp = this._xaxis.u2p;
		var yp = this._yaxis.u2p;
		// use a clipping path to cut lines outside of grid.
		ctx.moveTo(grid.left, grid.top);
		ctx.lineTo(grid.right, grid.top);
		ctx.lineTo(grid.right, grid.bottom);
		ctx.lineTo(grid.left, grid.bottom);
		ctx.closePath();
		ctx.clip();
		ctx.beginPath();
		ctx.lineJoin = 'round';
		ctx.lineCap = 'round';
		ctx.lineWidth = this.lineWidth;
		ctx.strokeStyle = this.color;
		var pointx, pointy;
		// recalculate the grid data
		this.gridData = [];
		this.gridData.push([xp.call(this._xaxis, this.data[0][0]), yp.call(this._yaxis, this.data[0][1])]);
		ctx.moveTo(this.gridData[0][0], this.gridData[0][1]);
		for (var i=1; i<this.data.length; i++) {
			this.gridData.push([xp.call(this._xaxis, this.data[i][0])+0, yp.call(this._yaxis, this.data[i][1])]);
			ctx.lineTo(this.gridData[i][0], this.gridData[i][1]);
		}
		ctx.stroke();
		ctx.restore();
	};
    
	$.jqplot.lineRenderer.prototype.processData = function() {
		// don't have axes conversion functions yet, all we can do is look for bad
		// points and set the axes bounds.
		var d = this.data;
		var i;
		var dbx = this._xaxis.dataBounds;
		var dby = this._yaxis.dataBounds;
		// weed out any null points and set the axes bounds
		for (i=0; i<d.length; i++) {
			if (d[i] == null || d[i][0] == null || d[i][1] == null) {
				// if line breaking on null values is set, keep the null in the data
				if (this.breakOnNull) d[i] = null;
				// else delete the null to skip the point.
				else d.splice(i,1);
			}
		}
		for (i=0; i<d.length; i++) {
			if (d[i] == null || d[i][0] == null || d[i][1] == null) continue;
			else {
				if (d[i][0] < dbx.min || dbx.min == null) dbx.min = d[i][0];
				if (d[i][0] > dbx.max || dbx.max == null) dbx.max = d[i][0];
				if (d[i][1] < dby.min || dby.min == null) dby.min = d[i][1];
				if (d[i][1] > dby.max || dby.max == null) dby.max = d[i][1];
			}
		}
	}
	
	// Convienence function that won't hang IE.
	function log() {
		if (window.console && debug) {
			console.log(arguments);
		}
	};
	
	function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }

	function sprintf () {
		var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
		while (f) {
			if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
			else if (m = /^\x25{2}/.exec(f)) o.push('%');
			else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
				if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
				if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
				throw("Expecting number but found " + typeof(a));
				switch (m[7]) {
					case 'b': a = a.toString(2); break;
					case 'c': a = String.fromCharCode(a); break;
					case 'd': a = parseInt(a); break;
					case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
					case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
					case 'o': a = a.toString(8); break;
					case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
					case 'u': a = Math.abs(a); break;
					case 'x': a = a.toString(16); break;
					case 'X': a = a.toString(16).toUpperCase(); break;
				}
				a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
				c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
				x = m[5] - String(a).length;
				p = m[5] ? str_repeat(c, x) : '';
				o.push(m[4] ? a + p : p + a);
			}	else throw ("Huh ?!");
			f = f.substring(m[0].length);
		}
		return o.join('');
	}

	$.sprintf = sprintf;
    
})(jQuery);

