source: Dev/branches/jQueryUI/client/RGraph/libraries/RGraph.line.js @ 249

Last change on this file since 249 was 249, checked in by hendrikvanantwerpen, 13 years ago

This one's for Subversion, because it's so close...

First widget (stripped down sequencer).
Seperated client and server code in two direcotry trees.

File size: 86.4 KB
Line 
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 line chart constructor
19    *
20    * @param object canvas The cxanvas object
21    * @param array  data   The chart data
22    * @param array  ...    Other lines to plot
23    */
24    RGraph.Line = function (id)
25    {
26        // Get the canvas and context objects
27        this.id                = id;
28        this.canvas            = document.getElementById(id);
29        this.context           = this.canvas.getContext ? this.canvas.getContext("2d") : null;
30        this.canvas.__object__ = this;
31        this.type              = 'line';
32        this.max               = 0;
33        this.coords            = [];
34        this.coords2           = [];
35        this.coords.key        = [];
36        this.hasnegativevalues = false;
37        this.isRGraph          = true;
38
39
40
41        /**
42        * Compatibility with older browsers
43        */
44        RGraph.OldBrowserCompat(this.context);
45
46
47        // Various config type stuff
48        this.properties = {
49            'chart.background.barcolor1':   'rgba(0,0,0,0)',
50            'chart.background.barcolor2':   'rgba(0,0,0,0)',
51            'chart.background.grid':        1,
52            'chart.background.grid.width':  1,
53            'chart.background.grid.hsize':  25,
54            'chart.background.grid.vsize':  25,
55            'chart.background.grid.color':  '#ddd',
56            'chart.background.grid.vlines': true,
57            'chart.background.grid.hlines': true,
58            'chart.background.grid.border': true,
59            'chart.background.grid.autofit':           false,
60            'chart.background.grid.autofit.align':     false,
61            'chart.background.grid.autofit.numhlines': 7,
62            'chart.background.grid.autofit.numvlines': 20,
63            'chart.background.hbars':       null,
64            'chart.background.image':       null,
65            'chart.labels':                 null,
66            'chart.labels.ingraph':         null,
67            'chart.labels.above':           false,
68            'chart.labels.above.size':      8,
69            'chart.xtickgap':               20,
70            'chart.smallxticks':            3,
71            'chart.largexticks':            5,
72            'chart.ytickgap':               20,
73            'chart.smallyticks':            3,
74            'chart.largeyticks':            5,
75            'chart.linewidth':              1.01,
76            'chart.colors':                 ['red', '#0f0', '#00f', '#f0f', '#ff0', '#0ff'],
77            'chart.hmargin':                0,
78            'chart.tickmarks.dot.color':    'white',
79            'chart.tickmarks':              null,
80            'chart.tickmarks.linewidth':    null,
81            'chart.ticksize':               3,
82            'chart.gutter.left':            25,
83            'chart.gutter.right':           25,
84            'chart.gutter.top':             25,
85            'chart.gutter.bottom':          25,
86            'chart.tickdirection':          -1,
87            'chart.yaxispoints':            5,
88            'chart.fillstyle':              null,
89            'chart.xaxispos':               'bottom',
90            'chart.yaxispos':               'left',
91            'chart.xticks':                 null,
92            'chart.text.size':              10,
93            'chart.text.angle':             0,
94            'chart.text.color':             'black',
95            'chart.text.font':              'Verdana',
96            'chart.ymin':                   null,
97            'chart.ymax':                   null,
98            'chart.title':                  '',
99            'chart.title.background':       null,
100            'chart.title.hpos':             null,
101            'chart.title.vpos':             0.5,
102            'chart.title.xaxis':            '',
103            'chart.title.yaxis':            '',
104            'chart.title.xaxis.pos':        0.25,
105            'chart.title.yaxis.pos':        0.25,
106            'chart.shadow':                 false,
107            'chart.shadow.offsetx':         2,
108            'chart.shadow.offsety':         2,
109            'chart.shadow.blur':            3,
110            'chart.shadow.color':           'rgba(0,0,0,0.5)',
111            'chart.tooltips':               null,
112            'chart.tooltips.effect':         'fade',
113            'chart.tooltips.css.class':      'RGraph_tooltip',
114            'chart.tooltips.highlight':     true,
115            'chart.highlight.stroke':       '#999',
116            'chart.highlight.fill':         'white',
117            'chart.stepped':                false,
118            'chart.key':                    [],
119            'chart.key.background':         'white',
120            'chart.key.position':           'graph',
121            'chart.key.halign':             null,
122            'chart.key.shadow':             false,
123            'chart.key.shadow.color':       '#666',
124            'chart.key.shadow.blur':        3,
125            'chart.key.shadow.offsetx':     2,
126            'chart.key.shadow.offsety':     2,
127            'chart.key.position.gutter.boxed': true,
128            'chart.key.position.x':         null,
129            'chart.key.position.y':         null,
130            'chart.key.color.shape':        'square',
131            'chart.key.rounded':            true,
132            'chart.key.linewidth':          1,
133            'chart.key.interactive':        false,
134            'chart.contextmenu':            null,
135            'chart.ylabels':                true,
136            'chart.ylabels.count':          5,
137            'chart.ylabels.inside':         false,
138            'chart.ylabels.invert':         false,
139            'chart.xlabels.inside':         false,
140            'chart.xlabels.inside.color':   'rgba(255,255,255,0.5)',
141            'chart.noaxes':                 false,
142            'chart.noyaxis':                false,
143            'chart.noxaxis':                false,
144            'chart.noendxtick':             false,
145            'chart.units.post':             '',
146            'chart.units.pre':              '',
147            'chart.scale.decimals':         null,
148            'chart.scale.point':            '.',
149            'chart.scale.thousand':         ',',
150            'chart.crosshairs':             false,
151            'chart.crosshairs.color':       '#333',
152            'chart.annotatable':            false,
153            'chart.annotate.color':         'black',
154            'chart.axesontop':              false,
155            'chart.filled':                 false,
156            'chart.filled.range':           false,
157            'chart.filled.accumulative':    true,
158            'chart.variant':                null,
159            'chart.axis.color':             'black',
160            'chart.zoom.factor':            1.5,
161            'chart.zoom.fade.in':           true,
162            'chart.zoom.fade.out':          true,
163            'chart.zoom.hdir':              'right',
164            'chart.zoom.vdir':              'down',
165            'chart.zoom.frames':            15,
166            'chart.zoom.delay':             33,
167            'chart.zoom.shadow':            true,
168            'chart.zoom.mode':              'canvas',
169            'chart.zoom.thumbnail.width':   75,
170            'chart.zoom.thumbnail.height':  75,
171            'chart.zoom.background':        true,
172            'chart.zoom.action':            'zoom',
173            'chart.backdrop':               false,
174            'chart.backdrop.size':          30,
175            'chart.backdrop.alpha':         0.2,
176            'chart.resizable':              false,
177            'chart.resize.handle.adjust':   [0,0],
178            'chart.resize.handle.background': null,
179            'chart.adjustable':             false,
180            'chart.noredraw':               false,
181            'chart.outofbounds':            false,
182            'chart.chromefix':              true,
183            'chart.draw.delegate':          null
184        }
185
186        /**
187        * Change null arguments to empty arrays
188        */
189        for (var i=1; i<arguments.length; ++i) {
190            if (typeof(arguments[i]) == 'null' || !arguments[i]) {
191                arguments[i] = [];
192            }
193        }
194
195
196        /**
197        * Store the original data. Thiss also allows for giving arguments as one big array.
198        */
199        this.original_data = [];
200
201        for (var i=1; i<arguments.length; ++i) {
202            if (arguments[1] && typeof(arguments[1]) == 'object' && arguments[1][0] && typeof(arguments[1][0]) == 'object' && arguments[1][0].length) {
203
204                var tmp = [];
205
206                for (var i=0; i<arguments[1].length; ++i) {
207                    tmp[i] = RGraph.array_clone(arguments[1][i]);
208                }
209
210                for (var j=0; j<tmp.length; ++j) {
211                    this.original_data[j] = RGraph.array_clone(tmp[j]);
212                }
213
214            } else {
215                this.original_data[i - 1] = RGraph.array_clone(arguments[i]);
216            }
217        }
218
219        // Check for support
220        if (!this.canvas) {
221            alert('[LINE] Fatal error: no canvas support');
222            return;
223        }
224       
225        /**
226        * Store the data here as one big array
227        */
228        this.data_arr = [];
229
230        for (var i=1; i<arguments.length; ++i) {
231            for (var j=0; j<arguments[i].length; ++j) {
232                this.data_arr.push(arguments[i][j]);
233            }
234        }
235
236
237        /**
238        * Set the .getShape commonly named method
239        */
240        this.getShape = this.getPoint;
241    }
242
243
244    /**
245    * An all encompassing accessor
246    *
247    * @param string name The name of the property
248    * @param mixed value The value of the property
249    */
250    RGraph.Line.prototype.Set = function (name, value)
251    {
252        // Consolidate the tooltips
253        if (name == 'chart.tooltips') {
254       
255            var tooltips = [];
256
257            for (var i=1; i<arguments.length; i++) {
258                if (typeof(arguments[i]) == 'object' && arguments[i][0]) {
259                    for (var j=0; j<arguments[i].length; j++) {
260                        tooltips.push(arguments[i][j]);
261                    }
262
263                } else if (typeof(arguments[i]) == 'function') {
264                    tooltips = arguments[i];
265
266                } else {
267                    tooltips.push(arguments[i]);
268                }
269            }
270
271            // Because "value" is used further down at the end of this function, set it to the expanded array os tooltips
272            value = tooltips;
273        }
274
275        /**
276        * Reverse the tickmarks to make them correspond to the right line
277        */
278        if (name == 'chart.tickmarks' && typeof(value) == 'object' && value) {
279            value = RGraph.array_reverse(value);
280        }
281       
282        /**
283        * Inverted Y axis should show the bottom end of the scale
284        */
285        if (name == 'chart.ylabels.invert' && value && this.Get('chart.ymin') == null) {
286            this.Set('chart.ymin', 0);
287        }
288       
289        /**
290        * If (buggy) Chrome and the linewidth is 1, change it to 1.01
291        */
292        if (name == 'chart.linewidth' && navigator.userAgent.match(/Chrome/)) {
293            if (value == 1) {
294                value = 1.01;
295           
296            } else if (RGraph.is_array(value)) {
297                for (var i=0; i<value.length; ++i) {
298                    if (typeof(value[i]) == 'number' && value[i] == 1) {
299                        value[i] = 1.01;
300                    }
301                }
302            }
303        }
304       
305        /**
306        * Check for xaxispos
307        */
308        if (name == 'chart.xaxispos' ) {
309            if (value != 'bottom' && value != 'center' && value != 'top') {
310                alert('[LINE] (' + this.id + ') chart.xaxispos should be top, center or bottom. Tried to set it to: ' + value + ' Changing it to center');
311                value = 'center';
312            }
313        }
314
315        this.properties[name] = value;
316    }
317
318
319    /**
320    * An all encompassing accessor
321    *
322    * @param string name The name of the property
323    */
324    RGraph.Line.prototype.Get = function (name)
325    {
326        return this.properties[name];
327    }
328
329
330    /**
331    * The function you call to draw the line chart
332    */
333    RGraph.Line.prototype.Draw = function ()
334    {
335        // MUST be the first thing done!
336        if (typeof(this.Get('chart.background.image')) == 'string' && !this.__background_image__) {
337            RGraph.DrawBackgroundImage(this);
338            return;
339        }
340
341        /**
342        * Fire the onbeforedraw event
343        */
344        RGraph.FireCustomEvent(this, 'onbeforedraw');
345
346        /**
347        * Clear all of this canvases event handlers (the ones installed by RGraph)
348        */
349        RGraph.ClearEventListeners(this.id);
350       
351        /**
352        * This is new in May 2011 and facilitates indiviual gutter settings,
353        * eg chart.gutter.left
354        */
355        this.gutterLeft   = this.Get('chart.gutter.left');
356        this.gutterRight  = this.Get('chart.gutter.right');
357        this.gutterTop    = this.Get('chart.gutter.top');
358        this.gutterBottom = this.Get('chart.gutter.bottom');
359
360
361        /**
362        * Check for Chrome 6 and shadow
363        *
364        * TODO Remove once it's been fixed (for a while)
365        * SEARCH TAGS: CHROME FIX SHADOW BUG
366        */
367        if (   this.Get('chart.shadow')
368            && navigator.userAgent.match(/Chrome/)
369            && this.Get('chart.linewidth') <= 1
370            && this.Get('chart.chromefix')
371            && this.Get('chart.shadow.blur') > 0) {
372                alert('[RGRAPH WARNING] Chrome has a shadow bug, meaning you should increase the linewidth to at least 1.01');
373        }
374
375
376        // Reset the data back to that which was initially supplied
377        this.data = RGraph.array_clone(this.original_data);
378
379
380        // Reset the max value
381        this.max = 0;
382
383        /**
384        * Reverse the datasets so that the data and the labels tally
385        */
386        this.data = RGraph.array_reverse(this.data);
387
388        if (this.Get('chart.filled') && !this.Get('chart.filled.range') && this.data.length > 1 && this.Get('chart.filled.accumulative')) {
389
390            var accumulation = [];
391       
392            for (var set=0; set<this.data.length; ++set) {
393                for (var point=0; point<this.data[set].length; ++point) {
394                    this.data[set][point] = Number(accumulation[point] ? accumulation[point] : 0) + this.data[set][point];
395                    accumulation[point] = this.data[set][point];
396                }
397            }
398        }
399
400        /**
401        * Get the maximum Y scale value
402        */
403        if (this.Get('chart.ymax')) {
404           
405            this.max = this.Get('chart.ymax');
406            this.min = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
407
408            this.scale = [
409                          ( ((this.max - this.min) * (1/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
410                          ( ((this.max - this.min) * (2/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
411                          ( ((this.max - this.min) * (3/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
412                          ( ((this.max - this.min) * (4/5)) + this.min).toFixed(this.Get('chart.scale.decimals')),
413                          this.max.toFixed(this.Get('chart.scale.decimals'))
414                         ];
415
416            // Check for negative values
417            if (!this.Get('chart.outofbounds')) {
418                for (dataset=0; dataset<this.data.length; ++dataset) {
419                    for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
420           
421                        // Check for negative values
422                        this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
423                    }
424                }
425            }
426
427        } else {
428
429            this.min = this.Get('chart.ymin') ? this.Get('chart.ymin') : 0;
430
431            // Work out the max Y value
432            for (dataset=0; dataset<this.data.length; ++dataset) {
433                for (var datapoint=0; datapoint<this.data[dataset].length; datapoint++) {
434   
435                    this.max = Math.max(this.max, this.data[dataset][datapoint] ? Math.abs(parseFloat(this.data[dataset][datapoint])) : 0);
436   
437                    // Check for negative values
438                    if (!this.Get('chart.outofbounds')) {
439                        this.hasnegativevalues = (this.data[dataset][datapoint] < 0) || this.hasnegativevalues;
440                    }
441                }
442            }
443
444            // 20th April 2009 - moved out of the above loop
445            this.scale = RGraph.getScale(Math.abs(parseFloat(this.max)), this);
446            this.max   = this.scale[4] ? this.scale[4] : 0;
447
448            if (this.Get('chart.ymin')) {
449                this.scale[0] = ((this.max - this.Get('chart.ymin')) * (1/5)) + this.Get('chart.ymin');
450                this.scale[1] = ((this.max - this.Get('chart.ymin')) * (2/5)) + this.Get('chart.ymin');
451                this.scale[2] = ((this.max - this.Get('chart.ymin')) * (3/5)) + this.Get('chart.ymin');
452                this.scale[3] = ((this.max - this.Get('chart.ymin')) * (4/5)) + this.Get('chart.ymin');
453                this.scale[4] = ((this.max - this.Get('chart.ymin')) * (5/5)) + this.Get('chart.ymin');
454            }
455
456            if (typeof(this.Get('chart.scale.decimals')) == 'number') {
457                this.scale[0] = Number(this.scale[0]).toFixed(this.Get('chart.scale.decimals'));
458                this.scale[1] = Number(this.scale[1]).toFixed(this.Get('chart.scale.decimals'));
459                this.scale[2] = Number(this.scale[2]).toFixed(this.Get('chart.scale.decimals'));
460                this.scale[3] = Number(this.scale[3]).toFixed(this.Get('chart.scale.decimals'));
461                this.scale[4] = Number(this.scale[4]).toFixed(this.Get('chart.scale.decimals'));
462            }
463        }
464
465        /**
466        * Setup the context menu if required
467        */
468        if (this.Get('chart.contextmenu')) {
469            RGraph.ShowContext(this);
470        }
471
472        /**
473        * Reset the coords array otherwise it will keep growing
474        */
475        this.coords = [];
476
477        /**
478        * Work out a few things. They need to be here because they depend on things you can change before you
479        * call Draw() but after you instantiate the object
480        */
481        this.grapharea      = RGraph.GetHeight(this) - this.gutterTop - this.gutterBottom;
482        this.halfgrapharea  = this.grapharea / 2;
483        this.halfTextHeight = this.Get('chart.text.size') / 2;
484
485        // Check the combination of the X axis position and if there any negative values
486        //
487        // 19th Dec 2010 - removed for Opera since it can be reported incorrectly whn there
488        // are multiple graphs on the page
489        if (this.Get('chart.xaxispos') == 'bottom' && this.hasnegativevalues && navigator.userAgent.indexOf('Opera') == -1) {
490            alert('[LINE] You have negative values and the X axis is at the bottom. This is not good...');
491        }
492
493        if (this.Get('chart.variant') == '3d') {
494            RGraph.Draw3DAxes(this);
495        }
496       
497        // Progressively Draw the chart
498        RGraph.background.Draw(this);
499
500        /**
501        * Draw any horizontal bars that have been defined
502        */
503        if (this.Get('chart.background.hbars') && this.Get('chart.background.hbars').length > 0) {
504            RGraph.DrawBars(this);
505        }
506
507        if (this.Get('chart.axesontop') == false) {
508            this.DrawAxes();
509        }
510
511        /**
512        * Handle the appropriate shadow color. This now facilitates an array of differing
513        * shadow colors
514        */
515        var shadowColor = this.Get('chart.shadow.color');
516       
517        if (typeof(shadowColor) == 'object') {
518            shadowColor = RGraph.array_reverse(RGraph.array_clone(this.Get('chart.shadow.color')));
519        }
520
521
522        for (var i=(this.data.length - 1), j=0; i>=0; i--, j++) {
523
524            this.context.beginPath();
525
526            /**
527            * Turn on the shadow if required
528            */
529            if (this.Get('chart.shadow') && !this.Get('chart.filled')) {
530
531                /**
532                * Accommodate an array of shadow colors as well as a single string
533                */
534                if (typeof(shadowColor) == 'object' && shadowColor[i - 1]) {
535                    this.context.shadowColor = shadowColor[i];
536                } else if (typeof(shadowColor) == 'object') {
537                    this.context.shadowColor = shadowColor[0];
538                } else if (typeof(shadowColor) == 'string') {
539                    this.context.shadowColor = shadowColor;
540                }
541
542                this.context.shadowBlur    = this.Get('chart.shadow.blur');
543                this.context.shadowOffsetX = this.Get('chart.shadow.offsetx');
544                this.context.shadowOffsetY = this.Get('chart.shadow.offsety');
545           
546            } else if (this.Get('chart.filled') && this.Get('chart.shadow')) {
547                alert('[LINE] Shadows are not permitted when the line is filled');
548            }
549
550            /**
551            * Draw the line
552            */
553
554            if (this.Get('chart.fillstyle')) {
555                if (typeof(this.Get('chart.fillstyle')) == 'object' && this.Get('chart.fillstyle')[j]) {
556                   var fill = this.Get('chart.fillstyle')[j];
557               
558                } else if (typeof(this.Get('chart.fillstyle')) == 'string') {
559                    var fill = this.Get('chart.fillstyle');
560   
561                } else {
562                    alert('[LINE] Warning: chart.fillstyle must be either a string or an array with the same number of elements as you have sets of data');
563                }
564            } else if (this.Get('chart.filled')) {
565                var fill = this.Get('chart.colors')[j];
566
567            } else {
568                var fill = null;
569            }
570
571            /**
572            * Figure out the tickmark to use
573            */
574            if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'object') {
575                var tickmarks = this.Get('chart.tickmarks')[i];
576            } else if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'string') {
577                var tickmarks = this.Get('chart.tickmarks');
578            } else if (this.Get('chart.tickmarks') && typeof(this.Get('chart.tickmarks')) == 'function') {
579                var tickmarks = this.Get('chart.tickmarks');
580            } else {
581                var tickmarks = null;
582            }
583
584
585            this.DrawLine(this.data[i],
586                          this.Get('chart.colors')[j],
587                          fill,
588                          this.GetLineWidth(j),
589                           tickmarks,
590                           this.data.length - i - 1);
591                           
592            this.context.stroke();
593        }
594
595
596
597
598
599
600
601
602
603
604
605
606
607        /**
608        * If tooltips are defined, handle them
609        */
610        if (this.Get('chart.tooltips') && (this.Get('chart.tooltips').length || typeof(this.Get('chart.tooltips')) == 'function')) {
611
612            // Need to register this object for redrawing
613            if (this.Get('chart.tooltips.highlight')) {
614                RGraph.Register(this);
615            }
616
617            canvas_onmousemove_func = function (e)
618            {
619                e = RGraph.FixEventObject(e);
620
621                var canvas  = e.target;
622                var context = canvas.getContext('2d');
623                var obj     = canvas.__object__;
624                var point   = obj.getPoint(e);
625
626                if (obj.Get('chart.tooltips.highlight')) {
627                    RGraph.Register(obj);
628                }
629
630                if (   point
631                    && typeof(point[0]) == 'object'
632                    && typeof(point[1]) == 'number'
633                    && typeof(point[2]) == 'number'
634                    && typeof(point[3]) == 'number'
635                   ) {
636
637                    // point[0] is the graph object
638                    var xCoord = point[1];
639                    var yCoord = point[2];
640                    var idx    = point[3];
641
642                    if ((obj.Get('chart.tooltips')[idx] || typeof(obj.Get('chart.tooltips')) == 'function')) {
643
644                        // Get the tooltip text
645                        if (typeof(obj.Get('chart.tooltips')) == 'function') {
646                            var text = obj.Get('chart.tooltips')(idx);
647                       
648                        } else if (typeof(obj.Get('chart.tooltips')) == 'object' && typeof(obj.Get('chart.tooltips')[idx]) == 'function') {
649                            var text = obj.Get('chart.tooltips')[idx](idx);
650                       
651                        } else if (typeof(obj.Get('chart.tooltips')) == 'object') {
652                            var text = String(obj.Get('chart.tooltips')[idx]);
653                        } else {
654                            var text = '';
655                        }
656
657
658                        // No tooltip text - do nada
659                        if (text.match(/^id:(.*)$/) && RGraph.getTooltipText(text).substring(0,3) == 'id:') {
660                            return;
661                        }
662
663                        // Chnage the pointer to a hand
664                        canvas.style.cursor = 'pointer';
665
666                        /**
667                        * If the tooltip is the same one as is currently visible (going by the array index), don't do squat and return.
668                        */
669                        if (   RGraph.Registry.Get('chart.tooltip')
670                            && RGraph.Registry.Get('chart.tooltip').__index__ == idx
671                            && RGraph.Registry.Get('chart.tooltip').__canvas__.id == canvas.id) {
672
673                            return;
674                        }
675
676                        /**
677                        * Redraw the graph
678                        */
679                        if (obj.Get('chart.tooltips.highlight')) {
680                           // Redraw the graph
681                            RGraph.Redraw();
682                        }
683
684                        // SHOW THE CORRECT TOOLTIP
685                        RGraph.Tooltip(canvas, text, e.pageX, e.pageY, idx);
686
687                        // Store the tooltip index on the tooltip object
688                        RGraph.Registry.Get('chart.tooltip').__index__ = Number(idx);
689
690                        /**
691                        * This converts idx into the index that is not greater than the
692                        * number of items in the data array
693                        */
694                        while (idx >= obj.data[0].length) {
695                            idx -= obj.data[0].length
696                        }
697
698                        RGraph.Registry.Get('chart.tooltip').__index2__ = idx;
699
700                        /**
701                        * Highlight the graph
702                        */
703                        if (obj.Get('chart.tooltips.highlight')) {
704                            context.beginPath();
705                            context.moveTo(xCoord, yCoord);
706                            context.arc(xCoord, yCoord, 2, 0, 6.28, 0);
707                            context.strokeStyle = obj.Get('chart.highlight.stroke');
708                            context.fillStyle = obj.Get('chart.highlight.fill');
709                            context.stroke();
710                            context.fill();
711                        }
712                       
713                        e.stopPropagation();
714                        return;
715                    }
716                }
717               
718                /**
719                * Not over a hotspot?
720                */
721                canvas.style.cursor = 'default';
722            }
723            this.canvas.addEventListener('mousemove', canvas_onmousemove_func, false);
724            RGraph.AddEventListener(this.id, 'mousemove', canvas_onmousemove_func);
725           
726            // TODO COMBINED LINE BAR ONCLICK HANDLER
727            // ONLY INSTALL IF THERE IS A BAR CHART
728
729            //canvas_onclick_func = function (e)
730            //{
731            //    p(666)
732            //}
733            //this.canvas.addEventListener('click', canvas_onclick_func, false);
734            //RGraph.AddEventListener(this.id, 'click', canvas_onclick_func);
735        }
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750        /**
751        * If the axes have been requested to be on top, do that
752        */
753        if (this.Get('chart.axesontop')) {
754            this.DrawAxes();
755        }
756
757        /**
758        * Draw the labels
759        */
760        this.DrawLabels();
761       
762        /**
763        * Draw the range if necessary
764        */
765        this.DrawRange();
766       
767        // Draw a key if necessary
768        if (this.Get('chart.key').length) {
769            RGraph.DrawKey(this, this.Get('chart.key'), this.Get('chart.colors'));
770        }
771
772        /**
773        * Draw " above" labels if enabled
774        */
775        if (this.Get('chart.labels.above')) {
776            this.DrawAboveLabels();
777        }
778
779        /**
780        * Draw the "in graph" labels
781        */
782        RGraph.DrawInGraphLabels(this);
783
784        /**
785        * Draw crosschairs
786        */
787        RGraph.DrawCrosshairs(this);
788       
789        /**
790        * If the canvas is annotatable, do install the event handlers
791        */
792        if (this.Get('chart.annotatable')) {
793            RGraph.Annotate(this);
794        }
795
796        /**
797        * Redraw the lines if a filled range is on the cards
798        */
799        if (this.Get('chart.filled') && this.Get('chart.filled.range') && this.data.length == 2) {
800
801            this.context.beginPath();
802            var len = this.coords.length / 2;
803            this.context.lineWidth = this.Get('chart.linewidth');
804            this.context.strokeStyle = this.Get('chart.colors')[0];
805
806            for (var i=0; i<len; ++i) {
807                if (i == 0) {
808                    this.context.moveTo(this.coords[i][0], this.coords[i][1]);
809                } else {
810                    this.context.lineTo(this.coords[i][0], this.coords[i][1]);
811                }
812            }
813           
814            this.context.stroke();
815
816
817            this.context.beginPath();
818           
819            if (this.Get('chart.colors')[1]) {
820                this.context.strokeStyle = this.Get('chart.colors')[1];
821            }
822           
823            for (var i=this.coords.length - 1; i>=len; --i) {
824                if (i == (this.coords.length - 1) ) {
825                    this.context.moveTo(this.coords[i][0], this.coords[i][1]);
826                } else {
827                    this.context.lineTo(this.coords[i][0], this.coords[i][1]);
828                }
829            }
830           
831            this.context.stroke();
832        } else if (this.Get('chart.filled') && this.Get('chart.filled.range')) {
833            alert('[LINE] You must have only two sets of data for a filled range chart');
834        }
835
836        /**
837        * This bit shows the mini zoom window if requested
838        */
839        if (this.Get('chart.zoom.mode') == 'thumbnail') {
840            RGraph.ShowZoomWindow(this);
841        }
842
843        /**
844        * This function enables the zoom in area mode
845        */
846        if (this.Get('chart.zoom.mode') == 'area') {
847            RGraph.ZoomArea(this);
848        }
849       
850        /**
851        * This function enables resizing
852        */
853        if (this.Get('chart.resizable')) {
854            RGraph.AllowResizing(this);
855        }
856       
857        /**
858        * This function enables adjustments
859        */
860        if (this.Get('chart.adjustable')) {
861            RGraph.AllowAdjusting(this);
862        }
863       
864        /**
865        * Fire the RGraph ondraw event
866        */
867        RGraph.FireCustomEvent(this, 'ondraw');
868    }
869
870   
871    /**
872    * Draws the axes
873    */
874    RGraph.Line.prototype.DrawAxes = function ()
875    {
876        // Don't draw the axes?
877        if (this.Get('chart.noaxes')) {
878            return;
879        }
880
881        // Turn any shadow off
882        RGraph.NoShadow(this);
883
884        this.context.lineWidth   = 1;
885        this.context.strokeStyle = this.Get('chart.axis.color');
886        this.context.beginPath();
887
888        // Draw the X axis
889        if (this.Get('chart.noxaxis') == false) {
890            if (this.Get('chart.xaxispos') == 'center') {
891                this.context.moveTo(this.gutterLeft, (this.grapharea / 2) + this.gutterTop);
892                this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, (this.grapharea / 2) + this.gutterTop);
893            } else if (this.Get('chart.xaxispos') == 'top') {
894                this.context.moveTo(this.gutterLeft, this.gutterTop);
895                this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, this.gutterTop);
896            } else {
897                this.context.moveTo(this.gutterLeft, RGraph.GetHeight(this) - this.gutterBottom);
898                this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
899            }
900        }
901       
902        // Draw the Y axis
903        if (this.Get('chart.noyaxis') == false) {
904            if (this.Get('chart.yaxispos') == 'left') {
905                this.context.moveTo(this.gutterLeft, this.gutterTop);
906                this.context.lineTo(this.gutterLeft, RGraph.GetHeight(this) - this.gutterBottom );
907            } else {
908                this.context.moveTo(RGraph.GetWidth(this) - this.gutterRight, this.gutterTop);
909                this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
910            }
911        }
912
913        /**
914        * Draw the X tickmarks
915        */
916        if (this.Get('chart.noxaxis') == false) {
917
918            var xTickInterval = (RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight) / (this.Get('chart.xticks') ? this.Get('chart.xticks') : (this.data[0].length - 1));
919   
920            for (x=this.gutterLeft + (this.Get('chart.yaxispos') == 'left' ? xTickInterval : 0); x<=(RGraph.GetWidth(this) - this.gutterRight + 1 ); x+=xTickInterval) {
921
922                if (this.Get('chart.yaxispos') == 'right' && x >= (RGraph.GetWidth(this) - this.gutterRight - 1) ) {
923                    break;
924                }
925               
926                // If the last tick is not desired...
927                if (this.Get('chart.noendxtick')) {
928                    if (this.Get('chart.yaxispos') == 'left' && x >= (RGraph.GetWidth(this) - this.gutterRight)) {
929                        break;
930                    } else if (this.Get('chart.yaxispos') == 'right' && x == this.gutterLeft) {
931                        continue;
932                    }
933                }
934   
935                var yStart = this.Get('chart.xaxispos') == 'center' ? (this.gutterTop + (this.grapharea / 2)) - 3 : RGraph.GetHeight(this) - this.gutterBottom;
936                var yEnd = this.Get('chart.xaxispos') == 'center' ? yStart + 6 : RGraph.GetHeight(this) - this.gutterBottom - (x % 60 == 0 ? this.Get('chart.largexticks') * this.Get('chart.tickdirection') : this.Get('chart.smallxticks') * this.Get('chart.tickdirection'));
937               
938                if (this.Get('chart.xaxispos') == 'top') {
939                    yStart = this.gutterTop - 3;
940                    yEnd   = this.gutterTop;
941                }
942
943                this.context.moveTo(x, yStart);
944                this.context.lineTo(x, yEnd);
945            }
946
947        // Draw an extra tickmark if there is no X axis, but there IS a Y axis
948        } else if (this.Get('chart.noyaxis') == false) {
949            if (this.Get('chart.yaxispos') == 'left') {
950                this.context.moveTo(this.gutterLeft, RGraph.GetHeight(this) - this.gutterBottom);
951                this.context.lineTo(this.gutterLeft - this.Get('chart.smallyticks'), RGraph.GetHeight(this) - this.gutterBottom);
952            } else {
953                this.context.moveTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
954                this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight + this.Get('chart.smallyticks'), RGraph.GetHeight(this) - this.gutterBottom);
955            }
956        }
957
958        /**
959        * Draw the Y tickmarks
960        */
961        if (this.Get('chart.noyaxis') == false) {
962            var counter    = 0;
963            var adjustment = 0;
964   
965            if (this.Get('chart.yaxispos') == 'right') {
966                adjustment = (RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight);
967            }
968           
969            // X axis at the center
970            if (this.Get('chart.xaxispos') == 'center') {
971                var interval = (this.grapharea / 10);
972                var lineto = (this.Get('chart.yaxispos') == 'left' ? this.gutterLeft : RGraph.GetWidth(this) - this.gutterRight + this.Get('chart.smallyticks'));
973   
974                // Draw the upper halves Y tick marks
975                for (y=this.gutterTop; y < (this.grapharea / 2) + this.gutterTop; y+=interval) {
976                    this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), y);
977                    this.context.lineTo(lineto, y);
978                }
979               
980                // Draw the lower halves Y tick marks
981                for (y=this.gutterTop + (this.halfgrapharea) + interval; y <= this.grapharea + this.gutterTop; y+=interval) {
982                    this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), y);
983                    this.context.lineTo(lineto, y);
984                }
985           
986            // X axis at the top
987            } else if (this.Get('chart.xaxispos') == 'top') {
988                var interval = (this.grapharea / 10);
989                var lineto = (this.Get('chart.yaxispos') == 'left' ? this.gutterLeft : RGraph.GetWidth(this) - this.gutterRight + this.Get('chart.smallyticks'));
990
991                // Draw the Y tick marks
992                for (y=this.gutterTop + interval; y <=this.grapharea + this.gutterTop; y+=interval) {
993                    this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), y);
994                    this.context.lineTo(lineto, y);
995                }
996               
997                // If there's no X axis draw an extra tick
998                if (this.Get('chart.noxaxis')) {
999                    this.context.moveTo((this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : RGraph.GetWidth(this) - this.gutterRight), this.gutterTop);
1000                    this.context.lineTo(lineto, this.gutterTop);
1001                }
1002           
1003            // X axis at the bottom
1004            } else {
1005                var lineto = (this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - this.Get('chart.smallyticks') : this.canvas.width - this.gutterRight + this.Get('chart.smallyticks'));
1006   
1007                for (y=this.gutterTop; y < (this.canvas.height - this.gutterBottom) && counter < 10; y+=( (RGraph.GetHeight(this) - this.gutterTop - this.gutterBottom) / 10) ) {
1008   
1009                    this.context.moveTo(this.gutterLeft + adjustment, y);
1010                    this.context.lineTo(lineto, y);
1011               
1012                    var counter = counter +1;
1013                }
1014            }
1015
1016        // Draw an extra X tickmark
1017        } else if (this.Get('chart.noxaxis') == false) {
1018
1019            if (this.Get('chart.yaxispos') == 'left') {
1020                this.context.moveTo(this.gutterLeft, this.Get('chart.xaxispos') == 'top' ? this.gutterTop : RGraph.GetHeight(this) - this.gutterBottom);
1021                this.context.lineTo(this.gutterLeft, this.Get('chart.xaxispos') == 'top' ? this.gutterTop - this.Get('chart.smallxticks') : RGraph.GetHeight(this) - this.gutterBottom + this.Get('chart.smallxticks'));
1022           } else {
1023                this.context.moveTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom);
1024                this.context.lineTo(RGraph.GetWidth(this) - this.gutterRight, RGraph.GetHeight(this) - this.gutterBottom + this.Get('chart.smallxticks'));
1025            }
1026        }
1027
1028        this.context.stroke();
1029    }
1030
1031
1032    /**
1033    * Draw the text labels for the axes
1034    */
1035    RGraph.Line.prototype.DrawLabels = function ()
1036    {
1037        this.context.strokeStyle = 'black';
1038        this.context.fillStyle   = this.Get('chart.text.color');
1039        this.context.lineWidth   = 1;
1040       
1041        // Turn off any shadow
1042        RGraph.NoShadow(this);
1043
1044        // This needs to be here
1045        var font      = this.Get('chart.text.font');
1046        var text_size = this.Get('chart.text.size');
1047        var context   = this.context;
1048        var canvas    = this.canvas;
1049
1050        // Draw the Y axis labels
1051        if (this.Get('chart.ylabels') && this.Get('chart.ylabels.specific') == null) {
1052
1053            var units_pre  = this.Get('chart.units.pre');
1054            var units_post = this.Get('chart.units.post');
1055            var xpos       = this.Get('chart.yaxispos') == 'left' ? this.gutterLeft - 5 : RGraph.GetWidth(this) - this.gutterRight + 5;
1056            var align      = this.Get('chart.yaxispos') == 'left' ? 'right' : 'left';
1057           
1058            var numYLabels = this.Get('chart.ylabels.count');
1059            var bounding   = false;
1060            var bgcolor    = this.Get('chart.ylabels.inside') ? this.Get('chart.ylabels.inside.color') : null;
1061
1062           
1063            /**
1064            * If the Y labels are inside the Y axis, invert the alignment
1065            */
1066            if (this.Get('chart.ylabels.inside') == true && align == 'left') {
1067                xpos -= 10;
1068                align = 'right';
1069                bounding = true;
1070               
1071
1072            } else if (this.Get('chart.ylabels.inside') == true && align == 'right') {
1073                xpos += 10;
1074                align = 'left';
1075                bounding = true;
1076            }
1077
1078
1079
1080            if (this.Get('chart.xaxispos') == 'center') {
1081                var half = this.grapharea / 2;
1082
1083                if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
1084                    //  Draw the upper halves labels
1085                    RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (0/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
1086   
1087                    if (numYLabels == 5) {
1088                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (1/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
1089                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (3/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
1090                    }
1091   
1092                    if (numYLabels >= 3) {
1093                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (2/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
1094                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (4/5) * half ) + this.halfTextHeight, RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1095                    }
1096                   
1097                    //  Draw the lower halves labels
1098                    if (numYLabels >= 3) {
1099                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (6/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1100                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (8/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
1101                    }
1102   
1103                    if (numYLabels == 5) {
1104                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (7/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
1105                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (9/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
1106                    }
1107   
1108                    RGraph.Text(context, font, text_size, xpos, this.gutterTop + ( (10/5) * half ) + this.halfTextHeight, '-' + RGraph.number_format(this, (this.scale[4] == '1.0' ? '1.0' : this.scale[4]), units_pre, units_post), null, align, bounding, null, bgcolor);
1109               
1110                } else if (numYLabels == 10) {
1111
1112                    // 10 Y labels
1113                    var interval = (this.grapharea / numYLabels) / 2;
1114               
1115                    for (var i=0; i<numYLabels; ++i) {
1116                        // This draws the upper halves labels
1117                        RGraph.Text(context,font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((i/20) * (this.grapharea) ), RGraph.number_format(this, ((this.scale[4] / numYLabels) * (numYLabels - i)).toFixed((this.Get('chart.scale.decimals'))),units_pre, units_post), null, align, bounding, null, bgcolor);
1118                       
1119                        // And this draws the lower halves labels
1120                        RGraph.Text(context, font, text_size, xpos,
1121                       
1122                        this.gutterTop + this.halfTextHeight + ((i/20) * this.grapharea) + (this.grapharea / 2) + (this.grapharea / 20),
1123                       
1124                        '-' + RGraph.number_format(this, (this.scale[4] - ((this.scale[4] / numYLabels) * (numYLabels - i - 1))).toFixed((this.Get('chart.scale.decimals'))),units_pre, units_post), null, align, bounding, null, bgcolor);
1125                    }
1126                   
1127                } else {
1128                    alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
1129                }
1130
1131                // Draw the lower limit if chart.ymin is specified
1132                if (typeof(this.Get('chart.ymin')) == 'number') {
1133                    RGraph.Text(context, font, text_size, xpos, RGraph.GetHeight(this) / 2, RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post), 'center', align, bounding, null, bgcolor);
1134                }
1135               
1136                // No X axis - so draw 0
1137                if (this.Get('chart.noxaxis') == true) {
1138                    RGraph.Text(context,font,text_size,xpos,this.gutterTop + ( (5/5) * half ) + this.halfTextHeight,'0',null, align, bounding, null, bgcolor);
1139                }
1140
1141            // X axis at the top
1142            } else if (this.Get('chart.xaxispos') == 'top') {
1143
1144                var scale = RGraph.array_reverse(this.scale);
1145
1146                /**
1147                * Accommodate reversing the Y labels
1148                */
1149                if (this.Get('chart.ylabels.invert')) {
1150
1151                    scale = RGraph.array_reverse(scale);
1152
1153                    this.context.translate(0, this.grapharea * -0.2);
1154                    if (typeof(this.Get('chart.ymin')) == null) {
1155                        this.Set('chart.ymin', 0);
1156                    }
1157                }
1158
1159                if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
1160                    RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((1/5) * (this.grapharea ) ), '-' + RGraph.number_format(this, scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
1161   
1162                    if (numYLabels == 5) {
1163                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((4/5) * (this.grapharea) ), '-' + RGraph.number_format(this, scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
1164                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((2/5) * (this.grapharea) ), '-' + RGraph.number_format(this, scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
1165                    }
1166   
1167                    if (numYLabels >= 3) {
1168                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((3/5) * (this.grapharea ) ), '-' + RGraph.number_format(this, scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
1169                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((5/5) * (this.grapharea) ), '-' + RGraph.number_format(this, scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1170                    }
1171               
1172                } else if (numYLabels == 10) {
1173
1174                    // 10 Y labels
1175                    var interval = (this.grapharea / numYLabels) / 2;
1176
1177                    for (var i=0; i<numYLabels; ++i) {
1178
1179                        RGraph.Text(context,font,text_size,xpos,(2 * interval) + this.gutterTop + this.halfTextHeight + ((i/10) * (this.grapharea) ),'-' + RGraph.number_format(this,(scale[0] - (((scale[0] - this.min) / numYLabels) * (numYLabels - i - 1))).toFixed((this.Get('chart.scale.decimals'))),units_pre,units_post),null,align,bounding,null,bgcolor);
1180                    }
1181
1182                } else {
1183                    alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
1184                }
1185
1186
1187                /**
1188                * Accommodate translating back after reversing the labels
1189                */
1190                if (this.Get('chart.ylabels.invert')) {
1191                    this.context.translate(0, 0 - (this.grapharea * -0.2));
1192                }
1193
1194                // Draw the lower limit if chart.ymin is specified
1195                if (typeof(this.Get('chart.ymin')) == 'number') {
1196                    RGraph.Text(context,font,text_size,xpos,this.Get('chart.ylabels.invert') ? this.canvas.height - this.gutterBottom : this.gutterTop,'-' + RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post),'center',align,bounding,null,bgcolor);
1197                }
1198
1199            } else {
1200
1201                /**
1202                * Accommodate reversing the Y labels
1203                */
1204                if (this.Get('chart.ylabels.invert')) {
1205                    this.scale = RGraph.array_reverse(this.scale);
1206                    this.context.translate(0, this.grapharea * 0.2);
1207                    if (typeof(this.Get('chart.ymin')) == null) {
1208                        this.Set('chart.ymin', 0);
1209                    }
1210                }
1211
1212                if (numYLabels == 1 || numYLabels == 3 || numYLabels == 5) {
1213                    RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((0/5) * (this.grapharea ) ), RGraph.number_format(this, this.scale[4], units_pre, units_post), null, align, bounding, null, bgcolor);
1214   
1215                    if (numYLabels == 5) {
1216                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((3/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[1], units_pre, units_post), null, align, bounding, null, bgcolor);
1217                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((1/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[3], units_pre, units_post), null, align, bounding, null, bgcolor);
1218                    }
1219   
1220                    if (numYLabels >= 3) {
1221                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((2/5) * (this.grapharea ) ), RGraph.number_format(this, this.scale[2], units_pre, units_post), null, align, bounding, null, bgcolor);
1222                        RGraph.Text(context, font, text_size, xpos, this.gutterTop + this.halfTextHeight + ((4/5) * (this.grapharea) ), RGraph.number_format(this, this.scale[0], units_pre, units_post), null, align, bounding, null, bgcolor);
1223                    }
1224               
1225                } else if (numYLabels == 10) {
1226
1227                    // 10 Y labels
1228                    var interval = (this.grapharea / numYLabels) / 2;
1229               
1230                    for (var i=0; i<numYLabels; ++i) {
1231                        RGraph.Text(context,font,text_size,xpos,this.gutterTop + this.halfTextHeight + ((i/10) * (this.grapharea) ),RGraph.number_format(this,((((this.scale[4] - this.min) / numYLabels) * (numYLabels - i)) + this.min).toFixed((this.Get('chart.scale.decimals'))),units_pre,units_post),null,align,bounding,null,bgcolor);
1232                    }
1233
1234                } else {
1235                    alert('[LINE SCALE] The number of Y labels must be 1/3/5/10');
1236                }
1237
1238
1239                /**
1240                * Accommodate translating back after reversing the labels
1241                */
1242                if (this.Get('chart.ylabels.invert')) {
1243                    this.context.translate(0, 0 - (this.grapharea * 0.2));
1244                }
1245
1246                // Draw the lower limit if chart.ymin is specified
1247                if (typeof(this.Get('chart.ymin')) == 'number') {
1248                    RGraph.Text(context,font,text_size,xpos,this.Get('chart.ylabels.invert') ? this.gutterTop : RGraph.GetHeight(this) - this.gutterBottom,RGraph.number_format(this, this.Get('chart.ymin').toFixed(this.Get('chart.scale.decimals')), units_pre, units_post),'center',align,bounding,null,bgcolor);
1249                }
1250            }
1251
1252            // No X axis - so draw 0
1253            if (   this.Get('chart.noxaxis') == true
1254                && this.Get('chart.ymin') == null
1255               ) {
1256
1257                RGraph.Text(context,font,text_size,xpos,this.Get('chart.xaxispos') == 'top' ? this.gutterTop + this.halfTextHeight: (RGraph.GetHeight(this) - this.gutterBottom + this.halfTextHeight),'0',null, align, bounding, null, bgcolor);
1258            }
1259       
1260        } else if (this.Get('chart.ylabels') && typeof(this.Get('chart.ylabels.specific')) == 'object') {
1261           
1262            // A few things
1263            var gap      = this.grapharea / this.Get('chart.ylabels.specific').length;
1264            var halign   = this.Get('chart.yaxispos') == 'left' ? 'right' : 'left';
1265            var bounding = false;
1266            var bgcolor  = null;
1267            var ymin     = this.Get('chart.ymin') != null && this.Get('chart.ymin');
1268
1269            // Figure out the X coord based on the position of the axis
1270            if (this.Get('chart.yaxispos') == 'left') {
1271                var x = this.gutterLeft - 5;
1272               
1273                if (this.Get('chart.ylabels.inside')) {
1274                    x += 10;
1275                    halign   = 'left';
1276                    bounding = true;
1277                    bgcolor  = 'rgba(255,255,255,0.5)';
1278                }
1279
1280            } else if (this.Get('chart.yaxispos') == 'right') {
1281                var x = this.canvas.width - this.gutterRight + 5;
1282               
1283                if (this.Get('chart.ylabels.inside')) {
1284                    x -= 10;
1285                    halign = 'right';
1286                    bounding = true;
1287                    bgcolor  = 'rgba(255,255,255,0.5)';
1288                }
1289            }
1290
1291
1292            // Draw the labels
1293            if (this.Get('chart.xaxispos') == 'center') {
1294           
1295                // Draw the top halfs labels
1296                for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
1297                    var y = this.gutterTop + (this.grapharea / ((this.Get('chart.ylabels.specific').length ) * 2) * i);
1298                   
1299                    if (ymin && ymin > 0) {
1300                        var y  = ((this.grapharea / 2) / (this.Get('chart.ylabels.specific').length - (ymin ? 1 : 0)) ) * i;
1301                            y += this.gutterTop;
1302                    }
1303                   
1304                    RGraph.Text(context, font, text_size,x,y,String(this.Get('chart.ylabels.specific')[i]), 'center', halign, bounding, 0, bgcolor);
1305                }
1306               
1307                // Now reverse the labels and draw the bottom half
1308                var reversed_labels = RGraph.array_reverse(this.Get('chart.ylabels.specific'));
1309           
1310                // Draw the bottom halfs labels
1311                for (var i=0; i<reversed_labels.length; ++i) {
1312                    var y = (this.grapharea / 2) + this.gutterTop + ((this.grapharea / (reversed_labels.length * 2) ) * (i + 1));
1313
1314                    if (ymin && ymin > 0) {
1315                        var y  = ((this.grapharea / 2) / (reversed_labels.length - (ymin ? 1 : 0)) ) * i;
1316                            y += this.gutterTop;
1317                            y += (this.grapharea / 2);
1318                    }
1319
1320                    RGraph.Text(context, font, text_size,x,y,String(reversed_labels[i]), 'center', halign, bounding, 0, bgcolor);
1321                }
1322           
1323            } else if (this.Get('chart.xaxispos') == 'top') {
1324
1325                // Reverse the labels and draw
1326                var reversed_labels = RGraph.array_reverse(this.Get('chart.ylabels.specific'));
1327           
1328                // Draw the bottom halfs labels
1329                for (var i=0; i<reversed_labels.length; ++i) {
1330                   
1331                    var y = ((this.grapharea / (reversed_labels.length - (ymin ? 1 : 0)) ) * (i + (ymin ? 0 : 1)));
1332                        y = y + this.gutterTop;
1333
1334                    RGraph.Text(context, font, text_size,x,y,String(reversed_labels[i]), 'center', halign, bounding, 0, bgcolor);
1335                }
1336
1337            } else {
1338                for (var i=0; i<this.Get('chart.ylabels.specific').length; ++i) {
1339                    var y = this.gutterTop + ((this.grapharea / (this.Get('chart.ylabels.specific').length - (ymin ? 1 : 0) )) * i);
1340                    RGraph.Text(context, font, text_size,x,y,String(this.Get('chart.ylabels.specific')[i]), 'center', halign, bounding, 0, bgcolor);
1341                }
1342            }
1343        }
1344
1345        // Draw the X axis labels
1346        if (this.Get('chart.labels') && this.Get('chart.labels').length > 0) {
1347
1348
1349            var yOffset  = 13;
1350            var bordered = false;
1351            var bgcolor  = null;
1352
1353            if (this.Get('chart.xlabels.inside')) {
1354                yOffset = -5;
1355                bordered = true;
1356                bgcolor  = this.Get('chart.xlabels.inside.color');
1357            }
1358
1359            /**
1360            * Text angle
1361            */
1362            var angle  = 0;
1363            var valign = null;
1364            var halign = 'center';
1365
1366            if (typeof(this.Get('chart.text.angle')) == 'number' && this.Get('chart.text.angle') > 0) {
1367                angle   = -1 * this.Get('chart.text.angle');
1368                valign  = 'center';
1369                halign  = 'right';
1370                yOffset = 10;
1371               
1372                if (this.Get('chart.xaxispos') == 'top') {
1373                    yOffset = 10;
1374                }
1375            }
1376
1377            this.context.fillStyle = this.Get('chart.text.color');
1378            var numLabels = this.Get('chart.labels').length;
1379
1380            for (i=0; i<numLabels; ++i) {
1381
1382                // Changed 8th Nov 2010 to be not reliant on the coords
1383                //if (this.Get('chart.labels')[i] && this.coords && this.coords[i] && this.coords[i][0]) {
1384                if (this.Get('chart.labels')[i]) {
1385
1386                    var labelX = ((RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight - (2 * this.Get('chart.hmargin'))) / (numLabels - 1) ) * i;
1387                        labelX += this.gutterLeft + this.Get('chart.hmargin');
1388
1389                    /**
1390                    * Account for an unrelated number of labels
1391                    */
1392                    if (this.Get('chart.labels').length != this.data[0].length) {
1393                        labelX = this.gutterLeft + this.Get('chart.hmargin') + ((RGraph.GetWidth(this) - this.gutterLeft - this.gutterRight - (2 * this.Get('chart.hmargin'))) * (i / (this.Get('chart.labels').length - 1)));
1394                    }
1395                   
1396                    // This accounts for there only being one point on the chart
1397                    if (!labelX) {
1398                        labelX = this.gutterLeft + this.Get('chart.hmargin');
1399                    }
1400
1401                    if (this.Get('chart.xaxispos') == 'top' && this.Get('chart.text.angle') > 0) {
1402                        halign = 'left';
1403                    }
1404
1405                    RGraph.Text(context,
1406                                font,
1407                                text_size,
1408                                labelX,
1409                                (this.Get('chart.xaxispos') == 'top') ? this.gutterTop - yOffset - (this.Get('chart.xlabels.inside') ? -22 : 0) : (RGraph.GetHeight(this) - this.gutterBottom) + yOffset,
1410                                String(this.Get('chart.labels')[i]),
1411                                valign,
1412                                halign,
1413                                bordered,
1414                                angle,
1415                                bgcolor);
1416                }
1417            }
1418
1419        }
1420
1421        this.context.stroke();
1422        this.context.fill();
1423    }
1424
1425
1426    /**
1427    * Draws the line
1428    */
1429    RGraph.Line.prototype.DrawLine = function (lineData, color, fill, linewidth, tickmarks, index)
1430    {
1431        var penUp = false;
1432        var yPos  = null;
1433        var xPos  = 0;
1434        this.context.lineWidth = 1;
1435        var lineCoords = [];
1436
1437        // Work out the X interval
1438        var xInterval = (this.canvas.width - (2 * this.Get('chart.hmargin')) - this.gutterLeft - this.gutterRight) / (lineData.length - 1);
1439
1440        // Loop thru each value given, plotting the line
1441        for (i=0; i<lineData.length; i++) {
1442
1443            var data_point = lineData[i];
1444
1445
1446            yPos = this.canvas.height - (((data_point - (data_point > 0 ?  this.Get('chart.ymin') : (-1 * this.Get('chart.ymin')))) / (this.max - this.min) ) * this.grapharea);
1447            yPos = (this.grapharea / (this.max - this.min)) * (data_point - this.min);
1448            yPos = this.canvas.height - yPos;
1449           
1450            /**
1451            * This skirts an annoying JS rounding error
1452            * SEARCH TAGS: JS ROUNDING ERROR DECIMALS
1453            */
1454            if (data_point == this.max) {
1455                yPos = Math.round(yPos);
1456            }
1457
1458            if (this.Get('chart.ylabels.invert')) {
1459                yPos -= this.gutterBottom;
1460                yPos -= this.gutterTop;
1461                yPos = this.canvas.height - yPos;
1462            }
1463
1464            // Make adjustments depending on the X axis position
1465            if (this.Get('chart.xaxispos') == 'center') {
1466                yPos = (yPos - this.gutterBottom - this.gutterTop) / 2;
1467                yPos = yPos + this.gutterTop;
1468           
1469            // TODO Check this
1470            } else if (this.Get('chart.xaxispos') == 'top') {
1471
1472                yPos = (this.grapharea / (this.max - this.min)) * (Math.abs(data_point) - this.min);
1473                yPos += this.gutterTop;
1474               
1475                if (this.Get('chart.ylabels.invert')) {
1476                    yPos -= this.gutterTop;
1477                    yPos  = this.grapharea - yPos;
1478                    yPos += this.gutterTop;
1479                }
1480
1481            } else if (this.Get('chart.xaxispos') == 'bottom') {
1482                // TODO
1483                yPos -= this.gutterBottom; // Without this the line is out of place due to the gutter
1484            }
1485
1486
1487
1488            // Null data points, and a special case for this bug:http://dev.rgraph.net/tests/ymin.html
1489            if (   lineData[i] == null
1490                || (this.Get('chart.xaxispos') == 'bottom' && lineData[i] < this.min && !this.Get('chart.outofbounds'))
1491                ||  (this.Get('chart.xaxispos') == 'center' && lineData[i] < (-1 * this.max) && !this.Get('chart.outofbounds'))) {
1492
1493                yPos = null;
1494            }
1495
1496            // Not always very noticeable, but it does have an effect
1497            // with thick lines
1498            this.context.lineCap  = 'round';
1499            this.context.lineJoin = 'round';
1500
1501            // Plot the line if we're at least on the second iteration
1502            if (i > 0) {
1503                xPos = xPos + xInterval;
1504            } else {
1505                xPos = this.Get('chart.hmargin') + this.gutterLeft;
1506            }
1507
1508            /**
1509            * Add the coords to an array
1510            */
1511            this.coords.push([xPos, yPos]);
1512            lineCoords.push([xPos, yPos]);
1513        }
1514       
1515        this.context.stroke();
1516
1517        // Store the coords in another format, indexed by line number
1518        this.coords2[index] = lineCoords;
1519
1520        /**
1521        * For IE only: Draw the shadow ourselves as ExCanvas doesn't produce shadows
1522        */
1523        if (RGraph.isIE8() && this.Get('chart.shadow')) {
1524            this.DrawIEShadow(lineCoords, this.context.shadowColor);
1525        }
1526
1527        /**
1528        * Now draw the actual line [FORMERLY SECOND]
1529        */
1530        this.context.beginPath();
1531        this.context.strokeStyle = 'rgba(240,240,240,0.9)'; // Almost transparent - changed on 10th May 2010
1532        //this.context.strokeStyle = fill;
1533        if (fill) this.context.fillStyle   = fill;
1534
1535        var isStepped = this.Get('chart.stepped');
1536        var isFilled = this.Get('chart.filled');
1537
1538
1539        for (var i=0; i<lineCoords.length; ++i) {
1540
1541            xPos = lineCoords[i][0];
1542            yPos = lineCoords[i][1];
1543            var set = index;
1544
1545            var prevY     = (lineCoords[i - 1] ? lineCoords[i - 1][1] : null);
1546            var isLast    = (i + 1) == lineCoords.length;
1547
1548            /**
1549            * This nullifys values which are out-of-range
1550            */
1551            if (prevY < this.gutterTop || prevY > (RGraph.GetHeight(this) - this.gutterBottom) ) {
1552                penUp = true;
1553            }
1554
1555            if (i == 0 || penUp || !yPos || !prevY || prevY < this.gutterTop) {
1556
1557                if (this.Get('chart.filled') && !this.Get('chart.filled.range')) {
1558                    this.context.moveTo(xPos + 1, this.canvas.height - this.gutterBottom - (this.Get('chart.xaxispos') == 'center' ? (this.canvas.height - this.gutterTop - this.gutterBottom) / 2 : 0) -1);
1559                    this.context.lineTo(xPos + 1, yPos);
1560
1561                } else {
1562                    this.context.moveTo(xPos, yPos);
1563                }
1564               
1565                if (yPos == null) {
1566                    penUp = true;
1567                } else {
1568                    penUp = false;
1569                }
1570
1571            } else {
1572
1573                // Draw the stepped part of stepped lines
1574                if (isStepped) {
1575                    this.context.lineTo(xPos, lineCoords[i - 1][1]);
1576                }
1577
1578                if ((yPos >= this.gutterTop && yPos <= (RGraph.GetHeight(this) - this.gutterBottom)) || this.Get('chart.outofbounds') ) {
1579
1580                    if (isLast && this.Get('chart.filled') && !this.Get('chart.filled.range') && this.Get('chart.yaxispos') == 'right') {
1581                        xPos -= 1;
1582                    }
1583
1584
1585                    // Added 8th September 2009
1586                    if (!isStepped || !isLast) {
1587                        this.context.lineTo(xPos, yPos);
1588                       
1589                        if (isFilled && lineCoords[i+1] && lineCoords[i+1][1] == null) {
1590                            this.context.lineTo(xPos, RGraph.GetHeight(this) - this.gutterBottom);
1591                        }
1592                   
1593                    // Added August 2010
1594                    } else if (isStepped && isLast) {
1595                        this.context.lineTo(xPos,yPos);
1596                    }
1597
1598
1599                    penUp = false;
1600                } else {
1601                    penUp = true;
1602                }
1603            }
1604        }
1605
1606        if (this.Get('chart.filled') && !this.Get('chart.filled.range')) {
1607            var fillStyle = this.Get('chart.fillstyle');
1608
1609            this.context.lineTo(xPos, RGraph.GetHeight(this) - this.gutterBottom - 1 -  + (this.Get('chart.xaxispos') == 'center' ? (RGraph.GetHeight(this) - this.gutterTop - this.gutterBottom) / 2 : 0));
1610            this.context.fillStyle = fill;
1611
1612            this.context.fill();
1613            this.context.beginPath();
1614        }
1615
1616        /**
1617        * FIXME this may need removing when Chrome is fixed
1618        * SEARCH TAGS: CHROME SHADOW BUG
1619        */
1620        if (navigator.userAgent.match(/Chrome/) && this.Get('chart.shadow') && this.Get('chart.chromefix') && this.Get('chart.shadow.blur') > 0) {
1621
1622            for (var i=lineCoords.length - 1; i>=0; --i) {
1623                if (
1624                       typeof(lineCoords[i][1]) != 'number'
1625                    || (typeof(lineCoords[i+1]) == 'object' && typeof(lineCoords[i+1][1]) != 'number')
1626                   ) {
1627                    this.context.moveTo(lineCoords[i][0],lineCoords[i][1]);
1628                } else {
1629                    this.context.lineTo(lineCoords[i][0],lineCoords[i][1]);
1630                }
1631            }
1632        }
1633
1634        this.context.stroke();
1635
1636
1637        if (this.Get('chart.backdrop')) {
1638            this.DrawBackdrop(lineCoords, color);
1639        }
1640
1641        // Now redraw the lines with the correct line width
1642        this.RedrawLine(lineCoords, color, linewidth);
1643       
1644        this.context.stroke();
1645
1646        // Draw the tickmarks
1647        for (var i=0; i<lineCoords.length; ++i) {
1648
1649            i = Number(i);
1650
1651            if (isStepped && i == (lineCoords.length - 1)) {
1652                this.context.beginPath();
1653                //continue;
1654            }
1655
1656            if (
1657                (
1658                    tickmarks != 'endcircle'
1659                 && tickmarks != 'endsquare'
1660                 && tickmarks != 'filledendsquare'
1661                 && tickmarks != 'endtick'
1662                 && tickmarks != 'endtriangle'
1663                 && tickmarks != 'arrow'
1664                 && tickmarks != 'filledarrow'
1665                )
1666                || (i == 0 && tickmarks != 'arrow' && tickmarks != 'filledarrow')
1667                || i == (lineCoords.length - 1)
1668               ) {
1669
1670                var prevX = (i <= 0 ? null : lineCoords[i - 1][0]);
1671                var prevY = (i <= 0 ? null : lineCoords[i - 1][1]);
1672
1673                this.DrawTick(lineData, lineCoords[i][0], lineCoords[i][1], color, false, prevX, prevY, tickmarks, i);
1674
1675                // Draws tickmarks on the stepped bits of stepped charts. Takend out 14th July 2010
1676                //
1677                //if (this.Get('chart.stepped') && lineCoords[i + 1] && this.Get('chart.tickmarks') != 'endsquare' && this.Get('chart.tickmarks') != 'endcircle' && this.Get('chart.tickmarks') != 'endtick') {
1678                //    this.DrawTick(lineCoords[i + 1][0], lineCoords[i][1], color);
1679                //}
1680            }
1681        }
1682
1683        // Draw something off canvas to skirt an annoying bug
1684        this.context.beginPath();
1685        this.context.arc(RGraph.GetWidth(this) + 50000, RGraph.GetHeight(this) + 50000, 2, 0, 6.38, 1);
1686    }
1687   
1688   
1689    /**
1690    * This functions draws a tick mark on the line
1691    *
1692    * @param xPos  int  The x position of the tickmark
1693    * @param yPos  int  The y position of the tickmark
1694    * @param color str  The color of the tickmark
1695    * @param       bool Whether the tick is a shadow. If it is, it gets offset by the shadow offset
1696    */
1697    RGraph.Line.prototype.DrawTick = function (lineData, xPos, yPos, color, isShadow, prevX, prevY, tickmarks, index)
1698    {
1699        // If the yPos is null - no tick
1700        if ((yPos == null || yPos > (this.canvas.height - this.gutterBottom) || yPos < this.gutterTop) && !this.Get('chart.outofbounds')) {
1701            return;
1702        }
1703
1704        this.context.beginPath();
1705
1706        var offset   = 0;
1707
1708        // Reset the stroke and lineWidth back to the same as what they were when the line was drawm
1709        // UPDATE 28th July 2011 - the line width is now set to 1
1710        this.context.lineWidth   = this.Get('chart.tickmarks.linewidth') ? this.Get('chart.tickmarks.linewidth') : this.Get('chart.linewidth');
1711        this.context.strokeStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1712        this.context.fillStyle   = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1713
1714        // Cicular tick marks
1715        if (   tickmarks == 'circle'
1716            || tickmarks == 'filledcircle'
1717            || tickmarks == 'endcircle') {
1718
1719            if (tickmarks == 'circle'|| tickmarks == 'filledcircle' || (tickmarks == 'endcircle') ) {
1720                this.context.beginPath();
1721                this.context.arc(xPos + offset, yPos + offset, this.Get('chart.ticksize'), 0, 360 / (180 / Math.PI), false);
1722
1723                if (tickmarks == 'filledcircle') {
1724                    this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1725                } else {
1726                    this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : 'white';
1727                }
1728
1729                this.context.stroke();
1730                this.context.fill();
1731            }
1732
1733        // Halfheight "Line" style tick marks
1734        } else if (tickmarks == 'halftick') {
1735            this.context.beginPath();
1736            this.context.moveTo(xPos, yPos);
1737            this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
1738
1739            this.context.stroke();
1740       
1741        // Tick style tickmarks
1742        } else if (tickmarks == 'tick') {
1743            this.context.beginPath();
1744            this.context.moveTo(xPos, yPos -  this.Get('chart.ticksize'));
1745            this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
1746
1747            this.context.stroke();
1748       
1749        // Endtick style tickmarks
1750        } else if (tickmarks == 'endtick') {
1751            this.context.beginPath();
1752            this.context.moveTo(xPos, yPos -  this.Get('chart.ticksize'));
1753            this.context.lineTo(xPos, yPos + this.Get('chart.ticksize'));
1754
1755            this.context.stroke();
1756       
1757        // "Cross" style tick marks
1758        } else if (tickmarks == 'cross') {
1759            this.context.beginPath();
1760            this.context.moveTo(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'));
1761            this.context.lineTo(xPos + this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
1762            this.context.moveTo(xPos + this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'));
1763            this.context.lineTo(xPos - this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
1764           
1765            this.context.stroke();
1766
1767
1768        // Triangle style tick marks
1769        } else if (tickmarks == 'triangle' || tickmarks == 'filledtriangle' || tickmarks == 'endtriangle') {
1770            this.context.beginPath();
1771               
1772                if (tickmarks == 'filledtriangle') {
1773                    this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1774                } else {
1775                    this.context.fillStyle = 'white';
1776                }
1777
1778                this.context.moveTo(xPos - this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
1779                this.context.lineTo(xPos, yPos - this.Get('chart.ticksize'));
1780                this.context.lineTo(xPos + this.Get('chart.ticksize'), yPos + this.Get('chart.ticksize'));
1781            this.context.closePath();
1782           
1783            this.context.stroke();
1784            this.context.fill();
1785
1786
1787        // A white bordered circle
1788        } else if (tickmarks == 'borderedcircle' || tickmarks == 'dot') {
1789                this.context.lineWidth   = 1;
1790                this.context.strokeStyle = this.Get('chart.tickmarks.dot.color');
1791                this.context.fillStyle   = this.Get('chart.tickmarks.dot.color');
1792
1793                // The outer white circle
1794                this.context.beginPath();
1795                this.context.arc(xPos, yPos, this.Get('chart.ticksize'), 0, 360 / (180 / Math.PI), false);
1796                this.context.closePath();
1797
1798
1799                this.context.fill();
1800                this.context.stroke();
1801               
1802                // Now do the inners
1803                this.context.beginPath();
1804                this.context.fillStyle   = color;
1805                this.context.strokeStyle = color;
1806                this.context.arc(xPos, yPos, this.Get('chart.ticksize') - 2, 0, 360 / (180 / Math.PI), false);
1807
1808                this.context.closePath();
1809
1810                this.context.fill();
1811                this.context.stroke();
1812       
1813        } else if (   tickmarks == 'square'
1814                   || tickmarks == 'filledsquare'
1815                   || (tickmarks == 'endsquare')
1816                   || (tickmarks == 'filledendsquare') ) {
1817
1818            this.context.fillStyle   = 'white';
1819            this.context.strokeStyle = this.context.strokeStyle; // FIXME Is this correct?
1820
1821            this.context.beginPath();
1822            this.context.strokeRect(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'), this.Get('chart.ticksize') * 2, this.Get('chart.ticksize') * 2);
1823
1824            // Fillrect
1825            if (tickmarks == 'filledsquare' || tickmarks == 'filledendsquare') {
1826                this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : this.context.strokeStyle;
1827                this.context.fillRect(xPos - this.Get('chart.ticksize'), yPos - this.Get('chart.ticksize'), this.Get('chart.ticksize') * 2, this.Get('chart.ticksize') * 2);
1828
1829            } else if (tickmarks == 'square' || tickmarks == 'endsquare') {
1830                this.context.fillStyle = isShadow ? this.Get('chart.shadow.color') : 'white';
1831                this.context.fillRect((xPos - this.Get('chart.ticksize')) + 1, (yPos - this.Get('chart.ticksize')) + 1, (this.Get('chart.ticksize') * 2) - 2, (this.Get('chart.ticksize') * 2) - 2);
1832            }
1833
1834            this.context.stroke();
1835            this.context.fill();
1836
1837        /**
1838        * FILLED arrowhead
1839        */
1840        } else if (tickmarks == 'filledarrow') {
1841       
1842            var x = Math.abs(xPos - prevX);
1843            var y = Math.abs(yPos - prevY);
1844
1845            if (yPos < prevY) {
1846                var a = Math.atan(x / y) + 1.57;
1847            } else {
1848                var a = Math.atan(y / x) + 3.14;
1849            }
1850
1851            this.context.beginPath();
1852                this.context.moveTo(xPos, yPos);
1853                this.context.arc(xPos, yPos, 7, a - 0.5, a + 0.5, false);
1854            this.context.closePath();
1855
1856            this.context.stroke();
1857            this.context.fill();
1858
1859        /**
1860        * Arrow head, NOT filled
1861        */
1862        } else if (tickmarks == 'arrow') {
1863
1864            var x = Math.abs(xPos - prevX);
1865            var y = Math.abs(yPos - prevY);
1866
1867            if (yPos < prevY) {
1868                var a = Math.atan(x / y) + 1.57;
1869            } else {
1870                var a = Math.atan(y / x) + 3.14;
1871            }
1872
1873            this.context.beginPath();
1874                this.context.moveTo(xPos, yPos);
1875                this.context.arc(xPos, yPos, 7, a - 0.5 - (document.all ? 0.1 : 0.01), a - 0.4, false);
1876
1877                this.context.moveTo(xPos, yPos);
1878                this.context.arc(xPos, yPos, 7, a + 0.5 + (document.all ? 0.1 : 0.01), a + 0.5, true);
1879
1880
1881            this.context.stroke();
1882       
1883        /**
1884        * Custom tick drawing function
1885        */
1886        } else if (typeof(tickmarks) == 'function') {
1887            tickmarks(this, lineData, lineData[index], index, xPos, yPos, color, prevX, prevY);
1888        }
1889    }
1890
1891
1892    /**
1893    * Draws a filled range if necessary
1894    */
1895    RGraph.Line.prototype.DrawRange = function ()
1896    {
1897        /**
1898        * Fill the range if necessary
1899        */
1900        if (this.Get('chart.filled.range') && this.Get('chart.filled')) {
1901            this.context.beginPath();
1902            this.context.fillStyle = this.Get('chart.fillstyle');
1903            this.context.strokeStyle = this.Get('chart.fillstyle');
1904            this.context.lineWidth = 1;
1905            var len = (this.coords.length / 2);
1906
1907            for (var i=0; i<len; ++i) {
1908                if (i == 0) {
1909                    this.context.moveTo(this.coords[i][0], this.coords[i][1])
1910                } else {
1911                    this.context.lineTo(this.coords[i][0], this.coords[i][1])
1912                }
1913            }
1914
1915            for (var i=this.coords.length - 1; i>=len; --i) {
1916                this.context.lineTo(this.coords[i][0], this.coords[i][1])
1917            }
1918            this.context.stroke();
1919            this.context.fill();
1920        }
1921    }
1922
1923
1924    /**
1925    * Redraws the line with the correct line width etc
1926    *
1927    * @param array coords The coordinates of the line
1928    */
1929    RGraph.Line.prototype.RedrawLine = function (coords, color, linewidth)
1930    {
1931        if (this.Get('chart.noredraw')) {
1932            return;
1933        }
1934
1935        this.context.beginPath();
1936        this.context.strokeStyle = (typeof(color) == 'object' && color ? color[0] : color);
1937        this.context.lineWidth = linewidth;
1938
1939        var len    = coords.length;
1940        var width  = RGraph.GetWidth(this);
1941        var height = RGraph.GetHeight(this);
1942        var penUp  = false;
1943
1944        for (var i=0; i<len; ++i) {
1945
1946            var xPos   = coords[i][0];
1947            var yPos   = coords[i][1];
1948
1949            if (i > 0) {
1950                var prevX = coords[i - 1][0];
1951                var prevY = coords[i - 1][1];
1952            }
1953
1954
1955            if ((
1956                   (i == 0 && coords[i])
1957                || (yPos < this.gutterTop)
1958                || (prevY < this.gutterTop)
1959                || (yPos > (height - this.gutterBottom))
1960                || (i > 0 && prevX > (width - this.gutterRight))
1961                || (i > 0 && prevY > (height - this.gutterBottom))
1962                || prevY == null
1963                || penUp == true
1964               ) && (!this.Get('chart.outofbounds') || yPos == null || prevY == null) ) {
1965
1966                this.context.moveTo(coords[i][0], coords[i][1]);
1967
1968                penUp = false;
1969
1970            } else {
1971
1972                if (this.Get('chart.stepped') && i > 0) {
1973                    this.context.lineTo(coords[i][0], coords[i - 1][1]);
1974                }
1975               
1976                // Don't draw the last bit of a stepped chart. Now DO
1977                //if (!this.Get('chart.stepped') || i < (coords.length - 1)) {
1978                this.context.lineTo(coords[i][0], coords[i][1]);
1979                //}
1980                penUp = false;
1981            }
1982        }
1983
1984        /**
1985        * If two colors are specified instead of one, go over the up bits
1986        */
1987        if (this.Get('chart.colors.alternate') && typeof(color) == 'object' && color[0] && color[1]) {
1988            for (var i=1; i<len; ++i) {
1989
1990                var prevX = coords[i - 1][0];
1991                var prevY = coords[i - 1][1];
1992               
1993                this.context.beginPath();
1994                this.context.strokeStyle = color[coords[i][1] < prevY ? 0 : 1];
1995                this.context.lineWidth = this.Get('chart.linewidth');
1996                this.context.moveTo(prevX, prevY);
1997                this.context.lineTo(coords[i][0], coords[i][1]);
1998                this.context.stroke();
1999            }
2000        }
2001    }
2002
2003
2004    /**
2005    * This function is used by MSIE only to manually draw the shadow
2006    *
2007    * @param array coords The coords for the line
2008    */
2009    RGraph.Line.prototype.DrawIEShadow = function (coords, color)
2010    {
2011        var offsetx = this.Get('chart.shadow.offsetx');
2012        var offsety = this.Get('chart.shadow.offsety');
2013       
2014        this.context.lineWidth   = this.Get('chart.linewidth');
2015        this.context.strokeStyle = color;
2016        this.context.beginPath();
2017
2018        for (var i=0; i<coords.length; ++i) {
2019            if (i == 0) {
2020                this.context.moveTo(coords[i][0] + offsetx, coords[i][1] + offsety);
2021            } else {
2022                this.context.lineTo(coords[i][0] + offsetx, coords[i][1] + offsety);
2023            }
2024        }
2025
2026        this.context.stroke();
2027    }
2028
2029
2030    /**
2031    * Draw the backdrop
2032    */
2033    RGraph.Line.prototype.DrawBackdrop = function (coords, color)
2034    {
2035        var size = this.Get('chart.backdrop.size');
2036        this.context.lineWidth = size;
2037        this.context.globalAlpha = this.Get('chart.backdrop.alpha');
2038        this.context.strokeStyle = color;
2039        this.context.lineJoin = 'miter';
2040       
2041        this.context.beginPath();
2042            this.context.moveTo(coords[0][0], coords[0][1]);
2043            for (var j=1; j<coords.length; ++j) {
2044                this.context.lineTo(coords[j][0], coords[j][1]);
2045            }
2046   
2047        this.context.stroke();
2048   
2049        // Reset the alpha value
2050        this.context.globalAlpha = 1;
2051        this.context.lineJoin = 'round';
2052        RGraph.NoShadow(this);
2053    }
2054
2055
2056    /**
2057    * Returns the linewidth
2058    */
2059    RGraph.Line.prototype.GetLineWidth = function (i)
2060    {
2061        var linewidth = this.Get('chart.linewidth');
2062       
2063        if (typeof(linewidth) == 'number') {
2064            return linewidth;
2065       
2066        } else if (typeof(linewidth) == 'object') {
2067            if (linewidth[i]) {
2068                return linewidth[i];
2069            } else {
2070                return linewidth[0];
2071            }
2072
2073            alert('[LINE] Error! chart.linewidth should be a single number or an array of one or more numbers');
2074        }
2075    }
2076
2077
2078    /**
2079    * The getPoint() method - used to get the point the mouse is currently over, if any
2080    *
2081    * @param object e The event object
2082    */
2083    RGraph.Line.prototype.getPoint = function (e)
2084    {
2085        var canvas  = e.target;
2086        var obj     = canvas.__object__;
2087        var context = obj.context;
2088        var mouseXY = RGraph.getMouseXY(e);
2089        var mouseX  = mouseXY[0];
2090        var mouseY  = mouseXY[1];
2091
2092        for (var i=0; i<obj.coords.length; ++i) {
2093       
2094            var xCoord = obj.coords[i][0];
2095            var yCoord = obj.coords[i][1];
2096 
2097            if (   mouseX <= (xCoord + 5)
2098                && mouseX >= (xCoord - 5)
2099                && mouseY <= (yCoord + 5)
2100                && mouseY >= (yCoord - 5)
2101               ) {
2102
2103                    return [obj, xCoord, yCoord, i];
2104            }
2105        }
2106    }
2107
2108
2109    /**
2110    * Draws the above line labels
2111    */
2112    RGraph.Line.prototype.DrawAboveLabels = function ()
2113    {
2114        var context    = this.context;
2115        var size       = this.Get('chart.labels.above.size');
2116        var font       = this.Get('chart.text.font');
2117        var units_pre  = this.Get('chart.units.pre');
2118        var units_post = this.Get('chart.units.post');
2119
2120        context.beginPath();
2121
2122        // Don't need to check that chart.labels.above is enabled here, it's been done already
2123        for (var i=0; i<this.coords.length; ++i) {
2124            var coords = this.coords[i];
2125           
2126            RGraph.Text(context, font, size, coords[0], coords[1] - 5 - size, RGraph.number_format(this, this.data_arr[i], units_pre, units_post), 'center', 'center', true, null, 'rgba(255, 255, 255, 0.7)');
2127        }
2128       
2129        context.fill();
2130    }
Note: See TracBrowser for help on using the repository browser.