jquerysvg 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1482 @@
1
+ /* http://keith-wood.name/svg.html
2
+ SVG graphing extension for jQuery v1.4.5.
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)