rgraph-rails 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +4 -0
  7. data/README.md +73 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/lib/rgraph-rails/version.rb +3 -0
  12. data/lib/rgraph-rails.rb +8 -0
  13. data/license.txt +19 -0
  14. data/rgraph-rails.gemspec +26 -0
  15. data/vendor/assets/images/bg.png +0 -0
  16. data/vendor/assets/images/bullet.png +0 -0
  17. data/vendor/assets/images/facebook-large.png +0 -0
  18. data/vendor/assets/images/google-plus-large.png +0 -0
  19. data/vendor/assets/images/logo.png +0 -0
  20. data/vendor/assets/images/meter-image-sd-needle.png +0 -0
  21. data/vendor/assets/images/meter-image-sd.png +0 -0
  22. data/vendor/assets/images/meter-sketch-needle.png +0 -0
  23. data/vendor/assets/images/meter-sketch.png +0 -0
  24. data/vendor/assets/images/odometer-background.png +0 -0
  25. data/vendor/assets/images/rgraph.jpg +0 -0
  26. data/vendor/assets/images/title.png +0 -0
  27. data/vendor/assets/images/twitter-large.png +0 -0
  28. data/vendor/assets/javascripts/RGraph.bar.js +3246 -0
  29. data/vendor/assets/javascripts/RGraph.bipolar.js +2003 -0
  30. data/vendor/assets/javascripts/RGraph.common.annotate.js +399 -0
  31. data/vendor/assets/javascripts/RGraph.common.context.js +600 -0
  32. data/vendor/assets/javascripts/RGraph.common.core.js +4751 -0
  33. data/vendor/assets/javascripts/RGraph.common.csv.js +275 -0
  34. data/vendor/assets/javascripts/RGraph.common.deprecated.js +454 -0
  35. data/vendor/assets/javascripts/RGraph.common.dynamic.js +1194 -0
  36. data/vendor/assets/javascripts/RGraph.common.effects.js +1524 -0
  37. data/vendor/assets/javascripts/RGraph.common.key.js +735 -0
  38. data/vendor/assets/javascripts/RGraph.common.resizing.js +550 -0
  39. data/vendor/assets/javascripts/RGraph.common.tooltips.js +605 -0
  40. data/vendor/assets/javascripts/RGraph.common.zoom.js +223 -0
  41. data/vendor/assets/javascripts/RGraph.drawing.background.js +636 -0
  42. data/vendor/assets/javascripts/RGraph.drawing.circle.js +579 -0
  43. data/vendor/assets/javascripts/RGraph.drawing.image.js +810 -0
  44. data/vendor/assets/javascripts/RGraph.drawing.marker1.js +710 -0
  45. data/vendor/assets/javascripts/RGraph.drawing.marker2.js +672 -0
  46. data/vendor/assets/javascripts/RGraph.drawing.marker3.js +568 -0
  47. data/vendor/assets/javascripts/RGraph.drawing.poly.js +623 -0
  48. data/vendor/assets/javascripts/RGraph.drawing.rect.js +603 -0
  49. data/vendor/assets/javascripts/RGraph.drawing.text.js +648 -0
  50. data/vendor/assets/javascripts/RGraph.drawing.xaxis.js +815 -0
  51. data/vendor/assets/javascripts/RGraph.drawing.yaxis.js +860 -0
  52. data/vendor/assets/javascripts/RGraph.fuel.js +965 -0
  53. data/vendor/assets/javascripts/RGraph.funnel.js +988 -0
  54. data/vendor/assets/javascripts/RGraph.gantt.js +1242 -0
  55. data/vendor/assets/javascripts/RGraph.gauge.js +1391 -0
  56. data/vendor/assets/javascripts/RGraph.hbar.js +1794 -0
  57. data/vendor/assets/javascripts/RGraph.hprogress.js +1307 -0
  58. data/vendor/assets/javascripts/RGraph.line.js +3940 -0
  59. data/vendor/assets/javascripts/RGraph.meter.js +1242 -0
  60. data/vendor/assets/javascripts/RGraph.modaldialog.js +292 -0
  61. data/vendor/assets/javascripts/RGraph.odo.js +1265 -0
  62. data/vendor/assets/javascripts/RGraph.pie.js +1979 -0
  63. data/vendor/assets/javascripts/RGraph.radar.js +1840 -0
  64. data/vendor/assets/javascripts/RGraph.rose.js +1860 -0
  65. data/vendor/assets/javascripts/RGraph.rscatter.js +1332 -0
  66. data/vendor/assets/javascripts/RGraph.scatter.js +3029 -0
  67. data/vendor/assets/javascripts/RGraph.thermometer.js +1131 -0
  68. data/vendor/assets/javascripts/RGraph.vprogress.js +1326 -0
  69. data/vendor/assets/javascripts/RGraph.waterfall.js +1252 -0
  70. data/vendor/assets/javascripts/financial-data.js +1067 -0
  71. data/vendor/assets/stylesheets/ModalDialog.css +90 -0
  72. data/vendor/assets/stylesheets/animations.css +3347 -0
  73. data/vendor/assets/stylesheets/website.css +402 -0
  74. metadata +175 -0
@@ -0,0 +1,3940 @@
1
+ // version: 2015-11-02
2
+ /**
3
+ * o--------------------------------------------------------------------------------o
4
+ * | This file is part of the RGraph package - you can learn more at: |
5
+ * | |
6
+ * | http://www.rgraph.net |
7
+ * | |
8
+ * | RGraph is dual licensed under the Open Source GPL (General Public License) |
9
+ * | v2.0 license and a commercial license which means that you're not bound by |
10
+ * | the terms of the GPL. The commercial license is just �99 (GBP) and you can |
11
+ * | read about it here: |
12
+ * | http://www.rgraph.net/license |
13
+ * o--------------------------------------------------------------------------------o
14
+ */
15
+
16
+ RGraph = window.RGraph || {isRGraph: true};
17
+
18
+ /**
19
+ * The line chart constructor
20
+ *
21
+ * @param object canvas The cxanvas object
22
+ * @param array ... The lines to plot
23
+ */
24
+ RGraph.Line = function (conf)
25
+ {
26
+ /**
27
+ * Allow for object config style
28
+ */
29
+ if ( typeof conf === 'object'
30
+ && typeof conf.data === 'object'
31
+ && typeof conf.id === 'string') {
32
+
33
+ var id = conf.id;
34
+ var canvas = document.getElementById(id);
35
+ var data = conf.data;
36
+ var parseConfObjectForOptions = true; // Set this so the config is parsed (at the end of the constructor)
37
+
38
+ } else {
39
+
40
+ var id = conf;
41
+ var canvas = document.getElementById(id);
42
+ var data = arguments[1];
43
+ }
44
+
45
+
46
+
47
+
48
+ this.id = id;
49
+ this.canvas = canvas;
50
+ this.context = this.canvas.getContext('2d');
51
+ this.canvas.__object__ = this;
52
+ this.type = 'line';
53
+ this.max = 0;
54
+ this.coords = [];
55
+ this.coords2 = [];
56
+ this.coords.key = [];
57
+ this.coordsText = [];
58
+ this.coordsSpline = [];
59
+ this.hasnegativevalues = false;
60
+ this.isRGraph = true;
61
+ this.uid = RGraph.CreateUID();
62
+ this.canvas.uid = this.canvas.uid ? this.canvas.uid : RGraph.CreateUID();
63
+ this.colorsParsed = false;
64
+ this.original_colors = [];
65
+ this.firstDraw = true; // After the first draw this will be false
66
+
67
+
68
+ /**
69
+ * Compatibility with older browsers
70
+ */
71
+ //RGraph.OldBrowserCompat(this.context);
72
+
73
+
74
+ // Various config type stuff
75
+ this.properties =
76
+ {
77
+ 'chart.background.barcolor1': 'rgba(0,0,0,0)',
78
+ 'chart.background.barcolor2': 'rgba(0,0,0,0)',
79
+ 'chart.background.grid': 1,
80
+ 'chart.background.grid.width': 1,
81
+ 'chart.background.grid.hsize': 25,
82
+ 'chart.background.grid.vsize': 25,
83
+ 'chart.background.grid.color': '#ddd',
84
+ 'chart.background.grid.vlines': true,
85
+ 'chart.background.grid.hlines': true,
86
+ 'chart.background.grid.border': true,
87
+ 'chart.background.grid.autofit': true,
88
+ 'chart.background.grid.autofit.align': true,
89
+ 'chart.background.grid.autofit.numhlines': 5,
90
+ 'chart.background.grid.autofit.numvlines': null,
91
+ 'chart.background.grid.dashed': false,
92
+ 'chart.background.grid.dotted': false,
93
+ 'chart.background.hbars': null,
94
+ 'chart.background.image': null,
95
+ 'chart.background.image.stretch': true,
96
+ 'chart.background.image.x': null,
97
+ 'chart.background.image.y': null,
98
+ 'chart.background.image.w': null,
99
+ 'chart.background.image.h': null,
100
+ 'chart.background.image.align': null,
101
+ 'chart.background.color': null,
102
+ 'chart.labels': null,
103
+ 'chart.labels.bold': false,
104
+ 'chart.labels.color': null,
105
+ 'chart.labels.ingraph': null,
106
+ 'chart.labels.above': false, // Working
107
+ 'chart.labels.above.size': 8, // Working
108
+ 'chart.labels.above.decimals': null, // Working
109
+ 'chart.labels.above.color': null,
110
+ 'chart.labels.above.background': 'white',
111
+ 'chart.labels.above.font': null,
112
+ 'chart.labels.above.border': true,
113
+ 'chart.labels.above.offsety': 5,
114
+ 'chart.labels.above.units.pre': '',
115
+ 'chart.labels.above.units.post': '',
116
+ 'chart.labels.above.specific': null,
117
+ 'chart.xtickgap': 20,
118
+ 'chart.smallxticks': 3,
119
+ 'chart.largexticks': 5,
120
+ 'chart.ytickgap': 20,
121
+ 'chart.smallyticks': 3,
122
+ 'chart.largeyticks': 5,
123
+ 'chart.numyticks': 10,
124
+ 'chart.linewidth': 2.01,
125
+ 'chart.colors': ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff','green','pink','blue','black'],
126
+ 'chart.hmargin': 0,
127
+ 'chart.tickmarks.dot.stroke': 'white',
128
+ 'chart.tickmarks.dot.fill': null,
129
+ 'chart.tickmarks.dot.linewidth': 3,
130
+ 'chart.tickmarks': 'endcircle',
131
+ 'chart.tickmarks.linewidth': null,
132
+ 'chart.tickmarks.image': null,
133
+ 'chart.tickmarks.image.halign': 'center',
134
+ 'chart.tickmarks.image.valign': 'center',
135
+ 'chart.tickmarks.image.offsetx':0,
136
+ 'chart.tickmarks.image.offsety':0,
137
+ 'chart.ticksize': 3,
138
+ 'chart.gutter.left': 25,
139
+ 'chart.gutter.right': 25,
140
+ 'chart.gutter.top': 25,
141
+ 'chart.gutter.bottom': 30,
142
+ 'chart.tickdirection': -1,
143
+ 'chart.yaxispoints': 5,
144
+ 'chart.fillstyle': null,
145
+ 'chart.xaxispos': 'bottom',
146
+ 'chart.yaxispos': 'left',
147
+ 'chart.xticks': null,
148
+ 'chart.text.size': 12,
149
+ 'chart.text.angle': 0,
150
+ 'chart.text.color': 'black',
151
+ 'chart.text.font': 'Arial',
152
+ 'chart.ymin': 0,
153
+ 'chart.ymax': null,
154
+ 'chart.title': '',
155
+ 'chart.title.background': null,
156
+ 'chart.title.hpos': null,
157
+ 'chart.title.vpos': null,
158
+ 'chart.title.bold': true,
159
+ 'chart.title.font': null,
160
+ 'chart.title.xaxis': '',
161
+ 'chart.title.xaxis.bold': true,
162
+ 'chart.title.xaxis.size': null,
163
+ 'chart.title.xaxis.font': null,
164
+ 'chart.title.yaxis': '',
165
+ 'chart.title.yaxis.bold': true,
166
+ 'chart.title.yaxis.size': null,
167
+ 'chart.title.yaxis.font': null,
168
+ 'chart.title.yaxis.color': null,
169
+ 'chart.title.xaxis.pos': null,
170
+ 'chart.title.yaxis.pos': null,
171
+ 'chart.title.yaxis.x': null,
172
+ 'chart.title.yaxis.y': null,
173
+ 'chart.title.xaxis.x': null,
174
+ 'chart.title.xaxis.y': null,
175
+ 'chart.title.x': null,
176
+ 'chart.title.y': null,
177
+ 'chart.title.halign': null,
178
+ 'chart.title.valign': null,
179
+ 'chart.shadow': true,
180
+ 'chart.shadow.offsetx': 2,
181
+ 'chart.shadow.offsety': 2,
182
+ 'chart.shadow.blur': 3,
183
+ 'chart.shadow.color': 'rgba(128,128,128,0.5)',
184
+ 'chart.tooltips': null,
185
+ 'chart.tooltips.hotspot.xonly': false,
186
+ 'chart.tooltips.hotspot.size': 5,
187
+ 'chart.tooltips.effect': 'fade',
188
+ 'chart.tooltips.css.class': 'RGraph_tooltip',
189
+ 'chart.tooltips.event': 'onmousemove',
190
+ 'chart.tooltips.highlight': true,
191
+ 'chart.tooltips.coords.page': false,
192
+ 'chart.highlight.stroke': 'gray',
193
+ 'chart.highlight.fill': 'white',
194
+ 'chart.stepped': false,
195
+ 'chart.key': null,
196
+ 'chart.key.background': 'white',
197
+ 'chart.key.position': 'graph',
198
+ 'chart.key.halign': null,
199
+ 'chart.key.shadow': false,
200
+ 'chart.key.shadow.color': '#666',
201
+ 'chart.key.shadow.blur': 3,
202
+ 'chart.key.shadow.offsetx': 2,
203
+ 'chart.key.shadow.offsety': 2,
204
+ 'chart.key.position.gutter.boxed': false,
205
+ 'chart.key.position.x': null,
206
+ 'chart.key.position.y': null,
207
+ 'chart.key.color.shape': 'square',
208
+ 'chart.key.rounded': true,
209
+ 'chart.key.linewidth': 1,
210
+ 'chart.key.colors': null,
211
+ 'chart.key.interactive': false,
212
+ 'chart.key.interactive.highlight.chart.stroke': 'rgba(255,0,0,0.3)',
213
+ 'chart.key.interactive.highlight.label': 'rgba(255,0,0,0.2)',
214
+ 'chart.key.text.color': 'black',
215
+ 'chart.contextmenu': null,
216
+ 'chart.ylabels': true,
217
+ 'chart.ylabels.count': 5,
218
+ 'chart.ylabels.inside': false,
219
+ 'chart.scale.invert': false,
220
+ 'chart.xlabels.inside': false,
221
+ 'chart.xlabels.inside.color': 'rgba(255,255,255,0.5)',
222
+ 'chart.noaxes': false,
223
+ 'chart.noyaxis': false,
224
+ 'chart.noxaxis': false,
225
+ 'chart.noendxtick': false,
226
+ 'chart.noendytick': false,
227
+ 'chart.units.post': '',
228
+ 'chart.units.pre': '',
229
+ 'chart.scale.zerostart': false,
230
+ 'chart.scale.decimals': null,
231
+ 'chart.scale.point': '.',
232
+ 'chart.scale.thousand': ',',
233
+ 'chart.crosshairs': false,
234
+ 'chart.crosshairs.color': '#333',
235
+ 'chart.crosshairs.hline': true,
236
+ 'chart.crosshairs.vline': true,
237
+ 'chart.annotatable': false,
238
+ 'chart.annotate.color': 'black',
239
+ 'chart.axesontop': false,
240
+ 'chart.filled': false,
241
+ 'chart.filled.range': false,
242
+ 'chart.filled.range.threshold': null,
243
+ 'chart.filled.range.threshold.colors': ['red', 'green'],
244
+ 'chart.filled.accumulative': true,
245
+ 'chart.variant': null,
246
+ 'chart.axis.color': 'black',
247
+ 'chart.axis.linewidth': 1,
248
+ 'chart.numxticks': (data && typeof(data[0]) == 'number' ? data.length - 1: 20),
249
+ 'chart.numyticks': 10,
250
+ 'chart.zoom.factor': 1.5,
251
+ 'chart.zoom.fade.in': true,
252
+ 'chart.zoom.fade.out': true,
253
+ 'chart.zoom.hdir': 'right',
254
+ 'chart.zoom.vdir': 'down',
255
+ 'chart.zoom.frames': 25,
256
+ 'chart.zoom.delay': 16.666,
257
+ 'chart.zoom.shadow': true,
258
+ 'chart.zoom.background': true,
259
+ 'chart.zoom.action': 'zoom',
260
+ 'chart.backdrop': false,
261
+ 'chart.backdrop.size': 30,
262
+ 'chart.backdrop.alpha': 0.2,
263
+ 'chart.resizable': false,
264
+ 'chart.resize.handle.adjust': [0,0],
265
+ 'chart.resize.handle.background': null,
266
+ 'chart.adjustable': false,
267
+ 'chart.noredraw': false,
268
+ 'chart.outofbounds': false,
269
+ 'chart.chromefix': true,
270
+ 'chart.animation.factor': 1,
271
+ 'chart.animation.unfold.x': false,
272
+ 'chart.animation.unfold.y': true,
273
+ 'chart.animation.unfold.initial': 2,
274
+ 'chart.animation.trace.clip': 1,
275
+ 'chart.curvy': false,
276
+ 'chart.line.visible': true,
277
+ 'chart.events.click': null,
278
+ 'chart.events.mousemove': null
279
+ }
280
+
281
+ /**
282
+ * Change null arguments to empty arrays
283
+ */
284
+ for (var i=1; i<arguments.length; ++i) {
285
+ if (typeof(arguments[i]) == 'null' || !arguments[i]) {
286
+ arguments[i] = [];
287
+ }
288
+ }
289
+
290
+
291
+ /**
292
+ * Store the original data. This also allows for giving arguments as one big array.
293
+ */
294
+ this.original_data = [];
295
+
296
+ // This allows for the new object based configuration style
297
+ if (typeof conf === 'object' && conf.data) {
298
+ if (typeof conf.data[0] === 'number' || RGraph.isNull(conf.data[0])) {
299
+
300
+ this.original_data[0] = RGraph.arrayClone(conf.data);
301
+
302
+ //} else if (typeof conf.data[0] === 'object' && !RGraph.isNull(conf.data[0])) {
303
+ } else {
304
+
305
+ for (var i=0; i<conf.data.length; ++i) {
306
+ this.original_data[i] = RGraph.arrayClone(conf.data[i]);
307
+ }
308
+ }
309
+
310
+ // Allow for the older configuration style
311
+ } else {
312
+ for (var i=1; i<arguments.length; ++i) {
313
+
314
+ if ( arguments[1]
315
+ && typeof(arguments[1]) == 'object'
316
+ && arguments[1][0]
317
+ && typeof(arguments[1][0]) == 'object'
318
+ && arguments[1][0].length) {
319
+
320
+ var tmp = [];
321
+
322
+ for (var i=0; i<arguments[1].length; ++i) {
323
+ tmp[i] = RGraph.array_clone(arguments[1][i]);
324
+ }
325
+
326
+ for (var j=0; j<tmp.length; ++j) {
327
+ this.original_data[j] = RGraph.array_clone(tmp[j]);
328
+ }
329
+
330
+ } else {
331
+ this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
332
+ }
333
+ }
334
+ }
335
+
336
+
337
+ // Check for support
338
+ if (!this.canvas) {
339
+ alert('[LINE] Fatal error: no canvas support');
340
+ return;
341
+ }
342
+
343
+ /**
344
+ * Store the data here as one big array
345
+ */
346
+ this.data_arr = RGraph.array_linearize(this.original_data);
347
+
348
+ for (var i=0; i<this.data_arr.length; ++i) {
349
+ this['$' + i] = {};
350
+ }
351
+
352
+
353
+ /**
354
+ * Translate half a pixel for antialiasing purposes - but only if it hasn't beeen
355
+ * done already
356
+ */
357
+ if (!this.canvas.__rgraph_aa_translated__) {
358
+ this.context.translate(0.5,0.5);
359
+
360
+ this.canvas.__rgraph_aa_translated__ = true;
361
+ }
362
+
363
+
364
+
365
+
366
+ // Short variable names
367
+ var RG = RGraph,
368
+ ca = this.canvas,
369
+ co = ca.getContext('2d'),
370
+ prop = this.properties,
371
+ pa = RG.Path,
372
+ pa2 = RG.path2,
373
+ win = window,
374
+ doc = document,
375
+ ma = Math
376
+
377
+
378
+
379
+ /**
380
+ * "Decorate" the object with the generic effects if the effects library has been included
381
+ */
382
+ if (RG.Effects && typeof RG.Effects.decorate === 'function') {
383
+ RG.Effects.decorate(this);
384
+ }
385
+
386
+ //
387
+ // Wrap the canvas with a DIV so that DOM text can be positioned
388
+ // accurately
389
+ //
390
+ //RG.wrap(ca);
391
+
392
+
393
+
394
+ /**
395
+ * An all encompassing accessor
396
+ *
397
+ * @param string name The name of the property
398
+ * @param mixed value The value of the property
399
+ */
400
+ this.set =
401
+ this.Set = function (name)
402
+ {
403
+ var value = typeof arguments[1] === 'undefined' ? null : arguments[1];
404
+
405
+ /**
406
+ * the number of arguments is only one and it's an
407
+ * object - parse it for configuration data and return.
408
+ */
409
+ if (arguments.length === 1 && typeof name === 'object') {
410
+ RG.parseObjectStyleConfig(this, name);
411
+ return this;
412
+ }
413
+
414
+
415
+
416
+
417
+
418
+ /**
419
+ * This should be done first - prepend the propertyy name with "chart." if necessary
420
+ */
421
+ if (name.substr(0,6) != 'chart.') {
422
+ name = 'chart.' + name;
423
+ }
424
+
425
+
426
+
427
+
428
+ // Convert uppercase letters to dot+lower case letter
429
+ name = name.replace(/([A-Z])/g, function (str)
430
+ {
431
+ return '.' + String(RegExp.$1).toLowerCase();
432
+ });
433
+
434
+
435
+
436
+ // Consolidate the tooltips
437
+ if (name == 'chart.tooltips' && typeof value == 'object' && value) {
438
+
439
+ var tooltips = [];
440
+
441
+ for (var i=1; i<arguments.length; i++) {
442
+ if (typeof(arguments[i]) == 'object' && arguments[i][0]) {
443
+ for (var j=0; j<arguments[i].length; j++) {
444
+ tooltips.push(arguments[i][j]);
445
+ }
446
+
447
+ } else if (typeof(arguments[i]) == 'function') {
448
+ tooltips = arguments[i];
449
+
450
+ } else {
451
+ tooltips.push(arguments[i]);
452
+ }
453
+ }
454
+
455
+ // Because "value" is used further down at the end of this function, set it to the expanded array os tooltips
456
+ value = tooltips;
457
+ }
458
+
459
+
460
+ /**
461
+ * If (buggy) Chrome and the linewidth is 1, change it to 1.01
462
+ */
463
+ if (name == 'chart.linewidth' && navigator.userAgent.match(/Chrome/)) {
464
+ if (value == 1) {
465
+ value = 1.01;
466
+
467
+ } else if (RGraph.is_array(value)) {
468
+ for (var i=0; i<value.length; ++i) {
469
+ if (typeof(value[i]) == 'number' && value[i] == 1) {
470
+ value[i] = 1.01;
471
+ }
472
+ }
473
+ }
474
+ }
475
+
476
+
477
+ /**
478
+ * Check for xaxispos
479
+ */
480
+ if (name == 'chart.xaxispos' ) {
481
+ if (value != 'bottom' && value != 'center' && value != 'top') {
482
+ alert('[LINE] (' + this.id + ') chart.xaxispos should be top, center or bottom. Tried to set it to: ' + value + ' Changing it to center');
483
+ value = 'center';
484
+ }
485
+ }
486
+
487
+
488
+ /**
489
+ * chart.xticks is now called chart.numxticks
490
+ */
491
+ if (name == 'chart.xticks') {
492
+ name = 'chart.numxticks';
493
+ }
494
+
495
+
496
+ /**
497
+ * Change the new chart.spline option to chart.curvy
498
+ */
499
+ if (name == 'chart.spline') {
500
+ name = 'chart.curvy';
501
+ }
502
+
503
+
504
+ /**
505
+ * Chnge chart.ylabels.invert to chart.scale.invert
506
+ */
507
+ if (name == 'chart.ylabels.invert') {
508
+ name = 'chart.scale.invert';
509
+ }
510
+
511
+
512
+
513
+
514
+
515
+
516
+
517
+ this.properties[name] = value;
518
+
519
+ return this;
520
+ };
521
+
522
+
523
+
524
+
525
+ /**
526
+ * An all encompassing accessor
527
+ *
528
+ * @param string name The name of the property
529
+ */
530
+ this.get =
531
+ this.Get = function (name)
532
+ {
533
+ /**
534
+ * This should be done first - prepend the property name with "chart." if necessary
535
+ */
536
+ if (name.substr(0,6) != 'chart.') {
537
+ name = 'chart.' + name;
538
+ }
539
+
540
+ // Convert uppercase letters to dot+lower case letter
541
+ name = name.replace(/([A-Z])/g, function (str)
542
+ {
543
+ return '.' + String(RegExp.$1).toLowerCase()
544
+ });
545
+
546
+ /**
547
+ * If requested property is chart.spline - change it to chart.curvy
548
+ */
549
+ if (name == 'chart.spline') {
550
+ name = 'chart.curvy';
551
+ }
552
+
553
+ return prop[name];
554
+ };
555
+
556
+
557
+
558
+
559
+ /**
560
+ * The function you call to draw the line chart
561
+ *
562
+ * @param bool An optional bool used internally to ditinguish whether the
563
+ * line chart is being called by the bar chart
564
+ *
565
+ * Draw()
566
+ * |
567
+ * +--Draw()
568
+ * | |
569
+ * | +-DrawLine()
570
+ * |
571
+ * +-RedrawLine()
572
+ * |
573
+ * +-DrawCurvyLine()
574
+ * |
575
+ * +-DrawSpline()
576
+ */
577
+ this.draw =
578
+ this.Draw = function ()
579
+ {
580
+ // MUST be the first thing done!
581
+ if (typeof(prop['chart.background.image']) == 'string') {
582
+ RG.DrawBackgroundImage(this);
583
+ }
584
+
585
+
586
+ /**
587
+ * Fire the onbeforedraw event
588
+ */
589
+ RG.FireCustomEvent(this, 'onbeforedraw');
590
+
591
+
592
+
593
+
594
+
595
+
596
+ /**
597
+ * Parse the colors. This allows for simple gradient syntax
598
+ */
599
+ if (!this.colorsParsed) {
600
+
601
+ this.parseColors();
602
+
603
+ // Don't want to do this again
604
+ this.colorsParsed = true;
605
+ }
606
+
607
+
608
+
609
+ /**
610
+ * This is new in May 2011 and facilitates indiviual gutter settings,
611
+ * eg chart.gutter.left
612
+ */
613
+ this.gutterLeft = prop['chart.gutter.left'];
614
+ this.gutterRight = prop['chart.gutter.right'];
615
+ this.gutterTop = prop['chart.gutter.top'];
616
+ this.gutterBottom = prop['chart.gutter.bottom'];
617
+
618
+
619
+ /**
620
+ * Check for Chrome 6 and shadow
621
+ *
622
+ * TODO Remove once it's been fixed (for a while)
623
+ * 07/03/2014 - Removed
624
+ * 29/10/2011 - Looks like it's been fixed as long the linewidth is at least 1.01
625
+ * SEARCH TAGS: CHROME FIX SHADOW BUG
626
+ */
627
+ //if ( prop['chart.shadow']
628
+ // && RG.ISCHROME
629
+ // && prop['chart.linewidth'] <= 1
630
+ // && prop['chart.chromefix']
631
+ // && prop['chart.shadow.blur'] > 0) {
632
+ // alert('[RGRAPH WARNING] Chrome has a shadow bug, meaning you should increase the linewidth to at least 1.01');
633
+ //}
634
+
635
+
636
+ // Reset the data back to that which was initially supplied
637
+ this.data = RG.array_clone(this.original_data);
638
+
639
+
640
+ // Reset the max value
641
+ this.max = 0;
642
+
643
+ /**
644
+ * Reverse the datasets so that the data and the labels tally
645
+ * COMMENTED OUT 15TH AUGUST 2011
646
+ */
647
+ //this.data = RG.array_reverse(this.data);
648
+
649
+ if (prop['chart.filled'] && !prop['chart.filled.range'] && this.data.length > 1 && prop['chart.filled.accumulative']) {
650
+
651
+ var accumulation = [];
652
+
653
+ for (var set=0; set<this.data.length; ++set) {
654
+ for (var point=0; point<this.data[set].length; ++point) {
655
+ this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
656
+ accumulation[point] = this.data[set][point];
657
+ }
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Get the maximum Y scale value
663
+ */
664
+ if (prop['chart.ymax']) {
665
+
666
+ this.max = prop['chart.ymax'];
667
+ this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
668
+
669
+ this.scale2 = RG.getScale2(this, {
670
+ 'max':this.max,
671
+ 'min':prop['chart.ymin'],
672
+ 'strict':true,
673
+ 'scale.thousand':prop['chart.scale.thousand'],
674
+ 'scale.point':prop['chart.scale.point'],
675
+ 'scale.decimals':prop['chart.scale.decimals'],
676
+ 'ylabels.count':prop['chart.ylabels.count'],
677
+ 'scale.round':prop['chart.scale.round'],
678
+ 'units.pre': prop['chart.units.pre'],
679
+ 'units.post': prop['chart.units.post']
680
+ });
681
+
682
+ this.max = this.scale2.max ? this.scale2.max : 0;
683
+
684
+ // Check for negative values
685
+ if (!prop['chart.outofbounds']) {
686
+ for (dataset=0; dataset<this.data.length; ++dataset) {
687
+ if (RGraph.isArray(this.data[dataset])) {
688
+ for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
689
+ // Check for negative values
690
+ this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
691
+ }
692
+ }
693
+ }
694
+ }
695
+
696
+ } else {
697
+
698
+ this.min = prop['chart.ymin'] ? prop['chart.ymin'] : 0;
699
+
700
+ // Work out the max Y value
701
+ for (dataset=0; dataset<this.data.length; ++dataset) {
702
+ for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
703
+
704
+ this.max = Math.max(this.max, this.data[dataset][datapoint] ? Math.abs(parseFloat(this.data[dataset][datapoint])) : 0);
705
+
706
+ // Check for negative values
707
+ if (!prop['chart.outofbounds']) {
708
+ this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
709
+ }
710
+ }
711
+ }
712
+
713
+ this.scale2 = RG.getScale2(this, {
714
+ 'max':this.max,
715
+ 'min':prop['chart.ymin'],
716
+ 'scale.thousand':prop['chart.scale.thousand'],
717
+ 'scale.point':prop['chart.scale.point'],
718
+ 'scale.decimals':prop['chart.scale.decimals'],
719
+ 'ylabels.count':prop['chart.ylabels.count'],
720
+ 'scale.round':prop['chart.scale.round'],
721
+ 'units.pre': prop['chart.units.pre'],
722
+ 'units.post': prop['chart.units.post']
723
+ });
724
+
725
+ this.max = this.scale2.max ? this.scale2.max : 0;
726
+ }
727
+
728
+ /**
729
+ * Setup the context menu if required
730
+ */
731
+ if (prop['chart.contextmenu']) {
732
+ RG.ShowContext(this);
733
+ }
734
+
735
+ /**
736
+ * Reset the coords arrays otherwise it will keep growing
737
+ */
738
+ this.coords = [];
739
+ this.coordsText = [];
740
+
741
+ /**
742
+ * Work out a few things. They need to be here because they depend on things you can change before you
743
+ * call Draw() but after you instantiate the object
744
+ */
745
+ this.grapharea = ca.height - this.gutterTop - this.gutterBottom;
746
+ this.halfgrapharea = this.grapharea / 2;
747
+ this.halfTextHeight = prop['chart.text.size'] / 2;
748
+
749
+ // Check the combination of the X axis position and if there any negative values
750
+ //
751
+ // 19th Dec 2010 - removed for Opera since it can be reported incorrectly whn there
752
+ // are multiple graphs on the page
753
+ if (prop['chart.xaxispos'] == 'bottom' && this.hasnegativevalues && !RG.ISOPERA) {
754
+ alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
755
+ }
756
+
757
+ if (prop['chart.variant'] == '3d') {
758
+ RG.Draw3DAxes(this);
759
+ }
760
+
761
+ // Progressively Draw the chart
762
+ RG.background.Draw(this);
763
+
764
+
765
+ /**
766
+ * Draw any horizontal bars that have been defined
767
+ */
768
+ if (prop['chart.background.hbars'] && prop['chart.background.hbars'].length > 0) {
769
+ RG.DrawBars(this);
770
+ }
771
+
772
+ if (prop['chart.axesontop'] == false) {
773
+ this.DrawAxes();
774
+ }
775
+
776
+ //if (typeof(shadowColor) == 'object') {
777
+ // shadowColor = RG.array_reverse(RG.array_clone(prop['chart.shadow.color']]);
778
+ //}
779
+
780
+ /**
781
+ * This facilitates the new Trace2 effect
782
+ */
783
+
784
+ co.save()
785
+ co.beginPath();
786
+ co.rect(0, 0, ca.width * prop['chart.animation.trace.clip'], ca.height);
787
+ co.clip();
788
+
789
+ for (var i=0, j=0, len=this.data.length; i<len; i++, j++) {
790
+
791
+ co.beginPath();
792
+
793
+ /**
794
+ * Turn on the shadow if required
795
+ */
796
+ if (!prop['chart.filled']) {
797
+ this.SetShadow(i);
798
+ }
799
+
800
+ /**
801
+ * Draw the line
802
+ */
803
+
804
+ if (prop['chart.fillstyle']) {
805
+ if (typeof(prop['chart.fillstyle']) == 'object' && prop['chart.fillstyle'][j]) {
806
+ var fill = prop['chart.fillstyle'][j];
807
+
808
+ } else if (typeof(prop['chart.fillstyle']) == 'object' && prop['chart.fillstyle'].toString().indexOf('Gradient') > 0) {
809
+ var fill = prop['chart.fillstyle'];
810
+
811
+ } else if (typeof(prop['chart.fillstyle']) == 'string') {
812
+ var fill = prop['chart.fillstyle'];
813
+
814
+ }
815
+ } else if (prop['chart.filled']) {
816
+ var fill = prop['chart.colors'][j];
817
+
818
+ } else {
819
+ var fill = null;
820
+ }
821
+
822
+ /**
823
+ * Figure out the tickmark to use
824
+ */
825
+ if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'object') {
826
+ var tickmarks = prop['chart.tickmarks'][i];
827
+ } else if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'string') {
828
+ var tickmarks = prop['chart.tickmarks'];
829
+ } else if (prop['chart.tickmarks'] && typeof(prop['chart.tickmarks']) == 'function') {
830
+ var tickmarks = prop['chart.tickmarks'];
831
+ } else {
832
+ var tickmarks = null;
833
+ }
834
+
835
+
836
+ this.DrawLine(this.data[i],
837
+ prop['chart.colors'][j],
838
+ fill,
839
+ this.GetLineWidth(j),
840
+ tickmarks,
841
+ i);
842
+
843
+ co.stroke();
844
+ }
845
+
846
+ /**
847
+ * If the line is filled re-stroke the lines
848
+ */
849
+ if (prop['chart.filled'] && prop['chart.filled.accumulative'] && !prop['chart.curvy']) {
850
+
851
+ for (var i=0; i<this.coords2.length; ++i) {
852
+
853
+ co.beginPath();
854
+ co.lineWidth = this.GetLineWidth(i);
855
+ co.strokeStyle = prop['chart.colors'][i];
856
+
857
+ for (var j=0,len=this.coords2[i].length; j<len; ++j) {
858
+
859
+ if (j == 0 || this.coords2[i][j][1] == null || (this.coords2[i][j - 1] && this.coords2[i][j - 1][1] == null)) {
860
+ co.moveTo(this.coords2[i][j][0], this.coords2[i][j][1]);
861
+ } else {
862
+ if (prop['chart.stepped']) {
863
+ co.lineTo(this.coords2[i][j][0], this.coords2[i][j - 1][1]);
864
+ }
865
+ co.lineTo(this.coords2[i][j][0], this.coords2[i][j][1]);
866
+ }
867
+ }
868
+
869
+ co.stroke();
870
+ // No fill!
871
+ }
872
+
873
+ //Redraw the tickmarks
874
+ if (prop['chart.tickmarks']) {
875
+
876
+ co.beginPath();
877
+
878
+ co.fillStyle = 'white';
879
+
880
+ for (var i=0,len=this.coords2.length; i<len; ++i) {
881
+
882
+ co.beginPath();
883
+ co.strokeStyle = prop['chart.colors'][i];
884
+
885
+ for (var j=0; j<this.coords2[i].length; ++j) {
886
+ if (typeof(this.coords2[i][j]) == 'object' && typeof(this.coords2[i][j][0]) == 'number' && typeof(this.coords2[i][j][1]) == 'number') {
887
+
888
+ var tickmarks = typeof(prop['chart.tickmarks']) == 'object' ? prop['chart.tickmarks'][i] : prop['chart.tickmarks'];
889
+
890
+ this.DrawTick( this.coords2[i],
891
+ this.coords2[i][j][0],
892
+ this.coords2[i][j][1],
893
+ co.strokeStyle,
894
+ false,
895
+ j == 0 ? 0 : this.coords2[i][j - 1][0],
896
+ j == 0 ? 0 : this.coords2[i][j - 1][1],
897
+ tickmarks,
898
+ j);
899
+ }
900
+ }
901
+ }
902
+
903
+ co.stroke();
904
+ co.fill();
905
+ }
906
+
907
+ } else if (prop['chart.filled'] && prop['chart.filled.accumulative'] && prop['chart.curvy']) {
908
+
909
+ // Restroke the curvy filled accumulative lines
910
+
911
+ for (var i=0; i<this.coordsSpline.length; i+=1) {
912
+ co.beginPath();
913
+ co.strokeStyle = prop['chart.colors'][i];
914
+ co.lineWidth = this.GetLineWidth(i);
915
+
916
+ for (var j=0,len=this.coordsSpline[i].length; j<len; j+=1) {
917
+
918
+ var point = this.coordsSpline[i][j];
919
+
920
+ j == 0 ? co.moveTo(point[0], point[1]) : co.lineTo(point[0], point[1]);
921
+ }
922
+
923
+ co.stroke();
924
+ }
925
+
926
+
927
+
928
+ for (var i=0,len=this.coords2.length; i<len; i+=1) {
929
+ for (var j=0,len2=this.coords2[i].length; j<len2; ++j) {
930
+ if (typeof(this.coords2[i][j]) == 'object' && typeof(this.coords2[i][j][0]) == 'number' && typeof(this.coords2[i][j][1]) == 'number') {
931
+
932
+ var tickmarks = typeof prop['chart.tickmarks'] == 'object' && !RGraph.is_null(prop['chart.tickmarks']) ? prop['chart.tickmarks'][i] : prop['chart.tickmarks'];
933
+ co.strokeStyle = prop['chart.colors'][i];
934
+ this.DrawTick( this.coords2[i],
935
+ this.coords2[i][j][0],
936
+ this.coords2[i][j][1],
937
+ prop['chart.colors'][i],
938
+ false,
939
+ j == 0 ? 0 : this.coords2[i][j - 1][0],
940
+ j == 0 ? 0 : this.coords2[i][j - 1][1],
941
+ tickmarks,
942
+ j);
943
+ }
944
+ }
945
+ }
946
+
947
+
948
+
949
+ }
950
+ co.restore();
951
+
952
+ // ???
953
+ co.beginPath();
954
+
955
+
956
+
957
+
958
+ /**
959
+ * If the axes have been requested to be on top, do that
960
+ */
961
+ if (prop['chart.axesontop']) {
962
+ this.DrawAxes();
963
+ }
964
+
965
+ /**
966
+ * Draw the labels
967
+ */
968
+ this.DrawLabels();
969
+
970
+ /**
971
+ * Draw the range if necessary
972
+ */
973
+ this.DrawRange();
974
+
975
+ // Draw a key if necessary
976
+ if (prop['chart.key'] && prop['chart.key'].length && RG.DrawKey) {
977
+ RG.DrawKey(this, prop['chart.key'], prop['chart.colors']);
978
+ }
979
+
980
+ /**
981
+ * Draw " above" labels if enabled
982
+ */
983
+ if (prop['chart.labels.above']) {
984
+ this.drawAboveLabels();
985
+ }
986
+
987
+ /**
988
+ * Draw the "in graph" labels
989
+ */
990
+ RG.DrawInGraphLabels(this);
991
+
992
+ /**
993
+ * Redraw the lines if a filled range is on the cards
994
+ */
995
+ if (prop['chart.filled'] && prop['chart.filled.range'] && this.data.length == 2) {
996
+
997
+ co.beginPath();
998
+ var len = this.coords.length / 2;
999
+ co.lineWidth = prop['chart.linewidth'];
1000
+ co.strokeStyle = prop['chart.colors'][0];
1001
+
1002
+ for (var i=0; i<len; ++i) {
1003
+
1004
+ if (!RG.is_null(this.coords[i][1])) {
1005
+ if (i == 0) {
1006
+ co.moveTo(this.coords[i][0], this.coords[i][1]);
1007
+ } else {
1008
+ co.lineTo(this.coords[i][0], this.coords[i][1]);
1009
+ }
1010
+ }
1011
+ }
1012
+
1013
+ co.stroke();
1014
+
1015
+
1016
+ co.beginPath();
1017
+
1018
+ if (prop['chart.colors'][1]) {
1019
+ co.strokeStyle = prop['chart.colors'][1];
1020
+ }
1021
+
1022
+ for (var i=this.coords.length - 1; i>=len; --i) {
1023
+ if (!RG.is_null(this.coords[i][1])) {
1024
+ if (i == (this.coords.length - 1)) {
1025
+ co.moveTo(this.coords[i][0], this.coords[i][1]);
1026
+ } else {
1027
+ co.lineTo(this.coords[i][0], this.coords[i][1]);
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ co.stroke();
1033
+
1034
+
1035
+ } else if (prop['chart.filled'] && prop['chart.filled.range']) {
1036
+ alert('[LINE] You must have only two sets of data for a filled range chart');
1037
+ }
1038
+
1039
+ /**
1040
+ * This function enables resizing
1041
+ */
1042
+ if (prop['chart.resizable']) {
1043
+ RG.AllowResizing(this);
1044
+ }
1045
+
1046
+
1047
+ /**
1048
+ * This installs the event listeners
1049
+ */
1050
+ RG.InstallEventListeners(this);
1051
+
1052
+
1053
+
1054
+
1055
+
1056
+
1057
+ /**
1058
+ * Fire the onfirstdraw event
1059
+ */
1060
+ if (this.firstDraw) {
1061
+ RG.fireCustomEvent(this, 'onfirstdraw');
1062
+ this.firstDraw = false;
1063
+ this.firstDrawFunc();
1064
+ }
1065
+
1066
+
1067
+
1068
+
1069
+ /**
1070
+ * Fire the RGraph ondraw event
1071
+ */
1072
+ RG.FireCustomEvent(this, 'ondraw');
1073
+
1074
+ return this;
1075
+ };
1076
+
1077
+
1078
+
1079
+ /**
1080
+ * Used in chaining. Runs a function there and then - not waiting for
1081
+ * the events to fire (eg the onbeforedraw event)
1082
+ *
1083
+ * @param function func The function to execute
1084
+ */
1085
+ this.exec = function (func)
1086
+ {
1087
+ func(this);
1088
+
1089
+ return this;
1090
+ };
1091
+
1092
+
1093
+
1094
+
1095
+ /**
1096
+ * Draws the axes
1097
+ */
1098
+ this.drawAxes =
1099
+ this.DrawAxes = function ()
1100
+ {
1101
+ //var RG = RGraph;
1102
+ //var ca = this.canvas;
1103
+ //var co = this.context;
1104
+ //var prop = this.properties;
1105
+
1106
+ // Don't draw the axes?
1107
+ if (prop['chart.noaxes']) {
1108
+ return;
1109
+ }
1110
+
1111
+ // Turn any shadow off
1112
+ RG.noShadow(this);
1113
+
1114
+ co.lineWidth = prop['chart.axis.linewidth'] + 0.001;
1115
+ co.lineCap = 'butt';
1116
+ co.strokeStyle = prop['chart.axis.color'];
1117
+ co.beginPath();
1118
+
1119
+ // Draw the X axis
1120
+ if (prop['chart.noxaxis'] == false) {
1121
+ if (prop['chart.xaxispos'] == 'center') {
1122
+ co.moveTo(this.gutterLeft, Math.round((this.grapharea / 2) + this.gutterTop));
1123
+ co.lineTo(ca.width - this.gutterRight, Math.round((this.grapharea / 2) + this.gutterTop));
1124
+ } else if (prop['chart.xaxispos'] === 'top') {
1125
+ co.moveTo(this.gutterLeft, this.gutterTop);
1126
+ co.lineTo(ca.width - this.gutterRight, this.gutterTop);
1127
+ } else {
1128
+ co.moveTo(this.gutterLeft, ca.height - this.gutterBottom);
1129
+ co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
1130
+ }
1131
+ }
1132
+
1133
+ // Draw the Y axis
1134
+ if (prop['chart.noyaxis'] == false) {
1135
+ if (prop['chart.yaxispos'] == 'left') {
1136
+ co.moveTo(this.gutterLeft, this.gutterTop);
1137
+ co.lineTo(this.gutterLeft, ca.height - this.gutterBottom);
1138
+ } else {
1139
+ co.moveTo(ca.width - this.gutterRight, this.gutterTop);
1140
+ co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * Draw the X tickmarks
1146
+ */
1147
+ if (prop['chart.noxaxis'] == false && prop['chart.numxticks'] > 0) {
1148
+
1149
+ var xTickInterval = (ca.width - this.gutterLeft - this.gutterRight) / prop['chart.numxticks'];
1150
+
1151
+
1152
+ if (!xTickInterval || xTickInterval <= 0) {
1153
+ xTickInterval = (ca.width - this.gutterLeft - this.gutterRight) / (prop['chart.labels'] && prop['chart.labels'].length ? prop['chart.labels'].length - 1 : 10);
1154
+ }
1155
+
1156
+ for (x=this.gutterLeft + (prop['chart.yaxispos'] == 'left' ? xTickInterval : 0); x<=(ca.width - this.gutterRight + 1 ); x+=xTickInterval) {
1157
+
1158
+ if (prop['chart.yaxispos'] == 'right' && x >= (ca.width - this.gutterRight - 1) ) {
1159
+ break;
1160
+ }
1161
+
1162
+ // If the last tick is not desired...
1163
+ if (prop['chart.noendxtick']) {
1164
+ if (prop['chart.yaxispos'] == 'left' && x >= (ca.width - this.gutterRight - 1)) {
1165
+ break;
1166
+ } else if (prop['chart.yaxispos'] == 'right' && x == this.gutterLeft) {
1167
+ continue;
1168
+ }
1169
+ }
1170
+
1171
+ var yStart = prop['chart.xaxispos'] == 'center' ? (this.gutterTop + (this.grapharea / 2)) - 3 : ca.height - this.gutterBottom;
1172
+ var yEnd = prop['chart.xaxispos'] == 'center' ? yStart + 6 : ca.height - this.gutterBottom - (x % 60 == 0 ? prop['chart.largexticks'] * prop['chart.tickdirection'] : prop['chart.smallxticks'] * prop['chart.tickdirection']);
1173
+
1174
+ if (prop['chart.xaxispos'] == 'center') {
1175
+ var yStart = Math.round((this.gutterTop + (this.grapharea / 2))) - 3;
1176
+ var yEnd = yStart + 6;
1177
+
1178
+ } else if (prop['chart.xaxispos'] == 'bottom') {
1179
+ var yStart = ca.height - this.gutterBottom;
1180
+ var yEnd = ca.height - this.gutterBottom - (x % 60 == 0 ? prop['chart.largexticks'] * prop['chart.tickdirection'] : prop['chart.smallxticks'] * prop['chart.tickdirection']);
1181
+ yEnd += 0;
1182
+
1183
+
1184
+ } else if (prop['chart.xaxispos'] == 'top') {
1185
+ yStart = this.gutterTop - 3;
1186
+ yEnd = this.gutterTop;
1187
+ }
1188
+
1189
+ co.moveTo(Math.round(x), yStart);
1190
+ co.lineTo(Math.round(x), yEnd);
1191
+ }
1192
+
1193
+ // Draw an extra tickmark if there is no X axis, but there IS a Y axis
1194
+ } else if (prop['chart.noyaxis'] == false && prop['chart.numyticks'] > 0) {
1195
+ if (!prop['chart.noendytick']) {
1196
+ if (prop['chart.yaxispos'] == 'left') {
1197
+ co.moveTo(this.gutterLeft, Math.round(ca.height - this.gutterBottom));
1198
+ co.lineTo(this.gutterLeft - prop['chart.smallyticks'], Math.round(ca.height - this.gutterBottom));
1199
+ } else {
1200
+ co.moveTo(ca.width - this.gutterRight, Math.round(ca.height - this.gutterBottom));
1201
+ co.lineTo(ca.width - this.gutterRight + prop['chart.smallyticks'], Math.round(ca.height - this.gutterBottom));
1202
+ }
1203
+ }
1204
+ }
1205
+
1206
+ /**
1207
+ * Draw the Y tickmarks
1208
+ */
1209
+ var numyticks = prop['chart.numyticks'];
1210
+
1211
+ if (prop['chart.noyaxis'] == false && numyticks > 0) {
1212
+ var counter = 0;
1213
+ var adjustment = 0;
1214
+
1215
+ if (prop['chart.yaxispos'] == 'right') {
1216
+ adjustment = (ca.width - this.gutterLeft - this.gutterRight);
1217
+ }
1218
+
1219
+ // X axis at the center
1220
+ if (prop['chart.xaxispos'] == 'center') {
1221
+ var interval = (this.grapharea / numyticks);
1222
+ var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft : ca.width - this.gutterRight + prop['chart.smallyticks']);
1223
+
1224
+ // Draw the upper halves Y tick marks
1225
+ for (y=this.gutterTop; y<(this.grapharea / 2) + this.gutterTop; y+=interval) {
1226
+ if (y < (this.grapharea / 2) + this.gutterTop) {
1227
+ co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
1228
+ co.lineTo(lineto, Math.round(y));
1229
+ }
1230
+ }
1231
+
1232
+ // Draw the lower halves Y tick marks
1233
+ for (y=this.gutterTop + (this.halfgrapharea) + interval; y <= this.grapharea + this.gutterTop; y+=interval) {
1234
+ co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
1235
+ co.lineTo(lineto, Math.round(y));
1236
+ }
1237
+
1238
+ // X axis at the top
1239
+ } else if (prop['chart.xaxispos'] == 'top') {
1240
+ var interval = (this.grapharea / numyticks);
1241
+ var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft : ca.width - this.gutterRight + prop['chart.smallyticks']);
1242
+
1243
+ // Draw the Y tick marks
1244
+ for (y=this.gutterTop + interval; y <=this.grapharea + this.gutterTop; y+=interval) {
1245
+ co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), Math.round(y));
1246
+ co.lineTo(lineto, Math.round(y));
1247
+ }
1248
+
1249
+ // If there's no X axis draw an extra tick
1250
+ if (prop['chart.noxaxis'] && prop['chart.noendytick'] == false) {
1251
+ co.moveTo((prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight), this.gutterTop);
1252
+ co.lineTo(lineto, this.gutterTop);
1253
+ }
1254
+
1255
+ // X axis at the bottom
1256
+ } else {
1257
+
1258
+ var lineto = (prop['chart.yaxispos'] == 'left' ? this.gutterLeft - prop['chart.smallyticks'] : ca.width - this.gutterRight + prop['chart.smallyticks']);
1259
+
1260
+ for (y=this.gutterTop; y<(ca.height - this.gutterBottom) && counter < numyticks; y+=( (ca.height - this.gutterTop - this.gutterBottom) / numyticks) ) {
1261
+
1262
+ co.moveTo(this.gutterLeft + adjustment, Math.round(y));
1263
+ co.lineTo(lineto, Math.round(y));
1264
+
1265
+ var counter = counter + 1;
1266
+ }
1267
+ }
1268
+
1269
+ // Draw an extra X tickmark
1270
+ } else if (prop['chart.noxaxis'] == false && prop['chart.numxticks'] > 0) {
1271
+
1272
+ if (prop['chart.yaxispos'] == 'left') {
1273
+ co.moveTo(this.gutterLeft, prop['chart.xaxispos'] == 'top' ? this.gutterTop : ca.height - this.gutterBottom);
1274
+ co.lineTo(this.gutterLeft, prop['chart.xaxispos'] == 'top' ? this.gutterTop - prop['chart.smallxticks'] : ca.height - this.gutterBottom + prop['chart.smallxticks']);
1275
+ } else {
1276
+ co.moveTo(ca.width - this.gutterRight, ca.height - this.gutterBottom);
1277
+ co.lineTo(ca.width - this.gutterRight, ca.height - this.gutterBottom + prop['chart.smallxticks']);
1278
+ }
1279
+ }
1280
+
1281
+ co.stroke();
1282
+
1283
+ /**
1284
+ * This is here so that setting the color after this function doesn't
1285
+ * change the color of the axes
1286
+ */
1287
+ co.beginPath();
1288
+ };
1289
+
1290
+
1291
+
1292
+
1293
+ /**
1294
+ * Draw the text labels for the axes
1295
+ */
1296
+ this.drawLabels =
1297
+ this.DrawLabels = function ()
1298
+ {
1299
+ co.strokeStyle = 'black';
1300
+ co.fillStyle = prop['chart.text.color'];
1301
+ co.lineWidth = 1;
1302
+
1303
+ // Turn off any shadow
1304
+ RG.NoShadow(this);
1305
+
1306
+ // This needs to be here
1307
+ var font = prop['chart.text.font'];
1308
+ var text_size = prop['chart.text.size'];
1309
+ var decimals = prop['chart.scale.decimals'];
1310
+ var context = co;
1311
+ var canvas = ca;
1312
+ var ymin = prop['chart.ymin'];
1313
+
1314
+ // Draw the Y axis labels
1315
+ if (prop['chart.ylabels'] && prop['chart.ylabels.specific'] == null) {
1316
+
1317
+ var units_pre = prop['chart.units.pre'];
1318
+ var units_post = prop['chart.units.post'];
1319
+ var xpos = prop['chart.yaxispos'] == 'left' ? this.gutterLeft - 5 : ca.width - this.gutterRight + 5;
1320
+ var align = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
1321
+ var numYLabels = this.scale2.labels.length;
1322
+ var bounding = false;
1323
+ var bgcolor = prop['chart.ylabels.inside'] ? prop['chart.ylabels.inside.color'] : null;
1324
+
1325
+
1326
+ /**
1327
+ * If the Y labels are inside the Y axis, invert the alignment
1328
+ */
1329
+ if (prop['chart.ylabels.inside'] == true && align == 'left') {
1330
+ xpos -= 10;
1331
+ align = 'right';
1332
+ bounding = true;
1333
+
1334
+
1335
+ } else if (prop['chart.ylabels.inside'] == true && align == 'right') {
1336
+ xpos += 10;
1337
+ align = 'left';
1338
+ bounding = true;
1339
+ }
1340
+
1341
+
1342
+
1343
+
1344
+ /**
1345
+ * X axis in the center
1346
+ */
1347
+ if (prop['chart.xaxispos'] == 'center') {
1348
+
1349
+ var half = this.grapharea / 2;
1350
+
1351
+ /**
1352
+ * Draw the top half
1353
+ */
1354
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1355
+ RG.Text2(this, {'font': font,
1356
+ 'size': text_size,
1357
+ 'x': xpos,
1358
+ 'y': this.gutterTop + half - (((i+1)/numYLabels) * half),
1359
+ 'valign': 'center',
1360
+ 'halign':align,
1361
+ 'bounding': bounding,
1362
+ 'boundingFill': bgcolor,
1363
+ 'text': this.scale2.labels[i],
1364
+ 'tag': 'scale'
1365
+ });
1366
+ }
1367
+
1368
+ /**
1369
+ * Draw the bottom half
1370
+ */
1371
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1372
+ RG.Text2(this, {'font': font,
1373
+ 'size': text_size,
1374
+ 'x': xpos,
1375
+ 'y': this.gutterTop + half + (((i+1)/numYLabels) * half),
1376
+ 'valign': 'center',
1377
+ 'halign':align,
1378
+ 'bounding': bounding,
1379
+ 'boundingFill': bgcolor,
1380
+ 'text': '-' + this.scale2.labels[i],
1381
+ 'tag': 'scale'
1382
+ });
1383
+ }
1384
+
1385
+ // No X axis - so draw 0
1386
+ if (prop['chart.noxaxis'] == true || ymin != 0 || prop['chart.scale.zerostart']) {
1387
+ RG.Text2(this,{'font':font,
1388
+ 'size':text_size,
1389
+ 'x':xpos,
1390
+ 'y':this.gutterTop + half,
1391
+ 'text':prop['chart.units.pre'] + ymin.toFixed(decimals) + prop['chart.units.post'],
1392
+ 'bounding':bounding,
1393
+ 'boundingFill':bgcolor,
1394
+ 'valign':'center',
1395
+ 'halign':align,
1396
+ 'tag': 'scale'
1397
+ });
1398
+ }
1399
+
1400
+
1401
+
1402
+ /**
1403
+ * X axis at the top
1404
+ */
1405
+ } else if (prop['chart.xaxispos'] == 'top') {
1406
+
1407
+ var half = this.grapharea / 2;
1408
+
1409
+ if (prop['chart.scale.invert']) {
1410
+
1411
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1412
+
1413
+ RG.Text2(this, {'font': font,
1414
+ 'size': text_size,
1415
+ 'x': xpos,
1416
+ 'y': this.gutterTop + ((i/this.scale2.labels.length) * this.grapharea),
1417
+ 'valign': 'center',
1418
+ 'halign':align,
1419
+ 'bounding': bounding,
1420
+ 'boundingFill': bgcolor,
1421
+ 'text': '-' + this.scale2.labels[this.scale2.labels.length - (i+1)],
1422
+ 'tag': 'scale'
1423
+ });
1424
+ }
1425
+ } else {
1426
+ for (var i=0; i<this.scale2.labels.length; ++i) {
1427
+ RG.Text2(this, {'font': font,
1428
+ 'size': text_size,
1429
+ 'x': xpos,
1430
+ 'y': this.gutterTop + (((i+1)/numYLabels) * this.grapharea),
1431
+ 'valign': 'center',
1432
+ 'halign':align,
1433
+ 'bounding': bounding,
1434
+ 'boundingFill': bgcolor,
1435
+ 'text': '-' + this.scale2.labels[i],
1436
+ 'tag': 'scale'
1437
+ });
1438
+ }
1439
+ }
1440
+
1441
+ // Draw the lower limit if chart.ymin is specified
1442
+ if ((prop['chart.ymin'] != 0 || prop['chart.noxaxis']) || prop['chart.scale.invert'] || prop['chart.scale.zerostart']) {
1443
+ RG.Text2(this, {'font':font,
1444
+ 'size':text_size,
1445
+ 'x':xpos,
1446
+ 'y': prop['chart.scale.invert'] ? ca.height - this.gutterBottom : this.gutterTop,
1447
+ 'text': (prop['chart.ymin'] != 0 ? '-' : '') + RG.number_format(this, prop['chart.ymin'].toFixed(decimals), units_pre, units_post),
1448
+ 'valign':'center',
1449
+ 'halign': align,
1450
+ 'bounding':bounding,
1451
+ 'boundingFill':bgcolor,
1452
+ 'tag': 'scale'});
1453
+ }
1454
+
1455
+
1456
+
1457
+
1458
+
1459
+
1460
+ /**
1461
+ * X axis labels at the bottom
1462
+ */
1463
+ } else {
1464
+
1465
+ if (prop['chart.scale.invert']) {
1466
+
1467
+ // Draw the minimum value
1468
+ RG.Text2(this, {'font': font,
1469
+ 'size': text_size,
1470
+ 'x': xpos,
1471
+ 'y': this.gutterTop,
1472
+ 'valign': 'center',
1473
+ 'halign':align,
1474
+ 'bounding': bounding,
1475
+ 'boundingFill': bgcolor,
1476
+ 'text': RG.number_format(this, this.min.toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1477
+ 'tag': 'scale'
1478
+ });
1479
+
1480
+ for (var i=0,len=this.scale2.labels.length; i<len; ++i) {
1481
+ RG.Text2(this, {'font': font,
1482
+ 'size': text_size,
1483
+ 'x': xpos,
1484
+ 'y': this.gutterTop + (((i+1)/this.scale2.labels.length) * this.grapharea),
1485
+ 'valign': 'center',
1486
+ 'halign':align,
1487
+ 'bounding': bounding,
1488
+ 'boundingFill': bgcolor,
1489
+ 'text': this.scale2.labels[i],
1490
+ 'tag': 'scale'
1491
+ });
1492
+ }
1493
+ } else {
1494
+ for (var i=0,len=this.scale2.labels.length; i<len; ++i) {
1495
+ RG.Text2(this, {'font': font,
1496
+ 'size': text_size,
1497
+ 'x': xpos,
1498
+ 'y': this.gutterTop + ((i/this.scale2.labels.length) * this.grapharea),
1499
+ 'valign': 'center',
1500
+ 'halign':align,
1501
+ 'bounding': bounding,
1502
+ 'boundingFill': bgcolor,
1503
+ 'text': this.scale2.labels[this.scale2.labels.length - (i + 1)],
1504
+ 'tag': 'scale'
1505
+ });
1506
+ }
1507
+ }
1508
+
1509
+ // Draw the lower limit if chart.ymin is specified
1510
+ if ( (prop['chart.ymin']!= 0 && !prop['chart.scale.invert'] || prop['chart.scale.zerostart'])
1511
+ || prop['chart.noxaxis']
1512
+ ) {
1513
+ RG.Text2(this, {'font':font,
1514
+ 'size':text_size,
1515
+ 'x':xpos,
1516
+ 'y':prop['chart.scale.invert'] ? this.gutterTop : ca.height - this.gutterBottom,
1517
+ 'text':RG.number_format(this, prop['chart.ymin'].toFixed(prop['chart.scale.decimals']), units_pre, units_post),
1518
+ 'valign':'center',
1519
+ 'halign':align,
1520
+ 'bounding':bounding,
1521
+ 'boundingFill':bgcolor,
1522
+ 'tag': 'scale'
1523
+ });
1524
+ }
1525
+ }
1526
+
1527
+
1528
+
1529
+
1530
+
1531
+
1532
+
1533
+ // No X axis - so draw 0 - but not if the X axis is in the center
1534
+ if ( prop['chart.noxaxis'] == true
1535
+ && prop['chart.ymin'] == null
1536
+ && prop['chart.xaxispos'] != 'center'
1537
+ && prop['chart.noendytick'] == false
1538
+ ) {
1539
+
1540
+ RG.Text2(this, {'font':font,
1541
+ 'size':text_size,
1542
+ 'x':xpos,
1543
+ 'y':prop['chart.xaxispos'] == 'top' ? this.gutterTop : (ca.height - this.gutterBottom),'text': prop['chart.units.pre'] + Number(0).toFixed(prop['chart.scale.decimals']) + prop['chart.units.post'],
1544
+ 'valign':'center',
1545
+ 'halign':align,
1546
+ 'bounding':bounding,
1547
+ 'boundingFill':bgcolor,
1548
+ 'tag':'scale'
1549
+ });
1550
+ }
1551
+
1552
+ } else if (prop['chart.ylabels'] && typeof(prop['chart.ylabels.specific']) == 'object') {
1553
+
1554
+ // A few things
1555
+ var gap = this.grapharea / prop['chart.ylabels.specific'].length;
1556
+ var halign = prop['chart.yaxispos'] == 'left' ? 'right' : 'left';
1557
+ var bounding = false;
1558
+ var bgcolor = null;
1559
+ var ymin = prop['chart.ymin'] != null && prop['chart.ymin'];
1560
+
1561
+ // Figure out the X coord based on the position of the axis
1562
+ if (prop['chart.yaxispos'] == 'left') {
1563
+ var x = this.gutterLeft - 5;
1564
+
1565
+ if (prop['chart.ylabels.inside']) {
1566
+ x += 10;
1567
+ halign = 'left';
1568
+ bounding = true;
1569
+ bgcolor = 'rgba(255,255,255,0.5)';
1570
+ }
1571
+
1572
+ } else if (prop['chart.yaxispos'] == 'right') {
1573
+ var x = ca.width - this.gutterRight + 5;
1574
+
1575
+ if (prop['chart.ylabels.inside']) {
1576
+ x -= 10;
1577
+ halign = 'right';
1578
+ bounding = true;
1579
+ bgcolor = 'rgba(255,255,255,0.5)';
1580
+ }
1581
+ }
1582
+
1583
+
1584
+ // Draw the labels
1585
+ if (prop['chart.xaxispos'] == 'center') {
1586
+
1587
+ // Draw the top halfs labels
1588
+ for (var i=0; i<prop['chart.ylabels.specific'].length; ++i) {
1589
+
1590
+ var y = this.gutterTop + (this.grapharea / (((prop['chart.ylabels.specific'].length - 1)) * 2) * i);
1591
+
1592
+ if (ymin && ymin > 0) {
1593
+ var y = ((this.grapharea / 2) / (prop['chart.ylabels.specific'].length - (ymin ? 1 : 0)) ) * i;
1594
+ y += this.gutterTop;
1595
+ }
1596
+
1597
+ RG.Text2(this, {'font':font,
1598
+ 'size':text_size,
1599
+ 'x':x,
1600
+ 'y':y,
1601
+ 'text':String(prop['chart.ylabels.specific'][i]),
1602
+ 'valign': 'center',
1603
+ 'halign':halign,
1604
+ 'bounding':bounding,
1605
+ 'boundingFill':bgcolor,
1606
+ 'tag': 'ylabels.specific'
1607
+ });
1608
+ }
1609
+
1610
+ // Now reverse the labels and draw the bottom half
1611
+ var reversed_labels = RG.array_reverse(prop['chart.ylabels.specific']);
1612
+
1613
+ // Draw the bottom halfs labels
1614
+ for (var i=0; i<reversed_labels.length; ++i) {
1615
+
1616
+ var y = (this.grapharea / 2) + this.gutterTop + ((this.grapharea / ((reversed_labels.length - 1) * 2) ) * i);
1617
+
1618
+ RG.Text2(this, {'font':font,
1619
+ 'size':text_size,
1620
+ 'x':x,
1621
+ 'y':y,
1622
+ 'text':i == 0 ? '' : String(reversed_labels[i]),
1623
+ 'valign': 'center',
1624
+ 'halign':halign,
1625
+ 'bounding':bounding,
1626
+ 'boundingFill':bgcolor,
1627
+ 'tag': 'ylabels.specific'
1628
+ });
1629
+ }
1630
+
1631
+ } else if (prop['chart.xaxispos'] == 'top') {
1632
+
1633
+ // Reverse the labels and draw
1634
+ var reversed_labels = RG.array_reverse(prop['chart.ylabels.specific']);
1635
+
1636
+ // Draw the bottom halfs labels
1637
+ for (var i=0; i<reversed_labels.length; ++i) {
1638
+
1639
+ var y = (this.grapharea / (reversed_labels.length - 1)) * i;
1640
+ y = y + this.gutterTop;
1641
+
1642
+ RG.Text2(this, {'font':font,
1643
+ 'size':text_size,
1644
+ 'x':x,
1645
+ 'y':y,
1646
+ 'text':String(reversed_labels[i]),
1647
+ 'valign': 'center',
1648
+ 'halign':halign,
1649
+ 'bounding':bounding,
1650
+ 'boundingFill':bgcolor,
1651
+ 'tag': 'ylabels.specific'
1652
+ });
1653
+ }
1654
+
1655
+ } else {
1656
+ for (var i=0; i<prop['chart.ylabels.specific'].length; ++i) {
1657
+ var y = this.gutterTop + ((this.grapharea / (prop['chart.ylabels.specific'].length - 1)) * i);
1658
+ RG.Text2(this, {'font':font,
1659
+ 'size':text_size,
1660
+ 'x':x,
1661
+ 'y':y,
1662
+ 'text':String(prop['chart.ylabels.specific'][i]),
1663
+ 'valign':'center',
1664
+ 'halign':halign,
1665
+ 'bounding':bounding,
1666
+ 'boundingFill':bgcolor,
1667
+ 'tag': 'ylabels.specific'
1668
+ });
1669
+ }
1670
+ }
1671
+ }
1672
+
1673
+ // Draw the X axis labels
1674
+ if (prop['chart.labels'] && prop['chart.labels'].length > 0) {
1675
+
1676
+ var yOffset = 5,
1677
+ bordered = false,
1678
+ bgcolor = null
1679
+
1680
+ co.fillStyle = prop['chart.labels.color'] || prop['chart.text.color'];
1681
+
1682
+ /**
1683
+ * Text angle
1684
+ */
1685
+ var angle = 0,
1686
+ valign = 'top',
1687
+ halign = 'center',
1688
+ bold = prop['chart.labels.bold']
1689
+
1690
+ if (prop['chart.xlabels.inside']) {
1691
+ yOffset = -5;
1692
+ bordered = true;
1693
+ bgcolor = prop['chart.xlabels.inside.color'];
1694
+ valign = 'bottom';
1695
+ }
1696
+
1697
+ if (prop['chart.xaxispos'] == 'top') {
1698
+ valign = 'bottom';
1699
+ yOffset += 2;
1700
+ }
1701
+
1702
+ if (typeof(prop['chart.text.angle']) == 'number' && prop['chart.text.angle'] > 0) {
1703
+ angle = -1 * prop['chart.text.angle'];
1704
+ valign = 'center';
1705
+ halign = 'right';
1706
+ yOffset = 10;
1707
+
1708
+ if (prop['chart.xaxispos'] == 'top') {
1709
+ yOffset = 10;
1710
+ }
1711
+ }
1712
+
1713
+ var numLabels = prop['chart.labels'].length;
1714
+
1715
+ for (i=0; i<numLabels; ++i) {
1716
+
1717
+ // Changed 8th Nov 2010 to be not reliant on the coords
1718
+ //if (this.properties['chart.labels'][i] && this.coords && this.coords[i] && this.coords[i][0]) {
1719
+ if (prop['chart.labels'][i]) {
1720
+
1721
+ var labelX = ((ca.width - this.gutterLeft - this.gutterRight - (2 * prop['chart.hmargin'])) / (numLabels - 1) ) * i;
1722
+ labelX += this.gutterLeft + prop['chart.hmargin'];
1723
+
1724
+ /**
1725
+ * Account for an unrelated number of labels
1726
+ */
1727
+
1728
+ if (this.data.length === 0 || !this.data[0] || prop['chart.labels'].length != this.data[0].length) {
1729
+ labelX = this.gutterLeft + prop['chart.hmargin'] + ((ca.width - this.gutterLeft - this.gutterRight - (2 * prop['chart.hmargin'])) * (i / (prop['chart.labels'].length - 1)));
1730
+ }
1731
+
1732
+ // This accounts for there only being one point on the chart
1733
+ if (!labelX) {
1734
+ labelX = this.gutterLeft + prop['chart.hmargin'];
1735
+ }
1736
+
1737
+ if (prop['chart.xaxispos'] == 'top' && prop['chart.text.angle'] > 0) {
1738
+ halign = 'left';
1739
+ }
1740
+
1741
+ if (prop['chart.text.angle'] != 0) {
1742
+ halign = 'right';
1743
+ }
1744
+
1745
+ RG.Text2(this, {
1746
+ 'font':font,
1747
+ 'size':text_size,
1748
+ 'bold': bold,
1749
+ 'x':labelX,
1750
+ 'y':(prop['chart.xaxispos'] == 'top') ? this.gutterTop - yOffset - (prop['chart.xlabels.inside'] ? -22 : 0) : (ca.height - this.gutterBottom) + yOffset,
1751
+ 'text':String(prop['chart.labels'][i]),
1752
+ 'valign':valign,
1753
+ 'halign':halign,
1754
+ 'bounding':bordered,
1755
+ 'boundingFill':bgcolor,
1756
+ 'angle':angle,
1757
+ 'tag': 'labels'
1758
+ });
1759
+ }
1760
+ }
1761
+
1762
+ }
1763
+
1764
+ co.stroke();
1765
+ co.fill();
1766
+ }
1767
+
1768
+
1769
+
1770
+ /**
1771
+ * Draws the line
1772
+ */
1773
+ this.drawLine =
1774
+ this.DrawLine = function (lineData, color, fill, linewidth, tickmarks, index)
1775
+ {
1776
+ // This facilitates the Rise animation (the Y value only)
1777
+ if (prop['chart.animation.unfold.y'] && prop['chart.animation.factor'] != 1) {
1778
+ for (var i=0; i<lineData.length; ++i) {
1779
+ lineData[i] *= prop['chart.animation.factor'];
1780
+ }
1781
+ }
1782
+
1783
+ var penUp = false;
1784
+ var yPos = null;
1785
+ var xPos = 0;
1786
+ co.lineWidth = 1;
1787
+ var lineCoords = [];
1788
+
1789
+ /**
1790
+ * Get the previous line data
1791
+ */
1792
+ if (index > 0) {
1793
+ var prevLineCoords = this.coords2[index - 1];
1794
+ }
1795
+
1796
+
1797
+ // Work out the X interval
1798
+ var xInterval = (ca.width - (2 * prop['chart.hmargin']) - this.gutterLeft - this.gutterRight) / (lineData.length - 1);
1799
+
1800
+ // Loop thru each value given, plotting the line
1801
+ // (FORMERLY FIRST)
1802
+ for (i=0,len=lineData.length; i<len; i+=1) {
1803
+
1804
+ var data_point = lineData[i];
1805
+
1806
+ /**
1807
+ * Get the yPos for the given data point
1808
+ */
1809
+ var yPos = this.getYCoord(data_point);
1810
+
1811
+
1812
+ // Null data points, and a special case for this bug:http://dev.rgraph.net/tests/ymin.html
1813
+ if ( lineData[i] == null
1814
+ || (prop['chart.xaxispos'] == 'bottom' && lineData[i] < this.min && !prop['chart.outofbounds'])
1815
+ || (prop['chart.xaxispos'] == 'center' && lineData[i] < (-1 * this.max) && !prop['chart.outofbounds'])
1816
+ || (((lineData[i] < this.min && prop['chart.xaxispos'] !== 'center') || lineData[i] > this.max) && !prop['chart.outofbounds'])) {
1817
+
1818
+ yPos = null;
1819
+ }
1820
+
1821
+ // Not always very noticeable, but it does have an effect
1822
+ // with thick lines
1823
+ co.lineCap = 'round';
1824
+ co.lineJoin = 'round';
1825
+
1826
+ // Plot the line if we're at least on the second iteration
1827
+ if (i > 0) {
1828
+ xPos = xPos + xInterval;
1829
+ } else {
1830
+ xPos = prop['chart.hmargin'] + this.gutterLeft;
1831
+ }
1832
+
1833
+ if (prop['chart.animation.unfold.x']) {
1834
+ xPos *= prop['chart.animation.factor'];
1835
+
1836
+ if (xPos < prop['chart.gutter.left']) {
1837
+ xPos = prop['chart.gutter.left'];
1838
+ }
1839
+ }
1840
+
1841
+ /**
1842
+ * Add the coords to an array
1843
+ */
1844
+ this.coords.push([xPos, yPos]);
1845
+ lineCoords.push([xPos, yPos]);
1846
+ }
1847
+
1848
+ co.stroke();
1849
+
1850
+ // Store the coords in another format, indexed by line number
1851
+ this.coords2[index] = lineCoords;
1852
+
1853
+ /**
1854
+ * For IE only: Draw the shadow ourselves as ExCanvas doesn't produce shadows
1855
+ */
1856
+ if (RG.ISOLD && prop['chart.shadow']) {
1857
+ this.DrawIEShadow(lineCoords, co.shadowColor);
1858
+ }
1859
+
1860
+
1861
+
1862
+ /**
1863
+ * Now draw the actual line [FORMERLY SECOND]
1864
+ */
1865
+ co.beginPath();
1866
+ // Transparent now as of 11/19/2011
1867
+ co.strokeStyle = 'rgba(0,0,0,0)';
1868
+ //co.strokeStyle = fill;
1869
+ if (fill) {
1870
+ co.fillStyle = fill;
1871
+ }
1872
+
1873
+ var isStepped = prop['chart.stepped'];
1874
+ var isFilled = prop['chart.filled'];
1875
+
1876
+ if (prop['chart.xaxispos'] == 'top') {
1877
+ var xAxisPos = this.gutterTop;
1878
+ } else if (prop['chart.xaxispos'] == 'center') {
1879
+ var xAxisPos = this.gutterTop + (this.grapharea / 2);
1880
+ } else if (prop['chart.xaxispos'] == 'bottom') {
1881
+ var xAxisPos = ca.height - this.gutterBottom;
1882
+ }
1883
+
1884
+
1885
+
1886
+
1887
+ for (var i=0,len=lineCoords.length; i<len; i+=1) {
1888
+
1889
+ xPos = lineCoords[i][0];
1890
+ yPos = lineCoords[i][1];
1891
+ var set = index;
1892
+
1893
+ var prevY = (lineCoords[i - 1] ? lineCoords[i - 1][1] : null);
1894
+ var isLast = (i + 1) == lineCoords.length;
1895
+
1896
+ /**
1897
+ * This nullifys values which are out-of-range
1898
+ */
1899
+ if (!prop['chart.outofbounds'] && (prevY < this.gutterTop || prevY > (ca.height - this.gutterBottom) ) ) {
1900
+ penUp = true;
1901
+ }
1902
+
1903
+ if (i == 0 || penUp || !yPos || !prevY || prevY < this.gutterTop) {
1904
+
1905
+ if (prop['chart.filled'] && !prop['chart.filled.range']) {
1906
+
1907
+ if (!prop['chart.outofbounds'] || prevY === null || yPos === null) {
1908
+ co.moveTo(xPos + 1, xAxisPos);
1909
+ }
1910
+
1911
+ // This facilitates the X axis being at the top
1912
+ // NOTE: Also done below
1913
+ if (prop['chart.xaxispos'] == 'top') {
1914
+ co.moveTo(xPos + 1, xAxisPos);
1915
+ }
1916
+
1917
+ if (isStepped && i > 0) {
1918
+ co.lineTo(xPos, lineCoords[i - 1][1]);
1919
+ }
1920
+
1921
+ co.lineTo(xPos, yPos);
1922
+
1923
+ } else {
1924
+
1925
+ if (RG.ISOLD && yPos == null) {
1926
+ // Nada
1927
+ } else {
1928
+ co.moveTo(xPos + 1, yPos);
1929
+ }
1930
+ }
1931
+
1932
+ if (yPos == null) {
1933
+ penUp = true;
1934
+
1935
+ } else {
1936
+ penUp = false;
1937
+ }
1938
+
1939
+ } else {
1940
+
1941
+ // Draw the stepped part of stepped lines
1942
+ if (isStepped) {
1943
+ co.lineTo(xPos, lineCoords[i - 1][1]);
1944
+ }
1945
+
1946
+ if ((yPos >= this.gutterTop && yPos <= (ca.height - this.gutterBottom)) || prop['chart.outofbounds'] ) {
1947
+
1948
+ if (isLast && prop['chart.filled'] && !prop['chart.filled.range'] && prop['chart.yaxispos'] == 'right') {
1949
+ xPos -= 1;
1950
+ }
1951
+
1952
+
1953
+ // Added 8th September 2009
1954
+ if (!isStepped || !isLast) {
1955
+ co.lineTo(xPos, yPos);
1956
+
1957
+ if (isFilled && lineCoords[i+1] && lineCoords[i+1][1] == null) {
1958
+ co.lineTo(xPos, xAxisPos);
1959
+ }
1960
+
1961
+ // Added August 2010
1962
+ } else if (isStepped && isLast) {
1963
+ co.lineTo(xPos,yPos);
1964
+ }
1965
+
1966
+
1967
+ penUp = false;
1968
+ } else {
1969
+ penUp = true;
1970
+ }
1971
+ }
1972
+ }
1973
+
1974
+ /**
1975
+ * Draw a line to the X axis if the chart is filled
1976
+ */
1977
+ if (prop['chart.filled'] && !prop['chart.filled.range'] && !prop['chart.curvy']) {
1978
+
1979
+ // Is this needed ??
1980
+ var fillStyle = prop['chart.fillstyle'];
1981
+
1982
+ /**
1983
+ * Draw the bottom edge of the filled bit using either the X axis or the prevlinedata,
1984
+ * depending on the index of the line. The first line uses the X axis, and subsequent
1985
+ * lines use the prevLineCoords array
1986
+ */
1987
+ if (index > 0 && prop['chart.filled.accumulative']) {
1988
+
1989
+ co.lineTo(xPos, prevLineCoords ? prevLineCoords[i - 1][1] : (ca.height - this.gutterBottom - 1 + (prop['chart.xaxispos'] == 'center' ? (ca.height - this.gutterTop - this.gutterBottom) / 2 : 0)));
1990
+
1991
+ for (var k=(i - 1); k>=0; --k) {
1992
+ co.lineTo(k == 0 ? prevLineCoords[k][0] + 1: prevLineCoords[k][0], prevLineCoords[k][1]);
1993
+ }
1994
+ } else {
1995
+
1996
+ // Draw a line down to the X axis
1997
+ if (prop['chart.xaxispos'] == 'top') {
1998
+ co.lineTo(xPos, prop['chart.gutter.top'] + 1);
1999
+ co.lineTo(lineCoords[0][0],prop['chart.gutter.top'] + 1);
2000
+ } else if (typeof(lineCoords[i - 1][1]) == 'number') {
2001
+
2002
+ var yPosition = prop['chart.xaxispos'] == 'center' ? ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop : ca.height - this.gutterBottom;
2003
+
2004
+ co.lineTo(xPos,yPosition);
2005
+ co.lineTo(lineCoords[0][0],yPosition);
2006
+ }
2007
+ }
2008
+
2009
+ co.fillStyle = fill;
2010
+
2011
+ co.fill();
2012
+ co.beginPath();
2013
+
2014
+ }
2015
+
2016
+ /**
2017
+ * FIXME this may need removing when Chrome is fixed
2018
+ * SEARCH TAGS: CHROME SHADOW BUG
2019
+ */
2020
+ //if (false && RGraph.ISCHROME && prop['chart.shadow'] && prop['chart.chromefix'] && prop['chart.shadow.blur'] > 0) {
2021
+ //
2022
+ // for (var i=lineCoords.length - 1; i>=0; --i) {
2023
+ // if (
2024
+ // typeof(lineCoords[i][1]) != 'number'
2025
+ // || (typeof(lineCoords[i+1]) == 'object' && typeof(lineCoords[i+1][1]) != 'number')
2026
+ // ) {
2027
+ // co.moveTo(lineCoords[i][0],lineCoords[i][1]);
2028
+ // } else {
2029
+ // co.lineTo(lineCoords[i][0],lineCoords[i][1]);
2030
+ // }
2031
+ // }
2032
+ //}
2033
+
2034
+ co.stroke();
2035
+
2036
+
2037
+ if (prop['chart.backdrop']) {
2038
+ this.DrawBackdrop(lineCoords, color);
2039
+ }
2040
+
2041
+
2042
+
2043
+
2044
+ /**
2045
+ * TODO CLIP TRACE
2046
+ * By using the clip() method the Trace animation can be updated.
2047
+ * NOTE: Needs to be done for the filled part as well
2048
+ */
2049
+ co.save();
2050
+ co.beginPath();
2051
+ co.rect(0,0,ca.width * prop['chart.animation.trace.clip'],ca.height);
2052
+ co.clip();
2053
+
2054
+ // Now redraw the lines with the correct line width
2055
+ this.SetShadow(index);
2056
+ this.RedrawLine(lineCoords, color, linewidth, index);
2057
+ co.stroke();
2058
+ RG.NoShadow(this);
2059
+
2060
+
2061
+
2062
+
2063
+ // Draw the tickmarks
2064
+ for (var i=0; i<lineCoords.length; ++i) {
2065
+
2066
+ i = Number(i);
2067
+
2068
+ /**
2069
+ * Set the color
2070
+ */
2071
+ co.strokeStyle = color;
2072
+
2073
+
2074
+ if (isStepped && i == (lineCoords.length - 1)) {
2075
+ co.beginPath();
2076
+ //continue;
2077
+ }
2078
+
2079
+ if (
2080
+ (
2081
+ tickmarks != 'endcircle'
2082
+ && tickmarks != 'endsquare'
2083
+ && tickmarks != 'filledendsquare'
2084
+ && tickmarks != 'endtick'
2085
+ && tickmarks != 'endtriangle'
2086
+ && tickmarks != 'arrow'
2087
+ && tickmarks != 'filledarrow'
2088
+ )
2089
+ || (i == 0 && tickmarks != 'arrow' && tickmarks != 'filledarrow')
2090
+ || i == (lineCoords.length - 1)
2091
+ ) {
2092
+
2093
+ var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
2094
+ var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
2095
+
2096
+ this.DrawTick(lineData, lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY, tickmarks, i);
2097
+
2098
+ // Draws tickmarks on the stepped bits of stepped charts. Takend out 14th July 2010
2099
+ //
2100
+ //if (this.properties['chart.stepped'] && lineCoords[i + 1] && this.properties['chart.tickmarks'] != 'endsquare' && this.properties['chart.tickmarks'] != 'endcircle' && this.properties['chart.tickmarks'] != 'endtick') {
2101
+ // this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
2102
+ //}
2103
+ }
2104
+ }
2105
+
2106
+ co.restore();
2107
+
2108
+ // Draw something off canvas to skirt an annoying bug
2109
+ co.beginPath();
2110
+ co.arc(ca.width + 50000, ca.height + 50000, 2, 0, 6.38, 1);
2111
+ };
2112
+
2113
+
2114
+
2115
+
2116
+ /**
2117
+ * This functions draws a tick mark on the line
2118
+ *
2119
+ * @param xPos int The x position of the tickmark
2120
+ * @param yPos int The y position of the tickmark
2121
+ * @param color str The color of the tickmark
2122
+ * @param bool Whether the tick is a shadow. If it is, it gets offset by the shadow offset
2123
+ */
2124
+ this.drawTick =
2125
+ this.DrawTick = function (lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index)
2126
+ {
2127
+ // Various conditions mean no tick
2128
+ if (!prop['chart.line.visible']) {
2129
+ return;
2130
+ } else if (RG.is_null(yPos)) {
2131
+ return false;
2132
+ } else if ((yPos > (ca.height - this.gutterBottom)) && !prop['chart.outofbounds']) {
2133
+ return;
2134
+ } else if ((yPos < this.gutterTop) && !prop['chart.outofbounds']) {
2135
+ return;
2136
+ }
2137
+
2138
+ co.beginPath();
2139
+
2140
+ var offset = 0;
2141
+
2142
+ // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
2143
+ // UPDATE 28th July 2011 - the line width is now set to 1
2144
+ co.lineWidth = prop['chart.tickmarks.linewidth'] ? prop['chart.tickmarks.linewidth'] : prop['chart.linewidth'];
2145
+ co.strokeStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
2146
+ co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
2147
+
2148
+ // Cicular tick marks
2149
+ if ( tickmarks == 'circle'
2150
+ || tickmarks == 'filledcircle'
2151
+ || tickmarks == 'endcircle') {
2152
+
2153
+ if (tickmarks == 'circle'|| tickmarks == 'filledcircle' || (tickmarks == 'endcircle' && (index == 0 || index == (lineData.length - 1)))) {
2154
+ co.beginPath();
2155
+ co.arc(xPos + offset, yPos + offset, prop['chart.ticksize'], 0, 360 / (180 / RG.PI), false);
2156
+
2157
+ if (tickmarks == 'filledcircle') {
2158
+ co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
2159
+ } else {
2160
+ co.fillStyle = isShadow ? prop['chart.shadow.color'] : 'white';
2161
+ }
2162
+
2163
+ co.stroke();
2164
+ co.fill();
2165
+ }
2166
+
2167
+ // Halfheight "Line" style tick marks
2168
+ } else if (tickmarks == 'halftick') {
2169
+ co.beginPath();
2170
+ co.moveTo(Math.round(xPos), yPos);
2171
+ co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);
2172
+
2173
+ co.stroke();
2174
+
2175
+ // Tick style tickmarks
2176
+ } else if (tickmarks == 'tick') {
2177
+ co.beginPath();
2178
+ co.moveTo(Math.round(xPos), yPos - prop['chart.ticksize']);
2179
+ co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);
2180
+
2181
+ co.stroke();
2182
+
2183
+ // Endtick style tickmarks
2184
+ } else if (tickmarks == 'endtick' && (index == 0 || index == (lineData.length - 1))) {
2185
+ co.beginPath();
2186
+ co.moveTo(Math.round(xPos), yPos - prop['chart.ticksize']);
2187
+ co.lineTo(Math.round(xPos), yPos + prop['chart.ticksize']);
2188
+
2189
+ co.stroke();
2190
+
2191
+ // "Cross" style tick marks
2192
+ } else if (tickmarks == 'cross') {
2193
+ co.beginPath();
2194
+
2195
+ var ticksize = prop['chart.ticksize'];
2196
+
2197
+ co.moveTo(xPos - ticksize, yPos - ticksize);
2198
+ co.lineTo(xPos + ticksize, yPos + ticksize);
2199
+ co.moveTo(xPos + ticksize, yPos - ticksize);
2200
+ co.lineTo(xPos - ticksize, yPos + ticksize);
2201
+ co.stroke();
2202
+
2203
+
2204
+ // Triangle style tick marks
2205
+ } else if (tickmarks == 'triangle' || tickmarks == 'filledtriangle' || (tickmarks == 'endtriangle' && (index == 0 || index == (lineData.length - 1)))) {
2206
+ co.beginPath();
2207
+
2208
+ if (tickmarks == 'filledtriangle') {
2209
+ co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
2210
+ } else {
2211
+ co.fillStyle = 'white';
2212
+ }
2213
+
2214
+ co.moveTo(ma.round(xPos - prop['chart.ticksize']), yPos + prop['chart.ticksize']);
2215
+ co.lineTo(ma.round(xPos), yPos - prop['chart.ticksize']);
2216
+ co.lineTo(ma.round(xPos + prop['chart.ticksize']), yPos + prop['chart.ticksize']);
2217
+ co.closePath();
2218
+
2219
+ co.stroke();
2220
+ co.fill();
2221
+
2222
+
2223
+ //
2224
+ // A white bordered circle
2225
+ //
2226
+ } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') {
2227
+
2228
+ co.lineWidth = prop['chart.tickmarks.dot.linewidth'] || 0.00000001;
2229
+
2230
+ pa(this, [
2231
+ 'b',
2232
+ 'a',xPos, yPos, prop['chart.ticksize'], 0, 360 / (180 / RG.PI), false,
2233
+ 'c',
2234
+ 'f',prop['chart.tickmarks.dot.fill'] || color,
2235
+ 's',prop['chart.tickmarks.dot.stroke'] || color
2236
+ ]);
2237
+
2238
+ } else if ( tickmarks == 'square'
2239
+ || tickmarks == 'filledsquare'
2240
+ || (tickmarks == 'endsquare' && (index == 0 || index == (lineData.length - 1)))
2241
+ || (tickmarks == 'filledendsquare' && (index == 0 || index == (lineData.length - 1))) ) {
2242
+
2243
+ co.fillStyle = 'white';
2244
+ co.strokeStyle = co.strokeStyle;
2245
+
2246
+ co.beginPath();
2247
+ co.rect(Math.round(xPos - prop['chart.ticksize']), Math.round(yPos - prop['chart.ticksize']), prop['chart.ticksize'] * 2, prop['chart.ticksize'] * 2);
2248
+
2249
+ // Fillrect
2250
+ if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') {
2251
+ co.fillStyle = isShadow ? prop['chart.shadow.color'] : co.strokeStyle;
2252
+ co.rect(Math.round(xPos - prop['chart.ticksize']), Math.round(yPos - prop['chart.ticksize']), prop['chart.ticksize'] * 2, prop['chart.ticksize'] * 2);
2253
+
2254
+ } else if (tickmarks == 'square' || tickmarks == 'endsquare') {
2255
+ co.fillStyle = isShadow ? prop['chart.shadow.color'] : 'white';
2256
+ co.rect(Math.round((xPos - prop['chart.ticksize']) + 1), Math.round((yPos - prop['chart.ticksize']) + 1), (prop['chart.ticksize'] * 2) - 2, (prop['chart.ticksize'] * 2) - 2);
2257
+ }
2258
+
2259
+ co.stroke();
2260
+ co.fill();
2261
+
2262
+ /**
2263
+ * FILLED arrowhead
2264
+ */
2265
+ } else if (tickmarks == 'filledarrow') {
2266
+
2267
+ var x = Math.abs(xPos - prevX);
2268
+ var y = Math.abs(yPos - prevY);
2269
+
2270
+ if (yPos < prevY) {
2271
+ var a = Math.atan(x / y) + 1.57;
2272
+ } else {
2273
+ var a = Math.atan(y / x) + 3.14;
2274
+ }
2275
+
2276
+ co.beginPath();
2277
+ co.moveTo(Math.round(xPos), Math.round(yPos));
2278
+ co.arc(Math.round(xPos), Math.round(yPos), 7, a - 0.5, a + 0.5, false);
2279
+ co.closePath();
2280
+
2281
+ co.stroke();
2282
+ co.fill();
2283
+
2284
+ /**
2285
+ * Arrow head, NOT filled
2286
+ */
2287
+ } else if (tickmarks == 'arrow') {
2288
+
2289
+ var orig_linewidth = co.lineWidth;
2290
+
2291
+ var x = Math.abs(xPos - prevX);
2292
+ var y = Math.abs(yPos - prevY);
2293
+
2294
+ co.lineWidth;
2295
+
2296
+ if (yPos < prevY) {
2297
+ var a = Math.atan(x / y) + 1.57;
2298
+ } else {
2299
+ var a = Math.atan(y / x) + 3.14;
2300
+ }
2301
+
2302
+ co.beginPath();
2303
+ co.moveTo(Math.round(xPos), Math.round(yPos));
2304
+ co.arc(Math.round(xPos), Math.round(yPos), 7, a - 0.5 - (doc.all ? 0.1 : 0.01), a - 0.4, false);
2305
+
2306
+ co.moveTo(Math.round(xPos), Math.round(yPos));
2307
+ co.arc(Math.round(xPos), Math.round(yPos), 7, a + 0.5 + (doc.all ? 0.1 : 0.01), a + 0.5, true);
2308
+ co.stroke();
2309
+ co.fill();
2310
+
2311
+ // Revert to original lineWidth
2312
+ co.lineWidth = orig_linewidth;
2313
+
2314
+
2315
+
2316
+
2317
+
2318
+
2319
+
2320
+
2321
+
2322
+
2323
+
2324
+
2325
+
2326
+
2327
+
2328
+ /**
2329
+ * Image based tickmark
2330
+ */
2331
+ // lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index
2332
+ } else if (
2333
+ typeof tickmarks === 'string' &&
2334
+ (
2335
+ tickmarks.substr(0, 6) === 'image:' ||
2336
+ tickmarks.substr(0, 5) === 'data:' ||
2337
+ tickmarks.substr(0, 1) === '/' ||
2338
+ tickmarks.substr(0, 3) === '../' ||
2339
+ tickmarks.substr(0, 7) === 'images/'
2340
+ )
2341
+ ) {
2342
+
2343
+ var img = new Image();
2344
+
2345
+ if (tickmarks.substr(0, 6) === 'image:') {
2346
+ img.src = tickmarks.substr(6);
2347
+ } else {
2348
+ img.src = tickmarks;
2349
+ }
2350
+
2351
+
2352
+ img.onload = function ()
2353
+ {
2354
+ if (prop['chart.tickmarks.image.halign'] === 'center') xPos -= (this.width / 2);
2355
+ if (prop['chart.tickmarks.image.halign'] === 'right') xPos -= this.width;
2356
+
2357
+ if (prop['chart.tickmarks.image.valign'] === 'center') yPos -= (this.height / 2);
2358
+ if (prop['chart.tickmarks.image.valign'] === 'bottom') yPos -= this.height;
2359
+
2360
+ xPos += prop['chart.tickmarks.image.offsetx'];
2361
+ yPos += prop['chart.tickmarks.image.offsety'];
2362
+
2363
+ co.drawImage(this, xPos, yPos);
2364
+ };
2365
+
2366
+
2367
+
2368
+
2369
+
2370
+
2371
+
2372
+
2373
+
2374
+
2375
+
2376
+
2377
+
2378
+ /**
2379
+ * Custom tick drawing function
2380
+ */
2381
+ } else if (typeof(tickmarks) == 'function') {
2382
+ tickmarks(this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY);
2383
+ }
2384
+ };
2385
+
2386
+
2387
+
2388
+
2389
+ /**
2390
+ * Draws a filled range if necessary
2391
+ */
2392
+ this.drawRange =
2393
+ this.DrawRange = function ()
2394
+ {
2395
+ //var RG = RGraph;
2396
+ //var ca = this.canvas;
2397
+ //var co = this.context;
2398
+ //var prop = this.properties;
2399
+
2400
+ /**
2401
+ * Fill the range if necessary
2402
+ */
2403
+ if (prop['chart.filled.range'] && prop['chart.filled'] && prop['chart.line.visible']) {
2404
+
2405
+ if (RG.is_null(prop['chart.filled.range.threshold'])) {
2406
+ prop['chart.filled.range.threshold'] = this.ymin
2407
+ prop['chart.filled.range.threshold.colors'] = [prop['chart.fillstyle'], prop['chart.fillstyle']]
2408
+ }
2409
+
2410
+ for (var idx=0; idx<2; ++idx) {
2411
+
2412
+ var threshold_colors = prop['chart.filled.range.threshold.colors'];
2413
+ var y = this.getYCoord(prop['chart.filled.range.threshold'])
2414
+
2415
+ co.save();
2416
+ if (idx == 0) {
2417
+ co.beginPath();
2418
+ co.rect(0,0,ca.width,y);
2419
+ co.clip();
2420
+
2421
+ } else {
2422
+
2423
+ co.beginPath();
2424
+ co.rect(0,y,ca.width, ca.height);
2425
+ co.clip();
2426
+ }
2427
+
2428
+ co.beginPath();
2429
+ co.fillStyle = (idx == 1 ? prop['chart.filled.range.threshold.colors'][1] : prop['chart.filled.range.threshold.colors'][0]);
2430
+
2431
+ //co.strokeStyle = prop['chart.fillstyle']; // Strokestyle not used now (10th October 2012)
2432
+
2433
+ co.lineWidth = 1;
2434
+ var len = (this.coords.length / 2);
2435
+
2436
+
2437
+
2438
+ for (var i=0; i<len; ++i) {
2439
+ if (!RG.is_null(this.coords[i][1])) {
2440
+ if (i == 0) {
2441
+ co.moveTo(this.coords[i][0], this.coords[i][1])
2442
+ } else {
2443
+ co.lineTo(this.coords[i][0], this.coords[i][1])
2444
+ }
2445
+ }
2446
+ }
2447
+
2448
+
2449
+ for (var i=this.coords.length - 1; i>=len; --i) {
2450
+ if (RG.is_null(this.coords[i][1])) {
2451
+ co.moveTo(this.coords[i][0], this.coords[i][1])
2452
+ } else {
2453
+ co.lineTo(this.coords[i][0], this.coords[i][1])
2454
+ }
2455
+ //co.lineTo(this.coords[i][0], this.coords[i][1])
2456
+ }
2457
+
2458
+
2459
+
2460
+ // Taken out - 10th Oct 2012
2461
+ //co.stroke();
2462
+
2463
+ co.fill();
2464
+ co.restore();
2465
+ }
2466
+ }
2467
+ };
2468
+
2469
+
2470
+
2471
+
2472
+ /**
2473
+ * Redraws the line with the correct line width etc
2474
+ *
2475
+ * @param array coords The coordinates of the line
2476
+ */
2477
+ this.redrawLine =
2478
+ this.RedrawLine = function (coords, color, linewidth, index)
2479
+ {
2480
+ if (prop['chart.noredraw'] || prop['chart.filled.range']) {
2481
+ return;
2482
+ }
2483
+
2484
+ co.strokeStyle = (typeof(color) == 'object' && color && color.toString().indexOf('CanvasGradient') == -1 ? color[0] : color);
2485
+ co.lineWidth = linewidth;
2486
+
2487
+ if (!prop['chart.line.visible']) {
2488
+ co.strokeStyle = 'rgba(0,0,0,0)';
2489
+ }
2490
+
2491
+
2492
+
2493
+
2494
+
2495
+
2496
+
2497
+
2498
+ if (!RG.ISOLD && (prop['chart.curvy'] || prop['chart.spline'])) {
2499
+ this.DrawCurvyLine(coords, !prop['chart.line.visible'] ? 'rgba(0,0,0,0)' : color, linewidth, index);
2500
+ return;
2501
+ }
2502
+
2503
+
2504
+
2505
+
2506
+
2507
+
2508
+
2509
+
2510
+ co.beginPath();
2511
+
2512
+ var len = coords.length;
2513
+ var width = ca.width
2514
+ var height = ca.height;
2515
+ var penUp = false;
2516
+
2517
+ for (var i=0; i<len; ++i) {
2518
+
2519
+ var xPos = coords[i][0];
2520
+ var yPos = coords[i][1];
2521
+
2522
+ if (i > 0) {
2523
+ var prevX = coords[i - 1][0];
2524
+ var prevY = coords[i - 1][1];
2525
+ }
2526
+
2527
+
2528
+ if ((
2529
+ (i == 0 && coords[i])
2530
+ || (yPos < this.gutterTop)
2531
+ || (prevY < this.gutterTop)
2532
+ || (yPos > (height - this.gutterBottom))
2533
+ || (i > 0 && prevX > (width - this.gutterRight))
2534
+ || (i > 0 && prevY > (height - this.gutterBottom))
2535
+ || prevY == null
2536
+ || penUp == true
2537
+ ) && (!prop['chart.outofbounds'] || yPos == null || prevY == null) ) {
2538
+
2539
+ if (RG.ISOLD && yPos == null) {
2540
+ // ...?
2541
+ } else {
2542
+ co.moveTo(coords[i][0], coords[i][1]);
2543
+ }
2544
+
2545
+ penUp = false;
2546
+
2547
+ } else {
2548
+
2549
+ if (prop['chart.stepped'] && i > 0) {
2550
+ co.lineTo(coords[i][0], coords[i - 1][1]);
2551
+ }
2552
+
2553
+ // Don't draw the last bit of a stepped chart. Now DO
2554
+ //if (!this.properties['chart.stepped'] || i < (coords.length - 1)) {
2555
+ co.lineTo(coords[i][0], coords[i][1]);
2556
+ //}
2557
+ penUp = false;
2558
+ }
2559
+ }
2560
+
2561
+ /**
2562
+ * If two colors are specified instead of one, go over the up bits
2563
+ */
2564
+ if (prop['chart.colors.alternate'] && typeof(color) == 'object' && color[0] && color[1]) {
2565
+ for (var i=1; i<len; ++i) {
2566
+
2567
+ var prevX = coords[i - 1][0];
2568
+ var prevY = coords[i - 1][1];
2569
+
2570
+ if (prevY != null && coords[i][1] != null) {
2571
+ co.beginPath();
2572
+ co.strokeStyle = color[coords[i][1] < prevY ? 0 : 1];
2573
+ co.lineWidth = prop['chart.linewidth'];
2574
+ co.moveTo(prevX, prevY);
2575
+ co.lineTo(coords[i][0], coords[i][1]);
2576
+ co.stroke();
2577
+ }
2578
+ }
2579
+ }
2580
+ };
2581
+
2582
+
2583
+
2584
+
2585
+ /**
2586
+ * This function is used by MSIE only to manually draw the shadow
2587
+ *
2588
+ * @param array coords The coords for the line
2589
+ */
2590
+ this.drawIEShadow =
2591
+ this.DrawIEShadow = function (coords, color)
2592
+ {
2593
+ var offsetx = prop['chart.shadow.offsetx'];
2594
+ var offsety = prop['chart.shadow.offsety'];
2595
+
2596
+ co.lineWidth = prop['chart.linewidth'];
2597
+ co.strokeStyle = color;
2598
+
2599
+ co.beginPath();
2600
+ for (var i=0; i<coords.length; ++i) {
2601
+
2602
+ var isNull = RG.isNull(coords[i][1]);
2603
+ var prevIsNull = RG.isNull(coords[i-1]) || RG.isNull(coords[i-1][1]);
2604
+
2605
+ if (i == 0 || isNull || prevIsNull) {
2606
+ if (!isNull) {
2607
+ co.moveTo(coords[i][0] + offsetx, coords[i][1] + offsety);
2608
+ }
2609
+ } else {
2610
+ co.lineTo(coords[i][0] + offsetx, coords[i][1] + offsety);
2611
+ }
2612
+ }
2613
+ co.stroke();
2614
+ };
2615
+
2616
+
2617
+
2618
+
2619
+ /**
2620
+ * Draw the backdrop
2621
+ */
2622
+ this.drawBackdrop =
2623
+ this.DrawBackdrop = function (coords, color)
2624
+ {
2625
+ //var ca = this.canvas;
2626
+ //var co = this.context;
2627
+ //var prop = this.properties;
2628
+
2629
+ var size = prop['chart.backdrop.size'];
2630
+ co.lineWidth = size;
2631
+ co.globalAlpha = prop['chart.backdrop.alpha'];
2632
+ co.strokeStyle = color;
2633
+ var yCoords = [];
2634
+
2635
+ co.beginPath();
2636
+ if (prop['chart.curvy'] && !RG.ISOLD) {
2637
+
2638
+ // The DrawSpline function only takes the Y coords so extract them from the coords that have
2639
+ // (which are X/Y pairs)
2640
+ for (var i=0; i<coords.length; ++i) {
2641
+ yCoords.push(coords[i][1])
2642
+ }
2643
+
2644
+ this.DrawSpline(co, yCoords, color, null);
2645
+
2646
+ } else {
2647
+ co.moveTo(coords[0][0], coords[0][1]);
2648
+ for (var j=1; j<coords.length; ++j) {
2649
+ co.lineTo(coords[j][0], coords[j][1]);
2650
+ }
2651
+ }
2652
+ co.stroke();
2653
+
2654
+ // Reset the alpha value
2655
+ co.globalAlpha = 1;
2656
+ RG.NoShadow(this);
2657
+ };
2658
+
2659
+
2660
+
2661
+
2662
+ /**
2663
+ * Returns the linewidth
2664
+ */
2665
+ this.getLineWidth =
2666
+ this.GetLineWidth = function (i)
2667
+ {
2668
+ var linewidth = prop['chart.linewidth'];
2669
+
2670
+ if (typeof(linewidth) == 'number') {
2671
+ return linewidth;
2672
+
2673
+ } else if (typeof(linewidth) == 'object') {
2674
+ if (linewidth[i]) {
2675
+ return linewidth[i];
2676
+ } else {
2677
+ return linewidth[0];
2678
+ }
2679
+
2680
+ alert('[LINE] Error! chart.linewidth should be a single number or an array of one or more numbers');
2681
+ }
2682
+ };
2683
+
2684
+
2685
+
2686
+
2687
+ /**
2688
+ * The getPoint() method - used to get the point the mouse is currently over, if any
2689
+ *
2690
+ * @param object e The event object
2691
+ * @param object OPTIONAL You can pass in the bar object instead of the
2692
+ * function getting it from the canvas
2693
+ */
2694
+ this.getShape =
2695
+ this.getPoint = function (e)
2696
+ {
2697
+ var obj = this;
2698
+ var RG = RGraph;
2699
+ var ca = canvas = e.target;
2700
+ var co = context = this.context;
2701
+ var prop = this.properties;
2702
+
2703
+ var mouseXY = RG.getMouseXY(e);
2704
+ var mouseX = mouseXY[0];
2705
+ var mouseY = mouseXY[1];
2706
+
2707
+ // This facilitates you being able to pass in the bar object as a parameter instead of
2708
+ // the function getting it from the object
2709
+ if (arguments[1]) {
2710
+ obj = arguments[1];
2711
+ }
2712
+
2713
+ for (var i=0; i<obj.coords.length; ++i) {
2714
+
2715
+ var x = obj.coords[i][0];
2716
+ var y = obj.coords[i][1];
2717
+
2718
+ // Do this if the hotspot is triggered by the X coord AND the Y coord
2719
+ if ( mouseX <= (x + prop['chart.tooltips.hotspot.size'])
2720
+ && mouseX >= (x - prop['chart.tooltips.hotspot.size'])
2721
+ && mouseY <= (y + prop['chart.tooltips.hotspot.size'])
2722
+ && mouseY >= (y - prop['chart.tooltips.hotspot.size'])
2723
+ ) {
2724
+
2725
+ if (RG.parseTooltipText) {
2726
+ var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
2727
+ }
2728
+
2729
+ // Work out the dataset
2730
+ var dataset = 0;
2731
+ var idx = i;
2732
+ while ((idx + 1) > this.data[dataset].length) {
2733
+ idx -= this.data[dataset].length;
2734
+ dataset++;
2735
+ }
2736
+
2737
+ return {0:obj, 1:x, 2:y, 3:i, 'object': obj, 'x': x, 'y': y, 'index': i, 'tooltip': tooltip, 'dataset': dataset, 'index_adjusted': idx};
2738
+
2739
+ } else if ( prop['chart.tooltips.hotspot.xonly'] == true
2740
+ && mouseX <= (x + prop['chart.tooltips.hotspot.size'])
2741
+ && mouseX >= (x - prop['chart.tooltips.hotspot.size'])) {
2742
+
2743
+ var tooltip = RG.parseTooltipText(prop['chart.tooltips'], i);
2744
+
2745
+ return {0:obj, 1:x, 2:y, 3:i, 'object': obj, 'x': x, 'y': y, 'index': i, 'tooltip': tooltip};
2746
+ }
2747
+ }
2748
+ };
2749
+
2750
+
2751
+
2752
+
2753
+ /**
2754
+ * Draws the above line labels
2755
+ */
2756
+ this.drawAboveLabels =
2757
+ this.DrawAboveLabels = function ()
2758
+ {
2759
+ var size = prop['chart.labels.above.size'],
2760
+ font = prop['chart.labels.above.font'] || prop['chart.text.font'],
2761
+ units_pre = prop['chart.labels.above.units.pre'],
2762
+ units_post = prop['chart.labels.above.units.post'],
2763
+ decimals = prop['chart.labels.above.decimals'],
2764
+ color = prop['chart.labels.above.color'] || prop['chart.text.color'],
2765
+ bgcolor = prop['chart.labels.above.background'] || 'white',
2766
+ border = ((
2767
+ typeof prop['chart.labels.above.border'] === 'boolean'
2768
+ || typeof prop['chart.labels.above.border'] === 'number'
2769
+ ) ? prop['chart.labels.above.border'] : true),
2770
+ offsety = prop['chart.labels.above.offsety'] + size,
2771
+ specific = prop['chart.labels.above.specific'];
2772
+
2773
+ // Use this to 'reset' the drawing state
2774
+ co.beginPath();
2775
+
2776
+ // Don't need to check that chart.labels.above is enabled here, it's been done already
2777
+ for (var i=0, len=this.coords.length; i<len; i+=1) {
2778
+
2779
+ var coords = this.coords[i];
2780
+
2781
+ RG.text2(this, {
2782
+ color:color,
2783
+ 'font':font,
2784
+ 'size':size,
2785
+ 'x':coords[0],
2786
+ 'y':coords[1] - offsety,
2787
+ 'text':(specific && specific[i]) ? specific[i] : (specific ? null : RG.numberFormat(this, typeof decimals === 'number' ? this.data_arr[i].toFixed(decimals) : this.data_arr[i], units_pre, units_post)),
2788
+ 'valign':'center',
2789
+ 'halign':'center',
2790
+ 'bounding':true,
2791
+ 'boundingFill':bgcolor,
2792
+ 'boundingStroke':border ? 'black' : 'rgba(0,0,0,0)',
2793
+ 'tag':'labels.above'
2794
+ });
2795
+ }
2796
+ };
2797
+
2798
+
2799
+
2800
+
2801
+ /**
2802
+ * Draw a curvy line.
2803
+ */
2804
+ this.drawCurvyLine =
2805
+ this.DrawCurvyLine = function (coords, color, linewidth, index)
2806
+ {
2807
+ if (RG.ISOLD) {
2808
+ return;
2809
+ }
2810
+
2811
+ var yCoords = [];
2812
+
2813
+ for (var i=0; i<coords.length; ++i) {
2814
+ yCoords.push(coords[i][1]);
2815
+ }
2816
+
2817
+ if (prop['chart.filled']) {
2818
+ co.beginPath();
2819
+
2820
+ // First, work out the xaxispos
2821
+ if (prop['chart.xaxispos'] === 'center') {
2822
+ var xaxisY = ((ca.height - this.gutterTop - this.gutterBottom) / 2) + this.gutterTop;
2823
+ } else {
2824
+ var xaxisY = ca.height - this.gutterBottom;
2825
+ }
2826
+
2827
+
2828
+ co.moveTo(coords[0][0],xaxisY);
2829
+ this.drawSpline(co, yCoords, color, index);
2830
+
2831
+ if (prop['chart.filled.accumulative'] && index > 0) {
2832
+ for (var i=(this.coordsSpline[index - 1].length - 1); i>=0; i-=1) {
2833
+ co.lineTo(this.coordsSpline[index - 1][i][0], this.coordsSpline[index - 1][i][1]);
2834
+ }
2835
+ } else {
2836
+ co.lineTo(coords[coords.length - 1][0],xaxisY);
2837
+ }
2838
+ co.fill();
2839
+ }
2840
+
2841
+ co.beginPath();
2842
+ this.DrawSpline(co, yCoords, color, index);
2843
+ co.stroke();
2844
+ };
2845
+
2846
+
2847
+
2848
+
2849
+ /**
2850
+ * When you click on the chart, this method can return the Y value at that point. It works for any point on the
2851
+ * chart (that is inside the gutters) - not just points on the Line.
2852
+ *
2853
+ * @param object e The event object
2854
+ */
2855
+ this.getValue = function (arg)
2856
+ {
2857
+ if (arg.length == 2) {
2858
+ var mouseX = arg[0];
2859
+ var mouseY = arg[1];
2860
+ } else {
2861
+ var mouseCoords = RG.getMouseXY(arg);
2862
+ var mouseX = mouseCoords[0];
2863
+ var mouseY = mouseCoords[1];
2864
+ }
2865
+
2866
+ var obj = this;
2867
+ var xaxispos = prop['chart.xaxispos'];
2868
+
2869
+ if (mouseY < prop['chart.gutter.top']) {
2870
+ return xaxispos == 'bottom' || xaxispos == 'center' ? this.max : this.min;
2871
+ } else if (mouseY > (ca.height - prop['chart.gutter.bottom'])) {
2872
+ return xaxispos == 'bottom' ? this.min : this.max;
2873
+ }
2874
+
2875
+ if (prop['chart.xaxispos'] == 'center') {
2876
+ var value = (( (obj.grapharea / 2) - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min);
2877
+ value *= 2;
2878
+ value > 0 ? value += this.min : value -= this.min;
2879
+ return value;
2880
+ } else if (prop['chart.xaxispos'] == 'top') {
2881
+ var value = ((obj.grapharea - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min);
2882
+ value = Math.abs(obj.max - value) * -1;
2883
+ return value;
2884
+ } else {
2885
+ var value = ((obj.grapharea - (mouseY - prop['chart.gutter.top'])) / obj.grapharea) * (obj.max - obj.min)
2886
+ value += obj.min;
2887
+ return value;
2888
+ }
2889
+ };
2890
+
2891
+
2892
+
2893
+
2894
+ /**
2895
+ * Each object type has its own Highlight() function which highlights the appropriate shape
2896
+ *
2897
+ * @param object shape The shape to highlight
2898
+ */
2899
+ this.highlight =
2900
+ this.Highlight = function (shape)
2901
+ {
2902
+ if (prop['chart.tooltips.highlight']) {
2903
+ // Add the new highlight
2904
+ RG.Highlight.Point(this, shape);
2905
+ }
2906
+ };
2907
+
2908
+
2909
+
2910
+
2911
+ /**
2912
+ * The getObjectByXY() worker method. Don't call this call:
2913
+ *
2914
+ * RG.ObjectRegistry.getObjectByXY(e)
2915
+ *
2916
+ * @param object e The event object
2917
+ */
2918
+ this.getObjectByXY = function (e)
2919
+ {
2920
+ //var ca = this.canvas;
2921
+ //var prop = this.properties;
2922
+ var mouseXY = RG.getMouseXY(e);
2923
+
2924
+ // The 5 is so that the cursor doesn't have to be over the graphArea to trigger the hotspot
2925
+ if (
2926
+ (mouseXY[0] > prop['chart.gutter.left'] - 5)
2927
+ && mouseXY[0] < (ca.width - prop['chart.gutter.right'] + 5)
2928
+ && mouseXY[1] > (prop['chart.gutter.top'] - 5)
2929
+ && mouseXY[1] < (ca.height - prop['chart.gutter.bottom'] + 5)
2930
+ ) {
2931
+
2932
+ return this;
2933
+ }
2934
+ };
2935
+
2936
+
2937
+
2938
+
2939
+ /**
2940
+ * This method handles the adjusting calculation for when the mouse is moved
2941
+ *
2942
+ * @param object e The event object
2943
+ */
2944
+ this.adjusting_mousemove =
2945
+ this.Adjusting_mousemove = function (e)
2946
+ {
2947
+ /**
2948
+ * Handle adjusting for the Bar
2949
+ */
2950
+ if (prop['chart.adjustable'] && RG.Registry.Get('chart.adjusting') && RG.Registry.Get('chart.adjusting').uid == this.uid) {
2951
+
2952
+ // Rounding the value to the given number of decimals make the chart step
2953
+ var value = Number(this.getValue(e));//.toFixed(this.properties['chart.scale.decimals']);
2954
+ var shape = RG.Registry.Get('chart.adjusting.shape');
2955
+
2956
+ if (shape) {
2957
+
2958
+ RG.Registry.Set('chart.adjusting.shape', shape);
2959
+
2960
+ this.original_data[shape['dataset']][shape['index_adjusted']] = Number(value);
2961
+
2962
+ RG.redrawCanvas(e.target);
2963
+
2964
+ RG.fireCustomEvent(this, 'onadjust');
2965
+ }
2966
+ }
2967
+ };
2968
+
2969
+
2970
+
2971
+
2972
+ /**
2973
+ * This function can be used when the canvas is clicked on (or similar - depending on the event)
2974
+ * to retrieve the relevant Y coordinate for a particular value.
2975
+ *
2976
+ * @param int value The value to get the Y coordinate for
2977
+ */
2978
+ this.getYCoord = function (value)
2979
+ {
2980
+ if (typeof(value) != 'number') {
2981
+ return null;
2982
+ }
2983
+
2984
+ var y;
2985
+ var xaxispos = prop['chart.xaxispos'];
2986
+
2987
+ // Higher than max
2988
+ // Commented out on March 7th 2013 because the tan curve was not showing correctly
2989
+ //if (value > this.max) {
2990
+ // value = this.max;
2991
+ //}
2992
+
2993
+ if (xaxispos == 'top') {
2994
+
2995
+ // Account for negative numbers
2996
+ //if (value < 0) {
2997
+ // value = Math.abs(value);
2998
+ //}
2999
+
3000
+ y = ((value - this.min) / (this.max - this.min)) * this.grapharea;
3001
+
3002
+ // Inverted Y labels
3003
+ if (prop['chart.scale.invert']) {
3004
+ y = this.grapharea - y;
3005
+ }
3006
+
3007
+ y = y + this.gutterTop
3008
+
3009
+ } else if (xaxispos == 'center') {
3010
+
3011
+ y = ((value - this.min) / (this.max - this.min)) * (this.grapharea / 2);
3012
+ y = (this.grapharea / 2) - y;
3013
+ y += this.gutterTop;
3014
+
3015
+ } else {
3016
+
3017
+ if ((value < this.min || value > this.max) && prop['chart.outofbounds'] == false) {
3018
+ return null;
3019
+ }
3020
+
3021
+ y = ((value - this.min) / (this.max - this.min)) * this.grapharea;
3022
+
3023
+ // Inverted Y labels
3024
+ if (prop['chart.scale.invert']) {
3025
+ y = this.grapharea - y;
3026
+ }
3027
+
3028
+ y = ca.height - this.gutterBottom - y;
3029
+ }
3030
+
3031
+ return y;
3032
+ };
3033
+
3034
+
3035
+
3036
+
3037
+ /**
3038
+ * This function positions a tooltip when it is displayed
3039
+ *
3040
+ * @param obj object The chart object
3041
+ * @param int x The X coordinate specified for the tooltip
3042
+ * @param int y The Y coordinate specified for the tooltip
3043
+ * @param objec tooltip The tooltips DIV element
3044
+ */
3045
+ this.positionTooltip = function (obj, x, y, tooltip, idx)
3046
+ {
3047
+ //var ca = obj.canvas;
3048
+ //var co = obj.context;
3049
+ //var prop = obj.properties;
3050
+
3051
+ var coordX = obj.coords[tooltip.__index__][0];
3052
+ var coordY = obj.coords[tooltip.__index__][1];
3053
+ var canvasXY = RG.getCanvasXY(obj.canvas);
3054
+ var gutterLeft = prop['chart.gutter.left'];
3055
+ var gutterTop = prop['chart.gutter.top'];
3056
+ var width = tooltip.offsetWidth;
3057
+
3058
+ // Set the top position
3059
+ tooltip.style.left = 0;
3060
+ tooltip.style.top = parseInt(tooltip.style.top) - 9 + 'px';
3061
+
3062
+ // By default any overflow is hidden
3063
+ tooltip.style.overflow = '';
3064
+
3065
+ // The arrow
3066
+ var img = new Image();
3067
+ img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAFCAYAAACjKgd3AAAARUlEQVQYV2NkQAN79+797+RkhC4M5+/bd47B2dmZEVkBCgcmgcsgbAaA9GA1BCSBbhAuA/AagmwQPgMIGgIzCD0M0AMMAEFVIAa6UQgcAAAAAElFTkSuQmCC';
3068
+ img.style.position = 'absolute';
3069
+ img.id = '__rgraph_tooltip_pointer__';
3070
+ img.style.top = (tooltip.offsetHeight - 2) + 'px';
3071
+ tooltip.appendChild(img);
3072
+
3073
+ // Reposition the tooltip if at the edges:
3074
+
3075
+ // LEFT edge
3076
+ if ((canvasXY[0] + coordX - (width / 2)) < 10) {
3077
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.2)) + 'px';
3078
+ img.style.left = ((width * 0.2) - 8.5) + 'px';
3079
+
3080
+ // RIGHT edge
3081
+ } else if ((canvasXY[0] + coordX + (width / 2)) > doc.body.offsetWidth) {
3082
+ tooltip.style.left = canvasXY[0] + coordX - (width * 0.8) + 'px';
3083
+ img.style.left = ((width * 0.8) - 8.5) + 'px';
3084
+
3085
+ // Default positioning - CENTERED
3086
+ } else {
3087
+ tooltip.style.left = (canvasXY[0] + coordX - (width * 0.5)) + 'px';
3088
+ img.style.left = ((width * 0.5) - 8.5) + 'px';
3089
+ }
3090
+ };
3091
+
3092
+
3093
+
3094
+
3095
+ /**
3096
+ * This function draws a curvy line
3097
+ *
3098
+ * @param object context The 2D context
3099
+ * @param array coords The coordinates
3100
+ */
3101
+ this.drawSpline =
3102
+ this.DrawSpline = function (context, coords, color, index)
3103
+ {
3104
+ this.coordsSpline[index] = [];
3105
+ var xCoords = [];
3106
+ var gutterLeft = prop['chart.gutter.left'];
3107
+ var gutterRight = prop['chart.gutter.right'];
3108
+ var hmargin = prop['chart.hmargin'];
3109
+ var interval = (ca.width - (gutterLeft + gutterRight) - (2 * hmargin)) / (coords.length - 1);
3110
+
3111
+ co.strokeStyle = color;
3112
+
3113
+ /**
3114
+ * The drawSpline function takes an array of JUST Y coords - not X/Y coords. So the line coords need converting
3115
+ * if we've been given X/Y pairs
3116
+ */
3117
+ for (var i=0,len=coords.length; i<len;i+=1) {
3118
+ if (typeof coords[i] == 'object' && coords[i] && coords[i].length == 2) {
3119
+ coords[i] = Number(coords[i][1]);
3120
+ }
3121
+ }
3122
+
3123
+
3124
+
3125
+
3126
+ /**
3127
+ * Get the Points array in the format we want - first value should be null along with the lst value
3128
+ */
3129
+ var P = [coords[0]];
3130
+ for (var i=0; i<coords.length; ++i) {
3131
+ P.push(coords[i]);
3132
+ }
3133
+ P.push(coords[coords.length - 1] + (coords[coords.length - 1] - coords[coords.length - 2]));
3134
+
3135
+ for (var j=1; j<P.length-2; ++j) {
3136
+ for (var t=0; t<10; ++t) {
3137
+
3138
+ var yCoord = Spline( t/10, P[j-1], P[j], P[j+1], P[j+2] );
3139
+
3140
+ xCoords.push(((j-1) * interval) + (t * (interval / 10)) + gutterLeft + hmargin);
3141
+
3142
+ co.lineTo(xCoords[xCoords.length - 1], yCoord);
3143
+
3144
+
3145
+ if (typeof index == 'number') {
3146
+ this.coordsSpline[index].push([xCoords[xCoords.length - 1], yCoord]);
3147
+ }
3148
+ }
3149
+ }
3150
+
3151
+
3152
+
3153
+
3154
+
3155
+ // Draw the last section
3156
+ co.lineTo(((j-1) * interval) + gutterLeft + hmargin, P[j]);
3157
+ if (typeof index == 'number') {
3158
+ this.coordsSpline[index].push([((j-1) * interval) + gutterLeft + hmargin, P[j]]);
3159
+ }
3160
+
3161
+
3162
+
3163
+
3164
+
3165
+
3166
+ function Spline (t, P0, P1, P2, P3)
3167
+ {
3168
+ return 0.5 * ((2 * P1) +
3169
+ ((0-P0) + P2) * t +
3170
+ ((2*P0 - (5*P1) + (4*P2) - P3) * (t*t) +
3171
+ ((0-P0) + (3*P1)- (3*P2) + P3) * (t*t*t)));
3172
+ }
3173
+ };
3174
+
3175
+
3176
+
3177
+
3178
+ /**
3179
+ * This allows for easy specification of gradients
3180
+ */
3181
+ this.parseColors = function ()
3182
+ {
3183
+ // Save the original colors so that they can be restored when the canvas is reset
3184
+ if (this.original_colors.length === 0) {
3185
+ this.original_colors['chart.colors'] = RGraph.array_clone(prop['chart.colors']);
3186
+ this.original_colors['chart.fillstyle'] = RGraph.array_clone(prop['chart.fillstyle']);
3187
+ this.original_colors['chart.key.colors'] = RGraph.array_clone(prop['chart.key.colors']);
3188
+ this.original_colors['chart.background.barcolor1'] = prop['chart.background.barcolor1'];
3189
+ this.original_colors['chart.background.barcolor2'] = prop['chart.background.barcolor2'];
3190
+ this.original_colors['chart.background.grid.color'] = prop['chart.background.grid.color'];
3191
+ this.original_colors['chart.background.color'] = prop['chart.background.color'];
3192
+ this.original_colors['chart.text.color'] = prop['chart.text.color'];
3193
+ this.original_colors['chart.crosshairs.color'] = prop['chart.crosshairs.color'];
3194
+ this.original_colors['chart.annotate.color'] = prop['chart.annotate.color'];
3195
+ this.original_colors['chart.title.color'] = prop['chart.title.color'];
3196
+ this.original_colors['chart.title.yaxis.color'] = prop['chart.title.yaxis.color'];
3197
+ this.original_colors['chart.key.background'] = prop['chart.key.background'];
3198
+ this.original_colors['chart.axis.color'] = prop['chart.axis.color'];
3199
+ this.original_colors['chart.highlight.fill'] = prop['chart.highlight.fill'];
3200
+ }
3201
+
3202
+
3203
+
3204
+ for (var i=0; i<prop['chart.colors'].length; ++i) {
3205
+ if (typeof(prop['chart.colors'][i]) == 'object' && prop['chart.colors'][i][0] && prop['chart.colors'][i][1]) {
3206
+ prop['chart.colors'][i][0] = this.parseSingleColorForGradient(prop['chart.colors'][i][0]);
3207
+ prop['chart.colors'][i][1] = this.parseSingleColorForGradient(prop['chart.colors'][i][1]);
3208
+ } else {
3209
+ prop['chart.colors'][i] = this.parseSingleColorForGradient(prop['chart.colors'][i]);
3210
+ }
3211
+ }
3212
+
3213
+ /**
3214
+ * Fillstyle
3215
+ */
3216
+ if (prop['chart.fillstyle']) {
3217
+ if (typeof(prop['chart.fillstyle']) == 'string') {
3218
+ prop['chart.fillstyle'] = this.parseSingleColorForGradient(prop['chart.fillstyle'], 'vertical');
3219
+ } else {
3220
+ for (var i=0; i<prop['chart.fillstyle'].length; ++i) {
3221
+ prop['chart.fillstyle'][i] = this.parseSingleColorForGradient(prop['chart.fillstyle'][i], 'vertical');
3222
+ }
3223
+ }
3224
+ }
3225
+
3226
+ /**
3227
+ * Key colors
3228
+ */
3229
+ if (!RG.is_null(prop['chart.key.colors'])) {
3230
+ for (var i=0; i<prop['chart.key.colors'].length; ++i) {
3231
+ prop['chart.key.colors'][i] = this.parseSingleColorForGradient(prop['chart.key.colors'][i]);
3232
+ }
3233
+ }
3234
+
3235
+ /**
3236
+ * Parse various properties for colors
3237
+ */
3238
+ var properties = [
3239
+ 'chart.background.barcolor1',
3240
+ 'chart.background.barcolor2',
3241
+ 'chart.background.grid.color',
3242
+ 'chart.background.color',
3243
+ 'chart.text.color',
3244
+ 'chart.crosshairs.color',
3245
+ 'chart.annotate.color',
3246
+ 'chart.title.color',
3247
+ 'chart.title.yaxis.color',
3248
+ 'chart.key.background',
3249
+ 'chart.axis.color',
3250
+ 'chart.highlight.fill'
3251
+ ];
3252
+
3253
+ for (var i=0; i<properties.length; ++i) {
3254
+ prop[properties[i]] = this.parseSingleColorForGradient(prop[properties[i]]);
3255
+ }
3256
+ };
3257
+
3258
+
3259
+
3260
+
3261
+ /**
3262
+ * Use this function to reset the object to the post-constructor state. Eg reset colors if
3263
+ * need be etc
3264
+ */
3265
+ this.reset = function ()
3266
+ {
3267
+ };
3268
+
3269
+
3270
+
3271
+
3272
+ /**
3273
+ * This parses a single color value
3274
+ */
3275
+ this.parseSingleColorForGradient = function (color)
3276
+ {
3277
+ if (!color || typeof(color) != 'string') {
3278
+ return color;
3279
+ }
3280
+
3281
+ /**
3282
+ * Horizontal or vertical gradient
3283
+ */
3284
+ var dir = typeof(arguments[1]) == 'string' ? arguments[1] : 'vertical';
3285
+
3286
+ if (typeof color === 'string' && color.match(/^gradient\((.*)\)$/i)) {
3287
+
3288
+ var parts = RegExp.$1.split(':');
3289
+
3290
+ // Create the gradient
3291
+ if (dir == 'horizontal') {
3292
+ var grad = co.createLinearGradient(0,0,ca.width,0);
3293
+ } else {
3294
+ var grad = co.createLinearGradient(0,ca.height - prop['chart.gutter.bottom'],0,prop['chart.gutter.top']);
3295
+ }
3296
+
3297
+ var diff = 1 / (parts.length - 1);
3298
+
3299
+ grad.addColorStop(0, RG.trim(parts[0]));
3300
+
3301
+ for (var j=1; j<parts.length; ++j) {
3302
+ grad.addColorStop(j * diff, RG.trim(parts[j]));
3303
+ }
3304
+ }
3305
+
3306
+ return grad ? grad : color;
3307
+ };
3308
+
3309
+
3310
+
3311
+
3312
+ /**
3313
+ * Sets the appropriate shadow
3314
+ */
3315
+ this.setShadow =
3316
+ this.SetShadow = function (i)
3317
+ {
3318
+ //var ca = this.canvas;
3319
+ //var co = this.context;
3320
+ //var prop = this.properties;
3321
+
3322
+ if (prop['chart.shadow']) {
3323
+ /**
3324
+ * Handle the appropriate shadow color. This now facilitates an array of differing
3325
+ * shadow colors
3326
+ */
3327
+ var shadowColor = prop['chart.shadow.color'];
3328
+
3329
+ /**
3330
+ * Accommodate an array of shadow colors as well as a single string
3331
+ */
3332
+ if (typeof(shadowColor) == 'object' && shadowColor[i - 1]) {
3333
+ co.shadowColor = shadowColor[i];
3334
+
3335
+ } else if (typeof(shadowColor) == 'object') {
3336
+ co.shadowColor = shadowColor[0];
3337
+
3338
+ } else if (typeof(shadowColor) == 'string') {
3339
+ co.shadowColor = shadowColor;
3340
+ }
3341
+
3342
+ co.shadowBlur = prop['chart.shadow.blur'];
3343
+ co.shadowOffsetX = prop['chart.shadow.offsetx'];
3344
+ co.shadowOffsetY = prop['chart.shadow.offsety'];
3345
+ }
3346
+ };
3347
+
3348
+
3349
+
3350
+
3351
+ /**
3352
+ * This function handles highlighting an entire data-series for the interactive
3353
+ * key
3354
+ *
3355
+ * @param int index The index of the data series to be highlighted
3356
+ */
3357
+ this.interactiveKeyHighlight = function (index)
3358
+ {
3359
+ var coords = this.coords2[index];
3360
+
3361
+ if (coords) {
3362
+
3363
+ var pre_linewidth = co.lineWidth;
3364
+ var pre_linecap = co.lineCap;
3365
+
3366
+ co.lineWidth = prop['chart.linewidth'] + 10;
3367
+ co.lineCap = 'round';
3368
+ co.strokeStyle = prop['chart.key.interactive.highlight.chart.stroke'];
3369
+
3370
+
3371
+ co.beginPath();
3372
+ if (prop['chart.curvy']) {
3373
+ this.DrawSpline(co, coords, prop['chart.key.interactive.highlight.chart'], null);
3374
+ } else {
3375
+ for (var i=0,len=coords.length; i<len; i+=1) {
3376
+ if ( i == 0
3377
+ || RG.is_null(coords[i][1])
3378
+ || (typeof coords[i - 1][1] != undefined && RG.is_null(coords[i - 1][1]))) {
3379
+ co.moveTo(coords[i][0], coords[i][1]);
3380
+ } else {
3381
+ co.lineTo(coords[i][0], coords[i][1]);
3382
+ }
3383
+ }
3384
+ }
3385
+ co.stroke();
3386
+
3387
+ // Reset the lineCap and lineWidth
3388
+ co.lineWidth = pre_linewidth;
3389
+ co.lineCap = pre_linecap;
3390
+ }
3391
+ };
3392
+
3393
+
3394
+
3395
+
3396
+ /**
3397
+ * Using a function to add events makes it easier to facilitate method chaining
3398
+ *
3399
+ * @param string type The type of even to add
3400
+ * @param function func
3401
+ */
3402
+ this.on = function (type, func)
3403
+ {
3404
+ if (type.substr(0,2) !== 'on') {
3405
+ type = 'on' + type;
3406
+ }
3407
+
3408
+ this[type] = func;
3409
+
3410
+ return this;
3411
+ };
3412
+
3413
+
3414
+
3415
+
3416
+ /**
3417
+ * This function runs once only
3418
+ * (put at the end of the file (before any effects))
3419
+ */
3420
+ this.firstDrawFunc = function ()
3421
+ {
3422
+ };
3423
+
3424
+
3425
+
3426
+
3427
+ /**
3428
+ * Trace
3429
+ *
3430
+ * This effect is for the Line chart, uses the jQuery library and slowly
3431
+ * uncovers the Line , but you can see the background of the chart. This effect
3432
+ * is quite new (1/10/2011) and as such should be used with caution.
3433
+ *
3434
+ * @param object An object of configuration. You can give 'duration' or 'frames' here.
3435
+ * @param function An optional callback function
3436
+ */
3437
+ this.trace = function ()
3438
+ {
3439
+ var obj = this;
3440
+ var callback = typeof arguments[1] === 'function' ? arguments[1] : function () {};
3441
+ var opt = arguments[0] || {};
3442
+
3443
+ if (opt.frames) {
3444
+ opt.duration = (opt.frames / 60) * 1000;
3445
+ }
3446
+
3447
+ if (!opt.duration) {
3448
+ opt.duration = 1500;
3449
+ }
3450
+
3451
+ RG.clear(obj.canvas);
3452
+ RG.redrawCanvas(obj.canvas);
3453
+
3454
+ /**
3455
+ * Create the DIV that the second canvas will sit in
3456
+ */
3457
+ var div = doc.createElement('DIV');
3458
+ var xy = RG.getCanvasXY(obj.canvas);
3459
+ div.id = '__rgraph_trace_animation_' + RG.random(0, 4351623) + '__';
3460
+ div.style.left = xy[0] + 'px';
3461
+ div.style.top = xy[1] + 'px';
3462
+ div.style.width = obj.Get('chart.gutter.left');
3463
+ div.style.height = obj.canvas.height + 'px';
3464
+ div.style.position = 'absolute';
3465
+ div.style.overflow = 'hidden';
3466
+ doc.body.appendChild(div);
3467
+
3468
+ obj.canvas.__rgraph_trace_div__ = div;
3469
+
3470
+ /**
3471
+ * Make the second canvas
3472
+ */
3473
+ var id = '__rgraph_line_trace_animation_' + RG.random(0, 99999999) + '__';
3474
+ var canvas2 = doc.createElement('CANVAS');
3475
+
3476
+
3477
+
3478
+
3479
+ // Copy the 3D CSS transformation properties across from the original canvas
3480
+ var properties = ['WebkitTransform','MozTransform','OTransform','MSTransform','transform'];
3481
+
3482
+ for (i in properties) {
3483
+ var name = properties[i];
3484
+ if (typeof obj.canvas.style[name] === 'string' && obj.canvas.style[name]) {
3485
+ canvas2.style[name] = obj.canvas.style[name];
3486
+ }
3487
+ }
3488
+
3489
+
3490
+
3491
+ obj.canvas.__rgraph_line_canvas2__ = canvas2;
3492
+ canvas2.width = obj.canvas.width;
3493
+ canvas2.height = obj.canvas.height;
3494
+ canvas2.style.position = 'absolute';
3495
+ canvas2.style.left = 0;
3496
+ canvas2.style.top = 0;
3497
+
3498
+
3499
+ // This stops the clear effect clearing the canvas - which can happen if you have multiple canvas tags on the page all with
3500
+ // dynamic effects that do redrawing
3501
+ canvas2.noclear = true;
3502
+
3503
+ canvas2.id = id;
3504
+ div.appendChild(canvas2);
3505
+
3506
+ var reposition_canvas2 = function (e)
3507
+ {
3508
+ var xy = RG.getCanvasXY(obj.canvas);
3509
+
3510
+ div.style.left = xy[0] + 'px';
3511
+ div.style.top = xy[1] + 'px';
3512
+ }
3513
+ window.addEventListener('resize', reposition_canvas2, false)
3514
+
3515
+ /**
3516
+ * Make a copy of the original Line object
3517
+ */
3518
+ var obj2 = new RG.Line(id, RG.array_clone(obj.original_data));
3519
+
3520
+ // Remove the new line from the ObjectRegistry so that it isn't redawn
3521
+ RG.ObjectRegistry.Remove(obj2);
3522
+
3523
+ for (i in obj.properties) {
3524
+ if (typeof i === 'string') {
3525
+ obj2.Set(i, obj.properties[i]);
3526
+ }
3527
+ }
3528
+
3529
+ //obj2.Set('chart.tooltips', null);
3530
+ obj2.Set('labels', []);
3531
+ obj2.Set('background.grid', false);
3532
+ obj2.Set('background.barcolor1', 'rgba(0,0,0,0)');
3533
+ obj2.Set('background.barcolor2', 'rgba(0,0,0,0)');
3534
+ obj2.Set('ylabels', false);
3535
+ obj2.Set('noaxes', true);
3536
+ obj2.Set('title', '');
3537
+ obj2.Set('title.xaxis', '');
3538
+ obj2.Set('title.yaxis', '');
3539
+ obj2.Set('filled.accumulative', obj.Get('chart.filled.accumulative'));
3540
+ obj.Set('key', []);
3541
+ obj2.Draw();
3542
+
3543
+ obj.canvas.__rgraph_trace_obj2__ = obj2;
3544
+
3545
+
3546
+ /**
3547
+ * This effectively hides the line
3548
+ */
3549
+ obj.Set('line.visible', false);
3550
+ obj.Set('colors', ['rgba(0,0,0,0)']);
3551
+ if (obj.Get('filled')) {
3552
+ var original_fillstyle = obj.Get('chart.fillstyle');
3553
+ obj.Set('fillstyle', 'rgba(0,0,0,0)');
3554
+ obj.Set('animation.trace.original.fillstyle', original_fillstyle);
3555
+ }
3556
+
3557
+ RG.clear(obj.canvas);
3558
+ //obj.Draw();
3559
+ RG.redrawCanvas(obj.canvas);
3560
+
3561
+ /**
3562
+ * Place a DIV over the canvas to stop interaction with it
3563
+ */
3564
+ if (!obj.canvas.__rgraph_trace_cover__) {
3565
+ var div2 = doc.createElement('DIV');
3566
+ div2.id = '__rgraph_trace_animation_' + RG.random(0, 4351623) + '__';
3567
+ div2.style.left = xy[0] + 'px';
3568
+ div2.style.top = xy[1] + 'px';
3569
+ div2.style.width = obj.canvas.width + 'px';
3570
+ div2.style.height = obj.canvas.height + 'px';
3571
+ div2.style.position = 'absolute';
3572
+ div2.style.overflow = 'hidden';
3573
+ div2.style.backgroundColor = 'rgba(0,0,0,0)';
3574
+ div.div2 = div2;
3575
+ obj.canvas.__rgraph_trace_cover__ = div2;
3576
+ doc.body.appendChild(div2);
3577
+ } else {
3578
+ div2 = obj.canvas.__rgraph_trace_cover__;
3579
+ }
3580
+
3581
+
3582
+
3583
+ /**
3584
+ * Get rid of the second canvas and turn the line back on
3585
+ * on the original.
3586
+ */
3587
+ trace_complete = function (obj)
3588
+ {
3589
+ var obj2 = obj.canvas.__rgraph_trace_obj2__;
3590
+
3591
+ // Remove the window resize listener
3592
+ win.removeEventListener('resize', reposition_canvas2, false);
3593
+
3594
+ div.style.display = 'none';
3595
+ div2.style.display = 'none';
3596
+
3597
+ //div.removeChild(canvas2);
3598
+ obj.Set('line.visible', true);
3599
+
3600
+ // Revert the filled status back to as it was
3601
+ obj.Set('filled', RGraph.array_clone(obj2.Get('chart.filled')));
3602
+ obj.Set('fillstyle', obj.Get('chart.animation.trace.original.fillstyle'));
3603
+ obj.Set('colors', RGraph.array_clone(obj2.Get('chart.colors')));
3604
+ obj.Set('key', RGraph.array_clone(obj2.Get('chart.key')));
3605
+
3606
+ RGraph.RedrawCanvas(obj.canvas);
3607
+
3608
+ obj.canvas.__rgraph_trace_div__.style.display = 'none';
3609
+ obj.canvas.__rgraph_line_canvas2__.style.display = 'none';
3610
+ obj.canvas.__rgraph_trace_cover__.style.display = 'none';
3611
+ obj.canvas.__rgraph_trace_div__ = null;
3612
+ obj.canvas.__rgraph_line_canvas2__ = null;
3613
+ obj.canvas.__rgraph_trace_cover__ = null;
3614
+
3615
+
3616
+ callback(obj);
3617
+ };
3618
+
3619
+
3620
+
3621
+
3622
+ /**
3623
+ * Animate the DIV that contains the canvas
3624
+ */
3625
+ jQuery('#' + div.id).animate({
3626
+ width: obj.canvas.width - obj.gutterRight + 'px'
3627
+ }, opt.duration, function () {trace_complete(obj)});
3628
+
3629
+ return this;
3630
+ };
3631
+
3632
+
3633
+
3634
+
3635
+ /**
3636
+ * Unfold
3637
+ *
3638
+ * This effect gradually increases the X/Y coordinatesfrom 0
3639
+ *
3640
+ * @param object obj The chart object
3641
+ */
3642
+ this.unfold = function ()
3643
+ {
3644
+ var obj = this;
3645
+ var opt = arguments[0] ? arguments[0] : {};
3646
+ var frames = opt.frames ? opt.frames : 30;
3647
+ var frame = 0;
3648
+ var callback = arguments[1] ? arguments[1] : function () {};
3649
+ var initial = prop['chart.animation.unfold.initial'];
3650
+
3651
+ prop['chart.animation.factor'] = prop['chart.animation.unfold.initial'];
3652
+
3653
+ function iterator ()
3654
+ {
3655
+ prop['chart.animation.factor'] = ((1 - initial) * (frame / frames)) + initial;
3656
+
3657
+ RG.clear(obj.canvas);
3658
+ RG.redrawCanvas(obj.canvas);
3659
+
3660
+ if (frame < frames) {
3661
+ frame++;
3662
+ RG.Effects.updateCanvas(iterator);
3663
+ } else {
3664
+ callback(obj);
3665
+ }
3666
+ }
3667
+
3668
+
3669
+ iterator();
3670
+
3671
+ return this;
3672
+ };
3673
+
3674
+
3675
+
3676
+
3677
+ /**
3678
+ * Trace2
3679
+ *
3680
+ * This is a new version of the Trace effect which no longer requires jQuery and is more compatible
3681
+ * with other effects (eg Expand). This new effect is considerably simpler and less code.
3682
+ *
3683
+ * @param object Options for the effect. Currently only "frames" is available.
3684
+ * @param int A function that is called when the ffect is complete
3685
+ */
3686
+ this.trace2 = function ()
3687
+ {
3688
+ var obj = this;
3689
+ var callback = arguments[2];
3690
+ var opt = arguments[0] || {};
3691
+ var frames = opt.frames || 30;
3692
+ var frame = 0;
3693
+ var callback = arguments[1] || function () {};
3694
+
3695
+ obj.Set('animation.trace.clip', 0);
3696
+
3697
+ function iterator ()
3698
+ {
3699
+ RG.clear(obj.canvas);
3700
+ RG.redrawCanvas(obj.canvas);
3701
+
3702
+ if (frame++ < frames) {
3703
+ obj.Set('animation.trace.clip', frame / frames);
3704
+ RG.Effects.updateCanvas(iterator);
3705
+ } else {
3706
+ callback(obj);
3707
+ }
3708
+ }
3709
+
3710
+ iterator();
3711
+
3712
+ return this;
3713
+ };
3714
+
3715
+
3716
+
3717
+
3718
+ /**
3719
+ * FoldToCenter
3720
+ *
3721
+ * Line chart FoldTocenter
3722
+ *
3723
+ * @param object OPTIONAL An object map of options
3724
+ * @param function OPTIONAL A callback to run when the effect is complete
3725
+ */
3726
+ this.foldtocenter =
3727
+ this.foldToCenter = function ()
3728
+ {
3729
+ var obj = this;
3730
+ var opt = arguments[0] || {};
3731
+ var frames = opt.frames || 30;
3732
+ var frame = 0;
3733
+ var callback = arguments[1] || function () {};
3734
+ var center_value = obj.scale2.max / 2;
3735
+
3736
+ obj.Set('chart.ymax', obj.scale2.max);
3737
+
3738
+ var original_data = RG.array_clone(obj.original_data);
3739
+
3740
+ function iterator ()
3741
+ {
3742
+ for (var i=0,len=obj.data.length; i<len; ++i) {
3743
+ if (obj.data[i].length) {
3744
+ for (var j=0,len2=obj.data[i].length; j<len2; ++j) {
3745
+
3746
+ var dataset = obj.original_data[i];
3747
+
3748
+ if (dataset[j] > center_value) {
3749
+ dataset[j] = original_data[i][j] - ((original_data[i][j] - center_value) * (frame / frames));
3750
+ } else {
3751
+ dataset[j] = original_data[i][j] + (((center_value - original_data[i][j]) / frames) * frame);
3752
+ }
3753
+ }
3754
+ }
3755
+ }
3756
+
3757
+ RG.clear(obj.canvas);
3758
+ RG.redrawCanvas(obj.canvas)
3759
+
3760
+ if (frame++ < frames) {
3761
+ RG.Effects.updateCanvas(iterator);
3762
+ } else {
3763
+ callback(obj);
3764
+ }
3765
+ }
3766
+
3767
+
3768
+
3769
+ iterator();
3770
+
3771
+
3772
+
3773
+ return this;
3774
+ };
3775
+
3776
+
3777
+
3778
+
3779
+ /**
3780
+ * UnfoldFromCenterTrace effect
3781
+ *
3782
+ * @param object An object containing options
3783
+ * @param function A callback function
3784
+ */
3785
+ this.unfoldFromCenterTrace =
3786
+ this.unfoldFromCenterTrace2 = function ()
3787
+ {
3788
+ var obj = this;
3789
+ var opt = arguments[0] || {};
3790
+ var frames = opt.frames || 30;
3791
+ var frame = 0;
3792
+ var data = RG.array_clone(obj.original_data);
3793
+ var callback = arguments[1] || function () {};
3794
+
3795
+
3796
+
3797
+ // Draw the chart once to get the scale values
3798
+ obj.canvas.style.visibility = 'hidden';
3799
+ obj.Draw();
3800
+ var max = obj.scale2.max;
3801
+ RG.clear(obj.canvas);
3802
+ obj.canvas.style.visibility = 'visible';
3803
+
3804
+
3805
+
3806
+
3807
+ /**
3808
+ * When the Trace function finishes it calls this function
3809
+ */
3810
+ var unfoldCallback = function ()
3811
+ {
3812
+ obj.original_data = data;
3813
+ obj.unfoldFromCenter({frames: frames / 2}, callback);
3814
+ };
3815
+
3816
+
3817
+
3818
+ /**
3819
+ * Determine the mid-point
3820
+ */
3821
+ var half = obj.Get('chart.xaxispos') == 'center' ? obj.min : ((obj.max - obj.min) / 2) + obj.min;
3822
+ obj.Set('chart.ymax', obj.max);
3823
+
3824
+ for (var i=0,len=obj.original_data.length; i<len; ++i) {
3825
+ for (var j=0; j<obj.original_data[i].length; ++j) {
3826
+ obj.original_data[i][j] = (obj.Get('chart.filled') && obj.Get('chart.filled.accumulative') && i > 0) ? 0 : half;
3827
+ }
3828
+ }
3829
+
3830
+ RG.clear(obj.canvas);
3831
+ obj.trace2({frames: frames / 2}, unfoldCallback);
3832
+ };
3833
+
3834
+
3835
+
3836
+
3837
+ /**
3838
+ * UnfoldFromCenter
3839
+ *
3840
+ * Line chart unfold from center
3841
+ *
3842
+ * @param object An option map of properties. Only frames is supported: {frames: 30}
3843
+ * @param function An optional callback
3844
+ */
3845
+ this.unfoldFromCenter = function ()
3846
+ {
3847
+ var obj = this;
3848
+ var opt = arguments[0] || {};
3849
+ var frames = opt.frames || 30;
3850
+ var frame = 0;
3851
+ var callback = arguments[1] || function () {};
3852
+
3853
+ // Draw the chart once to get the scale values
3854
+ obj.canvas.style.visibility = 'hidden';
3855
+ obj.Draw();
3856
+ var max = obj.scale2.max;
3857
+ RG.clear(obj.canvas);
3858
+ obj.canvas.style.visibility = 'visible';
3859
+
3860
+ var center_value = obj.Get('chart.xaxispos') === 'center' ? prop['chart.ymin'] : ((obj.max - obj.min) / 2) + obj.min;
3861
+ var original_data = RG.array_clone(obj.original_data);
3862
+ var steps = null;
3863
+
3864
+ obj.Set('chart.ymax', max);
3865
+
3866
+ if (!steps) {
3867
+
3868
+ steps = [];
3869
+
3870
+ for (var dataset=0,len=original_data.length; dataset<len; ++dataset) {
3871
+
3872
+ steps[dataset] = []
3873
+
3874
+ for (var i=0,len2=original_data[dataset].length; i<len2; ++i) {
3875
+ if (prop['chart.filled'] && prop['chart.filled.accumulative'] && dataset > 0) {
3876
+ steps[dataset][i] = original_data[dataset][i] / frames;
3877
+ obj.original_data[dataset][i] = center_value;
3878
+ } else {
3879
+ steps[dataset][i] = (original_data[dataset][i] - center_value) / frames;
3880
+ obj.original_data[dataset][i] = center_value;
3881
+ }
3882
+ }
3883
+ }
3884
+ }
3885
+
3886
+ function unfoldFromCenter ()
3887
+ {
3888
+ for (var dataset=0; dataset<original_data.length; ++dataset) {
3889
+ for (var i=0; i<original_data[dataset].length; ++i) {
3890
+ obj.original_data[dataset][i] += steps[dataset][i];
3891
+ }
3892
+ }
3893
+
3894
+ RG.clear(obj.canvas);
3895
+ RG.redrawCanvas(obj.canvas);
3896
+
3897
+ if (--frames > 0) {
3898
+ RG.Effects.updateCanvas(unfoldFromCenter);
3899
+ } else {
3900
+ obj.original_data = RG.array_clone(original_data);
3901
+ RG.clear(obj.canvas);
3902
+ RG.redrawCanvas(obj.canvas);
3903
+
3904
+ callback(obj);
3905
+ }
3906
+ }
3907
+
3908
+ unfoldFromCenter();
3909
+
3910
+ return this;
3911
+ };
3912
+
3913
+
3914
+
3915
+
3916
+
3917
+
3918
+
3919
+
3920
+ RG.att(ca);
3921
+
3922
+
3923
+
3924
+
3925
+ /**
3926
+ * Register the object so it is redrawn when necessary
3927
+ */
3928
+ RG.Register(this);
3929
+
3930
+
3931
+
3932
+
3933
+ /**
3934
+ * This is the 'end' of the constructor so if the first argument
3935
+ * contains configuration data - handle that.
3936
+ */
3937
+ if (parseConfObjectForOptions) {
3938
+ RG.parseObjectStyleConfig(this, conf.options);
3939
+ }
3940
+ };