comparison client/digitallibrary/jquery/svg/jquery.svggraph.js @ 749:4b492b7900fb jquery

added jQuery ui and svg javascripts
author hertzhaft
date Sun, 06 Feb 2011 22:17:41 +0100
parents
children
comparison
equal deleted inserted replaced
748:fb4ffac2950d 749:4b492b7900fb
1 /* http://keith-wood.name/svg.html
2 SVG graphing extension for jQuery v1.4.3.
3 Written by Keith Wood (kbwood{at}iinet.com.au) August 2007.
4 Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
5 MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
6 Please attribute the author if you use it. */
7
8 (function($) { // Hide scope, no $ conflict
9
10 $.svg.addExtension('graph', SVGGraph);
11
12 // Singleton primary SVG graphing interface
13 $.svg.graphing = new SVGGraphing();
14
15 function SVGGraphing() {
16 this.regional = [];
17 this.regional[''] = {percentageText: 'Percentage'};
18 this.region = this.regional[''];
19 }
20
21 $.extend(SVGGraphing.prototype, {
22 _chartTypes: [],
23
24 /* Add a new chart rendering type to the package.
25 The rendering object must implement the following functions:
26 getTitle(), getDescription(), getOptions(), drawChart(graph).
27 @param id (string) the ID of this graph renderer
28 @param chartType (object) the object implementing this chart type */
29 addChartType: function(id, chartType) {
30 this._chartTypes[id] = chartType;
31 },
32
33 /* Retrieve the list of chart types.
34 @return (object[string]) the array of chart types indexed by ID */
35 chartTypes: function() {
36 return this._chartTypes;
37 }
38 });
39
40 /* Extension point for SVG graphing.
41 Access through svg.graph. */
42 function SVGGraph(wrapper) {
43 this._wrapper = wrapper; // The attached SVG wrapper object
44 this._drawNow = false; // True for immediate update, false to wait for redraw call
45 for (var id in $.svg.graphing._chartTypes) {
46 this._chartType = $.svg.graphing._chartTypes[id]; // Use first graph renderer
47 break;
48 }
49 this._chartOptions = {}; // Extra options for the graph type
50 // The graph title and settings
51 this._title = {value: '', offset: 25, settings: {textAnchor: 'middle'}};
52 this._area = [0.1, 0.1, 0.8, 0.9]; // The chart area: left, top, right, bottom,
53 // > 1 in pixels, <= 1 as proportion
54 this._chartFormat = {fill: 'none', stroke: 'black'}; // The formatting for the chart area
55 this._gridlines = []; // The formatting of the x- and y-gridlines
56 this._series = []; // The series to be plotted, each is an object
57 this._onstatus = null; // The callback function for status updates
58 this._chartCont = this._wrapper.svg(0, 0, 0, 0, {class_: 'svg-graph'}); // The main container for the graph
59
60 this.xAxis = new SVGGraphAxis(this); // The main x-axis
61 this.xAxis.title('', 40);
62 this.yAxis = new SVGGraphAxis(this); // The main y-axis
63 this.yAxis.title('', 40);
64 this.x2Axis = null; // The secondary x-axis
65 this.y2Axis = null; // The secondary y-axis
66 this.legend = new SVGGraphLegend(this); // The chart legend
67 this._drawNow = true;
68 }
69
70 $.extend(SVGGraph.prototype, {
71
72 /* Useful indexes. */
73 X: 0,
74 Y: 1,
75 W: 2,
76 H: 3,
77 L: 0,
78 T: 1,
79 R: 2,
80 B: 3,
81
82 /* Standard percentage axis. */
83 _percentageAxis: new SVGGraphAxis(this, $.svg.graphing.region.percentageText, 0, 100, 10, 0),
84
85 /* Set or retrieve the container for the graph.
86 @param cont (SVG element) the container for the graph
87 @return (SVGGraph) this graph object or
88 (SVG element) the current container (if no parameters) */
89 container: function(cont) {
90 if (arguments.length == 0) {
91 return this._chartCont;
92 }
93 this._chartCont = cont;
94 return this;
95 },
96
97 /* Set or retrieve the type of chart to be rendered.
98 See $.svg.graphing.getChartTypes() for the list of available types.
99 @param id (string) the ID of the chart type
100 @param options (object) additional settings for this chart type (optional)
101 @return (SVGGraph) this graph object or
102 (string) the chart type (if no parameters)
103 @deprecated use type() */
104 chartType: function(id, options) {
105 return (arguments.length == 0 ? this.type() : this.type(id, options));
106 },
107
108 /* Set or retrieve the type of chart to be rendered.
109 See $.svg.graphing.getChartTypes() for the list of available types.
110 @param id (string) the ID of the chart type
111 @param options (object) additional settings for this chart type (optional)
112 @return (SVGGraph) this graph object or
113 (string) the chart type (if no parameters) */
114 type: function(id, options) {
115 if (arguments.length == 0) {
116 return this._chartType;
117 }
118 var chartType = $.svg.graphing._chartTypes[id];
119 if (chartType) {
120 this._chartType = chartType;
121 this._chartOptions = $.extend({}, options || {});
122 }
123 this._drawGraph();
124 return this;
125 },
126
127 /* Set or retrieve additional options for the particular chart type.
128 @param options (object) the extra options
129 @return (SVGGraph) this graph object or
130 (object) the chart options (if no parameters)
131 @deprecated use options() */
132 chartOptions: function(options) {
133 return(arguments.length == 0 ? this.options() : this.options(options));
134 },
135
136 /* Set or retrieve additional options for the particular chart type.
137 @param options (object) the extra options
138 @return (SVGGraph) this graph object or
139 (object) the chart options (if no parameters) */
140 options: function(options) {
141 if (arguments.length == 0) {
142 return this._chartOptions;
143 }
144 this._chartOptions = $.extend({}, options);
145 this._drawGraph();
146 return this;
147 },
148
149 /* Set or retrieve the background of the graph chart.
150 @param fill (string) how to fill the chart background
151 @param stroke (string) the colour of the outline (optional)
152 @param settings (object) additional formatting for the chart background (optional)
153 @return (SVGGraph) this graph object or
154 (object) the chart format (if no parameters)
155 @deprecated use format() */
156 chartFormat: function(fill, stroke, settings) {
157 return (arguments.length == 0 ? this.format() : this.format(fill, stroke, settings));
158 },
159
160 /* Set or retrieve the background of the graph chart.
161 @param fill (string) how to fill the chart background
162 @param stroke (string) the colour of the outline (optional)
163 @param settings (object) additional formatting for the chart background (optional)
164 @return (SVGGraph) this graph object or
165 (object) the chart format (if no parameters) */
166 format: function(fill, stroke, settings) {
167 if (arguments.length == 0) {
168 return this._chartFormat;
169 }
170 if (typeof stroke == 'object') {
171 settings = stroke;
172 stroke = null;
173 }
174 this._chartFormat = $.extend({fill: fill},
175 (stroke ? {stroke: stroke} : {}), settings || {});
176 this._drawGraph();
177 return this;
178 },
179
180 /* Set or retrieve the main chart area.
181 @param left (number) > 1 is pixels, <= 1 is proportion of width or
182 (number[4]) for left, top, right, bottom
183 @param top (number) > 1 is pixels, <= 1 is proportion of height
184 @param right (number) > 1 is pixels, <= 1 is proportion of width
185 @param bottom (number) > 1 is pixels, <= 1 is proportion of height
186 @return (SVGGraph) this graph object or
187 (number[4]) the chart area: left, top, right, bottom (if no parameters)
188 @deprecated use area() */
189 chartArea: function(left, top, right, bottom) {
190 return (arguments.length == 0 ? this.area() : this.area(left, top, right, bottom));
191 },
192
193 /* Set or retrieve the main chart area.
194 @param left (number) > 1 is pixels, <= 1 is proportion of width or
195 (number[4]) for left, top, right, bottom
196 @param top (number) > 1 is pixels, <= 1 is proportion of height
197 @param right (number) > 1 is pixels, <= 1 is proportion of width
198 @param bottom (number) > 1 is pixels, <= 1 is proportion of height
199 @return (SVGGraph) this graph object or
200 (number[4]) the chart area: left, top, right, bottom (if no parameters) */
201 area: function(left, top, right, bottom) {
202 if (arguments.length == 0) {
203 return this._area;
204 }
205 this._area = (isArray(left) ? left : [left, top, right, bottom]);
206 this._drawGraph();
207 return this;
208 },
209
210 /* Set or retrieve the gridlines formatting for the graph chart.
211 @param xSettings (string) the colour of the gridlines along the x-axis, or
212 (object) formatting for the gridlines along the x-axis, or
213 null for none
214 @param ySettings (string) the colour of the gridlines along the y-axis, or
215 (object) formatting for the gridlines along the y-axis, or
216 null for none
217 @return (SVGGraph) this graph object or
218 (object[2]) the gridlines formatting (if no parameters) */
219 gridlines: function(xSettings, ySettings) {
220 if (arguments.length == 0) {
221 return this._gridlines;
222 }
223 this._gridlines = [(typeof xSettings == 'string' ? {stroke: xSettings} : xSettings),
224 (typeof ySettings == 'string' ? {stroke: ySettings} : ySettings)];
225 if (this._gridlines[0] == null && this._gridlines[1] == null) {
226 this._gridlines = [];
227 }
228 this._drawGraph();
229 return this;
230 },
231
232 /* Set or retrieve the title of the graph and its formatting.
233 @param value (string) the title
234 @param offset (number) the vertical positioning of the title
235 > 1 is pixels, <= 1 is proportion of width (optional)
236 @param colour (string) the colour of the title (optional)
237 @param settings (object) formatting for the title (optional)
238 @return (SVGGraph) this graph object or
239 (object) value, offset, and settings for the title (if no parameters) */
240 title: function(value, offset, colour, settings) {
241 if (arguments.length == 0) {
242 return this._title;
243 }
244 if (typeof offset != 'number') {
245 settings = colour;
246 colour = offset;
247 offset = null;
248 }
249 if (typeof colour != 'string') {
250 settings = colour;
251 colour = null;
252 }
253 this._title = {value: value, offset: offset || this._title.offset,
254 settings: $.extend({textAnchor: 'middle'},
255 (colour ? {fill: colour} : {}), settings || {})};
256 this._drawGraph();
257 return this;
258 },
259
260 /* Add a series of values to be plotted on the graph.
261 @param name (string) the name of this series (optional)
262 @param values (number[]) the values to be plotted
263 @param fill (string) how the plotted values are filled
264 @param stroke (string) the colour of the plotted lines (optional)
265 @param strokeWidth (number) the width of the plotted lines (optional)
266 @param settings (object) additional settings for the plotted values (optional)
267 @return (SVGGraph) this graph object */
268 addSeries: function(name, values, fill, stroke, strokeWidth, settings) {
269 this._series.push(new SVGGraphSeries(
270 this, name, values, fill, stroke, strokeWidth, settings));
271 this._drawGraph();
272 return this;
273 },
274
275 /* Retrieve the series wrappers.
276 @param i (number) the series index (optional)
277 @return (SVGGraphSeries) the specified series or
278 (SVGGraphSeries[]) the list of series */
279 series: function(i) {
280 return (arguments.length > 0 ? this._series[i] : null) || this._series;
281 },
282
283 /* Suppress drawing of the graph until redraw() is called.
284 @return (SVGGraph) this graph object */
285 noDraw: function() {
286 this._drawNow = false;
287 return this;
288 },
289
290 /* Redraw the entire graph with the current settings and values.
291 @return (SVGGraph) this graph object */
292 redraw: function() {
293 this._drawNow = true;
294 this._drawGraph();
295 return this;
296 },
297
298 /* Set the callback function for status updates.
299 @param onstatus (function) the callback function
300 @return (SVGGraph) this graph object */
301 status: function(onstatus) {
302 this._onstatus = onstatus;
303 return this;
304 },
305
306 /* Actually draw the graph (if allowed) based on the graph type set. */
307 _drawGraph: function() {
308 if (!this._drawNow) {
309 return;
310 }
311 while (this._chartCont.firstChild) {
312 this._chartCont.removeChild(this._chartCont.firstChild);
313 }
314 if (!this._chartCont.parent) {
315 this._wrapper._svg.appendChild(this._chartCont);
316 }
317 // Set sizes if not already there
318 if (!this._chartCont.width) {
319 this._chartCont.setAttribute('width',
320 parseInt(this._chartCont.getAttribute('width'), 10) || this._wrapper._width());
321 }
322 else if (this._chartCont.width.baseVal) {
323 this._chartCont.width.baseVal.value =
324 this._chartCont.width.baseVal.value || this._wrapper._width();
325 }
326 else {
327 this._chartCont.width = this._chartCont.width || this._wrapper._width();
328 }
329 if (!this._chartCont.height) {
330 this._chartCont.setAttribute('height',
331 parseInt(this._chartCont.getAttribute('height'), 10) || this._wrapper._height());
332 }
333 else if (this._chartCont.height.baseVal) {
334 this._chartCont.height.baseVal.value =
335 this._chartCont.height.baseVal.value || this._wrapper._height();
336 }
337 else {
338 this._chartCont.height = this._chartCont.height || this._wrapper._height();
339 }
340 this._chartType.drawGraph(this);
341 },
342
343 /* Decode an attribute value.
344 @param node the node to examine
345 @param name the attribute name
346 @return the actual value */
347 _getValue: function(node, name) {
348 return (!node[name] ? parseInt(node.getAttribute(name), 10) :
349 (node[name].baseVal ? node[name].baseVal.value : node[name]));
350 },
351
352 /* Draw the graph title - centred. */
353 _drawTitle: function() {
354 this._wrapper.text(this._chartCont, this._getValue(this._chartCont, 'width') / 2,
355 this._title.offset, this._title.value, this._title.settings);
356 },
357
358 /* Calculate the actual dimensions of the chart area.
359 @param area (number[4]) the area values to evaluate (optional)
360 @return (number[4]) an array of dimension values: left, top, width, height */
361 _getDims: function(area) {
362 area = area || this._area;
363 var availWidth = this._getValue(this._chartCont, 'width');
364 var availHeight = this._getValue(this._chartCont, 'height');
365 var left = (area[this.L] > 1 ? area[this.L] : availWidth * area[this.L]);
366 var top = (area[this.T] > 1 ? area[this.T] : availHeight * area[this.T]);
367 var width = (area[this.R] > 1 ? area[this.R] : availWidth * area[this.R]) - left;
368 var height = (area[this.B] > 1 ? area[this.B] : availHeight * area[this.B]) - top;
369 return [left, top, width, height];
370 },
371
372 /* Draw the chart background, including gridlines.
373 @param noXGrid (boolean) true to suppress the x-gridlines, false to draw them (optional)
374 @param noYGrid (boolean) true to suppress the y-gridlines, false to draw them (optional)
375 @return (element) the background group element */
376 _drawChartBackground: function(noXGrid, noYGrid) {
377 var bg = this._wrapper.group(this._chartCont, {class_: 'background'});
378 var dims = this._getDims();
379 this._wrapper.rect(bg, dims[this.X], dims[this.Y], dims[this.W], dims[this.H], this._chartFormat);
380 if (this._gridlines[0] && this.yAxis._ticks.major && !noYGrid) {
381 this._drawGridlines(bg, this.yAxis, true, dims, this._gridlines[0]);
382 }
383 if (this._gridlines[1] && this.xAxis._ticks.major && !noXGrid) {
384 this._drawGridlines(bg, this.xAxis, false, dims, this._gridlines[1]);
385 }
386 return bg;
387 },
388
389 /* Draw one set of gridlines.
390 @param bg (element) the background group element
391 @param axis (SVGGraphAxis) the axis definition
392 @param horiz (boolean) true if horizontal, false if vertical
393 @param dims (number[]) the left, top, width, height of the chart area
394 @param format (object) additional settings for the gridlines */
395 _drawGridlines: function(bg, axis, horiz, dims, format) {
396 var g = this._wrapper.group(bg, format);
397 var scale = (horiz ? dims[this.H] : dims[this.W]) / (axis._scale.max - axis._scale.min);
398 var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
399 major = (major < axis._scale.min ? major + axis._ticks.major : major);
400 while (major <= axis._scale.max) {
401 var v = (horiz ? axis._scale.max - major : major - axis._scale.min) * scale +
402 (horiz ? dims[this.Y] : dims[this.X]);
403 this._wrapper.line(g, (horiz ? dims[this.X] : v), (horiz ? v : dims[this.Y]),
404 (horiz ? dims[this.X] + dims[this.W] : v), (horiz ? v : dims[this.Y] + dims[this.H]));
405 major += axis._ticks.major;
406 }
407 },
408
409 /* Draw the axes in their standard configuration.
410 @param noX (boolean) true to suppress the x-axes, false to draw it (optional) */
411 _drawAxes: function(noX) {
412 var dims = this._getDims();
413 if (this.xAxis && !noX) {
414 if (this.xAxis._title) {
415 this._wrapper.text(this._chartCont, dims[this.X] + dims[this.W] / 2,
416 dims[this.Y] + dims[this.H] + this.xAxis._titleOffset,
417 this.xAxis._title, this.xAxis._titleFormat);
418 }
419 this._drawAxis(this.xAxis, 'xAxis', dims[this.X], dims[this.Y] + dims[this.H],
420 dims[this.X] + dims[this.W], dims[this.Y] + dims[this.H]);
421 }
422 if (this.yAxis) {
423 if (this.yAxis._title) {
424 this._wrapper.text(this._chartCont, 0, 0, this.yAxis._title, $.extend({textAnchor: 'middle',
425 transform: 'translate(' + (dims[this.X] - this.yAxis._titleOffset) + ',' +
426 (dims[this.Y] + dims[this.H] / 2) + ') rotate(-90)'}, this.yAxis._titleFormat || {}));
427 }
428 this._drawAxis(this.yAxis, 'yAxis', dims[this.X], dims[this.Y],
429 dims[this.X], dims[this.Y] + dims[this.H]);
430 }
431 if (this.x2Axis && !noX) {
432 if (this.x2Axis._title) {
433 this._wrapper.text(this._chartCont, dims[this.X] + dims[this.W] / 2,
434 dims[this.X] - this.x2Axis._titleOffset, this.x2Axis._title, this.x2Axis._titleFormat);
435 }
436 this._drawAxis(this.x2Axis, 'x2Axis', dims[this.X], dims[this.Y],
437 dims[this.X] + dims[this.W], dims[this.Y]);
438 }
439 if (this.y2Axis) {
440 if (this.y2Axis._title) {
441 this._wrapper.text(this._chartCont, 0, 0, this.y2Axis._title, $.extend({textAnchor: 'middle',
442 transform: 'translate(' + (dims[this.X] + dims[this.W] + this.y2Axis._titleOffset) +
443 ',' + (dims[this.Y] + dims[this.H] / 2) + ') rotate(-90)'}, this.y2Axis._titleFormat || {}));
444 }
445 this._drawAxis(this.y2Axis, 'y2Axis', dims[this.X] + dims[this.W], dims[this.Y],
446 dims[this.X] + dims[this.W], dims[this.Y] + dims[this.H]);
447 }
448 },
449
450 /* Draw an axis and its tick marks.
451 @param axis (SVGGraphAxis) the axis definition
452 @param id (string) the identifier for the axis group element
453 @param x1 (number) starting x-coodinate for the axis
454 @param y1 (number) starting y-coodinate for the axis
455 @param x2 (number) ending x-coodinate for the axis
456 @param y2 (number) ending y-coodinate for the axis */
457 _drawAxis: function(axis, id, x1, y1, x2, y2) {
458 var horiz = (y1 == y2);
459 var gl = this._wrapper.group(this._chartCont, $.extend({class_: id}, axis._lineFormat));
460 var gt = this._wrapper.group(this._chartCont, $.extend({class_: id + 'Labels',
461 textAnchor: (horiz ? 'middle' : 'end')}, axis._labelFormat));
462 this._wrapper.line(gl, x1, y1, x2, y2);
463 if (axis._ticks.major) {
464 var bottomRight = (x2 > (this._getValue(this._chartCont, 'width') / 2) &&
465 y2 > (this._getValue(this._chartCont, 'height') / 2));
466 var scale = (horiz ? x2 - x1 : y2 - y1) / (axis._scale.max - axis._scale.min);
467 var size = axis._ticks.size;
468 var major = Math.floor(axis._scale.min / axis._ticks.major) * axis._ticks.major;
469 major = (major < axis._scale.min ? major + axis._ticks.major : major);
470 var minor = (!axis._ticks.minor ? axis._scale.max + 1 :
471 Math.floor(axis._scale.min / axis._ticks.minor) * axis._ticks.minor);
472 minor = (minor < axis._scale.min ? minor + axis._ticks.minor : minor);
473 var offsets = this._getTickOffsets(axis, bottomRight);
474 var count = 0;
475 while (major <= axis._scale.max || minor <= axis._scale.max) {
476 var cur = Math.min(major, minor);
477 var len = (cur == major ? size : size / 2);
478 var v = (horiz ? x1 : y1) +
479 (horiz ? cur - axis._scale.min : axis._scale.max - cur) * scale;
480 this._wrapper.line(gl, (horiz ? v : x1 + len * offsets[0]),
481 (horiz ? y1 + len * offsets[0] : v),
482 (horiz ? v : x1 + len * offsets[1]),
483 (horiz ? y1 + len * offsets[1] : v));
484 if (cur == major) {
485 this._wrapper.text(gt, (horiz ? v : x1 - size), (horiz ? y1 + 2 * size : v),
486 (axis._labels ? axis._labels[count++] : '' + cur));
487 }
488 major += (cur == major ? axis._ticks.major : 0);
489 minor += (cur == minor ? axis._ticks.minor : 0);
490 }
491 }
492 },
493
494 /* Calculate offsets based on axis and tick positions.
495 @param axis (SVGGraphAxis) the axis definition
496 @param bottomRight (boolean) true if this axis is appearing on the bottom or
497 right of the chart area, false if to the top or left
498 @return (number[2]) the array of offset multipliers (-1..+1) */
499 _getTickOffsets: function(axis, bottomRight) {
500 return [(axis._ticks.position == (bottomRight ? 'in' : 'out') ||
501 axis._ticks.position == 'both' ? -1 : 0),
502 (axis._ticks.position == (bottomRight ? 'out' : 'in') ||
503 axis._ticks.position == 'both' ? +1 : 0), ];
504 },
505
506 /* Retrieve the standard percentage axis.
507 @return (SVGGraphAxis) percentage axis */
508 _getPercentageAxis: function() {
509 this._percentageAxis._title = $.svg.graphing.region.percentageText;
510 return this._percentageAxis;
511 },
512
513 /* Calculate the column totals across all the series. */
514 _getTotals: function() {
515 var totals = [];
516 var numVal = (this._series.length ? this._series[0]._values.length : 0);
517 for (var i = 0; i < numVal; i++) {
518 totals[i] = 0;
519 for (var j = 0; j < this._series.length; j++) {
520 totals[i] += this._series[j]._values[i];
521 }
522 }
523 return totals;
524 },
525
526 /* Draw the chart legend. */
527 _drawLegend: function() {
528 if (!this.legend._show) {
529 return;
530 }
531 var g = this._wrapper.group(this._chartCont, {class_: 'legend'});
532 var dims = this._getDims(this.legend._area);
533 this._wrapper.rect(g, dims[this.X], dims[this.Y], dims[this.W], dims[this.H],
534 this.legend._bgSettings);
535 var horiz = dims[this.W] > dims[this.H];
536 var numSer = this._series.length;
537 var offset = (horiz ? dims[this.W] : dims[this.H]) / numSer;
538 var xBase = dims[this.X] + 5;
539 var yBase = dims[this.Y] + ((horiz ? dims[this.H] : offset) + this.legend._sampleSize) / 2;
540 for (var i = 0; i < numSer; i++) {
541 var series = this._series[i];
542 this._wrapper.rect(g, xBase + (horiz ? i * offset : 0),
543 yBase + (horiz ? 0 : i * offset) - this.legend._sampleSize,
544 this.legend._sampleSize, this.legend._sampleSize,
545 {fill: series._fill, stroke: series._stroke, strokeWidth: 1});
546 this._wrapper.text(g, xBase + (horiz ? i * offset : 0) + this.legend._sampleSize + 5,
547 yBase + (horiz ? 0 : i * offset), series._name, this.legend._textSettings);
548 }
549 },
550
551 /* Show the current value status on hover. */
552 _showStatus: function(elem, label, value) {
553 var status = this._onstatus;
554 if (this._onstatus) {
555 $(elem).hover(function() { status.apply(this, [label, value]); },
556 function() { status.apply(this, ['', 0]); });
557 }
558 }
559 });
560
561 /* Details about each graph series.
562 @param graph (SVGGraph) the owning graph
563 @param name (string) the name of this series (optional)
564 @param values (number[]) the list of values to be plotted
565 @param fill (string) how the series should be displayed
566 @param stroke (string) the colour of the (out)line for the series (optional)
567 @param strokeWidth (number) the width of the (out)line for the series (optional)
568 @param settings (object) additional formatting settings (optional)
569 @return (SVGGraphSeries) the new series object */
570 function SVGGraphSeries(graph, name, values, fill, stroke, strokeWidth, settings) {
571 if (typeof name != 'string') {
572 settings = strokeWidth;
573 strokeWidth = stroke;
574 stroke = fill;
575 fill = values;
576 values = name;
577 name = null;
578 }
579 if (typeof stroke != 'string') {
580 settings = strokeWidth;
581 strokeWidth = stroke;
582 stroke = null;
583 }
584 if (typeof strokeWidth != 'number') {
585 settings = strokeWidth;
586 strokeWidth = null;
587 }
588 this._graph = graph; // The owning graph
589 this._name = name || ''; // The name of this series
590 this._values = values || []; // The list of values for this series
591 this._axis = 1; // Which axis this series applies to: 1 = primary, 2 = secondary
592 this._fill = fill || 'green'; // How the series is plotted
593 this._stroke = stroke || 'black'; // The colour for the (out)line
594 this._strokeWidth = strokeWidth || 1; // The (out)line width
595 this._settings = settings || {}; // Additional formatting settings for the series
596 }
597
598 $.extend(SVGGraphSeries.prototype, {
599
600 /* Set or retrieve the name for this series.
601 @param name (string) the series' name
602 @return (SVGGraphSeries) this series object or
603 (string) the series name (if no parameters) */
604 name: function(name) {
605 if (arguments.length == 0) {
606 return this._name;
607 }
608 this._name = name;
609 this._graph._drawGraph();
610 return this;
611 },
612
613 /* Set or retrieve the values for this series.
614 @param name (string) the series' name (optional)
615 @param values (number[]) the values to be graphed
616 @return (SVGGraphSeries) this series object or
617 (number[]) the series values (if no parameters) */
618 values: function(name, values) {
619 if (arguments.length == 0) {
620 return this._values;
621 }
622 if (isArray(name)) {
623 values = name;
624 name = null;
625 }
626 this._name = name || this._name;
627 this._values = values;
628 this._graph._drawGraph();
629 return this;
630 },
631
632 /* Set or retrieve the formatting for this series.
633 @param fill (string) how the values are filled when plotted
634 @param stroke (string) the (out)line colour (optional)
635 @param strokeWidth (number) the line's width (optional)
636 @param settings (object) additional formatting settings for the series (optional)
637 @return (SVGGraphSeries) this series object or
638 (object) formatting settings (if no parameters) */
639 format: function(fill, stroke, strokeWidth, settings) {
640 if (arguments.length == 0) {
641 return $.extend({fill: this._fill, stroke: this._stroke,
642 strokeWidth: this._strokeWidth}, this._settings);
643 }
644 if (typeof stroke != 'string') {
645 settings = strokeWidth;
646 strokeWidth = stroke;
647 stroke = null;
648 }
649 if (typeof strokeWidth != 'number') {
650 settings = strokeWidth;
651 strokeWidth = null;
652 }
653 this._fill = fill || this._fill;
654 this._stroke = stroke || this._stroke;
655 this._strokeWidth = strokeWidth || this._strokeWidth;
656 $.extend(this._settings, settings || {});
657 this._graph._drawGraph();
658 return this;
659 },
660
661 /* Return to the parent graph. */
662 end: function() {
663 return this._graph;
664 }
665 });
666
667 /* Details about each graph axis.
668 @param graph (SVGGraph) the owning graph
669 @param title (string) the title of the axis
670 @param min (number) the minimum value displayed on this axis
671 @param max (number) the maximum value displayed on this axis
672 @param major (number) the distance between major ticks
673 @param minor (number) the distance between minor ticks (optional)
674 @return (SVGGraphAxis) the new axis object */
675 function SVGGraphAxis(graph, title, min, max, major, minor) {
676 this._graph = graph; // The owning graph
677 this._title = title || ''; // Title of this axis
678 this._titleFormat = {}; // Formatting settings for the title
679 this._titleOffset = 0; // The offset for positioning the title
680 this._labels = null; // List of labels for this axis - one per possible value across all series
681 this._labelFormat = {}; // Formatting settings for the labels
682 this._lineFormat = {stroke: 'black', strokeWidth: 1}; // Formatting settings for the axis lines
683 this._ticks = {major: major || 10, minor: minor || 0, size: 10, position: 'out'}; // Tick mark options
684 this._scale = {min: min || 0, max: max || 100}; // Axis scale settings
685 this._crossAt = 0; // Where this axis crosses the other one
686 }
687
688 $.extend(SVGGraphAxis.prototype, {
689
690 /* Set or retrieve the scale for this axis.
691 @param min (number) the minimum value shown
692 @param max (number) the maximum value shown
693 @return (SVGGraphAxis) this axis object or
694 (object) min and max values (if no parameters) */
695 scale: function(min, max) {
696 if (arguments.length == 0) {
697 return this._scale;
698 }
699 this._scale.min = min;
700 this._scale.max = max;
701 this._graph._drawGraph();
702 return this;
703 },
704
705 /* Set or retrieve the ticks for this axis.
706 @param major (number) the distance between major ticks
707 @param minor (number) the distance between minor ticks
708 @param size (number) the length of the major ticks (minor are half) (optional)
709 @param position (string) the location of the ticks:
710 'in', 'out', 'both' (optional)
711 @return (SVGGraphAxis) this axis object or
712 (object) major, minor, size, and position values (if no parameters) */
713 ticks: function(major, minor, size, position) {
714 if (arguments.length == 0) {
715 return this._ticks;
716 }
717 if (typeof size == 'string') {
718 position = size;
719 size = null;
720 }
721 this._ticks.major = major;
722 this._ticks.minor = minor;
723 this._ticks.size = size || this._ticks.size;
724 this._ticks.position = position || this._ticks.position;
725 this._graph._drawGraph();
726 return this;
727 },
728
729 /* Set or retrieve the title for this axis.
730 @param title (string) the title text
731 @param offset (number) the distance to offset the title position (optional)
732 @param colour (string) how to colour the title (optional)
733 @param format (object) formatting settings for the title (optional)
734 @return (SVGGraphAxis) this axis object or
735 (object) title, offset, and format values (if no parameters) */
736 title: function(title, offset, colour, format) {
737 if (arguments.length == 0) {
738 return {title: this._title, offset: this._titleOffset, format: this._titleFormat};
739 }
740 if (typeof offset != 'number') {
741 format = colour;
742 colour = offset;
743 offset = null;
744 }
745 if (typeof colour != 'string') {
746 format = colour;
747 colour = null;
748 }
749 this._title = title;
750 this._titleOffset = (offset != null ? offset : this._titleOffset);
751 if (colour || format) {
752 this._titleFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
753 }
754 this._graph._drawGraph();
755 return this;
756 },
757
758 /* Set or retrieve the labels for this axis.
759 @param labels (string[]) the text for each entry
760 @param colour (string) how to colour the labels (optional)
761 @param format (object) formatting settings for the labels (optional)
762 @return (SVGGraphAxis) this axis object or
763 (object) labels and format values (if no parameters) */
764 labels: function(labels, colour, format) {
765 if (arguments.length == 0) {
766 return {labels: this._labels, format: this._labelFormat};
767 }
768 if (typeof colour != 'string') {
769 format = colour;
770 colour = null;
771 }
772 this._labels = labels;
773 if (colour || format) {
774 this._labelFormat = $.extend(format || {}, (colour ? {fill: colour} : {}));
775 }
776 this._graph._drawGraph();
777 return this;
778 },
779
780 /* Set or retrieve the line formatting for this axis.
781 @param colour (string) the line's colour
782 @param width (number) the line's width (optional)
783 @param settings (object) additional formatting settings for the line (optional)
784 @return (SVGGraphAxis) this axis object or
785 (object) line formatting values (if no parameters) */
786 line: function(colour, width, settings) {
787 if (arguments.length == 0) {
788 return this._lineFormat;
789 }
790 if (typeof width == 'object') {
791 settings = width;
792 width = null;
793 }
794 $.extend(this._lineFormat, {stroke: colour},
795 (width ? {strokeWidth: width} : {}), settings || {});
796 this._graph._drawGraph();
797 return this;
798 },
799
800 /* Return to the parent graph. */
801 end: function() {
802 return this._graph;
803 }
804 });
805
806 /* Details about the graph legend.
807 @param graph (SVGGraph) the owning graph
808 @param bgSettings (object) additional formatting settings for the legend background (optional)
809 @param textSettings (object) additional formatting settings for the legend text (optional)
810 @return (SVGGraphLegend) the new legend object */
811 function SVGGraphLegend(graph, bgSettings, textSettings) {
812 this._graph = graph; // The owning graph
813 this._show = true; // Show the legend?
814 this._area = [0.9, 0.1, 1.0, 0.9]; // The legend area: left, top, right, bottom,
815 // > 1 in pixels, <= 1 as proportion
816 this._sampleSize = 15; // Size of sample box
817 this._bgSettings = bgSettings || {stroke: 'gray'}; // Additional formatting settings for the legend background
818 this._textSettings = textSettings || {}; // Additional formatting settings for the text
819 }
820
821 $.extend(SVGGraphLegend.prototype, {
822
823 /* Set or retrieve whether the legend should be shown.
824 @param show (boolean) true to display it, false to hide it
825 @return (SVGGraphLegend) this legend object or
826 (boolean) show the legend? (if no parameters) */
827 show: function(show) {
828 if (arguments.length == 0) {
829 return this._show;
830 }
831 this._show = show;
832 this._graph._drawGraph();
833 return this;
834 },
835
836 /* Set or retrieve the legend area.
837 @param left (number) > 1 is pixels, <= 1 is proportion of width or
838 (number[4]) for left, top, right, bottom
839 @param top (number) > 1 is pixels, <= 1 is proportion of height
840 @param right (number) > 1 is pixels, <= 1 is proportion of width
841 @param bottom (number) > 1 is pixels, <= 1 is proportion of height
842 @return (SVGGraphLegend) this legend object or
843 (number[4]) the legend area: left, top, right, bottom (if no parameters) */
844 area: function(left, top, right, bottom) {
845 if (arguments.length == 0) {
846 return this._area;
847 }
848 this._area = (isArray(left) ? left : [left, top, right, bottom]);
849 this._graph._drawGraph();
850 return this;
851 },
852
853 /* Set or retrieve additional settings for the legend area.
854 @param sampleSize (number) the size of the sample box to display (optional)
855 @param bgSettings (object) additional formatting settings for the legend background
856 @param textSettings (object) additional formatting settings for the legend text (optional)
857 @return (SVGGraphLegend) this legend object or
858 (object) bgSettings and textSettings for the legend (if no parameters) */
859 settings: function(sampleSize, bgSettings, textSettings) {
860 if (arguments.length == 0) {
861 return {sampleSize: this._sampleSize, bgSettings: this._bgSettings,
862 textSettings: this._textSettings};
863 }
864 if (typeof sampleSize != 'number') {
865 textSettings = bgSettings;
866 bgSettings = sampleSize;
867 sampleSize = null;
868 }
869 this._sampleSize = sampleSize || this._sampleSize;
870 this._bgSettings = bgSettings;
871 this._textSettings = textSettings || this._textSettings;
872 this._graph._drawGraph();
873 return this;
874 },
875
876 /* Return to the parent graph. */
877 end: function() {
878 return this._graph;
879 }
880 });
881
882 //==============================================================================
883
884 /* Round a number to a given number of decimal points. */
885 function roundNumber(num, dec) {
886 return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
887 }
888
889 var barOptions = ['barWidth (number) - the width of each bar',
890 'barGap (number) - the gap between sets of bars'];
891
892 //------------------------------------------------------------------------------
893
894 /* Draw a standard grouped column bar chart. */
895 function SVGColumnChart() {
896 }
897
898 $.extend(SVGColumnChart.prototype, {
899
900 /* Retrieve the display title for this chart type.
901 @return the title */
902 title: function() {
903 return 'Basic column chart';
904 },
905
906 /* Retrieve a description of this chart type.
907 @return its description */
908 description: function() {
909 return 'Compare sets of values as vertical bars with grouped categories.';
910 },
911
912 /* Retrieve a list of the options that may be set for this chart type.
913 @return options list */
914 options: function() {
915 return barOptions;
916 },
917
918 /* Actually draw the graph in this type's style.
919 @param graph (object) the SVGGraph object */
920 drawGraph: function(graph) {
921 graph._drawChartBackground(true);
922 var barWidth = graph._chartOptions.barWidth || 10;
923 var barGap = graph._chartOptions.barGap || 10;
924 var numSer = graph._series.length;
925 var numVal = (numSer ? (graph._series[0])._values.length : 0);
926 var dims = graph._getDims();
927 var xScale = dims[graph.W] / ((numSer * barWidth + barGap) * numVal + barGap);
928 var yScale = dims[graph.H] / (graph.yAxis._scale.max - graph.yAxis._scale.min);
929 this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
930 for (var i = 0; i < numSer; i++) {
931 this._drawSeries(graph, i, numSer, barWidth, barGap, dims, xScale, yScale);
932 }
933 graph._drawTitle();
934 graph._drawAxes(true);
935 this._drawXAxis(graph, numSer, numVal, barWidth, barGap, dims, xScale);
936 graph._drawLegend();
937 },
938
939 /* Plot an individual series. */
940 _drawSeries: function(graph, cur, numSer, barWidth, barGap, dims, xScale, yScale) {
941 var series = graph._series[cur];
942 var g = graph._wrapper.group(this._chart,
943 $.extend({class_: 'series' + cur, fill: series._fill, stroke: series._stroke,
944 strokeWidth: series._strokeWidth}, series._settings || {}));
945 for (var i = 0; i < series._values.length; i++) {
946 var r = graph._wrapper.rect(g,
947 dims[graph.X] + xScale * (barGap + i * (numSer * barWidth + barGap) + (cur * barWidth)),
948 dims[graph.Y] + yScale * (graph.yAxis._scale.max - series._values[i]),
949 xScale * barWidth, yScale * series._values[i]);
950 graph._showStatus(r, series._name, series._values[i]);
951 }
952 },
953
954 /* Draw the x-axis and its ticks. */
955 _drawXAxis: function(graph, numSer, numVal, barWidth, barGap, dims, xScale) {
956 var axis = graph.xAxis;
957 if (axis._title) {
958 graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
959 dims[graph.Y] + dims[graph.H] + axis._titleOffset,
960 axis._title, $.extend({textAnchor: 'middle'}, axis._titleFormat || {}));
961 }
962 var gl = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxis'}, axis._lineFormat));
963 var gt = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxisLabels',
964 textAnchor: 'middle'}, axis._labelFormat));
965 graph._wrapper.line(gl, dims[graph.X], dims[graph.Y] + dims[graph.H],
966 dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
967 if (axis._ticks.major) {
968 var offsets = graph._getTickOffsets(axis, true);
969 for (var i = 1; i < numVal; i++) {
970 var x = dims[graph.X] + xScale * (barGap / 2 + i * (numSer * barWidth + barGap));
971 graph._wrapper.line(gl, x, dims[graph.Y] + dims[graph.H] + offsets[0] * axis._ticks.size,
972 x, dims[graph.Y] + dims[graph.H] + offsets[1] * axis._ticks.size);
973 }
974 for (var i = 0; i < numVal; i++) {
975 var x = dims[graph.X] + xScale * (barGap / 2 + (i + 0.5) * (numSer * barWidth + barGap));
976 graph._wrapper.text(gt, x, dims[graph.Y] + dims[graph.H] + 2 * axis._ticks.size,
977 (axis._labels ? axis._labels[i] : '' + i));
978 }
979 }
980 }
981 });
982
983 //------------------------------------------------------------------------------
984
985 /* Draw a stacked column bar chart. */
986 function SVGStackedColumnChart() {
987 }
988
989 $.extend(SVGStackedColumnChart.prototype, {
990
991 /* Retrieve the display title for this chart type.
992 @return the title */
993 title: function() {
994 return 'Stacked column chart';
995 },
996
997 /* Retrieve a description of this chart type.
998 @return its description */
999 description: function() {
1000 return 'Compare sets of values as vertical bars showing ' +
1001 'relative contributions to the whole for each category.';
1002 },
1003
1004 /* Retrieve a list of the options that may be set for this chart type.
1005 @return options list */
1006 options: function() {
1007 return barOptions;
1008 },
1009
1010 /* Actually draw the graph in this type's style.
1011 @param graph (object) the SVGGraph object */
1012 drawGraph: function(graph) {
1013 var bg = graph._drawChartBackground(true, true);
1014 var dims = graph._getDims();
1015 if (graph._gridlines[0] && graph.xAxis._ticks.major) {
1016 graph._drawGridlines(bg, graph._getPercentageAxis(), true, dims, graph._gridlines[0]);
1017 }
1018 var barWidth = graph._chartOptions.barWidth || 10;
1019 var barGap = graph._chartOptions.barGap || 10;
1020 var numSer = graph._series.length;
1021 var numVal = (numSer ? (graph._series[0])._values.length : 0);
1022 var xScale = dims[graph.W] / ((barWidth + barGap) * numVal + barGap);
1023 var yScale = dims[graph.H];
1024 this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
1025 this._drawColumns(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale);
1026 graph._drawTitle();
1027 graph._wrapper.text(graph._chartCont, 0, 0, $.svg.graphing.region.percentageText,
1028 $.extend({textAnchor: 'middle', transform: 'translate(' +
1029 (dims[graph.X] - graph.yAxis._titleOffset) + ',' +
1030 (dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}, graph.yAxis._titleFormat || {}));
1031 var pAxis = $.extend({}, graph._getPercentageAxis());
1032 $.extend(pAxis._labelFormat, graph.yAxis._labelFormat || {});
1033 graph._drawAxis(pAxis, 'yAxis', dims[graph.X], dims[graph.Y],
1034 dims[graph.X], dims[graph.Y] + dims[graph.H]);
1035 this._drawXAxis(graph, numVal, barWidth, barGap, dims, xScale);
1036 graph._drawLegend();
1037 },
1038
1039 /* Plot all of the columns. */
1040 _drawColumns: function(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale) {
1041 var totals = graph._getTotals();
1042 var accum = [];
1043 for (var i = 0; i < numVal; i++) {
1044 accum[i] = 0;
1045 }
1046 for (var s = 0; s < numSer; s++) {
1047 var series = graph._series[s];
1048 var g = graph._wrapper.group(this._chart,
1049 $.extend({class_: 'series' + s, fill: series._fill,
1050 stroke: series._stroke, strokeWidth: series._strokeWidth},
1051 series._settings || {}));
1052 for (var i = 0; i < series._values.length; i++) {
1053 accum[i] += series._values[i];
1054 var r = graph._wrapper.rect(g,
1055 dims[graph.X] + xScale * (barGap + i * (barWidth + barGap)),
1056 dims[graph.Y] + yScale * (totals[i] - accum[i]) / totals[i],
1057 xScale * barWidth, yScale * series._values[i] / totals[i]);
1058 graph._showStatus(r, series._name,
1059 roundNumber(series._values[i] / totals[i] * 100, 2));
1060 }
1061 }
1062 },
1063
1064 /* Draw the x-axis and its ticks. */
1065 _drawXAxis: function(graph, numVal, barWidth, barGap, dims, xScale) {
1066 var axis = graph.xAxis;
1067 if (axis._title) {
1068 graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
1069 dims[graph.Y] + dims[graph.H] + axis._titleOffset,
1070 axis._title, $.extend({textAnchor: 'middle'}, axis._titleFormat || {}));
1071 }
1072 var gl = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxis'}, axis._lineFormat));
1073 var gt = graph._wrapper.group(graph._chartCont, $.extend({class_: 'xAxisLabels',
1074 textAnchor: 'middle'}, axis._labelFormat));
1075 graph._wrapper.line(gl, dims[graph.X], dims[graph.Y] + dims[graph.H],
1076 dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
1077 if (axis._ticks.major) {
1078 var offsets = graph._getTickOffsets(axis, true);
1079 for (var i = 1; i < numVal; i++) {
1080 var x = dims[graph.X] + xScale * (barGap / 2 + i * (barWidth + barGap));
1081 graph._wrapper.line(gl, x, dims[graph.Y] + dims[graph.H] + offsets[0] * axis._ticks.size,
1082 x, dims[graph.Y] + dims[graph.H] + offsets[1] * axis._ticks.size);
1083 }
1084 for (var i = 0; i < numVal; i++) {
1085 var x = dims[graph.X] + xScale * (barGap / 2 + (i + 0.5) * (barWidth + barGap));
1086 graph._wrapper.text(gt, x, dims[graph.Y] + dims[graph.H] + 2 * axis._ticks.size,
1087 (axis._labels ? axis._labels[i] : '' + i));
1088 }
1089 }
1090 }
1091 });
1092
1093 //------------------------------------------------------------------------------
1094
1095 /* Draw a standard grouped row bar chart. */
1096 function SVGRowChart() {
1097 }
1098
1099 $.extend(SVGRowChart.prototype, {
1100
1101 /* Retrieve the display title for this chart type.
1102 @return the title */
1103 title: function() {
1104 return 'Basic row chart';
1105 },
1106
1107 /* Retrieve a description of this chart type.
1108 @return its description */
1109 description: function() {
1110 return 'Compare sets of values as horizontal rows with grouped categories.';
1111 },
1112
1113 /* Retrieve a list of the options that may be set for this chart type.
1114 @return options list */
1115 options: function() {
1116 return barOptions;
1117 },
1118
1119 /* Actually draw the graph in this type's style.
1120 @param graph (object) the SVGGraph object */
1121 drawGraph: function(graph) {
1122 var bg = graph._drawChartBackground(true, true);
1123 var dims = graph._getDims();
1124 graph._drawGridlines(bg, graph.yAxis, false, dims, graph._gridlines[0]);
1125 var barWidth = graph._chartOptions.barWidth || 10;
1126 var barGap = graph._chartOptions.barGap || 10;
1127 var numSer = graph._series.length;
1128 var numVal = (numSer ? (graph._series[0])._values.length : 0);
1129 var xScale = dims[graph.W] / (graph.yAxis._scale.max - graph.yAxis._scale.min);
1130 var yScale = dims[graph.H] / ((numSer * barWidth + barGap) * numVal + barGap);
1131 this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
1132 for (var i = 0; i < numSer; i++) {
1133 this._drawSeries(graph, i, numSer, barWidth, barGap, dims, xScale, yScale);
1134 }
1135 graph._drawTitle();
1136 this._drawAxes(graph, numSer, numVal, barWidth, barGap, dims, yScale);
1137 graph._drawLegend();
1138 },
1139
1140 /* Plot an individual series. */
1141 _drawSeries: function(graph, cur, numSer, barWidth, barGap, dims, xScale, yScale) {
1142 var series = graph._series[cur];
1143 var g = graph._wrapper.group(this._chart,
1144 $.extend({class_: 'series' + cur, fill: series._fill,
1145 stroke: series._stroke, strokeWidth: series._strokeWidth},
1146 series._settings || {}));
1147 for (var i = 0; i < series._values.length; i++) {
1148 var r = graph._wrapper.rect(g,
1149 dims[graph.X] + xScale * (0 - graph.yAxis._scale.min),
1150 dims[graph.Y] + yScale * (barGap + i * (numSer * barWidth + barGap) + (cur * barWidth)),
1151 xScale * series._values[i], yScale * barWidth);
1152 graph._showStatus(r, series._name, series._values[i]);
1153 }
1154 },
1155
1156 /* Draw the axes for this graph. */
1157 _drawAxes: function(graph, numSer, numVal, barWidth, barGap, dims, yScale) {
1158 // X-axis
1159 var axis = graph.yAxis;
1160 if (axis) {
1161 if (axis._title) {
1162 graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
1163 dims[graph.Y] + dims[graph.H] + axis._titleOffset, axis._title, axis._titleFormat);
1164 }
1165 graph._drawAxis(axis, 'xAxis', dims[graph.X], dims[graph.Y] + dims[graph.H],
1166 dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
1167 }
1168 // Y-axis
1169 var axis = graph.xAxis;
1170 if (axis._title) {
1171 graph._wrapper.text(graph._chartCont, 0, 0, axis._title, $.extend({textAnchor: 'middle',
1172 transform: 'translate(' + (dims[graph.X] - axis._titleOffset) + ',' +
1173 (dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}, axis._titleFormat || {}));
1174 }
1175 var gl = graph._wrapper.group(graph._chartCont, $.extend({class_: 'yAxis'}, axis._lineFormat));
1176 var gt = graph._wrapper.group(graph._chartCont, $.extend(
1177 {class_: 'yAxisLabels', textAnchor: 'end'}, axis._labelFormat));
1178 graph._wrapper.line(gl, dims[graph.X], dims[graph.Y], dims[graph.X], dims[graph.Y] + dims[graph.H]);
1179 if (axis._ticks.major) {
1180 var offsets = graph._getTickOffsets(axis, false);
1181 for (var i = 1; i < numVal; i++) {
1182 var y = dims[graph.Y] + yScale * (barGap / 2 + i * (numSer * barWidth + barGap));
1183 graph._wrapper.line(gl, dims[graph.X] + offsets[0] * axis._ticks.size, y,
1184 dims[graph.X] + offsets[1] * axis._ticks.size, y);
1185 }
1186 for (var i = 0; i < numVal; i++) {
1187 var y = dims[graph.Y] + yScale * (barGap / 2 + (i + 0.5) * (numSer * barWidth + barGap));
1188 graph._wrapper.text(gt, dims[graph.X] - axis._ticks.size, y,
1189 (axis._labels ? axis._labels[i] : '' + i));
1190 }
1191 }
1192 }
1193 });
1194
1195 //------------------------------------------------------------------------------
1196
1197 /* Draw a stacked row bar chart. */
1198 function SVGStackedRowChart() {
1199 }
1200
1201 $.extend(SVGStackedRowChart.prototype, {
1202
1203 /* Retrieve the display title for this chart type.
1204 @return the title */
1205 title: function() {
1206 return 'Stacked row chart';
1207 },
1208
1209 /* Retrieve a description of this chart type.
1210 @return its description */
1211 description: function() {
1212 return 'Compare sets of values as horizontal bars showing ' +
1213 'relative contributions to the whole for each category.';
1214 },
1215
1216 /* Retrieve a list of the options that may be set for this chart type.
1217 @return options list */
1218 options: function() {
1219 return barOptions;
1220 },
1221
1222 /* Actually draw the graph in this type's style.
1223 @param graph (object) the SVGGraph object */
1224 drawGraph: function(graph) {
1225 var bg = graph._drawChartBackground(true, true);
1226 var dims = graph._getDims();
1227 if (graph._gridlines[0] && graph.xAxis._ticks.major) {
1228 graph._drawGridlines(bg, graph._getPercentageAxis(), false, dims, graph._gridlines[0]);
1229 }
1230 var barWidth = graph._chartOptions.barWidth || 10;
1231 var barGap = graph._chartOptions.barGap || 10;
1232 var numSer = graph._series.length;
1233 var numVal = (numSer ? (graph._series[0])._values.length : 0);
1234 var xScale = dims[graph.W];
1235 var yScale = dims[graph.H] / ((barWidth + barGap) * numVal + barGap);
1236 this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
1237 this._drawRows(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale);
1238 graph._drawTitle();
1239 graph._wrapper.text(graph._chartCont, dims[graph.X] + dims[graph.W] / 2,
1240 dims[graph.Y] + dims[graph.H] + graph.xAxis._titleOffset,
1241 $.svg.graphing.region.percentageText,
1242 $.extend({textAnchor: 'middle'}, graph.yAxis._titleFormat || {}));
1243 var pAxis = $.extend({}, graph._getPercentageAxis());
1244 $.extend(pAxis._labelFormat, graph.yAxis._labelFormat || {});
1245 graph._drawAxis(pAxis, 'xAxis', dims[graph.X], dims[graph.Y] + dims[graph.H],
1246 dims[graph.X] + dims[graph.W], dims[graph.Y] + dims[graph.H]);
1247 this._drawYAxis(graph, numVal, barWidth, barGap, dims, yScale);
1248 graph._drawLegend();
1249 },
1250
1251 /* Plot all of the rows. */
1252 _drawRows: function(graph, numSer, numVal, barWidth, barGap, dims, xScale, yScale) {
1253 var totals = graph._getTotals();
1254 var accum = [];
1255 for (var i = 0; i < numVal; i++) {
1256 accum[i] = 0;
1257 }
1258 for (var s = 0; s < numSer; s++) {
1259 var series = graph._series[s];
1260 var g = graph._wrapper.group(this._chart,
1261 $.extend({class_: 'series' + s, fill: series._fill,
1262 stroke: series._stroke, strokeWidth: series._strokeWidth},
1263 series._settings || {}));
1264 for (var i = 0; i < series._values.length; i++) {
1265 var r = graph._wrapper.rect(g,
1266 dims[graph.X] + xScale * accum[i] / totals[i],
1267 dims[graph.Y] + yScale * (barGap + i * (barWidth + barGap)),
1268 xScale * series._values[i] / totals[i], yScale * barWidth);
1269 graph._showStatus(r, series._name,
1270 roundNumber(series._values[i] / totals[i] * 100, 2));
1271 accum[i] += series._values[i];
1272 }
1273 }
1274 },
1275
1276 /* Draw the y-axis and its ticks. */
1277 _drawYAxis: function(graph, numVal, barWidth, barGap, dims, yScale) {
1278 var axis = graph.xAxis;
1279 if (axis._title) {
1280 graph._wrapper.text(graph._chartCont, 0, 0, axis._title, $.extend({textAnchor: 'middle',
1281 transform: 'translate(' + (dims[graph.X] - axis._titleOffset) + ',' +
1282 (dims[graph.Y] + dims[graph.H] / 2) + ') rotate(-90)'}, axis._titleFormat || {}));
1283 }
1284 var gl = graph._wrapper.group(graph._chartCont,
1285 $.extend({class_: 'yAxis'}, axis._lineFormat));
1286 var gt = graph._wrapper.group(graph._chartCont,
1287 $.extend({class_: 'yAxisLabels', textAnchor: 'end'}, axis._labelFormat));
1288 graph._wrapper.line(gl, dims[graph.X], dims[graph.Y],
1289 dims[graph.X], dims[graph.Y] + dims[graph.H]);
1290 if (axis._ticks.major) {
1291 var offsets = graph._getTickOffsets(axis, false);
1292 for (var i = 1; i < numVal; i++) {
1293 var y = dims[graph.Y] + yScale * (barGap / 2 + i * (barWidth + barGap));
1294 graph._wrapper.line(gl, dims[graph.X] + offsets[0] * axis._ticks.size, y,
1295 dims[graph.X] + offsets[1] * axis._ticks.size, y);
1296 }
1297 for (var i = 0; i < numVal; i++) {
1298 var y = dims[graph.Y] + yScale * (barGap / 2 + (i + 0.5) * (barWidth + barGap));
1299 graph._wrapper.text(gt, dims[graph.X] - axis._ticks.size, y,
1300 (axis._labels ? axis._labels[i] : '' + i));
1301 }
1302 }
1303 }
1304 });
1305
1306 //------------------------------------------------------------------------------
1307
1308 /* Draw a standard line chart. */
1309 function SVGLineChart() {
1310 }
1311
1312 $.extend(SVGLineChart.prototype, {
1313
1314 /* Retrieve the display title for this chart type.
1315 @return the title */
1316 title: function() {
1317 return 'Basic line chart';
1318 },
1319
1320 /* Retrieve a description of this chart type.
1321 @return its description */
1322 description: function() {
1323 return 'Compare sets of values as continuous lines.';
1324 },
1325
1326 /* Retrieve a list of the options that may be set for this chart type.
1327 @return options list */
1328 options: function() {
1329 return [];
1330 },
1331
1332 /* Actually draw the graph in this type's style.
1333 @param graph (object) the SVGGraph object */
1334 drawGraph: function(graph) {
1335 graph._drawChartBackground();
1336 var dims = graph._getDims();
1337 var xScale = dims[graph.W] / (graph.xAxis._scale.max - graph.xAxis._scale.min);
1338 var yScale = dims[graph.H] / (graph.yAxis._scale.max - graph.yAxis._scale.min);
1339 this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
1340 for (var i = 0; i < graph._series.length; i++) {
1341 this._drawSeries(graph, i, dims, xScale, yScale);
1342 }
1343 graph._drawTitle();
1344 graph._drawAxes();
1345 graph._drawLegend();
1346 },
1347
1348 /* Plot an individual series. */
1349 _drawSeries: function(graph, cur, dims, xScale, yScale) {
1350 var series = graph._series[cur];
1351 var path = graph._wrapper.createPath();
1352 for (var i = 0; i < series._values.length; i++) {
1353 var x = dims[graph.X] + i * xScale;
1354 var y = dims[graph.Y] + (graph.yAxis._scale.max - series._values[i]) * yScale;
1355 if (i == 0) {
1356 path.move(x, y);
1357 }
1358 else {
1359 path.line(x, y);
1360 }
1361 }
1362 var p = graph._wrapper.path(this._chart, path,
1363 $.extend({id: 'series' + cur, fill: 'none', stroke: series._stroke,
1364 strokeWidth: series._strokeWidth}, series._settings || {}));
1365 graph._showStatus(p, series._name, 0);
1366 }
1367 });
1368
1369 //------------------------------------------------------------------------------
1370
1371 /* Draw a standard pie chart. */
1372 function SVGPieChart() {
1373 }
1374
1375 $.extend(SVGPieChart.prototype, {
1376
1377 _options: ['explode (number or number[]) - indexes of sections to explode out of the pie',
1378 'explodeDist (number) - the distance to move an exploded section',
1379 'pieGap (number) - the distance between pies for multiple values'],
1380
1381 /* Retrieve the display title for this chart type.
1382 @return the title */
1383 title: function() {
1384 return 'Pie chart';
1385 },
1386
1387 /* Retrieve a description of this chart type.
1388 @return its description */
1389 description: function() {
1390 return 'Compare relative sizes of values as contributions to the whole.';
1391 },
1392
1393 /* Retrieve a list of the options that may be set for this chart type.
1394 @return options list */
1395 options: function() {
1396 return this._options;
1397 },
1398
1399 /* Actually draw the graph in this type's style.
1400 @param graph (object) the SVGGraph object */
1401 drawGraph: function(graph) {
1402 graph._drawChartBackground(true, true);
1403 this._chart = graph._wrapper.group(graph._chartCont, {class_: 'chart'});
1404 var dims = graph._getDims();
1405 this._drawSeries(graph, dims);
1406 graph._drawTitle();
1407 graph._drawLegend();
1408 },
1409
1410 /* Plot all the series. */
1411 _drawSeries: function(graph, dims) {
1412 var totals = graph._getTotals();
1413 var numSer = graph._series.length;
1414 var numVal = (numSer ? (graph._series[0])._values.length : 0);
1415 var path = graph._wrapper.createPath();
1416 var explode = graph._chartOptions.explode || [];
1417 explode = (isArray(explode) ? explode : [explode]);
1418 var explodeDist = graph._chartOptions.explodeDist || 10;
1419 var pieGap = (numVal <= 1 ? 0 : graph._chartOptions.pieGap || 10);
1420 var xBase = (dims[graph.W] - (numVal * pieGap) - pieGap) / numVal / 2;
1421 var yBase = dims[graph.H] / 2;
1422 var radius = Math.min(xBase, yBase) - (explode.length > 0 ? explodeDist : 0);
1423 var gt = graph._wrapper.group(graph._chartCont, $.extend(
1424 {class_: 'xAxisLabels', textAnchor: 'middle'}, graph.xAxis._labelFormat));
1425 var gl = [];
1426 for (var i = 0; i < numVal; i++) {
1427 var cx = dims[graph.X] + xBase + (i * (2 * Math.min(xBase, yBase) + pieGap)) + pieGap;
1428 var cy = dims[graph.Y] + yBase;
1429 var curTotal = 0;
1430 for (var j = 0; j < numSer; j++) {
1431 var series = graph._series[j];
1432 if (i == 0) {
1433 gl[j] = graph._wrapper.group(this._chart, $.extend({class_: 'series' + j,
1434 fill: series._fill, stroke: series._stroke,
1435 strokeWidth: series._strokeWidth}, series._settings || {}));
1436 }
1437 if (series._values[i] == 0) {
1438 continue;
1439 }
1440 var start = (curTotal / totals[i]) * 2 * Math.PI;
1441 curTotal += series._values[i];
1442 var end = (curTotal / totals[i]) * 2 * Math.PI;
1443 var exploding = false;
1444 for (var k = 0; k < explode.length; k++) {
1445 if (explode[k] == j) {
1446 exploding = true;
1447 break;
1448 }
1449 }
1450 var x = cx + (exploding ? explodeDist * Math.cos((start + end) / 2) : 0);
1451 var y = cy + (exploding ? explodeDist * Math.sin((start + end) / 2) : 0);
1452 var p = graph._wrapper.path(gl[j], path.reset().move(x, y).
1453 line(x + radius * Math.cos(start), y + radius * Math.sin(start)).
1454 arc(radius, radius, 0, (end - start < Math.PI ? 0 : 1), 1,
1455 x + radius * Math.cos(end), y + radius * Math.sin(end)).close());
1456 graph._showStatus(p, series._name,
1457 roundNumber((end - start) / 2 / Math.PI * 100, 2));
1458 }
1459 if (graph.xAxis) {
1460 graph._wrapper.text(gt, cx, dims[graph.Y] + dims[graph.H] + graph.xAxis._titleOffset,
1461 graph.xAxis._labels[i])
1462 }
1463 }
1464 }
1465 });
1466
1467 //------------------------------------------------------------------------------
1468
1469 /* Determine whether an object is an array. */
1470 function isArray(a) {
1471 return (a && a.constructor == Array);
1472 }
1473
1474 // Basic chart types
1475 $.svg.graphing.addChartType('column', new SVGColumnChart());
1476 $.svg.graphing.addChartType('stackedColumn', new SVGStackedColumnChart());
1477 $.svg.graphing.addChartType('row', new SVGRowChart());
1478 $.svg.graphing.addChartType('stackedRow', new SVGStackedRowChart());
1479 $.svg.graphing.addChartType('line', new SVGLineChart());
1480 $.svg.graphing.addChartType('pie', new SVGPieChart());
1481
1482 })(jQuery)