rgraph-rails 1.0.1

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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +4 -0
  7. data/README.md +73 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/lib/rgraph-rails/version.rb +3 -0
  12. data/lib/rgraph-rails.rb +8 -0
  13. data/license.txt +19 -0
  14. data/rgraph-rails.gemspec +26 -0
  15. data/vendor/assets/images/bg.png +0 -0
  16. data/vendor/assets/images/bullet.png +0 -0
  17. data/vendor/assets/images/facebook-large.png +0 -0
  18. data/vendor/assets/images/google-plus-large.png +0 -0
  19. data/vendor/assets/images/logo.png +0 -0
  20. data/vendor/assets/images/meter-image-sd-needle.png +0 -0
  21. data/vendor/assets/images/meter-image-sd.png +0 -0
  22. data/vendor/assets/images/meter-sketch-needle.png +0 -0
  23. data/vendor/assets/images/meter-sketch.png +0 -0
  24. data/vendor/assets/images/odometer-background.png +0 -0
  25. data/vendor/assets/images/rgraph.jpg +0 -0
  26. data/vendor/assets/images/title.png +0 -0
  27. data/vendor/assets/images/twitter-large.png +0 -0
  28. data/vendor/assets/javascripts/RGraph.bar.js +3246 -0
  29. data/vendor/assets/javascripts/RGraph.bipolar.js +2003 -0
  30. data/vendor/assets/javascripts/RGraph.common.annotate.js +399 -0
  31. data/vendor/assets/javascripts/RGraph.common.context.js +600 -0
  32. data/vendor/assets/javascripts/RGraph.common.core.js +4751 -0
  33. data/vendor/assets/javascripts/RGraph.common.csv.js +275 -0
  34. data/vendor/assets/javascripts/RGraph.common.deprecated.js +454 -0
  35. data/vendor/assets/javascripts/RGraph.common.dynamic.js +1194 -0
  36. data/vendor/assets/javascripts/RGraph.common.effects.js +1524 -0
  37. data/vendor/assets/javascripts/RGraph.common.key.js +735 -0
  38. data/vendor/assets/javascripts/RGraph.common.resizing.js +550 -0
  39. data/vendor/assets/javascripts/RGraph.common.tooltips.js +605 -0
  40. data/vendor/assets/javascripts/RGraph.common.zoom.js +223 -0
  41. data/vendor/assets/javascripts/RGraph.drawing.background.js +636 -0
  42. data/vendor/assets/javascripts/RGraph.drawing.circle.js +579 -0
  43. data/vendor/assets/javascripts/RGraph.drawing.image.js +810 -0
  44. data/vendor/assets/javascripts/RGraph.drawing.marker1.js +710 -0
  45. data/vendor/assets/javascripts/RGraph.drawing.marker2.js +672 -0
  46. data/vendor/assets/javascripts/RGraph.drawing.marker3.js +568 -0
  47. data/vendor/assets/javascripts/RGraph.drawing.poly.js +623 -0
  48. data/vendor/assets/javascripts/RGraph.drawing.rect.js +603 -0
  49. data/vendor/assets/javascripts/RGraph.drawing.text.js +648 -0
  50. data/vendor/assets/javascripts/RGraph.drawing.xaxis.js +815 -0
  51. data/vendor/assets/javascripts/RGraph.drawing.yaxis.js +860 -0
  52. data/vendor/assets/javascripts/RGraph.fuel.js +965 -0
  53. data/vendor/assets/javascripts/RGraph.funnel.js +988 -0
  54. data/vendor/assets/javascripts/RGraph.gantt.js +1242 -0
  55. data/vendor/assets/javascripts/RGraph.gauge.js +1391 -0
  56. data/vendor/assets/javascripts/RGraph.hbar.js +1794 -0
  57. data/vendor/assets/javascripts/RGraph.hprogress.js +1307 -0
  58. data/vendor/assets/javascripts/RGraph.line.js +3940 -0
  59. data/vendor/assets/javascripts/RGraph.meter.js +1242 -0
  60. data/vendor/assets/javascripts/RGraph.modaldialog.js +292 -0
  61. data/vendor/assets/javascripts/RGraph.odo.js +1265 -0
  62. data/vendor/assets/javascripts/RGraph.pie.js +1979 -0
  63. data/vendor/assets/javascripts/RGraph.radar.js +1840 -0
  64. data/vendor/assets/javascripts/RGraph.rose.js +1860 -0
  65. data/vendor/assets/javascripts/RGraph.rscatter.js +1332 -0
  66. data/vendor/assets/javascripts/RGraph.scatter.js +3029 -0
  67. data/vendor/assets/javascripts/RGraph.thermometer.js +1131 -0
  68. data/vendor/assets/javascripts/RGraph.vprogress.js +1326 -0
  69. data/vendor/assets/javascripts/RGraph.waterfall.js +1252 -0
  70. data/vendor/assets/javascripts/financial-data.js +1067 -0
  71. data/vendor/assets/stylesheets/ModalDialog.css +90 -0
  72. data/vendor/assets/stylesheets/animations.css +3347 -0
  73. data/vendor/assets/stylesheets/website.css +402 -0
  74. metadata +175 -0
