Mercurial > hg > digilib-old
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)