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,3029 @@
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
+
20
+
21
+ /**
22
+ * The scatter graph constructor
23
+ *
24
+ * @param object canvas The cxanvas object
25
+ * @param array data The chart data
26
+ */
27
+ RGraph.Scatter = function (conf)
28
+ {
29
+ /**
30
+ * Allow for object config style
31
+ */
32
+ if ( typeof conf === 'object'
33
+ && typeof conf.data === 'object'
34
+ && typeof conf.id === 'string') {
35
+
36
+ var parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor)
37
+
38
+ this.data = new Array(conf.data.length);
39
+
40
+ // Store the data set(s)
41
+ this.data = RGraph.arrayClone(conf.data);
42
+
43
+
44
+ // Account for just one dataset being given
45
+ if (typeof conf.data === 'object' && typeof conf.data[0] === 'object' && (typeof conf.data[0][0] === 'number' || typeof conf.data[0][0] === 'string')) {
46
+ var tmp = RGraph.arrayClone(conf.data);
47
+ conf.data = new Array();
48
+ conf.data[0] = RGraph.arrayClone(tmp);
49
+
50
+ this.data = RGraph.arrayClone(conf.data);
51
+ }
52
+
53
+ } else {
54
+
55
+ var conf = {id: conf};
56
+ conf.data = arguments[1];
57
+
58
+
59
+ this.data = [];
60
+
61
+ // Handle multiple datasets being given as one argument
62
+ if (arguments[1][0] && arguments[1][0][0] && typeof arguments[1][0][0] == 'object') {
63
+ // Store the data set(s)
64
+ for (var i=0; i<arguments[1].length; ++i) {
65
+ this.data[i] = RGraph.arrayClone(arguments[1][i]);
66
+ }
67
+
68
+ // Handle multiple data sets being supplied as seperate arguments
69
+ } else {
70
+
71
+ // Store the data set(s)
72
+ for (var i=1; i<arguments.length; ++i) {
73
+ this.data[i - 1] = RGraph.arrayClone(arguments[i]);
74
+ }
75
+ }
76
+ }
77
+
78
+
79
+
80
+
81
+ this.id = conf.id;
82
+ this.canvas = document.getElementById(this.id);
83
+ this.canvas.__object__ = this;
84
+ this.context = this.canvas.getContext ? this.canvas.getContext('2d') : null;
85
+ this.max = 0;
86
+ this.coords = [];
87
+ this.type = 'scatter';
88
+ this.isRGraph = true;
89
+ this.uid = RGraph.CreateUID();
90
+ this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
91
+ this.colorsParsed = false;
92
+ this.coordsText = [];
93
+ this.original_colors = [];
94
+ this.firstDraw = true; // After the first draw this will be false
95
+
96
+
97
+
98
+
99
+ // Handle multiple datasets being given as one argument
100
+ //if (arguments[1][0] && arguments[1][0][0] && typeof(arguments[1][0][0]) == 'object') {
101
+ // // Store the data set(s)
102
+ // for (var i=0; i<arguments[1].length; ++i) {
103
+ // this.data[i] = arguments[1][i];
104
+ // }
105
+
106
+ // Handle multiple data sets being supplied as seperate arguments
107
+ //} else {
108
+ // Store the data set(s)
109
+ //for (var i=1; i<arguments.length; ++i) {
110
+ // this.data[i - 1] = arguments[i];
111
+ //}
112
+ //}
113
+
114
+
115
+ // Various config properties
116
+ this.properties = {
117
+ 'chart.background.barcolor1': 'rgba(0,0,0,0)',
118
+ 'chart.background.barcolor2': 'rgba(0,0,0,0)',
119
+ 'chart.background.grid': true,
120
+ 'chart.background.grid.width': 1,
121
+ 'chart.background.grid.color': '#ddd',
122
+ 'chart.background.grid.hsize': 20,
123
+ 'chart.background.grid.vsize': 20,
124
+ 'chart.background.hbars': null,
125
+ 'chart.background.vbars': null,
126
+ 'chart.background.grid.vlines': true,
127
+ 'chart.background.grid.hlines': true,
128
+ 'chart.background.grid.border': true,
129
+ 'chart.background.grid.autofit':true,
130
+ 'chart.background.grid.autofit.align': true,
131
+ 'chart.background.grid.autofit.numhlines': 5,
132
+ 'chart.background.grid.autofit.numvlines': 20,
133
+ 'chart.background.image': null,
134
+ 'chart.background.image.stretch': true,
135
+ 'chart.background.image.x': null,
136
+ 'chart.background.image.y': null,
137
+ 'chart.background.image.w': null,
138
+ 'chart.background.image.h': null,
139
+ 'chart.background.image.align': null,
140
+ 'chart.background.color': null,
141
+ 'chart.text.size': 12,
142
+ 'chart.text.angle': 0,
143
+ 'chart.text.color': 'black',
144
+ 'chart.text.font': 'Arial',
145
+ 'chart.tooltips': [], // Default must be an empty array
146
+ 'chart.tooltips.effect': 'fade',
147
+ 'chart.tooltips.event': 'onmousemove',
148
+ 'chart.tooltips.hotspot': 3,
149
+ 'chart.tooltips.css.class': 'RGraph_tooltip',
150
+ 'chart.tooltips.highlight': true,
151
+ 'chart.tooltips.coords.page': false,
152
+ 'chart.units.pre': '',
153
+ 'chart.units.post': '',
154
+ 'chart.numyticks': 10,
155
+ 'chart.tickmarks': 'cross',
156
+ 'chart.tickmarks.image.halign': 'center',
157
+ 'chart.tickmarks.image.valign': 'center',
158
+ 'chart.tickmarks.image.offsetx': 0,
159
+ 'chart.tickmarks.image.offsety': 0,
160
+ 'chart.ticksize': 5,
161
+ 'chart.numxticks': true,
162
+ 'chart.xaxis': true,
163
+ 'chart.gutter.left': 25,
164
+ 'chart.gutter.right': 25,
165
+ 'chart.gutter.top': 25,
166
+ 'chart.gutter.bottom': 30,
167
+ 'chart.xmin': 0,
168
+ 'chart.xmax': 0,
169
+ 'chart.ymax': null,
170
+ 'chart.ymin': 0,
171
+ 'chart.scale.decimals': null,
172
+ 'chart.scale.point': '.',
173
+ 'chart.scale.thousand': ',',
174
+ 'chart.title': '',
175
+ 'chart.title.background': null,
176
+ 'chart.title.hpos': null,
177
+ 'chart.title.vpos': null,
178
+ 'chart.title.bold': true,
179
+ 'chart.title.font': null,
180
+ 'chart.title.xaxis': '',
181
+ 'chart.title.xaxis.bold': true,
182
+ 'chart.title.xaxis.size': null,
183
+ 'chart.title.xaxis.font': null,
184
+ 'chart.title.yaxis': '',
185
+ 'chart.title.yaxis.bold': true,
186
+ 'chart.title.yaxis.size': null,
187
+ 'chart.title.yaxis.font': null,
188
+ 'chart.title.yaxis.color': null,
189
+ 'chart.title.xaxis.pos': null,
190
+ 'chart.title.yaxis.pos': null,
191
+ 'chart.title.yaxis.x': null,
192
+ 'chart.title.yaxis.y': null,
193
+ 'chart.title.xaxis.x': null,
194
+ 'chart.title.xaxis.y': null,
195
+ 'chart.title.x': null,
196
+ 'chart.title.y': null,
197
+ 'chart.title.halign': null,
198
+ 'chart.title.valign': null,
199
+ 'chart.labels': [],
200
+ 'chart.labels.bold': false,
201
+ 'chart.labels.color': null,
202
+ 'chart.labels.ingraph': null,
203
+ 'chart.labels.above': false,
204
+ 'chart.labels.above.size': 8,
205
+ 'chart.labels.above.decimals': 0,
206
+ 'chart.ylabels': true,
207
+ 'chart.ylabels.count': 5,
208
+ 'chart.ylabels.invert': false,
209
+ 'chart.ylabels.specific': null,
210
+ 'chart.ylabels.inside': false,
211
+ 'chart.contextmenu': null,
212
+ 'chart.defaultcolor': 'black',
213
+ 'chart.xaxispos': 'bottom',
214
+ 'chart.yaxispos': 'left',
215
+ 'chart.crosshairs': false,
216
+ 'chart.crosshairs.color': '#333',
217
+ 'chart.crosshairs.linewidth': 1,
218
+ 'chart.crosshairs.coords': false,
219
+ 'chart.crosshairs.coords.fixed':true,
220
+ 'chart.crosshairs.coords.fadeout':false,
221
+ 'chart.crosshairs.coords.labels.x': 'X',
222
+ 'chart.crosshairs.coords.labels.y': 'Y',
223
+ 'chart.crosshairs.hline': true,
224
+ 'chart.crosshairs.vline': true,
225
+ 'chart.annotatable': false,
226
+ 'chart.annotate.color': 'black',
227
+ 'chart.line': false,
228
+ 'chart.line.linewidth': 1,
229
+ 'chart.line.colors': ['green', 'red'],
230
+ 'chart.line.shadow.color': 'rgba(0,0,0,0)',
231
+ 'chart.line.shadow.blur': 2,
232
+ 'chart.line.shadow.offsetx': 3,
233
+ 'chart.line.shadow.offsety': 3,
234
+ 'chart.line.stepped': false,
235
+ 'chart.line.visible': true,
236
+ 'chart.noaxes': false,
237
+ 'chart.noyaxis': false,
238
+ 'chart.key': null,
239
+ 'chart.key.background': 'white',
240
+ 'chart.key.position': 'graph',
241
+ 'chart.key.halign': 'right',
242
+ 'chart.key.shadow': false,
243
+ 'chart.key.shadow.color': '#666',
244
+ 'chart.key.shadow.blur': 3,
245
+ 'chart.key.shadow.offsetx': 2,
246
+ 'chart.key.shadow.offsety': 2,
247
+ 'chart.key.position.gutter.boxed': false,
248
+ 'chart.key.position.x': null,
249
+ 'chart.key.position.y': null,
250
+
251
+ 'chart.key.interactive': false,
252
+ 'chart.key.interactive.highlight.chart.fill': 'rgba(255,0,0,0.9)',
253
+ 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
254
+
255
+ 'chart.key.color.shape': 'square',
256
+ 'chart.key.rounded': true,
257
+ 'chart.key.linewidth': 1,
258
+ 'chart.key.colors': null,
259
+ 'chart.key.text.color': 'black',
260
+ 'chart.axis.color': 'black',
261
+ 'chart.zoom.factor': 1.5,
262
+ 'chart.zoom.fade.in': true,
263
+ 'chart.zoom.fade.out': true,
264
+ 'chart.zoom.hdir': 'right',
265
+ 'chart.zoom.vdir': 'down',
266
+ 'chart.zoom.frames': 25,
267
+ 'chart.zoom.delay': 16.666,
268
+ 'chart.zoom.shadow': true,
269
+ 'chart.zoom.background': true,
270
+ 'chart.zoom.action': 'zoom',
271
+ 'chart.boxplot.width': 1,
272
+ 'chart.boxplot.capped': true,
273
+ 'chart.resizable': false,
274
+ 'chart.resize.handle.background': null,
275
+ 'chart.xmin': 0,
276
+ 'chart.labels.specific.align': 'left',
277
+ 'chart.xscale': false,
278
+ 'chart.xscale.units.pre': '',
279
+ 'chart.xscale.units.post': '',
280
+ 'chart.xscale.numlabels': 10,
281
+ 'chart.xscale.formatter': null,
282
+ 'chart.xscale.decimals': 0,
283
+ 'chart.xscale.thousand': ',',
284
+ 'chart.xscale.point': '.',
285
+ 'chart.noendxtick': false,
286
+ 'chart.noendytick': true,
287
+ 'chart.events.mousemove': null,
288
+ 'chart.events.click': null,
289
+ 'chart.highlight.stroke': 'rgba(0,0,0,0)',
290
+ 'chart.highlight.fill': 'rgba(255,255,255,0.7)'
291
+ }
292
+
293
+ /**
294
+ * This allows the data points to be given as dates as well as numbers. Formats supported by RGraph.parseDate() are accepted.
295
+ */
296
+ for (var i=0; i<this.data.length; ++i) {
297
+ for (var j=0; j<this.data[i].length; ++j) {
298
+ if (this.data[i][j] && typeof(this.data[i][j][0]) == 'string') {
299
+ this.data[i][j][0] = RGraph.parseDate(this.data[i][j][0]);
300
+ }
301
+ }
302
+ }
303
+
304
+
305
+ /**
306
+ * Now make the data_arr array - all the data as one big array
307
+ */
308
+ this.data_arr = [];
309
+
310
+ for (var i=0; i<this.data.length; ++i) {
311
+ for (var j=0; j<this.data[i].length; ++j) {
312
+ this.data_arr.push(this.data[i][j]);
313
+ }
314
+ }
315
+
316
+ // Create the $ objects so that they can be used
317
+ for (var i=0; i<this.data_arr.length; ++i) {
318
+ this['$' + i] = {}
319
+ }
320
+
321
+
322
+ // Check for support
323
+ if (!this.canvas) {
324
+ alert('[SCATTER] No canvas support');
325
+ return;
326
+ }
327
+
328
+
329
+ /**
330
+ * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
331
+ * done already
332
+ */
333
+ if (!this.canvas.__rgraph_aa_translated__) {
334
+ this.context.translate(0.5,0.5);
335
+
336
+ this.canvas.__rgraph_aa_translated__ = true;
337
+ }
338
+
339
+
340
+
341
+ // Short variable names
342
+ var RG = RGraph,
343
+ ca = this.canvas,
344
+ co = ca.getContext('2d'),
345
+ prop = this.properties,
346
+ pa = RG.Path,
347
+ pa2 = RG.path2,
348
+ win = window,
349
+ doc = document,
350
+ ma = Math
351
+
352
+
353
+
354
+ /**
355
+ * "Decorate" the object with the generic effects if the effects library has been included
356
+ */
357
+ if (RG.Effects && typeof RG.Effects.decorate === 'function') {
358
+ RG.Effects.decorate(this);
359
+ }
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+ /**
368
+ * A simple setter
369
+ *
370
+ * @param string name The name of the property to set
371
+ * @param string value The value of the property
372
+ */
373
+ this.set =
374
+ this.Set = function (name)
375
+ {
376
+ var value = typeof arguments[1] === 'undefined' ? null : arguments[1];
377
+
378
+ /**
379
+ * the number of arguments is only one and it's an
380
+ * object - parse it for configuration data and return.
381
+ */
382
+ if (arguments.length === 1 && typeof name === 'object') {
383
+ RG.parseObjectStyleConfig(this, name);
384
+ return this;
385
+ }
386
+
387
+
388
+
389
+ /**
390
+ * This should be done first - prepend the propertyy name with "chart." if necessary
391
+ */
392
+ if (name.substr(0,6) != 'chart.') {
393
+ name = 'chart.' + name;
394
+ }
395
+
396
+
397
+
398
+
399
+ // Convert uppercase letters to dot+lower case letter
400
+ name = name.replace(/([A-Z])/g, function (str)
401
+ {
402
+ return '.' + String(RegExp.$1).toLowerCase();
403
+ });
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+ /**
412
+ * BC for chart.xticks
413
+ */
414
+ if (name == 'chart.xticks') {
415
+ name == 'chart.numxticks';
416
+ }
417
+
418
+ /**
419
+ * This is here because the key expects a name of "chart.colors"
420
+ */
421
+ if (name == 'chart.line.colors') {
422
+ prop['chart.colors'] = value;
423
+ }
424
+
425
+ /**
426
+ * Allow compatibility with older property names
427
+ */
428
+ if (name == 'chart.tooltip.hotspot') {
429
+ name = 'chart.tooltips.hotspot';
430
+ }
431
+
432
+
433
+ /**
434
+ * chart.yaxispos should be left or right
435
+ */
436
+ if (name == 'chart.yaxispos' && value != 'left' && value != 'right') {
437
+ alert("[SCATTER] chart.yaxispos should be left or right. You've set it to: '" + value + "' Changing it to left");
438
+ value = 'left';
439
+ }
440
+
441
+ /**
442
+ * Check for xaxispos
443
+ */
444
+ if (name == 'chart.xaxispos' ) {
445
+ if (value != 'bottom' && value != 'center') {
446
+ alert('[SCATTER] (' + this.id + ') chart.xaxispos should be center or bottom. Tried to set it to: ' + value + ' Changing it to center');
447
+ value = 'center';
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Compatibility for chart.noxaxisoption
453
+ */
454
+ if (name == 'chart.noxaxis' ) {
455
+ name = 'chart.xaxis';
456
+ value = !value;
457
+ }
458
+ prop[name.toLowerCase()] = value;
459
+
460
+ return this;
461
+ };
462
+
463
+
464
+
465
+
466
+ /**
467
+ * A simple getter
468
+ *
469
+ * @param string name The name of the property to set
470
+ */
471
+ this.get =
472
+ this.Get = function (name)
473
+ {
474
+ /**
475
+ * This should be done first - prepend the property name with "chart." if necessary
476
+ */
477
+ if (name.substr(0,6) != 'chart.') {
478
+ name = 'chart.' + name;
479
+ }
480
+
481
+ // Convert uppercase letters to dot+lower case letter
482
+ name = name.replace(/([A-Z])/g, function (str)
483
+ {
484
+ return '.' + String(RegExp.$1).toLowerCase()
485
+ });
486
+
487
+ return prop[name];
488
+ };
489
+
490
+
491
+
492
+
493
+ /**
494
+ * The function you call to draw the line chart
495
+ */
496
+ this.draw =
497
+ this.Draw = function ()
498
+ {
499
+ // MUST be the first thing done!
500
+ if (typeof prop['chart.background.image'] === 'string') {
501
+ RG.DrawBackgroundImage(this);
502
+ }
503
+
504
+
505
+ /**
506
+ * Fire the onbeforedraw event
507
+ */
508
+ RG.FireCustomEvent(this, 'onbeforedraw');
509
+
510
+
511
+ /**
512
+ * Parse the colors. This allows for simple gradient syntax
513
+ */
514
+ if (!this.colorsParsed) {
515
+ this.parseColors();
516
+
517
+ // Don't want to do this again
518
+ this.colorsParsed = true;
519
+ }
520
+
521
+
522
+
523
+
524
+ /**
525
+ * Stop this growing uncontrollably
526
+ */
527
+ this.coordsText = [];
528
+
529
+
530
+
531
+
532
+ /**
533
+ * This is new in May 2011 and facilitates indiviual gutter settings,
534
+ * eg chart.gutter.left
535
+ */
536
+ this.gutterLeft = prop['chart.gutter.left'];
537
+ this.gutterRight = prop['chart.gutter.right'];
538
+ this.gutterTop = prop['chart.gutter.top'];
539
+ this.gutterBottom = prop['chart.gutter.bottom'];
540
+
541
+ // Go through all the data points and see if a tooltip has been given
542
+ this.hasTooltips = false;
543
+ var overHotspot = false;
544
+
545
+ // Reset the coords array
546
+ this.coords = [];
547
+
548
+ /**
549
+ * This facilitates the xmax, xmin and X values being dates
550
+ */
551
+ if (typeof(prop['chart.xmin']) == 'string') prop['chart.xmin'] = RG.parseDate(prop['chart.xmin']);
552
+ if (typeof(prop['chart.xmax']) == 'string') prop['chart.xmax'] = RG.parseDate(prop['chart.xmax']);
553
+
554
+
555
+ /**
556
+ * Look for tooltips and populate chart.tooltips
557
+ *
558
+ * NB 26/01/2011 Updated so that chart.tooltips is ALWAYS populated
559
+ */
560
+ if (!RGraph.ISOLD) {
561
+ this.Set('chart.tooltips', []);
562
+ for (var i=0,len=this.data.length; i<len; i+=1) {
563
+ for (var j =0,len2=this.data[i].length;j<len2; j+=1) {
564
+
565
+ if (this.data[i][j] && this.data[i][j][3]) {
566
+ prop['chart.tooltips'].push(this.data[i][j][3]);
567
+ this.hasTooltips = true;
568
+ } else {
569
+ prop['chart.tooltips'].push(null);
570
+ }
571
+ }
572
+ }
573
+ }
574
+
575
+ // Reset the maximum value
576
+ this.max = 0;
577
+
578
+ // Work out the maximum Y value
579
+ //if (prop['chart.ymax'] && prop['chart.ymax'] > 0) {
580
+ if (typeof prop['chart.ymax'] === 'number') {
581
+
582
+ this.max = prop['chart.ymax'];
583
+ this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
584
+
585
+
586
+ this.scale2 = RG.getScale2(this, {
587
+ 'max':this.max,
588
+ 'min':this.min,
589
+ 'strict':true,
590
+ 'scale.thousand':prop['chart.scale.thousand'],
591
+ 'scale.point':prop['chart.scale.point'],
592
+ 'scale.decimals':prop['chart.scale.decimals'],
593
+ 'ylabels.count':prop['chart.ylabels.count'],
594
+ 'scale.round':prop['chart.scale.round'],
595
+ 'units.pre': prop['chart.units.pre'],
596
+ 'units.post': prop['chart.units.post']
597
+ });
598
+
599
+ this.max = this.scale2.max;
600
+ this.min = this.scale2.min;
601
+ var decimals = prop['chart.scale.decimals'];
602
+
603
+ } else {
604
+
605
+ var i = 0;
606
+ var j = 0;
607
+
608
+ for (i=0,len=this.data.length; i<len; i+=1) {
609
+ for (j=0,len2=this.data[i].length; j<len2; j+=1) {
610
+ if (this.data[i][j][1] != null) {
611
+ this.max = Math.max(this.max, typeof(this.data[i][j][1]) == 'object' ? RG.array_max(this.data[i][j][1]) : Math.abs(this.data[i][j][1]));
612
+ }
613
+ }
614
+ }
615
+
616
+ this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
617
+
618
+ this.scale2 = RG.getScale2(this, {
619
+ 'max':this.max,
620
+ 'min':this.min,
621
+ 'scale.thousand':prop['chart.scale.thousand'],
622
+ 'scale.point':prop['chart.scale.point'],
623
+ 'scale.decimals':prop['chart.scale.decimals'],
624
+ 'ylabels.count':prop['chart.ylabels.count'],
625
+ 'scale.round':prop['chart.scale.round'],
626
+ 'units.pre': prop['chart.units.pre'],
627
+ 'units.post': prop['chart.units.post']
628
+ });
629
+
630
+ this.max = this.scale2.max;
631
+ this.min = this.scale2.min;
632
+ }
633
+
634
+ this.grapharea = ca.height - this.gutterTop - this.gutterBottom;
635
+
636
+
637
+
638
+ // Progressively Draw the chart
639
+ RG.background.Draw(this);
640
+
641
+ /**
642
+ * Draw any horizontal bars that have been specified
643
+ */
644
+ if (prop['chart.background.hbars'] && prop['chart.background.hbars'].length) {
645
+ RG.DrawBars(this);
646
+ }
647
+
648
+ /**
649
+ * Draw any vertical bars that have been specified
650
+ */
651
+ if (prop['chart.background.vbars'] && prop['chart.background.vbars'].length) {
652
+ this.DrawVBars();
653
+ }
654
+
655
+ if (!prop['chart.noaxes']) {
656
+ this.DrawAxes();
657
+ }
658
+
659
+ this.DrawLabels();
660
+
661
+ i = 0;
662
+ for(i=0; i<this.data.length; ++i) {
663
+ this.DrawMarks(i);
664
+
665
+ // Set the shadow
666
+ co.shadowColor = prop['chart.line.shadow.color'];
667
+ co.shadowOffsetX = prop['chart.line.shadow.offsetx'];
668
+ co.shadowOffsetY = prop['chart.line.shadow.offsety'];
669
+ co.shadowBlur = prop['chart.line.shadow.blur'];
670
+
671
+ this.DrawLine(i);
672
+
673
+ // Turn the shadow off
674
+ RG.NoShadow(this);
675
+ }
676
+
677
+
678
+ if (prop['chart.line']) {
679
+ for (var i=0,len=this.data.length;i<len; i+=1) {
680
+ this.DrawMarks(i); // Call this again so the tickmarks appear over the line
681
+ }
682
+ }
683
+
684
+
685
+
686
+ /**
687
+ * Setup the context menu if required
688
+ */
689
+ if (prop['chart.contextmenu']) {
690
+ RG.ShowContext(this);
691
+ }
692
+
693
+
694
+
695
+ /**
696
+ * Draw the key if necessary
697
+ */
698
+ if (prop['chart.key'] && prop['chart.key'].length) {
699
+ RG.DrawKey(this, prop['chart.key'], prop['chart.line.colors']);
700
+ }
701
+
702
+
703
+ /**
704
+ * Draw " above" labels if enabled
705
+ */
706
+ if (prop['chart.labels.above']) {
707
+ this.DrawAboveLabels();
708
+ }
709
+
710
+ /**
711
+ * Draw the "in graph" labels, using the member function, NOT the shared function in RGraph.common.core.js
712
+ */
713
+ this.DrawInGraphLabels(this);
714
+
715
+
716
+ /**
717
+ * This function enables resizing
718
+ */
719
+ if (prop['chart.resizable']) {
720
+ RG.AllowResizing(this);
721
+ }
722
+
723
+
724
+ /**
725
+ * This installs the event listeners
726
+ */
727
+ RG.InstallEventListeners(this);
728
+
729
+
730
+ /**
731
+ * Fire the onfirstdraw event
732
+ */
733
+ if (this.firstDraw) {
734
+ RG.fireCustomEvent(this, 'onfirstdraw');
735
+ this.firstDraw = false;
736
+ this.firstDrawFunc();
737
+ }
738
+
739
+
740
+
741
+ /**
742
+ * Fire the RGraph ondraw event
743
+ */
744
+ RG.FireCustomEvent(this, 'ondraw');
745
+
746
+
747
+ return this;
748
+ }
749
+
750
+
751
+
752
+
753
+ /**
754
+ * Draws the axes of the scatter graph
755
+ */
756
+ this.drawAxes =
757
+ this.DrawAxes = function ()
758
+ {
759
+ var graphHeight = ca.height - this.gutterTop - this.gutterBottom;
760
+
761
+ co.beginPath();
762
+ co.strokeStyle = prop['chart.axis.color'];
763
+ co.lineWidth = (prop['chart.axis.linewidth'] || 1) + 0.001; // Strange Chrome bug
764
+
765
+ // Draw the Y axis
766
+ if (prop['chart.noyaxis'] == false) {
767
+ if (prop['chart.yaxispos'] == 'left') {
768
+ co.moveTo(this.gutterLeft, this.gutterTop);
769
+ co.lineTo(this.gutterLeft, ca.height - this.gutterBottom);
770
+ } else {
771
+ co.moveTo(ca.width - this.gutterRight, this.gutterTop);
772
+ co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
773
+ }
774
+ }
775
+
776
+
777
+ // Draw the X axis
778
+ if (prop['chart.xaxis']) {
779
+ if (prop['chart.xaxispos'] == 'center') {
780
+ co.moveTo(this.gutterLeft, Math.round(this.gutterTop + ((ca.height - this.gutterTop - this.gutterBottom) / 2)));
781
+ co.lineTo(ca.width - this.gutterRight, Math.round(this.gutterTop + ((ca.height - this.gutterTop - this.gutterBottom) / 2)));
782
+ } else {
783
+ co.moveTo(this.gutterLeft, ca.height - this.gutterBottom);
784
+ co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
785
+ }
786
+ }
787
+
788
+ // Draw the Y tickmarks
789
+ if (prop['chart.noyaxis'] == false) {
790
+ var numyticks = prop['chart.numyticks'];
791
+
792
+ //for (y=this.gutterTop; y < ca.height - this.gutterBottom + (prop['chart.xaxispos'] == 'center' ? 1 : 0) ; y+=(graphHeight / numyticks)) {
793
+ for (i=0; i<numyticks; ++i) {
794
+
795
+ var y = ((ca.height - this.gutterTop - this.gutterBottom) / numyticks) * i;
796
+ y = y + this.gutterTop;
797
+
798
+ if (prop['chart.xaxispos'] == 'center' && i == (numyticks / 2)) {
799
+ continue;
800
+ }
801
+
802
+ if (prop['chart.yaxispos'] == 'left') {
803
+ co.moveTo(this.gutterLeft, Math.round(y));
804
+ co.lineTo(this.gutterLeft - 3, Math.round(y));
805
+ } else {
806
+ co.moveTo(ca.width - this.gutterRight +3, Math.round(y));
807
+ co.lineTo(ca.width - this.gutterRight, Math.round(y));
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Draw the end Y tickmark if the X axis is in the centre
813
+ */
814
+ if (prop['chart.numyticks'] > 0) {
815
+ if (prop['chart.xaxispos'] == 'center' && prop['chart.yaxispos'] == 'left') {
816
+ co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
817
+ co.lineTo(this.gutterLeft - 3, Math.round(ca.height - this.gutterBottom));
818
+ } else if (prop['chart.xaxispos'] == 'center') {
819
+ co.moveTo(ca.width - this.gutterRight + 3, Math.round(ca.height - this.gutterBottom));
820
+ co.lineTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
821
+ }
822
+ }
823
+
824
+ /**
825
+ * Draw an extra tick if the X axis isn't being shown
826
+ */
827
+ if (prop['chart.xaxis'] == false && prop['chart.yaxispos'] == 'left') {
828
+ co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
829
+ co.lineTo(this.gutterLeft - 3, Math.round(ca.height - this.gutterBottom));
830
+ } else if (prop['chart.xaxis'] == false && prop['chart.yaxispos'] == 'right') {
831
+ co.moveTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
832
+ co.lineTo(ca.width - this.gutterRight + 3, Math.round(ca.height - this.gutterBottom));
833
+ }
834
+ }
835
+
836
+
837
+ /**
838
+ * Draw the X tickmarks
839
+ */
840
+ if (prop['chart.numxticks'] > 0 && prop['chart.xaxis']) {
841
+
842
+ var x = 0;
843
+ var y = (prop['chart.xaxispos'] == 'center') ? this.gutterTop + (this.grapharea / 2) : (ca.height - this.gutterBottom);
844
+ this.xTickGap = (prop['chart.labels'] && prop['chart.labels'].length) ? ((ca.width - this.gutterLeft - this.gutterRight ) / prop['chart.labels'].length) : (ca.width - this.gutterLeft - this.gutterRight) / 10;
845
+
846
+ /**
847
+ * This allows the number of X tickmarks to be specified
848
+ */
849
+ if (typeof(prop['chart.numxticks']) == 'number') {
850
+ this.xTickGap = (ca.width - this.gutterLeft - this.gutterRight) / prop['chart.numxticks'];
851
+ }
852
+
853
+
854
+ for (x=(this.gutterLeft + (prop['chart.yaxispos'] == 'left' && prop['chart.noyaxis'] == false ? this.xTickGap : 0) );
855
+ x <= (ca.width - this.gutterRight - (prop['chart.yaxispos'] == 'left' || prop['chart.noyaxis'] == true ? -1 : 1));
856
+ x += this.xTickGap) {
857
+
858
+ if (prop['chart.yaxispos'] == 'left' && prop['chart.noendxtick'] == true && x == (ca.width - this.gutterRight) ) {
859
+ continue;
860
+ } else if (prop['chart.yaxispos'] == 'right' && prop['chart.noendxtick'] == true && x == this.gutterLeft) {
861
+ continue;
862
+ }
863
+
864
+ co.moveTo(Math.round(x), y - (prop['chart.xaxispos'] == 'center' ? 3 : 0));
865
+ co.lineTo(Math.round(x), y + 3);
866
+ }
867
+
868
+ }
869
+
870
+ co.stroke();
871
+
872
+ /**
873
+ * Reset the linewidth back to one
874
+ */
875
+ co.lineWidth = 1;
876
+ };
877
+
878
+
879
+
880
+
881
+
882
+
883
+
884
+
885
+
886
+
887
+
888
+ /**
889
+ * Draws the labels on the scatter graph
890
+ */
891
+ this.drawLabels =
892
+ this.DrawLabels = function ()
893
+ {
894
+ co.fillStyle = prop['chart.text.color'];
895
+
896
+ var font = prop['chart.text.font'],
897
+ xMin = prop['chart.xmin'],
898
+ xMax = prop['chart.xmax'],
899
+ yMax = this.scale2.max,
900
+ yMin = prop['chart.ymin'] ? prop['chart.ymin'] : 0,
901
+ text_size = prop['chart.text.size'],
902
+ units_pre = prop['chart.units.pre'],
903
+ units_post = prop['chart.units.post'],
904
+ numYLabels = prop['chart.ylabels.count'],
905
+ invert = prop['chart.ylabels.invert'],
906
+ inside = prop['chart.ylabels.inside'],
907
+ context = co,
908
+ canvas = ca,
909
+ boxed = false
910
+
911
+ this.halfTextHeight = text_size / 2;
912
+
913
+
914
+ this.halfGraphHeight = (ca.height - this.gutterTop - this.gutterBottom) / 2;
915
+
916
+ /**
917
+ * Draw the Y yaxis labels, be it at the top or center
918
+ */
919
+ if (prop['chart.ylabels']) {
920
+
921
+ var xPos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
922
+ var align = prop['chart.yaxispos'] == 'right' ? 'left' : 'right';
923
+
924
+ /**
925
+ * Now change the two things above if chart.ylabels.inside is specified
926
+ */
927
+ if (inside) {
928
+ if (prop['chart.yaxispos'] == 'left') {
929
+ xPos = prop['chart.gutter.left'] + 5;
930
+ align = 'left';
931
+ boxed = true;
932
+ } else {
933
+ xPos = ca.width - prop['chart.gutter.right'] - 5;
934
+ align = 'right';
935
+ boxed = true;
936
+ }
937
+ }
938
+
939
+ if (prop['chart.xaxispos'] == 'center') {
940
+
941
+
942
+ /**
943
+ * Specific Y labels
944
+ */
945
+ if (typeof(prop['chart.ylabels.specific']) == 'object' && prop['chart.ylabels.specific'] != null && prop['chart.ylabels.specific'].length) {
946
+
947
+ var labels = prop['chart.ylabels.specific'];
948
+
949
+ if (prop['chart.ymin'] > 0) {
950
+ labels = [];
951
+ for (var i=0; i<(prop['chart.ylabels.specific'].length - 1); ++i) {
952
+ labels.push(prop['chart.ylabels.specific'][i]);
953
+ }
954
+ }
955
+
956
+ for (var i=0; i<labels.length; ++i) {
957
+ var y = this.gutterTop + (i * (this.grapharea / (labels.length * 2) ) );
958
+ RG.Text2(this, {'font':font,
959
+ 'size':text_size,
960
+ 'x':xPos,
961
+ 'y':y,
962
+ 'text':labels[i],
963
+ 'valign':'center',
964
+ 'halign':align,
965
+ 'bounding':boxed,
966
+ 'tag': 'labels.specific'
967
+ });
968
+ }
969
+
970
+ var reversed_labels = RG.array_reverse(labels);
971
+
972
+ for (var i=0; i<reversed_labels.length; ++i) {
973
+ var y = this.gutterTop + (this.grapharea / 2) + ((i+1) * (this.grapharea / (labels.length * 2) ) );
974
+ RG.Text2(this, {'font':font,
975
+ 'size':text_size,
976
+ 'x':xPos,
977
+ 'y':y,
978
+ 'text':reversed_labels[i],
979
+ 'valign':'center',
980
+ 'halign':align,
981
+ 'bounding':boxed,
982
+ 'tag': 'labels.specific'
983
+ });
984
+ }
985
+
986
+ /**
987
+ * Draw the center label if chart.ymin is specified
988
+ */
989
+ if (prop['chart.ymin'] != 0) {
990
+ RG.Text2(this, {'font':font,
991
+ 'size':text_size,
992
+ 'x':xPos,
993
+ 'y':(this.grapharea / 2) + this.gutterTop,
994
+ 'text':prop['chart.ylabels.specific'][prop['chart.ylabels.specific'].length - 1],
995
+ 'valign':'center',
996
+ 'halign':align,
997
+ 'bounding':boxed,
998
+ 'tag': 'labels.specific'
999
+ });
1000
+ }
1001
+ }
1002
+
1003
+
1004
+ if (!prop['chart.ylabels.specific'] && typeof numYLabels == 'number') {
1005
+
1006
+ /**
1007
+ * Draw the top half
1008
+ */
1009
+ for (var i=0,len=this.scale2.labels.length; i<len; i+=1) {
1010
+
1011
+ //var value = ((this.max - this.min)/ numYLabels) * (i+1);
1012
+ //value = (invert ? this.max - value : value);
1013
+ //if (!invert) value += this.min;
1014
+ //value = value.toFixed(prop['chart.scale.decimals']);
1015
+
1016
+ if (!invert) {
1017
+ RG.Text2(this, {'font':font,
1018
+ 'size': text_size,
1019
+ 'x': xPos,
1020
+ 'y': this.gutterTop + this.halfGraphHeight - (((i + 1)/numYLabels) * this.halfGraphHeight),
1021
+ 'valign': 'center',
1022
+ 'halign':align,
1023
+ 'bounding': boxed,
1024
+ 'boundingFill': 'white',
1025
+ 'text': this.scale2.labels[i],
1026
+ 'tag': 'scale'
1027
+ });
1028
+ } else {
1029
+ RG.Text2(this, {'font':font,
1030
+ 'size': text_size,
1031
+ 'x': xPos,
1032
+ 'y': this.gutterTop + this.halfGraphHeight - ((i/numYLabels) * this.halfGraphHeight),
1033
+ 'valign': 'center',
1034
+ 'halign':align,
1035
+ 'bounding': boxed,
1036
+ 'boundingFill': 'white',
1037
+ 'text': this.scale2.labels[this.scale2.labels.length - (i + 1)],
1038
+ 'tag': 'scale'
1039
+ });
1040
+ }
1041
+ }
1042
+
1043
+ /**
1044
+ * Draw the bottom half
1045
+ */
1046
+ for (var i=0,len=this.scale2.labels.length; i<len; i+=1) {
1047
+
1048
+ //var value = (((this.max - this.min)/ numYLabels) * i) + this.min;
1049
+ // value = (invert ? value : this.max - (value - this.min)).toFixed(prop['chart.scale.decimals']);
1050
+
1051
+ if (!invert) {
1052
+ RG.Text2(this, {'font':font,
1053
+ 'size': text_size,
1054
+ 'x': xPos,
1055
+ 'y': this.gutterTop + this.halfGraphHeight + this.halfGraphHeight - ((i/numYLabels) * this.halfGraphHeight),
1056
+ 'valign': 'center',
1057
+ 'halign':align,
1058
+ 'bounding': boxed,
1059
+ 'boundingFill': 'white',
1060
+ 'text': '-' + this.scale2.labels[len - (i+1)],
1061
+ 'tag': 'scale'
1062
+ });
1063
+ } else {
1064
+
1065
+ // This ensures that the center label isn't drawn twice
1066
+ if (i == (len - 1)&& invert) {
1067
+ continue;
1068
+ }
1069
+
1070
+ RG.Text2(this, {'font':font,
1071
+ 'size': text_size,
1072
+ 'x': xPos,
1073
+ 'y': this.gutterTop + this.halfGraphHeight + this.halfGraphHeight - (((i + 1)/numYLabels) * this.halfGraphHeight),
1074
+ 'valign': 'center',
1075
+ 'halign':align,
1076
+ 'bounding': boxed,
1077
+ 'boundingFill': 'white',
1078
+ 'text': '-' + this.scale2.labels[i],
1079
+ 'tag': 'scale'
1080
+ });
1081
+ }
1082
+ }
1083
+
1084
+
1085
+
1086
+
1087
+ // If ymin is specified draw that
1088
+ if (!invert && yMin > 0) {
1089
+ RG.Text2(this, {'font':font,
1090
+ 'size': text_size,
1091
+ 'x': xPos,
1092
+ 'y': this.gutterTop + this.halfGraphHeight,
1093
+ 'valign': 'center',
1094
+ 'halign':align,
1095
+ 'bounding': boxed,
1096
+ 'boundingFill': 'white',
1097
+ 'text': RG.number_format(this, yMin.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1098
+ 'tag': 'scale'
1099
+ });
1100
+ }
1101
+
1102
+ if (invert) {
1103
+ RG.Text2(this, {'font':font,
1104
+ 'size': text_size,
1105
+ 'x': xPos,
1106
+ 'y': this.gutterTop,
1107
+ 'valign': 'center',
1108
+ 'halign':align,
1109
+ 'bounding': boxed,
1110
+ 'boundingFill': 'white',
1111
+ 'text': RG.number_format(this, yMin.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1112
+ 'tag': 'scale'
1113
+ });
1114
+ RG.Text2(this, {'font':font,
1115
+ 'size': text_size,
1116
+ 'x': xPos,
1117
+ 'y': this.gutterTop + (this.halfGraphHeight * 2),
1118
+ 'valign': 'center',
1119
+ 'halign':align,
1120
+ 'bounding': boxed,
1121
+ 'boundingFill': 'white',
1122
+ 'text': '-' + RG.number_format(this, yMin.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1123
+ 'tag': 'scale'
1124
+ });
1125
+ }
1126
+ }
1127
+
1128
+ // X axis at the bottom
1129
+ } else {
1130
+
1131
+ var xPos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
1132
+ var align = prop['chart.yaxispos'] == 'right' ? 'left' : 'right';
1133
+
1134
+ if (inside) {
1135
+ if (prop['chart.yaxispos'] == 'left') {
1136
+ xPos = prop['chart.gutter.left'] + 5;
1137
+ align = 'left';
1138
+ boxed = true;
1139
+ } else {
1140
+ xPos = ca.width - obj.gutterRight - 5;
1141
+ align = 'right';
1142
+ boxed = true;
1143
+ }
1144
+ }
1145
+
1146
+ /**
1147
+ * Specific Y labels
1148
+ */
1149
+ if (typeof prop['chart.ylabels.specific'] == 'object' && prop['chart.ylabels.specific']) {
1150
+
1151
+ var labels = prop['chart.ylabels.specific'];
1152
+
1153
+ // Lose the last label
1154
+ if (prop['chart.ymin'] > 9999) {
1155
+ labels = [];
1156
+ for (var i=0; i<(prop['chart.ylabels.specific'].length - 1); ++i) {
1157
+ labels.push(prop['chart.ylabels.specific'][i]);
1158
+ }
1159
+ }
1160
+
1161
+ for (var i=0,len=labels.length; i<len; i+=1) {
1162
+
1163
+ var y = this.gutterTop + (i * (this.grapharea / (len - 1)) );
1164
+
1165
+ RG.Text2(this, {'font':font,
1166
+ 'size':text_size,
1167
+ 'x':xPos,
1168
+ 'y':y,
1169
+ 'text':labels[i],
1170
+ 'halign':align,
1171
+ 'valign':'center',
1172
+ 'bounding':boxed,
1173
+ 'tag': 'scale'
1174
+ });
1175
+ }
1176
+
1177
+ /**
1178
+ * X axis at the bottom
1179
+ */
1180
+ } else {
1181
+
1182
+ if (typeof(numYLabels) == 'number') {
1183
+
1184
+ if (invert) {
1185
+
1186
+ for (var i=0; i<numYLabels; ++i) {
1187
+
1188
+ //var value = ((this.max - this.min)/ numYLabels) * i;
1189
+ // value = value.toFixed(prop['chart.scale.decimals']);
1190
+ var interval = (ca.height - this.gutterTop - this.gutterBottom) / numYLabels;
1191
+
1192
+ RG.Text2(this, {'font':font,
1193
+ 'size': text_size,
1194
+ 'x': xPos,
1195
+ 'y': this.gutterTop + ((i+1) * interval),
1196
+ 'valign': 'center',
1197
+ 'halign':align,
1198
+ 'bounding': boxed,
1199
+ 'boundingFill': 'white',
1200
+ 'text': this.scale2.labels[i],
1201
+ 'tag': 'scale'
1202
+ });
1203
+ }
1204
+
1205
+
1206
+ // No X axis being shown and there's no ymin. If ymin IS set its added further down
1207
+ if (!prop['chart.xaxis'] && !prop['chart.ymin']) {
1208
+ RG.Text2(this, {'font':font,
1209
+ 'size': text_size,
1210
+ 'x': xPos,
1211
+ 'y': this.gutterTop,
1212
+ 'valign': 'center',
1213
+ 'halign':align,
1214
+ 'bounding': boxed,
1215
+ 'boundingFill': 'white',
1216
+ 'text': RG.number_format(this, (this.min).toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1217
+ 'tag': 'scale'
1218
+ });
1219
+ }
1220
+
1221
+ } else {
1222
+ for (var i=0,len=this.scale2.labels.length; i<len; i+=1) {
1223
+
1224
+ //var value = ((this.max - this.min)/ numYLabels) * (i+1);
1225
+ // value = (invert ? this.max - value : value);
1226
+ // if (!invert) value += this.min;
1227
+ // value = value.toFixed(prop['chart.scale.decimals']);
1228
+
1229
+ RG.Text2(this, {'font':font,
1230
+ 'size': text_size,
1231
+ 'x': xPos,
1232
+ 'y': this.gutterTop + this.grapharea - (((i + 1)/this.scale2.labels.length) * this.grapharea),
1233
+ 'valign': 'center',
1234
+ 'halign':align,
1235
+ 'bounding': boxed,
1236
+ 'boundingFill': 'white',
1237
+ 'text': this.scale2.labels[i],
1238
+ 'tag': 'scale'
1239
+ });
1240
+ }
1241
+
1242
+ if (!prop['chart.xaxis'] && prop['chart.ymin'] == 0) {
1243
+ RG.Text2(this, {'font':font,
1244
+ 'size': text_size,
1245
+ 'x': xPos,
1246
+ 'y': ca.height - this.gutterBottom,
1247
+ 'valign': 'center',
1248
+ 'halign':align,
1249
+ 'bounding': boxed,
1250
+ 'boundingFill': 'white',
1251
+ 'text': RG.number_format(this, (0).toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1252
+ 'tag': 'scale'
1253
+ });
1254
+ }
1255
+ }
1256
+ }
1257
+
1258
+ if (prop['chart.ymin'] && !invert) {
1259
+ RG.Text2(this, {'font':font,
1260
+ 'size': text_size,
1261
+ 'x': xPos,
1262
+ 'y': ca.height - this.gutterBottom,
1263
+ 'valign': 'center',
1264
+ 'halign':align,
1265
+ 'bounding': boxed,
1266
+ 'boundingFill': 'white',
1267
+ 'text': RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1268
+ 'tag': 'scale'
1269
+ });
1270
+ } else if (invert) {
1271
+ RG.Text2(this, {'font':font,
1272
+ 'size': text_size,
1273
+ 'x': xPos,
1274
+ 'y': this.gutterTop,
1275
+ 'valign': 'center',
1276
+ 'halign':align,
1277
+ 'bounding': boxed,
1278
+ 'boundingFill': 'white',
1279
+ 'text': RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1280
+ 'tag': 'scale'
1281
+ });
1282
+
1283
+ }
1284
+ }
1285
+ }
1286
+ }
1287
+
1288
+
1289
+
1290
+
1291
+ /**
1292
+ * Draw an X scale
1293
+ */
1294
+ if (prop['chart.xscale']) {
1295
+
1296
+ var numXLabels = prop['chart.xscale.numlabels'],
1297
+ y = ca.height - this.gutterBottom + 5 + (text_size / 2),
1298
+ units_pre_x = prop['chart.xscale.units.pre'],
1299
+ units_post_x = prop['chart.xscale.units.post'],
1300
+ decimals = prop['chart.xscale.decimals'],
1301
+ point = prop['chart.xscale.point'],
1302
+ thousand = prop['chart.xscale.thousand'],
1303
+ color = prop['chart.labels.color'],
1304
+ bold = prop['chart.labels.bold']
1305
+
1306
+
1307
+ if (!prop['chart.xmax']) {
1308
+
1309
+ var xmax = 0;
1310
+ var xmin = prop['chart.xmin'];
1311
+
1312
+ for (var ds=0,len=this.data.length; ds<len; ds+=1) {
1313
+ for (var point=0,len2=this.data[ds].length; point<len2; point+=1) {
1314
+ xmax = Math.max(xmax, this.data[ds][point][0]);
1315
+ }
1316
+ }
1317
+ } else {
1318
+ xmax = prop['chart.xmax'];
1319
+ xmin = prop['chart.xmin']
1320
+ }
1321
+
1322
+ this.xscale2 = RG.getScale2(this, {
1323
+ 'max':xmax,
1324
+ 'min': xmin,
1325
+ 'scale.decimals': decimals,
1326
+ 'scale.point': point,
1327
+ 'scale.thousand': thousand,
1328
+ 'units.pre': units_pre_x,
1329
+ 'units.post': units_post_x,
1330
+ 'ylabels.count': numXLabels,
1331
+ 'strict': true
1332
+ });
1333
+
1334
+ this.Set('chart.xmax', this.xscale2.max);
1335
+ var interval = (ca.width - this.gutterLeft - this.gutterRight) / this.xscale2.labels.length;
1336
+
1337
+ for (var i=0,len=this.xscale2.labels.length; i<len; i+=1) {
1338
+
1339
+ var num = ( (prop['chart.xmax'] - prop['chart.xmin']) * ((i+1) / numXLabels)) + (xmin || 0);
1340
+ var x = this.gutterLeft + ((i+1) * interval);
1341
+
1342
+ if (typeof(prop['chart.xscale.formatter']) == 'function') {
1343
+ var text = String(prop['chart.xscale.formatter'](this, num));
1344
+
1345
+ } else {
1346
+
1347
+ var text = this.xscale2.labels[i]
1348
+ }
1349
+
1350
+ RG.text2(this, {
1351
+ 'color': color,
1352
+ 'font':font,
1353
+ 'size': text_size,
1354
+ 'bold': bold,
1355
+ 'x': x,
1356
+ 'y': y,
1357
+ 'valign': 'center',
1358
+ 'halign':'center',
1359
+ 'text':text,
1360
+ 'tag': 'xscale'
1361
+ });
1362
+ }
1363
+
1364
+ // If the Y axis is on the right hand side - draw the left most X label
1365
+ // ** Always added now **
1366
+ RG.text2(this, {
1367
+ 'color': color,
1368
+ 'font':font,
1369
+ 'size': text_size,
1370
+ 'bold':bold,
1371
+ 'x': this.gutterLeft,
1372
+ 'y': y,
1373
+ 'valign': 'center',
1374
+ 'halign':'center',
1375
+ 'text':String(prop['chart.xmin']),
1376
+ 'tag': 'xscale'
1377
+ });
1378
+
1379
+ /**
1380
+ * Draw X labels
1381
+ */
1382
+ } else {
1383
+
1384
+ // Put the text on the X axis
1385
+ var graphArea = ca.width - this.gutterLeft - this.gutterRight;
1386
+ var xInterval = graphArea / prop['chart.labels'].length;
1387
+ var xPos = this.gutterLeft;
1388
+ var yPos = (ca.height - this.gutterBottom) + 3;
1389
+ var labels = prop['chart.labels'];
1390
+ var color = prop['chart.labels.color'];
1391
+ var bold = prop['chart.labels.bold'];
1392
+
1393
+ /**
1394
+ * Text angle
1395
+ */
1396
+ var angle = 0;
1397
+ var valign = 'top';
1398
+ var halign = 'center';
1399
+
1400
+ if (prop['chart.text.angle'] > 0) {
1401
+ angle = -1 * prop['chart.text.angle'];
1402
+ valign = 'center';
1403
+ halign = 'right';
1404
+ yPos += 10;
1405
+ }
1406
+
1407
+ for (i=0; i<labels.length; ++i) {
1408
+
1409
+ if (typeof(labels[i]) == 'object') {
1410
+
1411
+ if (prop['chart.labels.specific.align'] == 'center') {
1412
+ var rightEdge = 0;
1413
+
1414
+ if (labels[i+1] && labels[i+1][1]) {
1415
+ rightEdge = labels[i+1][1];
1416
+ } else {
1417
+ rightEdge = prop['chart.xmax'];
1418
+ }
1419
+
1420
+ var offset = (this.getXCoord(rightEdge) - this.getXCoord(labels[i][1])) / 2;
1421
+
1422
+ } else {
1423
+ var offset = 5;
1424
+ }
1425
+
1426
+
1427
+ RG.text2(this, {
1428
+ 'color': color,
1429
+ 'font':font,
1430
+ 'size': prop['chart.text.size'],
1431
+ 'bold': bold,
1432
+ 'x': this.getXCoord(labels[i][1]) + offset,
1433
+ 'y': yPos,
1434
+ 'valign': valign,
1435
+ 'halign':angle != 0 ? 'right' : (prop['chart.labels.specific.align'] == 'center' ? 'center' : 'left'),
1436
+ 'text':String(labels[i][0]),
1437
+ 'angle':angle,
1438
+ 'marker':false,
1439
+ 'tag': 'labels.specific'
1440
+ });
1441
+
1442
+ /**
1443
+ * Draw the gray indicator line
1444
+ */
1445
+ co.beginPath();
1446
+ co.strokeStyle = '#bbb';
1447
+ co.moveTo(Math.round(this.gutterLeft + (graphArea * ((labels[i][1] - xMin)/ (prop['chart.xmax'] - xMin)))), ca.height - this.gutterBottom);
1448
+ co.lineTo(Math.round(this.gutterLeft + (graphArea * ((labels[i][1] - xMin)/ (prop['chart.xmax'] - xMin)))), ca.height - this.gutterBottom + 20);
1449
+ co.stroke();
1450
+
1451
+ } else {
1452
+
1453
+ RG.Text2(this, {
1454
+ 'color': color,
1455
+ 'font':font,
1456
+ 'size': prop['chart.text.size'],
1457
+ 'bold': bold,
1458
+ 'x': xPos + (xInterval / 2),
1459
+ 'y': yPos,
1460
+ 'valign': valign,
1461
+ 'halign':halign,
1462
+ 'text':String(labels[i]),
1463
+ 'angle':angle,
1464
+ 'tag': 'labels'
1465
+ });
1466
+ }
1467
+
1468
+ // Do this for the next time around
1469
+ xPos += xInterval;
1470
+ }
1471
+
1472
+ /**
1473
+ * Draw the final indicator line
1474
+ */
1475
+ if (typeof(labels[0]) == 'object') {
1476
+ co.beginPath();
1477
+ co.strokeStyle = '#bbb';
1478
+ co.moveTo(this.gutterLeft + graphArea, ca.height - this.gutterBottom);
1479
+ co.lineTo(this.gutterLeft + graphArea, ca.height - this.gutterBottom + 20);
1480
+ co.stroke();
1481
+ }
1482
+ }
1483
+ };
1484
+
1485
+
1486
+
1487
+
1488
+
1489
+
1490
+
1491
+
1492
+
1493
+
1494
+
1495
+
1496
+
1497
+
1498
+ /**
1499
+ * Draws the actual scatter graph marks
1500
+ *
1501
+ * @param i integer The dataset index
1502
+ */
1503
+ this.drawMarks =
1504
+ this.DrawMarks = function (i)
1505
+ {
1506
+ /**
1507
+ * Reset the coords array
1508
+ */
1509
+ this.coords[i] = [];
1510
+
1511
+ /**
1512
+ * Plot the values
1513
+ */
1514
+ var xmax = prop['chart.xmax'];
1515
+ var default_color = prop['chart.defaultcolor'];
1516
+
1517
+ for (var j=0,len=this.data[i].length; j<len; j+=1) {
1518
+ /**
1519
+ * This is here because tooltips are optional
1520
+ */
1521
+ var data_point = this.data[i];
1522
+
1523
+ var xCoord = data_point[j][0];
1524
+ var yCoord = data_point[j][1];
1525
+ var color = data_point[j][2] ? data_point[j][2] : default_color;
1526
+ var tooltip = (data_point[j] && data_point[j][3]) ? data_point[j][3] : null;
1527
+
1528
+
1529
+ this.DrawMark(
1530
+ i,
1531
+ xCoord,
1532
+ yCoord,
1533
+ xmax,
1534
+ this.scale2.max,
1535
+ color,
1536
+ tooltip,
1537
+ this.coords[i],
1538
+ data_point,
1539
+ j
1540
+ );
1541
+ }
1542
+ };
1543
+
1544
+
1545
+
1546
+
1547
+ /**
1548
+ * Draws a single scatter mark
1549
+ */
1550
+ this.drawMark =
1551
+ this.DrawMark = function (data_set_index, x, y, xMax, yMax, color, tooltip, coords, data, data_index)
1552
+ {
1553
+ var tickmarks = prop['chart.tickmarks'];
1554
+ var tickSize = prop['chart.ticksize'];
1555
+ var xMin = prop['chart.xmin'];
1556
+ var x = ((x - xMin) / (xMax - xMin)) * (ca.width - this.gutterLeft - this.gutterRight);
1557
+ var originalX = x;
1558
+ var originalY = y;
1559
+
1560
+ /**
1561
+ * This allows chart.tickmarks to be an array
1562
+ */
1563
+ if (tickmarks && typeof(tickmarks) == 'object') {
1564
+ tickmarks = tickmarks[data_set_index];
1565
+ }
1566
+
1567
+
1568
+ /**
1569
+ * This allows chart.ticksize to be an array
1570
+ */
1571
+ if (typeof(tickSize) == 'object') {
1572
+ var tickSize = tickSize[data_set_index];
1573
+ var halfTickSize = tickSize / 2;
1574
+ } else {
1575
+ var halfTickSize = tickSize / 2;
1576
+ }
1577
+
1578
+
1579
+ /**
1580
+ * This bit is for boxplots only
1581
+ */
1582
+ if ( y
1583
+ && typeof(y) == 'object'
1584
+ && typeof(y[0]) == 'number'
1585
+ && typeof(y[1]) == 'number'
1586
+ && typeof(y[2]) == 'number'
1587
+ && typeof(y[3]) == 'number'
1588
+ && typeof(y[4]) == 'number'
1589
+ ) {
1590
+
1591
+ //var yMin = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
1592
+ this.Set('chart.boxplot', true);
1593
+ //this.graphheight = ca.height - this.gutterTop - this.gutterBottom;
1594
+
1595
+ //if (prop['chart.xaxispos'] == 'center') {
1596
+ // this.graphheight /= 2;
1597
+ //}
1598
+
1599
+
1600
+ var y0 = this.getYCoord(y[0]);//(this.graphheight) - ((y[4] - yMin) / (yMax - yMin)) * (this.graphheight);
1601
+ var y1 = this.getYCoord(y[1]);//(this.graphheight) - ((y[3] - yMin) / (yMax - yMin)) * (this.graphheight);
1602
+ var y2 = this.getYCoord(y[2]);//(this.graphheight) - ((y[2] - yMin) / (yMax - yMin)) * (this.graphheight);
1603
+ var y3 = this.getYCoord(y[3]);//(this.graphheight) - ((y[1] - yMin) / (yMax - yMin)) * (this.graphheight);
1604
+ var y4 = this.getYCoord(y[4]);//(this.graphheight) - ((y[0] - yMin) / (yMax - yMin)) * (this.graphheight);
1605
+
1606
+
1607
+ var col1 = y[5];
1608
+ var col2 = y[6];
1609
+
1610
+ var boxWidth = typeof(y[7]) == 'number' ? y[7] : prop['chart.boxplot.width'];
1611
+
1612
+ //var y = this.graphheight - y2;
1613
+
1614
+ } else {
1615
+
1616
+ /**
1617
+ * The new way of getting the Y coord. This function (should) handle everything
1618
+ */
1619
+ var yCoord = this.getYCoord(y);
1620
+ }
1621
+
1622
+ //if (prop['chart.xaxispos'] == 'center'] {
1623
+ // y /= 2;
1624
+ // y += this.halfGraphHeight;
1625
+ //
1626
+ // if (prop['chart.ylabels.invert']) {
1627
+ // p(y)
1628
+ // }
1629
+ //}
1630
+
1631
+ /**
1632
+ * Account for the X axis being at the centre
1633
+ */
1634
+ // This is so that points are on the graph, and not the gutter - which helps
1635
+ x += this.gutterLeft;
1636
+ //y = ca.height - this.gutterBottom - y;
1637
+
1638
+
1639
+
1640
+
1641
+ co.beginPath();
1642
+
1643
+ // Color
1644
+ co.strokeStyle = color;
1645
+
1646
+
1647
+
1648
+ /**
1649
+ * Boxplots
1650
+ */
1651
+ if (prop['chart.boxplot']) {
1652
+
1653
+ // boxWidth is a scale value, so convert it to a pixel vlue
1654
+ boxWidth = (boxWidth / prop['chart.xmax']) * (ca.width -this.gutterLeft - this.gutterRight);
1655
+
1656
+ var halfBoxWidth = boxWidth / 2;
1657
+
1658
+ if (prop['chart.line.visible']) {
1659
+ co.beginPath();
1660
+ co.strokeRect(x - halfBoxWidth, y1, boxWidth, y3 - y1);
1661
+
1662
+ // Draw the upper coloured box if a value is specified
1663
+ if (col1) {
1664
+ co.fillStyle = col1;
1665
+ co.fillRect(x - halfBoxWidth, y1, boxWidth, y2 - y1);
1666
+ }
1667
+
1668
+ // Draw the lower coloured box if a value is specified
1669
+ if (col2) {
1670
+ co.fillStyle = col2;
1671
+ co.fillRect(x - halfBoxWidth, y2, boxWidth, y3 - y2);
1672
+ }
1673
+ co.stroke();
1674
+
1675
+ // Now draw the whiskers
1676
+ co.beginPath();
1677
+ if (prop['chart.boxplot.capped']) {
1678
+ co.moveTo(x - halfBoxWidth, Math.round(y0));
1679
+ co.lineTo(x + halfBoxWidth, Math.round(y0));
1680
+ }
1681
+
1682
+ co.moveTo(Math.round(x), y0);
1683
+ co.lineTo(Math.round(x), y1);
1684
+
1685
+ if (prop['chart.boxplot.capped']) {
1686
+ co.moveTo(x - halfBoxWidth, Math.round(y4));
1687
+ co.lineTo(x + halfBoxWidth, Math.round(y4));
1688
+ }
1689
+
1690
+ co.moveTo(Math.round(x), y4);
1691
+ co.lineTo(Math.round(x), y3);
1692
+
1693
+ co.stroke();
1694
+ }
1695
+ }
1696
+
1697
+
1698
+ /**
1699
+ * Draw the tickmark, but not for boxplots
1700
+ */
1701
+ if (prop['chart.line.visible'] && typeof(y) == 'number' && !y0 && !y1 && !y2 && !y3 && !y4) {
1702
+
1703
+ if (tickmarks == 'circle') {
1704
+ co.arc(x, yCoord, halfTickSize, 0, 6.28, 0);
1705
+ co.fillStyle = color;
1706
+ co.fill();
1707
+
1708
+ } else if (tickmarks == 'plus') {
1709
+
1710
+ co.moveTo(x, yCoord - halfTickSize);
1711
+ co.lineTo(x, yCoord + halfTickSize);
1712
+ co.moveTo(x - halfTickSize, yCoord);
1713
+ co.lineTo(x + halfTickSize, yCoord);
1714
+ co.stroke();
1715
+
1716
+ } else if (tickmarks == 'square') {
1717
+ co.strokeStyle = color;
1718
+ co.fillStyle = color;
1719
+ co.fillRect(
1720
+ x - halfTickSize,
1721
+ yCoord - halfTickSize,
1722
+ tickSize,
1723
+ tickSize
1724
+ );
1725
+ //co.fill();
1726
+
1727
+ } else if (tickmarks == 'cross') {
1728
+
1729
+ co.moveTo(x - halfTickSize, yCoord - halfTickSize);
1730
+ co.lineTo(x + halfTickSize, yCoord + halfTickSize);
1731
+ co.moveTo(x + halfTickSize, yCoord - halfTickSize);
1732
+ co.lineTo(x - halfTickSize, yCoord + halfTickSize);
1733
+
1734
+ co.stroke();
1735
+
1736
+ /**
1737
+ * Diamond shape tickmarks
1738
+ */
1739
+ } else if (tickmarks == 'diamond') {
1740
+ co.fillStyle = co.strokeStyle;
1741
+
1742
+ co.moveTo(x, yCoord - halfTickSize);
1743
+ co.lineTo(x + halfTickSize, yCoord);
1744
+ co.lineTo(x, yCoord + halfTickSize);
1745
+ co.lineTo(x - halfTickSize, yCoord);
1746
+ co.lineTo(x, yCoord - halfTickSize);
1747
+
1748
+ co.fill();
1749
+ co.stroke();
1750
+
1751
+ /**
1752
+ * Custom tickmark style
1753
+ */
1754
+ } else if (typeof(tickmarks) == 'function') {
1755
+
1756
+ var graphWidth = ca.width - this.gutterLeft - this.gutterRight
1757
+ var graphheight = ca.height - this.gutterTop - this.gutterBottom;
1758
+ var xVal = ((x - this.gutterLeft) / graphWidth) * xMax;
1759
+ var yVal = ((graphheight - (yCoord - this.gutterTop)) / graphheight) * yMax;
1760
+
1761
+ tickmarks(this, data, x, yCoord, xVal, yVal, xMax, yMax, color, data_set_index, data_index)
1762
+
1763
+
1764
+
1765
+
1766
+
1767
+
1768
+
1769
+
1770
+
1771
+
1772
+
1773
+
1774
+
1775
+
1776
+
1777
+
1778
+
1779
+ /**
1780
+ * Image based tickmark
1781
+ */
1782
+ // lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index
1783
+ } else if (
1784
+ typeof tickmarks === 'string' &&
1785
+ (
1786
+ tickmarks.substr(0, 6) === 'image:' ||
1787
+ tickmarks.substr(0, 5) === 'data:' ||
1788
+ tickmarks.substr(0, 1) === '/' ||
1789
+ tickmarks.substr(0, 3) === '../' ||
1790
+ tickmarks.substr(0, 7) === 'images/'
1791
+ )
1792
+ ) {
1793
+
1794
+ var img = new Image();
1795
+
1796
+ if (tickmarks.substr(0, 6) === 'image:') {
1797
+ img.src = tickmarks.substr(6);
1798
+ } else {
1799
+ img.src = tickmarks;
1800
+ }
1801
+
1802
+
1803
+ img.onload = function ()
1804
+ {
1805
+ if (prop['chart.tickmarks.image.halign'] === 'center') x -= (this.width / 2);
1806
+ if (prop['chart.tickmarks.image.halign'] === 'right') x -= this.width;
1807
+
1808
+ if (prop['chart.tickmarks.image.valign'] === 'center') yCoord -= (this.height / 2);
1809
+ if (prop['chart.tickmarks.image.valign'] === 'bottom') yCoord -= this.height;
1810
+
1811
+ x += prop['chart.tickmarks.image.offsetx'];
1812
+ yCoord += prop['chart.tickmarks.image.offsety'];
1813
+
1814
+ co.drawImage(this, x, yCoord);
1815
+ }
1816
+
1817
+
1818
+
1819
+
1820
+
1821
+ /**
1822
+ * No tickmarks
1823
+ */
1824
+ } else if (tickmarks === null) {
1825
+
1826
+ /**
1827
+ * Unknown tickmark type
1828
+ */
1829
+ } else {
1830
+ alert('[SCATTER] (' + this.id + ') Unknown tickmark style: ' + tickmarks );
1831
+ }
1832
+ }
1833
+
1834
+ /**
1835
+ * Add the tickmark to the coords array
1836
+ */
1837
+ if ( prop['chart.boxplot']
1838
+ && typeof y0 === 'number'
1839
+ && typeof y1 === 'number'
1840
+ && typeof y2 === 'number'
1841
+ && typeof y3 === 'number'
1842
+ && typeof y4 === 'number') {
1843
+
1844
+ x = [x - halfBoxWidth, x + halfBoxWidth];
1845
+ yCoord = [y0, y1, y2, y3, y4];
1846
+ }
1847
+
1848
+ coords.push([x, yCoord, tooltip]);
1849
+ };
1850
+
1851
+
1852
+
1853
+
1854
+ /**
1855
+ * Draws an optional line connecting the tick marks.
1856
+ *
1857
+ * @param i The index of the dataset to use
1858
+ */
1859
+ this.drawLine =
1860
+ this.DrawLine = function (i)
1861
+ {
1862
+ if (typeof(prop['chart.line.visible']) == 'boolean' && prop['chart.line.visible'] == false) {
1863
+ return;
1864
+ }
1865
+
1866
+ if (prop['chart.line'] && this.coords[i].length >= 2) {
1867
+
1868
+ if (prop['chart.line.dash'] && typeof co.setLineDash === 'function') {
1869
+ co.setLineDash(prop['chart.line.dash']);
1870
+ }
1871
+
1872
+ co.lineCap = 'round';
1873
+ co.lineJoin = 'round';
1874
+ co.lineWidth = this.getLineWidth(i);// i is the index of the set of coordinates
1875
+ co.strokeStyle = prop['chart.line.colors'][i];
1876
+
1877
+ co.beginPath();
1878
+
1879
+ var prevY = null;
1880
+ var currY = null;
1881
+
1882
+ for (var j=0,len=this.coords[i].length; j<len; j+=1) {
1883
+
1884
+
1885
+ var xPos = this.coords[i][j][0];
1886
+ var yPos = this.coords[i][j][1];
1887
+
1888
+ if (j > 0) prevY = this.coords[i][j - 1][1];
1889
+ currY = yPos;
1890
+
1891
+ if (j == 0 || RG.is_null(prevY) || RG.is_null(currY)) {
1892
+ co.moveTo(xPos, yPos);
1893
+ } else {
1894
+
1895
+ // Stepped?
1896
+ var stepped = prop['chart.line.stepped'];
1897
+
1898
+ if ( (typeof stepped == 'boolean' && stepped)
1899
+ || (typeof stepped == 'object' && stepped[i])
1900
+ ) {
1901
+ co.lineTo(this.coords[i][j][0], this.coords[i][j - 1][1]);
1902
+ }
1903
+
1904
+ co.lineTo(xPos, yPos);
1905
+ }
1906
+ }
1907
+ co.stroke();
1908
+
1909
+ /**
1910
+ * Set the linedash back to the default
1911
+ */
1912
+ if (prop['chart.line.dash'] && typeof co.setLineDash === 'function') {
1913
+ co.setLineDash([1,0]);
1914
+ }
1915
+ }
1916
+
1917
+ /**
1918
+ * Set the linewidth back to 1
1919
+ */
1920
+ co.lineWidth = 1;
1921
+ };
1922
+
1923
+
1924
+
1925
+
1926
+ /**
1927
+ * Returns the linewidth
1928
+ *
1929
+ * @param number i The index of the "line" (/set of coordinates)
1930
+ */
1931
+ this.getLineWidth =
1932
+ this.GetLineWidth = function (i)
1933
+ {
1934
+ var linewidth = prop['chart.line.linewidth'];
1935
+
1936
+ if (typeof linewidth == 'number') {
1937
+ return linewidth;
1938
+
1939
+ } else if (typeof linewidth == 'object') {
1940
+ if (linewidth[i]) {
1941
+ return linewidth[i];
1942
+ } else {
1943
+ return linewidth[0];
1944
+ }
1945
+
1946
+ alert('[SCATTER] Error! chart.linewidth should be a single number or an array of one or more numbers');
1947
+ }
1948
+ };
1949
+
1950
+
1951
+
1952
+
1953
+ /**
1954
+ * Draws vertical bars. Line chart doesn't use a horizontal scale, hence this function
1955
+ * is not common
1956
+ */
1957
+ this.drawVBars =
1958
+ this.DrawVBars = function ()
1959
+ {
1960
+
1961
+ var vbars = prop['chart.background.vbars'];
1962
+ var graphWidth = ca.width - this.gutterLeft - this.gutterRight;
1963
+
1964
+ if (vbars) {
1965
+
1966
+ var xmax = prop['chart.xmax'];
1967
+ var xmin = prop['chart.xmin'];
1968
+
1969
+ for (var i=0,len=vbars.length; i<len; i+=1) {
1970
+
1971
+ var key = i;
1972
+ var value = vbars[key];
1973
+
1974
+ /**
1975
+ * Accomodate date/time values
1976
+ */
1977
+ if (typeof value[0] == 'string') value[0] = RG.parseDate(value[0]);
1978
+ if (typeof value[1] == 'string') value[1] = RG.parseDate(value[1]) - value[0];
1979
+
1980
+ var x = (( (value[0] - xmin) / (xmax - xmin) ) * graphWidth) + this.gutterLeft;
1981
+ var width = (value[1] / (xmax - xmin) ) * graphWidth;
1982
+
1983
+ co.fillStyle = value[2];
1984
+ co.fillRect(x, this.gutterTop, width, (ca.height - this.gutterTop - this.gutterBottom));
1985
+ }
1986
+ }
1987
+ };
1988
+
1989
+
1990
+
1991
+
1992
+ /**
1993
+ * Draws in-graph labels.
1994
+ *
1995
+ * @param object obj The graph object
1996
+ */
1997
+ this.drawInGraphLabels =
1998
+ this.DrawInGraphLabels = function (obj)
1999
+ {
2000
+ var labels = obj.Get('chart.labels.ingraph');
2001
+ var labels_processed = [];
2002
+
2003
+ if (!labels) {
2004
+ return;
2005
+ }
2006
+
2007
+ // Defaults
2008
+ var fgcolor = 'black';
2009
+ var bgcolor = 'white';
2010
+ var direction = 1;
2011
+
2012
+ /**
2013
+ * Preprocess the labels array. Numbers are expanded
2014
+ */
2015
+ for (var i=0,len=labels.length; i<len; i+=1) {
2016
+ if (typeof(labels[i]) == 'number') {
2017
+ for (var j=0; j<labels[i]; ++j) {
2018
+ labels_processed.push(null);
2019
+ }
2020
+ } else if (typeof(labels[i]) == 'string' || typeof(labels[i]) == 'object') {
2021
+ labels_processed.push(labels[i]);
2022
+
2023
+ } else {
2024
+ labels_processed.push('');
2025
+ }
2026
+ }
2027
+
2028
+ /**
2029
+ * Turn off any shadow
2030
+ */
2031
+ RG.NoShadow(obj);
2032
+
2033
+ if (labels_processed && labels_processed.length > 0) {
2034
+
2035
+ var i=0;
2036
+
2037
+ for (var set=0; set<obj.coords.length; ++set) {
2038
+ for (var point = 0; point<obj.coords[set].length; ++point) {
2039
+ if (labels_processed[i]) {
2040
+ var x = obj.coords[set][point][0];
2041
+ var y = obj.coords[set][point][1];
2042
+ var length = typeof(labels_processed[i][4]) == 'number' ? labels_processed[i][4] : 25;
2043
+
2044
+ var text_x = x;
2045
+ var text_y = y - 5 - length;
2046
+
2047
+ co.moveTo(x, y - 5);
2048
+ co.lineTo(x, y - 5 - length);
2049
+
2050
+ co.stroke();
2051
+ co.beginPath();
2052
+
2053
+ // This draws the arrow
2054
+ co.moveTo(x, y - 5);
2055
+ co.lineTo(x - 3, y - 10);
2056
+ co.lineTo(x + 3, y - 10);
2057
+ co.closePath();
2058
+
2059
+
2060
+ co.beginPath();
2061
+ // Fore ground color
2062
+ co.fillStyle = (typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][1]) == 'string') ? labels_processed[i][1] : 'black';
2063
+ RG.Text2(this, {
2064
+ 'font':obj.Get('chart.text.font'),
2065
+ 'size':obj.Get('chart.text.size'),
2066
+ 'x':text_x,
2067
+ 'y':text_y,
2068
+ 'text':(typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][0]) == 'string') ? labels_processed[i][0] : labels_processed[i],
2069
+ 'valign':'bottom',
2070
+ 'halign':'center',
2071
+ 'bounding':true,
2072
+ 'boundingFill':(typeof(labels_processed[i]) == 'object' && typeof(labels_processed[i][2]) == 'string') ? labels_processed[i][2] : 'white',
2073
+ 'tag': 'labels.ingraph'
2074
+ });
2075
+ co.fill();
2076
+ }
2077
+
2078
+ i++;
2079
+ }
2080
+ }
2081
+ }
2082
+ };
2083
+
2084
+
2085
+
2086
+
2087
+ /**
2088
+ * This function makes it much easier to get the (if any) point that is currently being hovered over.
2089
+ *
2090
+ * @param object e The event object
2091
+ */
2092
+ this.getShape =
2093
+ this.getPoint = function (e)
2094
+ {
2095
+ var mouseXY = RG.getMouseXY(e);
2096
+ var mouseX = mouseXY[0];
2097
+ var mouseY = mouseXY[1];
2098
+ var overHotspot = false;
2099
+ var offset = prop['chart.tooltips.hotspot']; // This is how far the hotspot extends
2100
+
2101
+ for (var set=0,len=this.coords.length; set<len; ++set) {
2102
+ for (var i=0,len2=this.coords[set].length; i<len2; ++i) {
2103
+
2104
+ var x = this.coords[set][i][0];
2105
+ var y = this.coords[set][i][1];
2106
+ var tooltip = this.data[set][i][3];
2107
+
2108
+ if (typeof(y) == 'number') {
2109
+ if (mouseX <= (x + offset) &&
2110
+ mouseX >= (x - offset) &&
2111
+ mouseY <= (y + offset) &&
2112
+ mouseY >= (y - offset)) {
2113
+
2114
+ var tooltip = RG.parseTooltipText(this.data[set][i][3], 0);
2115
+ var index_adjusted = i;
2116
+
2117
+ for (var ds=(set-1); ds >=0; --ds) {
2118
+ index_adjusted += this.data[ds].length;
2119
+ }
2120
+
2121
+ return {
2122
+ 0: this, 1: x, 2: y, 3: set, 4: i, 5: this.data[set][i][3],
2123
+ 'object': this, 'x': x, 'y': y, 'dataset': set, 'index': i, 'tooltip': tooltip, 'index_adjusted': index_adjusted
2124
+ };
2125
+ }
2126
+ } else if (RG.is_null(y)) {
2127
+ // Nothing to see here
2128
+
2129
+ } else {
2130
+
2131
+ var mark = this.data[set][i];
2132
+
2133
+ /**
2134
+ * Determine the width
2135
+ */
2136
+ var width = prop['chart.boxplot.width'];
2137
+
2138
+ if (typeof(mark[1][7]) == 'number') {
2139
+ width = mark[1][7];
2140
+ }
2141
+
2142
+ if ( typeof(x) == 'object'
2143
+ && mouseX > x[0]
2144
+ && mouseX < x[1]
2145
+ && mouseY < y[1]
2146
+ && mouseY > y[3]
2147
+ ) {
2148
+
2149
+ var tooltip = RG.parseTooltipText(this.data[set][i][3], 0);
2150
+
2151
+ return {
2152
+ 0: this, 1: x[0], 2: x[1] - x[0], 3: y[1], 4: y[3] - y[1], 5: set, 6: i, 7: this.data[set][i][3],
2153
+ 'object': this, 'x': x[0], 'y': y[1], 'width': x[1] - x[0], 'height': y[3] - y[1], 'dataset': set, 'index': i, 'tooltip': tooltip
2154
+ };
2155
+ }
2156
+ }
2157
+ }
2158
+ }
2159
+ };
2160
+
2161
+
2162
+
2163
+
2164
+ /**
2165
+ * Draws the above line labels
2166
+ */
2167
+ this.drawAboveLabels =
2168
+ this.DrawAboveLabels = function ()
2169
+ {
2170
+ var size = prop['chart.labels.above.size'];
2171
+ var font = prop['chart.text.font'];
2172
+ var units_pre = prop['chart.units.pre'];
2173
+ var units_post = prop['chart.units.post'];
2174
+
2175
+
2176
+ for (var set=0,len=this.coords.length; set<len; ++set) {
2177
+ for (var point=0,len2=this.coords[set].length; point<len2; ++point) {
2178
+
2179
+ var x_val = this.data[set][point][0];
2180
+ var y_val = this.data[set][point][1];
2181
+
2182
+ if (!RG.is_null(y_val)) {
2183
+
2184
+ // Use the top most value from a box plot
2185
+ if (RG.is_array(y_val)) {
2186
+ var max = 0;
2187
+ for (var i=0; i<y_val; ++i) {
2188
+ max = Math.max(max, y_val[i]);
2189
+ }
2190
+
2191
+ y_val = max;
2192
+ }
2193
+
2194
+ var x_pos = this.coords[set][point][0];
2195
+ var y_pos = this.coords[set][point][1];
2196
+
2197
+ RG.Text2(this, {
2198
+ 'font':font,
2199
+ 'size':size,
2200
+ 'x':x_pos,
2201
+ 'y':y_pos - 5 - size,
2202
+ 'text':x_val.toFixed(prop['chart.labels.above.decimals']) + ', ' + y_val.toFixed(prop['chart.labels.above.decimals']),
2203
+ 'valign':'center',
2204
+ 'halign':'center',
2205
+ 'bounding':true,
2206
+ 'boundingFill':'rgba(255, 255, 255, 0.7)',
2207
+ 'tag': 'labels.above'
2208
+ });
2209
+ }
2210
+ }
2211
+ }
2212
+ };
2213
+
2214
+
2215
+
2216
+
2217
+ /**
2218
+ * When you click on the chart, this method can return the Y value at that point. It works for any point on the
2219
+ * chart (that is inside the gutters) - not just points within the Bars.
2220
+ *
2221
+ * @param object e The event object
2222
+ */
2223
+ this.getYValue =
2224
+ this.getValue = function (arg)
2225
+ {
2226
+ if (arg.length == 2) {
2227
+ var mouseX = arg[0];
2228
+ var mouseY = arg[1];
2229
+ } else {
2230
+ var mouseCoords = RG.getMouseXY(arg);
2231
+ var mouseX = mouseCoords[0];
2232
+ var mouseY = mouseCoords[1];
2233
+ }
2234
+
2235
+ var obj = this;
2236
+
2237
+ if ( mouseY < this.gutterTop
2238
+ || mouseY > (ca.height - this.gutterBottom)
2239
+ || mouseX < this.gutterLeft
2240
+ || mouseX > (ca.width - this.gutterRight)
2241
+ ) {
2242
+ return null;
2243
+ }
2244
+
2245
+ if (prop['chart.xaxispos'] == 'center') {
2246
+ var value = (((this.grapharea / 2) - (mouseY - this.gutterTop)) / this.grapharea) * (this.max - this.min)
2247
+ value *= 2;
2248
+
2249
+
2250
+ // Account for each side of the X axis
2251
+ if (value >= 0) {
2252
+ value += this.min
2253
+
2254
+ if (prop['chart.ylabels.invert']) {
2255
+ value -= this.min;
2256
+ value = this.max - value;
2257
+ }
2258
+
2259
+ } else {
2260
+
2261
+ value -= this.min;
2262
+ if (prop['chart.ylabels.invert']) {
2263
+ value += this.min;
2264
+ value = this.max + value;
2265
+ value *= -1;
2266
+ }
2267
+ }
2268
+
2269
+ } else {
2270
+
2271
+ var value = ((this.grapharea - (mouseY - this.gutterTop)) / this.grapharea) * (this.max - this.min)
2272
+ value += this.min;
2273
+
2274
+ if (prop['chart.ylabels.invert']) {
2275
+ value -= this.min;
2276
+ value = this.max - value;
2277
+ }
2278
+ }
2279
+
2280
+ return value;
2281
+ };
2282
+
2283
+
2284
+
2285
+
2286
+ /**
2287
+ * When you click on the chart, this method can return the X value at that point.
2288
+ *
2289
+ * @param mixed arg This can either be an event object or the X coordinate
2290
+ * @param number If specifying the X coord as the first arg then this should be the Y coord
2291
+ */
2292
+ this.getXValue = function (arg)
2293
+ {
2294
+ if (arg.length == 2) {
2295
+ var mouseX = arg[0];
2296
+ var mouseY = arg[1];
2297
+ } else {
2298
+ var mouseXY = RG.getMouseXY(arg);
2299
+ var mouseX = mouseXY[0];
2300
+ var mouseY = mouseXY[1];
2301
+ }
2302
+ var obj = this;
2303
+
2304
+ if ( mouseY < this.gutterTop
2305
+ || mouseY > (ca.height - this.gutterBottom)
2306
+ || mouseX < this.gutterLeft
2307
+ || mouseX > (ca.width - this.gutterRight)
2308
+ ) {
2309
+ return null;
2310
+ }
2311
+
2312
+ var width = (ca.width - this.gutterLeft - this.gutterRight);
2313
+ var value = ((mouseX - this.gutterLeft) / width) * (prop['chart.xmax'] - prop['chart.xmin'])
2314
+ value += prop['chart.xmin'];
2315
+
2316
+ return value;
2317
+ };
2318
+
2319
+
2320
+
2321
+
2322
+ /**
2323
+ * Each object type has its own Highlight() function which highlights the appropriate shape
2324
+ *
2325
+ * @param object shape The shape to highlight
2326
+ */
2327
+ this.highlight =
2328
+ this.Highlight = function (shape)
2329
+ {
2330
+ // Boxplot highlight
2331
+ if (shape['height']) {
2332
+ RG.Highlight.Rect(this, shape);
2333
+
2334
+ // Point highlight
2335
+ } else {
2336
+ RG.Highlight.Point(this, shape);
2337
+ }
2338
+ };
2339
+
2340
+
2341
+
2342
+
2343
+ /**
2344
+ * The getObjectByXY() worker method. Don't call this call:
2345
+ *
2346
+ * RG.ObjectRegistry.getObjectByXY(e)
2347
+ *
2348
+ * @param object e The event object
2349
+ */
2350
+ this.getObjectByXY = function (e)
2351
+ {
2352
+ var mouseXY = RG.getMouseXY(e);
2353
+
2354
+ if (
2355
+ mouseXY[0] > (this.gutterLeft - 3)
2356
+ && mouseXY[0] < (ca.width - this.gutterRight + 3)
2357
+ && mouseXY[1] > (this.gutterTop - 3)
2358
+ && mouseXY[1] < ((ca.height - this.gutterBottom) + 3)
2359
+ ) {
2360
+
2361
+ return this;
2362
+ }
2363
+ };
2364
+
2365
+
2366
+
2367
+
2368
+ /**
2369
+ * This function can be used when the canvas is clicked on (or similar - depending on the event)
2370
+ * to retrieve the relevant X coordinate for a particular value.
2371
+ *
2372
+ * @param int value The value to get the X coordinate for
2373
+ */
2374
+ this.getXCoord = function (value)
2375
+ {
2376
+ if (typeof value != 'number' && typeof value != 'string') {
2377
+ return null;
2378
+ }
2379
+
2380
+ // Allow for date strings to be passed
2381
+ if (typeof value === 'string') {
2382
+ value = RG.parseDate(value);
2383
+ }
2384
+
2385
+ var xmin = prop['chart.xmin'];
2386
+ var xmax = prop['chart.xmax'];
2387
+ var x;
2388
+
2389
+ if (value < xmin) return null;
2390
+ if (value > xmax) return null;
2391
+
2392
+ var gutterRight = this.gutterRight;
2393
+ var gutterLeft = this.gutterLeft;
2394
+
2395
+ if (prop['chart.yaxispos'] == 'right') {
2396
+ x = ((value - xmin) / (xmax - xmin)) * (ca.width - gutterLeft - gutterRight);
2397
+ x = (ca.width - gutterRight - x);
2398
+ } else {
2399
+ x = ((value - xmin) / (xmax - xmin)) * (ca.width - gutterLeft - gutterRight);
2400
+ x = x + gutterLeft;
2401
+ }
2402
+
2403
+ return x;
2404
+ };
2405
+
2406
+
2407
+
2408
+
2409
+ /**
2410
+ * This function positions a tooltip when it is displayed
2411
+ *
2412
+ * @param obj object The chart object
2413
+ * @param int x The X coordinate specified for the tooltip
2414
+ * @param int y The Y coordinate specified for the tooltip
2415
+ * @param objec tooltip The tooltips DIV element
2416
+ */
2417
+ this.positionTooltip = function (obj, x, y, tooltip, idx)
2418
+ {
2419
+ var shape = RG.Registry.Get('chart.tooltip.shape');
2420
+ var dataset = shape['dataset'];
2421
+ var index = shape['index'];
2422
+ var coordX = obj.coords[dataset][index][0]
2423
+ var coordY = obj.coords[dataset][index][1]
2424
+ var canvasXY = RG.getCanvasXY(obj.canvas);
2425
+ var gutterLeft = obj.gutterLeft;
2426
+ var gutterTop = obj.gutterTop;
2427
+ var width = tooltip.offsetWidth;
2428
+ var height = tooltip.offsetHeight;
2429
+ tooltip.style.left = 0;
2430
+ tooltip.style.top = 0;
2431
+
2432
+ // Is the coord a boxplot
2433
+ var isBoxplot = typeof(coordY) == 'object' ? true : false;
2434
+
2435
+ // Show any overflow (ie the arrow)
2436
+ tooltip.style.overflow = '';
2437
+
2438
+ // Create the arrow
2439
+ var img = new Image();
2440
+ img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
2441
+ img.style.position = 'absolute';
2442
+ img.id = '__rgraph_tooltip_pointer__';
2443
+ img.style.top = (tooltip.offsetHeight - 2) + 'px';
2444
+ tooltip.appendChild(img);
2445
+
2446
+ // Reposition the tooltip if at the edges:
2447
+
2448
+ // LEFT edge //////////////////////////////////////////////////////////////////
2449
+
2450
+ if ((canvasXY[0] + (coordX[0] || coordX) - (width / 2)) < 10) {
2451
+
2452
+ if (isBoxplot) {
2453
+ tooltip.style.left = canvasXY[0] + coordX[0] + ((coordX[1] - coordX[0]) / 2) - (width * 0.1) + 'px';
2454
+ tooltip.style.top = canvasXY[1] + coordY[2] - height - 5 + 'px';
2455
+ img.style.left = ((width * 0.1) - 8.5) + 'px';
2456
+
2457
+ } else {
2458
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.1)) + 'px';
2459
+ tooltip.style.top = canvasXY[1] + coordY - height - 9 + 'px';
2460
+ img.style.left = ((width * 0.1) - 8.5) + 'px';
2461
+ }
2462
+
2463
+ // RIGHT edge //////////////////////////////////////////////////////////////////
2464
+
2465
+ } else if ((canvasXY[0] + (coordX[0] || coordX) + (width / 2)) > doc.body.offsetWidth) {
2466
+ if (isBoxplot) {
2467
+ tooltip.style.left = canvasXY[0] + coordX[0] + ((coordX[1] - coordX[0]) / 2) - (width * 0.9) + 'px';
2468
+ tooltip.style.top = canvasXY[1] + coordY[2] - height - 5 + 'px';
2469
+ img.style.left = ((width * 0.9) - 8.5) + 'px';
2470
+
2471
+ } else {
2472
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.9)) + 'px';
2473
+ tooltip.style.top = canvasXY[1] + coordY - height - 9 + 'px';
2474
+ img.style.left = ((width * 0.9) - 8.5) + 'px';
2475
+ }
2476
+
2477
+ // Default positioning - CENTERED //////////////////////////////////////////////////////////////////
2478
+
2479
+ } else {
2480
+ if (isBoxplot) {
2481
+ tooltip.style.left = canvasXY[0] + coordX[0] + ((coordX[1] - coordX[0]) / 2) - (width / 2) + 'px';
2482
+ tooltip.style.top = canvasXY[1] + coordY[2] - height - 5 + 'px';
2483
+ img.style.left = ((width * 0.5) - 8.5) + 'px';
2484
+
2485
+ } else {
2486
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
2487
+ tooltip.style.top = canvasXY[1] + coordY - height - 9 + 'px';
2488
+ img.style.left = ((width * 0.5) - 8.5) + 'px';
2489
+ }
2490
+ }
2491
+ };
2492
+
2493
+
2494
+
2495
+
2496
+ /**
2497
+ * Returns the applicable Y COORDINATE when given a Y value
2498
+ *
2499
+ * @param int value The value to use
2500
+ * @return int The appropriate Y coordinate
2501
+ */
2502
+ this.getYCoord =
2503
+ this.getYCoordFromValue = function (value)
2504
+ {
2505
+ if (typeof(value) != 'number') {
2506
+ return null;
2507
+ }
2508
+
2509
+ var invert = prop['chart.ylabels.invert'];
2510
+ var xaxispos = prop['chart.xaxispos'];
2511
+ var graphHeight = ca.height - this.gutterTop - this.gutterBottom;
2512
+ var halfGraphHeight = graphHeight / 2;
2513
+ var ymax = this.max;
2514
+ var ymin = prop['chart.ymin'];
2515
+ var coord = 0;
2516
+
2517
+ if (value > ymax || (prop['chart.xaxispos'] == 'bottom' && value < ymin) || (prop['chart.xaxispos'] == 'center' && ((value > 0 && value < ymin) || (value < 0 && value > (-1 * ymin))))) {
2518
+ return null;
2519
+ }
2520
+
2521
+ /**
2522
+ * This calculates scale values if the X axis is in the center
2523
+ */
2524
+ if (xaxispos == 'center') {
2525
+
2526
+ coord = ((Math.abs(value) - ymin) / (ymax - ymin)) * halfGraphHeight;
2527
+
2528
+ if (invert) {
2529
+ coord = halfGraphHeight - coord;
2530
+ }
2531
+
2532
+ if (value < 0) {
2533
+ coord += this.gutterTop;
2534
+ coord += halfGraphHeight;
2535
+ } else {
2536
+ coord = halfGraphHeight - coord;
2537
+ coord += this.gutterTop;
2538
+ }
2539
+
2540
+ /**
2541
+ * And this calculates scale values when the X axis is at the bottom
2542
+ */
2543
+ } else {
2544
+
2545
+ coord = ((value - ymin) / (ymax - ymin)) * graphHeight;
2546
+
2547
+ if (invert) {
2548
+ coord = graphHeight - coord;
2549
+ }
2550
+
2551
+ // Invert the coordinate because the Y scale starts at the top
2552
+ coord = graphHeight - coord;
2553
+
2554
+ // And add on the top gutter
2555
+ coord = this.gutterTop + coord;
2556
+ }
2557
+
2558
+ return coord;
2559
+ };
2560
+
2561
+
2562
+
2563
+
2564
+
2565
+
2566
+ /**
2567
+ * A helper class that helps facilitatesbubble charts
2568
+ */
2569
+ RG.Scatter.Bubble = function (scatter, min, max, width, data)
2570
+ {
2571
+ this.scatter = scatter;
2572
+ this.min = min;
2573
+ this.max = max;
2574
+ this.width = width;
2575
+ this.data = data;
2576
+
2577
+
2578
+
2579
+ /**
2580
+ * A setter for the Bubble chart class - it just acts as a "passthru" to the Scatter object
2581
+ */
2582
+ this.set =
2583
+ this.Set = function (name, value)
2584
+ {
2585
+ this.scatter.Set(name, value);
2586
+
2587
+ return this;
2588
+ };
2589
+
2590
+
2591
+
2592
+ /**
2593
+ * A getter for the Bubble chart class - it just acts as a "passthru" to the Scatter object
2594
+ */
2595
+ this.get =
2596
+ this.Get = function (name)
2597
+ {
2598
+ this.scatter.Get(name);
2599
+ };
2600
+
2601
+
2602
+
2603
+
2604
+ /**
2605
+ * Tha Bubble chart draw function
2606
+ */
2607
+ this.draw =
2608
+ this.Draw = function ()
2609
+ {
2610
+ var bubble_min = this.min;
2611
+ var bubble_max = this.max;
2612
+ var bubble_data = this.data;
2613
+ var bubble_max_width = this.width;
2614
+
2615
+ // This custom ondraw event listener draws the bubbles
2616
+ this.scatter.ondraw = function (obj)
2617
+ {
2618
+ // Loop through all the points (first dataset)
2619
+ for (var i=0; i<obj.coords[0].length; ++i) {
2620
+
2621
+ bubble_data[i] = Math.max(bubble_data[i], bubble_min);
2622
+ bubble_data[i] = Math.min(bubble_data[i], bubble_max);
2623
+
2624
+ var r = ((bubble_data[i] - bubble_min) / (bubble_max - bubble_min) ) * bubble_max_width;
2625
+
2626
+ co.beginPath();
2627
+ co.fillStyle = RG.RadialGradient(obj,
2628
+ obj.coords[0][i][0] + (r / 2.5),
2629
+ obj.coords[0][i][1] - (r / 2.5),
2630
+ 0,
2631
+ obj.coords[0][i][0] + (r / 2.5),
2632
+ obj.coords[0][i][1] - (r / 2.5),
2633
+ 50,
2634
+ 'white',
2635
+ obj.data[0][i][2] ? obj.data[0][i][2] : obj.properties['chart.defaultcolor']
2636
+ );
2637
+ co.arc(obj.coords[0][i][0], obj.coords[0][i][1], r, 0, RG.TWOPI, false);
2638
+ co.fill();
2639
+ }
2640
+ }
2641
+
2642
+ return this.scatter.Draw();
2643
+ };
2644
+ };
2645
+
2646
+
2647
+
2648
+
2649
+
2650
+ /**
2651
+ * This allows for easy specification of gradients
2652
+ */
2653
+ this.parseColors = function ()
2654
+ {
2655
+ // Save the original colors so that they can be restored when the canvas is reset
2656
+ if (this.original_colors.length === 0) {
2657
+ this.original_colors['data'] = RG.array_clone(this.data);
2658
+ this.original_colors['chart.background.vbars'] = RG.array_clone(prop['chart.background.vbars']);
2659
+ this.original_colors['chart.background.hbars'] = RG.array_clone(prop['chart.background.hbars']);
2660
+ this.original_colors['chart.line.colors'] = RG.array_clone(prop['chart.line.colors']);
2661
+ this.original_colors['chart.defaultcolor'] = RG.array_clone(prop['chart.defaultcolor']);
2662
+ this.original_colors['chart.crosshairs.color'] = RG.array_clone(prop['chart.crosshairs.color']);
2663
+ this.original_colors['chart.highlight.stroke'] = RG.array_clone(prop['chart.highlight.stroke']);
2664
+ this.original_colors['chart.highlight.fill'] = RG.array_clone(prop['chart.highlight.fill']);
2665
+ this.original_colors['chart.background.barcolor1'] = RG.array_clone(prop['chart.background.barcolor1']);
2666
+ this.original_colors['chart.background.barcolor2'] = RG.array_clone(prop['chart.background.barcolor2']);
2667
+ this.original_colors['chart.background.grid.color'] = RG.array_clone(prop['chart.background.grid.color']);
2668
+ this.original_colors['chart.background.color'] = RG.array_clone(prop['chart.background.color']);
2669
+ this.original_colors['chart.axis.color'] = RG.array_clone(prop['chart.axis.color']);
2670
+ }
2671
+
2672
+
2673
+
2674
+
2675
+
2676
+ // Colors
2677
+ var data = this.data;
2678
+ if (data) {
2679
+ for (var dataset=0; dataset<data.length; ++dataset) {
2680
+ for (var i=0; i<this.data[dataset].length; ++i) {
2681
+
2682
+ // Boxplots
2683
+ if (this.data[dataset][i] && typeof(this.data[dataset][i][1]) == 'object' && this.data[dataset][i][1]) {
2684
+
2685
+ if (typeof(this.data[dataset][i][1][5]) == 'string') this.data[dataset][i][1][5] = this.parseSingleColorForGradient(this.data[dataset][i][1][5]);
2686
+ if (typeof(this.data[dataset][i][1][6]) == 'string') this.data[dataset][i][1][6] = this.parseSingleColorForGradient(this.data[dataset][i][1][6]);
2687
+ }
2688
+
2689
+ this.data[dataset][i][2] = this.parseSingleColorForGradient(this.data[dataset][i][2]);
2690
+ }
2691
+ }
2692
+ }
2693
+
2694
+ // Parse HBars
2695
+ var hbars = prop['chart.background.hbars'];
2696
+ if (hbars) {
2697
+ for (i=0; i<hbars.length; ++i) {
2698
+ hbars[i][2] = this.parseSingleColorForGradient(hbars[i][2]);
2699
+ }
2700
+ }
2701
+
2702
+ // Parse HBars
2703
+ var vbars = prop['chart.background.vbars'];
2704
+ if (vbars) {
2705
+ for (i=0; i<vbars.length; ++i) {
2706
+ vbars[i][2] = this.parseSingleColorForGradient(vbars[i][2]);
2707
+ }
2708
+ }
2709
+
2710
+ // Parse line colors
2711
+ var colors = prop['chart.line.colors'];
2712
+ if (colors) {
2713
+ for (i=0; i<colors.length; ++i) {
2714
+ colors[i] = this.parseSingleColorForGradient(colors[i]);
2715
+ }
2716
+ }
2717
+
2718
+ prop['chart.defaultcolor'] = this.parseSingleColorForGradient(prop['chart.defaultcolor']);
2719
+ prop['chart.crosshairs.color'] = this.parseSingleColorForGradient(prop['chart.crosshairs.color']);
2720
+ prop['chart.highlight.stroke'] = this.parseSingleColorForGradient(prop['chart.highlight.stroke']);
2721
+ prop['chart.highlight.fill'] = this.parseSingleColorForGradient(prop['chart.highlight.fill']);
2722
+ prop['chart.background.barcolor1'] = this.parseSingleColorForGradient(prop['chart.background.barcolor1']);
2723
+ prop['chart.background.barcolor2'] = this.parseSingleColorForGradient(prop['chart.background.barcolor2']);
2724
+ prop['chart.background.grid.color'] = this.parseSingleColorForGradient(prop['chart.background.grid.color']);
2725
+ prop['chart.background.color'] = this.parseSingleColorForGradient(prop['chart.background.color']);
2726
+ prop['chart.axis.color'] = this.parseSingleColorForGradient(prop['chart.axis.color']);
2727
+ };
2728
+
2729
+
2730
+
2731
+
2732
+ /**
2733
+ * Use this function to reset the object to the post-constructor state. Eg reset colors if
2734
+ * need be etc
2735
+ */
2736
+ this.reset = function ()
2737
+ {
2738
+ };
2739
+
2740
+
2741
+
2742
+
2743
+ /**
2744
+ * This parses a single color value for a gradient
2745
+ */
2746
+ this.parseSingleColorForGradient = function (color)
2747
+ {
2748
+ if (!color || typeof(color) != 'string') {
2749
+ return color;
2750
+ }
2751
+
2752
+ if (color.match(/^gradient\((.*)\)$/i)) {
2753
+
2754
+ var parts = RegExp.$1.split(':');
2755
+
2756
+ // Create the gradient
2757
+ var grad = co.createLinearGradient(0,ca.height - prop['chart.gutter.bottom'], 0, prop['chart.gutter.top']);
2758
+
2759
+ var diff = 1 / (parts.length - 1);
2760
+
2761
+ grad.addColorStop(0, RG.trim(parts[0]));
2762
+
2763
+ for (var j=1; j<parts.length; ++j) {
2764
+ grad.addColorStop(j * diff, RG.trim(parts[j]));
2765
+ }
2766
+ }
2767
+
2768
+ return grad ? grad : color;
2769
+ };
2770
+
2771
+
2772
+
2773
+
2774
+ /**
2775
+ * This function handles highlighting an entire data-series for the interactive
2776
+ * key
2777
+ *
2778
+ * @param int index The index of the data series to be highlighted
2779
+ */
2780
+ this.interactiveKeyHighlight = function (index)
2781
+ {
2782
+ if (this.coords && this.coords[index] && this.coords[index].length) {
2783
+ this.coords[index].forEach(function (value, idx, arr)
2784
+ {
2785
+ co.beginPath();
2786
+ co.fillStyle = prop['chart.key.interactive.highlight.chart.fill'];
2787
+ co.arc(value[0], value[1], prop['chart.ticksize'] + 3, 0, RG.TWOPI, false);
2788
+ co.fill();
2789
+ });
2790
+ }
2791
+ };
2792
+
2793
+
2794
+
2795
+
2796
+ /**
2797
+ * Using a function to add events makes it easier to facilitate method chaining
2798
+ *
2799
+ * @param string type The type of even to add
2800
+ * @param function func
2801
+ */
2802
+ this.on = function (type, func)
2803
+ {
2804
+ if (type.substr(0,2) !== 'on') {
2805
+ type = 'on' + type;
2806
+ }
2807
+
2808
+ this[type] = func;
2809
+
2810
+ return this;
2811
+ };
2812
+
2813
+
2814
+
2815
+
2816
+ /**
2817
+ * This function runs once only
2818
+ * (put at the end of the file (before any effects))
2819
+ */
2820
+ this.firstDrawFunc = function ()
2821
+ {
2822
+ };
2823
+
2824
+
2825
+
2826
+
2827
+ /**
2828
+ * Trace
2829
+ *
2830
+ * This effect is for the Scatter chart, uses the jQuery library and slowly
2831
+ * uncovers the Line/marks, but you can see the background of the chart.
2832
+ */
2833
+ this.trace = function ()
2834
+ {
2835
+ var callback = typeof arguments[1] === 'function' ? arguments[1] : function () {};
2836
+ var opt = arguments[0] || [];
2837
+ var obj = this;
2838
+
2839
+ opt['duration'] = opt['duration'] || 1500;
2840
+ if (opt['frames']) {
2841
+ opt['duration'] = (opt['frames'] / 60) * 1000;
2842
+ }
2843
+
2844
+ RGraph.clear(ca);
2845
+ RGraph.redrawCanvas(ca);
2846
+
2847
+ /**
2848
+ * Create the DIV that the second canvas will sit in
2849
+ */
2850
+ var div = document.createElement('DIV');
2851
+ var xy = RG.getCanvasXY(this.canvas);
2852
+ div.id = '__rgraph_trace_animation_' + RGraph.random(0, 4351623) + '__';
2853
+ div.style.left = xy[0] + 'px';
2854
+ div.style.top = xy[1] + 'px';
2855
+ div.style.width = prop['chart.gutter.left'];
2856
+ div.style.height = ca.height + 'px';
2857
+ div.style.position = 'absolute';
2858
+ div.style.overflow = 'hidden';
2859
+ obj.canvas.parentNode.appendChild(div);
2860
+
2861
+
2862
+ /**
2863
+ * Make the second canvas
2864
+ */
2865
+ var id = '__rgraph_scatter_trace_animation_' + RG.random(0, 99999999) + '__';
2866
+ var canvas2 = document.createElement('CANVAS');
2867
+ canvas2.width = ca.width;
2868
+ canvas2.height = ca.height;
2869
+ canvas2.style.position = 'absolute';
2870
+ canvas2.style.left = 0;
2871
+ canvas2.style.top = 0;
2872
+ canvas2.setAttribute('data-l', 'false');
2873
+
2874
+
2875
+ // This stops the clear effect clearing the canvas - which can happen if you have multiple canvas tags on the page all with
2876
+ // dynamic effects that do redrawing
2877
+ canvas2.noclear = true;
2878
+
2879
+ canvas2.id = id;
2880
+ div.appendChild(canvas2);
2881
+
2882
+ var reposition_canvas2 = function (e)
2883
+ {
2884
+ var xy = RGraph.getCanvasXY(obj.canvas);
2885
+
2886
+ div.style.left = xy[0] + 'px';
2887
+ div.style.top = xy[1] + 'px';
2888
+ }
2889
+ window.addEventListener('resize', reposition_canvas2, false)
2890
+
2891
+ /**
2892
+ * Make a copy of the original Line object
2893
+ */
2894
+ var obj2 = new RGraph.Scatter(id, RG.array_clone(this.data));
2895
+
2896
+ // Remove the new line from the ObjectRegistry so that it isn't redawn
2897
+ RG.ObjectRegistry.Remove(obj2);
2898
+
2899
+ for (i in prop) {
2900
+ if (typeof i === 'string') {
2901
+ obj2.Set(i, prop[i]);
2902
+ }
2903
+ }
2904
+
2905
+
2906
+ obj2.Set('chart.labels', []);
2907
+ obj2.Set('chart.background.grid', false);
2908
+ obj2.Set('chart.background.barcolor1', 'rgba(0,0,0,0)');
2909
+ obj2.Set('chart.background.barcolor2', 'rgba(0,0,0,0)');
2910
+ obj2.Set('chart.ylabels', false);
2911
+ obj2.Set('chart.noaxes', true);
2912
+ obj2.Set('chart.title', '');
2913
+ obj2.Set('chart.title.xaxis', '');
2914
+ obj2.Set('chart.title.yaxis', '');
2915
+ obj2.Set('chart.key', []);
2916
+ obj2.Draw();
2917
+
2918
+
2919
+ /**
2920
+ * This effectively hides the line
2921
+ */
2922
+ this.set('chart.line.visible', false);
2923
+
2924
+
2925
+ RGraph.clear(ca);
2926
+ RGraph.redrawCanvas(ca);
2927
+
2928
+ /**
2929
+ * Place a DIV over the canvas to stop interaction with it
2930
+ */
2931
+ if (!ca.__rgraph_scatter_trace_cover__) {
2932
+ var div2 = document.createElement('DIV');
2933
+ div2.id = '__rgraph_trace_animation_' + RGraph.random(0, 4351623) + '__';
2934
+ div2.style.left = xy[0] + 'px';
2935
+ div2.style.top = xy[1] + 'px';
2936
+ div2.style.width = ca.width + 'px';
2937
+ div2.style.height = ca.height + 'px';
2938
+ div2.style.position = 'absolute';
2939
+ div2.style.overflow = 'hidden';
2940
+ div2.style.backgroundColor = 'rgba(0,0,0,0)';
2941
+ div.div2 = div2;
2942
+ ca.__rgraph_scatter_trace_cover__ = div2
2943
+ obj.canvas.parentNode.appendChild(div2);
2944
+
2945
+ } else {
2946
+ div2 = ca.__rgraph_scatter_trace_cover__;
2947
+ }
2948
+
2949
+ /**
2950
+ * Animate the DIV that contains the canvas
2951
+ */
2952
+ jQuery('#' + div.id).animate({
2953
+ width: ca.width + 'px'
2954
+ }, opt['duration'], function ()
2955
+ {
2956
+ // Remove the window resize listener
2957
+ window.removeEventListener('resize', reposition_canvas2, false);
2958
+
2959
+ div.style.display = 'none';
2960
+ div2.style.display = 'none';
2961
+
2962
+ //div.removeChild(canvas2);
2963
+ obj.Set('chart.line.visible', true);
2964
+
2965
+ // Revert the colors back to what they were
2966
+ obj.Set('chart.colors', RGraph.array_clone(obj2.Get('chart.colors')));
2967
+ obj.Set('chart.key', RG.array_clone(obj2.Get('chart.key')));
2968
+
2969
+ RGraph.RedrawCanvas(ca);
2970
+
2971
+ ca.__rgraph_trace_cover__ = null;
2972
+
2973
+ callback(obj);
2974
+ });
2975
+
2976
+ return this;
2977
+ };
2978
+
2979
+
2980
+
2981
+
2982
+ /**
2983
+ * This helps the Gantt reset colors when the reset function is called.
2984
+ * It handles going through the data and resetting the colors.
2985
+ */
2986
+ this.resetColorsToOriginalValues = function ()
2987
+ {
2988
+ /**
2989
+ * Copy the original colors over for single-event-per-line data
2990
+ */
2991
+ for (var i=0,len=this.original_colors['data'].length; i<len; ++i) {
2992
+ for (var j=0,len2=this.original_colors['data'][i].length; j<len2;++j) {
2993
+
2994
+ // The color for the point
2995
+ this.data[i][j][2] = RG.array_clone(this.original_colors['data'][i][j][2]);
2996
+
2997
+ // Handle boxplots
2998
+ if (typeof this.data[i][j][1] === 'object') {
2999
+ this.data[i][j][1][5] = RG.array_clone(this.original_colors['data'][i][j][1][5]);
3000
+ this.data[i][j][1][6] = RG.array_clone(this.original_colors['data'][i][j][1][6]);
3001
+ }
3002
+ }
3003
+ }
3004
+ };
3005
+
3006
+
3007
+
3008
+
3009
+ RG.att(ca);
3010
+
3011
+
3012
+
3013
+
3014
+ /**
3015
+ * Register the object
3016
+ */
3017
+ RG.Register(this);
3018
+
3019
+
3020
+
3021
+
3022
+ /**
3023
+ * This is the 'end' of the constructor so if the first argument
3024
+ * contains configuration data - handle that.
3025
+ */
3026
+ if (parseConfObjectForOptions) {
3027
+ RG.parseObjectStyleConfig(this, conf.options);
3028
+ }
3029
+ };