@@ -0,0 +1,1840 @@
1
+ // version: 2015-11-02
2
+ /**
3
+ * o--------------------------------------------------------------------------------o
4
+ * | This file is part of the RGraph package - you can learn more at: |
5
+ * | |
6
+ * | http://www.rgraph.net |
7
+ * | |
8
+ * | RGraph is dual licensed under the Open Source GPL (General Public License) |
9
+ * | v2.0 license and a commercial license which means that you're not bound by |
10
+ * | the terms of the GPL. The commercial license is just �99 (GBP) and you can |
11
+ * | read about it here: |
12
+ * | http://www.rgraph.net/license |
13
+ * o--------------------------------------------------------------------------------o
14
+ */
15
+
16
+ RGraph = window.RGraph || {isRGraph: true};
17
+
18
+ /**
19
+ * The traditional radar chart constructor
20
+ *
21
+ * @param string id The ID of the canvas
22
+ * @param array data An array of data to represent
23
+ */
24
+ RGraph.Radar = function (conf)
25
+ {
26
+ /**
27
+ * Allow for object config style
28
+ */
29
+ if ( typeof conf === 'object'
30
+ && typeof conf.data === 'object'
31
+ && typeof conf.id === 'string') {
32
+
33
+ var parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor)
34
+
35
+ // Turn conf.data into a multi-d array if it's not already
36
+ if (typeof conf.data[0] === 'number') {
37
+ conf.data = [conf.data];
38
+ }
39
+
40
+ } else {
41
+
42
+ var conf = {id: conf, data: []};
43
+
44
+ // Arguments style: var foo = new RGraph.Radar('cvs', [1,2,3], [1,2,3], [1,2,3]);
45
+ if (typeof arguments[1] === 'object' && typeof arguments[1][0] === 'number') {
46
+ for (var i=1; i<arguments.length; ++i) {
47
+ conf.data.push(RGraph.arrayClone(arguments[i]));
48
+ }
49
+
50
+ // Arguments style: var foo = new RGraph.Radar('cvs', [[1,2,3], [1,2,3], [1,2,3]]);
51
+ } else if ( typeof arguments[1] === 'object'
52
+ && typeof arguments[1][0] === 'object'
53
+ && typeof arguments[1][0][0] === 'number') {
54
+
55
+ conf.data = RGraph.arrayClone(arguments[1]);
56
+ }
57
+ }
58
+
59
+
60
+
61
+
62
+ this.id = conf.id;
63
+ this.canvas = document.getElementById(conf.id);
64
+ this.context = this.canvas.getContext ? this.canvas.getContext("2d") : null;
65
+ this.canvas.__object__ = this;
66
+ this.type = 'radar';
67
+ this.isRGraph = true;
68
+ this.data = [];
69
+ this.max = 0;
70
+ this.uid = RGraph.CreateUID();
71
+ this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
72
+ this.colorsParsed = false;
73
+ this.coords = [];
74
+ this.coordsText = [];
75
+ this.original_data = [];
76
+ this.original_colors = [];
77
+ this.firstDraw = true; // After the first draw this will be false
78
+
79
+ /**
80
+ * Add the data to the .original_data array and work out the max value
81
+ */
82
+ for (var i=0,len=conf.data.length; i<len; ++i) {
83
+ this.original_data.push(RGraph.arrayClone(conf.data[i]));
84
+ this.data.push(RGraph.arrayClone(conf.data[i]));
85
+ this.max = Math.max(this.max, RGraph.arrayMax(conf.data[i]));
86
+ }
87
+
88
+
89
+ this.properties =
90
+ {
91
+ 'chart.strokestyle': '#aaa',
92
+ 'chart.gutter.left': 25,
93
+ 'chart.gutter.right': 25,
94
+ 'chart.gutter.top': 25,
95
+ 'chart.gutter.bottom': 25,
96
+ 'chart.linewidth': 1,
97
+ 'chart.colors': ['rgba(255,255,0,0.25)','rgba(0,255,255,0.25)','rgba(255,0,0,0.5)', 'red', 'green', 'blue', 'pink', 'aqua','brown','orange','grey'],
98
+ 'chart.colors.alpha': null,
99
+ 'chart.circle': 0,
100
+ 'chart.circle.fill': 'red',
101
+ 'chart.circle.stroke': 'black',
102
+
103
+ 'chart.labels': [],
104
+ 'chart.labels.color': null,
105
+ 'chart.labels.offset': 10,
106
+ 'chart.labels.axes': '',
107
+ 'chart.labels.background.fill': 'white',
108
+ 'chart.labels.boxed': false,
109
+ 'chart.labels.axes.bold': [],
110
+ 'chart.labels.axes.boxed': null, // This defaults to true - but that's set in the Draw() method
111
+ 'chart.labels.axes.boxed.zero': true,
112
+ 'chart.labels.specific': [],
113
+
114
+ 'chart.labels.count': 5,
115
+ 'chart.background.circles': true,
116
+ 'chart.background.circles.count': null,
117
+ 'chart.background.circles.color': '#ddd',
118
+ 'chart.background.circles.poly': true,
119
+ 'chart.background.circles.spokes': 24,
120
+ 'chart.text.size': 12,
121
+ 'chart.text.size.scale': null,
122
+ 'chart.text.font': 'Arial',
123
+ 'chart.text.color': 'black',
124
+ 'chart.title': '',
125
+ 'chart.title.background': null,
126
+ 'chart.title.hpos': null,
127
+ 'chart.title.vpos': null,
128
+ 'chart.title.color': 'black',
129
+ 'chart.title.bold': true,
130
+ 'chart.title.font': null,
131
+ 'chart.title.x': null,
132
+ 'chart.title.y': null,
133
+ 'chart.title.halign': null,
134
+ 'chart.title.valign': null,
135
+ 'chart.linewidth': 1,
136
+ 'chart.key': null,
137
+ 'chart.key.background': 'white',
138
+ 'chart.key.shadow': false,
139
+ 'chart.key.shadow.color': '#666',
140
+ 'chart.key.shadow.blur': 3,
141
+ 'chart.key.shadow.offsetx': 2,
142
+ 'chart.key.shadow.offsety': 2,
143
+ 'chart.key.position': 'graph',
144
+ 'chart.key.halign': 'right',
145
+ 'chart.key.position.gutter.boxed': false,
146
+ 'chart.key.position.x': null,
147
+ 'chart.key.position.y': null,
148
+ 'chart.key.color.shape': 'square',
149
+ 'chart.key.rounded': true,
150
+ 'chart.key.linewidth': 1,
151
+ 'chart.key.colors': null,
152
+ 'chart.key.interactive': false,
153
+ 'chart.key.interactive.highlight.chart.stroke': 'rgba(255,0,0,0.3)',
154
+ 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
155
+ 'chart.key.text.color': 'black',
156
+ 'chart.contextmenu': null,
157
+ 'chart.annotatable': false,
158
+ 'chart.annotate.color': 'black',
159
+ 'chart.zoom.factor': 1.5,
160
+ 'chart.zoom.fade.in': true,
161
+ 'chart.zoom.fade.out': true,
162
+ 'chart.zoom.hdir': 'right',
163
+ 'chart.zoom.vdir': 'down',
164
+ 'chart.zoom.frames': 25,
165
+ 'chart.zoom.delay': 16.666,
166
+ 'chart.zoom.shadow': true,
167
+ 'chart.zoom.background': true,
168
+ 'chart.zoom.action': 'zoom',
169
+ 'chart.tooltips.effect': 'fade',
170
+ 'chart.tooltips.event': 'onmousemove',
171
+ 'chart.tooltips.css.class': 'RGraph_tooltip',
172
+ 'chart.tooltips.highlight': true,
173
+ 'chart.highlight.stroke': 'gray',
174
+ 'chart.highlight.fill': 'rgba(255,255,255,0.7)',
175
+ 'chart.highlight.point.radius': 2,
176
+ 'chart.resizable': false,
177
+ 'chart.resize.handle.adjust': [0,0],
178
+ 'chart.resize.handle.background': null,
179
+ 'chart.ymax': null,
180
+ 'chart.accumulative': false,
181
+ 'chart.radius': null,
182
+ 'chart.events.click': null,
183
+ 'chart.events.mousemove': null,
184
+ 'chart.scale.decimals': 0,
185
+ 'chart.scale.point': '.',
186
+ 'chart.scale.thousand': ',',
187
+ 'chart.units.pre': '',
188
+ 'chart.units.post': '',
189
+ 'chart.tooltips': null,
190
+ 'chart.tooltips.event': 'onmousemove',
191
+ 'chart.centerx': null,
192
+ 'chart.centery': null,
193
+ 'chart.radius': null,
194
+ 'chart.numxticks': 5,
195
+ 'chart.numyticks': 5,
196
+ 'chart.axes.color': 'rgba(0,0,0,0)',
197
+ 'chart.highlights': false,
198
+ 'chart.highlights.stroke': '#ddd',
199
+ 'chart.highlights.fill': null,
200
+ 'chart.highlights.radius': 3,
201
+ 'chart.fill.click': null,
202
+ 'chart.fill.mousemove': null,
203
+ 'chart.fill.tooltips': null,
204
+ 'chart.fill.highlight.fill': 'rgba(255,255,255,0.7)',
205
+ 'chart.fill.highlight.stroke': 'rgba(0,0,0,0)',
206
+ 'chart.fill.mousemove.redraw': false,
207
+ 'chart.animation.trace.clip': 1
208
+ }
209
+
210
+
211
+
212
+ // Must have at least 3 points
213
+ for (var dataset=0; dataset<this.data.length; ++dataset) {
214
+ if (this.data[dataset].length < 3) {
215
+ alert('[RADAR] You must specify at least 3 data points');
216
+ return;
217
+ }
218
+ }
219
+
220
+
221
+ /**
222
+ * Linearize the data and then create the $ objects
223
+ */
224
+ var idx = 0;
225
+ for (var dataset=0; dataset<this.data.length; ++dataset) {
226
+ for (var i=0,len=this.data[dataset].length; i<len; ++i) {
227
+ this['$' + (idx++)] = {};
228
+ }
229
+ }
230
+
231
+
232
+
233
+ /**
234
+ * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
235
+ * done already
236
+ */
237
+ if (!this.canvas.__rgraph_aa_translated__) {
238
+ this.context.translate(0.5,0.5);
239
+
240
+ this.canvas.__rgraph_aa_translated__ = true;
241
+ }
242
+
243
+
244
+
245
+
246
+ // Short variable names
247
+ var RG = RGraph,
248
+ ca = this.canvas,
249
+ co = ca.getContext('2d'),
250
+ prop = this.properties,
251
+ pa = RG.Path,
252
+ pa2 = RG.path2,
253
+ win = window,
254
+ doc = document,
255
+ ma = Math
256
+
257
+
258
+
259
+ /**
260
+ * "Decorate" the object with the generic effects if the effects library has been included
261
+ */
262
+ if (RG.Effects && typeof RG.Effects.decorate === 'function') {
263
+ RG.Effects.decorate(this);
264
+ }
265
+
266
+
267
+
268
+
269
+ /**
270
+ * A simple setter
271
+ *
272
+ * @param string name The name of the property to set
273
+ * @param string value The value of the property
274
+ */
275
+ this.set =
276
+ this.Set = function (name, value)
277
+ {
278
+ var value = typeof arguments[1] === 'undefined' ? null : arguments[1];
279
+
280
+ /**
281
+ * the number of arguments is only one and it's an
282
+ * object - parse it for configuration data and return.
283
+ */
284
+ if (arguments.length === 1 && typeof name === 'object') {
285
+ RG.parseObjectStyleConfig(this, name);
286
+ return this;
287
+ }
288
+
289
+
290
+
291
+
292
+
293
+ /**
294
+ * This should be done first - prepend the propertyy name with "chart." if necessary
295
+ */
296
+ if (name.substr(0,6) != 'chart.') {
297
+ name = 'chart.' + name;
298
+ }
299
+
300
+
301
+
302
+
303
+ // Convert uppercase letters to dot+lower case letter
304
+ name = name.replace(/([A-Z])/g, function (str)
305
+ {
306
+ return '.' + String(RegExp.$1).toLowerCase();
307
+ });
308
+
309
+
310
+
311
+
312
+
313
+
314
+ if (name == 'chart.text.diameter') {
315
+ name = 'chart.text.size';
316
+ }
317
+
318
+ /**
319
+ * If the name is chart.color, set chart.colors too
320
+ */
321
+ if (name == 'chart.color') {
322
+ this.properties['chart.colors'] = [value];
323
+ }
324
+
325
+
326
+
327
+
328
+
329
+
330
+ prop[name] = value;
331
+
332
+ return this;
333
+ };
334
+
335
+
336
+
337
+
338
+
339
+ /**
340
+ * A simple getter
341
+ *
342
+ * @param string name The name of the property to get
343
+ */
344
+ this.get =
345
+ this.Get = function (name)
346
+ {
347
+ /**
348
+ * This should be done first - prepend the property name with "chart." if necessary
349
+ */
350
+ if (name.substr(0,6) != 'chart.') {
351
+ name = 'chart.' + name;
352
+ }
353
+
354
+ // Convert uppercase letters to dot+lower case letter
355
+ name = name.replace(/([A-Z])/g, function (str)
356
+ {
357
+ return '.' + String(RegExp.$1).toLowerCase()
358
+ });
359
+
360
+ if (name == 'chart.text.diameter') {
361
+ name = 'chart.text.size';
362
+ }
363
+
364
+ return prop[name];
365
+ };
366
+
367
+
368
+
369
+
370
+ /**
371
+ * The draw method which does all the brunt of the work
372
+ */
373
+ this.draw =
374
+ this.Draw = function ()
375
+ {
376
+ /**
377
+ * Fire the onbeforedraw event
378
+ */
379
+ RG.FireCustomEvent(this, 'onbeforedraw');
380
+
381
+ // NB: Colors are parsed further down
382
+
383
+ // Reset the coords array to stop it growing
384
+ this.coords = [];
385
+ this.coords2 = [];
386
+ this.coordsText = [];
387
+
388
+ /**
389
+ * Reset the data to the original_data
390
+ */
391
+ this.data = RG.arrayClone(this.original_data);
392
+
393
+ // Loop thru the data array if chart.accumulative is enable checking to see if all the
394
+ // datasets have the same number of elements.
395
+ if (prop['chart.accumulative']) {
396
+ for (var i=0; i<this.data.length; ++i) {
397
+ if (this.data[i].length != this.data[0].length) {
398
+ alert('[RADAR] Error! When the radar has chart.accumulative set to true all the datasets must have the same number of elements');
399
+ }
400
+ }
401
+ }
402
+
403
+
404
+ /**
405
+ * This defaults to true, but needs to be an array with a size matching the number of
406
+ * labels.
407
+ */
408
+ if (RG.isNull(prop['chart.labels.axes.boxed'])) {
409
+ prop['chart.labels.axes.boxed'] = [];
410
+ for (var i=0; i<(prop['chart.labels.specific'].length || prop['chart.labels.count'] || 5); ++i) {
411
+ prop['chart.labels.axes.boxed'][i] = false;
412
+ }
413
+ }
414
+
415
+
416
+
417
+
418
+ /**
419
+ * This is new in May 2011 and facilitates indiviual gutter settings,
420
+ * eg chart.gutter.left
421
+ */
422
+ this.gutterLeft = prop['chart.gutter.left'];
423
+ this.gutterRight = prop['chart.gutter.right'];
424
+ this.gutterTop = prop['chart.gutter.top'];
425
+ this.gutterBottom = prop['chart.gutter.bottom'];
426
+
427
+ this.centerx = ((ca.width - this.gutterLeft - this.gutterRight) / 2) + this.gutterLeft;
428
+ this.centery = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop;
429
+ this.radius = Math.min(ca.width - this.gutterLeft - this.gutterRight, ca.height - this.gutterTop - this.gutterBottom) / 2;
430
+
431
+
432
+
433
+ /**
434
+ * Allow these to be set by hand
435
+ */
436
+ if (typeof prop['chart.centerx'] == 'number') this.centerx = 2 * prop['chart.centerx'];
437
+ if (typeof prop['chart.centery'] == 'number') this.centery = 2 * prop['chart.centery'];
438
+ if (typeof prop['chart.radius'] == 'number') this.radius = prop['chart.radius'];
439
+
440
+
441
+ /**
442
+ * Parse the colors for gradients. Its down here so that the center X/Y can be used
443
+ */
444
+ if (!this.colorsParsed) {
445
+
446
+ this.parseColors();
447
+
448
+ // Don't want to do this again
449
+ this.colorsParsed = true;
450
+ }
451
+
452
+
453
+
454
+ // Work out the maximum value and the sum
455
+ if (!prop['chart.ymax']) {
456
+
457
+ // this.max is calculated in the constructor
458
+
459
+ // Work out this.max again if the chart is (now) set to be accumulative
460
+ if (prop['chart.accumulative']) {
461
+
462
+ var accumulation = [];
463
+ var len = this.original_data[0].length
464
+
465
+ for (var i=1; i<this.original_data.length; ++i) {
466
+ if (this.original_data[i].length != len) {
467
+ alert('[RADAR] Error! Stacked Radar chart datasets must all be the same size!');
468
+ }
469
+
470
+ for (var j=0; j<this.original_data[i].length; ++j) {
471
+ this.data[i][j] += this.data[i - 1][j];
472
+ this.max = Math.max(this.max, this.data[i][j]);
473
+ }
474
+ }
475
+ }
476
+
477
+
478
+ this.scale2 = RG.getScale2(this, {'max':typeof(prop['chart.ymax']) == 'number' ? prop['chart.ymax'] : this.max,
479
+ 'min':0,
480
+ 'scale.decimals':Number(prop['chart.scale.decimals']),
481
+ 'scale.point':prop['chart.scale.point'],
482
+ 'scale.thousand':prop['chart.scale.thousand'],
483
+ 'scale.round':prop['chart.scale.round'],
484
+ 'units.pre':prop['chart.units.pre'],
485
+ 'units.post':prop['chart.units.post'],
486
+ 'ylabels.count':prop['chart.labels.count']
487
+ });
488
+ this.max = this.scale2.max;
489
+
490
+ } else {
491
+ var ymax = prop['chart.ymax'];
492
+
493
+ this.scale2 = RG.getScale2(this, {'max':ymax,
494
+ 'min':0,
495
+ 'strict':true,
496
+ 'scale.decimals':Number(prop['chart.scale.decimals']),
497
+ 'scale.point':prop['chart.scale.point'],
498
+ 'scale.thousand':prop['chart.scale.thousand'],
499
+ 'scale.round':prop['chart.scale.round'],
500
+ 'units.pre':prop['chart.units.pre'],
501
+ 'units.post':prop['chart.units.post'],
502
+ 'ylabels.count':prop['chart.labels.count']
503
+ });
504
+ this.max = this.scale2.max;
505
+ }
506
+
507
+ this.drawBackground();
508
+ this.drawAxes();
509
+ this.drawCircle();
510
+ this.drawLabels();
511
+
512
+
513
+ /**
514
+ * Allow clipping
515
+ */
516
+ co.save();
517
+ co.beginPath();
518
+ co.arc(this.centerx, this.centery, this.radius * 2, -RG.HALFPI, (RG.TWOPI * prop['chart.animation.trace.clip']) - RG.HALFPI, false);
519
+ co.lineTo(this.centerx, this.centery);
520
+ co.closePath();
521
+ co.clip();
522
+
523
+ this.DrawChart();
524
+ this.DrawHighlights();
525
+ co.restore();
526
+
527
+ //
528
+ // Draw the axis labels
529
+ //
530
+ this.drawAxisLabels();
531
+
532
+ // Draw the title
533
+ if (prop['chart.title']) {
534
+ RG.DrawTitle(this, prop['chart.title'], this.gutterTop, null, prop['chart.title.diameter'] ? prop['chart.title.diameter'] : null)
535
+ }
536
+
537
+ // Draw the key if necessary
538
+ // obj, key, colors
539
+ if (prop['chart.key']) {
540
+ RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
541
+ }
542
+
543
+ /**
544
+ * Show the context menu
545
+ */
546
+ if (prop['chart.contextmenu']) {
547
+ RG.ShowContext(this);
548
+ }
549
+
550
+
551
+ /**
552
+ * This function enables resizing
553
+ */
554
+ if (prop['chart.resizable']) {
555
+ RG.AllowResizing(this);
556
+ }
557
+
558
+
559
+ /**
560
+ * This installs the event listeners
561
+ */
562
+ RG.InstallEventListeners(this);
563
+
564
+ /**
565
+ * This installs the Radar chart specific area listener
566
+ */
567
+ if ( (prop['chart.fill.click'] || prop['chart.fill.mousemove'] || !RG.is_null(prop['chart.fill.tooltips'])) && !this.__fill_click_listeners_installed__) {
568
+ this.AddFillListeners();
569
+ this.__fill_click_listeners_installed__ = true;
570
+ }
571
+
572
+
573
+
574
+ /**
575
+ * Fire the onfirstdraw event
576
+ */
577
+ if (this.firstDraw) {
578
+ RG.fireCustomEvent(this, 'onfirstdraw');
579
+ this.firstDraw = false;
580
+ this.firstDrawFunc();
581
+ }
582
+
583
+
584
+
585
+
586
+ /**
587
+ * Fire the RGraph ondraw event
588
+ */
589
+ RGraph.FireCustomEvent(this, 'ondraw');
590
+
591
+ return this;
592
+ };
593
+
594
+
595
+
596
+ /**
597
+ * Used in chaining. Runs a function there and then - not waiting for
598
+ * the events to fire (eg the onbeforedraw event)
599
+ *
600
+ * @param function func The function to execute
601
+ */
602
+ this.exec = function (func)
603
+ {
604
+ func(this);
605
+
606
+ return this;
607
+ };
608
+
609
+
610
+
611
+
612
+ /**
613
+ * Draws the background circles
614
+ */
615
+ this.drawBackground =
616
+ this.DrawBackground = function ()
617
+ {
618
+ var color = prop['chart.background.circles.color'];
619
+ var poly = prop['chart.background.circles.poly'];
620
+ var spacing = prop['chart.background.circles.spacing'];
621
+ var spokes = prop['chart.background.circles.spokes'];
622
+
623
+
624
+
625
+
626
+ // Set the linewidth for the grid (so that repeated redrawing works OK)
627
+ co.lineWidth = 1;
628
+
629
+
630
+
631
+
632
+ /**
633
+ * Draws the background circles
634
+ */
635
+ if (prop['chart.background.circles'] && poly == false) {
636
+
637
+
638
+
639
+
640
+
641
+ // Draw the concentric circles
642
+ co.strokeStyle = color;
643
+ co.beginPath();
644
+
645
+ var numrings = typeof(prop['chart.background.circles.count']) == 'number' ? prop['chart.background.circles.count'] : prop['chart.labels.count'];
646
+
647
+ // TODO Currently set to 5 - needs changing
648
+ for (var r=0; r<=this.radius; r+=(this.radius / numrings)) {
649
+ co.moveTo(this.centerx, this.centery);
650
+ co.arc(this.centerx, this.centery,r, 0, RG.TWOPI, false);
651
+ }
652
+ co.stroke();
653
+
654
+
655
+
656
+
657
+
658
+ /**
659
+ * Draw the diagonals/spokes
660
+ */
661
+ co.strokeStyle = color;
662
+
663
+ for (var i=0; i<360; i+=(360 / spokes)) {
664
+ co.beginPath();
665
+ co.arc(this.centerx,
666
+ this.centery,
667
+ this.radius,
668
+ (i / 360) * RG.TWOPI,
669
+ ((i+0.001) / 360) * RG.TWOPI,
670
+ false); // The 0.01 avoids a bug in Chrome 6
671
+ co.lineTo(this.centerx, this.centery);
672
+ co.stroke();
673
+ }
674
+
675
+
676
+
677
+
678
+
679
+
680
+ /**
681
+ * The background"circles" are actually drawn as a poly based on how many points there are
682
+ * (ie hexagons if there are 6 points, squares if the are four etc)
683
+ */
684
+ } else if (prop['chart.background.circles'] && poly == true) {
685
+
686
+ /**
687
+ * Draw the diagonals/spokes
688
+ */
689
+ co.strokeStyle = color;
690
+ var increment = 360 / this.data[0].length
691
+
692
+ for (var i=0; i<360; i+=increment) {
693
+ co.beginPath();
694
+ co.arc(this.centerx,
695
+ this.centery,
696
+ this.radius,
697
+ ((i / 360) * RG.TWOPI) - RG.HALFPI,
698
+ (((i + 0.001) / 360) * RG.TWOPI) - RG.HALFPI,
699
+ false); // The 0.001 avoids a bug in Chrome 6
700
+ co.lineTo(this.centerx, this.centery);
701
+ co.stroke();
702
+ }
703
+
704
+
705
+ /**
706
+ * Draw the lines that go around the Radar chart
707
+ */
708
+ co.strokeStyle = color;
709
+
710
+ var numrings = typeof(prop['chart.background.circles.count']) == 'number' ? prop['chart.background.circles.count'] : prop['chart.labels.count'];
711
+
712
+ for (var r=0; r<=this.radius; r+=(this.radius / numrings)) {
713
+ co.beginPath();
714
+ for (var a=0; a<=360; a+=(360 / this.data[0].length)) {
715
+ co.arc(this.centerx,
716
+ this.centery,
717
+ r,
718
+ RG.degrees2Radians(a) - RG.HALFPI,
719
+ RG.degrees2Radians(a) + 0.001 - RG.HALFPI,
720
+ false);
721
+ }
722
+ co.closePath();
723
+ co.stroke();
724
+ }
725
+ }
726
+ };
727
+
728
+
729
+
730
+
731
+ /**
732
+ * Draws the axes
733
+ */
734
+ this.drawAxes =
735
+ this.DrawAxes = function ()
736
+ {
737
+ co.strokeStyle = prop['chart.axes.color'];
738
+
739
+ var halfsize = this.radius;
740
+
741
+ co.beginPath();
742
+ /**
743
+ * The Y axis
744
+ */
745
+ co.moveTo(Math.round(this.centerx), this.centery + this.radius);
746
+ co.lineTo(Math.round(this.centerx), this.centery - this.radius);
747
+
748
+
749
+ // Draw the bits at either end of the Y axis
750
+ co.moveTo(this.centerx - 5, Math.round(this.centery + this.radius));
751
+ co.lineTo(this.centerx + 5, Math.round(this.centery + this.radius));
752
+ co.moveTo(this.centerx - 5, Math.round(this.centery - this.radius));
753
+ co.lineTo(this.centerx + 5, Math.round(this.centery - this.radius));
754
+
755
+ // Draw Y axis tick marks
756
+ for (var y=(this.centery - this.radius); y<(this.centery + this.radius); y+=(this.radius/prop['chart.numyticks'])) {
757
+ co.moveTo(this.centerx - 3, Math.round(y));
758
+ co.lineTo(this.centerx + 3, Math.round(y));
759
+ }
760
+
761
+ /**
762
+ * The X axis
763
+ */
764
+ co.moveTo(this.centerx - this.radius, Math.round(this.centery));
765
+ co.lineTo(this.centerx + this.radius, Math.round(this.centery));
766
+
767
+ // Draw the bits at the end of the X axis
768
+ co.moveTo(Math.round(this.centerx - this.radius), this.centery - 5);
769
+ co.lineTo(Math.round(this.centerx - this.radius), this.centery + 5);
770
+ co.moveTo(Math.round(this.centerx + this.radius), this.centery - 5);
771
+ co.lineTo(Math.round(this.centerx + this.radius), this.centery + 5);
772
+
773
+ // Draw X axis tick marks
774
+ for (var x=(this.centerx - this.radius); x<(this.centerx + this.radius); x+=(this.radius/prop['chart.numxticks'])) {
775
+ co.moveTo(Math.round(x), this.centery - 3);
776
+ co.lineTo(Math.round(x), this.centery + 3);
777
+ }
778
+
779
+ // Stroke it
780
+ co.stroke();
781
+ };
782
+
783
+
784
+
785
+
786
+ /**
787
+ * The function which actually draws the radar chart
788
+ */
789
+ this.drawChart =
790
+ this.DrawChart = function ()
791
+ {
792
+ var alpha = prop['chart.colors.alpha'];
793
+
794
+ if (typeof(alpha) == 'number') {
795
+ var oldAlpha = co.globalAlpha;
796
+ co.globalAlpha = alpha;
797
+ }
798
+
799
+ var numDatasets = this.data.length;
800
+
801
+ for (var dataset=0; dataset<this.data.length; ++dataset) {
802
+
803
+ co.beginPath();
804
+
805
+ var coords_dataset = [];
806
+
807
+ for (var i=0; i<this.data[dataset].length; ++i) {
808
+
809
+ var coords = this.GetCoordinates(dataset, i);
810
+
811
+ if (coords_dataset == null) {
812
+ coords_dataset = [];
813
+ }
814
+
815
+ coords_dataset.push(coords);
816
+ this.coords.push(coords);
817
+ }
818
+
819
+ this.coords2[dataset] = coords_dataset;
820
+
821
+
822
+ /**
823
+ * Now go through the coords and draw the chart itself
824
+ *
825
+ * 18/5/2012 - chart.strokestyle can now be an array of colors as well as a single color
826
+ */
827
+
828
+ co.strokeStyle = (typeof(prop['chart.strokestyle']) == 'object' && prop['chart.strokestyle'][dataset]) ? prop['chart.strokestyle'][dataset] : prop['chart.strokestyle'];
829
+ co.fillStyle = prop['chart.colors'][dataset] ? prop['chart.colors'][dataset] : 'rgba(0,0,0,0)';
830
+ if (co.fillStyle === 'transparent') {
831
+ co.fillStyle = 'rgba(0,0,0,0)';
832
+ }
833
+ co.lineWidth = prop['chart.linewidth'];
834
+
835
+ for (i=0; i<coords_dataset.length; ++i) {
836
+ if (i == 0) {
837
+ co.moveTo(coords_dataset[i][0], coords_dataset[i][1]);
838
+ } else {
839
+ co.lineTo(coords_dataset[i][0], coords_dataset[i][1]);
840
+ }
841
+ }
842
+
843
+
844
+ // If on the second or greater dataset, backtrack
845
+ if (prop['chart.accumulative'] && dataset > 0) {
846
+
847
+ // This goes back to the start coords of this particular dataset
848
+ co.lineTo(coords_dataset[0][0], coords_dataset[0][1]);
849
+
850
+ //Now move down to the end point of the previous dataset
851
+ co.moveTo(last_coords[0][0], last_coords[0][1]);
852
+
853
+ for (var i=coords_dataset.length - 1; i>=0; --i) {
854
+ co.lineTo(last_coords[i][0], last_coords[i][1]);
855
+ }
856
+ }
857
+
858
+ // This is used by the next iteration of the loop
859
+ var last_coords = coords_dataset;
860
+
861
+ co.closePath();
862
+
863
+ co.stroke();
864
+ co.fill();
865
+ }
866
+
867
+ // Reset the globalAlpha
868
+ if (typeof(alpha) == 'number') {
869
+ co.globalAlpha = oldAlpha;
870
+ }
871
+ };
872
+
873
+
874
+
875
+
876
+ /**
877
+ * Gets the coordinates for a particular mark
878
+ *
879
+ * @param number i The index of the data (ie which one it is)
880
+ * @return array A two element array of the coordinates
881
+ */
882
+ this.getCoordinates =
883
+ this.GetCoordinates = function (dataset, index)
884
+ {
885
+ // The number of data points
886
+ var len = this.data[dataset].length;
887
+
888
+ // The magnitude of the data (NOT the x/y coords)
889
+ var mag = (this.data[dataset][index] / this.max) * this.radius;
890
+
891
+ /**
892
+ * Get the angle
893
+ */
894
+ var angle = (RG.TWOPI / len) * index; // In radians
895
+ angle -= RG.HALFPI;
896
+
897
+
898
+ /**
899
+ * Work out the X/Y coordinates
900
+ */
901
+ var x = Math.cos(angle) * mag;
902
+ var y = Math.sin(angle) * mag;
903
+
904
+ /**
905
+ * Put the coordinate in the right quadrant
906
+ */
907
+ x = this.centerx + x;
908
+ y = this.centery + y;
909
+
910
+ return [x,y];
911
+ };
912
+
913
+
914
+
915
+
916
+ /**
917
+ * This function adds the labels to the chart
918
+ */
919
+ this.drawLabels =
920
+ this.DrawLabels = function ()
921
+ {
922
+ var labels = prop['chart.labels'];
923
+
924
+ if (labels && labels.length > 0) {
925
+
926
+ co.lineWidth = 1;
927
+ co.strokeStyle = 'gray';
928
+ co.fillStyle = prop['chart.labels.color'] || prop['chart.text.color'];
929
+
930
+ var bgFill = prop['chart.labels.background.fill'],
931
+ bold = prop['chart.labels.bold'],
932
+ bgBoxed = prop['chart.labels.boxed'],
933
+ offset = prop['chart.labels.offset'],
934
+ font = prop['chart.text.font'],
935
+ size = prop['chart.text.size'],
936
+ radius = this.radius,
937
+ color = prop['chart.labels.color'] || prop['chart.text.color']
938
+
939
+ for (var i=0; i<labels.length; ++i) {
940
+
941
+ var angle = (RG.TWOPI / prop['chart.labels'].length) * i;
942
+ angle -= RG.HALFPI;
943
+
944
+ var x = this.centerx + (ma.cos(angle) * (radius + offset));
945
+ var y = this.centery + (ma.sin(angle) * (radius + offset));
946
+
947
+ /**
948
+ * Horizontal alignment
949
+ */
950
+ var halign = x < this.centerx ? 'right' : 'left' ;
951
+ if (i == 0 || (i / labels.length) == 0.5) halign = 'center';
952
+
953
+ if (labels[i] && labels[i].length) {
954
+
955
+ RG.text2(this, {
956
+ 'color': color,
957
+ 'font':font,
958
+ 'size':size,
959
+ 'x':x,
960
+ 'y':y,
961
+ 'text':labels[i],
962
+ 'valign':'center',
963
+ 'halign':halign,
964
+ 'bounding':bgBoxed,
965
+ 'boundingFill':bgFill,
966
+ 'bold':bold,
967
+ 'tag': 'labels'
968
+ });
969
+ }
970
+ }
971
+ }
972
+ };
973
+
974
+
975
+
976
+
977
+ /**
978
+ * Draws the circle. No arguments as it gets the information from the object properties.
979
+ */
980
+ this.drawCircle =
981
+ this.DrawCircle = function ()
982
+ {
983
+ var circle = {};
984
+ circle.limit = prop['chart.circle'];
985
+ circle.fill = prop['chart.circle.fill'];
986
+ circle.stroke = prop['chart.circle.stroke'];
987
+
988
+ if (circle.limit) {
989
+
990
+ var r = (circle.limit / this.max) * this.radius;
991
+
992
+ co.fillStyle = circle.fill;
993
+ co.strokeStyle = circle.stroke;
994
+
995
+ co.beginPath();
996
+ co.arc(this.centerx, this.centery, r, 0, RG.TWOPI, 0);
997
+ co.fill();
998
+ co.stroke();
999
+ }
1000
+ };
1001
+
1002
+
1003
+
1004
+
1005
+ /**
1006
+ * Unsuprisingly, draws the labels
1007
+ */
1008
+ this.drawAxisLabels =
1009
+ this.DrawAxisLabels = function ()
1010
+ {
1011
+ /**
1012
+ * Draw specific axis labels
1013
+ */
1014
+ if (RG.isArray(prop['chart.labels.specific']) && prop['chart.labels.specific'].length) {
1015
+ this.drawSpecificAxisLabels();
1016
+ return;
1017
+ }
1018
+
1019
+ co.lineWidth = 1;
1020
+
1021
+ // Set the color to black
1022
+ co.fillStyle = 'black';
1023
+ co.strokeStyle = 'black';
1024
+
1025
+ var r = this.radius;
1026
+ var font = prop['chart.text.font'];
1027
+ var size = typeof(prop['chart.text.size.scale']) == 'number' ? prop['chart.text.size.scale'] : prop['chart.text.size'];
1028
+ var axes = prop['chart.labels.axes'].toLowerCase();
1029
+ var color = 'rgba(255,255,255,0.9)';
1030
+ var drawzero = false;
1031
+ var units_pre = prop['chart.units.pre'];
1032
+ var units_post = prop['chart.units.post'];
1033
+ var decimals = prop['chart.scale.decimals'];
1034
+ var bold = prop['chart.labels.axes.bold'];
1035
+ var boxed = prop['chart.labels.axes.boxed'];
1036
+ var centerx = this.centerx;
1037
+ var centery = this.centery;
1038
+ var scale = this.scale;
1039
+
1040
+ co.fillStyle = prop['chart.text.color'];
1041
+
1042
+ // The "North" axis labels
1043
+ if (axes.indexOf('n') > -1) {
1044
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1045
+ RG.Text2(this, {
1046
+ 'bold':bold[i],
1047
+ 'font':font,
1048
+ 'size':size,
1049
+ 'x':centerx,
1050
+ 'y':centery - (r * ((i+1)/this.scale2.labels.length)),
1051
+ 'text':this.scale2.labels[i],
1052
+ 'valign':'center',
1053
+ 'halign':'center',
1054
+ 'bounding':boxed[i] || color,
1055
+ 'boundingFill':color,
1056
+ 'boundingStroke':'rgba(0,0,0,0)',
1057
+ 'tag': 'scale'
1058
+ });
1059
+ }
1060
+
1061
+ drawzero = true;
1062
+ }
1063
+
1064
+ // The "South" axis labels
1065
+ if (axes.indexOf('s') > -1) {
1066
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1067
+ RG.Text2(this, {
1068
+ 'bold':bold[i],
1069
+ 'font':font,
1070
+ 'size':size,
1071
+ 'x':centerx,
1072
+ 'y':centery + (r * ((i+1)/this.scale2.labels.length)),
1073
+ 'text':this.scale2.labels[i],
1074
+ 'valign':'center',
1075
+ 'halign':'center',
1076
+ 'bounding':boxed[i] || color,
1077
+ 'boundingFill':color,
1078
+ 'boundingStroke':'rgba(0,0,0,0)',
1079
+ 'tag': 'scale'
1080
+ });
1081
+ }
1082
+
1083
+ drawzero = true;
1084
+ }
1085
+
1086
+ // The "East" axis labels
1087
+ if (axes.indexOf('e') > -1) {
1088
+
1089
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1090
+ RG.Text2(this, {
1091
+ 'bold':bold[i],
1092
+ 'font':font,
1093
+ 'size':size,
1094
+ 'x':centerx + (r * ((i+1)/this.scale2.labels.length)),
1095
+ 'y':centery,
1096
+ 'text':this.scale2.labels[i],
1097
+ 'valign':'center',
1098
+ 'halign':'center',
1099
+ 'bounding':boxed[i]|| color,
1100
+ 'boundingFill':color,
1101
+ 'boundingStroke':'rgba(0,0,0,0)',
1102
+ 'tag': 'scale'
1103
+ });
1104
+ }
1105
+
1106
+ drawzero = true;
1107
+ }
1108
+
1109
+ // The "West" axis labels
1110
+ if (axes.indexOf('w') > -1) {
1111
+
1112
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1113
+ RG.Text2(this, {
1114
+ 'bold':bold[i],
1115
+ 'font':font,
1116
+ 'size':size,
1117
+ 'x':centerx - (r * ((i+1)/this.scale2.labels.length)),
1118
+ 'y':centery,
1119
+ 'text':this.scale2.labels[i],
1120
+ 'valign':'center',
1121
+ 'halign':'center',
1122
+ 'bounding':boxed[i]|| color,
1123
+ 'boundingFill':color,
1124
+ 'boundingStroke':'rgba(0,0,0,0)',
1125
+ 'tag': 'scale'
1126
+ });
1127
+ }
1128
+
1129
+ drawzero = true;
1130
+ }
1131
+
1132
+ if (drawzero) {
1133
+ RG.Text2(this, {
1134
+ 'font':font,
1135
+ 'size':size,
1136
+ 'x':centerx,
1137
+ 'y':centery,
1138
+ 'text':RG.number_format(this, Number(0).toFixed(decimals), units_pre, units_post),
1139
+ 'valign':'center',
1140
+ 'halign':'center',
1141
+ 'bounding':prop['chart.labels.axes.boxed.zero'],
1142
+ 'boundingFill':color,
1143
+ 'boundingStroke':'rgba(0,0,0,0)',
1144
+ 'bold':prop['chart.labels.axes.bold.zero'],
1145
+ 'tag': 'scale'
1146
+ });
1147
+ }
1148
+ };
1149
+
1150
+
1151
+
1152
+
1153
+ /**
1154
+ * Draws specific axis labels
1155
+ */
1156
+ this.drawSpecificAxisLabels =
1157
+ this.DrawSpecificAxisLabels = function ()
1158
+ {
1159
+ /**
1160
+ * Specific axis labels
1161
+ */
1162
+ var labels = prop['chart.labels.specific'];
1163
+ var bold = RG.array_pad(prop['chart.labels.axes.bold'],labels.length);
1164
+ var boxed = RG.array_pad(prop['chart.labels.axes.boxed'],labels.length);
1165
+ var reversed_labels = RG.array_reverse(labels);
1166
+ var reversed_bold = RG.array_reverse(bold);
1167
+ var reversed_boxed = RG.array_reverse(boxed);
1168
+ var font = prop['chart.text.font'];
1169
+ var size = typeof(prop['chart.text.size.scale']) == 'number' ? prop['chart.text.size.scale'] : prop['chart.text.size'];
1170
+ var axes = prop['chart.labels.axes'].toLowerCase();
1171
+
1172
+ co.fillStyle = prop['chart.text.color'];
1173
+
1174
+ for (var i=0; i<labels.length; ++i) {
1175
+
1176
+ if (axes.indexOf('n') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':reversed_bold[i],'font':font,'size':size,'x':this.centerx,'y':this.centery - this.radius + ((this.radius / labels.length) * i),'text':reversed_labels[i],'valign':'center','halign':'center','bounding':reversed_boxed[i],'boundingFill':'white'});
1177
+ if (axes.indexOf('s') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':bold[i],'font':font,'size':size,'x':this.centerx,'y':this.centery + ((this.radius / labels.length) * (i+1)),'text':labels[i],'valign':'center','halign':'center','bounding':boxed[i],'boundingFill':'white'});
1178
+
1179
+ if (axes.indexOf('w') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':reversed_bold[i],'font':font,'size':size,'x':this.centerx - this.radius + ((this.radius / labels.length) * i),'y':this.centery,'text':reversed_labels[i],'valign':'center','halign':'center','bounding':reversed_boxed[i],'boundingFill':'white'});
1180
+ if (axes.indexOf('e') > -1) RG.Text2(this, {'tag': 'labels.specific', 'bold':bold[i],'font':font,'size':size,'x':this.centerx + ((this.radius / labels.length) * (i+1)),'y':this.centery,'text':labels[i],'valign':'center','halign':'center','bounding':boxed[i],'boundingFill':'white'});
1181
+ }
1182
+ };
1183
+
1184
+
1185
+
1186
+
1187
+ /**
1188
+ * This method eases getting the focussed point (if any)
1189
+ *
1190
+ * @param event e The event object
1191
+ */
1192
+ this.getShape =
1193
+ this.getPoint = function (e)
1194
+ {
1195
+ for (var i=0; i<this.coords.length; ++i) {
1196
+
1197
+ var x = this.coords[i][0];
1198
+ var y = this.coords[i][1];
1199
+ var tooltips = prop['chart.tooltips'];
1200
+ var index = Number(i);
1201
+ var mouseXY = RG.getMouseXY(e);
1202
+ var mouseX = mouseXY[0];
1203
+ var mouseY = mouseXY[1];
1204
+
1205
+ if ( mouseX < (x + 5)
1206
+ && mouseX > (x - 5)
1207
+ && mouseY > (y - 5)
1208
+ && mouseY < (y + 5)
1209
+ ) {
1210
+
1211
+ var tooltip = RG.parseTooltipText(prop['chart.tooltips'], index);
1212
+
1213
+ return {0: this, 'object': this,
1214
+ 1: x, 'x': x,
1215
+ 2: y, 'y': y,
1216
+ 3: null, 'dataset': null,
1217
+ 4: index, 'index': i,
1218
+ 'tooltip': tooltip
1219
+ }
1220
+ }
1221
+ }
1222
+ };
1223
+
1224
+
1225
+
1226
+
1227
+ /**
1228
+ * Each object type has its own Highlight() function which highlights the appropriate shape
1229
+ *
1230
+ * @param object shape The shape to highlight
1231
+ */
1232
+ this.highlight =
1233
+ this.Highlight = function (shape)
1234
+ {
1235
+ // Add the new highlight
1236
+ RG.Highlight.Point(this, shape);
1237
+ };
1238
+
1239
+
1240
+
1241
+
1242
+ /**
1243
+ * The getObjectByXY() worker method. Don't call this call:
1244
+ *
1245
+ * RGraph.ObjectRegistry.getObjectByXY(e)
1246
+ *
1247
+ * @param object e The event object
1248
+ */
1249
+ this.getObjectByXY = function (e)
1250
+ {
1251
+ var mouseXY = RG.getMouseXY(e);
1252
+
1253
+ if (
1254
+ mouseXY[0] > (this.centerx - this.radius)
1255
+ && mouseXY[0] < (this.centerx + this.radius)
1256
+ && mouseXY[1] > (this.centery - this.radius)
1257
+ && mouseXY[1] < (this.centery + this.radius)
1258
+ ) {
1259
+
1260
+ return this;
1261
+ }
1262
+ };
1263
+
1264
+
1265
+
1266
+
1267
+ /**
1268
+ * This function positions a tooltip when it is displayed
1269
+ *
1270
+ * @param obj object The chart object
1271
+ * @param int x The X coordinate specified for the tooltip
1272
+ * @param int y The Y coordinate specified for the tooltip
1273
+ * @param objec tooltip The tooltips DIV element
1274
+ */
1275
+ this.positionTooltip = function (obj, x, y, tooltip, idx)
1276
+ {
1277
+ var dataset = tooltip.__dataset__;
1278
+ var index = tooltip.__index__;
1279
+ var coordX = this.coords[index][0];
1280
+ var coordY = this.coords[index][1];
1281
+ var canvasXY = RG.getCanvasXY(obj.canvas);
1282
+ var gutterLeft = this.gutterLeft;
1283
+ var gutterTop = this.gutterTop;
1284
+ var width = tooltip.offsetWidth;
1285
+
1286
+ // Set the top position
1287
+ tooltip.style.left = 0;
1288
+ tooltip.style.top = parseInt(tooltip.style.top) - 9 + 'px';
1289
+
1290
+ // By default any overflow is hidden
1291
+ tooltip.style.overflow = '';
1292
+
1293
+ // The arrow
1294
+ var img = new Image();
1295
+ img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
1296
+ img.style.position = 'absolute';
1297
+ img.id = '__rgraph_tooltip_pointer__';
1298
+ img.style.top = (tooltip.offsetHeight - 2) + 'px';
1299
+ tooltip.appendChild(img);
1300
+
1301
+ // Reposition the tooltip if at the edges:
1302
+
1303
+ // LEFT edge
1304
+ if ((canvasXY[0] + coordX - (width / 2)) < 10) {
1305
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + 'px';
1306
+ img.style.left = ((width * 0.1) - 8.5) + 'px';
1307
+
1308
+ // RIGHT edge
1309
+ } else if ((canvasXY[0] + coordX + (width / 2)) > doc.body.offsetWidth) {
1310
+ tooltip.style.left = canvasXY[0] + coordX - (width * 0.9) + 'px';
1311
+ img.style.left = ((width * 0.9) - 8.5) + 'px';
1312
+
1313
+ // Default positioning - CENTERED
1314
+ } else {
1315
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
1316
+ img.style.left = ((width * 0.5) - 8.5) + 'px';
1317
+ }
1318
+ };
1319
+
1320
+
1321
+
1322
+
1323
+ /**
1324
+ * This draws highlights on the points
1325
+ */
1326
+ this.drawHighlights =
1327
+ this.DrawHighlights = function ()
1328
+ {
1329
+ if (prop['chart.highlights']) {
1330
+
1331
+ var sequentialIdx = 0;
1332
+ var dataset = 0;
1333
+ var index = 0;
1334
+ var radius = prop['chart.highlights.radius'];
1335
+
1336
+
1337
+
1338
+ for (var dataset=0; dataset <this.data.length; ++dataset) {
1339
+ for (var index=0; index<this.data[dataset].length; ++index) {
1340
+ co.beginPath();
1341
+ co.strokeStyle = prop['chart.highlights.stroke'];
1342
+ co.fillStyle = prop['chart.highlights.fill'] ? prop['chart.highlights.fill'] : ((typeof(prop['chart.strokestyle']) == 'object' && prop['chart.strokestyle'][dataset]) ? prop['chart.strokestyle'][dataset] : prop['chart.strokestyle']);
1343
+ co.arc(this.coords[sequentialIdx][0], this.coords[sequentialIdx][1], radius, 0, RG.TWOPI, false);
1344
+ co.stroke();
1345
+ co.fill();
1346
+ ++sequentialIdx;
1347
+ }
1348
+ }
1349
+
1350
+ }
1351
+ };
1352
+
1353
+
1354
+
1355
+
1356
+ /**
1357
+ * This function returns the radius (ie the distance from the center) for a particular
1358
+ * value. Note that if you want the angle for a point you can use getAngle(index)
1359
+ *
1360
+ * @param number value The value you want the radius for
1361
+ */
1362
+ this.getRadius = function (value)
1363
+ {
1364
+ if (value < 0 || value > this.max) {
1365
+ return null;
1366
+ }
1367
+
1368
+ // Radar doesn't support minimum value
1369
+ var radius = (value / this.max) * this.radius;
1370
+
1371
+ return radius;
1372
+ };
1373
+
1374
+
1375
+
1376
+
1377
+ /**
1378
+ * This function returns the angle (in radians) for a particular index.
1379
+ *
1380
+ * @param number numitems The total number of items
1381
+ * @param number index The zero index number of the item to get the angle for
1382
+ */
1383
+ this.getAngle = function (numitems, index)
1384
+ {
1385
+ var angle = (RG.TWOPI / numitems) * index;
1386
+ angle -= RG.HALFPI;
1387
+
1388
+ return angle;
1389
+ };
1390
+
1391
+
1392
+
1393
+
1394
+ /**
1395
+ * This allows for easy specification of gradients
1396
+ */
1397
+ this.parseColors = function ()
1398
+ {
1399
+ // Save the original colors so that they can be restored when the canvas is reset
1400
+ if (this.original_colors.length === 0) {
1401
+ this.original_colors['chart.colors'] = RG.array_clone(prop['chart.colors']);
1402
+ this.original_colors['chart.key.colors'] = RG.array_clone(prop['chart.key.colors']);
1403
+ this.original_colors['chart.title.color'] = RG.array_clone(prop['chart.title.color']);
1404
+ this.original_colors['chart.text.color'] = RG.array_clone(prop['chart.text.color']);
1405
+ this.original_colors['chart.highlight.stroke'] = RG.array_clone(prop['chart.highlight.stroke']);
1406
+ this.original_colors['chart.highlight.fill'] = RG.array_clone(prop['chart.highlight.fill']);
1407
+ this.original_colors['chart.circle.fill'] = RG.array_clone(prop['chart.circle.fill']);
1408
+ this.original_colors['chart.circle.stroke'] = RG.array_clone(prop['chart.circle.stroke']);
1409
+ }
1410
+
1411
+ for (var i=0; i<prop['chart.colors'].length; ++i) {
1412
+ prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
1413
+ }
1414
+
1415
+ var keyColors = prop['chart.key.colors'];
1416
+
1417
+ if (typeof(keyColors) != 'null' && keyColors && keyColors.length) {
1418
+ for (var i=0; i<prop['chart.key.colors'].length; ++i) {
1419
+ prop['chart.key.colors'][i] = this.parseSingleColorForGradient(prop['chart.key.colors'][i]);
1420
+ }
1421
+ }
1422
+
1423
+ prop['chart.title.color'] = this.parseSingleColorForGradient(prop['chart.title.color']);
1424
+ prop['chart.text.color'] = this.parseSingleColorForGradient(prop['chart.text.color']);
1425
+ prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
1426
+ prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
1427
+ prop['chart.circle.fill'] = this.parseSingleColorForGradient(prop['chart.circle.fill']);
1428
+ prop['chart.circle.stroke'] = this.parseSingleColorForGradient(prop['chart.circle.stroke']);
1429
+ };
1430
+
1431
+
1432
+
1433
+
1434
+ /**
1435
+ * Use this function to reset the object to the post-constructor state. Eg reset colors if
1436
+ * need be etc
1437
+ */
1438
+ this.reset = function ()
1439
+ {
1440
+ };
1441
+
1442
+
1443
+
1444
+
1445
+ /**
1446
+ * This parses a single color value
1447
+ */
1448
+ this.parseSingleColorForGradient = function (color)
1449
+ {
1450
+ if (!color || typeof(color) != 'string') {
1451
+ return color;
1452
+ }
1453
+
1454
+ if (color.match(/^gradient\((.*)\)$/i)) {
1455
+
1456
+ var parts = RegExp.$1.split(':');
1457
+
1458
+ // Create the gradient
1459
+ var grad = co.createRadialGradient(this.centerx, this.centery, 0, this.centerx, this.centery, this.radius);
1460
+
1461
+ var diff = 1 / (parts.length - 1);
1462
+
1463
+ grad.addColorStop(0, RG.trim(parts[0]));
1464
+
1465
+ for (var j=1; j<parts.length; ++j) {
1466
+ grad.addColorStop(j * diff, RG.trim(parts[j]));
1467
+ }
1468
+ }
1469
+
1470
+ return grad ? grad : color;
1471
+ };
1472
+
1473
+
1474
+
1475
+
1476
+ this.addFillListeners =
1477
+ this.AddFillListeners = function (e)
1478
+ {
1479
+ var obj = this;
1480
+
1481
+ var func = function (e)
1482
+ {
1483
+ //var canvas = e.target;
1484
+ //var context = canvas.getContext('2d');
1485
+ var coords = this.coords;
1486
+ var coords2 = this.coords2;
1487
+ var mouseXY = RG.getMouseXY(e);
1488
+ var dataset = 0;
1489
+
1490
+ if (e.type == 'mousemove' && prop['chart.fill.mousemove.redraw']) {
1491
+ RG.RedrawCanvas(ca);
1492
+ }
1493
+
1494
+ for (var dataset=(obj.coords2.length-1); dataset>=0; --dataset) {
1495
+
1496
+ // Draw the path again so that it can be checked
1497
+ co.beginPath();
1498
+ co.moveTo(obj.coords2[dataset][0][0], obj.coords2[dataset][0][1]);
1499
+ for (var j=0; j<obj.coords2[dataset].length; ++j) {
1500
+ co.lineTo(obj.coords2[dataset][j][0], obj.coords2[dataset][j][1]);
1501
+ }
1502
+
1503
+ // Draw a line back to the starting point
1504
+ co.lineTo(obj.coords2[dataset][0][0], obj.coords2[dataset][0][1]);
1505
+
1506
+ // Go thru the previous datasets coords in reverse order
1507
+ if (prop['chart.accumulative'] && dataset > 0) {
1508
+ co.lineTo(obj.coords2[dataset - 1][0][0], obj.coords2[dataset - 1][0][1]);
1509
+ for (var j=(obj.coords2[dataset - 1].length - 1); j>=0; --j) {
1510
+ co.lineTo(obj.coords2[dataset - 1][j][0], obj.coords2[dataset - 1][j][1]);
1511
+ }
1512
+ }
1513
+
1514
+ co.closePath();
1515
+
1516
+ if (co.isPointInPath(mouseXY[0], mouseXY[1])) {
1517
+ var inPath = true;
1518
+ break;
1519
+ }
1520
+ }
1521
+
1522
+ // Call the events
1523
+ if (inPath) {
1524
+
1525
+ var fillTooltips = prop['chart.fill.tooltips'];
1526
+
1527
+ /**
1528
+ * Click event
1529
+ */
1530
+ if (e.type == 'click') {
1531
+ if (prop['chart.fill.click']) {
1532
+ prop['chart.fill.click'](e, dataset);
1533
+ }
1534
+
1535
+ if (prop['chart.fill.tooltips'] && prop['chart.fill.tooltips'][dataset]) {
1536
+ obj.DatasetTooltip(e, dataset);
1537
+ }
1538
+ }
1539
+
1540
+
1541
+
1542
+ /**
1543
+ * Mousemove event
1544
+ */
1545
+ if (e.type == 'mousemove') {
1546
+
1547
+ if (prop['chart.fill.mousemove']) {
1548
+ prop['chart.fill.mousemove'](e, dataset);
1549
+ }
1550
+
1551
+ if (!RG.is_null(fillTooltips)) {
1552
+ e.target.style.cursor = 'pointer';
1553
+ }
1554
+
1555
+ if (prop['chart.fill.tooltips'] && prop['chart.fill.tooltips'][dataset]) {
1556
+ e.target.style.cursor = 'pointer';
1557
+ }
1558
+ }
1559
+
1560
+ e.stopPropagation();
1561
+
1562
+ } else if (e.type == 'mousemove') {
1563
+ ca.style.cursor = 'default';
1564
+ }
1565
+ };
1566
+
1567
+ /**
1568
+ * Add the click listener
1569
+ */
1570
+ if (prop['chart.fill.click'] || !RG.is_null(prop['chart.fill.tooltips'])) {
1571
+ ca.addEventListener('click', func, false);
1572
+ }
1573
+
1574
+ /**
1575
+ * Add the mousemove listener
1576
+ */
1577
+ if (prop['chart.fill.mousemove'] || !RG.is_null(prop['chart.fill.tooltips'])) {
1578
+ ca.addEventListener('mousemove', func, false);
1579
+ }
1580
+ };
1581
+
1582
+
1583
+
1584
+
1585
+ /**
1586
+ * This highlights a specific dataset on the chart
1587
+ *
1588
+ * @param number dataset The index of the dataset (which starts at zero)
1589
+ */
1590
+ this.highlightDataset =
1591
+ this.HighlightDataset = function (dataset)
1592
+ {
1593
+ co.beginPath();
1594
+ for (var j=0; j<this.coords2[dataset].length; ++j) {
1595
+ if (j == 0) {
1596
+ co.moveTo(this.coords2[dataset][0][0], this.coords2[dataset][0][1]);
1597
+ } else {
1598
+ co.lineTo(this.coords2[dataset][j][0], this.coords2[dataset][j][1]);
1599
+ }
1600
+ }
1601
+
1602
+ co.lineTo(this.coords2[dataset][0][0], this.coords2[dataset][0][1]);
1603
+
1604
+ if (prop['chart.accumulative'] && dataset > 0) {
1605
+ co.lineTo(this.coords2[dataset - 1][0][0], this.coords2[dataset - 1][0][1]);
1606
+ for (var j=(this.coords2[dataset - 1].length - 1); j>=0; --j) {
1607
+ co.lineTo(this.coords2[dataset - 1][j][0], this.coords2[dataset - 1][j][1]);
1608
+ }
1609
+ }
1610
+
1611
+ co.strokeStyle = prop['chart.fill.highlight.stroke'];
1612
+ co.fillStyle = prop['chart.fill.highlight.fill'];
1613
+
1614
+ co.stroke();
1615
+ co.fill();
1616
+ };
1617
+
1618
+
1619
+
1620
+
1621
+ /**
1622
+ * Shows a tooltip for a dataset (a "fill" tooltip), You can pecify these
1623
+ * with chart.fill.tooltips
1624
+ */
1625
+ this.datasetTooltip =
1626
+ this.DatasetTooltip = function (e, dataset)
1627
+ {
1628
+ // Highlight the dataset
1629
+ this.HighlightDataset(dataset);
1630
+
1631
+ // Use the First datapoints coords for the Y position of the tooltip NOTE The X position is changed in the
1632
+ // obj.positionTooltip() method so set the index to be the first one
1633
+ var text = prop['chart.fill.tooltips'][dataset];
1634
+ var x = 0;
1635
+ var y = this.coords2[dataset][0][1] + RG.getCanvasXY(ca)[1];
1636
+
1637
+
1638
+ // Show a tooltip
1639
+ RG.Tooltip(this, text, x, y, 0, e);
1640
+ };
1641
+
1642
+
1643
+
1644
+
1645
+ /**
1646
+ * This function handles highlighting an entire data-series for the interactive
1647
+ * key
1648
+ *
1649
+ * @param int index The index of the data series to be highlighted
1650
+ */
1651
+ this.interactiveKeyHighlight = function (index)
1652
+ {
1653
+ var coords = this.coords2[index];
1654
+
1655
+ if (coords) {
1656
+
1657
+ var pre_linewidth = co.lineWidth;
1658
+ var pre_linecap = co.lineCap;
1659
+
1660
+
1661
+
1662
+
1663
+ // ------------------------------------------ //
1664
+
1665
+ co.lineWidth = prop['chart.linewidth'] + 10;
1666
+ co.lineCap = 'round';
1667
+ co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
1668
+
1669
+
1670
+ co.beginPath();
1671
+ for (var i=0,len=coords.length; i<len; i+=1) {
1672
+ if (i == 0) {
1673
+ co.moveTo(coords[i][0], coords[i][1]);
1674
+ } else {
1675
+ co.lineTo(coords[i][0], coords[i][1]);
1676
+ }
1677
+ }
1678
+ co.closePath();
1679
+ co.stroke();
1680
+
1681
+ // ------------------------------------------ //
1682
+
1683
+
1684
+
1685
+
1686
+ // Reset the lineCap and lineWidth
1687
+ co.lineWidth = pre_linewidth;
1688
+ co.lineCap = pre_linecap;
1689
+ }
1690
+ };
1691
+
1692
+
1693
+
1694
+
1695
+ /**
1696
+ * Using a function to add events makes it easier to facilitate method chaining
1697
+ *
1698
+ * @param string type The type of even to add
1699
+ * @param function func
1700
+ */
1701
+ this.on = function (type, func)
1702
+ {
1703
+ if (type.substr(0,2) !== 'on') {
1704
+ type = 'on' + type;
1705
+ }
1706
+
1707
+ this[type] = func;
1708
+
1709
+ return this;
1710
+ };
1711
+
1712
+
1713
+
1714
+
1715
+ /**
1716
+ * This function runs once only
1717
+ * (put at the end of the file (before any effects))
1718
+ */
1719
+ this.firstDrawFunc = function ()
1720
+ {
1721
+ };
1722
+
1723
+
1724
+
1725
+
1726
+ /**
1727
+ * Radar chart grow
1728
+ *
1729
+ * This effect gradually increases the magnitude of the points on the radar chart
1730
+ *
1731
+ * @param object Options for the effect
1732
+ * @param function An optional callback that is run when the effect is finished
1733
+ */
1734
+ this.grow = function ()
1735
+ {
1736
+ var obj = this;
1737
+ var callback = arguments[1] ? arguments[1] : function () {};
1738
+ var opt = arguments[0] ? arguments[0] : {};
1739
+ var frames = opt.frames ? opt.frames : 30;
1740
+ var frame = 0;
1741
+ var data = RG.array_clone(obj.data);
1742
+
1743
+ function iterator ()
1744
+ {
1745
+ for (var i=0,len=data.length; i<len; ++i) {
1746
+
1747
+ //if (obj.original_data[i] == null) {
1748
+ // obj.original_data[i] = [];
1749
+ //}
1750
+
1751
+ for (var j=0,len2=data[i].length; j<len2; ++j) {
1752
+ obj.original_data[i][j] = (frame / frames) * data[i][j];
1753
+ }
1754
+ }
1755
+
1756
+ RGraph.clear(obj.canvas);
1757
+ RGraph.redrawCanvas(obj.canvas);
1758
+
1759
+ if (frame < frames) {
1760
+ frame++;
1761
+ RGraph.Effects.updateCanvas(iterator);
1762
+ } else {
1763
+ callback(obj);
1764
+ }
1765
+ }
1766
+
1767
+ iterator();
1768
+
1769
+ return this;
1770
+ };
1771
+
1772
+
1773
+
1774
+
1775
+ /**
1776
+ * Trace (Radar chart)
1777
+ *
1778
+ * This is a Trace effect for the Radar chart
1779
+ *
1780
+ * @param object Options for the effect. Currently only "frames" is available.
1781
+ * @param function A function that is called when the ffect is complete
1782
+ */
1783
+ this.trace = function ()
1784
+ {
1785
+ var obj = this;
1786
+ var opt = arguments[0] || {};
1787
+ var frames = opt.frames || 60;
1788
+ var frame = 0;
1789
+ var callback = arguments[1] || function () {};
1790
+
1791
+ obj.Set('animation.trace.clip', 0);
1792
+
1793
+
1794
+ var iterator = function ()
1795
+ {
1796
+ if (frame < frames) {
1797
+
1798
+ obj.Set('animation.trace.clip', frame / frames);
1799
+
1800
+ frame++;
1801
+ RG.redrawCanvas(obj.canvas);
1802
+ RG.Effects.updateCanvas(iterator);
1803
+
1804
+ } else {
1805
+
1806
+ obj.Set('animation.trace.clip', 1);
1807
+ RG.redrawCanvas(obj.canvas);
1808
+ callback(obj);
1809
+ }
1810
+ };
1811
+
1812
+ iterator();
1813
+
1814
+ return this;
1815
+ };
1816
+
1817
+
1818
+
1819
+
1820
+ RG.att(ca);
1821
+
1822
+
1823
+
1824
+
1825
+ /**
1826
+ * Always register the object
1827
+ */
1828
+ RG.Register(this);
1829
+
1830
+
1831
+
1832
+
1833
+ /**
1834
+ * This is the 'end' of the constructor so if the first argument
1835
+ * contains configuration data - handle that.
1836
+ */
1837
+ if (parseConfObjectForOptions) {
1838
+ RG.parseObjectStyleConfig(this, conf.options);
1839
+ }
1840
+ };