initial commit
[home-automation.git] / libraries / RGraph.scatter.js
1     /**
2     * o------------------------------------------------------------------------------o
3     * | This file is part of the RGraph package - you can learn more at:             |
4     * |                                                                              |
5     * |                          http://www.rgraph.net                               |
6     * |                                                                              |
7     * | This package is licensed under the RGraph license. For all kinds of business |
8     * | purposes there is a small one-time licensing fee to pay and for non          |
9     * | commercial  purposes it is free to use. You can read the full license here:  |
10     * |                                                                              |
11     * |                      http://www.rgraph.net/LICENSE.txt                       |
12     * o------------------------------------------------------------------------------o
13     */
14
15     if (typeof(RGraph) == 'undefined') RGraph = {};
16
17     /**
18     * The scatter graph constructor
19     * 
20     * @param object canvas The cxanvas object
21     * @param array  data   The chart data
22     */
23     RGraph.Scatter = function (id, data)
24     {
25         // Get the canvas and context objects
26         this.id                = id;
27         this.canvas            = document.getElementById(id);
28         this.canvas.__object__ = this;
29         this.context           = this.canvas.getContext ? this.canvas.getContext("2d") : null;
30         this.max               = 0;
31         this.coords            = [];
32         this.data              = [];
33         this.type              = 'scatter';
34         this.isRGraph          = true;
35
36
37         /**
38         * Compatibility with older browsers
39         */
40         RGraph.OldBrowserCompat(this.context);
41
42
43         // Various config properties
44         this.properties = {
45             'chart.background.barcolor1':   'white',
46             'chart.background.barcolor2':   'white',
47             'chart.background.grid':        true,
48             'chart.background.grid.width':  1,
49             'chart.background.grid.color':  '#ddd',
50             'chart.background.grid.hsize':  20,
51             'chart.background.grid.vsize':  20,
52             'chart.background.hbars':       null,
53             'chart.background.vbars':       null,
54             'chart.background.grid.vlines': true,
55             'chart.background.grid.hlines': true,
56             'chart.background.grid.border': true,
57             'chart.background.grid.autofit':false,
58             'chart.background.grid.autofit.numhlines': 7,
59             'chart.background.grid.autofit.numvlines': 20,
60             'chart.text.size':              10,
61             'chart.text.angle':             0,
62             'chart.text.color':             'black',
63             'chart.text.font':              'Verdana',
64             'chart.tooltips.effect':         'fade',
65             'chart.tooltips.hotspot':        3,
66             'chart.tooltips.css.class':      'RGraph_tooltip',
67             'chart.tooltips.highlight':     true,
68             'chart.tooltips.coords.adjust':  [0,0],
69             'chart.units.pre':              '',
70             'chart.units.post':             '',
71             'chart.tickmarks':              'cross',
72             'chart.ticksize':               5,
73             'chart.xticks':                 true,
74             'chart.xaxis':                  true,
75             'chart.gutter':                 25,
76             'chart.xmax':                   0,
77             'chart.ymax':                   null,
78             'chart.ymin':                   null,
79             'chart.scale.decimals':         null,
80             'chart.scale.point':            '.',
81             'chart.scale.thousand':         ',',
82             'chart.title':                  '',
83             'chart.title.background':       null,
84             'chart.title.hpos':             null,
85             'chart.title.vpos':             null,
86             'chart.title.xaxis':            '',
87             'chart.title.yaxis':            '',
88             'chart.title.xaxis.pos':        0.25,
89             'chart.title.yaxis.pos':        0.25,
90             'chart.labels':                 [],
91             'chart.ylabels':                true,
92             'chart.ylabels.count':          5,
93             'chart.contextmenu':            null,
94             'chart.defaultcolor':           'black',
95             'chart.xaxispos':               'bottom',
96             'chart.yaxispos':               'left',
97             'chart.noendxtick':             false,
98             'chart.crosshairs':             false,
99             'chart.crosshairs.color':       '#333',
100             'chart.crosshairs.linewidth':   1,
101             'chart.crosshairs.coords':      false,
102             'chart.crosshairs.coords.fixed':true,
103             'chart.crosshairs.coords.fadeout':false,
104             'chart.crosshairs.coords.labels.x': 'X',
105             'chart.crosshairs.coords.labels.y': 'Y',
106             'chart.annotatable':            false,
107             'chart.annotate.color':         'black',
108             'chart.line':                   false,
109             'chart.line.linewidth':         1,
110             'chart.line.colors':            ['green', 'red'],
111             'chart.line.shadow.color':      'rgba(0,0,0,0)',
112             'chart.line.shadow.blur':       2,
113             'chart.line.shadow.offsetx':    3,
114             'chart.line.shadow.offsety':    3,
115             'chart.line.stepped':           false,
116             'chart.noaxes':                 false,
117             'chart.key':                    [],
118             'chart.key.background':         'white',
119             'chart.key.position':           'graph',
120             'chart.key.shadow':             false,
121             'chart.key.shadow.color':       '#666',
122             'chart.key.shadow.blur':        3,
123             'chart.key.shadow.offsetx':     2,
124             'chart.key.shadow.offsety':     2,
125             'chart.key.position.gutter.boxed': true,
126             'chart.key.position.x':         null,
127             'chart.key.position.y':         null,
128             'chart.key.color.shape':        'square',
129             'chart.key.rounded':            true,
130             'chart.axis.color':             'black',
131             'chart.zoom.factor':            1.5,
132             'chart.zoom.fade.in':           true,
133             'chart.zoom.fade.out':          true,
134             'chart.zoom.hdir':              'right',
135             'chart.zoom.vdir':              'down',
136             'chart.zoom.frames':            10,
137             'chart.zoom.delay':             50,
138             'chart.zoom.shadow':            true,
139             'chart.zoom.mode':              'canvas',
140             'chart.zoom.thumbnail.width':   75,
141             'chart.zoom.thumbnail.height':  75,
142             'chart.zoom.background':        true,
143             'chart.zoom.action':            'zoom',
144             'chart.boxplot.width':          8,
145             'chart.resizable':              false,
146             'chart.xmin':                   0
147         }
148
149         // Handle multiple datasets being given as one argument
150         if (arguments[1][0] && arguments[1][0][0] && typeof(arguments[1][0][0][0]) == 'number') {
151             // Store the data set(s)
152             for (var i=0; i<arguments[1].length; ++i) {
153                 this.data[i] = arguments[1][i];
154             }
155
156         // Handle multiple data sets being supplied as seperate arguments
157         } else {
158             // Store the data set(s)
159             for (var i=1; i<arguments.length; ++i) {
160                 this.data[i - 1] = arguments[i];
161             }
162         }
163
164         // Check for support
165         if (!this.canvas) {
166             alert('[SCATTER] No canvas support');
167             return;
168         }
169
170         // Check the common library has been included
171         if (typeof(RGraph) == 'undefined') {
172             alert('[SCATTER] Fatal error: The common library does not appear to have been included');
173         }
174     }
175
176
177     /**
178     * A simple setter
179     * 
180     * @param string name  The name of the property to set
181     * @param string value The value of the property
182     */
183     RGraph.Scatter.prototype.Set = function (name, value)
184     {
185         /**
186         * This is here because the key expects a name of "chart.colors"
187         */
188         if (name == 'chart.line.colors') {
189             this.properties['chart.colors'] = value;
190         }
191         
192         /**
193         * Allow compatibility with older property names
194         */
195         if (name == 'chart.tooltip.hotspot') {
196             name = 'chart.tooltips.hotspot';
197         }
198         
199         /**
200         * chart.yaxispos should be left or right
201         */
202         if (name == 'chart.yaxispos' && value != 'left' && value != 'right') {
203             alert("[SCATTER] chart.yaxispos should be left or right. You've set it to: '" + value + "' Changing it to left");
204             value = 'left';
205         }
206
207         this.properties[name.toLowerCase()] = value;
208     }
209
210
211     /**
212     * A simple getter
213     * 
214     * @param string name  The name of the property to set
215     */
216     RGraph.Scatter.prototype.Get = function (name)
217     {
218         return this.properties[name];
219     }
220
221
222     /**
223     * The function you call to draw the line chart
224     */
225     RGraph.Scatter.prototype.Draw = function ()
226     {
227         /**
228         * Fire the onbeforedraw event
229         */
230         RGraph.FireCustomEvent(this, 'onbeforedraw');
231
232         /**
233         * Clear all of this canvases event handlers (the ones installed by RGraph)
234         */
235         RGraph.ClearEventListeners(this.id);
236
237         // Go through all the data points and see if a tooltip has been given
238         this.Set('chart.tooltips', false);
239         this.hasTooltips = false;
240         var overHotspot  = false;
241
242         // Reset the coords array
243         this.coords = [];
244
245         if (!RGraph.isIE8()) {
246             for (var i=0; i<this.data.length; ++i) {
247                 for (var j =0;j<this.data[i].length; ++j) {
248                     if (this.data[i][j] && this.data[i][j][3] && typeof(this.data[i][j][3]) == 'string' && this.data[i][j][3].length) {
249                         this.Set('chart.tooltips', [1]); // An array
250                         this.hasTooltips = true;
251                     }
252                 }
253             }
254         }
255
256         // Reset the maximum value
257         this.max = 0;
258
259         // Work out the maximum Y value
260         if (this.Get('chart.ymax') && this.Get('chart.ymax') > 0) {
261
262             this.scale = [];
263             this.max   = this.Get('chart.ymax');
264             this.min   = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
265
266             this.scale[0] = ((this.max - this.min) * (1/5)) + this.min;
267             this.scale[1] = ((this.max - this.min) * (2/5)) + this.min;
268             this.scale[2] = ((this.max - this.min) * (3/5)) + this.min;
269             this.scale[3] = ((this.max - this.min) * (4/5)) + this.min;
270             this.scale[4] = ((this.max - this.min) * (5/5)) + this.min;
271
272             var decimals = this.Get('chart.scale.decimals');
273
274             this.scale = [
275                           Number(this.scale[0]).toFixed(decimals),
276                           Number(this.scale[1]).toFixed(decimals),
277                           Number(this.scale[2]).toFixed(decimals),
278                           Number(this.scale[3]).toFixed(decimals),
279                           Number(this.scale[4]).toFixed(decimals)
280                          ];
281
282         } else {
283
284             var i = 0;
285             var j = 0;
286
287             for (i=0; i<this.data.length; ++i) {
288                 for (j=0; j<this.data[i].length; ++j) {
289                     this.max = Math.max(this.max, typeof(this.data[i][j][1]) == 'object' ? RGraph.array_max(this.data[i][j][1]) : Math.abs(this.data[i][j][1]));
290                 }
291             }
292
293             this.scale = RGraph.getScale(this.max, this);
294
295             this.max   = this.scale[4];
296             this.min   = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
297
298             if (this.min) {
299                 this.scale[0] = ((this.max - this.min) * (1/5)) + this.min;
300                 this.scale[1] = ((this.max - this.min) * (2/5)) + this.min;
301                 this.scale[2] = ((this.max - this.min) * (3/5)) + this.min;
302                 this.scale[3] = ((this.max - this.min) * (4/5)) + this.min;
303                 this.scale[4] = ((this.max - this.min) * (5/5)) + this.min;
304             }
305
306
307             if (typeof(this.Get('chart.scale.decimals')) == 'number') {
308                 var decimals = this.Get('chart.scale.decimals');
309     
310                 this.scale = [
311                               Number(this.scale[0]).toFixed(decimals),
312                               Number(this.scale[1]).toFixed(decimals),
313                               Number(this.scale[2]).toFixed(decimals),
314                               Number(this.scale[3]).toFixed(decimals),
315                               Number(this.scale[4]).toFixed(decimals)
316                              ];
317             }
318         }
319
320         this.grapharea = this.canvas.height - (2 * this.Get('chart.gutter'));
321
322         // Progressively Draw the chart
323         RGraph.background.Draw(this);
324
325         /**
326         * Draw any horizontal bars that have been specified
327         */
328         if (this.Get('chart.background.hbars') && this.Get('chart.background.hbars').length) {
329             RGraph.DrawBars(this);
330         }
331
332         /**
333         * Draw any vertical bars that have been specified
334         */
335         if (this.Get('chart.background.vbars') && this.Get('chart.background.vbars').length) {
336             this.DrawVBars();
337         }
338
339         if (!this.Get('chart.noaxes')) {
340             this.DrawAxes();
341         }
342
343         this.DrawLabels();
344
345         i = 0;
346         for(i=0; i<this.data.length; ++i) {
347             this.DrawMarks(i);
348
349             // Set the shadow
350             this.context.shadowColor   = this.Get('chart.line.shadow.color');
351             this.context.shadowOffsetX = this.Get('chart.line.shadow.offsetx');
352             this.context.shadowOffsetY = this.Get('chart.line.shadow.offsety');
353             this.context.shadowBlur    = this.Get('chart.line.shadow.blur');
354             
355             this.DrawLine(i);
356
357             // Turn the shadow off
358             RGraph.NoShadow(this);
359         }
360
361
362         if (this.Get('chart.line')) {
363             for (var i=0;i<this.data.length; ++i) {
364                 this.DrawMarks(i); // Call this again so the tickmarks appear over the line
365             }
366         }
367
368
369
370         /**
371         * Setup the context menu if required
372         */
373         if (this.Get('chart.contextmenu')) {
374             RGraph.ShowContext(this);
375         }
376
377         /**
378         * Install the event handler for tooltips
379         */
380         if (this.hasTooltips) {
381
382             /**
383             * Register all charts
384             */
385             RGraph.Register(this);
386
387             var overHotspot = false;
388
389             var canvas_onmousemove_func = function (e)
390             {
391                 e = RGraph.FixEventObject(e);
392
393                 var canvas      = e.target;
394                 var obj         = canvas.__object__;
395                 var context     = canvas.getContext('2d');
396                 var mouseCoords = RGraph.getMouseXY(e);
397                 var overHotspot = false;
398
399                 /**
400                 * Now loop through each point comparing the coords
401                 */
402
403                 var offset = obj.Get('chart.tooltips.hotspot'); // This is how far the hotspot extends
404
405                 for (var set=0; set<obj.coords.length; ++set) {
406                     for (var i=0; i<obj.coords[set].length; ++i) {
407                         
408                         var adjust = obj.Get('chart.tooltips.coords.adjust');
409                         var xCoord = obj.coords[set][i][0] + adjust[0];
410                         var yCoord = obj.coords[set][i][1] + adjust[1];
411                         var tooltip = obj.coords[set][i][2];
412
413                         if (mouseCoords[0] <= (xCoord + offset) &&
414                             mouseCoords[0] >= (xCoord - offset) &&
415                             mouseCoords[1] <= (yCoord + offset) &&
416                             mouseCoords[1] >= (yCoord - offset) &&
417                             tooltip &&
418                             tooltip.length > 0) {
419         
420                             overHotspot = true;
421                             canvas.style.cursor = 'pointer';
422     
423                             if (
424                                 !RGraph.Registry.Get('chart.tooltip') ||
425                                 RGraph.Registry.Get('chart.tooltip').__text__ != tooltip ||
426                                 RGraph.Registry.Get('chart.tooltip').__index__ != i ||
427                                 RGraph.Registry.Get('chart.tooltip').__dataset__ != set
428                                ) {
429                                 
430                                 if (obj.Get('chart.tooltips.highlight')) {
431                                     RGraph.Redraw();
432                                 }
433
434                                 /**
435                                 * Get the tooltip text
436                                 */
437                                 if (typeof(tooltip) == 'function') {
438                                     var text = String(tooltip(i));
439         
440                                 } else {
441                                     var text = String(tooltip);
442                                 }
443
444                                 RGraph.Tooltip(canvas, text, e.pageX, e.pageY, i);
445                                 RGraph.Registry.Get('chart.tooltip').__dataset__ = set;
446                                 
447                                 /**
448                                 * Draw a circle around the mark
449                                 */
450                                 if (obj.Get('chart.tooltips.highlight')) {
451                                     context.beginPath();
452                                     context.fillStyle = 'rgba(255,255,255,0.5)';
453                                     context.arc(xCoord, yCoord, 3, 0, 6.28, 0);
454                                     context.fill();
455                                 }
456                             }
457                         }
458                     }
459                 }
460
461                 /**
462                 * Reset the pointer
463                 */
464                 if (!overHotspot) {
465                     canvas.style.cursor = 'default';
466                 }
467             }
468             this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
469             RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
470         }
471         
472         
473         /**
474         * Draw the key if necessary
475         */
476         if (this.Get('chart.key') && this.Get('chart.key').length) {
477             RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.line.colors'));
478         }
479
480
481         /**
482         * Draw crosschairs
483         */
484         RGraph.DrawCrosshairs(this);
485         
486         /**
487         * If the canvas is annotatable, do install the event handlers
488         */
489         if (this.Get('chart.annotatable')) {
490             RGraph.Annotate(this);
491         }
492         
493         /**
494         * This bit shows the mini zoom window if requested
495         */
496         if (this.Get('chart.zoom.mode') == 'thumbnail' || this.Get('chart.zoom.mode') == 'area') {
497             RGraph.ShowZoomWindow(this);
498         }
499
500         
501         /**
502         * This function enables resizing
503         */
504         if (this.Get('chart.resizable')) {
505             RGraph.AllowResizing(this);
506         }
507         
508         /**
509         * Fire the RGraph ondraw event
510         */
511         RGraph.FireCustomEvent(this, 'ondraw');
512     }
513
514
515     /**
516     * Draws the axes of the scatter graph
517     */
518     RGraph.Scatter.prototype.DrawAxes = function ()
519     {
520         var canvas      = this.canvas;
521         var context     = this.context;
522         var graphHeight = this.canvas.height - (this.Get('chart.gutter') * 2);
523         var gutter      = this.Get('chart.gutter');
524
525         context.beginPath();
526         context.strokeStyle = this.Get('chart.axis.color');
527         context.lineWidth   = 1;
528
529         // Draw the Y axis
530         if (this.Get('chart.yaxispos') == 'left') {
531             context.moveTo(gutter, gutter);
532             context.lineTo(gutter, this.canvas.height - gutter);
533         } else {
534             context.moveTo(canvas.width - gutter, gutter);
535             context.lineTo(canvas.width - gutter, canvas.height - gutter);
536         }
537
538
539         // Draw the X axis
540         if (this.Get('chart.xaxis')) {
541             if (this.Get('chart.xaxispos') == 'center') {
542                 context.moveTo(gutter, canvas.height / 2);
543                 context.lineTo(canvas.width - gutter, canvas.height / 2);
544             } else {
545                 context.moveTo(gutter, canvas.height - gutter);
546                 context.lineTo(canvas.width - gutter, canvas.height - gutter);
547             }
548         }
549
550         /**
551         * Draw the Y tickmarks
552         */
553         for (y=gutter; y < canvas.height - gutter + (this.Get('chart.xaxispos') == 'center' ? 1 : 0) ; y+=(graphHeight / 5) / 2) {
554
555             // This is here to accomodate the X axis being at the center
556             if (y == (canvas.height / 2) ) continue;
557
558             if (this.Get('chart.yaxispos') == 'left') {
559                 context.moveTo(gutter, y);
560                 context.lineTo(gutter - 3, y);
561             } else {
562                 context.moveTo(canvas.width - gutter +3, y);
563                 context.lineTo(canvas.width - gutter, y);
564             }
565         }
566
567
568         /**
569         * Draw the X tickmarks
570         */
571         if (this.Get('chart.xticks') && this.Get('chart.xaxis')) {
572
573             var x  = 0;
574             var y  =  (this.Get('chart.xaxispos') == 'center') ? (this.canvas.height / 2) : (this.canvas.height - gutter);
575             this.xTickGap = (this.canvas.width - (2 * gutter) ) / this.Get('chart.labels').length;
576     
577             for (x = (gutter + (this.Get('chart.yaxispos') == 'left' ? this.xTickGap / 2 : 0) ); x<=(canvas.width - gutter - (this.Get('chart.yaxispos') == 'left' ? 0 : 1)); x += this.xTickGap / 2) {
578                 
579                 if (this.Get('chart.yaxispos') == 'left' && this.Get('chart.noendxtick') == true && x == (canvas.width - gutter) ) {
580                     continue;
581                 
582                 } else if (this.Get('chart.yaxispos') == 'right' && this.Get('chart.noendxtick') == true && x == gutter) {
583                     continue;
584                 }
585
586                 context.moveTo(x, y - (this.Get('chart.xaxispos') == 'center' ? 3 : 0));
587                 context.lineTo(x, y + 3);
588             }
589         }
590
591         context.stroke();
592     }
593
594
595     /**
596     * Draws the labels on the scatter graph
597     */
598     RGraph.Scatter.prototype.DrawLabels = function ()
599     {
600         this.context.fillStyle = this.Get('chart.text.color');
601         var font       = this.Get('chart.text.font');
602         var xMin       = this.Get('chart.xmin');
603         var xMax       = this.Get('chart.xmax');
604         var yMax       = this.scale[4];
605         var gutter     = this.Get('chart.gutter');
606         var text_size  = this.Get('chart.text.size');
607         var units_pre  = this.Get('chart.units.pre');
608         var units_post = this.Get('chart.units.post');
609         var numYLabels = this.Get('chart.ylabels.count');
610         var context    = this.context;
611         var canvas     = this.canvas;
612
613         this.halfTextHeight = text_size / 2;
614
615             
616         this.halfGraphHeight = (this.canvas.height - (2 * this.Get('chart.gutter'))) / 2;
617
618         /**
619         * Draw the Y yaxis labels, be it at the top or center
620         */
621         if (this.Get('chart.ylabels')) {
622
623             var xPos  = this.Get('chart.yaxispos') == 'left' ? gutter - 5 : canvas.width - gutter + 5;
624             var align = this.Get('chart.yaxispos') == 'right' ? 'left' : 'right';
625
626             if (this.Get('chart.xaxispos') == 'center') {
627
628
629                 /**
630                 * Specific Y labels
631                 */
632                 if (typeof(this.Get('chart.ylabels.specific')) == 'object') {
633                 
634                     var labels = this.Get('chart.ylabels.specific');
635                 
636                     for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
637                         var y = gutter + (i * (this.grapharea / (labels.length * 2) ) );
638                         RGraph.Text(context, font, text_size, xPos, y, labels[i], 'center', align);
639                     }
640                     
641                     var reversed_labels = RGraph.array_reverse(labels);
642                 
643                     for (var i=0; i<reversed_labels.length; ++i) {
644                         var y = gutter + (this.grapharea / 2) + ((i+1) * (this.grapharea / (labels.length * 2) ) );
645                         
646                         RGraph.Text(context, font, text_size, xPos, y, reversed_labels[i], 'center', align);
647                     }
648                 
649                     return;
650                 }
651
652
653                 if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
654                     // Draw the top halves labels
655                     RGraph.Text(context, font, text_size, xPos, gutter, RGraph.number_format(this, this.scale[4], units_pre, units_post), 'center', align);
656                     
657                     
658                     if (numYLabels >= 5) {
659                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (1/10) ), RGraph.number_format(this, this.scale[3], units_pre, units_post), 'center', align);
660                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (3/10) ), RGraph.number_format(this, this.scale[1], units_pre, units_post), 'center', align);
661                     }
662         
663                     if (numYLabels >= 3) {
664                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (2/10) ), RGraph.number_format(this, this.scale[2], units_pre, units_post), 'center', align);
665                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (4/10) ), RGraph.number_format(this, this.scale[0], units_pre, units_post), 'center', align);
666                     }
667                     
668                     // Draw the bottom halves labels
669                     if (numYLabels >= 3) {
670                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (1/10) ) + this.halfGraphHeight, '-' + RGraph.number_format(this, this.scale[0], units_pre, units_post), 'center', align);
671                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (3/10) ) + this.halfGraphHeight, '-' + RGraph.number_format(this, this.scale[2], units_pre, units_post), 'center', align);
672                     }
673         
674                     if (numYLabels == 5) {
675                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (2/10) ) + this.halfGraphHeight, '-' + RGraph.number_format(this, this.scale[1], units_pre, units_post), 'center', align);
676                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (4/10) ) + this.halfGraphHeight, '-' + RGraph.number_format(this, this.scale[3], units_pre, units_post), 'center', align);
677                     }
678         
679                     RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (5/10) ) + this.halfGraphHeight, '-' + RGraph.number_format(this, this.scale[4], units_pre, units_post), 'center', align);
680                 
681                 } else if (numYLabels == 10) {
682                     // 10 Y labels
683                     var interval = (this.grapharea / numYLabels) / 2;
684                 
685                     for (var i=0; i<numYLabels; ++i) {
686                         RGraph.Text(context, font, text_size, xPos,gutter + ((canvas.height - (2 * gutter)) * (i/20) ),RGraph.number_format(this,
687                         
688                         (this.max - (this.max * (i/10))).toFixed(this.Get('chart.scale.decimals')),
689                         
690                         units_pre, units_post),'center', align);
691                         RGraph.Text(context, font, text_size, xPos,gutter + ((canvas.height - (2 * gutter)) * (i/20) ) + (this.grapharea / 2) + (this.grapharea / 20),'-' + RGraph.number_format(this, ((this.max * (i/10)) + (this.max * (1/10))).toFixed((this.Get('chart.scale.decimals'))), units_pre, units_post), 'center', align);
692                     }
693
694                 } else {
695                     alert('[SCATTER SCALE] Number of Y labels can be 1/3/5/10 only');
696                 }
697     
698             } else {
699                 
700                 var xPos  = this.Get('chart.yaxispos') == 'left' ? gutter - 5 : canvas.width - gutter + 5;
701                 var align = this.Get('chart.yaxispos') == 'right' ? 'left' : 'right';
702
703                 /**
704                 * Specific Y labels
705                 */
706                 if (typeof(this.Get('chart.ylabels.specific')) == 'object') {
707                 
708                     var labels = this.Get('chart.ylabels.specific');
709
710                     for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
711                         var y = gutter + (i * (this.grapharea / labels.length) );
712                         
713                         RGraph.Text(context, font, text_size, xPos, y, labels[i], 'center', align);
714                     }
715
716                     return;
717                 }
718
719                 if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
720                     RGraph.Text(context, font, text_size, xPos, gutter, RGraph.number_format(this, this.scale[4], units_pre, units_post), 'center', align);
721     
722                     if (numYLabels >= 5) {
723                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (1/5) ), RGraph.number_format(this, this.scale[3], units_pre, units_post), 'center', align);
724                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (3/5) ), RGraph.number_format(this, this.scale[1], units_pre, units_post), 'center', align);
725                     }
726     
727                     if (numYLabels >= 3) {
728                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (2/5) ), RGraph.number_format(this, this.scale[2], units_pre, units_post), 'center', align);
729                         RGraph.Text(context, font, text_size, xPos, gutter + ((canvas.height - (2 * gutter)) * (4/5) ), RGraph.number_format(this, this.scale[0], units_pre, units_post), 'center', align);
730                     }
731                 } else if (numYLabels == 10) {
732
733                     // 10 Y labels
734                     var interval = (this.grapharea / numYLabels) / 2;
735                 
736                     for (var i=0; i<numYLabels; ++i) {
737                         RGraph.Text(context, font, text_size, xPos,gutter + ((canvas.height - (2 * gutter)) * (i/10) ),RGraph.number_format(this,(this.max - (this.max * (i/10))).toFixed((this.Get('chart.scale.decimals'))), units_pre, units_post), 'center', align);
738                     }
739
740                 } else {
741                     alert('[SCATTER SCALE] Number of Y labels can be 1/3/5/10 only');
742                 }
743                 
744                 if (this.Get('chart.ymin')) {
745                     RGraph.Text(context, font, text_size, xPos, canvas.height - gutter,RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post),'center', align);
746                 }
747             }
748         }
749         
750         // Put the text on the X axis
751         var graphArea = this.canvas.width - (2 * gutter);
752         var xInterval = graphArea / this.Get('chart.labels').length;
753         var xPos      = gutter;
754         var yPos      = (this.canvas.height - gutter) + 15;
755         var labels    = this.Get('chart.labels');
756
757         /**
758         * Text angle
759         */
760         var angle  = 0;
761         var valign = null;
762         var halign = 'center';
763
764         if (this.Get('chart.text.angle') > 0) {
765             angle  = -1 * this.Get('chart.text.angle');
766             valign = 'center';
767             halign = 'right';
768             yPos -= 10;
769         }
770
771         for (i=0; i<labels.length; ++i) {
772             
773             if (typeof(labels[i]) == 'object') {
774
775                 RGraph.Text(context, font, this.Get('chart.text.size'), gutter + (graphArea * ((labels[i][1] - xMin) / (this.Get('chart.xmax') - xMin))) + 5, yPos, String(labels[i][0]), valign, angle != 0 ? 'right' : 'left', null, angle);
776                 
777                 /**
778                 * Draw the gray indicator line
779                 */
780                 this.context.beginPath();
781                     this.context.strokeStyle = '#bbb';
782                     this.context.moveTo(gutter + (graphArea * ((labels[i][1] - xMin)/ (this.Get('chart.xmax') - xMin))), this.canvas.height - gutter);
783                     this.context.lineTo(gutter + (graphArea * ((labels[i][1] - xMin)/ (this.Get('chart.xmax') - xMin))), this.canvas.height - gutter + 20);
784                 this.context.stroke();
785             
786             } else {
787                 RGraph.Text(context, font, this.Get('chart.text.size'), xPos + (this.xTickGap / 2), yPos, String(labels[i]), valign, halign, null, angle);
788             }
789             
790             // Do this for the next time around
791             xPos += xInterval;
792         }
793     }
794
795
796     /**
797     * Draws the actual scatter graph marks
798     * 
799     * @param i integer The dataset index
800     */
801     RGraph.Scatter.prototype.DrawMarks = function (i)
802     {
803         /**
804         *  Reset the coords array
805         */
806         this.coords[i] = [];
807
808         /**
809         * Plot the values
810         */
811         var xmax          = this.Get('chart.xmax');
812         var default_color = this.Get('chart.defaultcolor');
813
814         for (var j=0; j<this.data[i].length; ++j) {
815             /**
816             * This is here because tooltips are optional
817             */
818             var data_point = this.data[i];
819
820             var xCoord = data_point[j][0];
821             var yCoord = data_point[j][1];
822             var color  = data_point[j][2] ? data_point[j][2] : default_color;
823             var tooltip = (data_point[j] && data_point[j][3]) ? data_point[j][3] : null;
824
825             
826             this.DrawMark(
827                           i,
828                           xCoord,
829                           yCoord,
830                           xmax,
831                           this.scale[4],
832                           color,
833                           tooltip,
834                           this.coords[i],
835                           data_point
836                          );
837         }
838     }
839
840
841     /**
842     * Draws a single scatter mark
843     */
844     RGraph.Scatter.prototype.DrawMark = function (index, x, y, xMax, yMax, color, tooltip, coords, data)
845     {
846         var tickmarks = this.Get('chart.tickmarks');
847         var tickSize  = this.Get('chart.ticksize');
848         var gutter    = this.Get('chart.gutter');
849         var xMin      = this.Get('chart.xmin');
850         var x = ((x - xMin) / (xMax - xMin)) * (this.canvas.width - (2 * gutter));
851         var originalX = x;
852         var originalY = y;
853         
854         /**
855         * This allows chart.tickmarks to be an array
856         */
857
858         if (tickmarks && typeof(tickmarks) == 'object') {
859             tickmarks = tickmarks[index];
860         }
861
862
863         /**
864         * This allows chart.ticksize to be an array
865         */
866         if (typeof(tickSize) == 'object') {
867             var tickSize     = tickSize[index];
868             var halfTickSize = tickSize / 2;
869         } else {
870             var halfTickSize = tickSize / 2;
871         }
872
873
874         /**
875         * This bit is for boxplots only
876         */
877         if (   typeof(y) == 'object'
878             && typeof(y[0]) == 'number'
879             && typeof(y[1]) == 'number'
880             && typeof(y[2]) == 'number'
881             && typeof(y[3]) == 'number'
882             && typeof(y[4]) == 'number'
883            ) {
884
885             var yMin = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
886             this.Set('chart.boxplot', true);
887             this.graphheight = this.canvas.height - (2 * gutter);
888             
889             if (this.Get('chart.xaxispos') == 'center') {
890                 this.graphheight /= 2;
891             }
892
893             var y0 = (this.graphheight) - ((y[4] - yMin) / (yMax - yMin)) * (this.graphheight);
894             var y1 = (this.graphheight) - ((y[3] - yMin) / (yMax - yMin)) * (this.graphheight);
895             var y2 = (this.graphheight) - ((y[2] - yMin) / (yMax - yMin)) * (this.graphheight);
896             var y3 = (this.graphheight) - ((y[1] - yMin) / (yMax - yMin)) * (this.graphheight);
897             var y4 = (this.graphheight) - ((y[0] - yMin) / (yMax - yMin)) * (this.graphheight);
898
899             var col1  = y[5];
900             var col2  = y[6];
901
902             // Override the boxWidth
903             if (typeof(y[7]) == 'number') {
904                 var boxWidth = y[7];
905             }
906             
907             var y = this.graphheight - y2;
908
909         } else {
910             var yMin = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
911             var y = (( (y - yMin) / (yMax - yMin)) * (this.canvas.height - (2 * gutter)));
912         }
913
914         /**
915         * Account for the X axis being at the centre
916         */
917         if (this.Get('chart.xaxispos') == 'center') {
918             y /= 2;
919             y += this.halfGraphHeight;
920         }
921
922         // This is so that points are on the graph, and not the gutter
923         x += gutter;
924         y = this.canvas.height - gutter - y;
925
926         this.context.beginPath();
927         
928         // Color
929         this.context.strokeStyle = color;
930
931         /**
932         * Boxplots
933         */
934         if (   this.Get('chart.boxplot')
935             && typeof(y0) == 'number'
936             && typeof(y1) == 'number'
937             && typeof(y2) == 'number'
938             && typeof(y3) == 'number'
939             && typeof(y4) == 'number'
940            ) {
941
942             var boxWidth = boxWidth ? boxWidth : this.Get('chart.boxplot.width');
943             var halfBoxWidth = boxWidth / 2;
944
945             this.context.beginPath();
946
947             // Draw the upper coloured box if a value is specified
948             if (col1) {
949                 this.context.fillStyle = col1;
950                 this.context.fillRect(x - halfBoxWidth, y1 + gutter, boxWidth, y2 - y1);
951             }
952
953             // Draw the lower coloured box if a value is specified
954             if (col2) {
955                 this.context.fillStyle = col2;
956                 this.context.fillRect(x - halfBoxWidth, y2 + gutter, boxWidth, y3 - y2);
957             }
958
959             this.context.strokeRect(x - halfBoxWidth, y1 + gutter, boxWidth, y3 - y1);
960             this.context.stroke();
961
962             // Now draw the whiskers
963             this.context.beginPath();
964             this.context.moveTo(x - halfBoxWidth, y0 + gutter);
965             this.context.lineTo(x + halfBoxWidth, y0 + gutter);
966
967             this.context.moveTo(x, y0 + gutter);
968             this.context.lineTo(x, y1 + gutter);
969
970             this.context.moveTo(x - halfBoxWidth, y4 + gutter);
971             this.context.lineTo(x + halfBoxWidth, y4 + gutter);
972
973             this.context.moveTo(x, y4 + gutter);
974             this.context.lineTo(x, y3 + gutter);
975
976             this.context.stroke();
977         }
978
979
980         /**
981         * Draw the tickmark, but not for boxplots
982         */
983
984         if (!y0 && !y1 && !y2 && !y3 && !y4) {
985             
986             this.graphheight = this.canvas.height - (2 * gutter);
987
988
989             
990             if (tickmarks == 'circle') {
991                 this.context.arc(x, y, halfTickSize, 0, 6.28, 0);
992                 this.context.fillStyle = color;
993                 this.context.fill();
994             
995             } else if (tickmarks == 'plus') {
996
997                 this.context.moveTo(x, y - halfTickSize);
998                 this.context.lineTo(x, y + halfTickSize);
999                 this.context.moveTo(x - halfTickSize, y);
1000                 this.context.lineTo(x + halfTickSize, y);
1001                 this.context.stroke();
1002             
1003             } else if (tickmarks == 'square') {
1004                 this.context.strokeStyle = color;
1005                 this.context.fillStyle = color;
1006                 this.context.fillRect(
1007                                       x - halfTickSize,
1008                                       y - halfTickSize,
1009                                       tickSize,
1010                                       tickSize
1011                                      );
1012                 //this.context.fill();
1013
1014             } else if (tickmarks == 'cross') {
1015
1016                 this.context.moveTo(x - halfTickSize, y - halfTickSize);
1017                 this.context.lineTo(x + halfTickSize, y + halfTickSize);
1018                 this.context.moveTo(x + halfTickSize, y - halfTickSize);
1019                 this.context.lineTo(x - halfTickSize, y + halfTickSize);
1020                 
1021                 this.context.stroke();
1022             
1023             /**
1024             * Diamond shape tickmarks
1025             */
1026             } else if (tickmarks == 'diamond') {
1027                 this.context.fillStyle = this.context.strokeStyle;
1028
1029                 this.context.moveTo(x, y - halfTickSize);
1030                 this.context.lineTo(x + halfTickSize, y);
1031                 this.context.lineTo(x, y + halfTickSize);
1032                 this.context.lineTo(x - halfTickSize, y);
1033                 this.context.lineTo(x, y - halfTickSize);
1034                 
1035                 this.context.fill();
1036                 this.context.stroke();
1037
1038             /**
1039             * Custom tickmark style
1040             */
1041             } else if (typeof(tickmarks) == 'function') {
1042
1043                 var graphWidth = this.canvas.width - (2 * this.Get('chart.gutter'))
1044                 var xVal = ((x - this.Get('chart.gutter')) / graphWidth) * xMax;
1045                 var yVal = ((this.graphheight - (y - this.Get('chart.gutter'))) / this.graphheight) * yMax;
1046
1047                 tickmarks(this, data, x, y, xVal, yVal, xMax, yMax, color)
1048
1049             /**
1050             * No tickmarks
1051             */
1052             } else if (tickmarks == null) {
1053     
1054             /**
1055             * Unknown tickmark type
1056             */
1057             } else {
1058                 alert('[SCATTER] (' + this.id + ') Unknown tickmark style: ' + tickmarks );
1059             }
1060         }
1061
1062         /**
1063         * Add the tickmark to the coords array
1064         */
1065         coords.push([x, y, tooltip]);
1066     }
1067     
1068     
1069     /**
1070     * Draws an optional line connecting the tick marks.
1071     * 
1072     * @param i The index of the dataset to use
1073     */
1074     RGraph.Scatter.prototype.DrawLine = function (i)
1075     {
1076         if (this.Get('chart.line') && this.coords[i].length >= 2) {
1077
1078             this.context.lineCap     = 'round';
1079             this.context.lineJoin    = 'round';
1080             this.context.lineWidth   = this.GetLineWidth(i);// i is the index of the set of coordinates
1081             this.context.strokeStyle = this.Get('chart.line.colors')[i];
1082             this.context.beginPath();
1083             
1084             var len = this.coords[i].length;
1085
1086             for (var j=0; j<this.coords[i].length; ++j) {
1087
1088                 var xPos = this.coords[i][j][0];
1089                 var yPos = this.coords[i][j][1];
1090
1091                 if (j == 0) {
1092                     this.context.moveTo(xPos, yPos);
1093                 } else {
1094                 
1095                     // Stepped?
1096                     var stepped = this.Get('chart.line.stepped');
1097
1098                     if (   (typeof(stepped) == 'boolean' && stepped)
1099                         || (typeof(stepped) == 'object' && stepped[i])
1100                        ) {
1101                         this.context.lineTo(this.coords[i][j][0], this.coords[i][j - 1][1]);
1102                     }
1103
1104                     this.context.lineTo(xPos, yPos);
1105                 }
1106             }
1107             
1108             this.context.stroke();
1109         }
1110         
1111         /**
1112         * Set the linewidth back to 1
1113         */
1114         this.context.lineWidth = 1;
1115     }
1116
1117
1118     /**
1119     * Returns the linewidth
1120     * 
1121     * @param number i The index of the "line" (/set of coordinates)
1122     */
1123     RGraph.Scatter.prototype.GetLineWidth = function (i)
1124     {
1125         var linewidth = this.Get('chart.line.linewidth');
1126         
1127         if (typeof(linewidth) == 'number') {
1128             return linewidth;
1129         
1130         } else if (typeof(linewidth) == 'object') {
1131             if (linewidth[i]) {
1132                 return linewidth[i];
1133             } else {
1134                 return linewidth[0];
1135             }
1136
1137             alert('[SCATTER] Error! chart.linewidth should be a single number or an array of one or more numbers');
1138         }
1139     }
1140
1141
1142     /**
1143     * Draws vertical bars. Line chart doesn't use a horizontal scale, hence this function
1144     * is not common
1145     */
1146     RGraph.Scatter.prototype.DrawVBars = function ()
1147     {
1148         var canvas  = this.canvas;
1149         var context = this.context;
1150         var vbars = this.Get('chart.background.vbars');
1151         var gutter = this.Get('chart.gutter');
1152         var graphWidth = canvas.width - gutter - gutter;
1153         
1154         if (vbars) {
1155         
1156             var xmax = this.Get('chart.xmax');
1157
1158             for (var i=0; i<vbars.length; ++i) {
1159                 var startX = ((vbars[i][0] / xmax) * graphWidth) + gutter;
1160                 var width  = (vbars[i][1] / xmax) * graphWidth;
1161
1162                 context.beginPath();
1163                     context.fillStyle = vbars[i][2];
1164                     context.fillRect(startX, gutter, width, (canvas.height - gutter - gutter));
1165                 context.fill();
1166             }
1167         }
1168     }