diff client/digitallibrary/jquery/svg/jquery.svggraph.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.svggraph.js	Sun Feb 06 22:17:41 2011 +0100
@@ -0,0 +1,1482 @@
+/* http://keith-wood.name/svg.html
+   SVG graphing extension for jQuery v1.4.3.
+   Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
+   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('graph', SVGGraph);
+
+// Singleton primary SVG graphing interface
+$.svg.graphing = new SVGGraphing();
+
+function SVGGraphing() {
+	this.regional = [];
+	this.regional[''] = {percentageText: 'Percentage'};
+	this.region = this.regional[''];
+}
+
+$.extend(SVGGraphing.prototype, {
+	_chartTypes: [],
+
+	/* Add a new chart rendering type to the package.
+	   The rendering object must implement the following functions:
+	   getTitle(), getDescription(), getOptions(), drawChart(graph).
+	   @param  id         (string) the ID of this graph renderer
+	   @param  chartType  (object) the object implementing this chart type */
+	addChartType: function(id, chartType) {
+		this._chartTypes[id] = chartType;
+	},
+
+	/* Retrieve the list of chart types.
+	   @return  (object[string]) the array of chart types indexed by ID */
+	chartTypes: function() {
+		return this._chartTypes;
+	}
+});
+
+/* Extension point for SVG graphing.
+   Access through svg.graph. */
+function SVGGraph(wrapper) {
+	this._wrapper = wrapper; // The attached SVG wrapper object
+	this._drawNow = false; // True for immediate update, false to wait for redraw call
+	for (var id in $.svg.graphing._chartTypes) {
+		this._chartType = $.svg.graphing._chartTypes[id]; // Use first graph renderer
+		break;
+	}
+	this._chartOptions = {}; // Extra options for the graph type
+	// The graph 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._chartFormat = {fill: 'none', stroke: 'black'}; // The formatting for the chart area
+	this._gridlines = []; // The formatting of the x- and y-gridlines
+	this._series = []; // The series to be plotted, each is an object
+	this._onstatus = null; // The callback function for status updates
+	this._chartCont = this._wrapper.svg(0, 0, 0, 0, {class_: 'svg-graph'}); // The main container for the graph
+	
+	this.xAxis = new SVGGraphAxis(this); // The main x-axis
+	this.xAxis.title('', 40);
+	this.yAxis = new SVGGraphAxis(this); // The main y-axis
+	this.yAxis.title('', 40);
+	this.x2Axis = null; // The secondary x-axis
+	this.y2Axis = null; // The secondary y-axis
+	this.legend = new SVGGraphLegend(this); // The chart legend
+	this._drawNow = true;
+}
+
+$.extend(SVGGraph.prototype, {
+
+	/* Useful indexes. */
+	X: 0,
+	Y: 1,
+	W: 2,
+	H: 3,
+	L: 0,
+	T: 1,
+	R: 2,
+	B: 3,
+
+	/* Standard percentage axis. */
+	_percentageAxis: new SVGGraphAxis(this, $.svg.graphing.region.percentageText, 0, 100, 10, 0),
+
+	/* Set or retrieve the container for the graph.
+	   @param  cont  (SVG element) the container for the graph
+	   @return  (SVGGraph) this graph object or
+	            (SVG element) the current container (if no parameters) */
+	container: function(cont) {
+		if (arguments.length == 0) {
+			return this._chartCont;
+		}
+		this._chartCont = cont;
+		return this;
+	},
+
+	/* Set or retrieve the type of chart to be rendered.
+	   See $.svg.graphing.getChartTypes() for the list of available types.
+	   @param  id       (string) the ID of the chart type
+	   @param  options  (object) additional settings for this chart type (optional)
+	   @return  (SVGGraph) this graph object or
+	            (string) the chart type (if no parameters)
+	   @deprecated  use type() */
+	chartType: function(id, options) {
+		return (arguments.length == 0 ? this.type() : this.type(id, options));
+	},
+
+	/* Set or retrieve the type of chart to be rendered.
+	   See $.svg.graphing.getChartTypes() for the list of available types.
+	   @param  id       (string) the ID of the chart type
+	   @param  options  (object) additional settings for this chart type (optional)
+	   @return  (SVGGraph) this graph object or
+	            (string) the chart type (if no parameters) */
+	type: function(id, options) {
+		if (arguments.length == 0) {
+			return this._chartType;
+		}
+		var chartType = $.svg.graphing._chartTypes[id];
+		if (chartType) {
+			this._chartType = chartType;
+			this._chartOptions = $.extend({}, options || {});
+		}
+		this._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve additional options for the particular chart type.
+	   @param  options  (object) the extra options
+	   @return  (SVGGraph) this graph object or
+	            (object) the chart options (if no parameters)
+	   @deprecated  use options() */
+	chartOptions: function(options) {
+		return(arguments.length == 0 ? this.options() : this.options(options));
+	},
+
+	/* Set or retrieve additional options for the particular chart type.
+	   @param  options  (object) the extra options
+	   @return  (SVGGraph) this graph object or
+	            (object) the chart options (if no parameters) */
+	options: function(options) {
+		if (arguments.length == 0) {
+			return this._chartOptions;
+		}
+		this._chartOptions = $.extend({}, options);
+		this._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the background of the graph chart.
+	   @param  fill      (string) how to fill the chart background
+	   @param  stroke    (string) the colour of the outline (optional)
+	   @param  settings  (object) additional formatting for the chart background (optional)
+	   @return  (SVGGraph) this graph object or
+	            (object) the chart format (if no parameters)
+	   @deprecated  use format() */
+	chartFormat: function(fill, stroke, settings) {
+		return (arguments.length == 0 ? this.format() : this.format(fill, stroke, settings));
+	},
+
+	/* Set or retrieve the background of the graph chart.
+	   @param  fill      (string) how to fill the chart background
+	   @param  stroke    (string) the colour of the outline (optional)
+	   @param  settings  (object) additional formatting for the chart background (optional)
+	   @return  (SVGGraph) this graph object or
+	            (object) the chart format (if no parameters) */
+	format: function(fill, stroke, settings) {
+		if (arguments.length == 0) {
+			return this._chartFormat;
+		}
+		if (typeof stroke == 'object') {
+			settings = stroke;
+			stroke = null;
+		}
+		this._chartFormat = $.extend({fill: fill},
+			(stroke ? {stroke: stroke} : {}), settings || {});
+		this._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the main chart 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  (SVGGraph) this graph object or
+	            (number[4]) the chart area: left, top, right, bottom (if no parameters)
+	   @deprecated use area() */
+	chartArea: function(left, top, right, bottom) {
+		return (arguments.length == 0 ? this.area() : this.area(left, top, right, bottom));
+	},
+
+	/* Set or retrieve the main chart 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  (SVGGraph) this graph object or
+	            (number[4]) the chart 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._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the gridlines formatting for the graph chart.
+	   @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  (SVGGraph) this graph 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._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the title of the graph 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  (SVGGraph) this graph 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._drawGraph();
+		return this;
+	},
+
+	/* Add a series of values to be plotted on the graph.
+	   @param  name         (string) the name of this series (optional)
+	   @param  values       (number[]) the values to be plotted
+	   @param  fill         (string) how the plotted values are filled
+	   @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  (SVGGraph) this graph object */
+	addSeries: function(name, values, fill, stroke, strokeWidth, settings) {
+		this._series.push(new SVGGraphSeries(
+			this, name, values, fill, stroke, strokeWidth, settings));
+		this._drawGraph();
+		return this;
+	},
+
+	/* Retrieve the series wrappers.
+	   @param  i  (number) the series index (optional)
+	   @return  (SVGGraphSeries) the specified series or
+	            (SVGGraphSeries[]) the list of series */
+	series: function(i) {
+		return (arguments.length > 0 ? this._series[i] : null) || this._series;
+	},
+
+	/* Suppress drawing of the graph until redraw() is called.
+	   @return  (SVGGraph) this graph object */
+	noDraw: function() {
+		this._drawNow = false;
+		return this;
+	},
+
+	/* Redraw the entire graph with the current settings and values.
+	   @return  (SVGGraph) this graph object */
+	redraw: function() {
+		this._drawNow = true;
+		this._drawGraph();
+		return this;
+	},
+
+	/* Set the callback function for status updates.
+	   @param  onstatus  (function) the callback function
+	   @return  (SVGGraph) this graph object */
+	status: function(onstatus) {
+		this._onstatus = onstatus;
+		return this;
+	},
+
+	/* Actually draw the graph (if allowed) based on the graph type set. */
+	_drawGraph: function() {
+		if (!this._drawNow) {
+			return;
+		}
+		while (this._chartCont.firstChild) {
+			this._chartCont.removeChild(this._chartCont.firstChild);
+		}
+		if (!this._chartCont.parent) {
+			this._wrapper._svg.appendChild(this._chartCont);
+		}
+		// Set sizes if not already there
+		if (!this._chartCont.width) {
+			this._chartCont.setAttribute('width',
+				parseInt(this._chartCont.getAttribute('width'), 10) || this._wrapper._width());
+		}
+		else if (this._chartCont.width.baseVal) {
+			this._chartCont.width.baseVal.value =
+				this._chartCont.width.baseVal.value || this._wrapper._width();
+		}
+		else {
+			this._chartCont.width = this._chartCont.width || this._wrapper._width();
+		}
+		if (!this._chartCont.height) {
+			this._chartCont.setAttribute('height',
+				parseInt(this._chartCont.getAttribute('height'), 10) || this._wrapper._height());
+		}
+		else if (this._chartCont.height.baseVal) {
+			this._chartCont.height.baseVal.value =
+				this._chartCont.height.baseVal.value || this._wrapper._height();
+		}
+		else {
+			this._chartCont.height = this._chartCont.height || this._wrapper._height();
+		}
+		this._chartType.drawGraph(this);
+	},
+
+	/* 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]));
+	},
+
+	/* Draw the graph title - centred. */
+	_drawTitle: function() {
+		this._wrapper.text(this._chartCont, this._getValue(this._chartCont, 'width') / 2,
+			this._title.offset, this._title.value, this._title.settings);
+	},
+
+	/* Calculate the actual dimensions of the chart 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) {
+		area = area || this._area;
+		var availWidth = this._getValue(this._chartCont, 'width');
+		var availHeight = this._getValue(this._chartCont, '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;
+		return [left, top, width, height];
+	},
+
+	/* 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._chartCont, {class_: 'background'});
+		var dims = this._getDims();
+		this._wrapper.rect(bg, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this._chartFormat);
+		if (this._gridlines[0] && this.yAxis._ticks.major && !noYGrid) {
+			this._drawGridlines(bg, this.yAxis, true, dims, this._gridlines[0]);
+		}
+		if (this._gridlines[1] && this.xAxis._ticks.major && !noXGrid) {
+			this._drawGridlines(bg, this.xAxis, false, dims, this._gridlines[1]);
+		}
+		return bg;
+	},
+
+	/* Draw one set of gridlines.
+	   @param  bg      (element) the background group element
+	   @param  axis    (SVGGraphAxis) the axis definition
+	   @param  horiz   (boolean) true if horizontal, false if vertical
+	   @param  dims    (number[]) the left, top, width, height of the chart area
+	   @param  format  (object) additional settings for the gridlines */
+	_drawGridlines: function(bg, axis, horiz, dims, format) {
+		var g = this._wrapper.group(bg, format);
+		var scale = (horiz ? dims[this.H] : dims[this.W]) / (axis._scale.max - axis._scale.min);
+		var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
+		major = (major < axis._scale.min ? major + axis._ticks.major : major);
+		while (major <= axis._scale.max) {
+			var v = (horiz ? axis._scale.max - major : major - axis._scale.min) * scale +
+				(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 the axes in their standard configuration.
+	   @param  noX  (boolean) true to suppress the x-axes, false to draw it (optional) */
+	_drawAxes: function(noX) {
+		var dims = this._getDims();
+		if (this.xAxis && !noX) {
+			if (this.xAxis._title) {
+				this._wrapper.text(this._chartCont, dims[this.X] + dims[this.W] / 2,
+					dims[this.Y] + dims[this.H] + this.xAxis._titleOffset,
+					this.xAxis._title, this.xAxis._titleFormat);
+			}
+			this._drawAxis(this.xAxis, 'xAxis', dims[this.X], dims[this.Y] + dims[this.H],
+				dims[this.X] + dims[this.W], dims[this.Y] + dims[this.H]);
+		}
+		if (this.yAxis) {
+			if (this.yAxis._title) {
+				this._wrapper.text(this._chartCont, 0, 0, this.yAxis._title, $.extend({textAnchor: 'middle',
+					transform: 'translate(' + (dims[this.X] - this.yAxis._titleOffset) + ',' +
+					(dims[this.Y] + dims[this.H] / 2) + ') rotate(-90)'}, this.yAxis._titleFormat || {}));
+			}
+			this._drawAxis(this.yAxis, 'yAxis', dims[this.X], dims[this.Y],
+				dims[this.X], dims[this.Y] + dims[this.H]);
+		}
+		if (this.x2Axis && !noX) {
+			if (this.x2Axis._title) {
+				this._wrapper.text(this._chartCont, dims[this.X] + dims[this.W] / 2,
+					dims[this.X] - this.x2Axis._titleOffset, this.x2Axis._title, this.x2Axis._titleFormat);
+			}
+			this._drawAxis(this.x2Axis, 'x2Axis', dims[this.X], dims[this.Y],
+				dims[this.X] + dims[this.W], dims[this.Y]);
+		}
+		if (this.y2Axis) {
+			if (this.y2Axis._title) {
+				this._wrapper.text(this._chartCont, 0, 0, this.y2Axis._title, $.extend({textAnchor: 'middle',
+					transform: 'translate(' + (dims[this.X] + dims[this.W] + this.y2Axis._titleOffset) +
+					',' + (dims[this.Y] + dims[this.H] / 2) + ') rotate(-90)'}, this.y2Axis._titleFormat || {}));
+			}
+			this._drawAxis(this.y2Axis, 'y2Axis', dims[this.X] + dims[this.W], dims[this.Y],
+				dims[this.X] + dims[this.W], dims[this.Y] + dims[this.H]);
+		}
+	},
+
+	/* Draw an axis and its tick marks.
+	   @param  axis  (SVGGraphAxis) the axis definition
+	   @param  id    (string) the identifier for the axis group element
+	   @param  x1    (number) starting x-coodinate for the axis
+	   @param  y1    (number) starting y-coodinate for the axis
+	   @param  x2    (number) ending x-coodinate for the axis
+	   @param  y2    (number) ending y-coodinate for the axis */
+	_drawAxis: function(axis, id, x1, y1, x2, y2) {
+		var horiz = (y1 == y2);
+		var gl = this._wrapper.group(this._chartCont, $.extend({class_: id}, axis._lineFormat));
+		var gt = this._wrapper.group(this._chartCont, $.extend({class_: id + 'Labels',
+			textAnchor: (horiz ? 'middle' : 'end')}, axis._labelFormat));
+		this._wrapper.line(gl, x1, y1, x2, y2);
+		if (axis._ticks.major) {
+			var bottomRight = (x2 > (this._getValue(this._chartCont, 'width') / 2) &&
+				y2 > (this._getValue(this._chartCont, 'height') / 2));
+			var scale = (horiz ? x2 - x1 : y2 - y1) / (axis._scale.max - axis._scale.min);
+			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 = this._getTickOffsets(axis, bottomRight);
+			var count = 0;
+			while (major <= axis._scale.max || minor <= axis._scale.max) {
+				var cur = Math.min(major, minor);
+				var len = (cur == major ? size : size / 2);
+				var v = (horiz ? x1 : y1) +
+					(horiz ? cur - axis._scale.min : axis._scale.max - cur) * scale;
+				this._wrapper.line(gl, (horiz ? v : x1 + len * offsets[0]),
+					(horiz ? y1 + len * offsets[0] : v),
+					(horiz ? v : x1 + len * offsets[1]),
+					(horiz ? y1 + len * offsets[1] : v));
+				if (cur == major) {
+					this._wrapper.text(gt, (horiz ? v : x1 - size), (horiz ? y1 + 2 * size : v),
+						(axis._labels ? axis._labels[count++] : '' + cur));
+				}
+				major += (cur == major ? axis._ticks.major : 0);
+				minor += (cur == minor ? axis._ticks.minor : 0);
+			}
+		}
+	},
+
+	/* Calculate offsets based on axis and tick positions.
+	   @param  axis         (SVGGraphAxis) the axis definition
+	   @param  bottomRight  (boolean) true if this axis is appearing on the bottom or
+	                        right of the chart area, false if to the top or left
+	   @return  (number[2]) the array of offset multipliers (-1..+1) */
+	_getTickOffsets: function(axis, bottomRight) {
+		return [(axis._ticks.position == (bottomRight ? 'in' : 'out') ||
+			axis._ticks.position == 'both' ? -1 : 0),
+			(axis._ticks.position == (bottomRight ? 'out' : 'in') ||
+			axis._ticks.position == 'both' ? +1 : 0), ];
+	},
+
+	/* Retrieve the standard percentage axis.
+	   @return  (SVGGraphAxis) percentage axis */
+	_getPercentageAxis: function() {
+		this._percentageAxis._title = $.svg.graphing.region.percentageText;
+		return this._percentageAxis;
+	},
+
+	/* Calculate the column totals across all the series. */
+	_getTotals: function() {
+		var totals = [];
+		var numVal = (this._series.length ? this._series[0]._values.length : 0);
+		for (var i = 0; i < numVal; i++) {
+			totals[i] = 0;
+			for (var j = 0; j < this._series.length; j++) {
+				totals[i] += this._series[j]._values[i];
+			}
+		}
+		return totals;
+	},
+
+	/* Draw the chart legend. */
+	_drawLegend: function() {
+		if (!this.legend._show) {
+			return;
+		}
+		var g = this._wrapper.group(this._chartCont, {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 numSer = this._series.length;
+		var offset = (horiz ? dims[this.W] : dims[this.H]) / numSer;
+		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 < numSer; i++) {
+			var series = this._series[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: series._fill, stroke: series._stroke, strokeWidth: 1});
+			this._wrapper.text(g, xBase + (horiz ? i * offset : 0) + this.legend._sampleSize + 5,
+				yBase + (horiz ? 0 : i * offset), series._name, this.legend._textSettings);
+		}
+	},
+
+	/* Show the current value status on hover. */
+	_showStatus: function(elem, label, value) {
+		var status = this._onstatus;
+		if (this._onstatus) {
+			$(elem).hover(function() { status.apply(this, [label, value]); },
+				function() { status.apply(this, ['', 0]); });
+		}
+	}
+});
+
+/* Details about each graph series.
+   @param  graph        (SVGGraph) the owning graph
+   @param  name         (string) the name of this series (optional)
+   @param  values       (number[]) the list of values to be plotted
+   @param  fill         (string) how the series should be displayed
+   @param  stroke       (string) the colour of the (out)line for the series (optional)
+   @param  strokeWidth  (number) the width of the (out)line for the series (optional)
+   @param  settings     (object) additional formatting settings (optional)
+   @return  (SVGGraphSeries) the new series object */
+function SVGGraphSeries(graph, name, values, fill, stroke, strokeWidth, settings) {
+	if (typeof name != 'string') {
+		settings = strokeWidth;
+		strokeWidth = stroke;
+		stroke = fill;
+		fill = values;
+		values = name;
+		name = null;
+	}
+	if (typeof stroke != 'string') {
+		settings = strokeWidth;
+		strokeWidth = stroke;
+		stroke = null;
+	}
+	if (typeof strokeWidth != 'number') {
+		settings = strokeWidth;
+		strokeWidth = null;
+	}
+	this._graph = graph; // The owning graph
+	this._name = name || ''; // The name of this series
+	this._values = values || []; // The list of values for this series
+	this._axis = 1; // Which axis this series applies to: 1 = primary, 2 = secondary
+	this._fill = fill || 'green'; // How the series is plotted
+	this._stroke = stroke || 'black'; // The colour for the (out)line
+	this._strokeWidth = strokeWidth || 1; // The (out)line width
+	this._settings = settings || {}; // Additional formatting settings for the series
+}
+
+$.extend(SVGGraphSeries.prototype, {
+
+	/* Set or retrieve the name for this series.
+	   @param  name    (string) the series' name
+	   @return  (SVGGraphSeries) this series object or
+	            (string) the series name (if no parameters) */
+	name: function(name) {
+		if (arguments.length == 0) {
+			return this._name;
+		}
+		this._name = name;
+		this._graph._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the values for this series.
+	   @param  name    (string) the series' name (optional)
+	   @param  values  (number[]) the values to be graphed
+	   @return  (SVGGraphSeries) this series object or
+	            (number[]) the series values (if no parameters) */
+	values: function(name, values) {
+		if (arguments.length == 0) {
+			return this._values;
+		}
+		if (isArray(name)) {
+			values = name;
+			name = null;
+		}
+		this._name = name || this._name;
+		this._values = values;
+		this._graph._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the formatting for this series.
+	   @param  fill         (string) how the values are filled when plotted
+	   @param  stroke       (string) the (out)line colour (optional)
+	   @param  strokeWidth  (number) the line's width (optional)
+	   @param  settings     (object) additional formatting settings for the series (optional)
+	   @return  (SVGGraphSeries) this series object or
+	            (object) formatting settings (if no parameters) */
+	format: function(fill, stroke, strokeWidth, settings) {
+		if (arguments.length == 0) {
+			return $.extend({fill: this._fill, stroke: this._stroke,
+				strokeWidth: this._strokeWidth}, this._settings);
+		}
+		if (typeof stroke != 'string') {
+			settings = strokeWidth;
+			strokeWidth = stroke;
+			stroke = null;
+		}
+		if (typeof strokeWidth != 'number') {
+			settings = strokeWidth;
+			strokeWidth = null;
+		}
+		this._fill = fill || this._fill;
+		this._stroke = stroke || this._stroke;
+		this._strokeWidth = strokeWidth || this._strokeWidth;
+		$.extend(this._settings, settings || {});
+		this._graph._drawGraph();
+		return this;
+	},
+
+	/* Return to the parent graph. */
+	end: function() {
+		return this._graph;
+	}
+});
+
+/* Details about each graph axis.
+   @param  graph  (SVGGraph) the owning graph
+   @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  (SVGGraphAxis) the new axis object */
+function SVGGraphAxis(graph, title, min, max, major, minor) {
+	this._graph = graph; // The owning graph
+	this._title = title || ''; // Title of this axis
+	this._titleFormat = {}; // Formatting settings for the title
+	this._titleOffset = 0; // The offset for positioning the title
+	this._labels = null; // List of labels for this axis - one per possible value across all series
+	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: 'out'}; // 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(SVGGraphAxis.prototype, {
+
+	/* Set or retrieve the scale for this axis.
+	   @param  min  (number) the minimum value shown
+	   @param  max  (number) the maximum value shown
+	   @return  (SVGGraphAxis) 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._graph._drawGraph();
+		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:
+	                     'in', 'out', 'both' (optional)
+	   @return  (SVGGraphAxis) 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._graph._drawGraph();
+		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  (SVGGraphAxis) 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._graph._drawGraph();
+		return this;
+	},
+
+	/* Set or retrieve the labels for this axis.
+	   @param  labels  (string[]) the text for each entry
+	   @param  colour  (string) how to colour the labels (optional) 
+	   @param  format  (object) formatting settings for the labels (optional)
+	   @return  (SVGGraphAxis) this axis object or
+	            (object) labels and format values (if no parameters) */
+	labels: function(labels, colour, format) {
+		if (arguments.length == 0) {
+			return {labels: this._labels, format: this._labelFormat};
+		}
+		if (typeof colour != 'string') {
+			format = colour;
+			colour = null;
+		}
+		this._labels = labels;
+		if (colour || format) {
+			this._labelFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
+		}
+		this._graph._drawGraph();
+		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  (SVGGraphAxis) 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 == 'object') {
+			settings = width;
+			width = null;
+		}
+		$.extend(this._lineFormat, {stroke: colour},
+			(width ? {strokeWidth: width} : {}), settings || {});
+		this._graph._drawGraph();
+		return this;
+	},
+
+	/* Return to the parent graph. */
+	end: function() {
+		return this._graph;
+	}
+});
+
+/* Details about the graph legend.
+   @param  graph         (SVGGraph) the owning graph
+   @param  bgSettings    (object) additional formatting settings for the legend background (optional)
+   @param  textSettings  (object) additional formatting settings for the legend text (optional)
+   @return  (SVGGraphLegend) the new legend object */
+function SVGGraphLegend(graph, bgSettings, textSettings) {
+	this._graph = graph; // The owning graph
+	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(SVGGraphLegend.prototype, {
+
+	/* Set or retrieve whether the legend should be shown.
+	   @param  show  (boolean) true to display it, false to hide it
+	   @return  (SVGGraphLegend) 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._graph._drawGraph();
+		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  (SVGGraphLegend) 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._graph._drawGraph();
+		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  (SVGGraphLegend) 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 != 'number') {
+			textSettings = bgSettings;
+			bgSettings = sampleSize;
+			sampleSize = null;
+		}
+		this._sampleSize = sampleSize || this._sampleSize;
+		this._bgSettings = bgSettings;
+		this._textSettings = textSettings || this._textSettings;
+		this._graph._drawGraph();
+		return this;
+	},
+
+	/* Return to the parent graph. */
+	end: function() {
+		return this._graph;
+	}
+});
+
+//==============================================================================
+
+/* Round a number to a given number of decimal points. */
+function roundNumber(num, dec) {
+	return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
+}
+
+var barOptions = ['barWidth (number) - the width of each bar',
+	'barGap (number) - the gap between sets of bars'];
+
+//------------------------------------------------------------------------------
+
+/* Draw a standard grouped column bar chart. */
+function SVGColumnChart() {
+}
+
+$.extend(SVGColumnChart.prototype, {
+
+	/* Retrieve the display title for this chart type.
+	   @return  the title */
+	title: function() {
+		return 'Basic column chart';
+	},
+
+	/* Retrieve a description of this chart type.
+	   @return  its description */
+	description: function() {
+		return 'Compare sets of values as vertical bars with grouped categories.';
+	},
+
+	/* Retrieve a list of the options that may be set for this chart type.
+	   @return  options list */
+	options: function() {
+		return barOptions;
+	},
+
+	/* Actually draw the graph in this type's style.
+	   @param  graph  (object) the SVGGraph object */
+	drawGraph: function(graph) {
+		graph._drawChartBackground(true);
+		var barWidth = graph._chartOptions.barWidth || 10;
+		var barGap = graph._chartOptions.barGap || 10;
+		var numSer = graph._series.length;
+		var numVal = (numSer ? (graph._series[0])._values.length : 0);
+		var dims = graph._getDims();
+		var xScale = dims[graph.W] / ((numSer * barWidth + barGap) * numVal + barGap);
+		var yScale = dims[graph.H] / (graph.yAxis._scale.max - graph.yAxis._scale.min);
+		this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
+		for (var i = 0; i < numSer; i++) {
+			this._drawSeries(graph, i, numSer, barWidth, barGap, dims, xScale, yScale);
+		}
+		graph._drawTitle();
+		graph._drawAxes(true);
+		this._drawXAxis(graph, numSer, numVal, barWidth, barGap, dims, xScale);
+		graph._drawLegend();
+	},
+
+	/* Plot an individual series. */
+	_drawSeries: function(graph, cur, numSer, barWidth, barGap, dims, xScale, yScale) {
+		var series = graph._series[cur];
+		var g = graph._wrapper.group(this._chart,
+			$.extend({class_: 'series' + cur, fill: series._fill, stroke: series._stroke,
+			strokeWidth: series._strokeWidth}, series._settings || {}));
+		for (var i = 0; i < series._values.length; i++) {
+			var r = graph._wrapper.rect(g,
+				dims[graph.X] + xScale * (barGap + i * (numSer * barWidth + barGap) + (cur * barWidth)),
+				dims[graph.Y] + yScale * (graph.yAxis._scale.max - series._values[i]),
+				xScale * barWidth, yScale * series._values[i]);
+			graph._showStatus(r, series._name, series._values[i]);
+		}
+	},
+
+	/* Draw the x-axis and its ticks. */
+	_drawXAxis: function(graph, numSer, numVal, barWidth, barGap, dims, xScale) {
+		var axis = graph.xAxis;
+		if (axis._title) {
+			graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
+				dims[graph.Y] + dims[graph.H] + axis._titleOffset,
+				axis._title, $.extend({textAnchor: 'middle'}, axis._titleFormat || {}));
+		}
+		var gl = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxis'}, axis._lineFormat));
+		var gt = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxisLabels',
+			textAnchor: 'middle'}, axis._labelFormat));
+		graph._wrapper.line(gl, dims[graph.X], dims[graph.Y] + dims[graph.H],
+			dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
+		if (axis._ticks.major) {
+			var offsets = graph._getTickOffsets(axis, true);
+			for (var i = 1; i < numVal; i++) {
+				var x = dims[graph.X] + xScale * (barGap / 2 + i * (numSer * barWidth + barGap));
+				graph._wrapper.line(gl, x, dims[graph.Y] + dims[graph.H] + offsets[0] * axis._ticks.size,
+					x, dims[graph.Y] + dims[graph.H] + offsets[1] * axis._ticks.size);
+			}
+			for (var i = 0; i < numVal; i++) {
+				var x = dims[graph.X] + xScale * (barGap / 2 + (i + 0.5) * (numSer * barWidth + barGap));
+				graph._wrapper.text(gt, x, dims[graph.Y] + dims[graph.H] + 2 * axis._ticks.size,
+					(axis._labels ? axis._labels[i] : '' + i));
+			}
+		}
+	}
+});
+
+//------------------------------------------------------------------------------
+
+/* Draw a stacked column bar chart. */
+function SVGStackedColumnChart() {
+}
+
+$.extend(SVGStackedColumnChart.prototype, {
+
+	/* Retrieve the display title for this chart type.
+	   @return  the title */
+	title: function() {
+		return 'Stacked column chart';
+	},
+
+	/* Retrieve a description of this chart type.
+	   @return  its description */
+	description: function() {
+		return 'Compare sets of values as vertical bars showing ' +
+			'relative contributions to the whole for each category.';
+	},
+
+	/* Retrieve a list of the options that may be set for this chart type.
+	   @return  options list */
+	options: function() {
+		return barOptions;
+	},
+
+	/* Actually draw the graph in this type's style.
+	   @param  graph  (object) the SVGGraph object */
+	drawGraph: function(graph) {
+		var bg = graph._drawChartBackground(true, true);
+		var dims = graph._getDims();
+		if (graph._gridlines[0] && graph.xAxis._ticks.major) {
+			graph._drawGridlines(bg, graph._getPercentageAxis(), true, dims, graph._gridlines[0]);
+		}
+		var barWidth = graph._chartOptions.barWidth || 10;
+		var barGap = graph._chartOptions.barGap || 10;
+		var numSer = graph._series.length;
+		var numVal = (numSer ? (graph._series[0])._values.length : 0);
+		var xScale = dims[graph.W] / ((barWidth + barGap) * numVal + barGap);
+		var yScale = dims[graph.H];
+		this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
+		this._drawColumns(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale);
+		graph._drawTitle();
+		graph._wrapper.text(graph._chartCont, 0, 0, $.svg.graphing.region.percentageText,
+			$.extend({textAnchor: 'middle', transform: 'translate(' +
+			(dims[graph.X] - graph.yAxis._titleOffset) + ',' +
+			(dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}, graph.yAxis._titleFormat || {}));
+		var pAxis = $.extend({}, graph._getPercentageAxis());
+		$.extend(pAxis._labelFormat, graph.yAxis._labelFormat || {});
+		graph._drawAxis(pAxis, 'yAxis', dims[graph.X], dims[graph.Y],
+			dims[graph.X], dims[graph.Y] + dims[graph.H]);
+		this._drawXAxis(graph, numVal, barWidth, barGap, dims, xScale);
+		graph._drawLegend();
+	},
+
+	/* Plot all of the columns. */
+	_drawColumns: function(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale) {
+		var totals = graph._getTotals();
+		var accum = [];
+		for (var i = 0; i < numVal; i++) {
+			accum[i] = 0;
+		}
+		for (var s = 0; s < numSer; s++) {
+			var series = graph._series[s];
+			var g = graph._wrapper.group(this._chart,
+				$.extend({class_: 'series' + s, fill: series._fill,
+				stroke: series._stroke, strokeWidth: series._strokeWidth},
+				series._settings || {}));
+			for (var i = 0; i < series._values.length; i++) {
+				accum[i] += series._values[i];
+				var r = graph._wrapper.rect(g,
+					dims[graph.X] + xScale * (barGap + i * (barWidth + barGap)),
+					dims[graph.Y] + yScale * (totals[i] - accum[i]) / totals[i],
+					xScale * barWidth, yScale * series._values[i] / totals[i]);
+				graph._showStatus(r, series._name,
+					roundNumber(series._values[i] / totals[i] * 100, 2));
+			}
+		}
+	},
+
+	/* Draw the x-axis and its ticks. */
+	_drawXAxis: function(graph, numVal, barWidth, barGap, dims, xScale) {
+		var axis = graph.xAxis;
+		if (axis._title) {
+			graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
+				dims[graph.Y] + dims[graph.H] + axis._titleOffset,
+				axis._title, $.extend({textAnchor: 'middle'}, axis._titleFormat || {}));
+		}
+		var gl = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxis'}, axis._lineFormat));
+		var gt = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxisLabels',
+			textAnchor: 'middle'}, axis._labelFormat));
+		graph._wrapper.line(gl, dims[graph.X], dims[graph.Y] + dims[graph.H],
+		dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
+		if (axis._ticks.major) {
+			var offsets = graph._getTickOffsets(axis, true);
+			for (var i = 1; i < numVal; i++) {
+				var x = dims[graph.X] + xScale * (barGap / 2 + i * (barWidth + barGap));
+				graph._wrapper.line(gl, x, dims[graph.Y] + dims[graph.H] + offsets[0] * axis._ticks.size,
+					x, dims[graph.Y] + dims[graph.H] + offsets[1] * axis._ticks.size);
+			}
+			for (var i = 0; i < numVal; i++) {
+				var x = dims[graph.X] + xScale * (barGap / 2 + (i + 0.5) * (barWidth + barGap));
+				graph._wrapper.text(gt, x, dims[graph.Y] + dims[graph.H] + 2 * axis._ticks.size,
+					(axis._labels ? axis._labels[i] : '' + i));
+			}
+		}
+	}
+});
+
+//------------------------------------------------------------------------------
+
+/* Draw a standard grouped row bar chart. */
+function SVGRowChart() {
+}
+
+$.extend(SVGRowChart.prototype, {
+
+	/* Retrieve the display title for this chart type.
+	   @return  the title */
+	title: function() {
+		return 'Basic row chart';
+	},
+
+	/* Retrieve a description of this chart type.
+	   @return  its description */
+	description: function() {
+		return 'Compare sets of values as horizontal rows with grouped categories.';
+	},
+
+	/* Retrieve a list of the options that may be set for this chart type.
+	   @return  options list */
+	options: function() {
+		return barOptions;
+	},
+
+	/* Actually draw the graph in this type's style.
+	   @param  graph  (object) the SVGGraph object */
+	drawGraph: function(graph) {
+		var bg = graph._drawChartBackground(true, true);
+		var dims = graph._getDims();
+		graph._drawGridlines(bg, graph.yAxis, false, dims, graph._gridlines[0]);
+		var barWidth = graph._chartOptions.barWidth || 10;
+		var barGap = graph._chartOptions.barGap || 10;
+		var numSer = graph._series.length;
+		var numVal = (numSer ? (graph._series[0])._values.length : 0);
+		var xScale = dims[graph.W] / (graph.yAxis._scale.max - graph.yAxis._scale.min);
+		var yScale = dims[graph.H] / ((numSer * barWidth + barGap) * numVal + barGap);
+		this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
+		for (var i = 0; i < numSer; i++) {
+			this._drawSeries(graph, i, numSer, barWidth, barGap, dims, xScale, yScale);
+		}
+		graph._drawTitle();
+		this._drawAxes(graph, numSer, numVal, barWidth, barGap, dims, yScale);
+		graph._drawLegend();
+	},
+
+	/* Plot an individual series. */
+	_drawSeries: function(graph, cur, numSer, barWidth, barGap, dims, xScale, yScale) {
+		var series = graph._series[cur];
+		var g = graph._wrapper.group(this._chart,
+			$.extend({class_: 'series' + cur, fill: series._fill,
+			stroke: series._stroke, strokeWidth: series._strokeWidth},
+			series._settings || {}));
+		for (var i = 0; i < series._values.length; i++) {
+			var r = graph._wrapper.rect(g,
+				dims[graph.X] + xScale * (0 - graph.yAxis._scale.min),
+				dims[graph.Y] + yScale * (barGap + i * (numSer * barWidth + barGap) + (cur * barWidth)),
+				xScale * series._values[i], yScale * barWidth);
+			graph._showStatus(r, series._name, series._values[i]);
+		}
+	},
+
+	/* Draw the axes for this graph. */
+	_drawAxes: function(graph, numSer, numVal, barWidth, barGap, dims, yScale) {
+		// X-axis
+		var axis = graph.yAxis;
+		if (axis) {
+			if (axis._title) {
+				graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
+					dims[graph.Y] + dims[graph.H] + axis._titleOffset, axis._title, axis._titleFormat);
+			}
+			graph._drawAxis(axis, 'xAxis', dims[graph.X], dims[graph.Y] + dims[graph.H],
+				dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
+		}
+		// Y-axis
+		var axis = graph.xAxis;
+		if (axis._title) {
+			graph._wrapper.text(graph._chartCont, 0, 0, axis._title, $.extend({textAnchor: 'middle',
+				transform: 'translate(' + (dims[graph.X] - axis._titleOffset) + ',' +
+				(dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}, axis._titleFormat || {}));
+		}
+		var gl = graph._wrapper.group(graph._chartCont, $.extend({class_: 'yAxis'}, axis._lineFormat));
+		var gt = graph._wrapper.group(graph._chartCont, $.extend(
+			{class_: 'yAxisLabels', textAnchor: 'end'}, axis._labelFormat));
+		graph._wrapper.line(gl, dims[graph.X], dims[graph.Y], dims[graph.X], dims[graph.Y] + dims[graph.H]);
+		if (axis._ticks.major) {
+			var offsets = graph._getTickOffsets(axis, false);
+			for (var i = 1; i < numVal; i++) {
+				var y = dims[graph.Y] + yScale * (barGap / 2 + i * (numSer * barWidth + barGap));
+				graph._wrapper.line(gl, dims[graph.X] + offsets[0] * axis._ticks.size, y,
+					dims[graph.X] + offsets[1] * axis._ticks.size, y);
+			}
+			for (var i = 0; i < numVal; i++) {
+				var y = dims[graph.Y] + yScale * (barGap / 2 + (i + 0.5) * (numSer * barWidth + barGap));
+				graph._wrapper.text(gt, dims[graph.X] - axis._ticks.size, y,
+					(axis._labels ? axis._labels[i] : '' + i));
+			}
+		}
+	}
+});
+
+//------------------------------------------------------------------------------
+
+/* Draw a stacked row bar chart. */
+function SVGStackedRowChart() {
+}
+
+$.extend(SVGStackedRowChart.prototype, {
+
+	/* Retrieve the display title for this chart type.
+	   @return  the title */
+	title: function() {
+		return 'Stacked row chart';
+	},
+
+	/* Retrieve a description of this chart type.
+	   @return  its description */
+	description: function() {
+		return 'Compare sets of values as horizontal bars showing ' +
+			'relative contributions to the whole for each category.';
+	},
+
+	/* Retrieve a list of the options that may be set for this chart type.
+	   @return  options list */
+	options: function() {
+		return barOptions;
+	},
+
+	/* Actually draw the graph in this type's style.
+	   @param  graph  (object) the SVGGraph object */
+	drawGraph: function(graph) {
+		var bg = graph._drawChartBackground(true, true);
+		var dims = graph._getDims();
+		if (graph._gridlines[0] && graph.xAxis._ticks.major) {
+			graph._drawGridlines(bg, graph._getPercentageAxis(), false, dims, graph._gridlines[0]);
+		}
+		var barWidth = graph._chartOptions.barWidth || 10;
+		var barGap = graph._chartOptions.barGap || 10;
+		var numSer = graph._series.length;
+		var numVal = (numSer ? (graph._series[0])._values.length : 0);
+		var xScale = dims[graph.W];
+		var yScale = dims[graph.H] / ((barWidth + barGap) * numVal + barGap);
+		this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
+		this._drawRows(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale);
+		graph._drawTitle();
+		graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
+			dims[graph.Y] + dims[graph.H] + graph.xAxis._titleOffset,
+			$.svg.graphing.region.percentageText,
+			$.extend({textAnchor: 'middle'}, graph.yAxis._titleFormat || {}));
+		var pAxis = $.extend({}, graph._getPercentageAxis());
+		$.extend(pAxis._labelFormat, graph.yAxis._labelFormat || {});
+		graph._drawAxis(pAxis, 'xAxis', dims[graph.X], dims[graph.Y] + dims[graph.H],
+			dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
+		this._drawYAxis(graph, numVal, barWidth, barGap, dims, yScale);
+		graph._drawLegend();
+	},
+
+	/* Plot all of the rows. */
+	_drawRows: function(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale) {
+		var totals = graph._getTotals();
+		var accum = [];
+		for (var i = 0; i < numVal; i++) {
+			accum[i] = 0;
+		}
+		for (var s = 0; s < numSer; s++) {
+			var series = graph._series[s];
+			var g = graph._wrapper.group(this._chart,
+				$.extend({class_: 'series' + s, fill: series._fill,
+				stroke: series._stroke, strokeWidth: series._strokeWidth},
+				series._settings || {}));
+			for (var i = 0; i < series._values.length; i++) {
+				var r = graph._wrapper.rect(g,
+					dims[graph.X] + xScale * accum[i] / totals[i],
+					dims[graph.Y] + yScale * (barGap + i * (barWidth + barGap)),
+					xScale * series._values[i] / totals[i], yScale * barWidth);
+				graph._showStatus(r, series._name,
+					roundNumber(series._values[i] / totals[i] * 100, 2));
+				accum[i] += series._values[i];
+			}
+		}
+	},
+
+	/* Draw the y-axis and its ticks. */
+	_drawYAxis: function(graph, numVal, barWidth, barGap, dims, yScale) {
+		var axis = graph.xAxis;
+		if (axis._title) {
+			graph._wrapper.text(graph._chartCont, 0, 0, axis._title, $.extend({textAnchor: 'middle',
+				transform: 'translate(' + (dims[graph.X] - axis._titleOffset) + ',' +
+				(dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}, axis._titleFormat || {}));
+		}
+		var gl = graph._wrapper.group(graph._chartCont,
+			$.extend({class_: 'yAxis'}, axis._lineFormat));
+		var gt = graph._wrapper.group(graph._chartCont,
+			$.extend({class_: 'yAxisLabels', textAnchor: 'end'}, axis._labelFormat));
+		graph._wrapper.line(gl, dims[graph.X], dims[graph.Y],
+			dims[graph.X], dims[graph.Y] + dims[graph.H]);
+		if (axis._ticks.major) {
+			var offsets = graph._getTickOffsets(axis, false);
+			for (var i = 1; i < numVal; i++) {
+				var y = dims[graph.Y] + yScale * (barGap / 2 + i * (barWidth + barGap));
+				graph._wrapper.line(gl, dims[graph.X] + offsets[0] * axis._ticks.size, y,
+					dims[graph.X] + offsets[1] * axis._ticks.size, y);
+			}
+			for (var i = 0; i < numVal; i++) {
+				var y = dims[graph.Y] + yScale * (barGap / 2 + (i + 0.5) * (barWidth + barGap));
+				graph._wrapper.text(gt, dims[graph.X] - axis._ticks.size, y,
+					(axis._labels ? axis._labels[i] : '' + i));
+			}
+		}
+	}
+});
+
+//------------------------------------------------------------------------------
+
+/* Draw a standard line chart. */
+function SVGLineChart() {
+}
+
+$.extend(SVGLineChart.prototype, {
+
+	/* Retrieve the display title for this chart type.
+	   @return  the title */
+	title: function() {
+		return 'Basic line chart';
+	},
+
+	/* Retrieve a description of this chart type.
+	   @return  its description */
+	description: function() {
+		return 'Compare sets of values as continuous lines.';
+	},
+
+	/* Retrieve a list of the options that may be set for this chart type.
+	   @return  options list */
+	options: function() {
+		return [];
+	},
+	
+	/* Actually draw the graph in this type's style.
+	   @param  graph  (object) the SVGGraph object */
+	drawGraph: function(graph) {
+		graph._drawChartBackground();
+		var dims = graph._getDims();
+		var xScale = dims[graph.W] / (graph.xAxis._scale.max - graph.xAxis._scale.min);
+		var yScale = dims[graph.H] / (graph.yAxis._scale.max - graph.yAxis._scale.min);
+		this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
+		for (var i = 0; i < graph._series.length; i++) {
+			this._drawSeries(graph, i, dims, xScale, yScale);
+		}
+		graph._drawTitle();
+		graph._drawAxes();
+		graph._drawLegend();
+	},
+
+	/* Plot an individual series. */
+	_drawSeries: function(graph, cur, dims, xScale, yScale) {
+		var series = graph._series[cur];
+		var path = graph._wrapper.createPath();
+		for (var i = 0; i < series._values.length; i++) {
+			var x = dims[graph.X] + i * xScale;
+			var y = dims[graph.Y] + (graph.yAxis._scale.max - series._values[i]) * yScale;
+			if (i == 0) {
+				path.move(x, y);
+			}
+			else {
+				path.line(x, y);
+			}
+		}
+		var p = graph._wrapper.path(this._chart, path,
+			$.extend({id: 'series' + cur, fill: 'none', stroke: series._stroke,
+			strokeWidth: series._strokeWidth}, series._settings || {}));
+		graph._showStatus(p, series._name, 0);
+	}
+});
+
+//------------------------------------------------------------------------------
+
+/* Draw a standard pie chart. */
+function SVGPieChart() {
+}
+
+$.extend(SVGPieChart.prototype, {
+
+	_options: ['explode (number or number[]) - indexes of sections to explode out of the pie',
+		'explodeDist (number) - the distance to move an exploded section',
+		'pieGap (number) - the distance between pies for multiple values'],
+
+	/* Retrieve the display title for this chart type.
+	   @return  the title */
+	title: function() {
+		return 'Pie chart';
+	},
+
+	/* Retrieve a description of this chart type.
+	   @return  its description */
+	description: function() {
+		return 'Compare relative sizes of values as contributions to the whole.';
+	},
+
+	/* Retrieve a list of the options that may be set for this chart type.
+	   @return  options list */
+	options: function() {
+		return this._options;
+	},
+
+	/* Actually draw the graph in this type's style.
+	   @param  graph  (object) the SVGGraph object */
+	drawGraph: function(graph) {
+		graph._drawChartBackground(true, true);
+		this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
+		var dims = graph._getDims();
+		this._drawSeries(graph, dims);
+		graph._drawTitle();
+		graph._drawLegend();
+	},
+
+	/* Plot all the series. */
+	_drawSeries: function(graph, dims) {
+		var totals = graph._getTotals();
+		var numSer = graph._series.length;
+		var numVal = (numSer ? (graph._series[0])._values.length : 0);
+		var path = graph._wrapper.createPath();
+		var explode = graph._chartOptions.explode || [];
+		explode = (isArray(explode) ? explode : [explode]);
+		var explodeDist = graph._chartOptions.explodeDist || 10;
+		var pieGap = (numVal <= 1 ? 0 : graph._chartOptions.pieGap || 10);
+		var xBase = (dims[graph.W] - (numVal * pieGap) - pieGap) / numVal / 2;
+		var yBase = dims[graph.H] / 2;
+		var radius = Math.min(xBase, yBase) - (explode.length > 0 ? explodeDist : 0);
+		var gt = graph._wrapper.group(graph._chartCont, $.extend(
+			{class_: 'xAxisLabels', textAnchor: 'middle'}, graph.xAxis._labelFormat));
+		var gl = [];
+		for (var i = 0; i < numVal; i++) {
+			var cx = dims[graph.X] + xBase + (i * (2 * Math.min(xBase, yBase) + pieGap)) + pieGap;
+			var cy = dims[graph.Y] + yBase;
+			var curTotal = 0;
+			for (var j = 0; j < numSer; j++) {
+				var series = graph._series[j];
+				if (i == 0) {
+					gl[j] = graph._wrapper.group(this._chart, $.extend({class_: 'series' + j,
+						fill: series._fill, stroke: series._stroke,
+						strokeWidth: series._strokeWidth}, series._settings || {}));
+				}
+				if (series._values[i] == 0) {
+					continue;
+				}
+				var start = (curTotal / totals[i]) * 2 * Math.PI;
+				curTotal += series._values[i];
+				var end = (curTotal / totals[i]) * 2 * Math.PI;
+				var exploding = false;
+				for (var k = 0; k < explode.length; k++) {
+					if (explode[k] == j) {
+						exploding = true;
+						break;
+					}
+				}
+				var x = cx + (exploding ? explodeDist * Math.cos((start + end) / 2) : 0);
+				var y = cy + (exploding ? explodeDist * Math.sin((start + end) / 2) : 0);
+				var p = graph._wrapper.path(gl[j], path.reset().move(x, y).
+					line(x + radius * Math.cos(start), y + radius * Math.sin(start)).
+					arc(radius, radius, 0, (end - start < Math.PI ? 0 : 1), 1,
+					x + radius * Math.cos(end), y + radius * Math.sin(end)).close());
+				graph._showStatus(p, series._name,
+					roundNumber((end - start) / 2 / Math.PI * 100, 2));
+			}
+			if (graph.xAxis) {
+				graph._wrapper.text(gt, cx, dims[graph.Y] + dims[graph.H] + graph.xAxis._titleOffset,
+					graph.xAxis._labels[i])
+			}
+		}
+	}
+});
+
+//------------------------------------------------------------------------------
+
+/* Determine whether an object is an array. */
+function isArray(a) {
+	return (a && a.constructor == Array);
+}
+
+// Basic chart types
+$.svg.graphing.addChartType('column', new SVGColumnChart());
+$.svg.graphing.addChartType('stackedColumn', new SVGStackedColumnChart());
+$.svg.graphing.addChartType('row', new SVGRowChart());
+$.svg.graphing.addChartType('stackedRow', new SVGStackedRowChart());
+$.svg.graphing.addChartType('line', new SVGLineChart());
+$.svg.graphing.addChartType('pie', new SVGPieChart());
+
+})(jQuery)