diff client/digitallibrary/jquery/svg/jquery.svgplot.js @ 756:ccf67eaf97ee jquery

added jQuery ui and svg javascripts
author hertzhaft
date Sun, 06 Feb 2011 22:17:41 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/digitallibrary/jquery/svg/jquery.svgplot.js	Sun Feb 06 22:17:41 2011 +0100
@@ -0,0 +1,817 @@
+/* http://keith-wood.name/svg.html
+   SVG plotting extension for jQuery v1.4.3.
+   Written by Keith Wood (kbwood{at}iinet.com.au) December 2008.
+   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+   Please attribute the author if you use it. */
+
+(function($) { // Hide scope, no $ conflict
+
+$.svg.addExtension('plot', SVGPlot);
+
+/* Extension point for SVG plotting.
+   Access through svg.plot. */
+function SVGPlot(wrapper) {
+	this._wrapper = wrapper; // The attached SVG wrapper object
+	this._drawNow = false; // True for immediate update, false to wait for redraw call
+	// The plot title and settings
+	this._title = {value: '', offset: 25, settings: {textAnchor: 'middle'}};
+	this._area = [0.1, 0.1, 0.8, 0.9]; // The chart area: left, top, right, bottom,
+		// > 1 in pixels, <= 1 as proportion
+	this._areaFormat = {fill: 'none', stroke: 'black'}; // The formatting for the plot area
+	this._gridlines = []; // The formatting of the x- and y-gridlines
+	this._equalXY = true; // True for equal-sized x- and y-units, false to fill available space
+	this._functions = []; // The functions to be plotted, each is an object
+	this._onstatus = null; // The callback function for status updates
+	this._uuid = new Date().getTime();
+	this._plotCont = this._wrapper.svg(0, 0, 0, 0, {class_: 'svg-plot'}); // The main container for the plot
+	
+	this.xAxis = new SVGPlotAxis(this); // The main x-axis
+	this.xAxis.title('X', 20);
+	this.yAxis = new SVGPlotAxis(this); // The main y-axis
+	this.yAxis.title('Y', 20);
+	this.legend = new SVGPlotLegend(this); // The plot legend
+	this._drawNow = true;
+}
+
+$.extend(SVGPlot.prototype, {
+
+	/* Useful indexes. */
+	X: 0,
+	Y: 1,
+	W: 2,
+	H: 3,
+	L: 0,
+	T: 1,
+	R: 2,
+	B: 3,
+
+	/* Set or retrieve the container for the plot.
+	   @param  cont  (SVG element) the container for the plot
+	   @return  (SVGPlot) this plot object or
+	            (SVG element) the current container (if no parameters) */
+	container: function(cont) {
+		if (arguments.length == 0) {
+			return this._plotCont;
+		}
+		this._plotCont = cont;
+		return this;
+	},
+
+	/* Set or retrieve the main plotting area.
+	   @param  left    (number) > 1 is pixels, <= 1 is proportion of width or
+	                   (number[4]) for left, top, right, bottom
+	   @param  top     (number) > 1 is pixels, <= 1 is proportion of height
+	   @param  right   (number) > 1 is pixels, <= 1 is proportion of width
+	   @param  bottom  (number) > 1 is pixels, <= 1 is proportion of height
+	   @return  (SVGPlot) this plot object or
+	            (number[4]) the plotting area: left, top, right, bottom (if no parameters) */
+	area: function(left, top, right, bottom) {
+		if (arguments.length == 0) {
+			return this._area;
+		}
+		this._area = (isArray(left) ? left : [left, top, right, bottom]);
+		this._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the background of the plot area.
+	   @param  fill      (string) how to fill the area background
+	   @param  stroke    (string) the colour of the outline (optional)
+	   @param  settings  (object) additional formatting for the area background (optional)
+	   @return  (SVGPlot) this plot object or
+	            (object) the area format (if no parameters) */
+	format: function(fill, stroke, settings) {
+		if (arguments.length == 0) {
+			return this._areaFormat;
+		}
+		if (typeof stroke == 'object') {
+			settings = stroke;
+			stroke = null;
+		}
+		this._areaFormat = $.extend({fill: fill},
+			(stroke ? {stroke: stroke} : {}), settings || {});
+		this._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the gridlines formatting for the plot area.
+	   @param  xSettings  (string) the colour of the gridlines along the x-axis, or
+	                      (object) formatting for the gridlines along the x-axis, or
+						  null for none
+	   @param  ySettings  (string) the colour of the gridlines along the y-axis, or
+	                      (object) formatting for the gridlines along the y-axis, or
+						  null for none
+	   @return  (SVGPlot) this plot object or
+	            (object[2]) the gridlines formatting (if no parameters) */
+	gridlines: function(xSettings, ySettings) {
+		if (arguments.length == 0) {
+			return this._gridlines;
+		}
+		this._gridlines = [(typeof xSettings == 'string' ? {stroke: xSettings} : xSettings),
+			(typeof ySettings == 'string' ? {stroke: ySettings} : ySettings)];
+		if (this._gridlines[0] == null && this._gridlines[1] == null) {
+			this._gridlines = [];
+		}
+		this._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the equality of the x- and y-axes.
+	   @param  value  (boolean) true for equal x- and y-units, false to fill the available space
+	   @return  (SVGPlot) this plot object or
+	            (boolean) the current setting (if no parameters) */
+	equalXY: function(value) {
+		if (arguments.length == 0) {
+			return this._equalXY;
+		}
+		this._equalXY = value;
+		return this;
+	},
+
+	/* Set or retrieve the title of the plot and its formatting.
+	   @param  value     (string) the title
+	   @param  offset    (number) the vertical positioning of the title
+                          > 1 is pixels, <= 1 is proportion of width (optional)
+	   @param  colour    (string) the colour of the title (optional)
+	   @param  settings  (object) formatting for the title (optional)
+	   @return  (SVGPlot) this plot object or
+	            (object) value, offset, and settings for the title (if no parameters) */
+	title: function(value, offset, colour, settings) {
+		if (arguments.length == 0) {
+			return this._title;
+		}
+		if (typeof offset != 'number') {
+			settings = colour;
+			colour = offset;
+			offset = null;
+		}
+		if (typeof colour != 'string') {
+			settings = colour;
+			colour = null;
+		}
+		this._title = {value: value, offset: offset || this._title.offset,
+			settings: $.extend({textAnchor: 'middle'},
+			(colour ? {fill: colour} : {}), settings || {})};
+		this._drawPlot();
+		return this;
+	},
+
+	/* Add a function to be plotted on the plot.
+	   @param  name         (string) the name of this series (optional)
+	   @param  fn           (function) the function to be plotted
+	   @param  range        (number[2]) the range of values to plot (optional)
+	   @param  points       (number) the number of points to plot within this range (optional)
+	   @param  stroke       (string) the colour of the plotted lines (optional)
+	   @param  strokeWidth  (number) the width of the plotted lines (optional)
+	   @param  settings     (object) additional settings for the plotted values (optional)
+	   @return  (SVGPlot) this plot object */
+	addFunction: function(name, fn, range, points, stroke, strokeWidth, settings) {
+		this._functions.push(new SVGPlotFunction(
+			this, name, fn, range, points, stroke, strokeWidth, settings));
+		this._drawPlot();
+		return this;
+	},
+
+	/* Retrieve the function wrappers.
+	   @param  i  (number) the function index (optional)
+	   @return  (SVGPlotFunction) the specified function or
+	            (SVGPlotFunction[]) the list of functions */
+	functions: function(i) {
+		return (arguments.length > 0 ? this._functions[i] : null) || this._functions;
+	},
+
+	/* Suppress drawing of the plot until redraw() is called.
+	   @return  (SVGPlot) this plot object */
+	noDraw: function() {
+		this._drawNow = false;
+		return this;
+	},
+
+	/* Redraw the entire plot with the current settings and values.
+	   @return  (SVGPlot) this plot object */
+	redraw: function() {
+		this._drawNow = true;
+		this._drawPlot();
+		return this;
+	},
+
+	/* Set the callback function for status updates.
+	   @param  onstatus  (function) the callback function
+	   @return  (SVGPlot) this plot object */
+	status: function(onstatus) {
+		this._onstatus = onstatus;
+		return this;
+	},
+
+	/* Actually draw the plot (if allowed). */
+	_drawPlot: function() {
+		if (!this._drawNow) {
+			return;
+		}
+		while (this._plotCont.firstChild) {
+			this._plotCont.removeChild(this._plotCont.firstChild);
+		}
+		if (!this._plotCont.parent) {
+			this._wrapper._svg.appendChild(this._plotCont);
+		}
+		// Set sizes if not already there
+		if (!this._plotCont.width) {
+			this._plotCont.setAttribute('width',
+				parseInt(this._plotCont.getAttribute('width'), 10) || this._wrapper._width());
+		}
+		else if (this._plotCont.width.baseVal) {
+			this._plotCont.width.baseVal.value =
+				this._plotCont.width.baseVal.value || this._wrapper._width();
+		}
+		else {
+			this._plotCont.width = this._plotCont.width || this._wrapper._width();
+		}
+		if (!this._plotCont.height) {
+			this._plotCont.setAttribute('height',
+				parseInt(this._plotCont.getAttribute('height'), 10) || this._wrapper._height());
+		}
+		else if (this._plotCont.height.baseVal) {
+			this._plotCont.height.baseVal.value =
+				this._plotCont.height.baseVal.value || this._wrapper._height();
+		}
+		else {
+			this._plotCont.height = this._plotCont.height || this._wrapper._height();
+		}
+		this._drawChartBackground();
+		var dims = this._getDims();
+		var clip = this._wrapper.other(this._plotCont, 'clipPath', {id: 'clip' + this._uuid});
+		this._wrapper.rect(clip, dims[this.X], dims[this.Y], dims[this.W], dims[this.H]);
+		this._plot = this._wrapper.group(this._plotCont,
+			{class_: 'foreground', clipPath: 'url(#clip' + this._uuid + ')'});
+		this._drawAxis(true);
+		this._drawAxis(false);
+		for (var i = 0; i < this._functions.length; i++) {
+			this._plotFunction(this._functions[i], i);
+		}
+		this._drawTitle();
+		this._drawLegend();
+	},
+
+	/* Decode an attribute value.
+	   @param  node  the node to examine
+	   @param  name  the attribute name
+	   @return  the actual value */
+	_getValue: function(node, name) {
+		return (!node[name] ? parseInt(node.getAttribute(name), 10) :
+			(node[name].baseVal ? node[name].baseVal.value : node[name]));
+	},
+
+	/* Calculate the actual dimensions of the plot area.
+	    @param  area  (number[4]) the area values to evaluate (optional)
+		@return  (number[4]) an array of dimension values: left, top, width, height */
+	_getDims: function(area) {
+		var otherArea = (area != null);
+		area = area || this._area;
+		var availWidth = this._getValue(this._plotCont, 'width');
+		var availHeight = this._getValue(this._plotCont, 'height');
+		var left = (area[this.L] > 1 ? area[this.L] : availWidth * area[this.L]);
+		var top = (area[this.T] > 1 ? area[this.T] : availHeight * area[this.T]);
+		var width = (area[this.R] > 1 ? area[this.R] : availWidth * area[this.R]) - left;
+		var height = (area[this.B] > 1 ? area[this.B] : availHeight * area[this.B]) - top;
+		if (this._equalXY && !otherArea) {
+			var scale = Math.min(width / (this.xAxis._scale.max - this.xAxis._scale.min),
+				height / (this.yAxis._scale.max - this.yAxis._scale.min));
+			width = scale * (this.xAxis._scale.max - this.xAxis._scale.min);
+			height = scale * (this.yAxis._scale.max - this.yAxis._scale.min);
+		}
+		return [left, top, width, height];
+	},
+
+	/* Calculate the scaling factors for the plot area.
+	   @return  (number[2]) the x- and y-scaling factors */
+	_getScales: function() {
+		var dims = this._getDims();
+		return [dims[this.W] / (this.xAxis._scale.max - this.xAxis._scale.min),
+			dims[this.H] / (this.yAxis._scale.max - this.yAxis._scale.min)];
+	},
+
+	/* Draw the chart background, including gridlines.
+	   @param  noXGrid  (boolean) true to suppress the x-gridlines, false to draw them (optional)
+	   @param  noYGrid  (boolean) true to suppress the y-gridlines, false to draw them (optional)
+	   @return  (element) the background group element */
+	_drawChartBackground: function(noXGrid, noYGrid) {
+		var bg = this._wrapper.group(this._plotCont, {class_: 'background'});
+		var dims = this._getDims();
+		this._wrapper.rect(bg, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this._areaFormat);
+		if (this._gridlines[0] && this.yAxis._ticks.major && !noYGrid) {
+			this._drawGridlines(bg, true, this._gridlines[0], dims);
+		}
+		if (this._gridlines[1] && this.xAxis._ticks.major && !noXGrid) {
+			this._drawGridlines(bg, false, this._gridlines[1], dims);
+		}
+		return bg;
+	},
+
+	/* Draw one set of gridlines.
+	   @param  bg      (element) the background group element
+	   @param  horiz   (boolean) true if horizontal, false if vertical
+	   @param  format  (object) additional settings for the gridlines */
+	_drawGridlines: function(bg, horiz, format, dims) {
+		var g = this._wrapper.group(bg, format);
+		var axis = (horiz ? this.yAxis : this.xAxis);
+		var scales = this._getScales();
+		var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
+		major += (major <= axis._scale.min ? axis._ticks.major : 0);
+		while (major < axis._scale.max) {
+			var v = (horiz ? axis._scale.max - major : major - axis._scale.min) *
+				scales[horiz ? 1 : 0] + (horiz ? dims[this.Y] : dims[this.X]);
+			this._wrapper.line(g, (horiz ? dims[this.X] : v), (horiz ? v : dims[this.Y]),
+				(horiz ? dims[this.X] + dims[this.W] : v), (horiz ? v : dims[this.Y] + dims[this.H]));
+			major += axis._ticks.major;
+		}
+	},
+
+	/* Draw an axis, its tick marks, and title.
+	   @param  horiz  (boolean) true for x-axis, false for y-axis */
+	_drawAxis: function(horiz) {
+		var id = (horiz ? 'x' : 'y') + 'Axis';
+		var axis = (horiz ? this.xAxis : this.yAxis);
+		var axis2 = (horiz ? this.yAxis : this.xAxis);
+		var dims = this._getDims();
+		var scales = this._getScales();
+		var gl = this._wrapper.group(this._plot, $.extend({class_: id}, axis._lineFormat));
+		var gt = this._wrapper.group(this._plot, $.extend({class_: id + 'Labels',
+			textAnchor: (horiz ? 'middle' : 'end')}, axis._labelFormat));
+		var zero = (horiz ? axis2._scale.max : -axis2._scale.min) *
+			scales[horiz ? 1 : 0] + (horiz ? dims[this.Y] : dims[this.X]);
+		this._wrapper.line(gl, (horiz ? dims[this.X] : zero), (horiz ? zero : dims[this.Y]),
+			(horiz ? dims[this.X] + dims[this.W] : zero),
+			(horiz ? zero : dims[this.Y] + dims[this.H]));
+		if (axis._ticks.major) {
+			var size = axis._ticks.size;
+			var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
+			major = (major < axis._scale.min ? major + axis._ticks.major : major);
+			var minor = (!axis._ticks.minor ? axis._scale.max + 1 :
+				Math.floor(axis._scale.min / axis._ticks.minor) * axis._ticks.minor);
+			minor = (minor < axis._scale.min ? minor + axis._ticks.minor : minor);
+			var offsets = [(axis._ticks.position == 'nw' || axis._ticks.position == 'both' ? -1 : 0),
+				(axis._ticks.position == 'se' || axis._ticks.position == 'both' ? +1 : 0)];
+			while (major <= axis._scale.max || minor <= axis._scale.max) {
+				var cur = Math.min(major, minor);
+				var len = (cur == major ? size : size / 2);
+				var xy = (horiz ? cur - axis._scale.min : axis._scale.max - cur) *
+					scales[horiz ? 0 : 1] + (horiz ? dims[this.X] : dims[this.Y]);
+				this._wrapper.line(gl, (horiz ? xy : zero + len * offsets[0]),
+					(horiz ? zero + len * offsets[0] : xy),
+					(horiz ? xy : zero + len * offsets[1]),
+					(horiz ? zero + len * offsets[1] : xy));
+				if (cur == major && cur != 0) {
+					this._wrapper.text(gt, (horiz ? xy : zero - size),
+						(horiz ? zero - size : xy), '' + cur);
+				}
+				major += (cur == major ? axis._ticks.major : 0);
+				minor += (cur == minor ? axis._ticks.minor : 0);
+			}
+		}
+		if (axis._title) {
+			if (horiz) {
+				this._wrapper.text(this._plotCont, dims[this.X] - axis._titleOffset,
+					zero, axis._title, $.extend({textAnchor: 'end'}, axis._titleFormat || {}));
+			}
+			else {
+				this._wrapper.text(this._plotCont, zero,
+					dims[this.Y] + dims[this.H] + axis._titleOffset,
+					axis._title, $.extend({textAnchor : 'middle'}, axis._titleFormat || {}));
+			}
+		}
+	},
+
+	/* Plot an individual function. */
+	_plotFunction: function(fn, cur) {
+		var dims = this._getDims();
+		var scales = this._getScales();
+		var path = this._wrapper.createPath();
+		var range = fn._range || [this.xAxis._scale.min, this.xAxis._scale.max];
+		var xScale = (range[1] - range[0]) / fn._points;
+		var first = true;
+		for (var i = 0; i <= fn._points; i++) {
+			var x = range[0] + i * xScale;
+			if (x > this.xAxis._scale.max + xScale) {
+				break;
+			}
+			if (x < this.xAxis._scale.min - xScale) {
+				continue;
+			}
+			var px = (x - this.xAxis._scale.min) * scales[0] + dims[this.X];
+			var py = dims[this.H] - ((fn._fn(x) - this.yAxis._scale.min) * scales[1]) + dims[this.Y];
+			path[(first ? 'move' : 'line') + 'To'](px, py);
+			first = false;
+		}
+		var p = this._wrapper.path(this._plot, path,
+			$.extend({class_: 'fn' + cur, fill: 'none', stroke: fn._stroke,
+			strokeWidth: fn._strokeWidth}, fn._settings || {}));
+		this._showStatus(p, fn._name);
+	},
+
+	/* Draw the plot title - centred. */
+	_drawTitle: function() {
+		this._wrapper.text(this._plotCont, this._getValue(this._plotCont, 'width') / 2,
+			this._title.offset, this._title.value, this._title.settings);
+	},
+
+	/* Draw the chart legend. */
+	_drawLegend: function() {
+		if (!this.legend._show) {
+			return;
+		}
+		var g = this._wrapper.group(this._plotCont, {class_: 'legend'});
+		var dims = this._getDims(this.legend._area);
+		this._wrapper.rect(g, dims[this.X], dims[this.Y], dims[this.W], dims[this.H],
+			this.legend._bgSettings);
+		var horiz =  dims[this.W] > dims[this.H];
+		var numFn = this._functions.length;
+		var offset = (horiz ? dims[this.W] : dims[this.H]) / numFn;
+		var xBase = dims[this.X] + 5;
+		var yBase = dims[this.Y] + ((horiz ? dims[this.H] : offset) + this.legend._sampleSize) / 2;
+		for (var i = 0; i < numFn; i++) {
+			var fn = this._functions[i];
+			this._wrapper.rect(g, xBase + (horiz ? i * offset : 0),
+				yBase + (horiz ? 0 : i * offset) - this.legend._sampleSize,
+				this.legend._sampleSize, this.legend._sampleSize, {fill: fn._stroke});
+			this._wrapper.text(g, xBase + (horiz ? i * offset : 0) + this.legend._sampleSize + 5,
+				yBase + (horiz ? 0 : i * offset), fn._name, this.legend._textSettings);
+		}
+	},
+
+	/* Show the current value status on hover. */
+	_showStatus: function(elem, label) {
+		var status = this._onstatus;
+		if (this._onstatus) {
+			$(elem).hover(function(evt) { status.apply(this, [label]); },
+				function() { status.apply(this, ['']); });
+		}
+	}
+});
+
+/* Details about each plot function.
+   @param  plot         (SVGPlot) the owning plot
+   @param  name         (string) the name of this function (optional)
+   @param  fn           (function) the function to be plotted
+   @param  range        (number[2]) the range of values to be plotted (optional)
+   @param  points       (number) the number of points to plot within this range (optional)
+   @param  stroke       (string) the colour of the (out)line for the plot (optional)
+   @param  strokeWidth  (number) the width of the (out)line for the plot (optional)
+   @param  settings     (object) additional formatting settings (optional)
+   @return  (SVGPlotFunction) the new plot function object */
+function SVGPlotFunction(plot, name, fn, range, points, stroke, strokeWidth, settings) {
+	if (typeof name != 'string') {
+		settings = strokeWidth;
+		strokeWidth = stroke;
+		stroke = points;
+		points = range;
+		range = fn;
+		fn = name;
+		name = null;
+	}
+	if (!isArray(range)) {
+		settings = strokeWidth;
+		strokeWidth = stroke;
+		stroke = points;
+		points = range;
+		range = null;
+	}
+	if (typeof points != 'number') {
+		settings = strokeWidth;
+		strokeWidth = stroke;
+		stroke = points;
+		points = null;
+	}
+	if (typeof stroke != 'string') {
+		settings = strokeWidth;
+		strokeWidth = stroke;
+		stroke = null;
+	}
+	if (typeof strokeWidth != 'number') {
+		settings = strokeWidth;
+		strokeWidth = null;
+	}
+	this._plot = plot; // The owning plot
+	this._name = name || ''; // Display name
+	this._fn = fn || identity; // The actual function: y = fn(x)
+	this._range = range; // The range of values plotted
+	this._points = points || 100; // The number of points plotted
+	this._stroke = stroke || 'black'; // The line colour
+	this._strokeWidth = strokeWidth || 1; // The line width
+	this._settings = settings || {}; // Any other settings
+}
+
+$.extend(SVGPlotFunction.prototype, {
+
+	/* Set or retrieve the name for this function.
+	   @param  name    (string) the function's name
+	   @return  (SVGPlotFunction) this plot function object or
+	            (string) the function name (if no parameters) */
+	name: function(name) {
+		if (arguments.length == 0) {
+			return this._name;
+		}
+		this._name = name;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the function to be plotted.
+	   @param  name  (string) the function's name (optional)
+	   @param  fn    (function) the function to be ploted
+	   @return  (SVGPlotFunction) this plot function object or
+	            (function) the actual function (if no parameters) */
+	fn: function(name, fn) {
+		if (arguments.length == 0) {
+			return this._fn;
+		}
+		if (typeof name == 'function') {
+			fn = name;
+			name = null;
+		}
+		this._name = name || this._name;
+		this._fn = fn;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the range of values to be plotted.
+	   @param  min  (number) the minimum value to be plotted
+	   @param  max  (number) the maximum value to be plotted
+	   @return  (SVGPlotFunction) this plot function object or
+	            (number[2]) the value range (if no parameters) */
+	range: function(min, max) {
+		if (arguments.length == 0) {
+			return this._range;
+		}
+		this._range = (min == null ? null : [min, max]);
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the number of points to be plotted.
+	   @param  value  (number) the number of points to plot
+	   @return  (SVGPlotFunction) this plot function object or
+	            (number) the number of points (if no parameters) */
+	points: function(value) {
+		if (arguments.length == 0) {
+			return this._points;
+		}
+		this._points = value;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the formatting for this function.
+	   @param  stroke       (string) the (out)line colour
+	   @param  strokeWidth  (number) the line's width (optional)
+	   @param  settings     (object) additional formatting settings for the function (optional)
+	   @return  (SVGPlotFunction) this plot function object or
+	            (object) formatting settings (if no parameters) */
+	format: function(stroke, strokeWidth, settings) {
+		if (arguments.length == 0) {
+			return $.extend({stroke: this._stroke,
+				strokeWidth: this._strokeWidth}, this._settings);
+		}
+		if (typeof strokeWidth != 'number') {
+			settings = strokeWidth;
+			strokeWidth = null;
+		}
+		this._stroke = stroke || this._stroke;
+		this._strokeWidth = strokeWidth || this._strokeWidth;
+		$.extend(this._settings, settings || {});
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Return to the parent plot. */
+	end: function() {
+		return this._plot;
+	}
+});
+
+/* Default function to plot.
+   @param  x  (number) the input value
+   @return  (number) the same value */
+function identity(x) {
+	return x;
+}
+
+/* Details about each plot axis.
+   @param  plot   (SVGPlot) the owning plot
+   @param  title  (string) the title of the axis
+   @param  min    (number) the minimum value displayed on this axis
+   @param  max    (number) the maximum value displayed on this axis
+   @param  major  (number) the distance between major ticks
+   @param  minor  (number) the distance between minor ticks (optional)
+   @return  (SVGPlotAxis) the new axis object */
+function SVGPlotAxis(plot, title, min, max, major, minor) {
+	this._plot = plot; // The owning plot
+	this._title = title || ''; // The plot's title
+	this._titleFormat = {}; // Formatting settings for the title
+	this._titleOffset = 0; // The offset for positioning the title
+	this._labelFormat = {}; // Formatting settings for the labels
+	this._lineFormat = {stroke: 'black', strokeWidth: 1}; // Formatting settings for the axis lines
+	this._ticks = {major: major || 10, minor: minor || 0, size: 10, position: 'both'}; // Tick mark options
+	this._scale = {min: min || 0, max: max || 100}; // Axis scale settings
+	this._crossAt = 0; // Where this axis crosses the other one. */
+}
+
+$.extend(SVGPlotAxis.prototype, {
+
+	/* Set or retrieve the scale for this axis.
+	   @param  min  (number) the minimum value shown
+	   @param  max  (number) the maximum value shown
+	   @return  (SVGPlotAxis) this axis object or
+	            (object) min and max values (if no parameters) */
+	scale: function(min, max) {
+		if (arguments.length == 0) {
+			return this._scale;
+		}
+		this._scale.min = min;
+		this._scale.max = max;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the ticks for this axis.
+	   @param  major     (number) the distance between major ticks
+	   @param  minor     (number) the distance between minor ticks
+	   @param  size      (number) the length of the major ticks (minor are half) (optional)
+	   @param  position  (string) the location of the ticks:
+	                     'nw', 'se', 'both' (optional)
+	   @return  (SVGPlotAxis) this axis object or
+	            (object) major, minor, size, and position values (if no parameters) */
+	ticks: function(major, minor, size, position) {
+		if (arguments.length == 0) {
+			return this._ticks;
+		}
+		if (typeof size == 'string') {
+			position = size;
+			size = null;
+		}
+		this._ticks.major = major;
+		this._ticks.minor = minor;
+		this._ticks.size = size || this._ticks.size;
+		this._ticks.position = position || this._ticks.position;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the title for this axis.
+	   @param  title   (string) the title text
+	   @param  offset  (number) the distance to offset the title position (optional)
+	   @param  colour  (string) how to colour the title (optional) 
+	   @param  format  (object) formatting settings for the title (optional)
+	   @return  (SVGPlotAxis) this axis object or
+	            (object) title, offset, and format values (if no parameters) */
+	title: function(title, offset, colour, format) {
+		if (arguments.length == 0) {
+			return {title: this._title, offset: this._titleOffset, format: this._titleFormat};
+		}
+		if (typeof offset != 'number') {
+			format = colour;
+			colour = offset;
+			offset = null;
+		}
+		if (typeof colour != 'string') {
+			format = colour;
+			colour = null;
+		}
+		this._title = title;
+		this._titleOffset = (offset != null ? offset : this._titleOffset);
+		if (colour || format) {
+			this._titleFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
+		}
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the label format for this axis.
+	   @param  colour  (string) how to colour the labels (optional) 
+	   @param  format  (object) formatting settings for the labels (optional)
+	   @return  (SVGPlotAxis) this axis object or
+	            (object) format values (if no parameters) */
+	format: function(colour, format) {
+		if (arguments.length == 0) {
+			return this._labelFormat;
+		}
+		if (typeof colour != 'string') {
+			format = colour;
+			colour = null;
+		}
+		this._labelFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the line formatting for this axis.
+	   @param  colour    (string) the line's colour
+	   @param  width     (number) the line's width (optional)
+	   @param  settings  (object) additional formatting settings for the line (optional)
+	   @return  (SVGPlotAxis) this axis object or
+	            (object) line formatting values (if no parameters) */
+	line: function(colour, width, settings) {
+		if (arguments.length == 0) {
+			return this._lineFormat;
+		}
+		if (typeof width != 'number') {
+			settings = width;
+			width = null;
+		}
+		$.extend(this._lineFormat, {stroke: colour, strokeWidth:
+			width || this._lineFormat.strokeWidth}, settings || {});
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Return to the parent plot. */
+	end: function() {
+		return this._plot;
+	}
+});
+
+/* Details about the plot legend.
+   @param  plot          (SVGPlot) the owning plot
+   @param  bgSettings    (object) additional formatting settings for the legend background (optional)
+   @param  textSettings  (object) additional formatting settings for the legend text (optional)
+   @return  (SVGPlotLegend) the new legend object */
+function SVGPlotLegend(plot, bgSettings, textSettings) {
+	this._plot = plot; // The owning plot
+	this._show = true; // Show the legend?
+	this._area = [0.9, 0.1, 1.0, 0.9]; // The legend area: left, top, right, bottom,
+		// > 1 in pixels, <= 1 as proportion
+	this._sampleSize = 15; // Size of sample box
+	this._bgSettings = bgSettings || {stroke: 'gray'}; // Additional formatting settings for the legend background
+	this._textSettings = textSettings || {}; // Additional formatting settings for the text
+}
+
+$.extend(SVGPlotLegend.prototype, {
+
+	/* Set or retrieve whether the legend should be shown.
+	   @param  show  (boolean) true to display it, false to hide it
+	   @return  (SVGPlotLegend) this legend object or
+	            (boolean) show the legend? (if no parameters) */
+	show: function(show) {
+		if (arguments.length == 0) {
+			return this._show;
+		}
+		this._show = show;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve the legend area.
+	   @param  left    (number) > 1 is pixels, <= 1 is proportion of width or
+	                   (number[4]) for left, top, right, bottom
+	   @param  top     (number) > 1 is pixels, <= 1 is proportion of height
+	   @param  right   (number) > 1 is pixels, <= 1 is proportion of width
+	   @param  bottom  (number) > 1 is pixels, <= 1 is proportion of height
+	   @return  (SVGPlotLegend) this legend object or
+	            (number[4]) the legend area: left, top, right, bottom (if no parameters) */
+	area: function(left, top, right, bottom) {
+		if (arguments.length == 0) {
+			return this._area;
+		}
+		this._area = (isArray(left) ? left : [left, top, right, bottom]);
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Set or retrieve additional settings for the legend area.
+	   @param  sampleSize    (number) the size of the sample box to display (optional)
+	   @param  bgSettings    (object) additional formatting settings for the legend background
+	   @param  textSettings  (object) additional formatting settings for the legend text (optional)
+	   @return  (SVGPlotLegend) this legend object or
+	            (object) bgSettings and textSettings for the legend (if no parameters) */
+	settings: function(sampleSize, bgSettings, textSettings) {
+		if (arguments.length == 0) {
+			return {sampleSize: this._sampleSize, bgSettings: this._bgSettings,
+				textSettings: this._textSettings};
+		}
+		if (typeof sampleSize == 'object') {
+			textSettings = bgSettings;
+			bgSettings = sampleSize;
+			sampleSize = null;
+		}
+		this._sampleSize = sampleSize || this._sampleSize;
+		this._bgSettings = bgSettings;
+		this._textSettings = textSettings || this._textSettings;
+		this._plot._drawPlot();
+		return this;
+	},
+
+	/* Return to the parent plot. */
+	end: function() {
+		return this._plot;
+	}
+});
+
+//==============================================================================
+
+/* Determine whether an object is an array. */
+function isArray(a) {
+	return (a && a.constructor == Array);
+}
+
+})(jQuery)