source: Dev/branches/Cartis/Tiles preview/js/RGraph/libraries/RGraph.line.js @ 283

Last change on this file since 283 was 283, checked in by tjcschipper, 13 years ago

Cartis Mockup werkt!

W
I
N
N
I
N
G

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.