rgraph-rails 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ };