rgraph-rails 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/CODE_OF_CONDUCT.md +13 -0
  6. data/Gemfile +4 -0
  7. data/README.md +73 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/lib/rgraph-rails/version.rb +3 -0
  12. data/lib/rgraph-rails.rb +8 -0
  13. data/license.txt +19 -0
  14. data/rgraph-rails.gemspec +26 -0
  15. data/vendor/assets/images/bg.png +0 -0
  16. data/vendor/assets/images/bullet.png +0 -0
  17. data/vendor/assets/images/facebook-large.png +0 -0
  18. data/vendor/assets/images/google-plus-large.png +0 -0
  19. data/vendor/assets/images/logo.png +0 -0
  20. data/vendor/assets/images/meter-image-sd-needle.png +0 -0
  21. data/vendor/assets/images/meter-image-sd.png +0 -0
  22. data/vendor/assets/images/meter-sketch-needle.png +0 -0
  23. data/vendor/assets/images/meter-sketch.png +0 -0
  24. data/vendor/assets/images/odometer-background.png +0 -0
  25. data/vendor/assets/images/rgraph.jpg +0 -0
  26. data/vendor/assets/images/title.png +0 -0
  27. data/vendor/assets/images/twitter-large.png +0 -0
  28. data/vendor/assets/javascripts/RGraph.bar.js +3246 -0
  29. data/vendor/assets/javascripts/RGraph.bipolar.js +2003 -0
  30. data/vendor/assets/javascripts/RGraph.common.annotate.js +399 -0
  31. data/vendor/assets/javascripts/RGraph.common.context.js +600 -0
  32. data/vendor/assets/javascripts/RGraph.common.core.js +4751 -0
  33. data/vendor/assets/javascripts/RGraph.common.csv.js +275 -0
  34. data/vendor/assets/javascripts/RGraph.common.deprecated.js +454 -0
  35. data/vendor/assets/javascripts/RGraph.common.dynamic.js +1194 -0
  36. data/vendor/assets/javascripts/RGraph.common.effects.js +1524 -0
  37. data/vendor/assets/javascripts/RGraph.common.key.js +735 -0
  38. data/vendor/assets/javascripts/RGraph.common.resizing.js +550 -0
  39. data/vendor/assets/javascripts/RGraph.common.tooltips.js +605 -0
  40. data/vendor/assets/javascripts/RGraph.common.zoom.js +223 -0
  41. data/vendor/assets/javascripts/RGraph.drawing.background.js +636 -0
  42. data/vendor/assets/javascripts/RGraph.drawing.circle.js +579 -0
  43. data/vendor/assets/javascripts/RGraph.drawing.image.js +810 -0
  44. data/vendor/assets/javascripts/RGraph.drawing.marker1.js +710 -0
  45. data/vendor/assets/javascripts/RGraph.drawing.marker2.js +672 -0
  46. data/vendor/assets/javascripts/RGraph.drawing.marker3.js +568 -0
  47. data/vendor/assets/javascripts/RGraph.drawing.poly.js +623 -0
  48. data/vendor/assets/javascripts/RGraph.drawing.rect.js +603 -0
  49. data/vendor/assets/javascripts/RGraph.drawing.text.js +648 -0
  50. data/vendor/assets/javascripts/RGraph.drawing.xaxis.js +815 -0
  51. data/vendor/assets/javascripts/RGraph.drawing.yaxis.js +860 -0
  52. data/vendor/assets/javascripts/RGraph.fuel.js +965 -0
  53. data/vendor/assets/javascripts/RGraph.funnel.js +988 -0
  54. data/vendor/assets/javascripts/RGraph.gantt.js +1242 -0
  55. data/vendor/assets/javascripts/RGraph.gauge.js +1391 -0
  56. data/vendor/assets/javascripts/RGraph.hbar.js +1794 -0
  57. data/vendor/assets/javascripts/RGraph.hprogress.js +1307 -0
  58. data/vendor/assets/javascripts/RGraph.line.js +3940 -0
  59. data/vendor/assets/javascripts/RGraph.meter.js +1242 -0
  60. data/vendor/assets/javascripts/RGraph.modaldialog.js +292 -0
  61. data/vendor/assets/javascripts/RGraph.odo.js +1265 -0
  62. data/vendor/assets/javascripts/RGraph.pie.js +1979 -0
  63. data/vendor/assets/javascripts/RGraph.radar.js +1840 -0
  64. data/vendor/assets/javascripts/RGraph.rose.js +1860 -0
  65. data/vendor/assets/javascripts/RGraph.rscatter.js +1332 -0
  66. data/vendor/assets/javascripts/RGraph.scatter.js +3029 -0
  67. data/vendor/assets/javascripts/RGraph.thermometer.js +1131 -0
  68. data/vendor/assets/javascripts/RGraph.vprogress.js +1326 -0
  69. data/vendor/assets/javascripts/RGraph.waterfall.js +1252 -0
  70. data/vendor/assets/javascripts/financial-data.js +1067 -0
  71. data/vendor/assets/stylesheets/ModalDialog.css +90 -0
  72. data/vendor/assets/stylesheets/animations.css +3347 -0
  73. data/vendor/assets/stylesheets/website.css +402 -0
  74. metadata +175 -0
@@ -0,0 +1,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
+ };