new release 1.1.4
[psensor-pkg-ubuntu.git] / www / jqplot.js
1 /**
2  * Title: jqPlot Charts
3  * 
4  * Pure JavaScript plotting plugin for jQuery.
5  * 
6  * About: Version
7  * 
8  * version: 1.0.8 
9  * revision: 1250
10  * 
11  * About: Copyright & License
12  * 
13  * Copyright (c) 2009-2013 Chris Leonello
14  * jqPlot is currently available for use in all personal or commercial projects 
15  * under both the MIT and GPL version 2.0 licenses. This means that you can 
16  * choose the license that best suits your project and use it accordingly.
17  * 
18  * See <GPL Version 2> and <MIT License> contained within this distribution for further information. 
19  *
20  * The author would appreciate an email letting him know of any substantial
21  * use of jqPlot.  You can reach the author at: chris at jqplot dot com 
22  * or see http://www.jqplot.com/info.php.  This is, of course, not required.
23  *
24  * If you are feeling kind and generous, consider supporting the project by
25  * making a donation at: http://www.jqplot.com/donate.php.
26  *
27  * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
28  * 
29  *     version 2007.04.27
30  *     author Ash Searle
31  *     http://hexmen.com/blog/2007/03/printf-sprintf/
32  *     http://hexmen.com/js/sprintf.js
33  *     The author (Ash Searle) has placed this code in the public domain:
34  *     "This code is unrestricted: you are free to use it however you like."
35  * 
36  * 
37  * About: Introduction
38  * 
39  * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution.  
40  * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally 
41  * the excanvas script for IE support in your web page:
42  * 
43  * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
44  * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script>
45  * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script>
46  * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" />
47  * 
48  * jqPlot can be customized by overriding the defaults of any of the objects which make
49  * up the plot. The general usage of jqplot is:
50  * 
51  * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject});
52  * 
53  * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file.
54  * 
55  * An actual call to $.jqplot() may look like the 
56  * examples below:
57  * 
58  * > chart = $.jqplot('chartdiv',  [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]);
59  * 
60  * or
61  * 
62  * > dataArray = [34,12,43,55,77];
63  * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}});
64  * 
65  * For more inforrmation, see <jqPlot Usage>.
66  * 
67  * About: Usage
68  * 
69  * See <jqPlot Usage>
70  * 
71  * About: Available Options 
72  * 
73  * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!)
74  * 
75  * About: Options Usage
76  * 
77  * See <Options Tutorial>
78  * 
79  * About: Changes
80  * 
81  * See <Change Log>
82  * 
83  */
84
85 (function($) {
86     // make sure undefined is undefined
87     var undefined;
88     
89     $.fn.emptyForce = function() {
90       for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) {
91         // Remove element nodes and prevent memory leaks
92         if ( elem.nodeType === 1 ) {
93           $.cleanData( elem.getElementsByTagName("*") );
94         }
95   
96         // Remove any remaining nodes
97         if ($.jqplot.use_excanvas) {
98           elem.outerHTML = "";
99         }
100         else {
101           while ( elem.firstChild ) {
102             elem.removeChild( elem.firstChild );
103           }
104         }
105
106         elem = null;
107       }
108   
109       return $(this);
110     };
111   
112     $.fn.removeChildForce = function(parent) {
113       while ( parent.firstChild ) {
114         this.removeChildForce( parent.firstChild );
115         parent.removeChild( parent.firstChild );
116       }
117     };
118
119     $.fn.jqplot = function() {
120         var datas = [];
121         var options = [];
122         // see how many data arrays we have
123         for (var i=0, l=arguments.length; i<l; i++) {
124             if ($.isArray(arguments[i])) {
125                 datas.push(arguments[i]);
126             }
127             else if ($.isPlainObject(arguments[i])) {
128                 options.push(arguments[i]);
129             }
130         }
131
132         return this.each(function(index) {
133             var tid, 
134                 plot, 
135                 $this = $(this),
136                 dl = datas.length,
137                 ol = options.length,
138                 data, 
139                 opts;
140
141             if (index < dl) {
142                 data = datas[index];
143             }
144             else {
145                 data = dl ? datas[dl-1] : null;
146             }
147
148             if (index < ol) {
149                 opts = options[index];
150             }
151             else {
152                 opts = ol ? options[ol-1] : null;
153             }
154
155             // does el have an id?
156             // if not assign it one.
157             tid = $this.attr('id');
158             if (tid === undefined) {
159                 tid = 'jqplot_target_' + $.jqplot.targetCounter++;
160                 $this.attr('id', tid);
161             }
162
163             plot = $.jqplot(tid, data, opts);
164
165             $this.data('jqplot', plot);
166         });
167     };
168
169
170     /**
171      * Namespace: $.jqplot
172      * jQuery function called by the user to create a plot.
173      *  
174      * Parameters:
175      * target - ID of target element to render the plot into.
176      * data - an array of data series.
177      * options - user defined options object.  See the individual classes for available options.
178      * 
179      * Properties:
180      * config - object to hold configuration information for jqPlot plot object.
181      * 
182      * attributes:
183      * enablePlugins - False to disable plugins by default.  Plugins must then be explicitly 
184      *   enabled in the individual plot options.  Default: false.
185      *   This property sets the "show" property of certain plugins to true or false.
186      *   Only plugins that can be immediately active upon loading are affected.  This includes
187      *   non-renderer plugins like cursor, dragable, highlighter, and trendline.
188      * defaultHeight - Default height for plots where no css height specification exists.  This
189      *   is a jqplot wide default.
190      * defaultWidth - Default height for plots where no css height specification exists.  This
191      *   is a jqplot wide default.
192      */
193
194     $.jqplot = function(target, data, options) {
195         var _data = null, _options = null;
196
197         if (arguments.length === 3) {
198             _data = data;
199             _options = options;
200         }
201
202         else if (arguments.length === 2) {
203             if ($.isArray(data)) {
204                 _data = data;
205             }
206
207             else if ($.isPlainObject(data)) {
208                 _options = data;
209             }
210         }
211
212         if (_data === null && _options !== null && _options.data) {
213             _data = _options.data;
214         }
215
216         var plot = new jqPlot();
217         // remove any error class that may be stuck on target.
218         $('#'+target).removeClass('jqplot-error');
219         
220         if ($.jqplot.config.catchErrors) {
221             try {
222                 plot.init(target, _data, _options);
223                 plot.draw();
224                 plot.themeEngine.init.call(plot);
225                 return plot;
226             }
227             catch(e) {
228                 var msg = $.jqplot.config.errorMessage || e.message;
229                 $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>');
230                 $('#'+target).addClass('jqplot-error');
231                 document.getElementById(target).style.background = $.jqplot.config.errorBackground;
232                 document.getElementById(target).style.border = $.jqplot.config.errorBorder;
233                 document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily;
234                 document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize;
235                 document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle;
236                 document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight;
237             }
238         }
239         else {        
240             plot.init(target, _data, _options);
241             plot.draw();
242             plot.themeEngine.init.call(plot);
243             return plot;
244         }
245     };
246
247     $.jqplot.version = "1.0.8";
248     $.jqplot.revision = "1250";
249
250     $.jqplot.targetCounter = 1;
251
252     // canvas manager to reuse canvases on the plot.
253     // Should help solve problem of canvases not being freed and
254     // problem of waiting forever for firefox to decide to free memory.
255     $.jqplot.CanvasManager = function() {
256         // canvases are managed globally so that they can be reused
257         // across plots after they have been freed
258         if (typeof $.jqplot.CanvasManager.canvases == 'undefined') {
259             $.jqplot.CanvasManager.canvases = [];
260             $.jqplot.CanvasManager.free = [];
261         }
262         
263         var myCanvases = [];
264         
265         this.getCanvas = function() {
266             var canvas;
267             var makeNew = true;
268             
269             if (!$.jqplot.use_excanvas) {
270                 for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) {
271                     if ($.jqplot.CanvasManager.free[i] === true) {
272                         makeNew = false;
273                         canvas = $.jqplot.CanvasManager.canvases[i];
274                         // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse');
275                         $.jqplot.CanvasManager.free[i] = false;
276                         myCanvases.push(i);
277                         break;
278                     }
279                 }
280             }
281
282             if (makeNew) {
283                 canvas = document.createElement('canvas');
284                 myCanvases.push($.jqplot.CanvasManager.canvases.length);
285                 $.jqplot.CanvasManager.canvases.push(canvas);
286                 $.jqplot.CanvasManager.free.push(false);
287             }   
288             
289             return canvas;
290         };
291         
292         // this method has to be used after settings the dimesions
293         // on the element returned by getCanvas()
294         this.initCanvas = function(canvas) {
295             if ($.jqplot.use_excanvas) {
296                 return window.G_vmlCanvasManager.initElement(canvas);
297             }
298             return canvas;
299         };
300
301         this.freeAllCanvases = function() {
302             for (var i = 0, l=myCanvases.length; i < l; i++) {
303                 this.freeCanvas(myCanvases[i]);
304             }
305             myCanvases = [];
306         };
307
308         this.freeCanvas = function(idx) {
309             if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
310                 // excanvas can't be reused, but properly unset
311                 window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]);
312                 $.jqplot.CanvasManager.canvases[idx] = null;
313             } 
314             else {
315                 var canvas = $.jqplot.CanvasManager.canvases[idx];
316                 canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
317                 $(canvas).unbind().removeAttr('class').removeAttr('style');
318                 // Style attributes seemed to be still hanging around.  wierd.  Some ticks
319                 // still retained a left: 0px attribute after reusing a canvas.
320                 $(canvas).css({left: '', top: '', position: ''});
321                 // setting size to 0 may save memory of unused canvases?
322                 canvas.width = 0;
323                 canvas.height = 0;
324                 $.jqplot.CanvasManager.free[idx] = true;
325             }
326         };
327         
328     };
329
330             
331     // Convienence function that won't hang IE or FF without FireBug.
332     $.jqplot.log = function() {
333         if (window.console) {
334             window.console.log.apply(window.console, arguments);
335         }
336     };
337         
338     $.jqplot.config = {
339         addDomReference: false,
340         enablePlugins:false,
341         defaultHeight:300,
342         defaultWidth:400,
343         UTCAdjust:false,
344         timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000),
345         errorMessage: '',
346         errorBackground: '',
347         errorBorder: '',
348         errorFontFamily: '',
349         errorFontSize: '',
350         errorFontStyle: '',
351         errorFontWeight: '',
352         catchErrors: false,
353         defaultTickFormatString: "%.1f",
354         defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
355         defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"],
356         dashLength: 4,
357         gapLength: 4,
358         dotGapLength: 2.5,
359         srcLocation: 'jqplot/src/',
360         pluginLocation: 'jqplot/src/plugins/'
361     };
362     
363     
364     $.jqplot.arrayMax = function( array ){
365         return Math.max.apply( Math, array );
366     };
367     
368     $.jqplot.arrayMin = function( array ){
369         return Math.min.apply( Math, array );
370     };
371     
372     $.jqplot.enablePlugins = $.jqplot.config.enablePlugins;
373     
374     // canvas related tests taken from modernizer:
375     // Copyright (c) 2009 - 2010 Faruk Ates.
376     // http://www.modernizr.com
377     
378     $.jqplot.support_canvas = function() {
379         if (typeof $.jqplot.support_canvas.result == 'undefined') {
380             $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext; 
381         }
382         return $.jqplot.support_canvas.result;
383     };
384             
385     $.jqplot.support_canvas_text = function() {
386         if (typeof $.jqplot.support_canvas_text.result == 'undefined') {
387             if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) {
388                 $.jqplot.support_canvas_text.result = true;
389             }
390             else {
391                 $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function');
392             }
393              
394         }
395         return $.jqplot.support_canvas_text.result;
396     };
397     
398     $.jqplot.use_excanvas = ((!$.support.boxModel || !$.support.objectAll || !$support.leadingWhitespace) && !$.jqplot.support_canvas()) ? true : false;
399     
400     /**
401      * 
402      * Hooks: jqPlot Pugin Hooks
403      * 
404      * $.jqplot.preInitHooks - called before initialization.
405      * $.jqplot.postInitHooks - called after initialization.
406      * $.jqplot.preParseOptionsHooks - called before user options are parsed.
407      * $.jqplot.postParseOptionsHooks - called after user options are parsed.
408      * $.jqplot.preDrawHooks - called before plot draw.
409      * $.jqplot.postDrawHooks - called after plot draw.
410      * $.jqplot.preDrawSeriesHooks - called before each series is drawn.
411      * $.jqplot.postDrawSeriesHooks - called after each series is drawn.
412      * $.jqplot.preDrawLegendHooks - called before the legend is drawn.
413      * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins
414      *     can add rows to the legend table.
415      * $.jqplot.preSeriesInitHooks - called before series is initialized.
416      * $.jqplot.postSeriesInitHooks - called after series is initialized.
417      * $.jqplot.preParseSeriesOptionsHooks - called before series related options
418      *     are parsed.
419      * $.jqplot.postParseSeriesOptionsHooks - called after series related options
420      *     are parsed.
421      * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds
422      *     listeners to the event canvas which lays on top of the grid area.
423      * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn.
424      * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn.
425      * 
426      */
427     
428     $.jqplot.preInitHooks = [];
429     $.jqplot.postInitHooks = [];
430     $.jqplot.preParseOptionsHooks = [];
431     $.jqplot.postParseOptionsHooks = [];
432     $.jqplot.preDrawHooks = [];
433     $.jqplot.postDrawHooks = [];
434     $.jqplot.preDrawSeriesHooks = [];
435     $.jqplot.postDrawSeriesHooks = [];
436     $.jqplot.preDrawLegendHooks = [];
437     $.jqplot.addLegendRowHooks = [];
438     $.jqplot.preSeriesInitHooks = [];
439     $.jqplot.postSeriesInitHooks = [];
440     $.jqplot.preParseSeriesOptionsHooks = [];
441     $.jqplot.postParseSeriesOptionsHooks = [];
442     $.jqplot.eventListenerHooks = [];
443     $.jqplot.preDrawSeriesShadowHooks = [];
444     $.jqplot.postDrawSeriesShadowHooks = [];
445
446     // A superclass holding some common properties and methods.
447     $.jqplot.ElemContainer = function() {
448         this._elem;
449         this._plotWidth;
450         this._plotHeight;
451         this._plotDimensions = {height:null, width:null};
452     };
453     
454     $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) {
455         this._offsets = offsets;
456         var klass = clss || 'jqplot';
457         var elem = document.createElement(el);
458         this._elem = $(elem);
459         this._elem.addClass(klass);
460         this._elem.css(cssopts);
461         this._elem.attr(attrib);
462         // avoid memory leak;
463         elem = null;
464         return this._elem;
465     };
466     
467     $.jqplot.ElemContainer.prototype.getWidth = function() {
468         if (this._elem) {
469             return this._elem.outerWidth(true);
470         }
471         else {
472             return null;
473         }
474     };
475     
476     $.jqplot.ElemContainer.prototype.getHeight = function() {
477         if (this._elem) {
478             return this._elem.outerHeight(true);
479         }
480         else {
481             return null;
482         }
483     };
484     
485     $.jqplot.ElemContainer.prototype.getPosition = function() {
486         if (this._elem) {
487             return this._elem.position();
488         }
489         else {
490             return {top:null, left:null, bottom:null, right:null};
491         }
492     };
493     
494     $.jqplot.ElemContainer.prototype.getTop = function() {
495         return this.getPosition().top;
496     };
497     
498     $.jqplot.ElemContainer.prototype.getLeft = function() {
499         return this.getPosition().left;
500     };
501     
502     $.jqplot.ElemContainer.prototype.getBottom = function() {
503         return this._elem.css('bottom');
504     };
505     
506     $.jqplot.ElemContainer.prototype.getRight = function() {
507         return this._elem.css('right');
508     };
509     
510
511     /**
512      * Class: Axis
513      * An individual axis object.  Cannot be instantiated directly, but created
514      * by the Plot object.  Axis properties can be set or overridden by the 
515      * options passed in from the user.
516      * 
517      */
518     function Axis(name) {
519         $.jqplot.ElemContainer.call(this);
520         // Group: Properties
521         //
522         // Axes options are specified within an axes object at the top level of the 
523         // plot options like so:
524         // > {
525         // >    axes: {
526         // >        xaxis: {min: 5},
527         // >        yaxis: {min: 2, max: 8, numberTicks:4},
528         // >        x2axis: {pad: 1.5},
529         // >        y2axis: {ticks:[22, 44, 66, 88]}
530         // >        }
531         // > }
532         // There are 2 x axes, 'xaxis' and 'x2axis', and 
533         // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ...  Any or all of which may be specified.
534         this.name = name;
535         this._series = [];
536         // prop: show
537         // Wether to display the axis on the graph.
538         this.show = false;
539         // prop: tickRenderer
540         // A class of a rendering engine for creating the ticks labels displayed on the plot, 
541         // See <$.jqplot.AxisTickRenderer>.
542         this.tickRenderer = $.jqplot.AxisTickRenderer;
543         // prop: tickOptions
544         // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options.
545         this.tickOptions = {};
546         // prop: labelRenderer
547         // A class of a rendering engine for creating an axis label.
548         this.labelRenderer = $.jqplot.AxisLabelRenderer;
549         // prop: labelOptions
550         // Options passed to the label renderer.
551         this.labelOptions = {};
552         // prop: label
553         // Label for the axis
554         this.label = null;
555         // prop: showLabel
556         // true to show the axis label.
557         this.showLabel = true;
558         // prop: min
559         // minimum value of the axis (in data units, not pixels).
560         this.min = null;
561         // prop: max
562         // maximum value of the axis (in data units, not pixels).
563         this.max = null;
564         // prop: autoscale
565         // DEPRECATED
566         // the default scaling algorithm produces superior results.
567         this.autoscale = false;
568         // prop: pad
569         // Padding to extend the range above and below the data bounds.
570         // The data range is multiplied by this factor to determine minimum and maximum axis bounds.
571         // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0.
572         this.pad = 1.2;
573         // prop: padMax
574         // Padding to extend the range above data bounds.
575         // The top of the data range is multiplied by this factor to determine maximum axis bounds.
576         // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0.
577         this.padMax = null;
578         // prop: padMin
579         // Padding to extend the range below data bounds.
580         // The bottom of the data range is multiplied by this factor to determine minimum axis bounds.
581         // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0.
582         this.padMin = null;
583         // prop: ticks
584         // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis.
585         // If no label is specified, the value is formatted into an appropriate label.
586         this.ticks = [];
587         // prop: numberTicks
588         // Desired number of ticks.  Default is to compute automatically.
589         this.numberTicks;
590         // prop: tickInterval
591         // number of units between ticks.  Mutually exclusive with numberTicks.
592         this.tickInterval;
593         // prop: renderer
594         // A class of a rendering engine that handles tick generation, 
595         // scaling input data to pixel grid units and drawing the axis element.
596         this.renderer = $.jqplot.LinearAxisRenderer;
597         // prop: rendererOptions
598         // renderer specific options.  See <$.jqplot.LinearAxisRenderer> for options.
599         this.rendererOptions = {};
600         // prop: showTicks
601         // Wether to show the ticks (both marks and labels) or not.
602         // Will not override showMark and showLabel options if specified on the ticks themselves.
603         this.showTicks = true;
604         // prop: showTickMarks
605         // Wether to show the tick marks (line crossing grid) or not.
606         // Overridden by showTicks and showMark option of tick itself.
607         this.showTickMarks = true;
608         // prop: showMinorTicks
609         // Wether or not to show minor ticks.  This is renderer dependent.
610         this.showMinorTicks = true;
611         // prop: drawMajorGridlines
612         // True to draw gridlines for major axis ticks.
613         this.drawMajorGridlines = true;
614         // prop: drawMinorGridlines
615         // True to draw gridlines for minor ticks.
616         this.drawMinorGridlines = false;
617         // prop: drawMajorTickMarks
618         // True to draw tick marks for major axis ticks.
619         this.drawMajorTickMarks = true;
620         // prop: drawMinorTickMarks
621         // True to draw tick marks for minor ticks.  This is renderer dependent.
622         this.drawMinorTickMarks = true;
623         // prop: useSeriesColor
624         // Use the color of the first series associated with this axis for the
625         // tick marks and line bordering this axis.
626         this.useSeriesColor = false;
627         // prop: borderWidth
628         // width of line stroked at the border of the axis.  Defaults
629         // to the width of the grid boarder.
630         this.borderWidth = null;
631         // prop: borderColor
632         // color of the border adjacent to the axis.  Defaults to grid border color.
633         this.borderColor = null;
634         // prop: scaleToHiddenSeries
635         // True to include hidden series when computing axes bounds and scaling.
636         this.scaleToHiddenSeries = false;
637         // minimum and maximum values on the axis.
638         this._dataBounds = {min:null, max:null};
639         // statistics (min, max, mean) as well as actual data intervals for each series attached to axis.
640         // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis.
641         this._intervalStats = [];
642         // pixel position from the top left of the min value and max value on the axis.
643         this._offsets = {min:null, max:null};
644         this._ticks=[];
645         this._label = null;
646         // prop: syncTicks
647         // true to try and synchronize tick spacing across multiple axes so that ticks and
648         // grid lines line up.  This has an impact on autoscaling algorithm, however.
649         // In general, autoscaling an individual axis will work better if it does not
650         // have to sync ticks.
651         this.syncTicks = null;
652         // prop: tickSpacing
653         // Approximate pixel spacing between ticks on graph.  Used during autoscaling.
654         // This number will be an upper bound, actual spacing will be less.
655         this.tickSpacing = 75;
656         // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks
657         // so they can be restored if altered by plugins.
658         this._min = null;
659         this._max = null;
660         this._tickInterval = null;
661         this._numberTicks = null;
662         this.__ticks = null;
663         // hold original user options.
664         this._options = {};
665     }
666     
667     Axis.prototype = new $.jqplot.ElemContainer();
668     Axis.prototype.constructor = Axis;
669     
670     Axis.prototype.init = function() {
671         if ($.isFunction(this.renderer)) {
672             this.renderer = new this.renderer();  
673         }
674         // set the axis name
675         this.tickOptions.axis = this.name;
676         // if showMark or showLabel tick options not specified, use value of axis option.
677         // showTicks overrides showTickMarks.
678         if (this.tickOptions.showMark == null) {
679             this.tickOptions.showMark = this.showTicks;
680         }
681         if (this.tickOptions.showMark == null) {
682             this.tickOptions.showMark = this.showTickMarks;
683         }
684         if (this.tickOptions.showLabel == null) {
685             this.tickOptions.showLabel = this.showTicks;
686         }
687         
688         if (this.label == null || this.label == '') {
689             this.showLabel = false;
690         }
691         else {
692             this.labelOptions.label = this.label;
693         }
694         if (this.showLabel == false) {
695             this.labelOptions.show = false;
696         }
697         // set the default padMax, padMin if not specified
698         // special check, if no padding desired, padding
699         // should be set to 1.0
700         if (this.pad == 0) {
701             this.pad = 1.0;
702         }
703         if (this.padMax == 0) {
704             this.padMax = 1.0;
705         }
706         if (this.padMin == 0) {
707             this.padMin = 1.0;
708         }
709         if (this.padMax == null) {
710             this.padMax = (this.pad-1)/2 + 1;
711         }
712         if (this.padMin == null) {
713             this.padMin = (this.pad-1)/2 + 1;
714         }
715         // now that padMin and padMax are correctly set, reset pad in case user has supplied 
716         // padMin and/or padMax
717         this.pad = this.padMax + this.padMin - 1;
718         if (this.min != null || this.max != null) {
719             this.autoscale = false;
720         }
721         // if not set, sync ticks for y axes but not x by default.
722         if (this.syncTicks == null && this.name.indexOf('y') > -1) {
723             this.syncTicks = true;
724         }
725         else if (this.syncTicks == null){
726             this.syncTicks = false;
727         }
728         this.renderer.init.call(this, this.rendererOptions);
729         
730     };
731     
732     Axis.prototype.draw = function(ctx, plot) {
733         // Memory Leaks patch
734         if (this.__ticks) {
735           this.__ticks = null;
736         }
737
738         return this.renderer.draw.call(this, ctx, plot);
739         
740     };
741     
742     Axis.prototype.set = function() {
743         this.renderer.set.call(this);
744     };
745     
746     Axis.prototype.pack = function(pos, offsets) {
747         if (this.show) {
748             this.renderer.pack.call(this, pos, offsets);
749         }
750         // these properties should all be available now.
751         if (this._min == null) {
752             this._min = this.min;
753             this._max = this.max;
754             this._tickInterval = this.tickInterval;
755             this._numberTicks = this.numberTicks;
756             this.__ticks = this._ticks;
757         }
758     };
759     
760     // reset the axis back to original values if it has been scaled, zoomed, etc.
761     Axis.prototype.reset = function() {
762         this.renderer.reset.call(this);
763     };
764     
765     Axis.prototype.resetScale = function(opts) {
766         $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts);
767         this.resetDataBounds();
768     };
769     
770     Axis.prototype.resetDataBounds = function() {
771         // Go through all the series attached to this axis and find
772         // the min/max bounds for this axis.
773         var db = this._dataBounds;
774         db.min = null;
775         db.max = null;
776         var l, s, d;
777         // check for when to force min 0 on bar series plots.
778         var doforce = (this.show) ? true : false;
779         for (var i=0; i<this._series.length; i++) {
780             s = this._series[i];
781             if (s.show || this.scaleToHiddenSeries) {
782                 d = s._plotData;
783                 if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') {
784                     d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]];
785                 }
786
787                 var minyidx = 1, maxyidx = 1;
788
789                 if (s._type != null && s._type == 'ohlc') {
790                     minyidx = 3;
791                     maxyidx = 2;
792                 }
793                 
794                 for (var j=0, l=d.length; j<l; j++) { 
795                     if (this.name == 'xaxis' || this.name == 'x2axis') {
796                         if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
797                             db.min = d[j][0];
798                         }
799                         if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
800                             db.max = d[j][0];
801                         }
802                     }              
803                     else {
804                         if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) {
805                             db.min = d[j][minyidx];
806                         }
807                         if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) {
808                             db.max = d[j][maxyidx];
809                         }
810                     }              
811                 }
812
813                 // Hack to not pad out bottom of bar plots unless user has specified a padding.
814                 // every series will have a chance to set doforce to false.  once it is set to 
815                 // false, it cannot be reset to true.
816                 // If any series attached to axis is not a bar, wont force 0.
817                 if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) {
818                     doforce = false;
819                 }
820
821                 else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) {
822                     doforce = false;
823                 }
824
825                 else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) {
826                     if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') { 
827                         if (this._options.pad != null || this._options.padMin != null) {
828                             doforce = false;
829                         }
830                     }
831
832                     else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) {
833                         if (this._options.pad != null || this._options.padMin != null) {
834                             doforce = false;
835                         }
836                     }
837
838                 }
839             }
840         }
841
842         if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) {
843             this.padMin = 1.0;
844             this.forceTickAt0 = true;
845         }
846     };
847
848     /**
849      * Class: Legend
850      * Legend object.  Cannot be instantiated directly, but created
851      * by the Plot object.  Legend properties can be set or overridden by the 
852      * options passed in from the user.
853      */
854     function Legend(options) {
855         $.jqplot.ElemContainer.call(this);
856         // Group: Properties
857         
858         // prop: show
859         // Wether to display the legend on the graph.
860         this.show = false;
861         // prop: location
862         // Placement of the legend.  one of the compass directions: nw, n, ne, e, se, s, sw, w
863         this.location = 'ne';
864         // prop: labels
865         // Array of labels to use.  By default the renderer will look for labels on the series.
866         // Labels specified in this array will override labels specified on the series.
867         this.labels = [];
868         // prop: showLabels
869         // true to show the label text on the  legend.
870         this.showLabels = true;
871         // prop: showSwatch
872         // true to show the color swatches on the legend.
873         this.showSwatches = true;
874         // prop: placement
875         // "insideGrid" places legend inside the grid area of the plot.
876         // "outsideGrid" places the legend outside the grid but inside the plot container, 
877         // shrinking the grid to accomodate the legend.
878         // "inside" synonym for "insideGrid", 
879         // "outside" places the legend ouside the grid area, but does not shrink the grid which
880         // can cause the legend to overflow the plot container.
881         this.placement = "insideGrid";
882         // prop: xoffset
883         // DEPRECATED.  Set the margins on the legend using the marginTop, marginLeft, etc. 
884         // properties or via CSS margin styling of the .jqplot-table-legend class.
885         this.xoffset = 0;
886         // prop: yoffset
887         // DEPRECATED.  Set the margins on the legend using the marginTop, marginLeft, etc. 
888         // properties or via CSS margin styling of the .jqplot-table-legend class.
889         this.yoffset = 0;
890         // prop: border
891         // css spec for the border around the legend box.
892         this.border;
893         // prop: background
894         // css spec for the background of the legend box.
895         this.background;
896         // prop: textColor
897         // css color spec for the legend text.
898         this.textColor;
899         // prop: fontFamily
900         // css font-family spec for the legend text.
901         this.fontFamily; 
902         // prop: fontSize
903         // css font-size spec for the legend text.
904         this.fontSize ;
905         // prop: rowSpacing
906         // css padding-top spec for the rows in the legend.
907         this.rowSpacing = '0.5em';
908         // renderer
909         // A class that will create a DOM object for the legend,
910         // see <$.jqplot.TableLegendRenderer>.
911         this.renderer = $.jqplot.TableLegendRenderer;
912         // prop: rendererOptions
913         // renderer specific options passed to the renderer.
914         this.rendererOptions = {};
915         // prop: predraw
916         // Wether to draw the legend before the series or not.
917         // Used with series specific legend renderers for pie, donut, mekko charts, etc.
918         this.preDraw = false;
919         // prop: marginTop
920         // CSS margin for the legend DOM element. This will set an element 
921         // CSS style for the margin which will override any style sheet setting.
922         // The default will be taken from the stylesheet.
923         this.marginTop = null;
924         // prop: marginRight
925         // CSS margin for the legend DOM element. This will set an element 
926         // CSS style for the margin which will override any style sheet setting.
927         // The default will be taken from the stylesheet.
928         this.marginRight = null;
929         // prop: marginBottom
930         // CSS margin for the legend DOM element. This will set an element 
931         // CSS style for the margin which will override any style sheet setting.
932         // The default will be taken from the stylesheet.
933         this.marginBottom = null;
934         // prop: marginLeft
935         // CSS margin for the legend DOM element. This will set an element 
936         // CSS style for the margin which will override any style sheet setting.
937         // The default will be taken from the stylesheet.
938         this.marginLeft = null;
939         // prop: escapeHtml
940         // True to escape special characters with their html entity equivalents
941         // in legend text.  "<" becomes &lt; and so on, so html tags are not rendered.
942         this.escapeHtml = false;
943         this._series = [];
944         
945         $.extend(true, this, options);
946     }
947     
948     Legend.prototype = new $.jqplot.ElemContainer();
949     Legend.prototype.constructor = Legend;
950     
951     Legend.prototype.setOptions = function(options) {
952         $.extend(true, this, options);
953         
954         // Try to emulate deprecated behaviour
955         // if user has specified xoffset or yoffset, copy these to
956         // the margin properties.
957         
958         if (this.placement ==  'inside') {
959             this.placement = 'insideGrid';
960         }
961         
962         if (this.xoffset >0) {
963             if (this.placement == 'insideGrid') {
964                 switch (this.location) {
965                     case 'nw':
966                     case 'w':
967                     case 'sw':
968                         if (this.marginLeft == null) {
969                             this.marginLeft = this.xoffset + 'px';
970                         }
971                         this.marginRight = '0px';
972                         break;
973                     case 'ne':
974                     case 'e':
975                     case 'se':
976                     default:
977                         if (this.marginRight == null) {
978                             this.marginRight = this.xoffset + 'px';
979                         }
980                         this.marginLeft = '0px';
981                         break;
982                 }
983             }
984             else if (this.placement == 'outside') {
985                 switch (this.location) {
986                     case 'nw':
987                     case 'w':
988                     case 'sw':
989                         if (this.marginRight == null) {
990                             this.marginRight = this.xoffset + 'px';
991                         }
992                         this.marginLeft = '0px';
993                         break;
994                     case 'ne':
995                     case 'e':
996                     case 'se':
997                     default:
998                         if (this.marginLeft == null) {
999                             this.marginLeft = this.xoffset + 'px';
1000                         }
1001                         this.marginRight = '0px';
1002                         break;
1003                 }
1004             }
1005             this.xoffset = 0;
1006         }
1007         
1008         if (this.yoffset >0) {
1009             if (this.placement == 'outside') {
1010                 switch (this.location) {
1011                     case 'sw':
1012                     case 's':
1013                     case 'se':
1014                         if (this.marginTop == null) {
1015                             this.marginTop = this.yoffset + 'px';
1016                         }
1017                         this.marginBottom = '0px';
1018                         break;
1019                     case 'ne':
1020                     case 'n':
1021                     case 'nw':
1022                     default:
1023                         if (this.marginBottom == null) {
1024                             this.marginBottom = this.yoffset + 'px';
1025                         }
1026                         this.marginTop = '0px';
1027                         break;
1028                 }
1029             }
1030             else if (this.placement == 'insideGrid') {
1031                 switch (this.location) {
1032                     case 'sw':
1033                     case 's':
1034                     case 'se':
1035                         if (this.marginBottom == null) {
1036                             this.marginBottom = this.yoffset + 'px';
1037                         }
1038                         this.marginTop = '0px';
1039                         break;
1040                     case 'ne':
1041                     case 'n':
1042                     case 'nw':
1043                     default:
1044                         if (this.marginTop == null) {
1045                             this.marginTop = this.yoffset + 'px';
1046                         }
1047                         this.marginBottom = '0px';
1048                         break;
1049                 }
1050             }
1051             this.yoffset = 0;
1052         }
1053         
1054         // TO-DO:
1055         // Handle case where offsets are < 0.
1056         //
1057     };
1058     
1059     Legend.prototype.init = function() {
1060         if ($.isFunction(this.renderer)) {
1061             this.renderer = new this.renderer();  
1062         }
1063         this.renderer.init.call(this, this.rendererOptions);
1064     };
1065     
1066     Legend.prototype.draw = function(offsets, plot) {
1067         for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){
1068             $.jqplot.preDrawLegendHooks[i].call(this, offsets);
1069         }
1070         return this.renderer.draw.call(this, offsets, plot);
1071     };
1072     
1073     Legend.prototype.pack = function(offsets) {
1074         this.renderer.pack.call(this, offsets);
1075     };
1076
1077     /**
1078      * Class: Title
1079      * Plot Title object.  Cannot be instantiated directly, but created
1080      * by the Plot object.  Title properties can be set or overridden by the 
1081      * options passed in from the user.
1082      * 
1083      * Parameters:
1084      * text - text of the title.
1085      */
1086     function Title(text) {
1087         $.jqplot.ElemContainer.call(this);
1088         // Group: Properties
1089         
1090         // prop: text
1091         // text of the title;
1092         this.text = text;
1093         // prop: show
1094         // whether or not to show the title
1095         this.show = true;
1096         // prop: fontFamily
1097         // css font-family spec for the text.
1098         this.fontFamily;
1099         // prop: fontSize
1100         // css font-size spec for the text.
1101         this.fontSize ;
1102         // prop: textAlign
1103         // css text-align spec for the text.
1104         this.textAlign;
1105         // prop: textColor
1106         // css color spec for the text.
1107         this.textColor;
1108         // prop: renderer
1109         // A class for creating a DOM element for the title,
1110         // see <$.jqplot.DivTitleRenderer>.
1111         this.renderer = $.jqplot.DivTitleRenderer;
1112         // prop: rendererOptions
1113         // renderer specific options passed to the renderer.
1114         this.rendererOptions = {};   
1115         // prop: escapeHtml
1116         // True to escape special characters with their html entity equivalents
1117         // in title text.  "<" becomes &lt; and so on, so html tags are not rendered.
1118         this.escapeHtml = false;
1119     }
1120     
1121     Title.prototype = new $.jqplot.ElemContainer();
1122     Title.prototype.constructor = Title;
1123     
1124     Title.prototype.init = function() {
1125         if ($.isFunction(this.renderer)) {
1126             this.renderer = new this.renderer();  
1127         }
1128         this.renderer.init.call(this, this.rendererOptions);
1129     };
1130     
1131     Title.prototype.draw = function(width) {
1132         return this.renderer.draw.call(this, width);
1133     };
1134     
1135     Title.prototype.pack = function() {
1136         this.renderer.pack.call(this);
1137     };
1138
1139
1140     /**
1141      * Class: Series
1142      * An individual data series object.  Cannot be instantiated directly, but created
1143      * by the Plot object.  Series properties can be set or overridden by the 
1144      * options passed in from the user.
1145      */
1146     function Series(options) {
1147         options = options || {};
1148         $.jqplot.ElemContainer.call(this);
1149         // Group: Properties
1150         // Properties will be assigned from a series array at the top level of the
1151         // options.  If you had two series and wanted to change the color and line
1152         // width of the first and set the second to use the secondary y axis with
1153         // no shadow and supply custom labels for each:
1154         // > {
1155         // >    series:[
1156         // >        {color: '#ff4466', lineWidth: 5, label:'good line'},
1157         // >        {yaxis: 'y2axis', shadow: false, label:'bad line'}
1158         // >    ]
1159         // > }
1160
1161         // prop: show
1162         // whether or not to draw the series.
1163         this.show = true;
1164         // prop: xaxis
1165         // which x axis to use with this series, either 'xaxis' or 'x2axis'.
1166         this.xaxis = 'xaxis';
1167         this._xaxis;
1168         // prop: yaxis
1169         // which y axis to use with this series, either 'yaxis' or 'y2axis'.
1170         this.yaxis = 'yaxis';
1171         this._yaxis;
1172         this.gridBorderWidth = 2.0;
1173         // prop: renderer
1174         // A class of a renderer which will draw the series, 
1175         // see <$.jqplot.LineRenderer>.
1176         this.renderer = $.jqplot.LineRenderer;
1177         // prop: rendererOptions
1178         // Options to pass on to the renderer.
1179         this.rendererOptions = {};
1180         this.data = [];
1181         this.gridData = [];
1182         // prop: label
1183         // Line label to use in the legend.
1184         this.label = '';
1185         // prop: showLabel
1186         // true to show label for this series in the legend.
1187         this.showLabel = true;
1188         // prop: color
1189         // css color spec for the series
1190         this.color;
1191         // prop: negativeColor
1192         // css color spec used for filled (area) plots that are filled to zero and
1193         // the "useNegativeColors" option is true.
1194         this.negativeColor;
1195         // prop: lineWidth
1196         // width of the line in pixels.  May have different meanings depending on renderer.
1197         this.lineWidth = 2.5;
1198         // prop: lineJoin
1199         // Canvas lineJoin style between segments of series.
1200         this.lineJoin = 'round';
1201         // prop: lineCap
1202         // Canvas lineCap style at ends of line.
1203         this.lineCap = 'round';
1204         // prop: linePattern
1205         // line pattern 'dashed', 'dotted', 'solid', some combination
1206         // of '-' and '.' characters such as '.-.' or a numerical array like 
1207         // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, 
1208         // [1, 10, 20, 10] to draw a dot-dash line, and so on.
1209         this.linePattern = 'solid';
1210         this.shadow = true;
1211         // prop: shadowAngle
1212         // Shadow angle in degrees
1213         this.shadowAngle = 45;
1214         // prop: shadowOffset
1215         // Shadow offset from line in pixels
1216         this.shadowOffset = 1.25;
1217         // prop: shadowDepth
1218         // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
1219         this.shadowDepth = 3;
1220         // prop: shadowAlpha
1221         // Alpha channel transparency of shadow.  0 = transparent.
1222         this.shadowAlpha = '0.1';
1223         // prop: breakOnNull
1224         // Wether line segments should be be broken at null value.
1225         // False will join point on either side of line.
1226         this.breakOnNull = false;
1227         // prop: markerRenderer
1228         // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points,
1229         // see <$.jqplot.MarkerRenderer>.
1230         this.markerRenderer = $.jqplot.MarkerRenderer;
1231         // prop: markerOptions
1232         // renderer specific options to pass to the markerRenderer,
1233         // see <$.jqplot.MarkerRenderer>.
1234         this.markerOptions = {};
1235         // prop: showLine
1236         // whether to actually draw the line or not.  Series will still be renderered, even if no line is drawn.
1237         this.showLine = true;
1238         // prop: showMarker
1239         // whether or not to show the markers at the data points.
1240         this.showMarker = true;
1241         // prop: index
1242         // 0 based index of this series in the plot series array.
1243         this.index;
1244         // prop: fill
1245         // true or false, whether to fill under lines or in bars.
1246         // May not be implemented in all renderers.
1247         this.fill = false;
1248         // prop: fillColor
1249         // CSS color spec to use for fill under line.  Defaults to line color.
1250         this.fillColor;
1251         // prop: fillAlpha
1252         // Alpha transparency to apply to the fill under the line.
1253         // Use this to adjust alpha separate from fill color.
1254         this.fillAlpha;
1255         // prop: fillAndStroke
1256         // If true will stroke the line (with color this.color) as well as fill under it.
1257         // Applies only when fill is true.
1258         this.fillAndStroke = false;
1259         // prop: disableStack
1260         // true to not stack this series with other series in the plot.
1261         // To render properly, non-stacked series must come after any stacked series
1262         // in the plot's data series array.  So, the plot's data series array would look like:
1263         // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...]
1264         // disableStack will put a gap in the stacking order of series, and subsequent
1265         // stacked series will not fill down through the non-stacked series and will
1266         // most likely not stack properly on top of the non-stacked series.
1267         this.disableStack = false;
1268         // _stack is set by the Plot if the plot is a stacked chart.
1269         // will stack lines or bars on top of one another to build a "mountain" style chart.
1270         // May not be implemented in all renderers.
1271         this._stack = false;
1272         // prop: neighborThreshold
1273         // how close or far (in pixels) the cursor must be from a point marker to detect the point.
1274         this.neighborThreshold = 4;
1275         // prop: fillToZero
1276         // true will force bar and filled series to fill toward zero on the fill Axis.
1277         this.fillToZero = false;
1278         // prop: fillToValue
1279         // fill a filled series to this value on the fill axis.
1280         // Works in conjunction with fillToZero, so that must be true.
1281         this.fillToValue = 0;
1282         // prop: fillAxis
1283         // Either 'x' or 'y'.  Which axis to fill the line toward if fillToZero is true.
1284         // 'y' means fill up/down to 0 on the y axis for this series.
1285         this.fillAxis = 'y';
1286         // prop: useNegativeColors
1287         // true to color negative values differently in filled and bar charts.
1288         this.useNegativeColors = true;
1289         this._stackData = [];
1290         // _plotData accounts for stacking.  If plots not stacked, _plotData and data are same.  If
1291         // stacked, _plotData is accumulation of stacking data.
1292         this._plotData = [];
1293         // _plotValues hold the individual x and y values that will be plotted for this series.
1294         this._plotValues = {x:[], y:[]};
1295         // statistics about the intervals between data points.  Used for auto scaling.
1296         this._intervals = {x:{}, y:{}};
1297         // data from the previous series, for stacked charts.
1298         this._prevPlotData = [];
1299         this._prevGridData = [];
1300         this._stackAxis = 'y';
1301         this._primaryAxis = '_xaxis';
1302         // give each series a canvas to draw on.  This should allow for redrawing speedups.
1303         this.canvas = new $.jqplot.GenericCanvas();
1304         this.shadowCanvas = new $.jqplot.GenericCanvas();
1305         this.plugins = {};
1306         // sum of y values in this series.
1307         this._sumy = 0;
1308         this._sumx = 0;
1309         this._type = '';
1310     }
1311     
1312     Series.prototype = new $.jqplot.ElemContainer();
1313     Series.prototype.constructor = Series;
1314     
1315     Series.prototype.init = function(index, gridbw, plot) {
1316         // weed out any null values in the data.
1317         this.index = index;
1318         this.gridBorderWidth = gridbw;
1319         var d = this.data;
1320         var temp = [], i, l;
1321         for (i=0, l=d.length; i<l; i++) {
1322             if (! this.breakOnNull) {
1323                 if (d[i] == null || d[i][0] == null || d[i][1] == null) {
1324                     continue;
1325                 }
1326                 else {
1327                     temp.push(d[i]);
1328                 }
1329             }
1330             else {
1331                 // TODO: figure out what to do with null values
1332                 // probably involve keeping nulls in data array
1333                 // and then updating renderers to break line
1334                 // when it hits null value.
1335                 // For now, just keep value.
1336                 temp.push(d[i]);
1337             }
1338         }
1339         this.data = temp;
1340
1341         // parse the renderer options and apply default colors if not provided
1342         // Set color even if not shown, so series don't change colors when other
1343         // series on plot shown/hidden.
1344         if (!this.color) {
1345             this.color = plot.colorGenerator.get(this.index);
1346         }
1347         if (!this.negativeColor) {
1348             this.negativeColor = plot.negativeColorGenerator.get(this.index);
1349         }
1350
1351
1352         if (!this.fillColor) {
1353             this.fillColor = this.color;
1354         }
1355         if (this.fillAlpha) {
1356             var comp = $.jqplot.normalize2rgb(this.fillColor);
1357             var comp = $.jqplot.getColorComponents(comp);
1358             this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')';
1359         }
1360         if ($.isFunction(this.renderer)) {
1361             this.renderer = new this.renderer();  
1362         }
1363         this.renderer.init.call(this, this.rendererOptions, plot);
1364         this.markerRenderer = new this.markerRenderer();
1365         if (!this.markerOptions.color) {
1366             this.markerOptions.color = this.color;
1367         }
1368         if (this.markerOptions.show == null) {
1369             this.markerOptions.show = this.showMarker;
1370         }
1371         this.showMarker = this.markerOptions.show;
1372         // the markerRenderer is called within its own scope, don't want to overwrite series options!!
1373         this.markerRenderer.init(this.markerOptions);
1374     };
1375     
1376     // data - optional data point array to draw using this series renderer
1377     // gridData - optional grid data point array to draw using this series renderer
1378     // stackData - array of cumulative data for stacked plots.
1379     Series.prototype.draw = function(sctx, opts, plot) {
1380         var options = (opts == undefined) ? {} : opts;
1381         sctx = (sctx == undefined) ? this.canvas._ctx : sctx;
1382         
1383         var j, data, gridData;
1384         
1385         // hooks get called even if series not shown
1386         // we don't clear canvas here, it would wipe out all other series as well.
1387         for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) {
1388             $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options);
1389         }
1390         if (this.show) {
1391             this.renderer.setGridData.call(this, plot);
1392             if (!options.preventJqPlotSeriesDrawTrigger) {
1393                 $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]);
1394             }
1395             data = [];
1396             if (options.data) {
1397                 data = options.data;
1398             }
1399             else if (!this._stack) {
1400                 data = this.data;
1401             }
1402             else {
1403                 data = this._plotData;
1404             }
1405             gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
1406
1407             if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) {
1408                 gridData = this.renderer._smoothedData;
1409             }
1410
1411             this.renderer.draw.call(this, sctx, gridData, options, plot);
1412         }
1413         
1414         for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
1415             $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options, plot);
1416         }
1417         
1418         sctx = opts = plot = j = data = gridData = null;
1419     };
1420     
1421     Series.prototype.drawShadow = function(sctx, opts, plot) {
1422         var options = (opts == undefined) ? {} : opts;
1423         sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx;
1424         
1425         var j, data, gridData;
1426         
1427         // hooks get called even if series not shown
1428         // we don't clear canvas here, it would wipe out all other series as well.
1429         for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) {
1430             $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options);
1431         }
1432         if (this.shadow) {
1433             this.renderer.setGridData.call(this, plot);
1434
1435             data = [];
1436             if (options.data) {
1437                 data = options.data;
1438             }
1439             else if (!this._stack) {
1440                 data = this.data;
1441             }
1442             else {
1443                 data = this._plotData;
1444             }
1445             gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
1446         
1447             this.renderer.drawShadow.call(this, sctx, gridData, options, plot);
1448         }
1449         
1450         for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) {
1451             $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options);
1452         }
1453         
1454         sctx = opts = plot = j = data = gridData = null;
1455         
1456     };
1457     
1458     // toggles series display on plot, e.g. show/hide series
1459     Series.prototype.toggleDisplay = function(ev, callback) {
1460         var s, speed;
1461         if (ev.data.series) {
1462             s = ev.data.series;
1463         }
1464         else {
1465             s = this;
1466         }
1467
1468         if (ev.data.speed) {
1469             speed = ev.data.speed;
1470         }
1471         if (speed) {
1472             // this can be tricky because series may not have a canvas element if replotting.
1473             if (s.canvas._elem.is(':hidden') || !s.show) {
1474                 s.show = true;
1475
1476                 s.canvas._elem.removeClass('jqplot-series-hidden');
1477                 if (s.shadowCanvas._elem) {
1478                     s.shadowCanvas._elem.fadeIn(speed);
1479                 }
1480                 s.canvas._elem.fadeIn(speed, callback);
1481                 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed);
1482             }
1483             else {
1484                 s.show = false;
1485
1486                 s.canvas._elem.addClass('jqplot-series-hidden');
1487                 if (s.shadowCanvas._elem) {
1488                     s.shadowCanvas._elem.fadeOut(speed);
1489                 }
1490                 s.canvas._elem.fadeOut(speed, callback);
1491                 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed);
1492             }
1493         }
1494         else {
1495             // this can be tricky because series may not have a canvas element if replotting.
1496             if (s.canvas._elem.is(':hidden') || !s.show) {
1497                 s.show = true;
1498
1499                 s.canvas._elem.removeClass('jqplot-series-hidden');
1500                 if (s.shadowCanvas._elem) {
1501                     s.shadowCanvas._elem.show();
1502                 }
1503                 s.canvas._elem.show(0, callback);
1504                 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show();
1505             }
1506             else {
1507                 s.show = false;
1508
1509                 s.canvas._elem.addClass('jqplot-series-hidden');
1510                 if (s.shadowCanvas._elem) {
1511                     s.shadowCanvas._elem.hide();
1512                 }
1513                 s.canvas._elem.hide(0, callback);
1514                 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide();
1515             }
1516         }
1517     };
1518     
1519
1520
1521     /**
1522      * Class: Grid
1523      * 
1524      * Object representing the grid on which the plot is drawn.  The grid in this
1525      * context is the area bounded by the axes, the area which will contain the series.
1526      * Note, the series are drawn on their own canvas.
1527      * The Grid object cannot be instantiated directly, but is created by the Plot object.  
1528      * Grid properties can be set or overridden by the options passed in from the user.
1529      */
1530     function Grid() {
1531         $.jqplot.ElemContainer.call(this);
1532         // Group: Properties
1533         
1534         // prop: drawGridlines
1535         // whether to draw the gridlines on the plot.
1536         this.drawGridlines = true;
1537         // prop: gridLineColor
1538         // color of the grid lines.
1539         this.gridLineColor = '#cccccc';
1540         // prop: gridLineWidth
1541         // width of the grid lines.
1542         this.gridLineWidth = 1.0;
1543         // prop: background
1544         // css spec for the background color.
1545         this.background = '#fffdf6';
1546         // prop: borderColor
1547         // css spec for the color of the grid border.
1548         this.borderColor = '#999999';
1549         // prop: borderWidth
1550         // width of the border in pixels.
1551         this.borderWidth = 2.0;
1552         // prop: drawBorder
1553         // True to draw border around grid.
1554         this.drawBorder = true;
1555         // prop: shadow
1556         // whether to show a shadow behind the grid.
1557         this.shadow = true;
1558         // prop: shadowAngle
1559         // shadow angle in degrees
1560         this.shadowAngle = 45;
1561         // prop: shadowOffset
1562         // Offset of each shadow stroke from the border in pixels
1563         this.shadowOffset = 1.5;
1564         // prop: shadowWidth
1565         // width of the stoke for the shadow
1566         this.shadowWidth = 3;
1567         // prop: shadowDepth
1568         // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
1569         this.shadowDepth = 3;
1570         // prop: shadowColor
1571         // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form
1572         this.shadowColor = null;
1573         // prop: shadowAlpha
1574         // Alpha channel transparency of shadow.  0 = transparent.
1575         this.shadowAlpha = '0.07';
1576         this._left;
1577         this._top;
1578         this._right;
1579         this._bottom;
1580         this._width;
1581         this._height;
1582         this._axes = [];
1583         // prop: renderer
1584         // Instance of a renderer which will actually render the grid,
1585         // see <$.jqplot.CanvasGridRenderer>.
1586         this.renderer = $.jqplot.CanvasGridRenderer;
1587         // prop: rendererOptions
1588         // Options to pass on to the renderer,
1589         // see <$.jqplot.CanvasGridRenderer>.
1590         this.rendererOptions = {};
1591         this._offsets = {top:null, bottom:null, left:null, right:null};
1592     }
1593     
1594     Grid.prototype = new $.jqplot.ElemContainer();
1595     Grid.prototype.constructor = Grid;
1596     
1597     Grid.prototype.init = function() {
1598         if ($.isFunction(this.renderer)) {
1599             this.renderer = new this.renderer();  
1600         }
1601         this.renderer.init.call(this, this.rendererOptions);
1602     };
1603     
1604     Grid.prototype.createElement = function(offsets,plot) {
1605         this._offsets = offsets;
1606         return this.renderer.createElement.call(this, plot);
1607     };
1608     
1609     Grid.prototype.draw = function() {
1610         this.renderer.draw.call(this);
1611     };
1612     
1613     $.jqplot.GenericCanvas = function() {
1614         $.jqplot.ElemContainer.call(this);
1615         this._ctx;  
1616     };
1617     
1618     $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer();
1619     $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas;
1620     
1621     $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) {
1622         this._offsets = offsets;
1623         var klass = 'jqplot';
1624         if (clss != undefined) {
1625             klass = clss;
1626         }
1627         var elem;
1628
1629         elem = plot.canvasManager.getCanvas();
1630         
1631         // if new plotDimensions supplied, use them.
1632         if (plotDimensions != null) {
1633             this._plotDimensions = plotDimensions;
1634         }
1635         
1636         elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right;
1637         elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom;
1638         this._elem = $(elem);
1639         this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top });
1640         
1641         this._elem.addClass(klass);
1642         
1643         elem = plot.canvasManager.initCanvas(elem);
1644         
1645         elem = null;
1646         return this._elem;
1647     };
1648     
1649     $.jqplot.GenericCanvas.prototype.setContext = function() {
1650         this._ctx = this._elem.get(0).getContext("2d");
1651         return this._ctx;
1652     };
1653     
1654     // Memory Leaks patch
1655     $.jqplot.GenericCanvas.prototype.resetCanvas = function() {
1656       if (this._elem) {
1657         if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
1658            window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
1659         }
1660         
1661         //this._elem.remove();
1662         this._elem.emptyForce();
1663       }
1664       
1665       this._ctx = null;
1666     };
1667     
1668     $.jqplot.HooksManager = function () {
1669         this.hooks =[];
1670         this.args = [];
1671     };
1672     
1673     $.jqplot.HooksManager.prototype.addOnce = function(fn, args) {
1674         args = args || [];
1675         var havehook = false;
1676         for (var i=0, l=this.hooks.length; i<l; i++) {
1677             if (this.hooks[i] == fn) {
1678                 havehook = true;
1679             }
1680         }
1681         if (!havehook) {
1682             this.hooks.push(fn);
1683             this.args.push(args);
1684         }
1685     };
1686     
1687     $.jqplot.HooksManager.prototype.add = function(fn, args) {
1688         args = args || [];
1689         this.hooks.push(fn);
1690         this.args.push(args);
1691     };
1692     
1693     $.jqplot.EventListenerManager = function () {
1694         this.hooks =[];
1695     };
1696     
1697     $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) {
1698         var havehook = false, h, i;
1699         for (var i=0, l=this.hooks.length; i<l; i++) {
1700             h = this.hooks[i];
1701             if (h[0] == ev && h[1] == fn) {
1702                 havehook = true;
1703             }
1704         }
1705         if (!havehook) {
1706             this.hooks.push([ev, fn]);
1707         }
1708     };
1709     
1710     $.jqplot.EventListenerManager.prototype.add = function(ev, fn) {
1711         this.hooks.push([ev, fn]);
1712     };
1713
1714
1715     var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
1716
1717     /**
1718      * Class: jqPlot
1719      * Plot object returned by call to $.jqplot.  Handles parsing user options,
1720      * creating sub objects (Axes, legend, title, series) and rendering the plot.
1721      */
1722     function jqPlot() {
1723         // Group: Properties
1724         // These properties are specified at the top of the options object
1725         // like so:
1726         // > {
1727         // >     axesDefaults:{min:0},
1728         // >     series:[{color:'#6633dd'}],
1729         // >     title: 'A Plot'
1730         // > }
1731         //
1732
1733         // prop: animate
1734         // True to animate the series on initial plot draw (renderer dependent).
1735         // Actual animation functionality must be supported in the renderer.
1736         this.animate = false;
1737         // prop: animateReplot
1738         // True to animate series after a call to the replot() method.
1739         // Use with caution!  Replots can happen very frequently under
1740         // certain circumstances (e.g. resizing, dragging points) and
1741         // animation in these situations can cause problems.
1742         this.animateReplot = false;
1743         // prop: axes
1744         // up to 4 axes are supported, each with its own options, 
1745         // See <Axis> for axis specific options.
1746         this.axes = {xaxis: new Axis('xaxis'), yaxis: new Axis('yaxis'), x2axis: new Axis('x2axis'), y2axis: new Axis('y2axis'), y3axis: new Axis('y3axis'), y4axis: new Axis('y4axis'), y5axis: new Axis('y5axis'), y6axis: new Axis('y6axis'), y7axis: new Axis('y7axis'), y8axis: new Axis('y8axis'), y9axis: new Axis('y9axis'), yMidAxis: new Axis('yMidAxis')};
1747         this.baseCanvas = new $.jqplot.GenericCanvas();
1748         // true to intercept right click events and fire a 'jqplotRightClick' event.
1749         // this will also block the context menu.
1750         this.captureRightClick = false;
1751         // prop: data
1752         // user's data.  Data should *NOT* be specified in the options object,
1753         // but be passed in as the second argument to the $.jqplot() function.
1754         // The data property is described here soley for reference. 
1755         // The data should be in the form of an array of 2D or 1D arrays like
1756         // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ].
1757         this.data = [];
1758         // prop: dataRenderer
1759         // A callable which can be used to preprocess data passed into the plot.
1760         // Will be called with 3 arguments: the plot data, a reference to the plot,
1761         // and the value of dataRendererOptions.
1762         this.dataRenderer;
1763         // prop: dataRendererOptions
1764         // Options that will be passed to the dataRenderer.
1765         // Can be of any type.
1766         this.dataRendererOptions;
1767         this.defaults = {
1768             // prop: axesDefaults
1769             // default options that will be applied to all axes.
1770             // see <Axis> for axes options.
1771             axesDefaults: {},
1772             axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}},
1773             // prop: seriesDefaults
1774             // default options that will be applied to all series.
1775             // see <Series> for series options.
1776             seriesDefaults: {},
1777             series:[]
1778         };
1779         // prop: defaultAxisStart
1780         // 1-D data series are internally converted into 2-D [x,y] data point arrays
1781         // by jqPlot.  This is the default starting value for the missing x or y value.
1782         // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...])
1783         // starting at this value.
1784         this.defaultAxisStart = 1;
1785         // this.doCustomEventBinding = true;
1786         // prop: drawIfHidden
1787         // True to execute the draw method even if the plot target is hidden.
1788         // Generally, this should be false.  Most plot elements will not be sized/
1789         // positioned correclty if renderered into a hidden container.  To render into
1790         // a hidden container, call the replot method when the container is shown.
1791         this.drawIfHidden = false;
1792         this.eventCanvas = new $.jqplot.GenericCanvas();
1793         // prop: fillBetween
1794         // Fill between 2 line series in a plot.
1795         // Options object:
1796         // {
1797         //    series1: first index (0 based) of series in fill
1798         //    series2: second index (0 based) of series in fill
1799         //    color: color of fill [default fillColor of series1]
1800         //    baseSeries:  fill will be drawn below this series (0 based index)
1801         //    fill: false to turn off fill [default true].
1802         //  }
1803         this.fillBetween = {
1804             series1: null,
1805             series2: null,
1806             color: null,
1807             baseSeries: 0,
1808             fill: true
1809         };
1810         // prop; fontFamily
1811         // css spec for the font-family attribute.  Default for the entire plot.
1812         this.fontFamily;
1813         // prop: fontSize
1814         // css spec for the font-size attribute.  Default for the entire plot.
1815         this.fontSize;
1816         // prop: grid
1817         // See <Grid> for grid specific options.
1818         this.grid = new Grid();
1819         // prop: legend
1820         // see <$.jqplot.TableLegendRenderer>
1821         this.legend = new Legend();
1822         // prop: noDataIndicator
1823         // Options to set up a mock plot with a data loading indicator if no data is specified.
1824         this.noDataIndicator = {    
1825             show: false,
1826             indicator: 'Loading Data...',
1827             axes: {
1828                 xaxis: {
1829                     min: 0,
1830                     max: 10,
1831                     tickInterval: 2,
1832                     show: true
1833                 },
1834                 yaxis: {
1835                     min: 0,
1836                     max: 12,
1837                     tickInterval: 3,
1838                     show: true
1839                 }
1840             }
1841         };
1842         // prop: negativeSeriesColors 
1843         // colors to use for portions of the line below zero.
1844         this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors;
1845         // container to hold all of the merged options.  Convienence for plugins.
1846         this.options = {};
1847         this.previousSeriesStack = [];
1848         // Namespace to hold plugins.  Generally non-renderer plugins add themselves to here.
1849         this.plugins = {};
1850         // prop: series
1851         // Array of series object options.
1852         // see <Series> for series specific options.
1853         this.series = [];
1854         // array of series indices. Keep track of order
1855         // which series canvases are displayed, lowest
1856         // to highest, back to front.
1857         this.seriesStack = [];
1858         // prop: seriesColors
1859         // Ann array of CSS color specifications that will be applied, in order,
1860         // to the series in the plot.  Colors will wrap around so, if their
1861         // are more series than colors, colors will be reused starting at the
1862         // beginning.  For pie charts, this specifies the colors of the slices.
1863         this.seriesColors = $.jqplot.config.defaultColors;
1864         // prop: sortData
1865         // false to not sort the data passed in by the user.
1866         // Many bar, stacked and other graphs as well as many plugins depend on
1867         // having sorted data.
1868         this.sortData = true;
1869         // prop: stackSeries
1870         // true or false, creates a stack or "mountain" plot.
1871         // Not all series renderers may implement this option.
1872         this.stackSeries = false;
1873         // a shortcut for axis syncTicks options.  Not implemented yet.
1874         this.syncXTicks = true;
1875         // a shortcut for axis syncTicks options.  Not implemented yet.
1876         this.syncYTicks = true;
1877         // the jquery object for the dom target.
1878         this.target = null; 
1879         // The id of the dom element to render the plot into
1880         this.targetId = null;
1881         // prop textColor
1882         // css spec for the css color attribute.  Default for the entire plot.
1883         this.textColor;
1884         // prop: title
1885         // Title object.  See <Title> for specific options.  As a shortcut, you
1886         // can specify the title option as just a string like: title: 'My Plot'
1887         // and this will create a new title object with the specified text.
1888         this.title = new Title();
1889         // Count how many times the draw method has been called while the plot is visible.
1890         // Mostly used to test if plot has never been dran (=0), has been successfully drawn
1891         // into a visible container once (=1) or draw more than once into a visible container.
1892         // Can use this in tests to see if plot has been visibly drawn at least one time.
1893         // After plot has been visibly drawn once, it generally doesn't need redrawing if its
1894         // container is hidden and shown.
1895         this._drawCount = 0;
1896         // sum of y values for all series in plot.
1897         // used in mekko chart.
1898         this._sumy = 0;
1899         this._sumx = 0;
1900         // array to hold the cumulative stacked series data.
1901         // used to ajust the individual series data, which won't have access to other
1902         // series data.
1903         this._stackData = [];
1904         // array that holds the data to be plotted. This will be the series data
1905         // merged with the the appropriate data from _stackData according to the stackAxis.
1906         this._plotData = [];
1907         this._width = null;
1908         this._height = null; 
1909         this._plotDimensions = {height:null, width:null};
1910         this._gridPadding = {top:null, right:null, bottom:null, left:null};
1911         this._defaultGridPadding = {top:10, right:10, bottom:23, left:10};
1912
1913         this._addDomReference = $.jqplot.config.addDomReference;
1914
1915         this.preInitHooks = new $.jqplot.HooksManager();
1916         this.postInitHooks = new $.jqplot.HooksManager();
1917         this.preParseOptionsHooks = new $.jqplot.HooksManager();
1918         this.postParseOptionsHooks = new $.jqplot.HooksManager();
1919         this.preDrawHooks = new $.jqplot.HooksManager();
1920         this.postDrawHooks = new $.jqplot.HooksManager();
1921         this.preDrawSeriesHooks = new $.jqplot.HooksManager();
1922         this.postDrawSeriesHooks = new $.jqplot.HooksManager();
1923         this.preDrawLegendHooks = new $.jqplot.HooksManager();
1924         this.addLegendRowHooks = new $.jqplot.HooksManager();
1925         this.preSeriesInitHooks = new $.jqplot.HooksManager();
1926         this.postSeriesInitHooks = new $.jqplot.HooksManager();
1927         this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager();
1928         this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager();
1929         this.eventListenerHooks = new $.jqplot.EventListenerManager();
1930         this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager();
1931         this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager();
1932         
1933         this.colorGenerator = new $.jqplot.ColorGenerator();
1934         this.negativeColorGenerator = new $.jqplot.ColorGenerator();
1935
1936         this.canvasManager = new $.jqplot.CanvasManager();
1937
1938         this.themeEngine = new $.jqplot.ThemeEngine();
1939         
1940         var seriesColorsIndex = 0;
1941
1942         // Group: methods
1943         //
1944         // method: init
1945         // sets the plot target, checks data and applies user
1946         // options to plot.
1947         this.init = function(target, data, options) {
1948             options = options || {};
1949             for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
1950                 $.jqplot.preInitHooks[i].call(this, target, data, options);
1951             }
1952
1953             for (var i=0; i<this.preInitHooks.hooks.length; i++) {
1954                 this.preInitHooks.hooks[i].call(this, target, data, options);
1955             }
1956             
1957             this.targetId = '#'+target;
1958             this.target = $('#'+target);
1959
1960             //////
1961             // Add a reference to plot
1962             //////
1963             if (this._addDomReference) {
1964                 this.target.data('jqplot', this);
1965             }
1966             // remove any error class that may be stuck on target.
1967             this.target.removeClass('jqplot-error');
1968             if (!this.target.get(0)) {
1969                 throw new Error("No plot target specified");
1970             }
1971             
1972             // make sure the target is positioned by some means and set css
1973             if (this.target.css('position') == 'static') {
1974                 this.target.css('position', 'relative');
1975             }
1976             if (!this.target.hasClass('jqplot-target')) {
1977                 this.target.addClass('jqplot-target');
1978             }
1979             
1980             // if no height or width specified, use a default.
1981             if (!this.target.height()) {
1982                 var h;
1983                 if (options && options.height) {
1984                     h = parseInt(options.height, 10);
1985                 }
1986                 else if (this.target.attr('data-height')) {
1987                     h = parseInt(this.target.attr('data-height'), 10);
1988                 }
1989                 else {
1990                     h = parseInt($.jqplot.config.defaultHeight, 10);
1991                 }
1992                 this._height = h;
1993                 this.target.css('height', h+'px');
1994             }
1995             else {
1996                 this._height = h = this.target.height();
1997             }
1998             if (!this.target.width()) {
1999                 var w;
2000                 if (options && options.width) {
2001                     w = parseInt(options.width, 10);
2002                 }
2003                 else if (this.target.attr('data-width')) {
2004                     w = parseInt(this.target.attr('data-width'), 10);
2005                 }
2006                 else {
2007                     w = parseInt($.jqplot.config.defaultWidth, 10);
2008                 }
2009                 this._width = w;
2010                 this.target.css('width', w+'px');
2011             }
2012             else {
2013                 this._width = w = this.target.width();
2014             }
2015
2016             for (var i=0, l=_axisNames.length; i<l; i++) {
2017                 this.axes[_axisNames[i]] = new Axis(_axisNames[i]);
2018             }
2019             
2020             this._plotDimensions.height = this._height;
2021             this._plotDimensions.width = this._width;
2022             this.grid._plotDimensions = this._plotDimensions;
2023             this.title._plotDimensions = this._plotDimensions;
2024             this.baseCanvas._plotDimensions = this._plotDimensions;
2025             this.eventCanvas._plotDimensions = this._plotDimensions;
2026             this.legend._plotDimensions = this._plotDimensions;
2027             if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2028                 throw new Error("Canvas dimension not set");
2029             }
2030             
2031             if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
2032                 if (options.dataRendererOptions) {
2033                     this.dataRendererOptions = options.dataRendererOptions;
2034                 }
2035                 this.dataRenderer = options.dataRenderer;
2036                 data = this.dataRenderer(data, this, this.dataRendererOptions);
2037             }
2038             
2039             if (options.noDataIndicator && $.isPlainObject(options.noDataIndicator)) {
2040                 $.extend(true, this.noDataIndicator, options.noDataIndicator);
2041             }
2042             
2043             if (data == null || $.isArray(data) == false || data.length == 0 || $.isArray(data[0]) == false || data[0].length == 0) {
2044                 
2045                 if (this.noDataIndicator.show == false) {
2046                     throw new Error("No data specified");
2047                 }
2048                 
2049                 else {
2050                     // have to be descructive here in order for plot to not try and render series.
2051                     // This means that $.jqplot() will have to be called again when there is data.
2052                     //delete options.series;
2053                     
2054                     for (var ax in this.noDataIndicator.axes) {
2055                         for (var prop in this.noDataIndicator.axes[ax]) {
2056                             this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop];
2057                         }
2058                     }
2059                     
2060                     this.postDrawHooks.add(function() {
2061                         var eh = this.eventCanvas.getHeight();
2062                         var ew = this.eventCanvas.getWidth();
2063                         var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>');
2064                         this.target.append(temp);
2065                         temp.height(eh);
2066                         temp.width(ew);
2067                         temp.css('top', this.eventCanvas._offsets.top);
2068                         temp.css('left', this.eventCanvas._offsets.left);
2069                         
2070                         var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>');
2071                         temp.append(temp2);
2072                         temp2.html(this.noDataIndicator.indicator);
2073                         var th = temp2.height();
2074                         var tw = temp2.width();
2075                         temp2.height(th);
2076                         temp2.width(tw);
2077                         temp2.css('top', (eh - th)/2 + 'px');
2078                     });
2079
2080                 }
2081             }
2082             
2083             // make a copy of the data
2084             this.data = $.extend(true, [], data);
2085             
2086             this.parseOptions(options);
2087             
2088             if (this.textColor) {
2089                 this.target.css('color', this.textColor);
2090             }
2091             if (this.fontFamily) {
2092                 this.target.css('font-family', this.fontFamily);
2093             }
2094             if (this.fontSize) {
2095                 this.target.css('font-size', this.fontSize);
2096             }
2097             
2098             this.title.init();
2099             this.legend.init();
2100             this._sumy = 0;
2101             this._sumx = 0;
2102             this.computePlotData();
2103             for (var i=0; i<this.series.length; i++) {
2104                 // set default stacking order for series canvases
2105                 this.seriesStack.push(i);
2106                 this.previousSeriesStack.push(i);
2107                 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
2108                 this.series[i].canvas._plotDimensions = this._plotDimensions;
2109                 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
2110                     $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2111                 }
2112                 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
2113                     this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2114                 }
2115                 // this.populatePlotData(this.series[i], i);
2116                 this.series[i]._plotDimensions = this._plotDimensions;
2117                 this.series[i].init(i, this.grid.borderWidth, this);
2118                 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
2119                     $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2120                 }
2121                 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
2122                     this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2123                 }
2124                 this._sumy += this.series[i]._sumy;
2125                 this._sumx += this.series[i]._sumx;
2126             }
2127
2128             var name,
2129                 axis;
2130             for (var i=0, l=_axisNames.length; i<l; i++) {
2131                 name = _axisNames[i];
2132                 axis = this.axes[name];
2133                 axis._plotDimensions = this._plotDimensions;
2134                 axis.init();
2135                 if (this.axes[name].borderColor == null) {
2136                     if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
2137                         axis.borderColor = axis._series[0].color;
2138                     }
2139                     else {
2140                         axis.borderColor = this.grid.borderColor;
2141                     }
2142                 }
2143             }
2144             
2145             if (this.sortData) {
2146                 sortData(this.series);
2147             }
2148             this.grid.init();
2149             this.grid._axes = this.axes;
2150             
2151             this.legend._series = this.series;
2152
2153             for (var i=0; i<$.jqplot.postInitHooks.length; i++) {
2154                 $.jqplot.postInitHooks[i].call(this, target, this.data, options);
2155             }
2156
2157             for (var i=0; i<this.postInitHooks.hooks.length; i++) {
2158                 this.postInitHooks.hooks[i].call(this, target, this.data, options);
2159             }
2160         };  
2161         
2162         // method: resetAxesScale
2163         // Reset the specified axes min, max, numberTicks and tickInterval properties to null
2164         // or reset these properties on all axes if no list of axes is provided.
2165         //
2166         // Parameters:
2167         // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset.
2168         this.resetAxesScale = function(axes, options) {
2169             var opts = options || {};
2170             var ax = axes || this.axes;
2171             if (ax === true) {
2172                 ax = this.axes;
2173             }
2174             if ($.isArray(ax)) {
2175                 for (var i = 0; i < ax.length; i++) {
2176                     this.axes[ax[i]].resetScale(opts[ax[i]]);
2177                 }
2178             }
2179             else if (typeof(ax) === 'object') {
2180                 for (var name in ax) {
2181                     this.axes[name].resetScale(opts[name]);
2182                 }
2183             }
2184         };
2185         // method: reInitialize
2186         // reinitialize plot for replotting.
2187         // not called directly.
2188         this.reInitialize = function (data, opts) {
2189             // Plot should be visible and have a height and width.
2190             // If plot doesn't have height and width for some
2191             // reason, set it by other means.  Plot must not have
2192             // a display:none attribute, however.
2193
2194             var options = $.extend(true, {}, this.options, opts);
2195
2196             var target = this.targetId.substr(1);
2197             var tdata = (data == null) ? this.data : data;
2198
2199             for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
2200                 $.jqplot.preInitHooks[i].call(this, target, tdata, options);
2201             }
2202
2203             for (var i=0; i<this.preInitHooks.hooks.length; i++) {
2204                 this.preInitHooks.hooks[i].call(this, target, tdata, options);
2205             }
2206             
2207             this._height = this.target.height();
2208             this._width = this.target.width();
2209             
2210             if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2211                 throw new Error("Target dimension not set");
2212             }
2213             
2214             this._plotDimensions.height = this._height;
2215             this._plotDimensions.width = this._width;
2216             this.grid._plotDimensions = this._plotDimensions;
2217             this.title._plotDimensions = this._plotDimensions;
2218             this.baseCanvas._plotDimensions = this._plotDimensions;
2219             this.eventCanvas._plotDimensions = this._plotDimensions;
2220             this.legend._plotDimensions = this._plotDimensions;
2221
2222             var name,
2223                 t, 
2224                 j, 
2225                 axis;
2226
2227             for (var i=0, l=_axisNames.length; i<l; i++) {
2228                 name = _axisNames[i];
2229                 axis = this.axes[name];
2230
2231                 // Memory Leaks patch : clear ticks elements
2232                 t = axis._ticks;
2233                 for (var j = 0, tlen = t.length; j < tlen; j++) {
2234                   var el = t[j]._elem;
2235                   if (el) {
2236                     // if canvas renderer
2237                     if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
2238                       window.G_vmlCanvasManager.uninitElement(el.get(0));
2239                     }
2240                     el.emptyForce();
2241                     el = null;
2242                     t._elem = null;
2243                   }
2244                 }
2245                 t = null;
2246
2247                 delete axis.ticks;
2248                 delete axis._ticks;
2249                 this.axes[name] = new Axis(name);
2250                 this.axes[name]._plotWidth = this._width;
2251                 this.axes[name]._plotHeight = this._height;
2252             }
2253             
2254             if (data) {
2255                 if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
2256                     if (options.dataRendererOptions) {
2257                         this.dataRendererOptions = options.dataRendererOptions;
2258                     }
2259                     this.dataRenderer = options.dataRenderer;
2260                     data = this.dataRenderer(data, this, this.dataRendererOptions);
2261                 }
2262                 
2263                 // make a copy of the data
2264                 this.data = $.extend(true, [], data);
2265             }
2266
2267             if (opts) {
2268                 this.parseOptions(options);
2269             }
2270             
2271             this.title._plotWidth = this._width;
2272             
2273             if (this.textColor) {
2274                 this.target.css('color', this.textColor);
2275             }
2276             if (this.fontFamily) {
2277                 this.target.css('font-family', this.fontFamily);
2278             }
2279             if (this.fontSize) {
2280                 this.target.css('font-size', this.fontSize);
2281             }
2282
2283             this.title.init();
2284             this.legend.init();
2285             this._sumy = 0;
2286             this._sumx = 0;
2287
2288             this.seriesStack = [];
2289             this.previousSeriesStack = [];
2290
2291             this.computePlotData();
2292             for (var i=0, l=this.series.length; i<l; i++) {
2293                 // set default stacking order for series canvases
2294                 this.seriesStack.push(i);
2295                 this.previousSeriesStack.push(i);
2296                 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
2297                 this.series[i].canvas._plotDimensions = this._plotDimensions;
2298                 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
2299                     $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2300                 }
2301                 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
2302                     this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2303                 }
2304                 // this.populatePlotData(this.series[i], i);
2305                 this.series[i]._plotDimensions = this._plotDimensions;
2306                 this.series[i].init(i, this.grid.borderWidth, this);
2307                 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
2308                     $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2309                 }
2310                 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
2311                     this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2312                 }
2313                 this._sumy += this.series[i]._sumy;
2314                 this._sumx += this.series[i]._sumx;
2315             }
2316
2317             for (var i=0, l=_axisNames.length; i<l; i++) {
2318                 name = _axisNames[i];
2319                 axis = this.axes[name];
2320
2321                 axis._plotDimensions = this._plotDimensions;
2322                 axis.init();
2323                 if (axis.borderColor == null) {
2324                     if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
2325                         axis.borderColor = axis._series[0].color;
2326                     }
2327                     else {
2328                         axis.borderColor = this.grid.borderColor;
2329                     }
2330                 }
2331             }
2332             
2333             if (this.sortData) {
2334                 sortData(this.series);
2335             }
2336             this.grid.init();
2337             this.grid._axes = this.axes;
2338             
2339             this.legend._series = this.series;
2340
2341             for (var i=0, l=$.jqplot.postInitHooks.length; i<l; i++) {
2342                 $.jqplot.postInitHooks[i].call(this, target, this.data, options);
2343             }
2344
2345             for (var i=0, l=this.postInitHooks.hooks.length; i<l; i++) {
2346                 this.postInitHooks.hooks[i].call(this, target, this.data, options);
2347             }
2348         };
2349
2350
2351
2352         // method: quickInit
2353         // 
2354         // Quick reinitialization plot for replotting.
2355         // Does not parse options ore recreate axes and series.
2356         // not called directly.
2357         this.quickInit = function () {
2358             // Plot should be visible and have a height and width.
2359             // If plot doesn't have height and width for some
2360             // reason, set it by other means.  Plot must not have
2361             // a display:none attribute, however.
2362             
2363             this._height = this.target.height();
2364             this._width = this.target.width();
2365             
2366             if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2367                 throw new Error("Target dimension not set");
2368             }
2369             
2370             this._plotDimensions.height = this._height;
2371             this._plotDimensions.width = this._width;
2372             this.grid._plotDimensions = this._plotDimensions;
2373             this.title._plotDimensions = this._plotDimensions;
2374             this.baseCanvas._plotDimensions = this._plotDimensions;
2375             this.eventCanvas._plotDimensions = this._plotDimensions;
2376             this.legend._plotDimensions = this._plotDimensions;
2377             
2378             for (var n in this.axes) {
2379                 this.axes[n]._plotWidth = this._width;
2380                 this.axes[n]._plotHeight = this._height;
2381             }
2382             
2383             this.title._plotWidth = this._width;
2384             
2385             if (this.textColor) {
2386                 this.target.css('color', this.textColor);
2387             }
2388             if (this.fontFamily) {
2389                 this.target.css('font-family', this.fontFamily);
2390             }
2391             if (this.fontSize) {
2392                 this.target.css('font-size', this.fontSize);
2393             }
2394             
2395             this._sumy = 0;
2396             this._sumx = 0;
2397             this.computePlotData();
2398             for (var i=0; i<this.series.length; i++) {
2399                 // this.populatePlotData(this.series[i], i);
2400                 if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) {
2401                     this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this);
2402                 }
2403                 this.series[i]._plotDimensions = this._plotDimensions;
2404                 this.series[i].canvas._plotDimensions = this._plotDimensions;
2405                 //this.series[i].init(i, this.grid.borderWidth);
2406                 this._sumy += this.series[i]._sumy;
2407                 this._sumx += this.series[i]._sumx;
2408             }
2409
2410             var name;
2411             
2412             for (var j=0; j<12; j++) {
2413                 name = _axisNames[j];
2414                 // Memory Leaks patch : clear ticks elements
2415                 var t = this.axes[name]._ticks;
2416                 for (var i = 0; i < t.length; i++) {
2417                   var el = t[i]._elem;
2418                   if (el) {
2419                     // if canvas renderer
2420                     if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
2421                       window.G_vmlCanvasManager.uninitElement(el.get(0));
2422                     }
2423                     el.emptyForce();
2424                     el = null;
2425                     t._elem = null;
2426                   }
2427                 }
2428                 t = null;
2429                 
2430                 this.axes[name]._plotDimensions = this._plotDimensions;
2431                 this.axes[name]._ticks = [];
2432                 // this.axes[name].renderer.init.call(this.axes[name], {});
2433             }
2434             
2435             if (this.sortData) {
2436                 sortData(this.series);
2437             }
2438             
2439             this.grid._axes = this.axes;
2440             
2441             this.legend._series = this.series;
2442         };
2443         
2444         // sort the series data in increasing order.
2445         function sortData(series) {
2446             var d, sd, pd, ppd, ret;
2447             for (var i=0; i<series.length; i++) {
2448                 var check;
2449                 var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData];
2450                 for (var n=0; n<4; n++) {
2451                     check = true;
2452                     d = bat[n];
2453                     if (series[i]._stackAxis == 'x') {
2454                         for (var j = 0; j < d.length; j++) {
2455                             if (typeof(d[j][1]) != "number") {
2456                                 check = false;
2457                                 break;
2458                             }
2459                         }
2460                         if (check) {
2461                             d.sort(function(a,b) { return a[1] - b[1]; });
2462                         }
2463                     }
2464                     else {
2465                         for (var j = 0; j < d.length; j++) {
2466                             if (typeof(d[j][0]) != "number") {
2467                                 check = false;
2468                                 break;
2469                             }
2470                         }
2471                         if (check) {
2472                             d.sort(function(a,b) { return a[0] - b[0]; });
2473                         }
2474                     }
2475                 }
2476                
2477             }
2478         }
2479
2480         this.computePlotData = function() {
2481             this._plotData = [];
2482             this._stackData = [];
2483             var series,
2484                 index,
2485                 l;
2486
2487
2488             for (index=0, l=this.series.length; index<l; index++) {
2489                 series = this.series[index];
2490                 this._plotData.push([]);
2491                 this._stackData.push([]);
2492                 var cd = series.data;
2493                 this._plotData[index] = $.extend(true, [], cd);
2494                 this._stackData[index] = $.extend(true, [], cd);
2495                 series._plotData = this._plotData[index];
2496                 series._stackData = this._stackData[index];
2497                 var plotValues = {x:[], y:[]};
2498
2499                 if (this.stackSeries && !series.disableStack) {
2500                     series._stack = true;
2501                     ///////////////////////////
2502                     // have to check for nulls
2503                     ///////////////////////////
2504                     var sidx = (series._stackAxis === 'x') ? 0 : 1;
2505
2506                     for (var k=0, cdl=cd.length; k<cdl; k++) {
2507                         var temp = cd[k][sidx];
2508                         if (temp == null) {
2509                             temp = 0;
2510                         }
2511                         this._plotData[index][k][sidx] = temp;
2512                         this._stackData[index][k][sidx] = temp;
2513
2514                         if (index > 0) {
2515                             for (var j=index; j--;) {
2516                                 var prevval = this._plotData[j][k][sidx];
2517                                 // only need to sum up the stack axis column of data
2518                                 // and only sum if it is of same sign.
2519                                 // if previous series isn't same sign, keep looking
2520                                 // at earlier series untill we find one of same sign.
2521                                 if (temp * prevval >= 0) {
2522                                     this._plotData[index][k][sidx] += prevval;
2523                                     this._stackData[index][k][sidx] += prevval;
2524                                     break;
2525                                 } 
2526                             }
2527                         }
2528                     }
2529
2530                 }
2531                 else {
2532                     for (var i=0; i<series.data.length; i++) {
2533                         plotValues.x.push(series.data[i][0]);
2534                         plotValues.y.push(series.data[i][1]);
2535                     }
2536                     this._stackData.push(series.data);
2537                     this.series[index]._stackData = series.data;
2538                     this._plotData.push(series.data);
2539                     series._plotData = series.data;
2540                     series._plotValues = plotValues;
2541                 }
2542                 if (index>0) {
2543                     series._prevPlotData = this.series[index-1]._plotData;
2544                 }
2545                 series._sumy = 0;
2546                 series._sumx = 0;
2547                 for (i=series.data.length-1; i>-1; i--) {
2548                     series._sumy += series.data[i][1];
2549                     series._sumx += series.data[i][0];
2550                 }
2551             }
2552
2553         };
2554         
2555         // populate the _stackData and _plotData arrays for the plot and the series.
2556         this.populatePlotData = function(series, index) {
2557             // if a stacked chart, compute the stacked data
2558             this._plotData = [];
2559             this._stackData = [];
2560             series._stackData = [];
2561             series._plotData = [];
2562             var plotValues = {x:[], y:[]};
2563             if (this.stackSeries && !series.disableStack) {
2564                 series._stack = true;
2565                 var sidx = (series._stackAxis === 'x') ? 0 : 1;
2566                 // var idx = sidx ? 0 : 1;
2567                 // push the current data into stackData
2568                 //this._stackData.push(this.series[i].data);
2569                 var temp = $.extend(true, [], series.data);
2570                 // create the data that will be plotted for this series
2571                 var plotdata = $.extend(true, [], series.data);
2572                 var tempx, tempy, dval, stackval, comparator;
2573                 // for first series, nothing to add to stackData.
2574                 for (var j=0; j<index; j++) {
2575                     var cd = this.series[j].data;
2576                     for (var k=0; k<cd.length; k++) {
2577                         dval = cd[k];
2578                         tempx = (dval[0] != null) ? dval[0] : 0;
2579                         tempy = (dval[1] != null) ? dval[1] : 0;
2580                         temp[k][0] += tempx;
2581                         temp[k][1] += tempy;
2582                         stackval = (sidx) ? tempy : tempx;
2583                         // only need to sum up the stack axis column of data
2584                         // and only sum if it is of same sign.
2585                         if (series.data[k][sidx] * stackval >= 0) {
2586                             plotdata[k][sidx] += stackval;
2587                         }
2588                     }
2589                 }
2590                 for (var i=0; i<plotdata.length; i++) {
2591                     plotValues.x.push(plotdata[i][0]);
2592                     plotValues.y.push(plotdata[i][1]);
2593                 }
2594                 this._plotData.push(plotdata);
2595                 this._stackData.push(temp);
2596                 series._stackData = temp;
2597                 series._plotData = plotdata;
2598                 series._plotValues = plotValues;
2599             }
2600             else {
2601                 for (var i=0; i<series.data.length; i++) {
2602                     plotValues.x.push(series.data[i][0]);
2603                     plotValues.y.push(series.data[i][1]);
2604                 }
2605                 this._stackData.push(series.data);
2606                 this.series[index]._stackData = series.data;
2607                 this._plotData.push(series.data);
2608                 series._plotData = series.data;
2609                 series._plotValues = plotValues;
2610             }
2611             if (index>0) {
2612                 series._prevPlotData = this.series[index-1]._plotData;
2613             }
2614             series._sumy = 0;
2615             series._sumx = 0;
2616             for (i=series.data.length-1; i>-1; i--) {
2617                 series._sumy += series.data[i][1];
2618                 series._sumx += series.data[i][0];
2619             }
2620         };
2621         
2622         // function to safely return colors from the color array and wrap around at the end.
2623         this.getNextSeriesColor = (function(t) {
2624             var idx = 0;
2625             var sc = t.seriesColors;
2626             
2627             return function () { 
2628                 if (idx < sc.length) {
2629                     return sc[idx++];
2630                 }
2631                 else {
2632                     idx = 0;
2633                     return sc[idx++];
2634                 }
2635             };
2636         })(this);
2637     
2638         this.parseOptions = function(options){
2639             for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) {
2640                 this.preParseOptionsHooks.hooks[i].call(this, options);
2641             }
2642             for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) {
2643                 $.jqplot.preParseOptionsHooks[i].call(this, options);
2644             }
2645             this.options = $.extend(true, {}, this.defaults, options);
2646             var opts = this.options;
2647             this.animate = opts.animate;
2648             this.animateReplot = opts.animateReplot;
2649             this.stackSeries = opts.stackSeries;
2650             if ($.isPlainObject(opts.fillBetween)) {
2651
2652                 var temp = ['series1', 'series2', 'color', 'baseSeries', 'fill'], 
2653                     tempi;
2654
2655                 for (var i=0, l=temp.length; i<l; i++) {
2656                     tempi = temp[i];
2657                     if (opts.fillBetween[tempi] != null) {
2658                         this.fillBetween[tempi] = opts.fillBetween[tempi];
2659                     }
2660                 }
2661             }
2662
2663             if (opts.seriesColors) {
2664                 this.seriesColors = opts.seriesColors;
2665             }
2666             if (opts.negativeSeriesColors) {
2667                 this.negativeSeriesColors = opts.negativeSeriesColors;
2668             }
2669             if (opts.captureRightClick) {
2670                 this.captureRightClick = opts.captureRightClick;
2671             }
2672             this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart;
2673             this.colorGenerator.setColors(this.seriesColors);
2674             this.negativeColorGenerator.setColors(this.negativeSeriesColors);
2675             // var cg = new this.colorGenerator(this.seriesColors);
2676             // var ncg = new this.colorGenerator(this.negativeSeriesColors);
2677             // this._gridPadding = this.options.gridPadding;
2678             $.extend(true, this._gridPadding, opts.gridPadding);
2679             this.sortData = (opts.sortData != null) ? opts.sortData : this.sortData;
2680             for (var i=0; i<12; i++) {
2681                 var n = _axisNames[i];
2682                 var axis = this.axes[n];
2683                 axis._options = $.extend(true, {}, opts.axesDefaults, opts.axes[n]);
2684                 $.extend(true, axis, opts.axesDefaults, opts.axes[n]);
2685                 axis._plotWidth = this._width;
2686                 axis._plotHeight = this._height;
2687             }
2688             // if (this.data.length == 0) {
2689             //     this.data = [];
2690             //     for (var i=0; i<this.options.series.length; i++) {
2691             //         this.data.push(this.options.series.data);
2692             //     }    
2693             // }
2694                 
2695             var normalizeData = function(data, dir, start) {
2696                 // return data as an array of point arrays,
2697                 // in form [[x1,y1...], [x2,y2...], ...]
2698                 var temp = [];
2699                 var i, l;
2700                 dir = dir || 'vertical';
2701                 if (!$.isArray(data[0])) {
2702                     // we have a series of scalars.  One line with just y values.
2703                     // turn the scalar list of data into a data array of form:
2704                     // [[1, data[0]], [2, data[1]], ...]
2705                     for (i=0, l=data.length; i<l; i++) {
2706                         if (dir == 'vertical') {
2707                             temp.push([start + i, data[i]]);   
2708                         }
2709                         else {
2710                             temp.push([data[i], start+i]);
2711                         }
2712                     }
2713                 }            
2714                 else {
2715                     // we have a properly formatted data series, copy it.
2716                     $.extend(true, temp, data);
2717                 }
2718                 return temp;
2719             };
2720
2721             var colorIndex = 0;
2722             this.series = [];
2723             for (var i=0; i<this.data.length; i++) {
2724                 var sopts = $.extend(true, {index: i}, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i], {rendererOptions:{animation:{show: this.animate}}});
2725                 // pass in options in case something needs set prior to initialization.
2726                 var temp = new Series(sopts);
2727                 for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) {
2728                     $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
2729                 }
2730                 for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) {
2731                     this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
2732                 }
2733                 // Now go back and apply the options to the series.  Really should just do this during initializaiton, but don't want to
2734                 // mess up preParseSeriesOptionsHooks at this point.
2735                 $.extend(true, temp, sopts);
2736                 var dir = 'vertical';
2737                 if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal') {
2738                     dir = 'horizontal';
2739                     temp._stackAxis = 'x';
2740                     temp._primaryAxis = '_yaxis';
2741                 }
2742                 temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart);
2743                 switch (temp.xaxis) {
2744                     case 'xaxis':
2745                         temp._xaxis = this.axes.xaxis;
2746                         break;
2747                     case 'x2axis':
2748                         temp._xaxis = this.axes.x2axis;
2749                         break;
2750                     default:
2751                         break;
2752                 }
2753                 temp._yaxis = this.axes[temp.yaxis];
2754                 temp._xaxis._series.push(temp);
2755                 temp._yaxis._series.push(temp);
2756                 if (temp.show) {
2757                     temp._xaxis.show = true;
2758                     temp._yaxis.show = true;
2759                 }
2760                 else {
2761                     if (temp._xaxis.scaleToHiddenSeries) {
2762                         temp._xaxis.show = true;
2763                     }
2764                     if (temp._yaxis.scaleToHiddenSeries) {
2765                         temp._yaxis.show = true;
2766                     }
2767                 }
2768
2769                 // // parse the renderer options and apply default colors if not provided
2770                 // if (!temp.color && temp.show != false) {
2771                 //     temp.color = cg.next();
2772                 //     colorIndex = cg.getIndex() - 1;;
2773                 // }
2774                 // if (!temp.negativeColor && temp.show != false) {
2775                 //     temp.negativeColor = ncg.get(colorIndex);
2776                 //     ncg.setIndex(colorIndex);
2777                 // }
2778                 if (!temp.label) {
2779                     temp.label = 'Series '+ (i+1).toString();
2780                 }
2781                 // temp.rendererOptions.show = temp.show;
2782                 // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions);
2783                 this.series.push(temp);  
2784                 for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) {
2785                     $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
2786                 }
2787                 for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) {
2788                     this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
2789                 }
2790             }
2791             
2792             // copy the grid and title options into this object.
2793             $.extend(true, this.grid, this.options.grid);
2794             // if axis border properties aren't set, set default.
2795             for (var i=0, l=_axisNames.length; i<l; i++) {
2796                 var n = _axisNames[i];
2797                 var axis = this.axes[n];
2798                 if (axis.borderWidth == null) {
2799                     axis.borderWidth =this.grid.borderWidth;
2800                 }
2801             }
2802             
2803             if (typeof this.options.title == 'string') {
2804                 this.title.text = this.options.title;
2805             }
2806             else if (typeof this.options.title == 'object') {
2807                 $.extend(true, this.title, this.options.title);
2808             }
2809             this.title._plotWidth = this._width;
2810             this.legend.setOptions(this.options.legend);
2811             
2812             for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
2813                 $.jqplot.postParseOptionsHooks[i].call(this, options);
2814             }
2815             for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) {
2816                 this.postParseOptionsHooks.hooks[i].call(this, options);
2817             }
2818         };
2819         
2820         // method: destroy
2821         // Releases all resources occupied by the plot
2822         this.destroy = function() {
2823             this.canvasManager.freeAllCanvases();
2824             if (this.eventCanvas && this.eventCanvas._elem) {
2825                 this.eventCanvas._elem.unbind();
2826             }
2827             // Couple of posts on Stack Overflow indicate that empty() doesn't
2828             // always cear up the dom and release memory.  Sometimes setting
2829             // innerHTML property to null is needed.  Particularly on IE, may 
2830             // have to directly set it to null, bypassing $.
2831             this.target.empty();
2832
2833             this.target[0].innerHTML = '';
2834         };
2835         
2836         // method: replot
2837         // Does a reinitialization of the plot followed by
2838         // a redraw.  Method could be used to interactively
2839         // change plot characteristics and then replot.
2840         //
2841         // Parameters:
2842         // options - Options used for replotting.
2843         //
2844         // Properties:
2845         // clear - false to not clear (empty) the plot container before replotting (default: true).
2846         // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves.
2847         //             optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false).
2848         this.replot = function(options) {
2849             var opts =  options || {};
2850             var data = opts.data || null;
2851             var clear = (opts.clear === false) ? false : true;
2852             var resetAxes = opts.resetAxes || false;
2853             delete opts.data;
2854             delete opts.clear;
2855             delete opts.resetAxes;
2856
2857             this.target.trigger('jqplotPreReplot');
2858             
2859             if (clear) {
2860                 this.destroy();
2861             }
2862             // if have data or other options, full reinit.
2863             // otherwise, quickinit.
2864             if (data || !$.isEmptyObject(opts)) {
2865                 this.reInitialize(data, opts);
2866             }
2867             else {
2868                 this.quickInit();
2869             }
2870
2871             if (resetAxes) {
2872                 this.resetAxesScale(resetAxes, opts.axes);
2873             }
2874             this.draw();
2875             this.target.trigger('jqplotPostReplot');
2876         };
2877         
2878         // method: redraw
2879         // Empties the plot target div and redraws the plot.
2880         // This enables plot data and properties to be changed
2881         // and then to comletely clear the plot and redraw.
2882         // redraw *will not* reinitialize any plot elements.
2883         // That is, axes will not be autoscaled and defaults
2884         // will not be reapplied to any plot elements.  redraw
2885         // is used primarily with zooming. 
2886         //
2887         // Parameters:
2888         // clear - false to not clear (empty) the plot container before redrawing (default: true).
2889         this.redraw = function(clear) {
2890             clear = (clear != null) ? clear : true;
2891             this.target.trigger('jqplotPreRedraw');
2892             if (clear) {
2893                 this.canvasManager.freeAllCanvases();
2894                 this.eventCanvas._elem.unbind();
2895                 // Dont think I bind any events to the target, this shouldn't be necessary.
2896                 // It will remove user's events.
2897                 // this.target.unbind();
2898                 this.target.empty();
2899             }
2900              for (var ax in this.axes) {
2901                 this.axes[ax]._ticks = [];
2902             }
2903             this.computePlotData();
2904             // for (var i=0; i<this.series.length; i++) {
2905             //     this.populatePlotData(this.series[i], i);
2906             // }
2907             this._sumy = 0;
2908             this._sumx = 0;
2909             for (var i=0, tsl = this.series.length; i<tsl; i++) {
2910                 this._sumy += this.series[i]._sumy;
2911                 this._sumx += this.series[i]._sumx;
2912             }
2913             this.draw();
2914             this.target.trigger('jqplotPostRedraw');
2915         };
2916         
2917         // method: draw
2918         // Draws all elements of the plot into the container.
2919         // Does not clear the container before drawing.
2920         this.draw = function(){
2921             if (this.drawIfHidden || this.target.is(':visible')) {
2922                 this.target.trigger('jqplotPreDraw');
2923                 var i,
2924                     j,
2925                     l,
2926                     tempseries;
2927                 for (i=0, l=$.jqplot.preDrawHooks.length; i<l; i++) {
2928                     $.jqplot.preDrawHooks[i].call(this);
2929                 }
2930                 for (i=0, l=this.preDrawHooks.hooks.length; i<l; i++) {
2931                     this.preDrawHooks.hooks[i].apply(this, this.preDrawSeriesHooks.args[i]);
2932                 }
2933                 // create an underlying canvas to be used for special features.
2934                 this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this));
2935                 this.baseCanvas.setContext();
2936                 this.target.append(this.title.draw());
2937                 this.title.pack({top:0, left:0});
2938                 
2939                 // make room  for the legend between the grid and the edge.
2940                 // pass a dummy offsets object and a reference to the plot.
2941                 var legendElem = this.legend.draw({}, this);
2942                 
2943                 var gridPadding = {top:0, left:0, bottom:0, right:0};
2944                 
2945                 if (this.legend.placement == "outsideGrid") {
2946                     // temporarily append the legend to get dimensions
2947                     this.target.append(legendElem);
2948                     switch (this.legend.location) {
2949                         case 'n':
2950                             gridPadding.top += this.legend.getHeight();
2951                             break;
2952                         case 's':
2953                             gridPadding.bottom += this.legend.getHeight();
2954                             break;
2955                         case 'ne':
2956                         case 'e':
2957                         case 'se':
2958                             gridPadding.right += this.legend.getWidth();
2959                             break;
2960                         case 'nw':
2961                         case 'w':
2962                         case 'sw':
2963                             gridPadding.left += this.legend.getWidth();
2964                             break;
2965                         default:  // same as 'ne'
2966                             gridPadding.right += this.legend.getWidth();
2967                             break;
2968                     }
2969                     legendElem = legendElem.detach();
2970                 }
2971                 
2972                 var ax = this.axes;
2973                 var name;
2974                 // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed.
2975                 for (i=0; i<12; i++) {
2976                     name = _axisNames[i];
2977                     this.target.append(ax[name].draw(this.baseCanvas._ctx, this));
2978                     ax[name].set();
2979                 }
2980                 if (ax.yaxis.show) {
2981                     gridPadding.left += ax.yaxis.getWidth();
2982                 }
2983                 var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
2984                 var rapad = [0, 0, 0, 0, 0, 0, 0, 0];
2985                 var gpr = 0;
2986                 var n;
2987                 for (n=0; n<8; n++) {
2988                     if (ax[ra[n]].show) {
2989                         gpr += ax[ra[n]].getWidth();
2990                         rapad[n] = gpr;
2991                     }
2992                 }
2993                 gridPadding.right += gpr;
2994                 if (ax.x2axis.show) {
2995                     gridPadding.top += ax.x2axis.getHeight();
2996                 }
2997                 if (this.title.show) {
2998                     gridPadding.top += this.title.getHeight();
2999                 }
3000                 if (ax.xaxis.show) {
3001                     gridPadding.bottom += ax.xaxis.getHeight();
3002                 }
3003                 
3004                 // end of gridPadding adjustments.
3005
3006                 // if user passed in gridDimensions option, check against calculated gridPadding
3007                 if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) {
3008                     var gdw = parseInt(this.options.gridDimensions.width, 10) || 0;
3009                     var gdh = parseInt(this.options.gridDimensions.height, 10) || 0;
3010                     var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2;
3011                     var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2;
3012
3013                     if (heightAdj >= 0 && widthAdj >= 0) {
3014                         gridPadding.top += heightAdj;
3015                         gridPadding.bottom += heightAdj;
3016                         gridPadding.left += widthAdj;
3017                         gridPadding.right += widthAdj;
3018                     }
3019                 }
3020                 var arr = ['top', 'bottom', 'left', 'right'];
3021                 for (var n in arr) {
3022                     if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) {
3023                         this._gridPadding[arr[n]] = gridPadding[arr[n]];
3024                     }
3025                     else if (this._gridPadding[arr[n]] == null) {
3026                         this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]];
3027                     }
3028                 }
3029                 
3030                 var legendPadding = this._gridPadding;
3031                 
3032                 if (this.legend.placement === 'outsideGrid') {
3033                     legendPadding = {top:this.title.getHeight(), left: 0, right: 0, bottom: 0};
3034                     if (this.legend.location === 's') {
3035                         legendPadding.left = this._gridPadding.left;
3036                         legendPadding.right = this._gridPadding.right;
3037                     }
3038                 }
3039                 
3040                 ax.xaxis.pack({position:'absolute', bottom:this._gridPadding.bottom - ax.xaxis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right});
3041                 ax.yaxis.pack({position:'absolute', top:0, left:this._gridPadding.left - ax.yaxis.getWidth(), height:this._height}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
3042                 ax.x2axis.pack({position:'absolute', top:this._gridPadding.top - ax.x2axis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right});
3043                 for (i=8; i>0; i--) {
3044                     ax[ra[i-1]].pack({position:'absolute', top:0, right:this._gridPadding.right - rapad[i-1]}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
3045                 }
3046                 var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0;
3047                 ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
3048             
3049                 this.target.append(this.grid.createElement(this._gridPadding, this));
3050                 this.grid.draw();
3051                 
3052                 var series = this.series;
3053                 var seriesLength = series.length;
3054                 // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars.
3055                 for (i=0, l=seriesLength; i<l; i++) {
3056                     // draw series in order of stacking.  This affects only
3057                     // order in which canvases are added to dom.
3058                     j = this.seriesStack[i];
3059                     this.target.append(series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this));
3060                     series[j].shadowCanvas.setContext();
3061                     series[j].shadowCanvas._elem.data('seriesIndex', j);
3062                 }
3063                 
3064                 for (i=0, l=seriesLength; i<l; i++) {
3065                     // draw series in order of stacking.  This affects only
3066                     // order in which canvases are added to dom.
3067                     j = this.seriesStack[i];
3068                     this.target.append(series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this));
3069                     series[j].canvas.setContext();
3070                     series[j].canvas._elem.data('seriesIndex', j);
3071                 }
3072                 // Need to use filled canvas to capture events in IE.
3073                 // Also, canvas seems to block selection of other elements in document on FF.
3074                 this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this));
3075                 this.eventCanvas.setContext();
3076                 this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)';
3077                 this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height);
3078             
3079                 // bind custom event handlers to regular events.
3080                 this.bindCustomEvents();
3081             
3082                 // draw legend before series if the series needs to know the legend dimensions.
3083                 if (this.legend.preDraw) {  
3084                     this.eventCanvas._elem.before(legendElem);
3085                     this.legend.pack(legendPadding);
3086                     if (this.legend._elem) {
3087                         this.drawSeries({legendInfo:{location:this.legend.location, placement:this.legend.placement, width:this.legend.getWidth(), height:this.legend.getHeight(), xoffset:this.legend.xoffset, yoffset:this.legend.yoffset}});
3088                     }
3089                     else {
3090                         this.drawSeries();
3091                     }
3092                 }
3093                 else {  // draw series before legend
3094                     this.drawSeries();
3095                     if (seriesLength) {
3096                         $(series[seriesLength-1].canvas._elem).after(legendElem);
3097                     }
3098                     this.legend.pack(legendPadding);                
3099                 }
3100             
3101                 // register event listeners on the overlay canvas
3102                 for (var i=0, l=$.jqplot.eventListenerHooks.length; i<l; i++) {
3103                     // in the handler, this will refer to the eventCanvas dom element.
3104                     // make sure there are references back into plot objects.
3105                     this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
3106                 }
3107             
3108                 // register event listeners on the overlay canvas
3109                 for (var i=0, l=this.eventListenerHooks.hooks.length; i<l; i++) {
3110                     // in the handler, this will refer to the eventCanvas dom element.
3111                     // make sure there are references back into plot objects.
3112                     this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]);
3113                 }
3114
3115                 var fb = this.fillBetween;
3116                 if (fb.fill && fb.series1 !== fb.series2 && fb.series1 < seriesLength && fb.series2 < seriesLength && series[fb.series1]._type === 'line' && series[fb.series2]._type === 'line') {
3117                     this.doFillBetweenLines();
3118                 }
3119
3120                 for (var i=0, l=$.jqplot.postDrawHooks.length; i<l; i++) {
3121                     $.jqplot.postDrawHooks[i].call(this);
3122                 }
3123
3124                 for (var i=0, l=this.postDrawHooks.hooks.length; i<l; i++) {
3125                     this.postDrawHooks.hooks[i].apply(this, this.postDrawHooks.args[i]);
3126                 }
3127             
3128                 if (this.target.is(':visible')) {
3129                     this._drawCount += 1;
3130                 }
3131
3132                 var temps, 
3133                     tempr,
3134                     sel,
3135                     _els;
3136                 // ughh.  ideally would hide all series then show them.
3137                 for (i=0, l=seriesLength; i<l; i++) {
3138                     temps = series[i];
3139                     tempr = temps.renderer;
3140                     sel = '.jqplot-point-label.jqplot-series-'+i;
3141                     if (tempr.animation && tempr.animation._supported && tempr.animation.show && (this._drawCount < 2 || this.animateReplot)) {
3142                         _els = this.target.find(sel);
3143                         _els.stop(true, true).hide();
3144                         temps.canvas._elem.stop(true, true).hide();
3145                         temps.shadowCanvas._elem.stop(true, true).hide();
3146                         temps.canvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
3147                         temps.shadowCanvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
3148                         _els.fadeIn(tempr.animation.speed*0.8);
3149                     }
3150                 }
3151                 _els = null;
3152             
3153                 this.target.trigger('jqplotPostDraw', [this]);
3154             }
3155         };
3156
3157         jqPlot.prototype.doFillBetweenLines = function () {
3158             var fb = this.fillBetween;
3159             var sid1 = fb.series1;
3160             var sid2 = fb.series2;
3161             // first series should always be lowest index
3162             var id1 = (sid1 < sid2) ? sid1 : sid2;
3163             var id2 = (sid2 >  sid1) ? sid2 : sid1;
3164
3165             var series1 = this.series[id1];
3166             var series2 = this.series[id2];
3167
3168             if (series2.renderer.smooth) {
3169                 var tempgd = series2.renderer._smoothedData.slice(0).reverse();
3170             }
3171             else {
3172                 var tempgd = series2.gridData.slice(0).reverse();
3173             }
3174
3175             if (series1.renderer.smooth) {
3176                 var gd = series1.renderer._smoothedData.concat(tempgd);
3177             }
3178             else {
3179                 var gd = series1.gridData.concat(tempgd);
3180             }
3181
3182             var color = (fb.color !== null) ? fb.color : this.series[sid1].fillColor;
3183             var baseSeries = (fb.baseSeries !== null) ? fb.baseSeries : id1;
3184
3185             // now apply a fill to the shape on the lower series shadow canvas,
3186             // so it is behind both series.
3187             var sr = this.series[baseSeries].renderer.shapeRenderer;
3188             var opts = {fillStyle: color, fill: true, closePath: true};
3189             sr.draw(series1.shadowCanvas._ctx, gd, opts);
3190         };
3191         
3192         this.bindCustomEvents = function() {
3193             this.eventCanvas._elem.bind('click', {plot:this}, this.onClick);
3194             this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
3195             this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown);
3196             this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove);
3197             this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
3198             this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
3199             if (this.captureRightClick) {
3200                 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick);
3201                 this.eventCanvas._elem.get(0).oncontextmenu = function() {
3202                     return false;
3203                 };
3204             }
3205             else {
3206                 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp);
3207             }
3208         };
3209         
3210         function getEventPosition(ev) {
3211             var plot = ev.data.plot;
3212             var go = plot.eventCanvas._elem.offset();
3213             var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
3214             var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null};
3215             var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
3216             var ax = plot.axes;
3217             var n, axis;
3218             for (n=11; n>0; n--) {
3219                 axis = an[n-1];
3220                 if (ax[axis].show) {
3221                     dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
3222                 }
3223             }
3224
3225             return {offsets:go, gridPos:gridPos, dataPos:dataPos};
3226         }
3227         
3228         
3229         // function to check if event location is over a area area
3230         function checkIntersection(gridpos, plot) {
3231             var series = plot.series;
3232             var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang;
3233             var d0, d, p, pp, points, bw, hp;
3234             var threshold, t;
3235             for (k=plot.seriesStack.length-1; k>=0; k--) {
3236                 i = plot.seriesStack[k];
3237                 s = series[i];
3238                 hp = s._highlightThreshold;
3239                 switch (s.renderer.constructor) {
3240                     case $.jqplot.BarRenderer:
3241                         x = gridpos.x;
3242                         y = gridpos.y;
3243                         for (j=0; j<s._barPoints.length; j++) {
3244                             points = s._barPoints[j];
3245                             p = s.gridData[j];
3246                             if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
3247                                 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
3248                             }
3249                         }
3250                         break;
3251                     case $.jqplot.PyramidRenderer:
3252                         x = gridpos.x;
3253                         y = gridpos.y;
3254                         for (j=0; j<s._barPoints.length; j++) {
3255                             points = s._barPoints[j];
3256                             p = s.gridData[j];
3257                             if (x > points[0][0] + hp[0][0] && x < points[2][0] + hp[2][0] && y > points[2][1] && y < points[0][1]) {
3258                                 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
3259                             }
3260                         }
3261                         break;
3262                     
3263                     case $.jqplot.DonutRenderer:
3264                         sa = s.startAngle/180*Math.PI;
3265                         x = gridpos.x - s._center[0];
3266                         y = gridpos.y - s._center[1];
3267                         r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
3268                         if (x > 0 && -y >= 0) {
3269                             theta = 2*Math.PI - Math.atan(-y/x);
3270                         }
3271                         else if (x > 0 && -y < 0) {
3272                             theta = -Math.atan(-y/x);
3273                         }
3274                         else if (x < 0) {
3275                             theta = Math.PI - Math.atan(-y/x);
3276                         }
3277                         else if (x == 0 && -y > 0) {
3278                             theta = 3*Math.PI/2;
3279                         }
3280                         else if (x == 0 && -y < 0) {
3281                             theta = Math.PI/2;
3282                         }
3283                         else if (x == 0 && y == 0) {
3284                             theta = 0;
3285                         }
3286                         if (sa) {
3287                             theta -= sa;
3288                             if (theta < 0) {
3289                                 theta += 2*Math.PI;
3290                             }
3291                             else if (theta > 2*Math.PI) {
3292                                 theta -= 2*Math.PI;
3293                             }
3294                         }
3295             
3296                         sm = s.sliceMargin/180*Math.PI;
3297                         if (r < s._radius && r > s._innerRadius) {
3298                             for (j=0; j<s.gridData.length; j++) {
3299                                 minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
3300                                 maxang = s.gridData[j][1];
3301                                 if (theta > minang && theta < maxang) {
3302                                     return {seriesIndex:s.index, pointIndex:j, gridData:[gridpos.x,gridpos.y], data:s.data[j]};
3303                                 }
3304                             }
3305                         }
3306                         break;
3307                         
3308                     case $.jqplot.PieRenderer:
3309                         sa = s.startAngle/180*Math.PI;
3310                         x = gridpos.x - s._center[0];
3311                         y = gridpos.y - s._center[1];
3312                         r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
3313                         if (x > 0 && -y >= 0) {
3314                             theta = 2*Math.PI - Math.atan(-y/x);
3315                         }
3316                         else if (x > 0 && -y < 0) {
3317                             theta = -Math.atan(-y/x);
3318                         }
3319                         else if (x < 0) {
3320                             theta = Math.PI - Math.atan(-y/x);
3321                         }
3322                         else if (x == 0 && -y > 0) {
3323                             theta = 3*Math.PI/2;
3324                         }
3325                         else if (x == 0 && -y < 0) {
3326                             theta = Math.PI/2;
3327                         }
3328                         else if (x == 0 && y == 0) {
3329                             theta = 0;
3330                         }
3331                         if (sa) {
3332                             theta -= sa;
3333                             if (theta < 0) {
3334                                 theta += 2*Math.PI;
3335                             }
3336                             else if (theta > 2*Math.PI) {
3337                                 theta -= 2*Math.PI;
3338                             }
3339                         }
3340             
3341                         sm = s.sliceMargin/180*Math.PI;
3342                         if (r < s._radius) {
3343                             for (j=0; j<s.gridData.length; j++) {
3344                                 minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
3345                                 maxang = s.gridData[j][1];
3346                                 if (theta > minang && theta < maxang) {
3347                                     return {seriesIndex:s.index, pointIndex:j, gridData:[gridpos.x,gridpos.y], data:s.data[j]};
3348                                 }
3349                             }
3350                         }
3351                         break;
3352                         
3353                     case $.jqplot.BubbleRenderer:
3354                         x = gridpos.x;
3355                         y = gridpos.y;
3356                         var ret = null;
3357                         
3358                         if (s.show) {
3359                             for (var j=0; j<s.gridData.length; j++) {
3360                                 p = s.gridData[j];
3361                                 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3362                                 if (d <= p[2] && (d <= d0 || d0 == null)) {
3363                                    d0 = d;
3364                                    ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3365                                 }
3366                             }
3367                             if (ret != null) {
3368                                 return ret;
3369                             }
3370                         }
3371                         break;
3372                         
3373                     case $.jqplot.FunnelRenderer:
3374                         x = gridpos.x;
3375                         y = gridpos.y;
3376                         var v = s._vertices,
3377                             vfirst = v[0],
3378                             vlast = v[v.length-1],
3379                             lex,
3380                             rex,
3381                             cv;
3382     
3383                         // equations of right and left sides, returns x, y values given height of section (y value and 2 points)
3384     
3385                         function findedge (l, p1 , p2) {
3386                             var m = (p1[1] - p2[1])/(p1[0] - p2[0]);
3387                             var b = p1[1] - m*p1[0];
3388                             var y = l + p1[1];
3389         
3390                             return [(y - b)/m, y];
3391                         }
3392     
3393                         // check each section
3394                         lex = findedge(y, vfirst[0], vlast[3]);
3395                         rex = findedge(y, vfirst[1], vlast[2]);
3396                         for (j=0; j<v.length; j++) {
3397                             cv = v[j];
3398                             if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) {
3399                                 return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]};
3400                             }
3401                         }         
3402                         break;           
3403                     
3404                     case $.jqplot.LineRenderer:
3405                         x = gridpos.x;
3406                         y = gridpos.y;
3407                         r = s.renderer;
3408                         if (s.show) {
3409                             if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) {
3410                                 // first check if it is in bounding box
3411                                 var inside = false;
3412                                 if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) { 
3413                                     // now check the crossing number   
3414                                     
3415                                     var numPoints = s._areaPoints.length;
3416                                     var ii;
3417                                     var j = numPoints-1;
3418
3419                                     for(var ii=0; ii < numPoints; ii++) { 
3420                                         var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]];
3421                                         var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]];
3422
3423                                         if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y)     {
3424                                             if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) {
3425                                                 inside = !inside;
3426                                             }
3427                                         }
3428
3429                                         j = ii;
3430                                     }        
3431                                 }
3432                                 if (inside) {
3433                                     return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints};
3434                                 }
3435                                 break;
3436                                 
3437                             }
3438
3439                             else {
3440                                 t = s.markerRenderer.size/2+s.neighborThreshold;
3441                                 threshold = (t > 0) ? t : 0;
3442                                 for (var j=0; j<s.gridData.length; j++) {
3443                                     p = s.gridData[j];
3444                                     // neighbor looks different to OHLC chart.
3445                                     if (r.constructor == $.jqplot.OHLCRenderer) {
3446                                         if (r.candleStick) {
3447                                             var yp = s._yaxis.series_u2p;
3448                                             if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3449                                                 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3450                                             }
3451                                         }
3452                                         // if an open hi low close chart
3453                                         else if (!r.hlc){
3454                                             var yp = s._yaxis.series_u2p;
3455                                             if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3456                                                 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3457                                             }
3458                                         }
3459                                         // a hi low close chart
3460                                         else {
3461                                             var yp = s._yaxis.series_u2p;
3462                                             if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
3463                                                 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3464                                             }
3465                                         }
3466                             
3467                                     }
3468                                     else if (p[0] != null && p[1] != null){
3469                                         d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3470                                         if (d <= threshold && (d <= d0 || d0 == null)) {
3471                                            d0 = d;
3472                                            return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3473                                         }
3474                                     }
3475                                 } 
3476                             }
3477                         }
3478                         break;
3479                         
3480                     default:
3481                         x = gridpos.x;
3482                         y = gridpos.y;
3483                         r = s.renderer;
3484                         if (s.show) {
3485                             t = s.markerRenderer.size/2+s.neighborThreshold;
3486                             threshold = (t > 0) ? t : 0;
3487                             for (var j=0; j<s.gridData.length; j++) {
3488                                 p = s.gridData[j];
3489                                 // neighbor looks different to OHLC chart.
3490                                 if (r.constructor == $.jqplot.OHLCRenderer) {
3491                                     if (r.candleStick) {
3492                                         var yp = s._yaxis.series_u2p;
3493                                         if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3494                                             return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3495                                         }
3496                                     }
3497                                     // if an open hi low close chart
3498                                     else if (!r.hlc){
3499                                         var yp = s._yaxis.series_u2p;
3500                                         if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3501                                             return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3502                                         }
3503                                     }
3504                                     // a hi low close chart
3505                                     else {
3506                                         var yp = s._yaxis.series_u2p;
3507                                         if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
3508                                             return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3509                                         }
3510                                     }
3511                             
3512                                 }
3513                                 else {
3514                                     d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3515                                     if (d <= threshold && (d <= d0 || d0 == null)) {
3516                                        d0 = d;
3517                                        return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3518                                     }
3519                                 }
3520                             } 
3521                         }
3522                         break;
3523                 }
3524             }
3525             
3526             return null;
3527         }
3528         
3529         
3530         
3531         this.onClick = function(ev) {
3532             // Event passed in is normalized and will have data attribute.
3533             // Event passed out is unnormalized.
3534             var positions = getEventPosition(ev);
3535             var p = ev.data.plot;
3536             var neighbor = checkIntersection(positions.gridPos, p);
3537             var evt = $.Event('jqplotClick');
3538             evt.pageX = ev.pageX;
3539             evt.pageY = ev.pageY;
3540             $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3541         };
3542         
3543         this.onDblClick = function(ev) {
3544             // Event passed in is normalized and will have data attribute.
3545             // Event passed out is unnormalized.
3546             var positions = getEventPosition(ev);
3547             var p = ev.data.plot;
3548             var neighbor = checkIntersection(positions.gridPos, p);
3549             var evt = $.Event('jqplotDblClick');
3550             evt.pageX = ev.pageX;
3551             evt.pageY = ev.pageY;
3552             $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3553         };
3554         
3555         this.onMouseDown = function(ev) {
3556             var positions = getEventPosition(ev);
3557             var p = ev.data.plot;
3558             var neighbor = checkIntersection(positions.gridPos, p);
3559             var evt = $.Event('jqplotMouseDown');
3560             evt.pageX = ev.pageX;
3561             evt.pageY = ev.pageY;
3562             $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3563         };
3564         
3565         this.onMouseUp = function(ev) {
3566             var positions = getEventPosition(ev);
3567             var evt = $.Event('jqplotMouseUp');
3568             evt.pageX = ev.pageX;
3569             evt.pageY = ev.pageY;
3570             $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]);
3571         };
3572         
3573         this.onRightClick = function(ev) {
3574             var positions = getEventPosition(ev);
3575             var p = ev.data.plot;
3576             var neighbor = checkIntersection(positions.gridPos, p);
3577             if (p.captureRightClick) {
3578                 if (ev.which == 3) {
3579                 var evt = $.Event('jqplotRightClick');
3580                 evt.pageX = ev.pageX;
3581                 evt.pageY = ev.pageY;
3582                     $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3583                 }
3584                 else {
3585                 var evt = $.Event('jqplotMouseUp');
3586                 evt.pageX = ev.pageX;
3587                 evt.pageY = ev.pageY;
3588                     $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3589                 }
3590             }
3591         };
3592         
3593         this.onMouseMove = function(ev) {
3594             var positions = getEventPosition(ev);
3595             var p = ev.data.plot;
3596             var neighbor = checkIntersection(positions.gridPos, p);
3597             var evt = $.Event('jqplotMouseMove');
3598             evt.pageX = ev.pageX;
3599             evt.pageY = ev.pageY;
3600             $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3601         };
3602         
3603         this.onMouseEnter = function(ev) {
3604             var positions = getEventPosition(ev);
3605             var p = ev.data.plot;
3606             var evt = $.Event('jqplotMouseEnter');
3607             evt.pageX = ev.pageX;
3608             evt.pageY = ev.pageY;
3609             evt.relatedTarget = ev.relatedTarget;
3610             $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
3611         };
3612         
3613         this.onMouseLeave = function(ev) {
3614             var positions = getEventPosition(ev);
3615             var p = ev.data.plot;
3616             var evt = $.Event('jqplotMouseLeave');
3617             evt.pageX = ev.pageX;
3618             evt.pageY = ev.pageY;
3619             evt.relatedTarget = ev.relatedTarget;
3620             $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
3621         };
3622         
3623         // method: drawSeries
3624         // Redraws all or just one series on the plot.  No axis scaling
3625         // is performed and no other elements on the plot are redrawn.
3626         // options is an options object to pass on to the series renderers.
3627         // It can be an empty object {}.  idx is the series index
3628         // to redraw if only one series is to be redrawn.
3629         this.drawSeries = function(options, idx){
3630             var i, series, ctx;
3631             // if only one argument passed in and it is a number, use it ad idx.
3632             idx = (typeof(options) === "number" && idx == null) ? options : idx;
3633             options = (typeof(options) === "object") ? options : {};
3634             // draw specified series
3635             if (idx != undefined) {
3636                 series = this.series[idx];
3637                 ctx = series.shadowCanvas._ctx;
3638                 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3639                 series.drawShadow(ctx, options, this);
3640                 ctx = series.canvas._ctx;
3641                 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3642                 series.draw(ctx, options, this);
3643                 if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) {
3644                     if (idx < this.series.length - 1) {
3645                         this.drawSeries(idx+1); 
3646                     }
3647                 }
3648             }
3649             
3650             else {
3651                 // if call series drawShadow method first, in case all series shadows
3652                 // should be drawn before any series.  This will ensure, like for 
3653                 // stacked bar plots, that shadows don't overlap series.
3654                 for (i=0; i<this.series.length; i++) {
3655                     // first clear the canvas
3656                     series = this.series[i];
3657                     ctx = series.shadowCanvas._ctx;
3658                     ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3659                     series.drawShadow(ctx, options, this);
3660                     ctx = series.canvas._ctx;
3661                     ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3662                     series.draw(ctx, options, this);
3663                 }
3664             }
3665             options = idx = i = series = ctx = null;
3666         };
3667         
3668         // method: moveSeriesToFront
3669         // This method requires jQuery 1.4+
3670         // Moves the specified series canvas in front of all other series canvases.
3671         // This effectively "draws" the specified series on top of all other series,
3672         // although it is performed through DOM manipulation, no redrawing is performed.
3673         //
3674         // Parameters:
3675         // idx - 0 based index of the series to move.  This will be the index of the series
3676         // as it was first passed into the jqplot function.
3677         this.moveSeriesToFront = function (idx) { 
3678             idx = parseInt(idx, 10);
3679             var stackIndex = $.inArray(idx, this.seriesStack);
3680             // if already in front, return
3681             if (stackIndex == -1) {
3682                 return;
3683             }
3684             if (stackIndex == this.seriesStack.length -1) {
3685                 this.previousSeriesStack = this.seriesStack.slice(0);
3686                 return;
3687             }
3688             var opidx = this.seriesStack[this.seriesStack.length -1];
3689             var serelem = this.series[idx].canvas._elem.detach();
3690             var shadelem = this.series[idx].shadowCanvas._elem.detach();
3691             this.series[opidx].shadowCanvas._elem.after(shadelem);
3692             this.series[opidx].canvas._elem.after(serelem);
3693             this.previousSeriesStack = this.seriesStack.slice(0);
3694             this.seriesStack.splice(stackIndex, 1);
3695             this.seriesStack.push(idx);
3696         };
3697         
3698         // method: moveSeriesToBack
3699         // This method requires jQuery 1.4+
3700         // Moves the specified series canvas behind all other series canvases.
3701         //
3702         // Parameters:
3703         // idx - 0 based index of the series to move.  This will be the index of the series
3704         // as it was first passed into the jqplot function.
3705         this.moveSeriesToBack = function (idx) {
3706             idx = parseInt(idx, 10);
3707             var stackIndex = $.inArray(idx, this.seriesStack);
3708             // if already in back, return
3709             if (stackIndex == 0 || stackIndex == -1) {
3710                 return;
3711             }
3712             var opidx = this.seriesStack[0];
3713             var serelem = this.series[idx].canvas._elem.detach();
3714             var shadelem = this.series[idx].shadowCanvas._elem.detach();
3715             this.series[opidx].shadowCanvas._elem.before(shadelem);
3716             this.series[opidx].canvas._elem.before(serelem);
3717             this.previousSeriesStack = this.seriesStack.slice(0);
3718             this.seriesStack.splice(stackIndex, 1);
3719             this.seriesStack.unshift(idx);
3720         };
3721         
3722         // method: restorePreviousSeriesOrder
3723         // This method requires jQuery 1.4+
3724         // Restore the series canvas order to its previous state.
3725         // Useful to put a series back where it belongs after moving
3726         // it to the front.
3727         this.restorePreviousSeriesOrder = function () {
3728             var i, j, serelem, shadelem, temp, move, keep;
3729             // if no change, return.
3730             if (this.seriesStack == this.previousSeriesStack) {
3731                 return;
3732             }
3733             for (i=1; i<this.previousSeriesStack.length; i++) {
3734                 move = this.previousSeriesStack[i];
3735                 keep = this.previousSeriesStack[i-1];
3736                 serelem = this.series[move].canvas._elem.detach();
3737                 shadelem = this.series[move].shadowCanvas._elem.detach();
3738                 this.series[keep].shadowCanvas._elem.after(shadelem);
3739                 this.series[keep].canvas._elem.after(serelem);
3740             }
3741             temp = this.seriesStack.slice(0);
3742             this.seriesStack = this.previousSeriesStack.slice(0);
3743             this.previousSeriesStack = temp;
3744         };
3745         
3746         // method: restoreOriginalSeriesOrder
3747         // This method requires jQuery 1.4+
3748         // Restore the series canvas order to its original order
3749         // when the plot was created.
3750         this.restoreOriginalSeriesOrder = function () {
3751             var i, j, arr=[], serelem, shadelem;
3752             for (i=0; i<this.series.length; i++) {
3753                 arr.push(i);
3754             }
3755             if (this.seriesStack == arr) {
3756                 return;
3757             }
3758             this.previousSeriesStack = this.seriesStack.slice(0);
3759             this.seriesStack = arr;
3760             for (i=1; i<this.seriesStack.length; i++) {
3761                 serelem = this.series[i].canvas._elem.detach();
3762                 shadelem = this.series[i].shadowCanvas._elem.detach();
3763                 this.series[i-1].shadowCanvas._elem.after(shadelem);
3764                 this.series[i-1].canvas._elem.after(serelem);
3765             }
3766         };
3767         
3768         this.activateTheme = function (name) {
3769             this.themeEngine.activate(this, name);
3770         };
3771     }
3772     
3773     
3774     // conpute a highlight color or array of highlight colors from given colors.
3775     $.jqplot.computeHighlightColors  = function(colors) {
3776         var ret;
3777         if ($.isArray(colors)) {
3778             ret = [];
3779             for (var i=0; i<colors.length; i++){
3780                 var rgba = $.jqplot.getColorComponents(colors[i]);
3781                 var newrgb = [rgba[0], rgba[1], rgba[2]];
3782                 var sum = newrgb[0] + newrgb[1] + newrgb[2];
3783                 for (var j=0; j<3; j++) {
3784                     // when darkening, lowest color component can be is 60.
3785                     newrgb[j] = (sum > 660) ?  newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
3786                     newrgb[j] = parseInt(newrgb[j], 10);
3787                     (newrgb[j] > 255) ? 255 : newrgb[j];
3788                 }
3789                 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
3790                 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
3791                 newrgb[3] = 0.3 + 0.35 * rgba[3];
3792                 ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')');
3793             }
3794         }
3795         else {
3796             var rgba = $.jqplot.getColorComponents(colors);
3797             var newrgb = [rgba[0], rgba[1], rgba[2]];
3798             var sum = newrgb[0] + newrgb[1] + newrgb[2];
3799             for (var j=0; j<3; j++) {
3800                 // when darkening, lowest color component can be is 60.
3801                 // newrgb[j] = (sum > 570) ?  newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
3802                 // newrgb[j] = parseInt(newrgb[j], 10);
3803                 newrgb[j] = (sum > 660) ?  newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
3804                 newrgb[j] = parseInt(newrgb[j], 10);
3805                 (newrgb[j] > 255) ? 255 : newrgb[j];
3806             }
3807             // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
3808             // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
3809             newrgb[3] = 0.3 + 0.35 * rgba[3];
3810             ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')';
3811         }
3812         return ret;
3813     };
3814         
3815    $.jqplot.ColorGenerator = function(colors) {
3816         colors = colors || $.jqplot.config.defaultColors;
3817         var idx = 0;
3818         
3819         this.next = function () { 
3820             if (idx < colors.length) {
3821                 return colors[idx++];
3822             }
3823             else {
3824                 idx = 0;
3825                 return colors[idx++];
3826             }
3827         };
3828         
3829         this.previous = function () { 
3830             if (idx > 0) {
3831                 return colors[idx--];
3832             }
3833             else {
3834                 idx = colors.length-1;
3835                 return colors[idx];
3836             }
3837         };
3838         
3839         // get a color by index without advancing pointer.
3840         this.get = function(i) {
3841             var idx = i - colors.length * Math.floor(i/colors.length);
3842             return colors[idx];
3843         };
3844         
3845         this.setColors = function(c) {
3846             colors = c;
3847         };
3848         
3849         this.reset = function() {
3850             idx = 0;
3851         };
3852
3853         this.getIndex = function() {
3854             return idx;
3855         };
3856
3857         this.setIndex = function(index) {
3858             idx = index;
3859         };
3860     };
3861
3862     // convert a hex color string to rgb string.
3863     // h - 3 or 6 character hex string, with or without leading #
3864     // a - optional alpha
3865     $.jqplot.hex2rgb = function(h, a) {
3866         h = h.replace('#', '');
3867         if (h.length == 3) {
3868             h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2);
3869         }
3870         var rgb;
3871         rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16);
3872         if (a) {
3873             rgb += ', '+a;
3874         }
3875         rgb += ')';
3876         return rgb;
3877     };
3878     
3879     // convert an rgb color spec to a hex spec.  ignore any alpha specification.
3880     $.jqplot.rgb2hex = function(s) {
3881         var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/;
3882         var m = s.match(pat);
3883         var h = '#';
3884         for (var i=1; i<4; i++) {
3885             var temp;
3886             if (m[i].search(/%/) != -1) {
3887                 temp = parseInt(255*m[i]/100, 10).toString(16);
3888                 if (temp.length == 1) {
3889                     temp = '0'+temp;
3890                 }
3891             }
3892             else {
3893                 temp = parseInt(m[i], 10).toString(16);
3894                 if (temp.length == 1) {
3895                     temp = '0'+temp;
3896                 }
3897             }
3898             h += temp;
3899         }
3900         return h;
3901     };
3902     
3903     // given a css color spec, return an rgb css color spec
3904     $.jqplot.normalize2rgb = function(s, a) {
3905         if (s.search(/^ *rgba?\(/) != -1) {
3906             return s; 
3907         }
3908         else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) {
3909             return $.jqplot.hex2rgb(s, a);
3910         }
3911         else {
3912             throw new Error('Invalid color spec');
3913         }
3914     };
3915     
3916     // extract the r, g, b, a color components out of a css color spec.
3917     $.jqplot.getColorComponents = function(s) {
3918         // check to see if a color keyword.
3919         s = $.jqplot.colorKeywordMap[s] || s;
3920         var rgb = $.jqplot.normalize2rgb(s);
3921         var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/;
3922         var m = rgb.match(pat);
3923         var ret = [];
3924         for (var i=1; i<4; i++) {
3925             if (m[i].search(/%/) != -1) {
3926                 ret[i-1] = parseInt(255*m[i]/100, 10);
3927             }
3928             else {
3929                 ret[i-1] = parseInt(m[i], 10);
3930             }
3931         }
3932         ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0;
3933         return ret;
3934     };
3935     
3936     $.jqplot.colorKeywordMap = {
3937         aliceblue: 'rgb(240, 248, 255)',
3938         antiquewhite: 'rgb(250, 235, 215)',
3939         aqua: 'rgb( 0, 255, 255)',
3940         aquamarine: 'rgb(127, 255, 212)',
3941         azure: 'rgb(240, 255, 255)',
3942         beige: 'rgb(245, 245, 220)',
3943         bisque: 'rgb(255, 228, 196)',
3944         black: 'rgb( 0, 0, 0)',
3945         blanchedalmond: 'rgb(255, 235, 205)',
3946         blue: 'rgb( 0, 0, 255)',
3947         blueviolet: 'rgb(138, 43, 226)',
3948         brown: 'rgb(165, 42, 42)',
3949         burlywood: 'rgb(222, 184, 135)',
3950         cadetblue: 'rgb( 95, 158, 160)',
3951         chartreuse: 'rgb(127, 255, 0)',
3952         chocolate: 'rgb(210, 105, 30)',
3953         coral: 'rgb(255, 127, 80)',
3954         cornflowerblue: 'rgb(100, 149, 237)',
3955         cornsilk: 'rgb(255, 248, 220)',
3956         crimson: 'rgb(220, 20, 60)',
3957         cyan: 'rgb( 0, 255, 255)',
3958         darkblue: 'rgb( 0, 0, 139)',
3959         darkcyan: 'rgb( 0, 139, 139)',
3960         darkgoldenrod: 'rgb(184, 134, 11)',
3961         darkgray: 'rgb(169, 169, 169)',
3962         darkgreen: 'rgb( 0, 100, 0)',
3963         darkgrey: 'rgb(169, 169, 169)',
3964         darkkhaki: 'rgb(189, 183, 107)',
3965         darkmagenta: 'rgb(139, 0, 139)',
3966         darkolivegreen: 'rgb( 85, 107, 47)',
3967         darkorange: 'rgb(255, 140, 0)',
3968         darkorchid: 'rgb(153, 50, 204)',
3969         darkred: 'rgb(139, 0, 0)',
3970         darksalmon: 'rgb(233, 150, 122)',
3971         darkseagreen: 'rgb(143, 188, 143)',
3972         darkslateblue: 'rgb( 72, 61, 139)',
3973         darkslategray: 'rgb( 47, 79, 79)',
3974         darkslategrey: 'rgb( 47, 79, 79)',
3975         darkturquoise: 'rgb( 0, 206, 209)',
3976         darkviolet: 'rgb(148, 0, 211)',
3977         deeppink: 'rgb(255, 20, 147)',
3978         deepskyblue: 'rgb( 0, 191, 255)',
3979         dimgray: 'rgb(105, 105, 105)',
3980         dimgrey: 'rgb(105, 105, 105)',
3981         dodgerblue: 'rgb( 30, 144, 255)',
3982         firebrick: 'rgb(178, 34, 34)',
3983         floralwhite: 'rgb(255, 250, 240)',
3984         forestgreen: 'rgb( 34, 139, 34)',
3985         fuchsia: 'rgb(255, 0, 255)',
3986         gainsboro: 'rgb(220, 220, 220)',
3987         ghostwhite: 'rgb(248, 248, 255)',
3988         gold: 'rgb(255, 215, 0)',
3989         goldenrod: 'rgb(218, 165, 32)',
3990         gray: 'rgb(128, 128, 128)',
3991         grey: 'rgb(128, 128, 128)',
3992         green: 'rgb( 0, 128, 0)',
3993         greenyellow: 'rgb(173, 255, 47)',
3994         honeydew: 'rgb(240, 255, 240)',
3995         hotpink: 'rgb(255, 105, 180)',
3996         indianred: 'rgb(205, 92, 92)',
3997         indigo: 'rgb( 75, 0, 130)',
3998         ivory: 'rgb(255, 255, 240)',
3999         khaki: 'rgb(240, 230, 140)',
4000         lavender: 'rgb(230, 230, 250)',
4001         lavenderblush: 'rgb(255, 240, 245)',
4002         lawngreen: 'rgb(124, 252, 0)',
4003         lemonchiffon: 'rgb(255, 250, 205)',
4004         lightblue: 'rgb(173, 216, 230)',
4005         lightcoral: 'rgb(240, 128, 128)',
4006         lightcyan: 'rgb(224, 255, 255)',
4007         lightgoldenrodyellow: 'rgb(250, 250, 210)',
4008         lightgray: 'rgb(211, 211, 211)',
4009         lightgreen: 'rgb(144, 238, 144)',
4010         lightgrey: 'rgb(211, 211, 211)',
4011         lightpink: 'rgb(255, 182, 193)',
4012         lightsalmon: 'rgb(255, 160, 122)',
4013         lightseagreen: 'rgb( 32, 178, 170)',
4014         lightskyblue: 'rgb(135, 206, 250)',
4015         lightslategray: 'rgb(119, 136, 153)',
4016         lightslategrey: 'rgb(119, 136, 153)',
4017         lightsteelblue: 'rgb(176, 196, 222)',
4018         lightyellow: 'rgb(255, 255, 224)',
4019         lime: 'rgb( 0, 255, 0)',
4020         limegreen: 'rgb( 50, 205, 50)',
4021         linen: 'rgb(250, 240, 230)',
4022         magenta: 'rgb(255, 0, 255)',
4023         maroon: 'rgb(128, 0, 0)',
4024         mediumaquamarine: 'rgb(102, 205, 170)',
4025         mediumblue: 'rgb( 0, 0, 205)',
4026         mediumorchid: 'rgb(186, 85, 211)',
4027         mediumpurple: 'rgb(147, 112, 219)',
4028         mediumseagreen: 'rgb( 60, 179, 113)',
4029         mediumslateblue: 'rgb(123, 104, 238)',
4030         mediumspringgreen: 'rgb( 0, 250, 154)',
4031         mediumturquoise: 'rgb( 72, 209, 204)',
4032         mediumvioletred: 'rgb(199, 21, 133)',
4033         midnightblue: 'rgb( 25, 25, 112)',
4034         mintcream: 'rgb(245, 255, 250)',
4035         mistyrose: 'rgb(255, 228, 225)',
4036         moccasin: 'rgb(255, 228, 181)',
4037         navajowhite: 'rgb(255, 222, 173)',
4038         navy: 'rgb( 0, 0, 128)',
4039         oldlace: 'rgb(253, 245, 230)',
4040         olive: 'rgb(128, 128, 0)',
4041         olivedrab: 'rgb(107, 142, 35)',
4042         orange: 'rgb(255, 165, 0)',
4043         orangered: 'rgb(255, 69, 0)',
4044         orchid: 'rgb(218, 112, 214)',
4045         palegoldenrod: 'rgb(238, 232, 170)',
4046         palegreen: 'rgb(152, 251, 152)',
4047         paleturquoise: 'rgb(175, 238, 238)',
4048         palevioletred: 'rgb(219, 112, 147)',
4049         papayawhip: 'rgb(255, 239, 213)',
4050         peachpuff: 'rgb(255, 218, 185)',
4051         peru: 'rgb(205, 133, 63)',
4052         pink: 'rgb(255, 192, 203)',
4053         plum: 'rgb(221, 160, 221)',
4054         powderblue: 'rgb(176, 224, 230)',
4055         purple: 'rgb(128, 0, 128)',
4056         red: 'rgb(255, 0, 0)',
4057         rosybrown: 'rgb(188, 143, 143)',
4058         royalblue: 'rgb( 65, 105, 225)',
4059         saddlebrown: 'rgb(139, 69, 19)',
4060         salmon: 'rgb(250, 128, 114)',
4061         sandybrown: 'rgb(244, 164, 96)',
4062         seagreen: 'rgb( 46, 139, 87)',
4063         seashell: 'rgb(255, 245, 238)',
4064         sienna: 'rgb(160, 82, 45)',
4065         silver: 'rgb(192, 192, 192)',
4066         skyblue: 'rgb(135, 206, 235)',
4067         slateblue: 'rgb(106, 90, 205)',
4068         slategray: 'rgb(112, 128, 144)',
4069         slategrey: 'rgb(112, 128, 144)',
4070         snow: 'rgb(255, 250, 250)',
4071         springgreen: 'rgb( 0, 255, 127)',
4072         steelblue: 'rgb( 70, 130, 180)',
4073         tan: 'rgb(210, 180, 140)',
4074         teal: 'rgb( 0, 128, 128)',
4075         thistle: 'rgb(216, 191, 216)',
4076         tomato: 'rgb(255, 99, 71)',
4077         turquoise: 'rgb( 64, 224, 208)',
4078         violet: 'rgb(238, 130, 238)',
4079         wheat: 'rgb(245, 222, 179)',
4080         white: 'rgb(255, 255, 255)',
4081         whitesmoke: 'rgb(245, 245, 245)',
4082         yellow: 'rgb(255, 255, 0)',
4083         yellowgreen: 'rgb(154, 205, 50)'
4084     };
4085
4086     
4087
4088
4089     // class: $.jqplot.AxisLabelRenderer
4090     // Renderer to place labels on the axes.
4091     $.jqplot.AxisLabelRenderer = function(options) {
4092         // Group: Properties
4093         $.jqplot.ElemContainer.call(this);
4094         // name of the axis associated with this tick
4095         this.axis;
4096         // prop: show
4097         // whether or not to show the tick (mark and label).
4098         this.show = true;
4099         // prop: label
4100         // The text or html for the label.
4101         this.label = '';
4102         this.fontFamily = null;
4103         this.fontSize = null;
4104         this.textColor = null;
4105         this._elem;
4106         // prop: escapeHTML
4107         // true to escape HTML entities in the label.
4108         this.escapeHTML = false;
4109         
4110         $.extend(true, this, options);
4111     };
4112     
4113     $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer();
4114     $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer;
4115     
4116     $.jqplot.AxisLabelRenderer.prototype.init = function(options) {
4117         $.extend(true, this, options);
4118     };
4119     
4120     $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) {
4121         // Memory Leaks patch
4122         if (this._elem) {
4123             this._elem.emptyForce();
4124             this._elem = null;
4125         }
4126
4127         this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>');
4128         
4129         if (Number(this.label)) {
4130             this._elem.css('white-space', 'nowrap');
4131         }
4132         
4133         if (!this.escapeHTML) {
4134             this._elem.html(this.label);
4135         }
4136         else {
4137             this._elem.text(this.label);
4138         }
4139         if (this.fontFamily) {
4140             this._elem.css('font-family', this.fontFamily);
4141         }
4142         if (this.fontSize) {
4143             this._elem.css('font-size', this.fontSize);
4144         }
4145         if (this.textColor) {
4146             this._elem.css('color', this.textColor);
4147         }
4148         
4149         return this._elem;
4150     };
4151     
4152     $.jqplot.AxisLabelRenderer.prototype.pack = function() {
4153     };
4154
4155     // class: $.jqplot.AxisTickRenderer
4156     // A "tick" object showing the value of a tick/gridline on the plot.
4157     $.jqplot.AxisTickRenderer = function(options) {
4158         // Group: Properties
4159         $.jqplot.ElemContainer.call(this);
4160         // prop: mark
4161         // tick mark on the axis.  One of 'inside', 'outside', 'cross', '' or null.
4162         this.mark = 'outside';
4163         // name of the axis associated with this tick
4164         this.axis;
4165         // prop: showMark
4166         // whether or not to show the mark on the axis.
4167         this.showMark = true;
4168         // prop: showGridline
4169         // whether or not to draw the gridline on the grid at this tick.
4170         this.showGridline = true;
4171         // prop: isMinorTick
4172         // if this is a minor tick.
4173         this.isMinorTick = false;
4174         // prop: size
4175         // Length of the tick beyond the grid in pixels.
4176         // DEPRECATED: This has been superceeded by markSize
4177         this.size = 4;
4178         // prop:  markSize
4179         // Length of the tick marks in pixels.  For 'cross' style, length
4180         // will be stoked above and below axis, so total length will be twice this.
4181         this.markSize = 6;
4182         // prop: show
4183         // whether or not to show the tick (mark and label).
4184         // Setting this to false requires more testing.  It is recommended
4185         // to set showLabel and showMark to false instead.
4186         this.show = true;
4187         // prop: showLabel
4188         // whether or not to show the label.
4189         this.showLabel = true;
4190         this.label = null;
4191         this.value = null;
4192         this._styles = {};
4193         // prop: formatter
4194         // A class of a formatter for the tick text.  sprintf by default.
4195         this.formatter = $.jqplot.DefaultTickFormatter;
4196         // prop: prefix
4197         // String to prepend to the tick label.
4198         // Prefix is prepended to the formatted tick label.
4199         this.prefix = '';
4200         // prop: suffix
4201         // String to append to the tick label.
4202         // Suffix is appended to the formatted tick label.
4203         this.suffix = '';
4204         // prop: formatString
4205         // string passed to the formatter.
4206         this.formatString = '';
4207         // prop: fontFamily
4208         // css spec for the font-family css attribute.
4209         this.fontFamily;
4210         // prop: fontSize
4211         // css spec for the font-size css attribute.
4212         this.fontSize;
4213         // prop: textColor
4214         // css spec for the color attribute.
4215         this.textColor;
4216         // prop: escapeHTML
4217         // true to escape HTML entities in the label.
4218         this.escapeHTML = false;
4219         this._elem;
4220         this._breakTick = false;
4221         
4222         $.extend(true, this, options);
4223     };
4224     
4225     $.jqplot.AxisTickRenderer.prototype.init = function(options) {
4226         $.extend(true, this, options);
4227     };
4228     
4229     $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
4230     $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;
4231     
4232     $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
4233         this.value = value;
4234         this.axis = axisName;
4235         if (isMinor) {
4236             this.isMinorTick = true;
4237         }
4238         return this;
4239     };
4240     
4241     $.jqplot.AxisTickRenderer.prototype.draw = function() {
4242         if (this.label === null) {
4243             this.label = this.prefix + this.formatter(this.formatString, this.value) + this.suffix;
4244         }
4245         var style = {position: 'absolute'};
4246         if (Number(this.label)) {
4247             style['whitSpace'] = 'nowrap';
4248         }
4249         
4250         // Memory Leaks patch
4251         if (this._elem) {
4252             this._elem.emptyForce();
4253             this._elem = null;
4254         }
4255
4256         this._elem = $(document.createElement('div'));
4257         this._elem.addClass("jqplot-"+this.axis+"-tick");
4258         
4259         if (!this.escapeHTML) {
4260             this._elem.html(this.label);
4261         }
4262         else {
4263             this._elem.text(this.label);
4264         }
4265         
4266         this._elem.css(style);
4267
4268         for (var s in this._styles) {
4269             this._elem.css(s, this._styles[s]);
4270         }
4271         if (this.fontFamily) {
4272             this._elem.css('font-family', this.fontFamily);
4273         }
4274         if (this.fontSize) {
4275             this._elem.css('font-size', this.fontSize);
4276         }
4277         if (this.textColor) {
4278             this._elem.css('color', this.textColor);
4279         }
4280         if (this._breakTick) {
4281           this._elem.addClass('jqplot-breakTick');
4282         }
4283         
4284         return this._elem;
4285     };
4286         
4287     $.jqplot.DefaultTickFormatter = function (format, val) {
4288         if (typeof val == 'number') {
4289             if (!format) {
4290                 format = $.jqplot.config.defaultTickFormatString;
4291             }
4292             return $.jqplot.sprintf(format, val);
4293         }
4294         else {
4295             return String(val);
4296         }
4297     };
4298         
4299     $.jqplot.PercentTickFormatter = function (format, val) {
4300         if (typeof val == 'number') {
4301             val = 100 * val;
4302             if (!format) {
4303                 format = $.jqplot.config.defaultTickFormatString;
4304             }
4305             return $.jqplot.sprintf(format, val);
4306         }
4307         else {
4308             return String(val);
4309         }
4310     };
4311     
4312     $.jqplot.AxisTickRenderer.prototype.pack = function() {
4313     };
4314      
4315     // Class: $.jqplot.CanvasGridRenderer
4316     // The default jqPlot grid renderer, creating a grid on a canvas element.
4317     // The renderer has no additional options beyond the <Grid> class.
4318     $.jqplot.CanvasGridRenderer = function(){
4319         this.shadowRenderer = new $.jqplot.ShadowRenderer();
4320     };
4321     
4322     // called with context of Grid object
4323     $.jqplot.CanvasGridRenderer.prototype.init = function(options) {
4324         this._ctx;
4325         $.extend(true, this, options);
4326         // set the shadow renderer options
4327         var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.shadowWidth, closePath:false, strokeStyle:this.shadowColor};
4328         this.renderer.shadowRenderer.init(sopts);
4329     };
4330     
4331     // called with context of Grid.
4332     $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) {
4333         var elem;
4334         // Memory Leaks patch
4335         if (this._elem) {
4336           if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
4337             elem = this._elem.get(0);
4338             window.G_vmlCanvasManager.uninitElement(elem);
4339             elem = null;
4340           }
4341           
4342           this._elem.emptyForce();
4343           this._elem = null;
4344         }
4345       
4346         elem = plot.canvasManager.getCanvas();
4347
4348         var w = this._plotDimensions.width;
4349         var h = this._plotDimensions.height;
4350         elem.width = w;
4351         elem.height = h;
4352         this._elem = $(elem);
4353         this._elem.addClass('jqplot-grid-canvas');
4354         this._elem.css({ position: 'absolute', left: 0, top: 0 });
4355         
4356         elem = plot.canvasManager.initCanvas(elem);
4357
4358         this._top = this._offsets.top;
4359         this._bottom = h - this._offsets.bottom;
4360         this._left = this._offsets.left;
4361         this._right = w - this._offsets.right;
4362         this._width = this._right - this._left;
4363         this._height = this._bottom - this._top;
4364         // avoid memory leak
4365         elem = null;
4366         return this._elem;
4367     };
4368     
4369     $.jqplot.CanvasGridRenderer.prototype.draw = function() {
4370         this._ctx = this._elem.get(0).getContext("2d");
4371         var ctx = this._ctx;
4372         var axes = this._axes;
4373         // Add the grid onto the grid canvas.  This is the bottom most layer.
4374         ctx.save();
4375         ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
4376         ctx.fillStyle = this.backgroundColor || this.background;
4377         ctx.fillRect(this._left, this._top, this._width, this._height);
4378         
4379         ctx.save();
4380         ctx.lineJoin = 'miter';
4381         ctx.lineCap = 'butt';
4382         ctx.lineWidth = this.gridLineWidth;
4383         ctx.strokeStyle = this.gridLineColor;
4384         var b, e, s, m;
4385         var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis'];
4386         for (var i=4; i>0; i--) {
4387             var name = ax[i-1];
4388             var axis = axes[name];
4389             var ticks = axis._ticks;
4390             var numticks = ticks.length;
4391             if (axis.show) {
4392                 if (axis.drawBaseline) {
4393                     var bopts = {};
4394                     if (axis.baselineWidth !== null) {
4395                         bopts.lineWidth = axis.baselineWidth;
4396                     }
4397                     if (axis.baselineColor !== null) {
4398                         bopts.strokeStyle = axis.baselineColor;
4399                     }
4400                     switch (name) {
4401                         case 'xaxis':
4402                             drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
4403                             break;
4404                         case 'yaxis':
4405                             drawLine (this._left, this._bottom, this._left, this._top, bopts);
4406                             break;
4407                         case 'x2axis':
4408                             drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
4409                             break;
4410                         case 'y2axis':
4411                             drawLine (this._right, this._bottom, this._right, this._top, bopts);
4412                             break;
4413                     }
4414                 }
4415                 for (var j=numticks; j>0; j--) {
4416                     var t = ticks[j-1];
4417                     if (t.show) {
4418                         var pos = Math.round(axis.u2p(t.value)) + 0.5;
4419                         switch (name) {
4420                             case 'xaxis':
4421                                 // draw the grid line if we should
4422                                 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4423                                     drawLine(pos, this._top, pos, this._bottom);
4424                                 }
4425                                 // draw the mark
4426                                 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4427                                     s = t.markSize;
4428                                     m = t.mark;
4429                                     var pos = Math.round(axis.u2p(t.value)) + 0.5;
4430                                     switch (m) {
4431                                         case 'outside':
4432                                             b = this._bottom;
4433                                             e = this._bottom+s;
4434                                             break;
4435                                         case 'inside':
4436                                             b = this._bottom-s;
4437                                             e = this._bottom;
4438                                             break;
4439                                         case 'cross':
4440                                             b = this._bottom-s;
4441                                             e = this._bottom+s;
4442                                             break;
4443                                         default:
4444                                             b = this._bottom;
4445                                             e = this._bottom+s;
4446                                             break;
4447                                     }
4448                                     // draw the shadow
4449                                     if (this.shadow) {
4450                                         this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
4451                                     }
4452                                     // draw the line
4453                                     drawLine(pos, b, pos, e);
4454                                 }
4455                                 break;
4456                             case 'yaxis':
4457                                 // draw the grid line
4458                                 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4459                                     drawLine(this._right, pos, this._left, pos);
4460                                 }
4461                                 // draw the mark
4462                                 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4463                                     s = t.markSize;
4464                                     m = t.mark;
4465                                     var pos = Math.round(axis.u2p(t.value)) + 0.5;
4466                                     switch (m) {
4467                                         case 'outside':
4468                                             b = this._left-s;
4469                                             e = this._left;
4470                                             break;
4471                                         case 'inside':
4472                                             b = this._left;
4473                                             e = this._left+s;
4474                                             break;
4475                                         case 'cross':
4476                                             b = this._left-s;
4477                                             e = this._left+s;
4478                                             break;
4479                                         default:
4480                                             b = this._left-s;
4481                                             e = this._left;
4482                                             break;
4483                                             }
4484                                     // draw the shadow
4485                                     if (this.shadow) {
4486                                         this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
4487                                     }
4488                                     drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4489                                 }
4490                                 break;
4491                             case 'x2axis':
4492                                 // draw the grid line
4493                                 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4494                                     drawLine(pos, this._bottom, pos, this._top);
4495                                 }
4496                                 // draw the mark
4497                                 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4498                                     s = t.markSize;
4499                                     m = t.mark;
4500                                     var pos = Math.round(axis.u2p(t.value)) + 0.5;
4501                                     switch (m) {
4502                                         case 'outside':
4503                                             b = this._top-s;
4504                                             e = this._top;
4505                                             break;
4506                                         case 'inside':
4507                                             b = this._top;
4508                                             e = this._top+s;
4509                                             break;
4510                                         case 'cross':
4511                                             b = this._top-s;
4512                                             e = this._top+s;
4513                                             break;
4514                                         default:
4515                                             b = this._top-s;
4516                                             e = this._top;
4517                                             break;
4518                                             }
4519                                     // draw the shadow
4520                                     if (this.shadow) {
4521                                         this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false});
4522                                     }
4523                                     drawLine(pos, b, pos, e);
4524                                 }
4525                                 break;
4526                             case 'y2axis':
4527                                 // draw the grid line
4528                                 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4529                                     drawLine(this._left, pos, this._right, pos);
4530                                 }
4531                                 // draw the mark
4532                                 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4533                                     s = t.markSize;
4534                                     m = t.mark;
4535                                     var pos = Math.round(axis.u2p(t.value)) + 0.5;
4536                                     switch (m) {
4537                                         case 'outside':
4538                                             b = this._right;
4539                                             e = this._right+s;
4540                                             break;
4541                                         case 'inside':
4542                                             b = this._right-s;
4543                                             e = this._right;
4544                                             break;
4545                                         case 'cross':
4546                                             b = this._right-s;
4547                                             e = this._right+s;
4548                                             break;
4549                                         default:
4550                                             b = this._right;
4551                                             e = this._right+s;
4552                                             break;
4553                                             }
4554                                     // draw the shadow
4555                                     if (this.shadow) {
4556                                         this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
4557                                     }
4558                                     drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4559                                 }
4560                                 break;
4561                             default:
4562                                 break;
4563                         }
4564                     }
4565                 }
4566                 t = null;
4567             }
4568             axis = null;
4569             ticks = null;
4570         }
4571         // Now draw grid lines for additional y axes
4572         //////
4573         // TO DO: handle yMidAxis
4574         //////
4575         ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
4576         for (var i=7; i>0; i--) {
4577             var axis = axes[ax[i-1]];
4578             var ticks = axis._ticks;
4579             if (axis.show) {
4580                 var tn = ticks[axis.numberTicks-1];
4581                 var t0 = ticks[0];
4582                 var left = axis.getLeft();
4583                 var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]];
4584                 // draw the shadow
4585                 if (this.shadow) {
4586                     this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false});
4587                 }
4588                 // draw the line
4589                 drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth});
4590                 // draw the tick marks
4591                 for (var j=ticks.length; j>0; j--) {
4592                     var t = ticks[j-1];
4593                     s = t.markSize;
4594                     m = t.mark;
4595                     var pos = Math.round(axis.u2p(t.value)) + 0.5;
4596                     if (t.showMark && t.mark) {
4597                         switch (m) {
4598                             case 'outside':
4599                                 b = left;
4600                                 e = left+s;
4601                                 break;
4602                             case 'inside':
4603                                 b = left-s;
4604                                 e = left;
4605                                 break;
4606                             case 'cross':
4607                                 b = left-s;
4608                                 e = left+s;
4609                                 break;
4610                             default:
4611                                 b = left;
4612                                 e = left+s;
4613                                 break;
4614                         }
4615                         points = [[b,pos], [e,pos]];
4616                         // draw the shadow
4617                         if (this.shadow) {
4618                             this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
4619                         }
4620                         // draw the line
4621                         drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4622                     }
4623                     t = null;
4624                 }
4625                 t0 = null;
4626             }
4627             axis = null;
4628             ticks =  null;
4629         }
4630         
4631         ctx.restore();
4632         
4633         function drawLine(bx, by, ex, ey, opts) {
4634             ctx.save();
4635             opts = opts || {};
4636             if (opts.lineWidth == null || opts.lineWidth != 0){
4637                 $.extend(true, ctx, opts);
4638                 ctx.beginPath();
4639                 ctx.moveTo(bx, by);
4640                 ctx.lineTo(ex, ey);
4641                 ctx.stroke();
4642                 ctx.restore();
4643             }
4644         }
4645         
4646         if (this.shadow) {
4647             var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
4648             this.renderer.shadowRenderer.draw(ctx, points);
4649         }
4650         // Now draw border around grid.  Use axis border definitions. start at
4651         // upper left and go clockwise.
4652         if (this.borderWidth != 0 && this.drawBorder) {
4653             drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
4654             drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
4655             drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
4656             drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
4657         }
4658         // ctx.lineWidth = this.borderWidth;
4659         // ctx.strokeStyle = this.borderColor;
4660         // ctx.strokeRect(this._left, this._top, this._width, this._height);
4661         
4662         ctx.restore();
4663         ctx =  null;
4664         axes = null;
4665     };
4666  
4667     // Class: $.jqplot.DivTitleRenderer
4668     // The default title renderer for jqPlot.  This class has no options beyond the <Title> class. 
4669     $.jqplot.DivTitleRenderer = function() {
4670     };
4671     
4672     $.jqplot.DivTitleRenderer.prototype.init = function(options) {
4673         $.extend(true, this, options);
4674     };
4675     
4676     $.jqplot.DivTitleRenderer.prototype.draw = function() {
4677         // Memory Leaks patch
4678         if (this._elem) {
4679             this._elem.emptyForce();
4680             this._elem = null;
4681         }
4682
4683         var r = this.renderer;
4684         var elem = document.createElement('div');
4685         this._elem = $(elem);
4686         this._elem.addClass('jqplot-title');
4687
4688         if (!this.text) {
4689             this.show = false;
4690             this._elem.height(0);
4691             this._elem.width(0);
4692         }
4693         else if (this.text) {
4694             var color;
4695             if (this.color) {
4696                 color = this.color;
4697             }
4698             else if (this.textColor) {
4699                 color = this.textColor;
4700             }
4701
4702             // don't trust that a stylesheet is present, set the position.
4703             var styles = {position:'absolute', top:'0px', left:'0px'};
4704
4705             if (this._plotWidth) {
4706                 styles['width'] = this._plotWidth+'px';
4707             }
4708             if (this.fontSize) {
4709                 styles['fontSize'] = this.fontSize;
4710             }
4711             if (typeof this.textAlign === 'string') {
4712                 styles['textAlign'] = this.textAlign;
4713             }
4714             else {
4715                 styles['textAlign'] = 'center';
4716             }
4717             if (color) {
4718                 styles['color'] = color;
4719             }
4720             if (this.paddingBottom) {
4721                 styles['paddingBottom'] = this.paddingBottom;
4722             }
4723             if (this.fontFamily) {
4724                 styles['fontFamily'] = this.fontFamily;
4725             }
4726
4727             this._elem.css(styles);
4728             if (this.escapeHtml) {
4729                 this._elem.text(this.text);
4730             }
4731             else {
4732                 this._elem.html(this.text);
4733             }
4734
4735
4736             // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : '';
4737             // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
4738             // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;';
4739             // styletext += (color) ? 'color:'+color+';' : '';
4740             // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : '';
4741             // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>');
4742             // if (this.fontFamily) {
4743             //     this._elem.css('font-family', this.fontFamily);
4744             // }
4745         }
4746
4747         elem = null;
4748         
4749         return this._elem;
4750     };
4751     
4752     $.jqplot.DivTitleRenderer.prototype.pack = function() {
4753         // nothing to do here
4754     };
4755   
4756
4757     var dotlen = 0.1;
4758
4759     $.jqplot.LinePattern = function (ctx, pattern) {
4760
4761         var defaultLinePatterns = {
4762             dotted: [ dotlen, $.jqplot.config.dotGapLength ],
4763             dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ],
4764             solid: null
4765         };
4766
4767         if (typeof pattern === 'string') {
4768             if (pattern[0] === '.' || pattern[0] === '-') {
4769                 var s = pattern;
4770                 pattern = [];
4771                 for (var i=0, imax=s.length; i<imax; i++) {
4772                     if (s[i] === '.') {
4773                         pattern.push( dotlen );
4774                     }
4775                     else if (s[i] === '-') {
4776                         pattern.push( $.jqplot.config.dashLength );
4777                     }
4778                     else {
4779                         continue;
4780                     }
4781                     pattern.push( $.jqplot.config.gapLength );
4782                 }
4783             }
4784             else {
4785                 pattern = defaultLinePatterns[pattern];
4786             }
4787         }
4788
4789         if (!(pattern && pattern.length)) {
4790             return ctx;
4791         }
4792
4793         var patternIndex = 0;
4794         var patternDistance = pattern[0];
4795         var px = 0;
4796         var py = 0;
4797         var pathx0 = 0;
4798         var pathy0 = 0;
4799
4800         var moveTo = function (x, y) {
4801             ctx.moveTo( x, y );
4802             px = x;
4803             py = y;
4804             pathx0 = x;
4805             pathy0 = y;
4806         };
4807
4808         var lineTo = function (x, y) {
4809             var scale = ctx.lineWidth;
4810             var dx = x - px;
4811             var dy = y - py;
4812             var dist = Math.sqrt(dx*dx+dy*dy);
4813             if ((dist > 0) && (scale > 0)) {
4814                 dx /= dist;
4815                 dy /= dist;
4816                 while (true) {
4817                     var dp = scale * patternDistance;
4818                     if (dp < dist) {
4819                         px += dp * dx;
4820                         py += dp * dy;
4821                         if ((patternIndex & 1) == 0) {
4822                             ctx.lineTo( px, py );
4823                         }
4824                         else {
4825                             ctx.moveTo( px, py );
4826                         }
4827                         dist -= dp;
4828                         patternIndex++;
4829                         if (patternIndex >= pattern.length) {
4830                             patternIndex = 0;
4831                         }
4832                         patternDistance = pattern[patternIndex];
4833                     }
4834                     else {
4835                         px = x;
4836                         py = y;
4837                         if ((patternIndex & 1) == 0) {
4838                             ctx.lineTo( px, py );
4839                         }
4840                         else {
4841                             ctx.moveTo( px, py );
4842                         }
4843                         patternDistance -= dist / scale;
4844                         break;
4845                     }
4846                 }
4847             }
4848         };
4849
4850         var beginPath = function () {
4851             ctx.beginPath();
4852         };
4853
4854         var closePath = function () {
4855             lineTo( pathx0, pathy0 );
4856         };
4857
4858         return {
4859             moveTo: moveTo,
4860             lineTo: lineTo,
4861             beginPath: beginPath,
4862             closePath: closePath
4863         };
4864     };
4865
4866     // Class: $.jqplot.LineRenderer
4867     // The default line renderer for jqPlot, this class has no options beyond the <Series> class.
4868     // Draws series as a line.
4869     $.jqplot.LineRenderer = function(){
4870         this.shapeRenderer = new $.jqplot.ShapeRenderer();
4871         this.shadowRenderer = new $.jqplot.ShadowRenderer();
4872     };
4873     
4874     // called with scope of series.
4875     $.jqplot.LineRenderer.prototype.init = function(options, plot) {
4876         // Group: Properties
4877         //
4878         options = options || {};
4879         this._type='line';
4880         this.renderer.animation = {
4881             show: false,
4882             direction: 'left',
4883             speed: 2500,
4884             _supported: true
4885         };
4886         // prop: smooth
4887         // True to draw a smoothed (interpolated) line through the data points
4888         // with automatically computed number of smoothing points.
4889         // Set to an integer number > 2 to specify number of smoothing points
4890         // to use between each data point.
4891         this.renderer.smooth = false;  // true or a number > 2 for smoothing.
4892         this.renderer.tension = null; // null to auto compute or a number typically > 6.  Fewer points requires higher tension.
4893         // prop: constrainSmoothing
4894         // True to use a more accurate smoothing algorithm that will
4895         // not overshoot any data points.  False to allow overshoot but
4896         // produce a smoother looking line.
4897         this.renderer.constrainSmoothing = true;
4898         // this is smoothed data in grid coordinates, like gridData
4899         this.renderer._smoothedData = [];
4900         // this is smoothed data in plot units (plot coordinates), like plotData.
4901         this.renderer._smoothedPlotData = [];
4902         this.renderer._hiBandGridData = [];
4903         this.renderer._lowBandGridData = [];
4904         this.renderer._hiBandSmoothedData = [];
4905         this.renderer._lowBandSmoothedData = [];
4906
4907         // prop: bandData
4908         // Data used to draw error bands or confidence intervals above/below a line.
4909         //
4910         // bandData can be input in 3 forms.  jqPlot will figure out which is the
4911         // low band line and which is the high band line for all forms:
4912         // 
4913         // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where
4914         // [yl1, yl2, ...] are y values of the lower line and
4915         // [yu1, yu2, ...] are y values of the upper line.
4916         // In this case there must be the same number of y data points as data points
4917         // in the series and the bands will inherit the x values of the series.
4918         //
4919         // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]]
4920         // where [xl1, yl1] are x,y data points for the lower line and
4921         // [xh1, yh1] are x,y data points for the high line.
4922         // x values do not have to correspond to the x values of the series and can
4923         // be of any arbitrary length.
4924         //
4925         // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where
4926         // there must be 3 or more arrays and there must be the same number of arrays
4927         // as there are data points in the series.  In this case, 
4928         // [yl1, yu1] specifies the lower and upper y values for the 1st
4929         // data point and so on.  The bands will inherit the x
4930         // values from the series.
4931         this.renderer.bandData = [];
4932
4933         // Group: bands
4934         // Banding around line, e.g error bands or confidence intervals.
4935         this.renderer.bands = {
4936             // prop: show
4937             // true to show the bands.  If bandData or interval is
4938             // supplied, show will be set to true by default.
4939             show: false,
4940             hiData: [],
4941             lowData: [],
4942             // prop: color
4943             // color of lines at top and bottom of bands [default: series color].
4944             color: this.color,
4945             // prop: showLines
4946             // True to show lines at top and bottom of bands [default: false].
4947             showLines: false,
4948             // prop: fill
4949             // True to fill area between bands [default: true].
4950             fill: true,
4951             // prop: fillColor
4952             // css color spec for filled area.  [default: series color].
4953             fillColor: null,
4954             _min: null,
4955             _max: null,
4956             // prop: interval
4957             // User specified interval above and below line for bands [default: '3%''].
4958             // Can be a value like 3 or a string like '3%' 
4959             // or an upper/lower array like [1, -2] or ['2%', '-1.5%']
4960             interval: '3%'
4961         };
4962
4963
4964         var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor};
4965         
4966         delete (options.highlightMouseOver);
4967         delete (options.highlightMouseDown);
4968         delete (options.highlightColor);
4969         
4970         $.extend(true, this.renderer, options);
4971
4972         this.renderer.options = options;
4973
4974         // if we are given some band data, and bands aren't explicity set to false in options, turn them on.
4975         if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) {
4976             this.renderer.bands.show = true;
4977         }
4978
4979         // if we are given an interval, and bands aren't explicity set to false in options, turn them on.
4980         else if (options.bands && options.bands.show == null && options.bands.interval != null) {
4981             this.renderer.bands.show = true;
4982         }
4983
4984         // if plot is filled, turn off bands.
4985         if (this.fill) {
4986             this.renderer.bands.show = false;
4987         }
4988
4989         if (this.renderer.bands.show) {
4990             this.renderer.initBands.call(this, this.renderer.options, plot);
4991         }
4992
4993
4994         // smoothing is not compatible with stacked lines, disable
4995         if (this._stack) {
4996             this.renderer.smooth = false;
4997         }
4998
4999         // set the shape renderer options
5000         var opts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
5001         this.renderer.shapeRenderer.init(opts);
5002
5003         var shadow_offset = options.shadowOffset;
5004         // set the shadow renderer options
5005         if (shadow_offset == null) {
5006             // scale the shadowOffset to the width of the line.
5007             if (this.lineWidth > 2.5) {
5008                 shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
5009                 // var shadow_offset = this.shadowOffset;
5010             }
5011             // for skinny lines, don't make such a big shadow.
5012             else {
5013                 shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
5014             }
5015         }
5016         
5017         var sopts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
5018         this.renderer.shadowRenderer.init(sopts);
5019         this._areaPoints = [];
5020         this._boundingBox = [[],[]];
5021         
5022         if (!this.isTrendline && this.fill || this.renderer.bands.show) {
5023             // Group: Properties
5024             //        
5025             // prop: highlightMouseOver
5026             // True to highlight area on a filled plot when moused over.
5027             // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
5028             this.highlightMouseOver = true;
5029             // prop: highlightMouseDown
5030             // True to highlight when a mouse button is pressed over an area on a filled plot.
5031             // This will be disabled if highlightMouseOver is true.
5032             this.highlightMouseDown = false;
5033             // prop: highlightColor
5034             // color to use when highlighting an area on a filled plot.
5035             this.highlightColor = null;
5036             // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
5037             if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) {
5038                 lopts.highlightMouseOver = false;
5039             }
5040         
5041             $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor});
5042             
5043             if (!this.highlightColor) {
5044                 var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor;
5045                 this.highlightColor = $.jqplot.computeHighlightColors(fc);
5046             }
5047             // turn off (disable) the highlighter plugin
5048             if (this.highlighter) {
5049                 this.highlighter.show = false;
5050             }
5051         }
5052         
5053         if (!this.isTrendline && plot) {
5054             plot.plugins.lineRenderer = {};
5055             plot.postInitHooks.addOnce(postInit);
5056             plot.postDrawHooks.addOnce(postPlotDraw);
5057             plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
5058             plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
5059             plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
5060             plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
5061             plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
5062         }
5063
5064     };
5065
5066     $.jqplot.LineRenderer.prototype.initBands = function(options, plot) {
5067         // use bandData if no data specified in bands option
5068         //var bd = this.renderer.bandData;
5069         var bd = options.bandData || [];
5070         var bands = this.renderer.bands;
5071         bands.hiData = [];
5072         bands.lowData = [];
5073         var data = this.data;
5074         bands._max = null;
5075         bands._min = null;
5076         // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values.
5077         if (bd.length == 2) {
5078             // Do we have an array of x,y values?
5079             // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]]
5080             if ($.isArray(bd[0][0])) {
5081                 // since an arbitrary array of points, spin through all of them to determine max and min lines.
5082
5083                 var p;
5084                 var bdminidx = 0, bdmaxidx = 0;
5085                 for (var i = 0, l = bd[0].length; i<l; i++) {
5086                     p = bd[0][i];
5087                     if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
5088                         bands._max = p[1];
5089                     }
5090                     if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
5091                         bands._min = p[1];
5092                     }
5093                 }
5094                 for (var i = 0, l = bd[1].length; i<l; i++) {
5095                     p = bd[1][i];
5096                     if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
5097                         bands._max = p[1];
5098                         bdmaxidx = 1;
5099                     }
5100                     if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
5101                         bands._min = p[1];
5102                         bdminidx = 1;
5103                     }
5104                 }
5105
5106                 if (bdmaxidx === bdminidx) {
5107                     bands.show = false;
5108                 }
5109
5110                 bands.hiData = bd[bdmaxidx];
5111                 bands.lowData = bd[bdminidx];
5112             }
5113             // else data is arrays of y values
5114             // like [[1,4,3], [3,6,5]]
5115             // must have same number of band data points as points in series
5116             else if (bd[0].length === data.length && bd[1].length === data.length) {
5117                 var hi = (bd[0][0] > bd[1][0]) ? 0 : 1;
5118                 var low = (hi) ? 0 : 1;
5119                 for (var i=0, l=data.length; i < l; i++) {
5120                     bands.hiData.push([data[i][0], bd[hi][i]]);
5121                     bands.lowData.push([data[i][0], bd[low][i]]);
5122                 }
5123             }
5124
5125             // we don't have proper data array, don't show bands.
5126             else {
5127                 bands.show = false;
5128             }
5129         }
5130
5131         // if more than 2 arrays, have arrays of [ylow, yhi] values.
5132         // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]]
5133         // this is assumed to be of the latter form.
5134         else if (bd.length > 2 && !$.isArray(bd[0][0])) {
5135             var hi = (bd[0][0] > bd[0][1]) ? 0 : 1;
5136             var low = (hi) ? 0 : 1;
5137             for (var i=0, l=bd.length; i<l; i++) {
5138                 bands.hiData.push([data[i][0], bd[i][hi]]);
5139                 bands.lowData.push([data[i][0], bd[i][low]]);
5140             }
5141         }
5142
5143         // don't have proper data, auto calculate
5144         else {
5145             var intrv = bands.interval;
5146             var a = null;
5147             var b = null;
5148             var afunc = null;
5149             var bfunc = null;
5150
5151             if ($.isArray(intrv)) {
5152                 a = intrv[0];
5153                 b = intrv[1];
5154             }
5155             else {
5156                 a = intrv;
5157             }
5158
5159             if (isNaN(a)) {
5160                 // we have a string
5161                 if (a.charAt(a.length - 1) === '%') {
5162                     afunc = 'multiply';
5163                     a = parseFloat(a)/100 + 1;
5164                 }
5165             }
5166
5167             else {
5168                 a = parseFloat(a);
5169                 afunc = 'add';
5170             }
5171
5172             if (b !== null && isNaN(b)) {
5173                 // we have a string
5174                 if (b.charAt(b.length - 1) === '%') {
5175                     bfunc = 'multiply';
5176                     b = parseFloat(b)/100 + 1;
5177                 }
5178             }
5179
5180             else if (b !== null) {
5181                 b = parseFloat(b);
5182                 bfunc = 'add';
5183             }
5184
5185             if (a !== null) {
5186                 if (b === null) {
5187                     b = -a;
5188                     bfunc = afunc;
5189                     if (bfunc === 'multiply') {
5190                         b += 2;
5191                     }
5192                 }
5193
5194                 // make sure a always applies to hi band.
5195                 if (a < b) {
5196                     var temp = a;
5197                     a = b;
5198                     b = temp;
5199                     temp = afunc;
5200                     afunc = bfunc;
5201                     bfunc = temp;
5202                 }
5203
5204                 for (var i=0, l = data.length; i < l; i++) {
5205                     switch (afunc) {
5206                         case 'add':
5207                             bands.hiData.push([data[i][0], data[i][1] + a]);
5208                             break;
5209                         case 'multiply':
5210                             bands.hiData.push([data[i][0], data[i][1] * a]);
5211                             break;
5212                     }
5213                     switch (bfunc) {
5214                         case 'add':
5215                             bands.lowData.push([data[i][0], data[i][1] + b]);
5216                             break;
5217                         case 'multiply':
5218                             bands.lowData.push([data[i][0], data[i][1] * b]);
5219                             break;
5220                     }
5221                 }
5222             }
5223
5224             else {
5225                 bands.show = false;
5226             }
5227         }
5228
5229         var hd = bands.hiData;
5230         var ld = bands.lowData;
5231         for (var i = 0, l = hd.length; i<l; i++) {
5232             if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) {
5233                 bands._max = hd[i][1];
5234             }
5235         }
5236         for (var i = 0, l = ld.length; i<l; i++) {
5237             if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) {
5238                 bands._min = ld[i][1];
5239             }
5240         }
5241
5242         // one last check for proper data
5243         // these don't apply any more since allowing arbitrary x,y values
5244         // if (bands.hiData.length != bands.lowData.length) {
5245         //     bands.show = false;
5246         // }
5247
5248         // if (bands.hiData.length != this.data.length) {
5249         //     bands.show = false;
5250         // }
5251
5252         if (bands.fillColor === null) {
5253             var c = $.jqplot.getColorComponents(bands.color);
5254             // now adjust alpha to differentiate fill
5255             c[3] = c[3] * 0.5;
5256             bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
5257         }
5258     };
5259
5260     function getSteps (d, f) {
5261         return (3.4182054+f) * Math.pow(d, -0.3534992);
5262     }
5263
5264     function computeSteps (d1, d2) {
5265         var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2));
5266         return 5.7648 * Math.log(s) + 7.4456;
5267     }
5268
5269     function tanh (x) {
5270         var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1);
5271         return a;
5272     }
5273
5274     //////////
5275     // computeConstrainedSmoothedData
5276     // An implementation of the constrained cubic spline interpolation
5277     // method as presented in:
5278     //
5279     // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications
5280     // http://www.korf.co.uk/spline.pdf
5281     //
5282     // The implementation below borrows heavily from the sample Visual Basic
5283     // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls
5284     //
5285     /////////
5286
5287     // called with scope of series
5288     function computeConstrainedSmoothedData (gd) {
5289         var smooth = this.renderer.smooth;
5290         var dim = this.canvas.getWidth();
5291         var xp = this._xaxis.series_p2u;
5292         var yp = this._yaxis.series_p2u; 
5293         var steps =null;
5294         var _steps = null;
5295         var dist = gd.length/dim;
5296         var _smoothedData = [];
5297         var _smoothedPlotData = [];
5298
5299         if (!isNaN(parseFloat(smooth))) {
5300             steps = parseFloat(smooth);
5301         }
5302         else {
5303             steps = getSteps(dist, 0.5);
5304         }
5305
5306         var yy = [];
5307         var xx = [];
5308
5309         for (var i=0, l = gd.length; i<l; i++) {
5310             yy.push(gd[i][1]);
5311             xx.push(gd[i][0]);
5312         }
5313
5314         function dxx(x1, x0) {
5315             if (x1 - x0 == 0) {
5316                 return Math.pow(10,10);
5317             }
5318             else {
5319                 return x1 - x0;
5320             }
5321         }
5322
5323         var A, B, C, D;
5324         // loop through each line segment.  Have # points - 1 line segments.  Nmber segments starting at 1.
5325         var nmax = gd.length - 1;
5326         for (var num = 1, gdl = gd.length; num<gdl; num++) {
5327             var gxx = [];
5328             var ggxx = [];
5329             // point at each end of segment.
5330             for (var j = 0; j < 2; j++) {
5331                 var i = num - 1 + j; // point number, 0 to # points.
5332
5333                 if (i == 0 || i == nmax) {
5334                     gxx[j] = Math.pow(10, 10);
5335                 }
5336                 else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) {
5337                     gxx[j] = 0;
5338                 }
5339                 else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) {
5340                     gxx[j] = 0;
5341                 }
5342                 else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) {
5343                     gxx[j] = 0;
5344                 }
5345
5346                 else {
5347                     gxx[j] = 2 / (dxx(xx[i + 1], xx[i]) / (yy[i + 1] - yy[i]) + dxx(xx[i], xx[i - 1]) / (yy[i] - yy[i - 1]));
5348                 }
5349             }
5350
5351             // Reset first derivative (slope) at first and last point
5352             if (num == 1) {
5353                 // First point has 0 2nd derivative
5354                 gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2;
5355             }
5356             else if (num == nmax) {
5357                 // Last point has 0 2nd derivative
5358                 gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2;
5359             }   
5360
5361             // Calc second derivative at points
5362             ggxx[0] = -2 * (gxx[1] + 2 * gxx[0]) / dxx(xx[num], xx[num - 1]) + 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
5363             ggxx[1] = 2 * (2 * gxx[1] + gxx[0]) / dxx(xx[num], xx[num - 1]) - 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
5364
5365             // Calc constants for cubic interpolation
5366             D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]);
5367             C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]);
5368             B = (yy[num] - yy[num - 1] - C * (Math.pow(xx[num], 2) - Math.pow(xx[num - 1], 2)) - D * (Math.pow(xx[num], 3) - Math.pow(xx[num - 1], 3))) / dxx(xx[num], xx[num - 1]);
5369             A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3);
5370
5371             var increment = (xx[num] - xx[num - 1]) / steps;
5372             var temp, tempx;
5373
5374             for (var j = 0, l = steps; j < l; j++) {
5375                 temp = [];
5376                 tempx = xx[num - 1] + j * increment;
5377                 temp.push(tempx);
5378                 temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3));
5379                 _smoothedData.push(temp);
5380                 _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]);
5381             }
5382         }
5383
5384         _smoothedData.push(gd[i]);
5385         _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]);
5386
5387         return [_smoothedData, _smoothedPlotData];
5388     }
5389
5390     ///////
5391     // computeHermiteSmoothedData
5392     // A hermite spline smoothing of the plot data.
5393     // This implementation is derived from the one posted
5394     // by krypin on the jqplot-users mailing list:
5395     //
5396     // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1
5397     //
5398     // with a blog post:
5399     //
5400     // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/
5401     //
5402     // and download of the original plugin:
5403     //
5404     // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js
5405     //////////
5406
5407     // called with scope of series
5408     function computeHermiteSmoothedData (gd) {
5409         var smooth = this.renderer.smooth;
5410         var tension = this.renderer.tension;
5411         var dim = this.canvas.getWidth();
5412         var xp = this._xaxis.series_p2u;
5413         var yp = this._yaxis.series_p2u; 
5414         var steps =null;
5415         var _steps = null;
5416         var a = null;
5417         var a1 = null;
5418         var a2 = null;
5419         var slope = null;
5420         var slope2 = null;
5421         var temp = null;
5422         var t, s, h1, h2, h3, h4;
5423         var TiX, TiY, Ti1X, Ti1Y;
5424         var pX, pY, p;
5425         var sd = [];
5426         var spd = [];
5427         var dist = gd.length/dim;
5428         var min, max, stretch, scale, shift;
5429         var _smoothedData = [];
5430         var _smoothedPlotData = [];
5431         if (!isNaN(parseFloat(smooth))) {
5432             steps = parseFloat(smooth);
5433         }
5434         else {
5435             steps = getSteps(dist, 0.5);
5436         }
5437         if (!isNaN(parseFloat(tension))) {
5438             tension = parseFloat(tension);
5439         }
5440
5441         for (var i=0, l = gd.length-1; i < l; i++) {
5442
5443             if (tension === null) {
5444                 slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0]));
5445
5446                 min = 0.3;
5447                 max = 0.6;
5448                 stretch = (max - min)/2.0;
5449                 scale = 2.5;
5450                 shift = -1.4;
5451
5452                 temp = slope/scale + shift;
5453
5454                 a1 = stretch * tanh(temp) - stretch * tanh(shift) + min;
5455
5456                 // if have both left and right line segments, will use  minimum tension. 
5457                 if (i > 0) {
5458                     slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0]));
5459                 }
5460                 temp = slope2/scale + shift;
5461
5462                 a2 = stretch * tanh(temp) - stretch * tanh(shift) + min;
5463
5464                 a = (a1 + a2)/2.0;
5465
5466             }
5467             else {
5468                 a = tension;
5469             }
5470             for (t=0; t < steps; t++) {
5471                 s = t / steps;
5472                 h1 = (1 + 2*s)*Math.pow((1-s),2);
5473                 h2 = s*Math.pow((1-s),2);
5474                 h3 = Math.pow(s,2)*(3-2*s);
5475                 h4 = Math.pow(s,2)*(s-1);     
5476                 
5477                 if (gd[i-1]) {  
5478                     TiX = a * (gd[i+1][0] - gd[i-1][0]); 
5479                     TiY = a * (gd[i+1][1] - gd[i-1][1]);
5480                 } else {
5481                     TiX = a * (gd[i+1][0] - gd[i][0]); 
5482                     TiY = a * (gd[i+1][1] - gd[i][1]);                                  
5483                 }
5484                 if (gd[i+2]) {  
5485                     Ti1X = a * (gd[i+2][0] - gd[i][0]); 
5486                     Ti1Y = a * (gd[i+2][1] - gd[i][1]);
5487                 } else {
5488                     Ti1X = a * (gd[i+1][0] - gd[i][0]); 
5489                     Ti1Y = a * (gd[i+1][1] - gd[i][1]);                                 
5490                 }
5491                 
5492                 pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X;
5493                 pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y;
5494                 p = [pX, pY];
5495
5496                 _smoothedData.push(p);
5497                 _smoothedPlotData.push([xp(pX), yp(pY)]);
5498             }
5499         }
5500         _smoothedData.push(gd[l]);
5501         _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]);
5502
5503         return [_smoothedData, _smoothedPlotData];
5504     }
5505     
5506     // setGridData
5507     // converts the user data values to grid coordinates and stores them
5508     // in the gridData array.
5509     // Called with scope of a series.
5510     $.jqplot.LineRenderer.prototype.setGridData = function(plot) {
5511         // recalculate the grid data
5512         var xp = this._xaxis.series_u2p;
5513         var yp = this._yaxis.series_u2p;
5514         var data = this._plotData;
5515         var pdata = this._prevPlotData;
5516         this.gridData = [];
5517         this._prevGridData = [];
5518         this.renderer._smoothedData = [];
5519         this.renderer._smoothedPlotData = [];
5520         this.renderer._hiBandGridData = [];
5521         this.renderer._lowBandGridData = [];
5522         this.renderer._hiBandSmoothedData = [];
5523         this.renderer._lowBandSmoothedData = [];
5524         var bands = this.renderer.bands;
5525         var hasNull = false;
5526         for (var i=0, l=data.length; i < l; i++) {
5527             // if not a line series or if no nulls in data, push the converted point onto the array.
5528             if (data[i][0] != null && data[i][1] != null) {
5529                 this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
5530             }
5531             // else if there is a null, preserve it.
5532             else if (data[i][0] == null) {
5533                 hasNull = true;
5534                 this.gridData.push([null, yp.call(this._yaxis, data[i][1])]);
5535             }
5536             else if (data[i][1] == null) {
5537                 hasNull = true;
5538                 this.gridData.push([xp.call(this._xaxis, data[i][0]), null]);
5539             }
5540             // if not a line series or if no nulls in data, push the converted point onto the array.
5541             if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) {
5542                 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]);
5543             }
5544             // else if there is a null, preserve it.
5545             else if (pdata[i] != null && pdata[i][0] == null) {
5546                 this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]);
5547             }  
5548             else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) {
5549                 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]);
5550             }
5551         }
5552
5553         // don't do smoothing or bands on broken lines.
5554         if (hasNull) {
5555             this.renderer.smooth = false;
5556             if (this._type === 'line') {
5557                 bands.show = false;
5558             }
5559         }
5560
5561         if (this._type === 'line' && bands.show) {
5562             for (var i=0, l=bands.hiData.length; i<l; i++) {
5563                 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
5564             }
5565             for (var i=0, l=bands.lowData.length; i<l; i++) {
5566                 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
5567             }
5568         }
5569
5570         // calculate smoothed data if enough points and no nulls
5571         if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) {
5572             var ret;
5573             if (this.renderer.constrainSmoothing) {
5574                 ret = computeConstrainedSmoothedData.call(this, this.gridData);
5575                 this.renderer._smoothedData = ret[0];
5576                 this.renderer._smoothedPlotData = ret[1];
5577
5578                 if (bands.show) {
5579                     ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
5580                     this.renderer._hiBandSmoothedData = ret[0];
5581                     ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
5582                     this.renderer._lowBandSmoothedData = ret[0];
5583                 }
5584
5585                 ret = null;
5586             }
5587             else {
5588                 ret = computeHermiteSmoothedData.call(this, this.gridData);
5589                 this.renderer._smoothedData = ret[0];
5590                 this.renderer._smoothedPlotData = ret[1];
5591
5592                 if (bands.show) {
5593                     ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
5594                     this.renderer._hiBandSmoothedData = ret[0];
5595                     ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
5596                     this.renderer._lowBandSmoothedData = ret[0];
5597                 }
5598
5599                 ret = null;
5600             }
5601         }
5602     };
5603     
5604     // makeGridData
5605     // converts any arbitrary data values to grid coordinates and
5606     // returns them.  This method exists so that plugins can use a series'
5607     // linerenderer to generate grid data points without overwriting the
5608     // grid data associated with that series.
5609     // Called with scope of a series.
5610     $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) {
5611         // recalculate the grid data
5612         var xp = this._xaxis.series_u2p;
5613         var yp = this._yaxis.series_u2p;
5614         var gd = [];
5615         var pgd = [];
5616         this.renderer._smoothedData = [];
5617         this.renderer._smoothedPlotData = [];
5618         this.renderer._hiBandGridData = [];
5619         this.renderer._lowBandGridData = [];
5620         this.renderer._hiBandSmoothedData = [];
5621         this.renderer._lowBandSmoothedData = [];
5622         var bands = this.renderer.bands;
5623         var hasNull = false;
5624         for (var i=0; i<data.length; i++) {
5625             // if not a line series or if no nulls in data, push the converted point onto the array.
5626             if (data[i][0] != null && data[i][1] != null) {
5627                 gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
5628             }
5629             // else if there is a null, preserve it.
5630             else if (data[i][0] == null) {
5631                 hasNull = true;
5632                 gd.push([null, yp.call(this._yaxis, data[i][1])]);
5633             }
5634             else if (data[i][1] == null) {
5635                 hasNull = true;
5636                 gd.push([xp.call(this._xaxis, data[i][0]), null]);
5637             }
5638         }
5639
5640         // don't do smoothing or bands on broken lines.
5641         if (hasNull) {
5642             this.renderer.smooth = false;
5643             if (this._type === 'line') {
5644                 bands.show = false;
5645             }
5646         }
5647
5648         if (this._type === 'line' && bands.show) {
5649             for (var i=0, l=bands.hiData.length; i<l; i++) {
5650                 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
5651             }
5652             for (var i=0, l=bands.lowData.length; i<l; i++) {
5653                 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
5654             }
5655         }
5656
5657         if (this._type === 'line' && this.renderer.smooth && gd.length > 2) {
5658             var ret;
5659             if (this.renderer.constrainSmoothing) {
5660                 ret = computeConstrainedSmoothedData.call(this, gd);
5661                 this.renderer._smoothedData = ret[0];
5662                 this.renderer._smoothedPlotData = ret[1];
5663
5664                 if (bands.show) {
5665                     ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
5666                     this.renderer._hiBandSmoothedData = ret[0];
5667                     ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
5668                     this.renderer._lowBandSmoothedData = ret[0];
5669                 }
5670
5671                 ret = null;
5672             }
5673             else {
5674                 ret = computeHermiteSmoothedData.call(this, gd);
5675                 this.renderer._smoothedData = ret[0];
5676                 this.renderer._smoothedPlotData = ret[1];
5677
5678                 if (bands.show) {
5679                     ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
5680                     this.renderer._hiBandSmoothedData = ret[0];
5681                     ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
5682                     this.renderer._lowBandSmoothedData = ret[0];
5683                 }
5684
5685                 ret = null;
5686             }
5687         }
5688         return gd;
5689     };
5690     
5691
5692     // called within scope of series.
5693     $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
5694         var i;
5695         // get a copy of the options, so we don't modify the original object.
5696         var opts = $.extend(true, {}, options);
5697         var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
5698         var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
5699         var fill = (opts.fill != undefined) ? opts.fill : this.fill;
5700         var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
5701         var xmin, ymin, xmax, ymax;
5702         ctx.save();
5703         if (gd.length) {
5704             if (showLine) {
5705                 // if we fill, we'll have to add points to close the curve.
5706                 if (fill) {
5707                     if (this.fillToZero) { 
5708                         // have to break line up into shapes at axis crossings
5709                         var negativeColor = this.negativeColor;
5710                         if (! this.useNegativeColors) {
5711                             negativeColor = opts.fillStyle;
5712                         }
5713                         var isnegative = false;
5714                         var posfs = opts.fillStyle;
5715                     
5716                         // if stoking line as well as filling, get a copy of line data.
5717                         if (fillAndStroke) {
5718                             var fasgd = gd.slice(0);
5719                         }
5720                         // if not stacked, fill down to axis
5721                         if (this.index == 0 || !this._stack) {
5722                         
5723                             var tempgd = [];
5724                             var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData;
5725                             this._areaPoints = [];
5726                             var pyzero = this._yaxis.series_u2p(this.fillToValue);
5727                             var pxzero = this._xaxis.series_u2p(this.fillToValue);
5728
5729                             opts.closePath = true;
5730                             
5731                             if (this.fillAxis == 'y') {
5732                                 tempgd.push([gd[0][0], pyzero]);
5733                                 this._areaPoints.push([gd[0][0], pyzero]);
5734                                 
5735                                 for (var i=0; i<gd.length-1; i++) {
5736                                     tempgd.push(gd[i]);
5737                                     this._areaPoints.push(gd[i]);
5738                                     // do we have an axis crossing?
5739                                     if (pd[i][1] * pd[i+1][1] <= 0) {
5740                                         if (pd[i][1] < 0) {
5741                                             isnegative = true;
5742                                             opts.fillStyle = negativeColor;
5743                                         }
5744                                         else {
5745                                             isnegative = false;
5746                                             opts.fillStyle = posfs;
5747                                         }
5748                                         
5749                                         var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
5750                                         tempgd.push([xintercept, pyzero]);
5751                                         this._areaPoints.push([xintercept, pyzero]);
5752                                         // now draw this shape and shadow.
5753                                         if (shadow) {
5754                                             this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
5755                                         }
5756                                         this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
5757                                         // now empty temp array and continue
5758                                         tempgd = [[xintercept, pyzero]];
5759                                         // this._areaPoints = [[xintercept, pyzero]];
5760                                     }   
5761                                 }
5762                                 if (pd[gd.length-1][1] < 0) {
5763                                     isnegative = true;
5764                                     opts.fillStyle = negativeColor;
5765                                 }
5766                                 else {
5767                                     isnegative = false;
5768                                     opts.fillStyle = posfs;
5769                                 }
5770                                 tempgd.push(gd[gd.length-1]);
5771                                 this._areaPoints.push(gd[gd.length-1]);
5772                                 tempgd.push([gd[gd.length-1][0], pyzero]); 
5773                                 this._areaPoints.push([gd[gd.length-1][0], pyzero]); 
5774                             }
5775                             // now draw the last area.
5776                             if (shadow) {
5777                                 this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
5778                             }
5779                             this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
5780                             
5781                             
5782                             // var gridymin = this._yaxis.series_u2p(0);
5783                             // // IE doesn't return new length on unshift
5784                             // gd.unshift([gd[0][0], gridymin]);
5785                             // len = gd.length;
5786                             // gd.push([gd[len - 1][0], gridymin]);                   
5787                         }
5788                         // if stacked, fill to line below 
5789                         else {
5790                             var prev = this._prevGridData;
5791                             for (var i=prev.length; i>0; i--) {
5792                                 gd.push(prev[i-1]);
5793                                 // this._areaPoints.push(prev[i-1]);
5794                             }
5795                             if (shadow) {
5796                                 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5797                             }
5798                             this._areaPoints = gd;
5799                             this.renderer.shapeRenderer.draw(ctx, gd, opts);
5800                         }
5801                     }
5802                     /////////////////////////
5803                     // Not filled to zero
5804                     ////////////////////////
5805                     else {                    
5806                         // if stoking line as well as filling, get a copy of line data.
5807                         if (fillAndStroke) {
5808                             var fasgd = gd.slice(0);
5809                         }
5810                         // if not stacked, fill down to axis
5811                         if (this.index == 0 || !this._stack) {
5812                             // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
5813                             var gridymin = ctx.canvas.height;
5814                             // IE doesn't return new length on unshift
5815                             gd.unshift([gd[0][0], gridymin]);
5816                             var len = gd.length;
5817                             gd.push([gd[len - 1][0], gridymin]);                   
5818                         }
5819                         // if stacked, fill to line below 
5820                         else {
5821                             var prev = this._prevGridData;
5822                             for (var i=prev.length; i>0; i--) {
5823                                 gd.push(prev[i-1]);
5824                             }
5825                         }
5826                         this._areaPoints = gd;
5827                         
5828                         if (shadow) {
5829                             this.renderer.shadowRenderer.draw(ctx, gd, opts);
5830                         }
5831             
5832                         this.renderer.shapeRenderer.draw(ctx, gd, opts);                        
5833                     }
5834                     if (fillAndStroke) {
5835                         var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false});
5836                         this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
5837                         //////////
5838                         // TODO: figure out some way to do shadows nicely
5839                         // if (shadow) {
5840                         //     this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts);
5841                         // }
5842                         // now draw the markers
5843                         if (this.markerRenderer.show) {
5844                             if (this.renderer.smooth) {
5845                                 fasgd = this.gridData;
5846                             }
5847                             for (i=0; i<fasgd.length; i++) {
5848                                 this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions);
5849                             }
5850                         }
5851                     }
5852                 }
5853                 else {
5854
5855                     if (this.renderer.bands.show) {
5856                         var bdat;
5857                         var bopts = $.extend(true, {}, opts);
5858                         if (this.renderer.bands.showLines) {
5859                             bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData;
5860                             this.renderer.shapeRenderer.draw(ctx, bdat, opts);
5861                             bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData;
5862                             this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
5863                         }
5864
5865                         if (this.renderer.bands.fill) {
5866                             if (this.renderer.smooth) {
5867                                 bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse());
5868                             }
5869                             else {
5870                                 bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse());
5871                             }
5872                             this._areaPoints = bdat;
5873                             bopts.closePath = true;
5874                             bopts.fill = true;
5875                             bopts.fillStyle = this.renderer.bands.fillColor;
5876                             this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
5877                         }
5878                     }
5879
5880                     if (shadow) {
5881                         this.renderer.shadowRenderer.draw(ctx, gd, opts);
5882                     }
5883     
5884                     this.renderer.shapeRenderer.draw(ctx, gd, opts);
5885                 }
5886             }
5887             // calculate the bounding box
5888             var xmin = xmax = ymin = ymax = null;
5889             for (i=0; i<this._areaPoints.length; i++) {
5890                 var p = this._areaPoints[i];
5891                 if (xmin > p[0] || xmin == null) {
5892                     xmin = p[0];
5893                 }
5894                 if (ymax < p[1] || ymax == null) {
5895                     ymax = p[1];
5896                 }
5897                 if (xmax < p[0] || xmax == null) {
5898                     xmax = p[0];
5899                 }
5900                 if (ymin > p[1] || ymin == null) {
5901                     ymin = p[1];
5902                 }
5903             }
5904
5905             if (this.type === 'line' && this.renderer.bands.show) {
5906                 ymax = this._yaxis.series_u2p(this.renderer.bands._min);
5907                 ymin = this._yaxis.series_u2p(this.renderer.bands._max);
5908             }
5909
5910             this._boundingBox = [[xmin, ymax], [xmax, ymin]];
5911         
5912             // now draw the markers
5913             if (this.markerRenderer.show && !fill) {
5914                 if (this.renderer.smooth) {
5915                     gd = this.gridData;
5916                 }
5917                 for (i=0; i<gd.length; i++) {
5918                     if (gd[i][0] != null && gd[i][1] != null) {
5919                         this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions);
5920                     }
5921                 }
5922             }
5923         }
5924         
5925         ctx.restore();
5926     };  
5927     
5928     $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) {
5929         // This is a no-op, shadows drawn with lines.
5930     };
5931     
5932     // called with scope of plot.
5933     // make sure to not leave anything highlighted.
5934     function postInit(target, data, options) {
5935         for (var i=0; i<this.series.length; i++) {
5936             if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
5937                 // don't allow mouseover and mousedown at same time.
5938                 if (this.series[i].highlightMouseOver) {
5939                     this.series[i].highlightMouseDown = false;
5940                 }
5941             }
5942         }
5943     }  
5944     
5945     // called within context of plot
5946     // create a canvas which we can draw on.
5947     // insert it before the eventCanvas, so eventCanvas will still capture events.
5948     function postPlotDraw() {
5949         // Memory Leaks patch    
5950         if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) {
5951           this.plugins.lineRenderer.highlightCanvas.resetCanvas();
5952           this.plugins.lineRenderer.highlightCanvas = null;
5953         }
5954         
5955         this.plugins.lineRenderer.highlightedSeriesIndex = null;
5956         this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
5957         
5958         this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this));
5959         this.plugins.lineRenderer.highlightCanvas.setContext();
5960         this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
5961     } 
5962     
5963     function highlight (plot, sidx, pidx, points) {
5964         var s = plot.series[sidx];
5965         var canvas = plot.plugins.lineRenderer.highlightCanvas;
5966         canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
5967         s._highlightedPoint = pidx;
5968         plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
5969         var opts = {fillStyle: s.highlightColor};
5970         if (s.type === 'line' && s.renderer.bands.show) {
5971             opts.fill = true;
5972             opts.closePath = true;
5973         }
5974         s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
5975         canvas = null;
5976     }
5977     
5978     function unhighlight (plot) {
5979         var canvas = plot.plugins.lineRenderer.highlightCanvas;
5980         canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
5981         for (var i=0; i<plot.series.length; i++) {
5982             plot.series[i]._highlightedPoint = null;
5983         }
5984         plot.plugins.lineRenderer.highlightedSeriesIndex = null;
5985         plot.target.trigger('jqplotDataUnhighlight');
5986         canvas = null;
5987     }
5988     
5989     
5990     function handleMove(ev, gridpos, datapos, neighbor, plot) {
5991         if (neighbor) {
5992             var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
5993             var evt1 = jQuery.Event('jqplotDataMouseOver');
5994             evt1.pageX = ev.pageX;
5995             evt1.pageY = ev.pageY;
5996             plot.target.trigger(evt1, ins);
5997             if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
5998                 var evt = jQuery.Event('jqplotDataHighlight');
5999                 evt.which = ev.which;
6000                 evt.pageX = ev.pageX;
6001                 evt.pageY = ev.pageY;
6002                 plot.target.trigger(evt, ins);
6003                 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
6004             }
6005         }
6006         else if (neighbor == null) {
6007             unhighlight (plot);
6008         }
6009     }
6010     
6011     function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
6012         if (neighbor) {
6013             var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
6014             if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
6015                 var evt = jQuery.Event('jqplotDataHighlight');
6016                 evt.which = ev.which;
6017                 evt.pageX = ev.pageX;
6018                 evt.pageY = ev.pageY;
6019                 plot.target.trigger(evt, ins);
6020                 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
6021             }
6022         }
6023         else if (neighbor == null) {
6024             unhighlight (plot);
6025         }
6026     }
6027     
6028     function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
6029         var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
6030         if (idx != null && plot.series[idx].highlightMouseDown) {
6031             unhighlight(plot);
6032         }
6033     }
6034     
6035     function handleClick(ev, gridpos, datapos, neighbor, plot) {
6036         if (neighbor) {
6037             var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
6038             var evt = jQuery.Event('jqplotDataClick');
6039             evt.which = ev.which;
6040             evt.pageX = ev.pageX;
6041             evt.pageY = ev.pageY;
6042             plot.target.trigger(evt, ins);
6043         }
6044     }
6045     
6046     function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
6047         if (neighbor) {
6048             var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
6049             var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
6050             if (idx != null && plot.series[idx].highlightMouseDown) {
6051                 unhighlight(plot);
6052             }
6053             var evt = jQuery.Event('jqplotDataRightClick');
6054             evt.which = ev.which;
6055             evt.pageX = ev.pageX;
6056             evt.pageY = ev.pageY;
6057             plot.target.trigger(evt, ins);
6058         }
6059     }
6060     
6061     
6062     // class: $.jqplot.LinearAxisRenderer
6063     // The default jqPlot axis renderer, creating a numeric axis.
6064     $.jqplot.LinearAxisRenderer = function() {
6065     };
6066     
6067     // called with scope of axis object.
6068     $.jqplot.LinearAxisRenderer.prototype.init = function(options){
6069         // prop: breakPoints
6070         // EXPERIMENTAL!! Use at your own risk!
6071         // Works only with linear axes and the default tick renderer.
6072         // Array of [start, stop] points to create a broken axis.
6073         // Broken axes have a "jump" in them, which is an immediate 
6074         // transition from a smaller value to a larger value.
6075         // Currently, axis ticks MUST be manually assigned if using breakPoints
6076         // by using the axis ticks array option.
6077         this.breakPoints = null;
6078         // prop: breakTickLabel
6079         // Label to use at the axis break if breakPoints are specified.
6080         this.breakTickLabel = "&asymp;";
6081         // prop: drawBaseline
6082         // True to draw the axis baseline.
6083         this.drawBaseline = true;
6084         // prop: baselineWidth
6085         // width of the baseline in pixels.
6086         this.baselineWidth = null;
6087         // prop: baselineColor
6088         // CSS color spec for the baseline.
6089         this.baselineColor = null;
6090         // prop: forceTickAt0
6091         // This will ensure that there is always a tick mark at 0.
6092         // If data range is strictly positive or negative,
6093         // this will force 0 to be inside the axis bounds unless
6094         // the appropriate axis pad (pad, padMin or padMax) is set
6095         // to 0, then this will force an axis min or max value at 0.
6096         // This has know effect when any of the following options
6097         // are set:  autoscale, min, max, numberTicks or tickInterval.
6098         this.forceTickAt0 = false;
6099         // prop: forceTickAt100
6100         // This will ensure that there is always a tick mark at 100.
6101         // If data range is strictly above or below 100,
6102         // this will force 100 to be inside the axis bounds unless
6103         // the appropriate axis pad (pad, padMin or padMax) is set
6104         // to 0, then this will force an axis min or max value at 100.
6105         // This has know effect when any of the following options
6106         // are set:  autoscale, min, max, numberTicks or tickInterval.
6107         this.forceTickAt100 = false;
6108         // prop: tickInset
6109         // Controls the amount to inset the first and last ticks from 
6110         // the edges of the grid, in multiples of the tick interval.
6111         // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
6112         // tick interval, etc.
6113         this.tickInset = 0;
6114         // prop: minorTicks
6115         // Number of ticks to add between "major" ticks.
6116         // Major ticks are ticks supplied by user or auto computed.
6117         // Minor ticks cannot be created by user.
6118         this.minorTicks = 0;
6119         // prop: alignTicks
6120         // true to align tick marks across opposed axes
6121         // such as from the y2axis to yaxis.
6122         this.alignTicks = false;
6123         this._autoFormatString = '';
6124         this._overrideFormatString = false;
6125         this._scalefact = 1.0;
6126         $.extend(true, this, options);
6127         if (this.breakPoints) {
6128             if (!$.isArray(this.breakPoints)) {
6129                 this.breakPoints = null;
6130             }
6131             else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) {
6132                 this.breakPoints = null;
6133             }
6134         }
6135         if (this.numberTicks != null && this.numberTicks < 2) {
6136             this.numberTicks = 2;
6137         }
6138         this.resetDataBounds();
6139     };
6140     
6141     // called with scope of axis
6142     $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) {
6143         if (this.show) {
6144             // populate the axis label and value properties.
6145             // createTicks is a method on the renderer, but
6146             // call it within the scope of the axis.
6147             this.renderer.createTicks.call(this, plot);
6148             // fill a div with axes labels in the right direction.
6149             // Need to pregenerate each axis to get its bounds and
6150             // position it and the labels correctly on the plot.
6151             var dim=0;
6152             var temp;
6153             // Added for theming.
6154             if (this._elem) {
6155                 // Memory Leaks patch
6156                 //this._elem.empty();
6157                 this._elem.emptyForce();
6158                 this._elem = null;
6159             }
6160             
6161             this._elem = $(document.createElement('div'));
6162             this._elem.addClass('jqplot-axis jqplot-'+this.name);
6163             this._elem.css('position', 'absolute');
6164
6165             
6166             if (this.name == 'xaxis' || this.name == 'x2axis') {
6167                 this._elem.width(this._plotDimensions.width);
6168             }
6169             else {
6170                 this._elem.height(this._plotDimensions.height);
6171             }
6172             
6173             // create a _label object.
6174             this.labelOptions.axis = this.name;
6175             this._label = new this.labelRenderer(this.labelOptions);
6176             if (this._label.show) {
6177                 var elem = this._label.draw(ctx, plot);
6178                 elem.appendTo(this._elem);
6179                 elem = null;
6180             }
6181     
6182             var t = this._ticks;
6183             var tick;
6184             for (var i=0; i<t.length; i++) {
6185                 tick = t[i];
6186                 if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
6187                     this._elem.append(tick.draw(ctx, plot));
6188                 }
6189             }
6190             tick = null;
6191             t = null;
6192         }
6193         return this._elem;
6194     };
6195     
6196     // called with scope of an axis
6197     $.jqplot.LinearAxisRenderer.prototype.reset = function() {
6198         this.min = this._options.min;
6199         this.max = this._options.max;
6200         this.tickInterval = this._options.tickInterval;
6201         this.numberTicks = this._options.numberTicks;
6202         this._autoFormatString = '';
6203         if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
6204             this.tickOptions.formatString = '';
6205         }
6206
6207         // this._ticks = this.__ticks;
6208     };
6209     
6210     // called with scope of axis
6211     $.jqplot.LinearAxisRenderer.prototype.set = function() { 
6212         var dim = 0;
6213         var temp;
6214         var w = 0;
6215         var h = 0;
6216         var lshow = (this._label == null) ? false : this._label.show;
6217         if (this.show) {
6218             var t = this._ticks;
6219             var tick;
6220             for (var i=0; i<t.length; i++) {
6221                 tick = t[i];
6222                 if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
6223                     if (this.name == 'xaxis' || this.name == 'x2axis') {
6224                         temp = tick._elem.outerHeight(true);
6225                     }
6226                     else {
6227                         temp = tick._elem.outerWidth(true);
6228                     }
6229                     if (temp > dim) {
6230                         dim = temp;
6231                     }
6232                 }
6233             }
6234             tick = null;
6235             t = null;
6236             
6237             if (lshow) {
6238                 w = this._label._elem.outerWidth(true);
6239                 h = this._label._elem.outerHeight(true); 
6240             }
6241             if (this.name == 'xaxis') {
6242                 dim = dim + h;
6243                 this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
6244             }
6245             else if (this.name == 'x2axis') {
6246                 dim = dim + h;
6247                 this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
6248             }
6249             else if (this.name == 'yaxis') {
6250                 dim = dim + w;
6251                 this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
6252                 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
6253                     this._label._elem.css('width', w+'px');
6254                 }
6255             }
6256             else {
6257                 dim = dim + w;
6258                 this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
6259                 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
6260                     this._label._elem.css('width', w+'px');
6261                 }
6262             }
6263         }  
6264     };    
6265     
6266     // called with scope of axis
6267     $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) {
6268         // we're are operating on an axis here
6269         var ticks = this._ticks;
6270         var userTicks = this.ticks;
6271         var name = this.name;
6272         // databounds were set on axis initialization.
6273         var db = this._dataBounds;
6274         var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
6275         var interval;
6276         var min, max;
6277         var pos1, pos2;
6278         var tt, i;
6279         // get a copy of user's settings for min/max.
6280         var userMin = this.min;
6281         var userMax = this.max;
6282         var userNT = this.numberTicks;
6283         var userTI = this.tickInterval;
6284
6285         var threshold = 30;
6286         this._scalefact =  (Math.max(dim, threshold+1) - threshold)/300.0;
6287         
6288         // if we already have ticks, use them.
6289         // ticks must be in order of increasing value.
6290         
6291         if (userTicks.length) {
6292             // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
6293             for (i=0; i<userTicks.length; i++){
6294                 var ut = userTicks[i];
6295                 var t = new this.tickRenderer(this.tickOptions);
6296                 if ($.isArray(ut)) {
6297                     t.value = ut[0];
6298                     if (this.breakPoints) {
6299                         if (ut[0] == this.breakPoints[0]) {
6300                             t.label = this.breakTickLabel;
6301                             t._breakTick = true;
6302                             t.showGridline = false;
6303                             t.showMark = false;
6304                         }
6305                         else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) {
6306                             t.show = false;
6307                             t.showGridline = false;
6308                             t.label = ut[1];
6309                         }
6310                         else {
6311                             t.label = ut[1];
6312                         }
6313                     }
6314                     else {
6315                         t.label = ut[1];
6316                     }
6317                     t.setTick(ut[0], this.name);
6318                     this._ticks.push(t);
6319                 }
6320
6321                 else if ($.isPlainObject(ut)) {
6322                     $.extend(true, t, ut);
6323                     t.axis = this.name;
6324                     this._ticks.push(t);
6325                 }
6326                 
6327                 else {
6328                     t.value = ut;
6329                     if (this.breakPoints) {
6330                         if (ut == this.breakPoints[0]) {
6331                             t.label = this.breakTickLabel;
6332                             t._breakTick = true;
6333                             t.showGridline = false;
6334                             t.showMark = false;
6335                         }
6336                         else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) {
6337                             t.show = false;
6338                             t.showGridline = false;
6339                         }
6340                     }
6341                     t.setTick(ut, this.name);
6342                     this._ticks.push(t);
6343                 }
6344             }
6345             this.numberTicks = userTicks.length;
6346             this.min = this._ticks[0].value;
6347             this.max = this._ticks[this.numberTicks-1].value;
6348             this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
6349         }
6350         
6351         // we don't have any ticks yet, let's make some!
6352         else {
6353             if (name == 'xaxis' || name == 'x2axis') {
6354                 dim = this._plotDimensions.width;
6355             }
6356             else {
6357                 dim = this._plotDimensions.height;
6358             }
6359
6360             var _numberTicks = this.numberTicks;
6361
6362             // if aligning this axis, use number of ticks from previous axis.
6363             // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
6364             if (this.alignTicks) {
6365                 if (this.name === 'x2axis' && plot.axes.xaxis.show) {
6366                     _numberTicks = plot.axes.xaxis.numberTicks;
6367                 }
6368                 else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) {
6369                     _numberTicks = plot.axes.yaxis.numberTicks;
6370                 }
6371             }
6372         
6373             min = ((this.min != null) ? this.min : db.min);
6374             max = ((this.max != null) ? this.max : db.max);
6375
6376             var range = max - min;
6377             var rmin, rmax;
6378             var temp;
6379
6380             if (this.tickOptions == null || !this.tickOptions.formatString) {
6381                 this._overrideFormatString = true;
6382             }
6383
6384             // Doing complete autoscaling
6385             if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) {
6386                 // Check if user must have tick at 0 or 100 and ensure they are in range.
6387                 // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range.
6388                 if (this.forceTickAt0) {
6389                     if (min > 0) {
6390                         min = 0;
6391                     }
6392                     if (max < 0) {
6393                         max = 0;
6394                     }
6395                 }
6396
6397                 if (this.forceTickAt100) {
6398                     if (min > 100) {
6399                         min = 100;
6400                     }
6401                     if (max < 100) {
6402                         max = 100;
6403                     }
6404                 }
6405
6406                 var keepMin = false,
6407                     keepMax = false;
6408
6409                 if (this.min != null) {
6410                     keepMin = true;
6411                 }
6412
6413                 else if (this.max != null) {
6414                     keepMax = true;
6415                 }
6416
6417                 // var threshold = 30;
6418                 // var tdim = Math.max(dim, threshold+1);
6419                 // this._scalefact =  (tdim-threshold)/300.0;
6420                 var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax); 
6421                 // calculate a padded max and min, points should be less than these
6422                 // so that they aren't too close to the edges of the plot.
6423                 // User can adjust how much padding is allowed with pad, padMin and PadMax options. 
6424                 // If min or max is set, don't pad that end of axis.
6425                 var tumin = (this.min != null) ? min : min + range*(this.padMin - 1);
6426                 var tumax = (this.max != null) ? max : max - range*(this.padMax - 1);
6427
6428                 // if they're equal, we shouldn't have to do anything, right?
6429                 // if (min <=tumin || max >= tumax) {
6430                 if (min <tumin || max > tumax) {
6431                     tumin = (this.min != null) ? min : min - range*(this.padMin - 1);
6432                     tumax = (this.max != null) ? max : max + range*(this.padMax - 1);
6433                     ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax);
6434                 }
6435
6436                 this.min = ret[0];
6437                 this.max = ret[1];
6438                 // if numberTicks specified, it should return the same.
6439                 this.numberTicks = ret[2];
6440                 this._autoFormatString = ret[3];
6441                 this.tickInterval = ret[4];
6442             }
6443
6444             // User has specified some axis scale related option, can use auto algorithm
6445             else {
6446                 
6447                 // if min and max are same, space them out a bit
6448                 if (min == max) {
6449                     var adj = 0.05;
6450                     if (min > 0) {
6451                         adj = Math.max(Math.log(min)/Math.LN10, 0.05);
6452                     }
6453                     min -= adj;
6454                     max += adj;
6455                 }
6456                 
6457                 // autoscale.  Can't autoscale if min or max is supplied.
6458                 // Will use numberTicks and tickInterval if supplied.  Ticks
6459                 // across multiple axes may not line up depending on how
6460                 // bars are to be plotted.
6461                 if (this.autoscale && this.min == null && this.max == null) {
6462                     var rrange, ti, margin;
6463                     var forceMinZero = false;
6464                     var forceZeroLine = false;
6465                     var intervals = {min:null, max:null, average:null, stddev:null};
6466                     // if any series are bars, or if any are fill to zero, and if this
6467                     // is the axis to fill toward, check to see if we can start axis at zero.
6468                     for (var i=0; i<this._series.length; i++) {
6469                         var s = this._series[i];
6470                         var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name;
6471                         // check to see if this is the fill axis
6472                         if (this.name == faname) {
6473                             var vals = s._plotValues[s.fillAxis];
6474                             var vmin = vals[0];
6475                             var vmax = vals[0];
6476                             for (var j=1; j<vals.length; j++) {
6477                                 if (vals[j] < vmin) {
6478                                     vmin = vals[j];
6479                                 }
6480                                 else if (vals[j] > vmax) {
6481                                     vmax = vals[j];
6482                                 }
6483                             }
6484                             var dp = (vmax - vmin) / vmax;
6485                             // is this sries a bar?
6486                             if (s.renderer.constructor == $.jqplot.BarRenderer) {
6487                                 // if no negative values and could also check range.
6488                                 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
6489                                     forceMinZero = true;
6490                                 }
6491                                 else {
6492                                     forceMinZero = false;
6493                                     if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) {
6494                                         forceZeroLine = true;
6495                                     }
6496                                     else {
6497                                         forceZeroLine = false;
6498                                     }
6499                                 }
6500                             }
6501                             
6502                             // if not a bar and filling, use appropriate method.
6503                             else if (s.fill) {
6504                                 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
6505                                     forceMinZero = true;
6506                                 }
6507                                 else if (vmin < 0 && vmax > 0 && s.fillToZero) {
6508                                     forceMinZero = false;
6509                                     forceZeroLine = true;
6510                                 }
6511                                 else {
6512                                     forceMinZero = false;
6513                                     forceZeroLine = false;
6514                                 }
6515                             }
6516                             
6517                             // if not a bar and not filling, only change existing state
6518                             // if it doesn't make sense
6519                             else if (vmin < 0) {
6520                                 forceMinZero = false;
6521                             }
6522                         }
6523                     }
6524                     
6525                     // check if we need make axis min at 0.
6526                     if (forceMinZero) {
6527                         // compute number of ticks
6528                         this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6529                         this.min = 0;
6530                         userMin = 0;
6531                         // what order is this range?
6532                         // what tick interval does that give us?
6533                         ti = max/(this.numberTicks-1);
6534                         temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6535                         if (ti/temp == parseInt(ti/temp, 10)) {
6536                             ti += temp;
6537                         }
6538                         this.tickInterval = Math.ceil(ti/temp) * temp;
6539                         this.max = this.tickInterval * (this.numberTicks - 1);
6540                     }
6541                     
6542                     // check if we need to make sure there is a tick at 0.
6543                     else if (forceZeroLine) {
6544                         // compute number of ticks
6545                         this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6546                         var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1));
6547                         var ntmax = this.numberTicks - 1  - ntmin;
6548                         ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax));
6549                         temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6550                         this.tickInterval = Math.ceil(ti/temp) * temp;
6551                         this.max = this.tickInterval * ntmax;
6552                         this.min = -this.tickInterval * ntmin;
6553                     }
6554                     
6555                     // if nothing else, do autoscaling which will try to line up ticks across axes.
6556                     else {  
6557                         if (this.numberTicks == null){
6558                             if (this.tickInterval) {
6559                                 this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
6560                             }
6561                             else {
6562                                 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6563                             }
6564                         }
6565                 
6566                         if (this.tickInterval == null) {
6567                             // get a tick interval
6568                             ti = range/(this.numberTicks - 1);
6569
6570                             if (ti < 1) {
6571                                 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6572                             }
6573                             else {
6574                                 temp = 1;
6575                             }
6576                             this.tickInterval = Math.ceil(ti*temp*this.pad)/temp;
6577                         }
6578                         else {
6579                             temp = 1 / this.tickInterval;
6580                         }
6581                         
6582                         // try to compute a nicer, more even tick interval
6583                         // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10));
6584                         // this.tickInterval = Math.ceil(ti/temp) * temp;
6585                         rrange = this.tickInterval * (this.numberTicks - 1);
6586                         margin = (rrange - range)/2;
6587            
6588                         if (this.min == null) {
6589                             this.min = Math.floor(temp*(min-margin))/temp;
6590                         }
6591                         if (this.max == null) {
6592                             this.max = this.min + rrange;
6593                         }
6594                     }
6595
6596                     // Compute a somewhat decent format string if it is needed.
6597                     // get precision of interval and determine a format string.
6598                     var sf = $.jqplot.getSignificantFigures(this.tickInterval);
6599
6600                     var fstr;
6601
6602                     // if we have only a whole number, use integer formatting
6603                     if (sf.digitsLeft >= sf.significantDigits) {
6604                         fstr = '%d';
6605                     }
6606
6607                     else {
6608                         var temp = Math.max(0, 5 - sf.digitsLeft);
6609                         temp = Math.min(temp, sf.digitsRight);
6610                         fstr = '%.'+ temp + 'f';
6611                     }
6612
6613                     this._autoFormatString = fstr;
6614                 }
6615                 
6616                 // Use the default algorithm which pads each axis to make the chart
6617                 // centered nicely on the grid.
6618                 else {
6619
6620                     rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1);
6621                     rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1);
6622                     range = rmax - rmin;
6623         
6624                     if (this.numberTicks == null){
6625                         // if tickInterval is specified by user, we will ignore computed maximum.
6626                         // max will be equal or greater to fit even # of ticks.
6627                         if (this.tickInterval != null) {
6628                             this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1;
6629                         }
6630                         else if (dim > 100) {
6631                             this.numberTicks = parseInt(3+(dim-100)/75, 10);
6632                         }
6633                         else {
6634                             this.numberTicks = 2;
6635                         }
6636                     }
6637                 
6638                     if (this.tickInterval == null) {
6639                         this.tickInterval = range / (this.numberTicks-1);
6640                     }
6641                     
6642                     if (this.max == null) {
6643                         rmax = rmin + this.tickInterval*(this.numberTicks - 1);
6644                     }        
6645                     if (this.min == null) {
6646                         rmin = rmax - this.tickInterval*(this.numberTicks - 1);
6647                     }
6648
6649                     // get precision of interval and determine a format string.
6650                     var sf = $.jqplot.getSignificantFigures(this.tickInterval);
6651
6652                     var fstr;
6653
6654                     // if we have only a whole number, use integer formatting
6655                     if (sf.digitsLeft >= sf.significantDigits) {
6656                         fstr = '%d';
6657                     }
6658
6659                     else {
6660                         var temp = Math.max(0, 5 - sf.digitsLeft);
6661                         temp = Math.min(temp, sf.digitsRight);
6662                         fstr = '%.'+ temp + 'f';
6663                     }
6664
6665
6666                     this._autoFormatString = fstr;
6667
6668                     this.min = rmin;
6669                     this.max = rmax;
6670                 }
6671                 
6672                 if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') {
6673                     // fix for misleading tick display with small range and low precision.
6674                     range = this.max - this.min;
6675                     // figure out precision
6676                     var temptick = new this.tickRenderer(this.tickOptions);
6677                     // use the tick formatString or, the default.
6678                     var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString; 
6679                     var fs = fs.match($.jqplot.sprintf.regex)[0];
6680                     var precision = 0;
6681                     if (fs) {
6682                         if (fs.search(/[fFeEgGpP]/) > -1) {
6683                             var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/);
6684                             if (m) {
6685                                 precision = parseInt(m[1], 10);
6686                             }
6687                             else {
6688                                 precision = 6;
6689                             }
6690                         }
6691                         else if (fs.search(/[di]/) > -1) {
6692                             precision = 0;
6693                         }
6694                         // fact will be <= 1;
6695                         var fact = Math.pow(10, -precision);
6696                         if (this.tickInterval < fact) {
6697                             // need to correct underrange
6698                             if (userNT == null && userTI == null) {
6699                                 this.tickInterval = fact;
6700                                 if (userMax == null && userMin == null) {
6701                                     // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact;
6702                                     this.min = Math.floor(this._dataBounds.min/fact) * fact;
6703                                     if (this.min == this._dataBounds.min) {
6704                                         this.min = this._dataBounds.min - this.tickInterval;
6705                                     }
6706                                     // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact;
6707                                     this.max = Math.ceil(this._dataBounds.max/fact) * fact;
6708                                     if (this.max == this._dataBounds.max) {
6709                                         this.max = this._dataBounds.max + this.tickInterval;
6710                                     }
6711                                     var n = (this.max - this.min)/this.tickInterval;
6712                                     n = n.toFixed(11);
6713                                     n = Math.ceil(n);
6714                                     this.numberTicks = n + 1;
6715                                 }
6716                                 else if (userMax == null) {
6717                                     // add one tick for top of range.
6718                                     var n = (this._dataBounds.max - this.min) / this.tickInterval;
6719                                     n = n.toFixed(11);
6720                                     this.numberTicks = Math.ceil(n) + 2;
6721                                     this.max = this.min + this.tickInterval * (this.numberTicks-1);
6722                                 }
6723                                 else if (userMin == null) {
6724                                     // add one tick for bottom of range.
6725                                     var n = (this.max - this._dataBounds.min) / this.tickInterval;
6726                                     n = n.toFixed(11);
6727                                     this.numberTicks = Math.ceil(n) + 2;
6728                                     this.min = this.max - this.tickInterval * (this.numberTicks-1);
6729                                 }
6730                                 else {
6731                                     // calculate a number of ticks so max is within axis scale
6732                                     this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1;
6733                                     // if user's min and max don't fit evenly in ticks, adjust.
6734                                     // This takes care of cases such as user min set to 0, max set to 3.5 but tick
6735                                     // format string set to %d (integer ticks)
6736                                     this.min =  Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision);
6737                                     this.max =  Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision);
6738                                     // this.max = this.min + this.tickInterval*(this.numberTicks-1);
6739                                     this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1;
6740                                 }
6741                             }
6742                         }
6743                     }
6744                 }
6745                 
6746             }
6747             
6748             if (this._overrideFormatString && this._autoFormatString != '') {
6749                 this.tickOptions = this.tickOptions || {};
6750                 this.tickOptions.formatString = this._autoFormatString;
6751             }
6752
6753             var t, to;
6754             for (var i=0; i<this.numberTicks; i++){
6755                 tt = this.min + i * this.tickInterval;
6756                 t = new this.tickRenderer(this.tickOptions);
6757                 // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
6758
6759                 t.setTick(tt, this.name);
6760                 this._ticks.push(t);
6761
6762                 if (i < this.numberTicks - 1) {
6763                     for (var j=0; j<this.minorTicks; j++) {
6764                         tt += this.tickInterval/(this.minorTicks+1);
6765                         to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true});
6766                         t = new this.tickRenderer(to);
6767                         this._ticks.push(t);
6768                     }
6769                 }
6770                 t = null;
6771             }
6772         }
6773
6774         if (this.tickInset) {
6775             this.min = this.min - this.tickInset * this.tickInterval;
6776             this.max = this.max + this.tickInset * this.tickInterval;
6777         }
6778
6779         ticks = null;
6780     };
6781     
6782     // Used to reset just the values of the ticks and then repack, which will
6783     // recalculate the positioning functions.  It is assuemd that the 
6784     // number of ticks is the same and the values of the new array are at the
6785     // proper interval.
6786     // This method needs to be called with the scope of an axis object, like:
6787     //
6788     // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr);
6789     //
6790     $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) {
6791         if ($.isArray(opts) && opts.length == this._ticks.length) {
6792             var t;
6793             for (var i=0; i<opts.length; i++) {
6794                 t = this._ticks[i];
6795                 t.value = opts[i];
6796                 t.label = t.formatter(t.formatString, opts[i]);
6797                 t.label = t.prefix + t.label;
6798                 t._elem.html(t.label);
6799             }
6800             t = null;
6801             this.min = $.jqplot.arrayMin(opts);
6802             this.max = $.jqplot.arrayMax(opts);
6803             this.pack();
6804         }
6805         // Not implemented yet.
6806         // else if ($.isPlainObject(opts)) {
6807         // 
6808         // }
6809     };
6810     
6811     // called with scope of axis
6812     $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) {
6813         // Add defaults for repacking from resetTickValues function.
6814         pos = pos || {};
6815         offsets = offsets || this._offsets;
6816         
6817         var ticks = this._ticks;
6818         var max = this.max;
6819         var min = this.min;
6820         var offmax = offsets.max;
6821         var offmin = offsets.min;
6822         var lshow = (this._label == null) ? false : this._label.show;
6823         
6824         for (var p in pos) {
6825             this._elem.css(p, pos[p]);
6826         }
6827         
6828         this._offsets = offsets;
6829         // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
6830         var pixellength = offmax - offmin;
6831         var unitlength = max - min;
6832         
6833         // point to unit and unit to point conversions references to Plot DOM element top left corner.
6834         if (this.breakPoints) {
6835             unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0];
6836             
6837             this.p2u = function(p){
6838                 return (p - offmin) * unitlength / pixellength + min;
6839             };
6840         
6841             this.u2p = function(u){
6842                 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6843                     u = this.breakPoints[0];
6844                 }
6845                 if (u <= this.breakPoints[0]) {
6846                     return (u - min) * pixellength / unitlength + offmin;
6847                 }
6848                 else {
6849                     return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin;
6850                 }
6851             };
6852                 
6853             if (this.name.charAt(0) == 'x'){
6854                 this.series_u2p = function(u){
6855                     if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6856                         u = this.breakPoints[0];
6857                     }
6858                     if (u <= this.breakPoints[0]) {
6859                         return (u - min) * pixellength / unitlength;
6860                     }
6861                     else {
6862                         return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength;
6863                     }
6864                 };
6865                 this.series_p2u = function(p){
6866                     return p * unitlength / pixellength + min;
6867                 };
6868             }
6869         
6870             else {
6871                 this.series_u2p = function(u){
6872                     if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6873                         u = this.breakPoints[0];
6874                     }
6875                     if (u >= this.breakPoints[1]) {
6876                         return (u - max) * pixellength / unitlength;
6877                     }
6878                     else {
6879                         return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength;
6880                     }
6881                 };
6882                 this.series_p2u = function(p){
6883                     return p * unitlength / pixellength + max;
6884                 };
6885             }
6886         }
6887         else {
6888             this.p2u = function(p){
6889                 return (p - offmin) * unitlength / pixellength + min;
6890             };
6891         
6892             this.u2p = function(u){
6893                 return (u - min) * pixellength / unitlength + offmin;
6894             };
6895                 
6896             if (this.name == 'xaxis' || this.name == 'x2axis'){
6897                 this.series_u2p = function(u){
6898                     return (u - min) * pixellength / unitlength;
6899                 };
6900                 this.series_p2u = function(p){
6901                     return p * unitlength / pixellength + min;
6902                 };
6903             }
6904         
6905             else {
6906                 this.series_u2p = function(u){
6907                     return (u - max) * pixellength / unitlength;
6908                 };
6909                 this.series_p2u = function(p){
6910                     return p * unitlength / pixellength + max;
6911                 };
6912             }
6913         }
6914         
6915         if (this.show) {
6916             if (this.name == 'xaxis' || this.name == 'x2axis') {
6917                 for (var i=0; i<ticks.length; i++) {
6918                     var t = ticks[i];
6919                     if (t.show && t.showLabel) {
6920                         var shim;
6921                         
6922                         if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
6923                             // will need to adjust auto positioning based on which axis this is.
6924                             var temp = (this.name == 'xaxis') ? 1 : -1;
6925                             switch (t.labelPosition) {
6926                                 case 'auto':
6927                                     // position at end
6928                                     if (temp * t.angle < 0) {
6929                                         shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6930                                     }
6931                                     // position at start
6932                                     else {
6933                                         shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6934                                     }
6935                                     break;
6936                                 case 'end':
6937                                     shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6938                                     break;
6939                                 case 'start':
6940                                     shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6941                                     break;
6942                                 case 'middle':
6943                                     shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6944                                     break;
6945                                 default:
6946                                     shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6947                                     break;
6948                             }
6949                         }
6950                         else {
6951                             shim = -t.getWidth()/2;
6952                         }
6953                         var val = this.u2p(t.value) + shim + 'px';
6954                         t._elem.css('left', val);
6955                         t.pack();
6956                     }
6957                 }
6958                 if (lshow) {
6959                     var w = this._label._elem.outerWidth(true);
6960                     this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
6961                     if (this.name == 'xaxis') {
6962                         this._label._elem.css('bottom', '0px');
6963                     }
6964                     else {
6965                         this._label._elem.css('top', '0px');
6966                     }
6967                     this._label.pack();
6968                 }
6969             }
6970             else {
6971                 for (var i=0; i<ticks.length; i++) {
6972                     var t = ticks[i];
6973                     if (t.show && t.showLabel) {                        
6974                         var shim;
6975                         if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
6976                             // will need to adjust auto positioning based on which axis this is.
6977                             var temp = (this.name == 'yaxis') ? 1 : -1;
6978                             switch (t.labelPosition) {
6979                                 case 'auto':
6980                                     // position at end
6981                                 case 'end':
6982                                     if (temp * t.angle < 0) {
6983                                         shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
6984                                     }
6985                                     else {
6986                                         shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
6987                                     }
6988                                     break;
6989                                 case 'start':
6990                                     if (t.angle > 0) {
6991                                         shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
6992                                     }
6993                                     else {
6994                                         shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
6995                                     }
6996                                     break;
6997                                 case 'middle':
6998                                     // if (t.angle > 0) {
6999                                     //     shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
7000                                     // }
7001                                     // else {
7002                                     //     shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
7003                                     // }
7004                                     shim = -t.getHeight()/2;
7005                                     break;
7006                                 default:
7007                                     shim = -t.getHeight()/2;
7008                                     break;
7009                             }
7010                         }
7011                         else {
7012                             shim = -t.getHeight()/2;
7013                         }
7014                         
7015                         var val = this.u2p(t.value) + shim + 'px';
7016                         t._elem.css('top', val);
7017                         t.pack();
7018                     }
7019                 }
7020                 if (lshow) {
7021                     var h = this._label._elem.outerHeight(true);
7022                     this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
7023                     if (this.name == 'yaxis') {
7024                         this._label._elem.css('left', '0px');
7025                     }
7026                     else {
7027                         this._label._elem.css('right', '0px');
7028                     }   
7029                     this._label.pack();
7030                 }
7031             }
7032         }
7033
7034         ticks = null;
7035     };
7036
7037
7038     /**
7039     * The following code was generaously given to me a while back by Scott Prahl.
7040     * He did a good job at computing axes min, max and number of ticks for the 
7041     * case where the user has not set any scale related parameters (tickInterval,
7042     * numberTicks, min or max).  I had ignored this use case for a long time,
7043     * focusing on the more difficult case where user has set some option controlling
7044     * tick generation.  Anyway, about time I got this into jqPlot.
7045     * Thanks Scott!!
7046     */
7047     
7048     /**
7049     * Copyright (c) 2010 Scott Prahl
7050     * The next three routines are currently available for use in all personal 
7051     * or commercial projects under both the MIT and GPL version 2.0 licenses. 
7052     * This means that you can choose the license that best suits your project 
7053     * and use it accordingly. 
7054     */
7055
7056     // A good format string depends on the interval. If the interval is greater 
7057     // than 1 then there is no need to show any decimal digits. If it is < 1.0, then
7058     // use the magnitude of the interval to determine the number of digits to show.
7059     function bestFormatString (interval)
7060     {
7061         var fstr;
7062         interval = Math.abs(interval);
7063         if (interval >= 10) {
7064             fstr = '%d';
7065         }
7066
7067         else if (interval > 1) {
7068             if (interval === parseInt(interval, 10)) {
7069                 fstr = '%d';
7070             }
7071             else {
7072                 fstr = '%.1f';
7073             }
7074         }
7075
7076         else {
7077             var expv = -Math.floor(Math.log(interval)/Math.LN10);
7078             fstr = '%.' + expv + 'f';
7079         }
7080         
7081         return fstr; 
7082     }
7083
7084     var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
7085
7086     var _getLowerFactor = function(f) {
7087         var i = _factors.indexOf(f);
7088         if (i > 0) {
7089             return _factors[i-1];
7090         }
7091         else {
7092             return _factors[_factors.length - 1] / 100;
7093         }
7094     };
7095
7096     var _getHigherFactor = function(f) {
7097         var i = _factors.indexOf(f);
7098         if (i < _factors.length-1) {
7099             return _factors[i+1];
7100         }
7101         else {
7102             return _factors[0] * 100;
7103         }
7104     };
7105
7106     // Given a fixed minimum and maximum and a target number ot ticks
7107     // figure out the best interval and 
7108     // return min, max, number ticks, format string and tick interval
7109     function bestConstrainedInterval(min, max, nttarget) {
7110         // run through possible number to ticks and see which interval is best
7111         var low = Math.floor(nttarget/2);
7112         var hi = Math.ceil(nttarget*1.5);
7113         var badness = Number.MAX_VALUE;
7114         var r = (max - min);
7115         var temp;
7116         var sd;
7117         var bestNT;
7118         var gsf = $.jqplot.getSignificantFigures;
7119         var fsd;
7120         var fs;
7121         var currentNT;
7122         var bestPrec;
7123
7124         for (var i=0, l=hi-low+1; i<l; i++) {
7125             currentNT = low + i;
7126             temp = r/(currentNT-1);
7127             sd = gsf(temp);
7128
7129             temp = Math.abs(nttarget - currentNT) + sd.digitsRight;
7130             if (temp < badness) {
7131                 badness = temp;
7132                 bestNT = currentNT;
7133                 bestPrec = sd.digitsRight;
7134             }
7135             else if (temp === badness) {
7136                 // let nicer ticks trump number ot ticks
7137                 if (sd.digitsRight < bestPrec) {
7138                     bestNT = currentNT;
7139                     bestPrec = sd.digitsRight;
7140                 }
7141             }
7142
7143         }
7144
7145         fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight));
7146         if (fsd === 0) {
7147             fs = '%d';
7148         }
7149         else {
7150             fs = '%.' + fsd + 'f';
7151         }
7152         temp = r / (bestNT - 1);
7153         // min, max, number ticks, format string, tick interval
7154         return [min, max, bestNT, fs, temp];
7155     }
7156
7157     // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
7158     // it is based soley on the range and number of ticks.  So if user specifies
7159     // number of ticks, use this.
7160     function bestInterval(range, numberTicks) {
7161         numberTicks = numberTicks || 7;
7162         var minimum = range / (numberTicks - 1);
7163         var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
7164         var residual = minimum / magnitude;
7165         var interval;
7166         // "nicest" ranges are 1, 2, 5 or powers of these.
7167         // for magnitudes below 1, only allow these. 
7168         if (magnitude < 1) {
7169             if (residual > 5) {
7170                 interval = 10 * magnitude;
7171             }
7172             else if (residual > 2) {
7173                 interval = 5 * magnitude;
7174             }
7175             else if (residual > 1) {
7176                 interval = 2 * magnitude;
7177             }
7178             else {
7179                 interval = magnitude;
7180             }
7181         }
7182         // for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
7183         // this helps a lot with poor choices for number of ticks. 
7184         else {
7185             if (residual > 5) {
7186                 interval = 10 * magnitude;
7187             }
7188             else if (residual > 4) {
7189                 interval = 5 * magnitude;
7190             }
7191             else if (residual > 3) {
7192                 interval = 4 * magnitude;
7193             }
7194             else if (residual > 2) {
7195                 interval = 3 * magnitude;
7196             }
7197             else if (residual > 1) {
7198                 interval = 2 * magnitude;
7199             }
7200             else {
7201                 interval = magnitude;
7202             }
7203         }
7204
7205         return interval;
7206     }
7207
7208     // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
7209     // it is based soley on the range of data, number of ticks must be computed later.
7210     function bestLinearInterval(range, scalefact) {
7211         scalefact = scalefact || 1;
7212         var expv = Math.floor(Math.log(range)/Math.LN10);
7213         var magnitude = Math.pow(10, expv);
7214         // 0 < f < 10
7215         var f = range / magnitude;
7216         var fact;
7217         // for large plots, scalefact will decrease f and increase number of ticks.
7218         // for small plots, scalefact will increase f and decrease number of ticks.
7219         f = f/scalefact;
7220
7221         // for large plots, smaller interval, more ticks.
7222         if (f<=0.38) {
7223             fact = 0.1;
7224         }
7225         else if (f<=1.6) {
7226             fact = 0.2;
7227         }
7228         else if (f<=4.0) {
7229             fact = 0.5;
7230         }
7231         else if (f<=8.0) {
7232             fact = 1.0;
7233         }
7234         // for very small plots, larger interval, less ticks in number ticks
7235         else if (f<=16.0) {
7236             fact = 2;
7237         }
7238         else {
7239             fact = 5;
7240         } 
7241
7242         return fact*magnitude; 
7243     }
7244
7245     function bestLinearComponents(range, scalefact) {
7246         var expv = Math.floor(Math.log(range)/Math.LN10);
7247         var magnitude = Math.pow(10, expv);
7248         // 0 < f < 10
7249         var f = range / magnitude;
7250         var interval;
7251         var fact;
7252         // for large plots, scalefact will decrease f and increase number of ticks.
7253         // for small plots, scalefact will increase f and decrease number of ticks.
7254         f = f/scalefact;
7255
7256         // for large plots, smaller interval, more ticks.
7257         if (f<=0.38) {
7258             fact = 0.1;
7259         }
7260         else if (f<=1.6) {
7261             fact = 0.2;
7262         }
7263         else if (f<=4.0) {
7264             fact = 0.5;
7265         }
7266         else if (f<=8.0) {
7267             fact = 1.0;
7268         }
7269         // for very small plots, larger interval, less ticks in number ticks
7270         else if (f<=16.0) {
7271             fact = 2;
7272         }
7273         // else if (f<=20.0) {
7274         //     fact = 3;
7275         // }
7276         // else if (f<=24.0) {
7277         //     fact = 4;
7278         // }
7279         else {
7280             fact = 5;
7281         } 
7282
7283         interval = fact * magnitude;
7284
7285         return [interval, fact, magnitude];
7286     }
7287
7288     // Given the min and max for a dataset, return suitable endpoints
7289     // for the graphing, a good number for the number of ticks, and a
7290     // format string so that extraneous digits are not displayed.
7291     // returned is an array containing [min, max, nTicks, format]
7292     $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) {
7293         // Set to preserve EITHER min OR max.
7294         // If min is preserved, max must be free.
7295         keepMin = (keepMin === null) ? false : keepMin;
7296         keepMax = (keepMax === null || keepMin) ? false : keepMax;
7297         // if endpoints are equal try to include zero otherwise include one
7298         if (axis_min === axis_max) {
7299             axis_max = (axis_max) ? 0 : 1;
7300         }
7301
7302         scalefact = scalefact || 1.0;
7303
7304         // make sure range is positive
7305         if (axis_max < axis_min) {
7306             var a = axis_max;
7307             axis_max = axis_min;
7308             axis_min = a;
7309         }
7310
7311         var r = [];
7312         var ss = bestLinearInterval(axis_max - axis_min, scalefact);
7313
7314         var gsf = $.jqplot.getSignificantFigures;
7315         
7316         if (numberTicks == null) {
7317
7318             // Figure out the axis min, max and number of ticks
7319             // the min and max will be some multiple of the tick interval,
7320             // 1*10^n, 2*10^n or 5*10^n.  This gaurantees that, if the
7321             // axis min is negative, 0 will be a tick.
7322             if (!keepMin && !keepMax) {
7323                 r[0] = Math.floor(axis_min / ss) * ss;  // min
7324                 r[1] = Math.ceil(axis_max / ss) * ss;   // max
7325                 r[2] = Math.round((r[1]-r[0])/ss+1.0);  // number of ticks
7326                 r[3] = bestFormatString(ss);            // format string
7327                 r[4] = ss;                              // tick Interval
7328             }
7329
7330             else if (keepMin) {
7331                 r[0] = axis_min;                                        // min
7332                 r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0);     // number of ticks
7333                 r[1] = axis_min + (r[2] - 1) * ss;                      // max
7334                 var digitsMin = gsf(axis_min).digitsRight;
7335                 var digitsSS = gsf(ss).digitsRight;
7336                 if (digitsMin < digitsSS) {
7337                     r[3] = bestFormatString(ss);                        // format string
7338                 }
7339                 else {
7340                     r[3] = '%.' + digitsMin + 'f';
7341                 }
7342                 r[4] = ss;                                              // tick Interval
7343             }
7344
7345             else if (keepMax) {
7346                 r[1] = axis_max;                                        // max
7347                 r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0);     // number of ticks
7348                 r[0] = axis_max - (r[2] - 1) * ss;                      // min
7349                 var digitsMax = gsf(axis_max).digitsRight;
7350                 var digitsSS = gsf(ss).digitsRight;
7351                 if (digitsMax < digitsSS) {
7352                     r[3] = bestFormatString(ss);                        // format string
7353                 }
7354                 else {
7355                     r[3] = '%.' + digitsMax + 'f';
7356                 }
7357                 r[4] = ss;                                              // tick Interval
7358             }
7359         }
7360
7361         else {
7362             var tempr = [];
7363
7364             // Figure out the axis min, max and number of ticks
7365             // the min and max will be some multiple of the tick interval,
7366             // 1*10^n, 2*10^n or 5*10^n.  This gaurantees that, if the
7367             // axis min is negative, 0 will be a tick.
7368             tempr[0] = Math.floor(axis_min / ss) * ss;  // min
7369             tempr[1] = Math.ceil(axis_max / ss) * ss;   // max
7370             tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0);    // number of ticks
7371             tempr[3] = bestFormatString(ss);            // format string
7372             tempr[4] = ss;                              // tick Interval
7373
7374             // first, see if we happen to get the right number of ticks
7375             if (tempr[2] === numberTicks) {
7376                 r = tempr;
7377             }
7378
7379             else {
7380
7381                 var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
7382
7383                 r[0] = tempr[0];                        // min
7384                 r[2] = numberTicks;                     // number of ticks
7385                 r[4] = newti;                           // tick interval
7386                 r[3] = bestFormatString(newti);         // format string
7387                 r[1] = r[0] + (r[2] - 1) * r[4];        // max
7388             }
7389         }
7390
7391         return r;
7392     };
7393
7394     $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
7395     $.jqplot.LinearTickGenerator.bestInterval = bestInterval;
7396     $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
7397     $.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval;
7398
7399
7400     // class: $.jqplot.MarkerRenderer
7401     // The default jqPlot marker renderer, rendering the points on the line.
7402     $.jqplot.MarkerRenderer = function(options){
7403         // Group: Properties
7404         
7405         // prop: show
7406         // whether or not to show the marker.
7407         this.show = true;
7408         // prop: style
7409         // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare
7410         this.style = 'filledCircle';
7411         // prop: lineWidth
7412         // size of the line for non-filled markers.
7413         this.lineWidth = 2;
7414         // prop: size
7415         // Size of the marker (diameter or circle, length of edge of square, etc.)
7416         this.size = 9.0;
7417         // prop: color
7418         // color of marker.  Will be set to color of series by default on init.
7419         this.color = '#666666';
7420         // prop: shadow
7421         // whether or not to draw a shadow on the line
7422         this.shadow = true;
7423         // prop: shadowAngle
7424         // Shadow angle in degrees
7425         this.shadowAngle = 45;
7426         // prop: shadowOffset
7427         // Shadow offset from line in pixels
7428         this.shadowOffset = 1;
7429         // prop: shadowDepth
7430         // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
7431         this.shadowDepth = 3;
7432         // prop: shadowAlpha
7433         // Alpha channel transparency of shadow.  0 = transparent.
7434         this.shadowAlpha = '0.07';
7435         // prop: shadowRenderer
7436         // Renderer that will draws the shadows on the marker.
7437         this.shadowRenderer = new $.jqplot.ShadowRenderer();
7438         // prop: shapeRenderer
7439         // Renderer that will draw the marker.
7440         this.shapeRenderer = new $.jqplot.ShapeRenderer();
7441         
7442         $.extend(true, this, options);
7443     };
7444     
7445     $.jqplot.MarkerRenderer.prototype.init = function(options) {
7446         $.extend(true, this, options);
7447         var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true};
7448         if (this.style.indexOf('filled') != -1) {
7449             sdopt.fill = true;
7450         }
7451         if (this.style.indexOf('ircle') != -1) {
7452             sdopt.isarc = true;
7453             sdopt.closePath = false;
7454         }
7455         this.shadowRenderer.init(sdopt);
7456         
7457         var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true};
7458         if (this.style.indexOf('filled') != -1) {
7459             shopt.fill = true;
7460         }
7461         if (this.style.indexOf('ircle') != -1) {
7462             shopt.isarc = true;
7463             shopt.closePath = false;
7464         }
7465         this.shapeRenderer.init(shopt);
7466     };
7467     
7468     $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) {
7469         var stretch = 1.2;
7470         var dx = this.size/2/stretch;
7471         var dy = this.size/2*stretch;
7472         var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]];
7473         if (this.shadow) {
7474             this.shadowRenderer.draw(ctx, points);
7475         }
7476         this.shapeRenderer.draw(ctx, points, options);
7477     };
7478     
7479     $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) {
7480         var stretch = 1.0;
7481         var dx = this.size/2*stretch;
7482         var dy = this.size/2*stretch;
7483         var points1 = [[x, y-dy], [x, y+dy]];
7484         var points2 = [[x+dx, y], [x-dx, y]];
7485         var opts = $.extend(true, {}, this.options, {closePath:false});
7486         if (this.shadow) {
7487             this.shadowRenderer.draw(ctx, points1, {closePath:false});
7488             this.shadowRenderer.draw(ctx, points2, {closePath:false});
7489         }
7490         this.shapeRenderer.draw(ctx, points1, opts);
7491         this.shapeRenderer.draw(ctx, points2, opts);
7492     };
7493     
7494     $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) {
7495         var stretch = 1.0;
7496         var dx = this.size/2*stretch;
7497         var dy = this.size/2*stretch;
7498         var opts = $.extend(true, {}, this.options, {closePath:false});
7499         var points1 = [[x-dx, y-dy], [x+dx, y+dy]];
7500         var points2 = [[x-dx, y+dy], [x+dx, y-dy]];
7501         if (this.shadow) {
7502             this.shadowRenderer.draw(ctx, points1, {closePath:false});
7503             this.shadowRenderer.draw(ctx, points2, {closePath:false});
7504         }
7505         this.shapeRenderer.draw(ctx, points1, opts);
7506         this.shapeRenderer.draw(ctx, points2, opts);
7507     };
7508     
7509     $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) {
7510         var stretch = 1.0;
7511         var dx = this.size/2*stretch;
7512         var dy = this.size/2*stretch;
7513         var points = [[x-dx, y], [x+dx, y]];
7514         if (this.shadow) {
7515             this.shadowRenderer.draw(ctx, points);
7516         }
7517         this.shapeRenderer.draw(ctx, points, options);
7518     };
7519     
7520     $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) {
7521         var points = [p1, p2];
7522         if (this.shadow) {
7523             this.shadowRenderer.draw(ctx, points);
7524         }
7525         this.shapeRenderer.draw(ctx, points, options);
7526     };
7527     
7528     $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) {
7529         var stretch = 1.0;
7530         var dx = this.size/2/stretch;
7531         var dy = this.size/2*stretch;
7532         var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]];
7533         if (this.shadow) {
7534             this.shadowRenderer.draw(ctx, points);
7535         }
7536         this.shapeRenderer.draw(ctx, points, options);
7537     };
7538     
7539     $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) {
7540         var radius = this.size/2;
7541         var end = 2*Math.PI;
7542         var points = [x, y, radius, 0, end, true];
7543         if (this.shadow) {
7544             this.shadowRenderer.draw(ctx, points);
7545         }
7546         this.shapeRenderer.draw(ctx, points, options);
7547     };
7548     
7549     $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) {
7550         options = options || {};
7551         // hack here b/c shape renderer uses canvas based color style options
7552         // and marker uses css style names.
7553         if (options.show == null || options.show != false) {
7554             if (options.color && !options.fillStyle) {
7555                 options.fillStyle = options.color;
7556             }
7557             if (options.color && !options.strokeStyle) {
7558                 options.strokeStyle = options.color;
7559             }
7560             switch (this.style) {
7561                 case 'diamond':
7562                     this.drawDiamond(x,y,ctx, false, options);
7563                     break;
7564                 case 'filledDiamond':
7565                     this.drawDiamond(x,y,ctx, true, options);
7566                     break;
7567                 case 'circle':
7568                     this.drawCircle(x,y,ctx, false, options);
7569                     break;
7570                 case 'filledCircle':
7571                     this.drawCircle(x,y,ctx, true, options);
7572                     break;
7573                 case 'square':
7574                     this.drawSquare(x,y,ctx, false, options);
7575                     break;
7576                 case 'filledSquare':
7577                     this.drawSquare(x,y,ctx, true, options);
7578                     break;
7579                 case 'x':
7580                     this.drawX(x,y,ctx, true, options);
7581                     break;
7582                 case 'plus':
7583                     this.drawPlus(x,y,ctx, true, options);
7584                     break;
7585                 case 'dash':
7586                     this.drawDash(x,y,ctx, true, options);
7587                     break;
7588                 case 'line':
7589                     this.drawLine(x, y, ctx, false, options);
7590                     break;
7591                 default:
7592                     this.drawDiamond(x,y,ctx, false, options);
7593                     break;
7594             }
7595         }
7596     };
7597     
7598     // class: $.jqplot.shadowRenderer
7599     // The default jqPlot shadow renderer, rendering shadows behind shapes.
7600     $.jqplot.ShadowRenderer = function(options){ 
7601         // Group: Properties
7602         
7603         // prop: angle
7604         // Angle of the shadow in degrees.  Measured counter-clockwise from the x axis.
7605         this.angle = 45;
7606         // prop: offset
7607         // Pixel offset at the given shadow angle of each shadow stroke from the last stroke.
7608         this.offset = 1;
7609         // prop: alpha
7610         // alpha transparency of shadow stroke.
7611         this.alpha = 0.07;
7612         // prop: lineWidth
7613         // width of the shadow line stroke.
7614         this.lineWidth = 1.5;
7615         // prop: lineJoin
7616         // How line segments of the shadow are joined.
7617         this.lineJoin = 'miter';
7618         // prop: lineCap
7619         // how ends of the shadow line are rendered.
7620         this.lineCap = 'round';
7621         // prop; closePath
7622         // whether line path segment is closed upon itself.
7623         this.closePath = false;
7624         // prop: fill
7625         // whether to fill the shape.
7626         this.fill = false;
7627         // prop: depth
7628         // how many times the shadow is stroked.  Each stroke will be offset by offset at angle degrees.
7629         this.depth = 3;
7630         this.strokeStyle = 'rgba(0,0,0,0.1)';
7631         // prop: isarc
7632         // whether the shadow is an arc or not.
7633         this.isarc = false;
7634         
7635         $.extend(true, this, options);
7636     };
7637     
7638     $.jqplot.ShadowRenderer.prototype.init = function(options) {
7639         $.extend(true, this, options);
7640     };
7641     
7642     // function: draw
7643     // draws an transparent black (i.e. gray) shadow.
7644     //
7645     // ctx - canvas drawing context
7646     // points - array of points or [x, y, radius, start angle (rad), end angle (rad)]
7647     $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) {
7648         ctx.save();
7649         var opts = (options != null) ? options : {};
7650         var fill = (opts.fill != null) ? opts.fill : this.fill;
7651         var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
7652         var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
7653         var offset = (opts.offset != null) ? opts.offset : this.offset;
7654         var alpha = (opts.alpha != null) ? opts.alpha : this.alpha;
7655         var depth = (opts.depth != null) ? opts.depth : this.depth;
7656         var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
7657         var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
7658         ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth;
7659         ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin;
7660         ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap;
7661         ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')';
7662         ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')';
7663         for (var j=0; j<depth; j++) {
7664             var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
7665             ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset);
7666             ctxPattern.beginPath();
7667             if (isarc) {
7668                 ctx.arc(points[0], points[1], points[2], points[3], points[4], true);                
7669             }
7670             else if (fillRect) {
7671                 if (fillRect) {
7672                     ctx.fillRect(points[0], points[1], points[2], points[3]);
7673                 }
7674             }
7675             else if (points && points.length){
7676                 var move = true;
7677                 for (var i=0; i<points.length; i++) {
7678                     // skip to the first non-null point and move to it.
7679                     if (points[i][0] != null && points[i][1] != null) {
7680                         if (move) {
7681                             ctxPattern.moveTo(points[i][0], points[i][1]);
7682                             move = false;
7683                         }
7684                         else {
7685                             ctxPattern.lineTo(points[i][0], points[i][1]);
7686                         }
7687                     }
7688                     else {
7689                         move = true;
7690                     }
7691                 }
7692                 
7693             }
7694             if (closePath) {
7695                 ctxPattern.closePath();
7696             }
7697             if (fill) {
7698                 ctx.fill();
7699             }
7700             else {
7701                 ctx.stroke();
7702             }
7703         }
7704         ctx.restore();
7705     };
7706     
7707     // class: $.jqplot.shapeRenderer
7708     // The default jqPlot shape renderer.  Given a set of points will
7709     // plot them and either stroke a line (fill = false) or fill them (fill = true).
7710     // If a filled shape is desired, closePath = true must also be set to close
7711     // the shape.
7712     $.jqplot.ShapeRenderer = function(options){
7713         
7714         this.lineWidth = 1.5;
7715         // prop: linePattern
7716         // line pattern 'dashed', 'dotted', 'solid', some combination
7717         // of '-' and '.' characters such as '.-.' or a numerical array like 
7718         // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, 
7719         // [1, 10, 20, 10] to draw a dot-dash line, and so on.
7720         this.linePattern = 'solid';
7721         // prop: lineJoin
7722         // How line segments of the shadow are joined.
7723         this.lineJoin = 'miter';
7724         // prop: lineCap
7725         // how ends of the shadow line are rendered.
7726         this.lineCap = 'round';
7727         // prop; closePath
7728         // whether line path segment is closed upon itself.
7729         this.closePath = false;
7730         // prop: fill
7731         // whether to fill the shape.
7732         this.fill = false;
7733         // prop: isarc
7734         // whether the shadow is an arc or not.
7735         this.isarc = false;
7736         // prop: fillRect
7737         // true to draw shape as a filled rectangle.
7738         this.fillRect = false;
7739         // prop: strokeRect
7740         // true to draw shape as a stroked rectangle.
7741         this.strokeRect = false;
7742         // prop: clearRect
7743         // true to cear a rectangle.
7744         this.clearRect = false;
7745         // prop: strokeStyle
7746         // css color spec for the stoke style
7747         this.strokeStyle = '#999999';
7748         // prop: fillStyle
7749         // css color spec for the fill style.
7750         this.fillStyle = '#999999'; 
7751         
7752         $.extend(true, this, options);
7753     };
7754     
7755     $.jqplot.ShapeRenderer.prototype.init = function(options) {
7756         $.extend(true, this, options);
7757     };
7758     
7759     // function: draw
7760     // draws the shape.
7761     //
7762     // ctx - canvas drawing context
7763     // points - array of points for shapes or 
7764     // [x, y, width, height] for rectangles or
7765     // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs.
7766     $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) {
7767         ctx.save();
7768         var opts = (options != null) ? options : {};
7769         var fill = (opts.fill != null) ? opts.fill : this.fill;
7770         var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
7771         var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
7772         var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect;
7773         var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
7774         var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
7775         var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
7776         var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
7777         ctx.lineWidth = opts.lineWidth || this.lineWidth;
7778         ctx.lineJoin = opts.lineJoin || this.lineJoin;
7779         ctx.lineCap = opts.lineCap || this.lineCap;
7780         ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
7781         ctx.fillStyle = opts.fillStyle || this.fillStyle;
7782         ctx.beginPath();
7783         if (isarc) {
7784             ctx.arc(points[0], points[1], points[2], points[3], points[4], true);   
7785             if (closePath) {
7786                 ctx.closePath();
7787             }
7788             if (fill) {
7789                 ctx.fill();
7790             }
7791             else {
7792                 ctx.stroke();
7793             }
7794             ctx.restore();
7795             return;
7796         }
7797         else if (clearRect) {
7798             ctx.clearRect(points[0], points[1], points[2], points[3]);
7799             ctx.restore();
7800             return;
7801         }
7802         else if (fillRect || strokeRect) {
7803             if (fillRect) {
7804                 ctx.fillRect(points[0], points[1], points[2], points[3]);
7805             }
7806             if (strokeRect) {
7807                 ctx.strokeRect(points[0], points[1], points[2], points[3]);
7808                 ctx.restore();
7809                 return;
7810             }
7811         }
7812         else if (points && points.length){
7813             var move = true;
7814             for (var i=0; i<points.length; i++) {
7815                 // skip to the first non-null point and move to it.
7816                 if (points[i][0] != null && points[i][1] != null) {
7817                     if (move) {
7818                         ctxPattern.moveTo(points[i][0], points[i][1]);
7819                         move = false;
7820                     }
7821                     else {
7822                         ctxPattern.lineTo(points[i][0], points[i][1]);
7823                     }
7824                 }
7825                 else {
7826                     move = true;
7827                 }
7828             }
7829             if (closePath) {
7830                 ctxPattern.closePath();
7831             }
7832             if (fill) {
7833                 ctx.fill();
7834             }
7835             else {
7836                 ctx.stroke();
7837             }
7838         }
7839         ctx.restore();
7840     };
7841     
7842     // class $.jqplot.TableLegendRenderer
7843     // The default legend renderer for jqPlot.
7844     $.jqplot.TableLegendRenderer = function(){
7845         //
7846     };
7847     
7848     $.jqplot.TableLegendRenderer.prototype.init = function(options) {
7849         $.extend(true, this, options);
7850     };
7851         
7852     $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) {
7853         var rs = (pad) ? this.rowSpacing+'px' : '0px';
7854         var tr;
7855         var td;
7856         var elem;
7857         var div0;
7858         var div1;
7859         elem = document.createElement('tr');
7860         tr = $(elem);
7861         tr.addClass('jqplot-table-legend');
7862         elem = null;
7863
7864         if (reverse){
7865             tr.prependTo(this._elem);
7866         }
7867
7868         else{
7869             tr.appendTo(this._elem);
7870         }
7871
7872         if (this.showSwatches) {
7873             td = $(document.createElement('td'));
7874             td.addClass('jqplot-table-legend jqplot-table-legend-swatch');
7875             td.css({textAlign: 'center', paddingTop: rs});
7876
7877             div0 = $(document.createElement('div'));
7878             div0.addClass('jqplot-table-legend-swatch-outline');
7879             div1 = $(document.createElement('div'));
7880             div1.addClass('jqplot-table-legend-swatch');
7881             div1.css({backgroundColor: color, borderColor: color});
7882
7883             tr.append(td.append(div0.append(div1)));
7884
7885             // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
7886             // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+
7887             // '</div></td>').appendTo(tr);
7888         }
7889         if (this.showLabels) {
7890             td = $(document.createElement('td'));
7891             td.addClass('jqplot-table-legend jqplot-table-legend-label');
7892             td.css('paddingTop', rs);
7893             tr.append(td);
7894
7895             // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
7896             // elem.appendTo(tr);
7897             if (this.escapeHtml) {
7898                 td.text(label);
7899             }
7900             else {
7901                 td.html(label);
7902             }
7903         }
7904         td = null;
7905         div0 = null;
7906         div1 = null;
7907         tr = null;
7908         elem = null;
7909     };
7910     
7911     // called with scope of legend
7912     $.jqplot.TableLegendRenderer.prototype.draw = function() {
7913         if (this._elem) {
7914             this._elem.emptyForce();
7915             this._elem = null;
7916         }
7917
7918         if (this.show) {
7919             var series = this._series;
7920             // make a table.  one line label per row.
7921             var elem = document.createElement('table');
7922             this._elem = $(elem);
7923             this._elem.addClass('jqplot-table-legend');
7924
7925             var ss = {position:'absolute'};
7926             if (this.background) {
7927                 ss['background'] = this.background;
7928             }
7929             if (this.border) {
7930                 ss['border'] = this.border;
7931             }
7932             if (this.fontSize) {
7933                 ss['fontSize'] = this.fontSize;
7934             }
7935             if (this.fontFamily) {
7936                 ss['fontFamily'] = this.fontFamily;
7937             }
7938             if (this.textColor) {
7939                 ss['textColor'] = this.textColor;
7940             }
7941             if (this.marginTop != null) {
7942                 ss['marginTop'] = this.marginTop;
7943             }
7944             if (this.marginBottom != null) {
7945                 ss['marginBottom'] = this.marginBottom;
7946             }
7947             if (this.marginLeft != null) {
7948                 ss['marginLeft'] = this.marginLeft;
7949             }
7950             if (this.marginRight != null) {
7951                 ss['marginRight'] = this.marginRight;
7952             }
7953             
7954         
7955             var pad = false, 
7956                 reverse = false,
7957                 s;
7958             for (var i = 0; i< series.length; i++) {
7959                 s = series[i];
7960                 if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){
7961                     reverse = true;
7962                 }
7963                 if (s.show && s.showLabel) {
7964                     var lt = this.labels[i] || s.label.toString();
7965                     if (lt) {
7966                         var color = s.color;
7967                         if (reverse && i < series.length - 1){
7968                             pad = true;
7969                         }
7970                         else if (reverse && i == series.length - 1){
7971                             pad = false;
7972                         }
7973                         this.renderer.addrow.call(this, lt, color, pad, reverse);
7974                         pad = true;
7975                     }
7976                     // let plugins add more rows to legend.  Used by trend line plugin.
7977                     for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
7978                         var item = $.jqplot.addLegendRowHooks[j].call(this, s);
7979                         if (item) {
7980                             this.renderer.addrow.call(this, item.label, item.color, pad);
7981                             pad = true;
7982                         } 
7983                     }
7984                     lt = null;
7985                 }
7986             }
7987         }
7988         return this._elem;
7989     };
7990     
7991     $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) {
7992         if (this.show) {       
7993             if (this.placement == 'insideGrid') {
7994                 switch (this.location) {
7995                     case 'nw':
7996                         var a = offsets.left;
7997                         var b = offsets.top;
7998                         this._elem.css('left', a);
7999                         this._elem.css('top', b);
8000                         break;
8001                     case 'n':
8002                         var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8003                         var b = offsets.top;
8004                         this._elem.css('left', a);
8005                         this._elem.css('top', b);
8006                         break;
8007                     case 'ne':
8008                         var a = offsets.right;
8009                         var b = offsets.top;
8010                         this._elem.css({right:a, top:b});
8011                         break;
8012                     case 'e':
8013                         var a = offsets.right;
8014                         var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8015                         this._elem.css({right:a, top:b});
8016                         break;
8017                     case 'se':
8018                         var a = offsets.right;
8019                         var b = offsets.bottom;
8020                         this._elem.css({right:a, bottom:b});
8021                         break;
8022                     case 's':
8023                         var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8024                         var b = offsets.bottom;
8025                         this._elem.css({left:a, bottom:b});
8026                         break;
8027                     case 'sw':
8028                         var a = offsets.left;
8029                         var b = offsets.bottom;
8030                         this._elem.css({left:a, bottom:b});
8031                         break;
8032                     case 'w':
8033                         var a = offsets.left;
8034                         var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8035                         this._elem.css({left:a, top:b});
8036                         break;
8037                     default:  // same as 'se'
8038                         var a = offsets.right;
8039                         var b = offsets.bottom;
8040                         this._elem.css({right:a, bottom:b});
8041                         break;
8042                 }
8043                 
8044             }
8045             else if (this.placement == 'outside'){
8046                 switch (this.location) {
8047                     case 'nw':
8048                         var a = this._plotDimensions.width - offsets.left;
8049                         var b = offsets.top;
8050                         this._elem.css('right', a);
8051                         this._elem.css('top', b);
8052                         break;
8053                     case 'n':
8054                         var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8055                         var b = this._plotDimensions.height - offsets.top;
8056                         this._elem.css('left', a);
8057                         this._elem.css('bottom', b);
8058                         break;
8059                     case 'ne':
8060                         var a = this._plotDimensions.width - offsets.right;
8061                         var b = offsets.top;
8062                         this._elem.css({left:a, top:b});
8063                         break;
8064                     case 'e':
8065                         var a = this._plotDimensions.width - offsets.right;
8066                         var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8067                         this._elem.css({left:a, top:b});
8068                         break;
8069                     case 'se':
8070                         var a = this._plotDimensions.width - offsets.right;
8071                         var b = offsets.bottom;
8072                         this._elem.css({left:a, bottom:b});
8073                         break;
8074                     case 's':
8075                         var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8076                         var b = this._plotDimensions.height - offsets.bottom;
8077                         this._elem.css({left:a, top:b});
8078                         break;
8079                     case 'sw':
8080                         var a = this._plotDimensions.width - offsets.left;
8081                         var b = offsets.bottom;
8082                         this._elem.css({right:a, bottom:b});
8083                         break;
8084                     case 'w':
8085                         var a = this._plotDimensions.width - offsets.left;
8086                         var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8087                         this._elem.css({right:a, top:b});
8088                         break;
8089                     default:  // same as 'se'
8090                         var a = offsets.right;
8091                         var b = offsets.bottom;
8092                         this._elem.css({right:a, bottom:b});
8093                         break;
8094                 }
8095             }
8096             else {
8097                 switch (this.location) {
8098                     case 'nw':
8099                         this._elem.css({left:0, top:offsets.top});
8100                         break;
8101                     case 'n':
8102                         var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8103                         this._elem.css({left: a, top:offsets.top});
8104                         break;
8105                     case 'ne':
8106                         this._elem.css({right:0, top:offsets.top});
8107                         break;
8108                     case 'e':
8109                         var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8110                         this._elem.css({right:offsets.right, top:b});
8111                         break;
8112                     case 'se':
8113                         this._elem.css({right:offsets.right, bottom:offsets.bottom});
8114                         break;
8115                     case 's':
8116                         var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8117                         this._elem.css({left: a, bottom:offsets.bottom});
8118                         break;
8119                     case 'sw':
8120                         this._elem.css({left:offsets.left, bottom:offsets.bottom});
8121                         break;
8122                     case 'w':
8123                         var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8124                         this._elem.css({left:offsets.left, top:b});
8125                         break;
8126                     default:  // same as 'se'
8127                         this._elem.css({right:offsets.right, bottom:offsets.bottom});
8128                         break;
8129                 }
8130             }
8131         } 
8132     };
8133
8134     /**
8135      * Class: $.jqplot.ThemeEngine
8136      * Theme Engine provides a programatic way to change some of the  more
8137      * common jqplot styling options such as fonts, colors and grid options.
8138      * A theme engine instance is created with each plot.  The theme engine
8139      * manages a collection of themes which can be modified, added to, or 
8140      * applied to the plot.
8141      * 
8142      * The themeEngine class is not instantiated directly.
8143      * When a plot is initialized, the current plot options are scanned
8144      * an a default theme named "Default" is created.  This theme is
8145      * used as the basis for other themes added to the theme engine and
8146      * is always available.
8147      * 
8148      * A theme is a simple javascript object with styling parameters for
8149      * various entities of the plot.  A theme has the form:
8150      * 
8151      * 
8152      * > {
8153      * >     _name:f "Default",
8154      * >     target: {
8155      * >         backgroundColor: "transparent"
8156      * >     },
8157      * >     legend: {
8158      * >         textColor: null,
8159      * >         fontFamily: null,
8160      * >         fontSize: null,
8161      * >         border: null,
8162      * >         background: null
8163      * >     },
8164      * >     title: {
8165      * >         textColor: "rgb(102, 102, 102)",
8166      * >         fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
8167      * >         fontSize: "19.2px",
8168      * >         textAlign: "center"
8169      * >     },
8170      * >     seriesStyles: {},
8171      * >     series: [{
8172      * >         color: "#4bb2c5",
8173      * >         lineWidth: 2.5,
8174      * >         linePattern: "solid",
8175      * >         shadow: true,
8176      * >         fillColor: "#4bb2c5",
8177      * >         showMarker: true,
8178      * >         markerOptions: {
8179      * >             color: "#4bb2c5",
8180      * >             show: true,
8181      * >             style: 'filledCircle',
8182      * >             lineWidth: 1.5,
8183      * >             size: 4,
8184      * >             shadow: true
8185      * >         }
8186      * >     }],
8187      * >     grid: {
8188      * >         drawGridlines: true,
8189      * >         gridLineColor: "#cccccc",
8190      * >         gridLineWidth: 1,
8191      * >         backgroundColor: "#fffdf6",
8192      * >         borderColor: "#999999",
8193      * >         borderWidth: 2,
8194      * >         shadow: true
8195      * >     },
8196      * >     axesStyles: {
8197      * >         label: {},
8198      * >         ticks: {}
8199      * >     },
8200      * >     axes: {
8201      * >         xaxis: {
8202      * >             borderColor: "#999999",
8203      * >             borderWidth: 2,
8204      * >             ticks: {
8205      * >                 show: true,
8206      * >                 showGridline: true,
8207      * >                 showLabel: true,
8208      * >                 showMark: true,
8209      * >                 size: 4,
8210      * >                 textColor: "",
8211      * >                 whiteSpace: "nowrap",
8212      * >                 fontSize: "12px",
8213      * >                 fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
8214      * >             },
8215      * >             label: {
8216      * >                 textColor: "rgb(102, 102, 102)",
8217      * >                 whiteSpace: "normal",
8218      * >                 fontSize: "14.6667px",
8219      * >                 fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
8220      * >                 fontWeight: "400"
8221      * >             }
8222      * >         },
8223      * >         yaxis: {
8224      * >             borderColor: "#999999",
8225      * >             borderWidth: 2,
8226      * >             ticks: {
8227      * >                 show: true,
8228      * >                 showGridline: true,
8229      * >                 showLabel: true,
8230      * >                 showMark: true,
8231      * >                 size: 4,
8232      * >                 textColor: "",
8233      * >                 whiteSpace: "nowrap",
8234      * >                 fontSize: "12px",
8235      * >                 fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
8236      * >             },
8237      * >             label: {
8238      * >                 textColor: null,
8239      * >                 whiteSpace: null,
8240      * >                 fontSize: null,
8241      * >                 fontFamily: null,
8242      * >                 fontWeight: null
8243      * >             }
8244      * >         },
8245      * >         x2axis: {...
8246      * >         },
8247      * >         ...
8248      * >         y9axis: {...
8249      * >         }
8250      * >     }
8251      * > }
8252      * 
8253      * "seriesStyles" is a style object that will be applied to all series in the plot.
8254      * It will forcibly override any styles applied on the individual series.  "axesStyles" is
8255      * a style object that will be applied to all axes in the plot.  It will also forcibly
8256      * override any styles on the individual axes.
8257      * 
8258      * The example shown above has series options for a line series.  Options for other
8259      * series types are shown below:
8260      * 
8261      * Bar Series:
8262      * 
8263      * > {
8264      * >     color: "#4bb2c5",
8265      * >     seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
8266      * >     lineWidth: 2.5,
8267      * >     shadow: true,
8268      * >     barPadding: 2,
8269      * >     barMargin: 10,
8270      * >     barWidth: 15.09375,
8271      * >     highlightColors: ["rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)"]
8272      * > }
8273      * 
8274      * Pie Series:
8275      * 
8276      * > {
8277      * >     seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
8278      * >     padding: 20,
8279      * >     sliceMargin: 0,
8280      * >     fill: true,
8281      * >     shadow: true,
8282      * >     startAngle: 0,
8283      * >     lineWidth: 2.5,
8284      * >     highlightColors: ["rgb(129,201,214)", "rgb(240,189,104)", "rgb(214,202,165)", "rgb(137,180,158)", "rgb(168,180,137)", "rgb(180,174,89)", "rgb(180,113,161)", "rgb(129,141,236)", "rgb(227,205,120)", "rgb(255,138,76)", "rgb(76,169,219)", "rgb(215,126,190)", "rgb(220,232,135)", "rgb(200,167,96)", "rgb(103,202,235)", "rgb(208,154,215)"]
8285      * > }
8286      * 
8287      * Funnel Series:
8288      * 
8289      * > {
8290      * >     color: "#4bb2c5",
8291      * >     lineWidth: 2,
8292      * >     shadow: true,
8293      * >     padding: {
8294      * >         top: 20,
8295      * >         right: 20,
8296      * >         bottom: 20,
8297      * >         left: 20
8298      * >     },
8299      * >     sectionMargin: 6,
8300      * >     seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
8301      * >     highlightColors: ["rgb(147,208,220)", "rgb(242,199,126)", "rgb(220,210,178)", "rgb(154,191,172)", "rgb(180,191,154)", "rgb(191,186,112)", "rgb(191,133,174)", "rgb(147,157,238)", "rgb(231,212,139)", "rgb(255,154,102)", "rgb(102,181,224)", "rgb(221,144,199)", "rgb(225,235,152)", "rgb(200,167,96)", "rgb(124,210,238)", "rgb(215,169,221)"]
8302      * > }
8303      * 
8304      */
8305     $.jqplot.ThemeEngine = function(){
8306         // Group: Properties
8307         //
8308         // prop: themes
8309         // hash of themes managed by the theme engine.  
8310         // Indexed by theme name.
8311         this.themes = {};
8312         // prop: activeTheme
8313         // Pointer to currently active theme
8314         this.activeTheme=null;
8315         
8316     };
8317     
8318     // called with scope of plot
8319     $.jqplot.ThemeEngine.prototype.init = function() {
8320         // get the Default theme from the current plot settings.
8321         var th = new $.jqplot.Theme({_name:'Default'});
8322         var n, i, nn;
8323         
8324         for (n in th.target) {
8325             if (n == "textColor") {
8326                 th.target[n] = this.target.css('color');
8327             }
8328             else {
8329                 th.target[n] = this.target.css(n);
8330             }
8331         }
8332         
8333         if (this.title.show && this.title._elem) {
8334             for (n in th.title) {
8335                 if (n == "textColor") {
8336                     th.title[n] = this.title._elem.css('color');
8337                 }
8338                 else {
8339                     th.title[n] = this.title._elem.css(n);
8340                 }
8341             }
8342         }
8343         
8344         for (n in th.grid) {
8345             th.grid[n] = this.grid[n];
8346         }
8347         if (th.grid.backgroundColor == null && this.grid.background != null) {
8348             th.grid.backgroundColor = this.grid.background;
8349         }
8350         if (this.legend.show && this.legend._elem) {
8351             for (n in th.legend) {
8352                 if (n == 'textColor') {
8353                     th.legend[n] = this.legend._elem.css('color');
8354                 }
8355                 else {
8356                     th.legend[n] = this.legend._elem.css(n);
8357                 }
8358             }
8359         }
8360         var s;
8361         
8362         for (i=0; i<this.series.length; i++) {
8363             s = this.series[i];
8364             if (s.renderer.constructor == $.jqplot.LineRenderer) {
8365                 th.series.push(new LineSeriesProperties());
8366             }
8367             else if (s.renderer.constructor == $.jqplot.BarRenderer) {
8368                 th.series.push(new BarSeriesProperties());
8369             }
8370             else if (s.renderer.constructor == $.jqplot.PieRenderer) {
8371                 th.series.push(new PieSeriesProperties());
8372             }
8373             else if (s.renderer.constructor == $.jqplot.DonutRenderer) {
8374                 th.series.push(new DonutSeriesProperties());
8375             }
8376             else if (s.renderer.constructor == $.jqplot.FunnelRenderer) {
8377                 th.series.push(new FunnelSeriesProperties());
8378             }
8379             else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) {
8380                 th.series.push(new MeterSeriesProperties());
8381             }
8382             else {
8383                 th.series.push({});
8384             }
8385             for (n in th.series[i]) {
8386                 th.series[i][n] = s[n];
8387             }
8388         }
8389         var a, ax;
8390         for (n in this.axes) {
8391             ax = this.axes[n];
8392             a = th.axes[n] = new AxisProperties();
8393             a.borderColor = ax.borderColor;
8394             a.borderWidth = ax.borderWidth;
8395             if (ax._ticks && ax._ticks[0]) {
8396                 for (nn in a.ticks) {
8397                     if (ax._ticks[0].hasOwnProperty(nn)) {
8398                         a.ticks[nn] = ax._ticks[0][nn];
8399                     }
8400                     else if (ax._ticks[0]._elem){
8401                         a.ticks[nn] = ax._ticks[0]._elem.css(nn);
8402                     }
8403                 }
8404             }
8405             if (ax._label && ax._label.show) {
8406                 for (nn in a.label) {
8407                     // a.label[nn] = ax._label._elem.css(nn);
8408                     if (ax._label[nn]) {
8409                         a.label[nn] = ax._label[nn];
8410                     }
8411                     else if (ax._label._elem){
8412                         if (nn == 'textColor') {
8413                             a.label[nn] = ax._label._elem.css('color');
8414                         }
8415                         else {
8416                             a.label[nn] = ax._label._elem.css(nn);
8417                         }
8418                     }
8419                 }
8420             }
8421         }
8422         this.themeEngine._add(th);
8423         this.themeEngine.activeTheme  = this.themeEngine.themes[th._name];
8424     };
8425     /**
8426      * Group: methods
8427      * 
8428      * method: get
8429      * 
8430      * Get and return the named theme or the active theme if no name given.
8431      * 
8432      * parameter:
8433      * 
8434      * name - name of theme to get.
8435      * 
8436      * returns:
8437      * 
8438      * Theme instance of given name.
8439      */   
8440     $.jqplot.ThemeEngine.prototype.get = function(name) {
8441         if (!name) {
8442             // return the active theme
8443             return this.activeTheme;
8444         }
8445         else {
8446             return this.themes[name];
8447         }
8448     };
8449     
8450     function numericalOrder(a,b) { return a-b; }
8451     
8452     /**
8453      * method: getThemeNames
8454      * 
8455      * Return the list of theme names in this manager in alpha-numerical order.
8456      * 
8457      * parameter:
8458      * 
8459      * None
8460      * 
8461      * returns:
8462      * 
8463      * A the list of theme names in this manager in alpha-numerical order.
8464      */       
8465     $.jqplot.ThemeEngine.prototype.getThemeNames = function() {
8466         var tn = [];
8467         for (var n in this.themes) {
8468             tn.push(n);
8469         }
8470         return tn.sort(numericalOrder);
8471     };
8472
8473     /**
8474      * method: getThemes
8475      * 
8476      * Return a list of themes in alpha-numerical order by name.
8477      * 
8478      * parameter:
8479      * 
8480      * None
8481      * 
8482      * returns:
8483      * 
8484      * A list of themes in alpha-numerical order by name.
8485      */ 
8486     $.jqplot.ThemeEngine.prototype.getThemes = function() {
8487         var tn = [];
8488         var themes = [];
8489         for (var n in this.themes) {
8490             tn.push(n);
8491         }
8492         tn.sort(numericalOrder);
8493         for (var i=0; i<tn.length; i++) {
8494             themes.push(this.themes[tn[i]]);
8495         }
8496         return themes;
8497     };
8498     
8499     $.jqplot.ThemeEngine.prototype.activate = function(plot, name) {
8500         // sometimes need to redraw whole plot.
8501         var redrawPlot = false;
8502         if (!name && this.activeTheme && this.activeTheme._name) {
8503             name = this.activeTheme._name;
8504         }
8505         if (!this.themes.hasOwnProperty(name)) {
8506             throw new Error("No theme of that name");
8507         }
8508         else {
8509             var th = this.themes[name];
8510             this.activeTheme = th;
8511             var val, checkBorderColor = false, checkBorderWidth = false;
8512             var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis'];
8513             
8514             for (i=0; i<arr.length; i++) {
8515                 var ax = arr[i];
8516                 if (th.axesStyles.borderColor != null) {
8517                     plot.axes[ax].borderColor = th.axesStyles.borderColor;
8518                 }
8519                 if (th.axesStyles.borderWidth != null) {
8520                     plot.axes[ax].borderWidth = th.axesStyles.borderWidth;
8521                 }
8522             }
8523             
8524             for (var axname in plot.axes) {
8525                 var axis = plot.axes[axname];
8526                 if (axis.show) {
8527                     var thaxis = th.axes[axname] || {};
8528                     var thaxstyle = th.axesStyles;
8529                     var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle);
8530                     val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor;
8531                     if (thax.borderColor != null) {
8532                         axis.borderColor = thax.borderColor;
8533                         redrawPlot = true;
8534                     }
8535                     val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth;
8536                     if (thax.borderWidth != null) {
8537                         axis.borderWidth = thax.borderWidth;
8538                         redrawPlot = true;
8539                     }
8540                     if (axis._ticks && axis._ticks[0]) {
8541                         for (var nn in thax.ticks) {
8542                             // val = null;
8543                             // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) {
8544                             //     val = th.axesStyles.ticks[nn];
8545                             // }
8546                             // else if (thax.ticks[nn] != null){
8547                             //     val = thax.ticks[nn]
8548                             // }
8549                             val = thax.ticks[nn];
8550                             if (val != null) {
8551                                 axis.tickOptions[nn] = val;
8552                                 axis._ticks = [];
8553                                 redrawPlot = true;
8554                             }
8555                         }
8556                     }
8557                     if (axis._label && axis._label.show) {
8558                         for (var nn in thax.label) {
8559                             // val = null;
8560                             // if (th.axesStyles.label && th.axesStyles.label[nn] != null) {
8561                             //     val = th.axesStyles.label[nn];
8562                             // }
8563                             // else if (thax.label && thax.label[nn] != null){
8564                             //     val = thax.label[nn]
8565                             // }
8566                             val = thax.label[nn];
8567                             if (val != null) {
8568                                 axis.labelOptions[nn] = val;
8569                                 redrawPlot = true;
8570                             }
8571                         }
8572                     }
8573                     
8574                 }
8575             }            
8576             
8577             for (var n in th.grid) {
8578                 if (th.grid[n] != null) {
8579                     plot.grid[n] = th.grid[n];
8580                 }
8581             }
8582             if (!redrawPlot) {
8583                 plot.grid.draw();
8584             }
8585             
8586             if (plot.legend.show) { 
8587                 for (n in th.legend) {
8588                     if (th.legend[n] != null) {
8589                         plot.legend[n] = th.legend[n];
8590                     }
8591                 }
8592             }
8593             if (plot.title.show) {
8594                 for (n in th.title) {
8595                     if (th.title[n] != null) {
8596                         plot.title[n] = th.title[n];
8597                     }
8598                 }
8599             }
8600             
8601             var i;
8602             for (i=0; i<th.series.length; i++) {
8603                 var opts = {};
8604                 var redrawSeries = false;
8605                 for (n in th.series[i]) {
8606                     val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n];
8607                     if (val != null) {
8608                         opts[n] = val;
8609                         if (n == 'color') {
8610                             plot.series[i].renderer.shapeRenderer.fillStyle = val;
8611                             plot.series[i].renderer.shapeRenderer.strokeStyle = val;
8612                             plot.series[i][n] = val;
8613                         }
8614                         else if ((n == 'lineWidth') || (n == 'linePattern')) {
8615                             plot.series[i].renderer.shapeRenderer[n] = val;
8616                             plot.series[i][n] = val;
8617                         }
8618                         else if (n == 'markerOptions') {
8619                             merge (plot.series[i].markerOptions, val);
8620                             merge (plot.series[i].markerRenderer, val);
8621                         }
8622                         else {
8623                             plot.series[i][n] = val;
8624                         }
8625                         redrawPlot = true;
8626                     }
8627                 }
8628             }
8629             
8630             if (redrawPlot) {
8631                 plot.target.empty();
8632                 plot.draw();
8633             }
8634             
8635             for (n in th.target) {
8636                 if (th.target[n] != null) {
8637                     plot.target.css(n, th.target[n]);
8638                 }
8639             }
8640         }
8641         
8642     };
8643     
8644     $.jqplot.ThemeEngine.prototype._add = function(theme, name) {
8645         if (name) {
8646             theme._name = name;
8647         }
8648         if (!theme._name) {
8649             theme._name = Date.parse(new Date());
8650         }
8651         if (!this.themes.hasOwnProperty(theme._name)) {
8652             this.themes[theme._name] = theme;
8653         }
8654         else {
8655             throw new Error("jqplot.ThemeEngine Error: Theme already in use");
8656         }
8657     };
8658     
8659     // method remove
8660     // Delete the named theme, return true on success, false on failure.
8661     
8662
8663     /**
8664      * method: remove
8665      * 
8666      * Remove the given theme from the themeEngine.
8667      * 
8668      * parameters:
8669      * 
8670      * name - name of the theme to remove.
8671      * 
8672      * returns:
8673      * 
8674      * true on success, false on failure.
8675      */
8676     $.jqplot.ThemeEngine.prototype.remove = function(name) {
8677         if (name == 'Default') {
8678             return false;
8679         }
8680         return delete this.themes[name];
8681     };
8682
8683     /**
8684      * method: newTheme
8685      * 
8686      * Create a new theme based on the default theme, adding it the themeEngine.
8687      * 
8688      * parameters:
8689      * 
8690      * name - name of the new theme.
8691      * obj - optional object of styles to be applied to this new theme.
8692      * 
8693      * returns:
8694      * 
8695      * new Theme object.
8696      */
8697     $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) {
8698         if (typeof(name) == 'object') {
8699             obj = obj || name;
8700             name = null;
8701         }
8702         if (obj && obj._name) {
8703             name = obj._name;
8704         }
8705         else {
8706             name = name || Date.parse(new Date());
8707         }
8708         // var th = new $.jqplot.Theme(name);
8709         var th = this.copy(this.themes['Default']._name, name);
8710         $.jqplot.extend(th, obj);
8711         return th;
8712     };
8713     
8714     // function clone(obj) {
8715     //     return eval(obj.toSource());
8716     // }
8717     
8718     function clone(obj){
8719         if(obj == null || typeof(obj) != 'object'){
8720             return obj;
8721         }
8722     
8723         var temp = new obj.constructor();
8724         for(var key in obj){
8725             temp[key] = clone(obj[key]);
8726         }   
8727         return temp;
8728     }
8729     
8730     $.jqplot.clone = clone;
8731     
8732     function merge(obj1, obj2) {
8733         if (obj2 ==  null || typeof(obj2) != 'object') {
8734             return;
8735         }
8736         for (var key in obj2) {
8737             if (key == 'highlightColors') {
8738                 obj1[key] = clone(obj2[key]);
8739             }
8740             if (obj2[key] != null && typeof(obj2[key]) == 'object') {
8741                 if (!obj1.hasOwnProperty(key)) {
8742                     obj1[key] = {};
8743                 }
8744                 merge(obj1[key], obj2[key]);
8745             }
8746             else {
8747                 obj1[key] = obj2[key];
8748             }
8749         }
8750     }
8751     
8752     $.jqplot.merge = merge;
8753     
8754         // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic
8755     $.jqplot.extend = function() {
8756         // copy reference to target object
8757         var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
8758
8759         // Handle a deep copy situation
8760         if ( typeof target === "boolean" ) {
8761             deep = target;
8762             target = arguments[1] || {};
8763             // skip the boolean and the target
8764             i = 2;
8765         }
8766
8767         // Handle case when target is a string or something (possible in deep copy)
8768         if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) {
8769             target = {};
8770         }
8771
8772         for ( ; i < length; i++ ){
8773             // Only deal with non-null/undefined values
8774             if ( (options = arguments[ i ]) != null ) {
8775                 // Extend the base object
8776                 for ( var name in options ) {
8777                     var src = target[ name ], copy = options[ name ];
8778
8779                     // Prevent never-ending loop
8780                     if ( target === copy ) {
8781                         continue;
8782                     }
8783
8784                     // Recurse if we're merging object values
8785                     if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
8786                         target[ name ] = $.jqplot.extend( deep, 
8787                             // Never move original objects, clone them
8788                             src || ( copy.length != null ? [ ] : { } )
8789                         , copy );
8790                     }
8791                     // Don't bring in undefined values
8792                     else if ( copy !== undefined ) {
8793                         target[ name ] = copy;
8794                     }
8795                 }
8796             }
8797         }
8798         // Return the modified object
8799         return target;
8800     };
8801
8802     /**
8803      * method: rename
8804      * 
8805      * Rename a theme.
8806      * 
8807      * parameters:
8808      * 
8809      * oldName - current name of the theme.
8810      * newName - desired name of the theme.
8811      * 
8812      * returns:
8813      * 
8814      * new Theme object.
8815      */
8816     $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) {
8817         if (oldName == 'Default' || newName == 'Default') {
8818             throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default");
8819         }
8820         if (this.themes.hasOwnProperty(newName)) {
8821             throw new Error ("jqplot.ThemeEngine Error: New name already in use.");
8822         }
8823         else if (this.themes.hasOwnProperty(oldName)) {
8824             var th = this.copy (oldName, newName);
8825             this.remove(oldName);
8826             return th;
8827         }
8828         throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid");
8829     };
8830
8831     /**
8832      * method: copy
8833      * 
8834      * Create a copy of an existing theme in the themeEngine, adding it the themeEngine.
8835      * 
8836      * parameters:
8837      * 
8838      * sourceName - name of the existing theme.
8839      * targetName - name of the copy.
8840      * obj - optional object of style parameter to apply to the new theme.
8841      * 
8842      * returns:
8843      * 
8844      * new Theme object.
8845      */
8846     $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) {
8847         if (targetName == 'Default') {
8848             throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme");
8849         }
8850         if (!this.themes.hasOwnProperty(sourceName)) {
8851             var s = "jqplot.ThemeEngine Error: Source name invalid";
8852             throw new Error(s);
8853         }
8854         if (this.themes.hasOwnProperty(targetName)) {
8855             var s = "jqplot.ThemeEngine Error: Target name invalid";
8856             throw new Error(s);
8857         }
8858         else {
8859             var th = clone(this.themes[sourceName]);
8860             th._name = targetName;
8861             $.jqplot.extend(true, th, obj);
8862             this._add(th);
8863             return th;
8864         }
8865     };
8866     
8867     
8868     $.jqplot.Theme = function(name, obj) {
8869         if (typeof(name) == 'object') {
8870             obj = obj || name;
8871             name = null;
8872         }
8873         name = name || Date.parse(new Date());
8874         this._name = name;
8875         this.target = {
8876             backgroundColor: null
8877         };
8878         this.legend = {
8879             textColor: null,
8880             fontFamily: null,
8881             fontSize: null,
8882             border: null,
8883             background: null
8884         };
8885         this.title = {
8886             textColor: null,
8887             fontFamily: null,
8888             fontSize: null,
8889             textAlign: null
8890         };
8891         this.seriesStyles = {};
8892         this.series = [];
8893         this.grid = {
8894             drawGridlines: null,
8895             gridLineColor: null,
8896             gridLineWidth: null,
8897             backgroundColor: null,
8898             borderColor: null,
8899             borderWidth: null,
8900             shadow: null
8901         };
8902         this.axesStyles = {label:{}, ticks:{}};
8903         this.axes = {};
8904         if (typeof(obj) == 'string') {
8905             this._name = obj;
8906         }
8907         else if(typeof(obj) == 'object') {
8908             $.jqplot.extend(true, this, obj);
8909         }
8910     };
8911     
8912     var AxisProperties = function() {
8913         this.borderColor = null;
8914         this.borderWidth = null;
8915         this.ticks = new AxisTicks();
8916         this.label = new AxisLabel();
8917     };
8918     
8919     var AxisTicks = function() {
8920         this.show = null;
8921         this.showGridline = null;
8922         this.showLabel = null;
8923         this.showMark = null;
8924         this.size = null;
8925         this.textColor = null;
8926         this.whiteSpace = null;
8927         this.fontSize = null;
8928         this.fontFamily = null;
8929     };
8930     
8931     var AxisLabel = function() {
8932         this.textColor = null;
8933         this.whiteSpace = null;
8934         this.fontSize = null;
8935         this.fontFamily = null;
8936         this.fontWeight = null;
8937     };
8938     
8939     var LineSeriesProperties = function() {
8940         this.color=null;
8941         this.lineWidth=null;
8942         this.linePattern=null;
8943         this.shadow=null;
8944         this.fillColor=null;
8945         this.showMarker=null;
8946         this.markerOptions = new MarkerOptions();
8947     };
8948     
8949     var MarkerOptions = function() {
8950         this.show = null;
8951         this.style = null;
8952         this.lineWidth = null;
8953         this.size = null;
8954         this.color = null;
8955         this.shadow = null;
8956     };
8957     
8958     var BarSeriesProperties = function() {
8959         this.color=null;
8960         this.seriesColors=null;
8961         this.lineWidth=null;
8962         this.shadow=null;
8963         this.barPadding=null;
8964         this.barMargin=null;
8965         this.barWidth=null;
8966         this.highlightColors=null;
8967     };
8968     
8969     var PieSeriesProperties = function() {
8970         this.seriesColors=null;
8971         this.padding=null;
8972         this.sliceMargin=null;
8973         this.fill=null;
8974         this.shadow=null;
8975         this.startAngle=null;
8976         this.lineWidth=null;
8977         this.highlightColors=null;
8978     };
8979     
8980     var DonutSeriesProperties = function() {
8981         this.seriesColors=null;
8982         this.padding=null;
8983         this.sliceMargin=null;
8984         this.fill=null;
8985         this.shadow=null;
8986         this.startAngle=null;
8987         this.lineWidth=null;
8988         this.innerDiameter=null;
8989         this.thickness=null;
8990         this.ringMargin=null;
8991         this.highlightColors=null;
8992     };
8993     
8994     var FunnelSeriesProperties = function() {
8995         this.color=null;
8996         this.lineWidth=null;
8997         this.shadow=null;
8998         this.padding=null;
8999         this.sectionMargin=null;
9000         this.seriesColors=null;
9001         this.highlightColors=null;
9002     };
9003     
9004     var MeterSeriesProperties = function() {
9005         this.padding=null;
9006         this.backgroundColor=null;
9007         this.ringColor=null;
9008         this.tickColor=null;
9009         this.ringWidth=null;
9010         this.intervalColors=null;
9011         this.intervalInnerRadius=null;
9012         this.intervalOuterRadius=null;
9013         this.hubRadius=null;
9014         this.needleThickness=null;
9015         this.needlePad=null;
9016     };
9017         
9018
9019
9020
9021     $.fn.jqplotChildText = function() {
9022         return $(this).contents().filter(function() {
9023             return this.nodeType == 3;  // Node.TEXT_NODE not defined in I7
9024         }).text();
9025     };
9026
9027     // Returns font style as abbreviation for "font" property.
9028     $.fn.jqplotGetComputedFontStyle = function() {
9029         var css = window.getComputedStyle ?  window.getComputedStyle(this[0], "") : this[0].currentStyle;
9030         var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily'];
9031         var style = [];
9032
9033         for (var i=0 ; i < attrs.length; ++i) {
9034             var attr = String(css[attrs[i]]);
9035
9036             if (attr && attr != 'normal') {
9037                 style.push(attr);
9038             }
9039         }
9040         return style.join(' ');
9041     };
9042
9043     /**
9044      * Namespace: $.fn
9045      * jQuery namespace to attach functions to jQuery elements.
9046      *  
9047      */
9048
9049     $.fn.jqplotToImageCanvas = function(options) {
9050
9051         options = options || {};
9052         var x_offset = (options.x_offset == null) ? 0 : options.x_offset;
9053         var y_offset = (options.y_offset == null) ? 0 : options.y_offset;
9054         var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor;
9055
9056         if ($(this).width() == 0 || $(this).height() == 0) {
9057             return null;
9058         }
9059
9060         // excanvas and hence IE < 9 do not support toDataURL and cannot export images.
9061         if ($.jqplot.use_excanvas) {
9062             return null;
9063         }
9064         
9065         var newCanvas = document.createElement("canvas");
9066         var h = $(this).outerHeight(true);
9067         var w = $(this).outerWidth(true);
9068         var offs = $(this).offset();
9069         var plotleft = offs.left;
9070         var plottop = offs.top;
9071         var transx = 0, transy = 0;
9072
9073         // have to check if any elements are hanging outside of plot area before rendering,
9074         // since changing width of canvas will erase canvas.
9075
9076         var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick', 
9077         'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick',
9078         'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label', 
9079         'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ];
9080
9081         var temptop, templeft, tempbottom, tempright;
9082
9083         for (var i = 0; i < clses.length; i++) {
9084             $(this).find('.'+clses[i]).each(function() {
9085                 temptop = $(this).offset().top - plottop;
9086                 templeft = $(this).offset().left - plotleft;
9087                 tempright = templeft + $(this).outerWidth(true) + transx;
9088                 tempbottom = temptop + $(this).outerHeight(true) + transy;
9089                 if (templeft < -transx) {
9090                     w = w - transx - templeft;
9091                     transx = -templeft;
9092                 }
9093                 if (temptop < -transy) {
9094                     h = h - transy - temptop;
9095                     transy = - temptop;
9096                 }
9097                 if (tempright > w) {
9098                     w = tempright;
9099                 }
9100                 if (tempbottom > h) {
9101                     h =  tempbottom;
9102                 }
9103             });
9104         }
9105
9106         newCanvas.width = w + Number(x_offset);
9107         newCanvas.height = h + Number(y_offset);
9108
9109         var newContext = newCanvas.getContext("2d"); 
9110
9111         newContext.save();
9112         newContext.fillStyle = backgroundColor;
9113         newContext.fillRect(0,0, newCanvas.width, newCanvas.height);
9114         newContext.restore();
9115
9116         newContext.translate(transx, transy);
9117         newContext.textAlign = 'left';
9118         newContext.textBaseline = 'top';
9119
9120         function getLineheight(el) {
9121             var lineheight = parseInt($(el).css('line-height'), 10);
9122
9123             if (isNaN(lineheight)) {
9124                 lineheight = parseInt($(el).css('font-size'), 10) * 1.2;
9125             }
9126             return lineheight;
9127         }
9128
9129         function writeWrappedText (el, context, text, left, top, canvasWidth) {
9130             var lineheight = getLineheight(el);
9131             var tagwidth = $(el).innerWidth();
9132             var tagheight = $(el).innerHeight();
9133             var words = text.split(/\s+/);
9134             var wl = words.length;
9135             var w = '';
9136             var breaks = [];
9137             var temptop = top;
9138             var templeft = left;
9139
9140             for (var i=0; i<wl; i++) {
9141                 w += words[i];
9142                 if (context.measureText(w).width > tagwidth) {
9143                     breaks.push(i);
9144                     w = '';
9145                     i--;
9146                 }   
9147             }
9148             if (breaks.length === 0) {
9149                 // center text if necessary
9150                 if ($(el).css('textAlign') === 'center') {
9151                     templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
9152                 }
9153                 context.fillText(text, templeft, top);
9154             }
9155             else {
9156                 w = words.slice(0, breaks[0]).join(' ');
9157                 // center text if necessary
9158                 if ($(el).css('textAlign') === 'center') {
9159                     templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
9160                 }
9161                 context.fillText(w, templeft, temptop);
9162                 temptop += lineheight;
9163                 for (var i=1, l=breaks.length; i<l; i++) {
9164                     w = words.slice(breaks[i-1], breaks[i]).join(' ');
9165                     // center text if necessary
9166                     if ($(el).css('textAlign') === 'center') {
9167                         templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
9168                     }
9169                     context.fillText(w, templeft, temptop);
9170                     temptop += lineheight;
9171                 }
9172                 w = words.slice(breaks[i-1], words.length).join(' ');
9173                 // center text if necessary
9174                 if ($(el).css('textAlign') === 'center') {
9175                     templeft = left + (canvasWidth - context.measureText(w).width)/2  - transx;
9176                 }
9177                 context.fillText(w, templeft, temptop);
9178             }
9179
9180         }
9181
9182         function _jqpToImage(el, x_offset, y_offset) {
9183             var tagname = el.tagName.toLowerCase();
9184             var p = $(el).position();
9185             var css = window.getComputedStyle ?  window.getComputedStyle(el, "") : el.currentStyle; // for IE < 9
9186             var left = x_offset + p.left + parseInt(css.marginLeft, 10) + parseInt(css.borderLeftWidth, 10) + parseInt(css.paddingLeft, 10);
9187             var top = y_offset + p.top + parseInt(css.marginTop, 10) + parseInt(css.borderTopWidth, 10)+ parseInt(css.paddingTop, 10);
9188             var w = newCanvas.width;
9189             // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth') 
9190
9191             // somehow in here, for divs within divs, the width of the inner div should be used instead of the canvas.
9192
9193             if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) {
9194                 $(el).children().each(function() {
9195                     _jqpToImage(this, left, top);
9196                 });
9197                 var text = $(el).jqplotChildText();
9198
9199                 if (text) {
9200                     newContext.font = $(el).jqplotGetComputedFontStyle();
9201                     newContext.fillStyle = $(el).css('color');
9202
9203                     writeWrappedText(el, newContext, text, left, top, w);
9204                 }
9205             }
9206
9207             // handle the standard table legend
9208
9209             else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) {
9210                 newContext.strokeStyle = $(el).css('border-top-color');
9211                 newContext.fillStyle = $(el).css('background-color');
9212                 newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight());
9213                 if (parseInt($(el).css('border-top-width'), 10) > 0) {
9214                     newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight());
9215                 }
9216
9217                 // find all the swatches
9218                 $(el).find('div.jqplot-table-legend-swatch-outline').each(function() {
9219                     // get the first div and stroke it
9220                     var elem = $(this);
9221                     newContext.strokeStyle = elem.css('border-top-color');
9222                     var l = left + elem.position().left;
9223                     var t = top + elem.position().top;
9224                     newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight());
9225
9226                     // now fill the swatch
9227                     
9228                     l += parseInt(elem.css('padding-left'), 10);
9229                     t += parseInt(elem.css('padding-top'), 10);
9230                     var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top'), 10);
9231                     var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left'), 10);
9232
9233                     var swatch = elem.children('div.jqplot-table-legend-swatch');
9234                     newContext.fillStyle = swatch.css('background-color');
9235                     newContext.fillRect(l, t, w, h);
9236                 });
9237
9238                 // now add text
9239
9240                 $(el).find('td.jqplot-table-legend-label').each(function(){
9241                     var elem = $(this);
9242                     var l = left + elem.position().left;
9243                     var t = top + elem.position().top + parseInt(elem.css('padding-top'), 10);
9244                     newContext.font = elem.jqplotGetComputedFontStyle();
9245                     newContext.fillStyle = elem.css('color');
9246                     writeWrappedText(elem, newContext, elem.text(), l, t, w);
9247                 });
9248
9249                 var elem = null;
9250             }
9251
9252             else if (tagname == 'canvas') {
9253                 newContext.drawImage(el, left, top);
9254             }
9255         }
9256         $(this).children().each(function() {
9257             _jqpToImage(this, x_offset, y_offset);
9258         });
9259         return newCanvas;
9260     };
9261
9262     // return the raw image data string.
9263     // Should work on canvas supporting browsers.
9264     $.fn.jqplotToImageStr = function(options) {
9265         var imgCanvas = $(this).jqplotToImageCanvas(options);
9266         if (imgCanvas) {
9267             return imgCanvas.toDataURL("image/png");
9268         }
9269         else {
9270             return null;
9271         }
9272     };
9273
9274     // return a DOM <img> element and return it.
9275     // Should work on canvas supporting browsers.
9276     $.fn.jqplotToImageElem = function(options) {
9277         var elem = document.createElement("img");
9278         var str = $(this).jqplotToImageStr(options);
9279         elem.src = str;
9280         return elem;
9281     };
9282
9283     // return a string for an <img> element and return it.
9284     // Should work on canvas supporting browsers.
9285     $.fn.jqplotToImageElemStr = function(options) {
9286         var str = '<img src='+$(this).jqplotToImageStr(options)+' />';
9287         return str;
9288     };
9289
9290     // Not guaranteed to work, even on canvas supporting browsers due to 
9291     // limitations with location.href and browser support.
9292     $.fn.jqplotSaveImage = function() {
9293         var imgData = $(this).jqplotToImageStr({});
9294         if (imgData) {
9295             window.location.href = imgData.replace("image/png", "image/octet-stream");
9296         }
9297
9298     };
9299
9300     // Not guaranteed to work, even on canvas supporting browsers due to
9301     // limitations with window.open and arbitrary data.
9302     $.fn.jqplotViewImage = function() {
9303         var imgStr = $(this).jqplotToImageElemStr({});
9304         var imgData = $(this).jqplotToImageStr({});
9305         if (imgStr) {
9306             var w = window.open('');
9307             w.document.open("image/png");
9308             w.document.write(imgStr);
9309             w.document.close();
9310             w = null;
9311         }
9312     };
9313     
9314
9315
9316
9317     /** 
9318      * @description
9319      * <p>Object with extended date parsing and formatting capabilities.
9320      * This library borrows many concepts and ideas from the Date Instance 
9321      * Methods by Ken Snyder along with some parts of Ken's actual code.</p>
9322      *
9323      * <p>jsDate takes a different approach by not extending the built-in 
9324      * Date Object, improving date parsing, allowing for multiple formatting 
9325      * syntaxes and multiple and more easily expandable localization.</p>
9326      * 
9327      * @author Chris Leonello
9328      * @date #date#
9329      * @version #VERSION#
9330      * @copyright (c) 2010-2013 Chris Leonello
9331      * jsDate is currently available for use in all personal or commercial projects 
9332      * under both the MIT and GPL version 2.0 licenses. This means that you can 
9333      * choose the license that best suits your project and use it accordingly.
9334      * 
9335      * <p>Ken's original Date Instance Methods and copyright notice:</p>
9336      * <pre>
9337      * Ken Snyder (ken d snyder at gmail dot com)
9338      * 2008-09-10
9339      * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
9340      * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
9341      * </pre>
9342      * 
9343      * @class
9344      * @name jsDate
9345      * @param  {String | Number | Array | Date&nbsp;Object | Options&nbsp;Object} arguments Optional arguments, either a parsable date/time string,
9346      * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
9347      * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional.
9348      */
9349      
9350     var jsDate = function () {
9351     
9352         this.syntax = jsDate.config.syntax;
9353         this._type = "jsDate";
9354         this.proxy = new Date();
9355         this.options = {};
9356         this.locale = jsDate.regional.getLocale();
9357         this.formatString = '';
9358         this.defaultCentury = jsDate.config.defaultCentury;
9359
9360         switch ( arguments.length ) {
9361             case 0:
9362                 break;
9363             case 1:
9364                 // other objects either won't have a _type property or,
9365                 // if they do, it shouldn't be set to "jsDate", so
9366                 // assume it is an options argument.
9367                 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
9368                     var opts = this.options = arguments[0];
9369                     this.syntax = opts.syntax || this.syntax;
9370                     this.defaultCentury = opts.defaultCentury || this.defaultCentury;
9371                     this.proxy = jsDate.createDate(opts.date);
9372                 }
9373                 else {
9374                     this.proxy = jsDate.createDate(arguments[0]);
9375                 }
9376                 break;
9377             default:
9378                 var a = [];
9379                 for ( var i=0; i<arguments.length; i++ ) {
9380                     a.push(arguments[i]);
9381                 }
9382                 // this should be the current date/time?
9383                 this.proxy = new Date();
9384                 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
9385                 if ( a.slice(3).length ) {
9386                     this.proxy.setHours.apply( this.proxy, a.slice(3) );
9387                 }
9388                 break;
9389         }
9390     };
9391     
9392     /**
9393      * @namespace Configuration options that will be used as defaults for all instances on the page.
9394      * @property {String} defaultLocale The default locale to use [en].
9395      * @property {String} syntax The default syntax to use [perl].
9396      * @property {Number} defaultCentury The default centry for 2 digit dates.
9397      */
9398     jsDate.config = {
9399         defaultLocale: 'en',
9400         syntax: 'perl',
9401         defaultCentury: 1900
9402     };
9403         
9404     /**
9405      * Add an arbitrary amount to the currently stored date
9406      * 
9407      * @param {Number} number      
9408      * @param {String} unit
9409      * @returns {jsDate}       
9410      */
9411      
9412     jsDate.prototype.add = function(number, unit) {
9413         var factor = multipliers[unit] || multipliers.day;
9414         if (typeof factor == 'number') {
9415             this.proxy.setTime(this.proxy.getTime() + (factor * number));
9416         } else {
9417             factor.add(this, number);
9418         }
9419         return this;
9420     };
9421         
9422     /**
9423      * Create a new jqplot.date object with the same date
9424      * 
9425      * @returns {jsDate}
9426      */  
9427      
9428     jsDate.prototype.clone = function() {
9429             return new jsDate(this.proxy.getTime());
9430     };
9431
9432     /**
9433      * Get the UTC TimeZone Offset of this date in milliseconds.
9434      *
9435      * @returns {Number}
9436      */
9437
9438     jsDate.prototype.getUtcOffset = function() {
9439         return this.proxy.getTimezoneOffset() * 60000;
9440     };
9441
9442     /**
9443      * Find the difference between this jsDate and another date.
9444      * 
9445      * @param {String| Number| Array| jsDate&nbsp;Object| Date&nbsp;Object} dateObj
9446      * @param {String} unit
9447      * @param {Boolean} allowDecimal
9448      * @returns {Number} Number of units difference between dates.
9449      */
9450      
9451     jsDate.prototype.diff = function(dateObj, unit, allowDecimal) {
9452         // ensure we have a Date object
9453         dateObj = new jsDate(dateObj);
9454         if (dateObj === null) {
9455             return null;
9456         }
9457         // get the multiplying factor integer or factor function
9458         var factor = multipliers[unit] || multipliers.day;
9459         if (typeof factor == 'number') {
9460             // multiply
9461             var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor;
9462         } else {
9463             // run function
9464             var unitDiff = factor.diff(this.proxy, dateObj.proxy);
9465         }
9466         // if decimals are not allowed, round toward zero
9467         return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff));          
9468     };
9469     
9470     /**
9471      * Get the abbreviated name of the current week day
9472      * 
9473      * @returns {String}
9474      */   
9475      
9476     jsDate.prototype.getAbbrDayName = function() {
9477         return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()];
9478     };
9479     
9480     /**
9481      * Get the abbreviated name of the current month
9482      * 
9483      * @returns {String}
9484      */
9485      
9486     jsDate.prototype.getAbbrMonthName = function() {
9487         return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()];
9488     };
9489     
9490     /**
9491      * Get UPPER CASE AM or PM for the current time
9492      * 
9493      * @returns {String}
9494      */
9495      
9496     jsDate.prototype.getAMPM = function() {
9497         return this.proxy.getHours() >= 12 ? 'PM' : 'AM';
9498     };
9499     
9500     /**
9501      * Get lower case am or pm for the current time
9502      * 
9503      * @returns {String}
9504      */
9505      
9506     jsDate.prototype.getAmPm = function() {
9507         return this.proxy.getHours() >= 12 ? 'pm' : 'am';
9508     };
9509     
9510     /**
9511      * Get the century (19 for 20th Century)
9512      *
9513      * @returns {Integer} Century (19 for 20th century).
9514      */
9515     jsDate.prototype.getCentury = function() { 
9516         return parseInt(this.proxy.getFullYear()/100, 10);
9517     };
9518     
9519     /**
9520      * Implements Date functionality
9521      */
9522     jsDate.prototype.getDate = function() {
9523         return this.proxy.getDate();
9524     };
9525     
9526     /**
9527      * Implements Date functionality
9528      */
9529     jsDate.prototype.getDay = function() {
9530         return this.proxy.getDay();
9531     };
9532     
9533     /**
9534      * Get the Day of week 1 (Monday) thru 7 (Sunday)
9535      * 
9536      * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday)
9537      */
9538     jsDate.prototype.getDayOfWeek = function() { 
9539         var dow = this.proxy.getDay(); 
9540         return dow===0?7:dow; 
9541     };
9542     
9543     /**
9544      * Get the day of the year
9545      * 
9546      * @returns {Integer} 1 - 366, day of the year
9547      */
9548     jsDate.prototype.getDayOfYear = function() {
9549         var d = this.proxy;
9550         var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
9551         ms += d.getTimezoneOffset()*60000;
9552         d = null;
9553         return parseInt(ms/60000/60/24, 10)+1;
9554     };
9555     
9556     /**
9557      * Get the name of the current week day
9558      * 
9559      * @returns {String}
9560      */  
9561      
9562     jsDate.prototype.getDayName = function() {
9563         return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()];
9564     };
9565     
9566     /**
9567      * Get the week number of the given year, starting with the first Sunday as the first week
9568      * @returns {Integer} Week number (13 for the 13th full week of the year).
9569      */
9570     jsDate.prototype.getFullWeekOfYear = function() {
9571         var d = this.proxy;
9572         var doy = this.getDayOfYear();
9573         var rdow = 6-d.getDay();
9574         var woy = parseInt((doy+rdow)/7, 10);
9575         return woy;
9576     };
9577     
9578     /**
9579      * Implements Date functionality
9580      */
9581     jsDate.prototype.getFullYear = function() {
9582         return this.proxy.getFullYear();
9583     };
9584     
9585     /**
9586      * Get the GMT offset in hours and minutes (e.g. +06:30)
9587      * 
9588      * @returns {String}
9589      */
9590      
9591     jsDate.prototype.getGmtOffset = function() {
9592         // divide the minutes offset by 60
9593         var hours = this.proxy.getTimezoneOffset() / 60;
9594         // decide if we are ahead of or behind GMT
9595         var prefix = hours < 0 ? '+' : '-';
9596         // remove the negative sign if any
9597         hours = Math.abs(hours);
9598         // add the +/- to the padded number of hours to : to the padded minutes
9599         return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2);
9600     };
9601     
9602     /**
9603      * Implements Date functionality
9604      */
9605     jsDate.prototype.getHours = function() {
9606         return this.proxy.getHours();
9607     };
9608     
9609     /**
9610      * Get the current hour on a 12-hour scheme
9611      * 
9612      * @returns {Integer}
9613      */
9614      
9615     jsDate.prototype.getHours12  = function() {
9616         var hours = this.proxy.getHours();
9617         return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
9618     };
9619     
9620     
9621     jsDate.prototype.getIsoWeek = function() {
9622         var d = this.proxy;
9623         var woy = this.getWeekOfYear();
9624         var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
9625         // First week is 01 and not 00 as in the case of %U and %W,
9626         // so we add 1 to the final result except if day 1 of the year
9627         // is a Monday (then %W returns 01).
9628         // We also need to subtract 1 if the day 1 of the year is 
9629         // Friday-Sunday, so the resulting equation becomes:
9630         var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
9631         if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
9632         {
9633             idow = 1;
9634         }
9635         else if(idow === 0)
9636         {
9637             d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31'));
9638             idow = d.getIsoWeek();
9639         }
9640         d = null;
9641         return idow;
9642     };
9643     
9644     /**
9645      * Implements Date functionality
9646      */
9647     jsDate.prototype.getMilliseconds = function() {
9648         return this.proxy.getMilliseconds();
9649     };
9650     
9651     /**
9652      * Implements Date functionality
9653      */
9654     jsDate.prototype.getMinutes = function() {
9655         return this.proxy.getMinutes();
9656     };
9657     
9658     /**
9659      * Implements Date functionality
9660      */
9661     jsDate.prototype.getMonth = function() {
9662         return this.proxy.getMonth();
9663     };
9664     
9665     /**
9666      * Get the name of the current month
9667      * 
9668      * @returns {String}
9669      */
9670      
9671     jsDate.prototype.getMonthName = function() {
9672         return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()];
9673     };
9674     
9675     /**
9676      * Get the number of the current month, 1-12
9677      * 
9678      * @returns {Integer}
9679      */
9680      
9681     jsDate.prototype.getMonthNumber = function() {
9682         return this.proxy.getMonth() + 1;
9683     };
9684     
9685     /**
9686      * Implements Date functionality
9687      */
9688     jsDate.prototype.getSeconds = function() {
9689         return this.proxy.getSeconds();
9690     };
9691     
9692     /**
9693      * Return a proper two-digit year integer
9694      * 
9695      * @returns {Integer}
9696      */
9697      
9698     jsDate.prototype.getShortYear = function() {
9699         return this.proxy.getYear() % 100;
9700     };
9701     
9702     /**
9703      * Implements Date functionality
9704      */
9705     jsDate.prototype.getTime = function() {
9706         return this.proxy.getTime();
9707     };
9708     
9709     /**
9710      * Get the timezone abbreviation
9711      *
9712      * @returns {String} Abbreviation for the timezone
9713      */
9714     jsDate.prototype.getTimezoneAbbr = function() {
9715         return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1'); 
9716     };
9717     
9718     /**
9719      * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time)
9720      * 
9721      * @returns {String}
9722      */
9723     jsDate.prototype.getTimezoneName = function() {
9724         var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString());
9725         return match[1] || match[2] || 'GMT' + this.getGmtOffset();
9726     }; 
9727     
9728     /**
9729      * Implements Date functionality
9730      */
9731     jsDate.prototype.getTimezoneOffset = function() {
9732         return this.proxy.getTimezoneOffset();
9733     };
9734     
9735     
9736     /**
9737      * Get the week number of the given year, starting with the first Monday as the first week
9738      * @returns {Integer} Week number (13 for the 13th week of the year).
9739      */
9740     jsDate.prototype.getWeekOfYear = function() {
9741         var doy = this.getDayOfYear();
9742         var rdow = 7 - this.getDayOfWeek();
9743         var woy = parseInt((doy+rdow)/7, 10);
9744         return woy;
9745     };
9746     
9747     /**
9748      * Get the current date as a Unix timestamp
9749      * 
9750      * @returns {Integer}
9751      */
9752      
9753     jsDate.prototype.getUnix = function() {
9754         return Math.round(this.proxy.getTime() / 1000, 0);
9755     }; 
9756     
9757     /**
9758      * Implements Date functionality
9759      */
9760     jsDate.prototype.getYear = function() {
9761         return this.proxy.getYear();
9762     };
9763     
9764     /**
9765      * Return a date one day ahead (or any other unit)
9766      * 
9767      * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond
9768      * @returns {jsDate}
9769      */
9770      
9771     jsDate.prototype.next = function(unit) {
9772         unit = unit || 'day';
9773         return this.clone().add(1, unit);
9774     };
9775     
9776     /**
9777      * Set the jsDate instance to a new date.
9778      *
9779      * @param  {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments, 
9780      * either a parsable date/time string,
9781      * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
9782      * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional.
9783      */
9784     jsDate.prototype.set = function() {
9785         switch ( arguments.length ) {
9786             case 0:
9787                 this.proxy = new Date();
9788                 break;
9789             case 1:
9790                 // other objects either won't have a _type property or,
9791                 // if they do, it shouldn't be set to "jsDate", so
9792                 // assume it is an options argument.
9793                 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
9794                     var opts = this.options = arguments[0];
9795                     this.syntax = opts.syntax || this.syntax;
9796                     this.defaultCentury = opts.defaultCentury || this.defaultCentury;
9797                     this.proxy = jsDate.createDate(opts.date);
9798                 }
9799                 else {
9800                     this.proxy = jsDate.createDate(arguments[0]);
9801                 }
9802                 break;
9803             default:
9804                 var a = [];
9805                 for ( var i=0; i<arguments.length; i++ ) {
9806                     a.push(arguments[i]);
9807                 }
9808                 // this should be the current date/time
9809                 this.proxy = new Date();
9810                 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
9811                 if ( a.slice(3).length ) {
9812                     this.proxy.setHours.apply( this.proxy, a.slice(3) );
9813                 }
9814                 break;
9815         }
9816         return this;
9817     };
9818     
9819     /**
9820      * Sets the day of the month for a specified date according to local time.
9821      * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month. 
9822      */
9823     jsDate.prototype.setDate = function(n) {
9824         this.proxy.setDate(n);
9825         return this;
9826     };
9827     
9828     /**
9829      * Sets the full year for a specified date according to local time.
9830      * @param {Integer} yearValue The numeric value of the year, for example, 1995.  
9831      * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December.  
9832      * @param {Integer} dayValue Optional, between 1 and 31 representing the day of the month. If you specify the dayValue parameter, you must also specify the monthValue. 
9833      */
9834     jsDate.prototype.setFullYear = function() {
9835         this.proxy.setFullYear.apply(this.proxy, arguments);
9836         return this;
9837     };
9838     
9839     /**
9840      * Sets the hours for a specified date according to local time.
9841      * 
9842      * @param {Integer} hoursValue An integer between 0 and 23, representing the hour.  
9843      * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes.  
9844      * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds. 
9845      * If you specify the secondsValue parameter, you must also specify the minutesValue.  
9846      * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds. 
9847      * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue. 
9848      */
9849     jsDate.prototype.setHours = function() {
9850         this.proxy.setHours.apply(this.proxy, arguments);
9851         return this;
9852     };
9853     
9854     /**
9855      * Implements Date functionality
9856      */ 
9857     jsDate.prototype.setMilliseconds = function(n) {
9858         this.proxy.setMilliseconds(n);
9859         return this;
9860     };
9861     
9862     /**
9863      * Implements Date functionality
9864      */ 
9865     jsDate.prototype.setMinutes = function() {
9866         this.proxy.setMinutes.apply(this.proxy, arguments);
9867         return this;
9868     };
9869     
9870     /**
9871      * Implements Date functionality
9872      */ 
9873     jsDate.prototype.setMonth = function() {
9874         this.proxy.setMonth.apply(this.proxy, arguments);
9875         return this;
9876     };
9877     
9878     /**
9879      * Implements Date functionality
9880      */ 
9881     jsDate.prototype.setSeconds = function() {
9882         this.proxy.setSeconds.apply(this.proxy, arguments);
9883         return this;
9884     };
9885     
9886     /**
9887      * Implements Date functionality
9888      */ 
9889     jsDate.prototype.setTime = function(n) {
9890         this.proxy.setTime(n);
9891         return this;
9892     };
9893     
9894     /**
9895      * Implements Date functionality
9896      */ 
9897     jsDate.prototype.setYear = function() {
9898         this.proxy.setYear.apply(this.proxy, arguments);
9899         return this;
9900     };
9901     
9902     /**
9903      * Provide a formatted string representation of this date.
9904      * 
9905      * @param {String} formatString A format string.  
9906      * See: {@link jsDate.formats}.
9907      * @returns {String} Date String.
9908      */
9909             
9910     jsDate.prototype.strftime = function(formatString) {
9911         formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString'];
9912         return jsDate.strftime(this, formatString, this.syntax);
9913     };
9914         
9915     /**
9916      * Return a String representation of this jsDate object.
9917      * @returns {String} Date string.
9918      */
9919     
9920     jsDate.prototype.toString = function() {
9921         return this.proxy.toString();
9922     };
9923         
9924     /**
9925      * Convert the current date to an 8-digit integer (%Y%m%d)
9926      * 
9927      * @returns {Integer}
9928      */
9929      
9930     jsDate.prototype.toYmdInt = function() {
9931         return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate();
9932     };
9933     
9934     /**
9935      * @namespace Holds localizations for month/day names.
9936      * <p>jsDate attempts to detect locale when loaded and defaults to 'en'.
9937      * If a localization is detected which is not available, jsDate defaults to 'en'.
9938      * Additional localizations can be added after jsDate loads.  After adding a localization,
9939      * call the jsDate.regional.getLocale() method.  Currently, en, fr and de are defined.</p>
9940      * 
9941      * <p>Localizations must be an object and have the following properties defined:  monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p>
9942      * <pre class="code">
9943      * jsDate.regional['en'] = {
9944      * monthNames      : 'January February March April May June July August September October November December'.split(' '),
9945      * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
9946      * dayNames        : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
9947      * dayNamesShort   : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
9948      * };
9949      * </pre>
9950      * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the
9951      * new localizations.</p>
9952      */
9953      
9954     jsDate.regional = {
9955         'en': {
9956             monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
9957             monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
9958             dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
9959             dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
9960             formatString: '%Y-%m-%d %H:%M:%S'
9961         },
9962         
9963         'fr': {
9964             monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
9965             monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'],
9966             dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
9967             dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
9968             formatString: '%Y-%m-%d %H:%M:%S'
9969         },
9970         
9971         'de': {
9972             monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
9973             monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
9974             dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
9975             dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
9976             formatString: '%Y-%m-%d %H:%M:%S'
9977         },
9978         
9979         'es': {
9980             monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
9981             monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
9982             dayNames: ['Domingo','Lunes','Martes','Mi&eacute;rcoles','Jueves','Viernes','S&aacute;bado'],
9983             dayNamesShort: ['Dom','Lun','Mar','Mi&eacute;','Juv','Vie','S&aacute;b'],
9984             formatString: '%Y-%m-%d %H:%M:%S'
9985         },
9986         
9987         'ru': {
9988             monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
9989             monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'],
9990             dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'],
9991             dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'],
9992             formatString: '%Y-%m-%d %H:%M:%S'
9993         },
9994         
9995         'ar': {
9996             monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول',   'تشرين الأول', 'تشرين الثاني', 'كانون الأول'],
9997             monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
9998             dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'],
9999             dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'],
10000             formatString: '%Y-%m-%d %H:%M:%S'
10001         },
10002         
10003         'pt': {
10004             monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
10005             monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
10006             dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
10007             dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
10008             formatString: '%Y-%m-%d %H:%M:%S'   
10009         },
10010         
10011         'pt-BR': {
10012             monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
10013             monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
10014             dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
10015             dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
10016             formatString: '%Y-%m-%d %H:%M:%S'
10017         },
10018         
10019         'pl': {
10020             monthNames: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'],
10021             monthNamesShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze','Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
10022             dayNames: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
10023             dayNamesShort: ['Ni', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'Sb'],
10024             formatString: '%Y-%m-%d %H:%M:%S'
10025         },
10026
10027         'nl': {
10028             monthNames: ['Januari','Februari','Maart','April','Mei','Juni','July','Augustus','September','Oktober','November','December'],
10029             monthNamesShort: ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Aug','Sep','Okt','Nov','Dec'],
10030             dayNames:','['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'],
10031             dayNamesShort: ['Zo','Ma','Di','Wo','Do','Vr','Za'],
10032             formatString: '%Y-%m-%d %H:%M:%S'
10033         },
10034
10035         'sv': {
10036             monthNames: ['januari','februari','mars','april','maj','juni','juli','augusti','september','oktober','november','december'],
10037           monthNamesShort: ['jan','feb','mar','apr','maj','jun','jul','aug','sep','okt','nov','dec'],
10038             dayNames: ['söndag','måndag','tisdag','onsdag','torsdag','fredag','lördag'],
10039             dayNamesShort: ['sön','mån','tis','ons','tor','fre','lör'],
10040             formatString: '%Y-%m-%d %H:%M:%S'
10041         }
10042     
10043     };
10044     
10045     // Set english variants to 'en'
10046     jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
10047     
10048     /**
10049      * Try to determine the users locale based on the lang attribute of the html page.  Defaults to 'en'
10050      * if it cannot figure out a locale of if the locale does not have a localization defined.
10051      * @returns {String} locale
10052      */
10053      
10054     jsDate.regional.getLocale = function () {
10055         var l = jsDate.config.defaultLocale;
10056         
10057         if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
10058             l = document.getElementsByTagName('html')[0].lang;
10059             if (!jsDate.regional.hasOwnProperty(l)) {
10060                 l = jsDate.config.defaultLocale;
10061             }
10062         }
10063         
10064         return l;
10065     };
10066     
10067     // ms in day
10068     var day = 24 * 60 * 60 * 1000;
10069     
10070     // padd a number with zeros
10071     var addZeros = function(num, digits) {
10072         num = String(num);
10073         var i = digits - num.length;
10074         var s = String(Math.pow(10, i)).slice(1);
10075         return s.concat(num);
10076     };
10077
10078     // representations used for calculating differences between dates.
10079     // This borrows heavily from Ken Snyder's work.
10080     var multipliers = {
10081         millisecond: 1,
10082         second: 1000,
10083         minute: 60 * 1000,
10084         hour: 60 * 60 * 1000,
10085         day: day,
10086         week: 7 * day,
10087         month: {
10088             // add a number of months
10089             add: function(d, number) {
10090                 // add any years needed (increments of 12)
10091                 multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
10092                 // ensure that we properly wrap betwen December and January
10093                 // 11 % 12 = 11
10094                 // 12 % 12 = 0
10095                 var prevMonth = d.getMonth() + (number % 12);
10096                 if (prevMonth == 12) {
10097                     prevMonth = 0;
10098                     d.setYear(d.getFullYear() + 1);
10099                 } else if (prevMonth == -1) {
10100                     prevMonth = 11;
10101                     d.setYear(d.getFullYear() - 1);
10102                 }
10103                 d.setMonth(prevMonth);
10104             },
10105             // get the number of months between two Date objects (decimal to the nearest day)
10106             diff: function(d1, d2) {
10107                 // get the number of years
10108                 var diffYears = d1.getFullYear() - d2.getFullYear();
10109                 // get the number of remaining months
10110                 var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
10111                 // get the number of remaining days
10112                 var diffDays = d1.getDate() - d2.getDate();
10113                 // return the month difference with the days difference as a decimal
10114                 return diffMonths + (diffDays / 30);
10115             }
10116         },
10117         year: {
10118             // add a number of years
10119             add: function(d, number) {
10120                 d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
10121             },
10122             // get the number of years between two Date objects (decimal to the nearest day)
10123             diff: function(d1, d2) {
10124                 return multipliers.month.diff(d1, d2) / 12;
10125             }
10126         }        
10127     };
10128     //
10129     // Alias each multiplier with an 's' to allow 'year' and 'years' for example.
10130     // This comes from Ken Snyders work.
10131     //
10132     for (var unit in multipliers) {
10133         if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
10134             multipliers[unit + 's'] = multipliers[unit];
10135         }
10136     }
10137     
10138     //
10139     // take a jsDate instance and a format code and return the formatted value.
10140     // This is a somewhat modified version of Ken Snyder's method.
10141     //
10142     var format = function(d, code, syntax) {
10143         // if shorcut codes are used, recursively expand those.
10144         if (jsDate.formats[syntax]["shortcuts"][code]) {
10145             return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
10146         } else {
10147             // get the format code function and addZeros() argument
10148             var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
10149             var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
10150             if (getter[1]) {
10151                 nbr = addZeros(nbr, getter[1]);
10152             }
10153             return nbr;
10154         }       
10155     };
10156     
10157     /**
10158      * @static
10159      * Static function for convert a date to a string according to a given format.  Also acts as namespace for strftime format codes.
10160      * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p>
10161      * <pre class="code">
10162      * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
10163      * </pre>
10164      * @param {String | Number | Array | jsDate&nbsp;Object | Date&nbsp;Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object.
10165      * @param {String} formatString String with embedded date formatting codes.  
10166      * See: {@link jsDate.formats}. 
10167      * @param {String} syntax Optional syntax to use [default perl].
10168      * @param {String} locale Optional locale to use.
10169      * @returns {String} Formatted representation of the date.
10170     */
10171     //
10172     // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
10173     //
10174     jsDate.strftime = function(d, formatString, syntax, locale) {
10175         var syn = 'perl';
10176         var loc = jsDate.regional.getLocale();
10177         
10178         // check if syntax and locale are available or reversed
10179         if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
10180             syn = syntax;
10181         }
10182         else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
10183             loc = syntax;
10184         }
10185         
10186         if (locale && jsDate.formats.hasOwnProperty(locale)) {
10187             syn = locale;
10188         }
10189         else if (locale && jsDate.regional.hasOwnProperty(locale)) {
10190             loc = locale;
10191         }
10192         
10193         if (get_type(d) != "[object Object]" || d._type != "jsDate") {
10194             d = new jsDate(d);
10195             d.locale = loc;
10196         }
10197         if (!formatString) {
10198             formatString = d.formatString || jsDate.regional[loc]['formatString'];
10199         }
10200         // default the format string to year-month-day
10201         var source = formatString || '%Y-%m-%d', 
10202             result = '', 
10203             match;
10204         // replace each format code
10205         while (source.length > 0) {
10206             if (match = source.match(jsDate.formats[syn].codes.matcher)) {
10207                 result += source.slice(0, match.index);
10208                 result += (match[1] || '') + format(d, match[2], syn);
10209                 source = source.slice(match.index + match[0].length);
10210             } else {
10211                 result += source;
10212                 source = '';
10213             }
10214         }
10215         return result;
10216     };
10217     
10218     /**
10219      * @namespace
10220      * Namespace to hold format codes and format shortcuts.  "perl" and "php" format codes 
10221      * and shortcuts are defined by default.  Additional codes and shortcuts can be
10222      * added like:
10223      * 
10224      * <pre class="code">
10225      * jsDate.formats["perl"] = {
10226      *     "codes": {
10227      *         matcher: /someregex/,
10228      *         Y: "fullYear",  // name of "get" method without the "get",
10229      *         ...,            // more codes
10230      *     },
10231      *     "shortcuts": {
10232      *         F: '%Y-%m-%d',
10233      *         ...,            // more shortcuts
10234      *     }
10235      * };
10236      * </pre>
10237      * 
10238      * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via:
10239      * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code>
10240      */
10241     
10242     jsDate.formats = {
10243         ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
10244         SQL:'%Y-%m-%d %H:%M:%S'
10245     };
10246     
10247     /**
10248      * Perl format codes and shortcuts for strftime.
10249      * 
10250      * A hash (object) of codes where each code must be an array where the first member is 
10251      * the name of a Date.prototype or jsDate.prototype function to call
10252      * and optionally a second member indicating the number to pass to addZeros()
10253      * 
10254      * <p>The following format codes are defined:</p>
10255      * 
10256      * <pre class="code">
10257      * Code    Result                    Description
10258      * == Years ==           
10259      * %Y      2008                      Four-digit year
10260      * %y      08                        Two-digit year
10261      * 
10262      * == Months ==          
10263      * %m      09                        Two-digit month
10264      * %#m     9                         One or two-digit month
10265      * %B      September                 Full month name
10266      * %b      Sep                       Abbreviated month name
10267      * 
10268      * == Days ==            
10269      * %d      05                        Two-digit day of month
10270      * %#d     5                         One or two-digit day of month
10271      * %e      5                         One or two-digit day of month
10272      * %A      Sunday                    Full name of the day of the week
10273      * %a      Sun                       Abbreviated name of the day of the week
10274      * %w      0                         Number of the day of the week (0 = Sunday, 6 = Saturday)
10275      * 
10276      * == Hours ==           
10277      * %H      23                        Hours in 24-hour format (two digits)
10278      * %#H     3                         Hours in 24-hour integer format (one or two digits)
10279      * %I      11                        Hours in 12-hour format (two digits)
10280      * %#I     3                         Hours in 12-hour integer format (one or two digits)
10281      * %p      PM                        AM or PM
10282      * 
10283      * == Minutes ==         
10284      * %M      09                        Minutes (two digits)
10285      * %#M     9                         Minutes (one or two digits)
10286      * 
10287      * == Seconds ==         
10288      * %S      02                        Seconds (two digits)
10289      * %#S     2                         Seconds (one or two digits)
10290      * %s      1206567625723             Unix timestamp (Seconds past 1970-01-01 00:00:00)
10291      * 
10292      * == Milliseconds ==    
10293      * %N      008                       Milliseconds (three digits)
10294      * %#N     8                         Milliseconds (one to three digits)
10295      * 
10296      * == Timezone ==        
10297      * %O      360                       difference in minutes between local time and GMT
10298      * %Z      Mountain Standard Time    Name of timezone as reported by browser
10299      * %G      06:00                     Hours and minutes between GMT
10300      * 
10301      * == Shortcuts ==       
10302      * %F      2008-03-26                %Y-%m-%d
10303      * %T      05:06:30                  %H:%M:%S
10304      * %X      05:06:30                  %H:%M:%S
10305      * %x      03/26/08                  %m/%d/%y
10306      * %D      03/26/08                  %m/%d/%y
10307      * %#c     Wed Mar 26 15:31:00 2008  %a %b %e %H:%M:%S %Y
10308      * %v      3-Sep-2008                %e-%b-%Y
10309      * %R      15:31                     %H:%M
10310      * %r      03:31:00 PM               %I:%M:%S %p
10311      * 
10312      * == Characters ==      
10313      * %n      \n                        Newline
10314      * %t      \t                        Tab
10315      * %%      %                         Percent Symbol
10316      * </pre>
10317      * 
10318      * <p>Formatting shortcuts that will be translated into their longer version.
10319      * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p>
10320      * 
10321      * <p>Format codes and format shortcuts can be redefined after the jsDate
10322      * module is imported.</p>
10323      * 
10324      * <p>Note that if you redefine the whole hash (object), you must supply a "matcher"
10325      * regex for the parser.  The default matcher is:</p>
10326      * 
10327      * <code>/()%(#?(%|[a-z]))/i</code>
10328      * 
10329      * <p>which corresponds to the Perl syntax used by default.</p>
10330      * 
10331      * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p>
10332      */
10333      
10334     jsDate.formats.perl = {
10335         codes: {
10336             //
10337             // 2-part regex matcher for format codes
10338             //
10339             // first match must be the character before the code (to account for escaping)
10340             // second match must be the format code character(s)
10341             //
10342             matcher: /()%(#?(%|[a-z]))/i,
10343             // year
10344             Y: 'FullYear',
10345             y: 'ShortYear.2',
10346             // month
10347             m: 'MonthNumber.2',
10348             '#m': 'MonthNumber',
10349             B: 'MonthName',
10350             b: 'AbbrMonthName',
10351             // day
10352             d: 'Date.2',
10353             '#d': 'Date',
10354             e: 'Date',
10355             A: 'DayName',
10356             a: 'AbbrDayName',
10357             w: 'Day',
10358             // hours
10359             H: 'Hours.2',
10360             '#H': 'Hours',
10361             I: 'Hours12.2',
10362             '#I': 'Hours12',
10363             p: 'AMPM',
10364             // minutes
10365             M: 'Minutes.2',
10366             '#M': 'Minutes',
10367             // seconds
10368             S: 'Seconds.2',
10369             '#S': 'Seconds',
10370             s: 'Unix',
10371             // milliseconds
10372             N: 'Milliseconds.3',
10373             '#N': 'Milliseconds',
10374             // timezone
10375             O: 'TimezoneOffset',
10376             Z: 'TimezoneName',
10377             G: 'GmtOffset'  
10378         },
10379         
10380         shortcuts: {
10381             // date
10382             F: '%Y-%m-%d',
10383             // time
10384             T: '%H:%M:%S',
10385             X: '%H:%M:%S',
10386             // local format date
10387             x: '%m/%d/%y',
10388             D: '%m/%d/%y',
10389             // local format extended
10390             '#c': '%a %b %e %H:%M:%S %Y',
10391             // local format short
10392             v: '%e-%b-%Y',
10393             R: '%H:%M',
10394             r: '%I:%M:%S %p',
10395             // tab and newline
10396             t: '\t',
10397             n: '\n',
10398             '%': '%'
10399         }
10400     };
10401     
10402     /**
10403      * PHP format codes and shortcuts for strftime.
10404      * 
10405      * A hash (object) of codes where each code must be an array where the first member is 
10406      * the name of a Date.prototype or jsDate.prototype function to call
10407      * and optionally a second member indicating the number to pass to addZeros()
10408      * 
10409      * <p>The following format codes are defined:</p>
10410      * 
10411      * <pre class="code">
10412      * Code    Result                    Description
10413      * === Days ===        
10414      * %a      Sun through Sat           An abbreviated textual representation of the day
10415      * %A      Sunday - Saturday         A full textual representation of the day
10416      * %d      01 to 31                  Two-digit day of the month (with leading zeros)
10417      * %e      1 to 31                   Day of the month, with a space preceding single digits.
10418      * %j      001 to 366                Day of the year, 3 digits with leading zeros
10419      * %u      1 - 7 (Mon - Sun)         ISO-8601 numeric representation of the day of the week
10420      * %w      0 - 6 (Sun - Sat)         Numeric representation of the day of the week
10421      *                                  
10422      * === Week ===                     
10423      * %U      13                        Full Week number, starting with the first Sunday as the first week
10424      * %V      01 through 53             ISO-8601:1988 week number, starting with the first week of the year 
10425      *                                   with at least 4 weekdays, with Monday being the start of the week
10426      * %W      46                        A numeric representation of the week of the year, 
10427      *                                   starting with the first Monday as the first week
10428      * === Month ===                    
10429      * %b      Jan through Dec           Abbreviated month name, based on the locale
10430      * %B      January - December        Full month name, based on the locale
10431      * %h      Jan through Dec           Abbreviated month name, based on the locale (an alias of %b)
10432      * %m      01 - 12 (Jan - Dec)       Two digit representation of the month
10433      * 
10434      * === Year ===                     
10435      * %C      19                        Two digit century (year/100, truncated to an integer)
10436      * %y      09 for 2009               Two digit year
10437      * %Y      2038                      Four digit year
10438      * 
10439      * === Time ===                     
10440      * %H      00 through 23             Two digit representation of the hour in 24-hour format
10441      * %I      01 through 12             Two digit representation of the hour in 12-hour format
10442      * %l      1 through 12              Hour in 12-hour format, with a space preceeding single digits
10443      * %M      00 through 59             Two digit representation of the minute
10444      * %p      AM/PM                     UPPER-CASE 'AM' or 'PM' based on the given time
10445      * %P      am/pm                     lower-case 'am' or 'pm' based on the given time
10446      * %r      09:34:17 PM               Same as %I:%M:%S %p
10447      * %R      00:35                     Same as %H:%M
10448      * %S      00 through 59             Two digit representation of the second
10449      * %T      21:34:17                  Same as %H:%M:%S
10450      * %X      03:59:16                  Preferred time representation based on locale, without the date
10451      * %z      -0500 or EST              Either the time zone offset from UTC or the abbreviation
10452      * %Z      -0500 or EST              The time zone offset/abbreviation option NOT given by %z
10453      * 
10454      * === Time and Date ===            
10455      * %D      02/05/09                  Same as %m/%d/%y
10456      * %F      2009-02-05                Same as %Y-%m-%d (commonly used in database datestamps)
10457      * %s      305815200                 Unix Epoch Time timestamp (same as the time() function)
10458      * %x      02/05/09                  Preferred date representation, without the time
10459      * 
10460      * === Miscellaneous ===            
10461      * %n        ---                     A newline character (\n)
10462      * %t        ---                     A Tab character (\t)
10463      * %%        ---                     A literal percentage character (%)
10464      * </pre>
10465      */
10466  
10467     jsDate.formats.php = {
10468         codes: {
10469             //
10470             // 2-part regex matcher for format codes
10471             //
10472             // first match must be the character before the code (to account for escaping)
10473             // second match must be the format code character(s)
10474             //
10475             matcher: /()%((%|[a-z]))/i,
10476             // day
10477             a: 'AbbrDayName',
10478             A: 'DayName',
10479             d: 'Date.2',
10480             e: 'Date',
10481             j: 'DayOfYear.3',
10482             u: 'DayOfWeek',
10483             w: 'Day',
10484             // week
10485             U: 'FullWeekOfYear.2',
10486             V: 'IsoWeek.2',
10487             W: 'WeekOfYear.2',
10488             // month
10489             b: 'AbbrMonthName',
10490             B: 'MonthName',
10491             m: 'MonthNumber.2',
10492             h: 'AbbrMonthName',
10493             // year
10494             C: 'Century.2',
10495             y: 'ShortYear.2',
10496             Y: 'FullYear',
10497             // time
10498             H: 'Hours.2',
10499             I: 'Hours12.2',
10500             l: 'Hours12',
10501             p: 'AMPM',
10502             P: 'AmPm',
10503             M: 'Minutes.2',
10504             S: 'Seconds.2',
10505             s: 'Unix',
10506             O: 'TimezoneOffset',
10507             z: 'GmtOffset',
10508             Z: 'TimezoneAbbr'
10509         },
10510         
10511         shortcuts: {
10512             D: '%m/%d/%y',
10513             F: '%Y-%m-%d',
10514             T: '%H:%M:%S',
10515             X: '%H:%M:%S',
10516             x: '%m/%d/%y',
10517             R: '%H:%M',
10518             r: '%I:%M:%S %p',
10519             t: '\t',
10520             n: '\n',
10521             '%': '%'
10522         }
10523     };   
10524     //
10525     // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
10526     // I use his idea of a set of parsers which can be regular expressions or functions,
10527     // iterating through those, and then seeing if Date.parse() will create a date.
10528     // The parser expressions and functions are a little different and some bugs have been
10529     // worked out.  Also, a lot of "pre-parsing" is done to fix implementation
10530     // variations of Date.parse() between browsers.
10531     //
10532     jsDate.createDate = function(date) {
10533         // if passing in multiple arguments, try Date constructor
10534         if (date == null) {
10535             return new Date();
10536         }
10537         // If the passed value is already a date object, return it
10538         if (date instanceof Date) {
10539             return date;
10540         }
10541         // if (typeof date == 'number') return new Date(date * 1000);
10542         // If the passed value is an integer, interpret it as a javascript timestamp
10543         if (typeof date == 'number') {
10544             return new Date(date);
10545         }
10546         
10547         // Before passing strings into Date.parse(), have to normalize them for certain conditions.
10548         // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.  
10549         // 
10550         // For example: 
10551         //  * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.  
10552         //  * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
10553         //  * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
10554         
10555         // remove leading and trailing spaces
10556         var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
10557         
10558         // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
10559         parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
10560         
10561         /////////
10562         // Need to check for '15-Dec-09' also.
10563         // FF will not parse, but Chrome will.
10564         // Chrome will set date to 2009 as well.
10565         /////////
10566         
10567         // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
10568         parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
10569         
10570         // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century.
10571         var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
10572         if (match && match.length > 3) {
10573             var m3 = parseFloat(match[3]);
10574             var ny = jsDate.config.defaultCentury + m3;
10575             ny = String(ny);
10576             
10577             // now replace 2 digit year with 4 digit year
10578             parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
10579             
10580         }
10581         
10582         // Check for '1/19/70 8:14PM'
10583         // where starts with mm/dd/yy or yy/mm/dd and have something after
10584         // Check if 1st postiion is greater than 31, assume it is year.
10585         // Assme all 2 digit years are 1900's.
10586         // Finally, change them into US style mm/dd/yyyy representations.
10587         match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
10588         
10589         function h1(parsable, match) {
10590             var m1 = parseFloat(match[1]);
10591             var m2 = parseFloat(match[2]);
10592             var m3 = parseFloat(match[3]);
10593             var cent = jsDate.config.defaultCentury;
10594             var ny, nd, nm, str;
10595             
10596             if (m1 > 31) { // first number is a year
10597                 nd = m3;
10598                 nm = m2;
10599                 ny = cent + m1;
10600             }
10601             
10602             else { // last number is the year
10603                 nd = m2;
10604                 nm = m1;
10605                 ny = cent + m3;
10606             }
10607             
10608             str = nm+'/'+nd+'/'+ny;
10609             
10610             // now replace 2 digit year with 4 digit year
10611             return  parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
10612         
10613         }
10614         
10615         if (match && match.length > 3) {
10616             parsable = h1(parsable, match);
10617         }
10618         
10619         // Now check for '1/19/70' with nothing after and do as above
10620         var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
10621         
10622         if (match && match.length > 3) {
10623             parsable = h1(parsable, match);
10624         }
10625                 
10626         
10627         var i = 0;
10628         var length = jsDate.matchers.length;
10629         var pattern,
10630             ms,
10631             current = parsable,
10632             obj;
10633         while (i < length) {
10634             ms = Date.parse(current);
10635             if (!isNaN(ms)) {
10636                 return new Date(ms);
10637             }
10638             pattern = jsDate.matchers[i];
10639             if (typeof pattern == 'function') {
10640                 obj = pattern.call(jsDate, current);
10641                 if (obj instanceof Date) {
10642                     return obj;
10643                 }
10644             } else {
10645                 current = parsable.replace(pattern[0], pattern[1]);
10646             }
10647             i++;
10648         }
10649         return NaN;
10650     };
10651     
10652
10653     /**
10654      * @static
10655      * Handy static utility function to return the number of days in a given month.
10656      * @param {Integer} year Year
10657      * @param {Integer} month Month (1-12)
10658      * @returns {Integer} Number of days in the month.
10659     */
10660     //
10661     // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
10662     // 
10663     jsDate.daysInMonth = function(year, month) {
10664         if (month == 2) {
10665             return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
10666         }
10667         return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
10668     };
10669
10670
10671     //
10672     // An Array of regular expressions or functions that will attempt to match the date string.
10673     // Functions are called with scope of a jsDate instance.
10674     //
10675     jsDate.matchers = [
10676         // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
10677         [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
10678         // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
10679         [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
10680         // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
10681         function(str) { 
10682             var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
10683             //                   opt. date      hour       opt. minute     opt. second       opt. msec   opt. am or pm
10684             if (match) {
10685                 if (match[1]) {
10686                     var d = this.createDate(match[1]);
10687                     if (isNaN(d)) {
10688                         return;
10689                     }
10690                 } else {
10691                     var d = new Date();
10692                     d.setMilliseconds(0);
10693                 }
10694                 var hour = parseFloat(match[2]);
10695                 if (match[6]) {
10696                     hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
10697                 }
10698                 d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
10699                 return d;
10700             }
10701             else {
10702                 return str;
10703             }
10704         },
10705         // Handle ISO timestamp with time zone.
10706         function(str) {
10707             var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
10708             if (match) {
10709                 if (match[1]) {
10710                     var d = this.createDate(match[1]);
10711                     if (isNaN(d)) {
10712                         return;
10713                     }
10714                 } else {
10715                     var d = new Date();
10716                     d.setMilliseconds(0);
10717                 }
10718                 var hour = parseFloat(match[2]);
10719                 d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
10720                 return d;
10721             }
10722             else {
10723                     return str;
10724             }
10725         },
10726         // Try to match ambiguous strings like 12/8/22.
10727         // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
10728         // This may be redundant with pre processing of date already performed.
10729         function(str) {
10730             var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
10731             if (match) {
10732                 var d = new Date();
10733                 var cent = jsDate.config.defaultCentury;
10734                 var m1 = parseFloat(match[1]);
10735                 var m3 = parseFloat(match[3]);
10736                 var ny, nd, nm;
10737                 if (m1 > 31) { // first number is a year
10738                     nd = m3;
10739                     ny = cent + m1;
10740                 }
10741                 
10742                 else { // last number is the year
10743                     nd = m1;
10744                     ny = cent + m3;
10745                 }
10746                 
10747                 var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]);
10748                 
10749                 if (nm == -1) {
10750                     nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]);
10751                 }
10752             
10753                 d.setFullYear(ny, nm, nd);
10754                 d.setHours(0,0,0,0);
10755                 return d;
10756             }
10757             
10758             else {
10759                 return str;
10760             }
10761         }      
10762     ];
10763
10764     //
10765     // I think John Reisig published this method on his blog, ejohn.
10766     //
10767     function inArray( elem, array ) {
10768         if ( array.indexOf ) {
10769             return array.indexOf( elem );
10770         }
10771
10772         for ( var i = 0, length = array.length; i < length; i++ ) {
10773             if ( array[ i ] === elem ) {
10774                 return i;
10775             }
10776         }
10777
10778         return -1;
10779     }
10780     
10781     //
10782     // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
10783     //
10784     function get_type(thing){
10785         if(thing===null) return "[object Null]"; // special case
10786         return Object.prototype.toString.call(thing);
10787     }
10788     
10789     $.jsDate = jsDate;
10790
10791       
10792     /**
10793      * JavaScript printf/sprintf functions.
10794      * 
10795      * This code has been adapted from the publicly available sprintf methods
10796      * by Ash Searle. His original header follows:
10797      *
10798      *     This code is unrestricted: you are free to use it however you like.
10799      *     
10800      *     The functions should work as expected, performing left or right alignment,
10801      *     truncating strings, outputting numbers with a required precision etc.
10802      *
10803      *     For complex cases, these functions follow the Perl implementations of
10804      *     (s)printf, allowing arguments to be passed out-of-order, and to set the
10805      *     precision or length of the output based on arguments instead of fixed
10806      *     numbers.
10807      *
10808      *     See http://perldoc.perl.org/functions/sprintf.html for more information.
10809      *
10810      *     Implemented:
10811      *     - zero and space-padding
10812      *     - right and left-alignment,
10813      *     - base X prefix (binary, octal and hex)
10814      *     - positive number prefix
10815      *     - (minimum) width
10816      *     - precision / truncation / maximum width
10817      *     - out of order arguments
10818      *
10819      *     Not implemented (yet):
10820      *     - vector flag
10821      *     - size (bytes, words, long-words etc.)
10822      *     
10823      *     Will not implement:
10824      *     - %n or %p (no pass-by-reference in JavaScript)
10825      *
10826      *     @version 2007.04.27
10827      *     @author Ash Searle 
10828      * 
10829      * You can see the original work and comments on his blog:
10830      * http://hexmen.com/blog/2007/03/printf-sprintf/
10831      * http://hexmen.com/js/sprintf.js
10832      */
10833      
10834      /**
10835       * @Modifications 2009.05.26
10836       * @author Chris Leonello
10837       * 
10838       * Added %p %P specifier
10839       * Acts like %g or %G but will not add more significant digits to the output than present in the input.
10840       * Example:
10841       * Format: '%.3p', Input: 0.012, Output: 0.012
10842       * Format: '%.3g', Input: 0.012, Output: 0.0120
10843       * Format: '%.4p', Input: 12.0, Output: 12.0
10844       * Format: '%.4g', Input: 12.0, Output: 12.00
10845       * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5
10846       * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5
10847       * 
10848       * Example:
10849       * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23)
10850       * "23.35, 43"
10851       * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524)
10852       * "no value: , decimal with thousands separator: 433,524"
10853       */
10854     $.jqplot.sprintf = function() {
10855         function pad(str, len, chr, leftJustify) {
10856             var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
10857             return leftJustify ? str + padding : padding + str;
10858
10859         }
10860
10861         function thousand_separate(value) {
10862             var value_str = new String(value);
10863             for (var i=10; i>0; i--) {
10864                 if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break;
10865             }
10866             return value_str; 
10867         }
10868
10869         function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) {
10870             var diff = minWidth - value.length;
10871             if (diff > 0) {
10872                 var spchar = ' ';
10873                 if (htmlSpace) { spchar = '&nbsp;'; }
10874                 if (leftJustify || !zeroPad) {
10875                     value = pad(value, minWidth, spchar, leftJustify);
10876                 } else {
10877                     value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
10878                 }
10879             }
10880             return value;
10881         }
10882
10883         function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
10884             // Note: casts negative numbers to positive ones
10885             var number = value >>> 0;
10886             prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
10887             value = prefix + pad(number.toString(base), precision || 0, '0', false);
10888             return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10889         }
10890
10891         function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
10892             if (precision != null) {
10893                 value = value.slice(0, precision);
10894             }
10895             return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace);
10896         }
10897
10898         var a = arguments, i = 0, format = a[i++];
10899
10900         return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
10901             if (substring == '%%') { return '%'; }
10902
10903             // parse flags
10904             var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false;
10905             for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
10906                 case ' ': positivePrefix = ' '; break;
10907                 case '+': positivePrefix = '+'; break;
10908                 case '-': leftJustify = true; break;
10909                 case '0': zeroPad = true; break;
10910                 case '#': prefixBaseX = true; break;
10911                 case '&': htmlSpace = true; break;
10912                 case '\'': thousandSeparation = true; break;
10913             }
10914
10915             // parameters may be null, undefined, empty-string or real valued
10916             // we want to ignore null, undefined and empty-string values
10917
10918             if (!minWidth) {
10919                 minWidth = 0;
10920             } 
10921             else if (minWidth == '*') {
10922                 minWidth = +a[i++];
10923             } 
10924             else if (minWidth.charAt(0) == '*') {
10925                 minWidth = +a[minWidth.slice(1, -1)];
10926             } 
10927             else {
10928                 minWidth = +minWidth;
10929             }
10930
10931             // Note: undocumented perl feature:
10932             if (minWidth < 0) {
10933                 minWidth = -minWidth;
10934                 leftJustify = true;
10935             }
10936
10937             if (!isFinite(minWidth)) {
10938                 throw new Error('$.jqplot.sprintf: (minimum-)width must be finite');
10939             }
10940
10941             if (!precision) {
10942                 precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
10943             } 
10944             else if (precision == '*') {
10945                 precision = +a[i++];
10946             } 
10947             else if (precision.charAt(0) == '*') {
10948                 precision = +a[precision.slice(1, -1)];
10949             } 
10950             else {
10951                 precision = +precision;
10952             }
10953
10954             // grab value using valueIndex if required?
10955             var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
10956
10957             switch (type) {
10958             case 's': {
10959                 if (value == null) {
10960                     return '';
10961                 }
10962                 return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
10963             }
10964             case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
10965             case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace);
10966             case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10967             case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10968             case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase();
10969             case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10970             case 'i': {
10971               var number = parseInt(+value, 10);
10972               if (isNaN(number)) {
10973                 return '';
10974               }
10975               var prefix = number < 0 ? '-' : positivePrefix;
10976               var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
10977               value = prefix + pad(number_str, precision, '0', false);
10978               //value = prefix + pad(String(Math.abs(number)), precision, '0', false);
10979               return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10980                   }
10981             case 'd': {
10982               var number = Math.round(+value);
10983               if (isNaN(number)) {
10984                 return '';
10985               }
10986               var prefix = number < 0 ? '-' : positivePrefix;
10987               var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
10988               value = prefix + pad(number_str, precision, '0', false);
10989               return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10990                   }
10991             case 'e':
10992             case 'E':
10993             case 'f':
10994             case 'F':
10995             case 'g':
10996             case 'G':
10997                       {
10998                       var number = +value;
10999                       if (isNaN(number)) {
11000                           return '';
11001                       }
11002                       var prefix = number < 0 ? '-' : positivePrefix;
11003                       var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
11004                       var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
11005                       var number_str = Math.abs(number)[method](precision);
11006                       
11007                       // Apply the decimal mark properly by splitting the number by the
11008                       //   decimalMark, applying thousands separator, and then placing it
11009                       //   back in.
11010                       var parts = number_str.toString().split('.');
11011                       parts[0] = thousandSeparation ? thousand_separate(parts[0]) : parts[0];
11012                       number_str = parts.join($.jqplot.sprintf.decimalMark);
11013                       
11014                       value = prefix + number_str;
11015                       var justified = justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
11016                       
11017                       return justified;
11018                   }
11019             case 'p':
11020             case 'P':
11021             {
11022                 // make sure number is a number
11023                 var number = +value;
11024                 if (isNaN(number)) {
11025                     return '';
11026                 }
11027                 var prefix = number < 0 ? '-' : positivePrefix;
11028
11029                 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
11030                 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : String(number).length;
11031                 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
11032                 
11033                 if (Math.abs(number) < 1) {
11034                     if (sd + zeros  <= precision) {
11035                         value = prefix + Math.abs(number).toPrecision(sd);
11036                     }
11037                     else {
11038                         if (sd  <= precision - 1) {
11039                             value = prefix + Math.abs(number).toExponential(sd-1);
11040                         }
11041                         else {
11042                             value = prefix + Math.abs(number).toExponential(precision-1);
11043                         }
11044                     }
11045                 }
11046                 else {
11047                     var prec = (sd <= precision) ? sd : precision;
11048                     value = prefix + Math.abs(number).toPrecision(prec);
11049                 }
11050                 var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2];
11051                 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
11052             }
11053             case 'n': return '';
11054             default: return substring;
11055             }
11056         });
11057     };
11058
11059     $.jqplot.sprintf.thousandsSeparator = ',';
11060     // Specifies the decimal mark for floating point values. By default a period '.'
11061     // is used. If you change this value to for example a comma be sure to also
11062     // change the thousands separator or else this won't work since a simple String
11063     // replace is used (replacing all periods with the mark specified here).
11064     $.jqplot.sprintf.decimalMark = '.';
11065     
11066     $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g;
11067
11068     $.jqplot.getSignificantFigures = function(number) {
11069         var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
11070         // total significant digits
11071         var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
11072         var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
11073         // exponent
11074         var expn = parseInt(parts[1], 10);
11075         // digits to the left of the decimal place
11076         var dleft = (expn + 1 > 0) ? expn + 1 : 0;
11077         // digits to the right of the decimal place
11078         var dright = (sd <= dleft) ? 0 : sd - expn - 1;
11079         return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ;
11080     };
11081
11082     $.jqplot.getPrecision = function(number) {
11083         return $.jqplot.getSignificantFigures(number).digitsRight;
11084     };
11085
11086   
11087
11088
11089     var backCompat = $.uiBackCompat !== false;
11090
11091     $.jqplot.effects = {
11092         effect: {}
11093     };
11094
11095     // prefix used for storing data on .data()
11096     var dataSpace = "jqplot.storage.";
11097
11098     /******************************************************************************/
11099     /*********************************** EFFECTS **********************************/
11100     /******************************************************************************/
11101
11102     $.extend( $.jqplot.effects, {
11103         version: "1.9pre",
11104
11105         // Saves a set of properties in a data storage
11106         save: function( element, set ) {
11107             for( var i=0; i < set.length; i++ ) {
11108                 if ( set[ i ] !== null ) {
11109                     element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
11110                 }
11111             }
11112         },
11113
11114         // Restores a set of previously saved properties from a data storage
11115         restore: function( element, set ) {
11116             for( var i=0; i < set.length; i++ ) {
11117                 if ( set[ i ] !== null ) {
11118                     element.css( set[ i ], element.data( dataSpace + set[ i ] ) );
11119                 }
11120             }
11121         },
11122
11123         setMode: function( el, mode ) {
11124             if (mode === "toggle") {
11125                 mode = el.is( ":hidden" ) ? "show" : "hide";
11126             }
11127             return mode;
11128         },
11129
11130         // Wraps the element around a wrapper that copies position properties
11131         createWrapper: function( element ) {
11132
11133             // if the element is already wrapped, return it
11134             if ( element.parent().is( ".ui-effects-wrapper" )) {
11135                 return element.parent();
11136             }
11137
11138             // wrap the element
11139             var props = {
11140                     width: element.outerWidth(true),
11141                     height: element.outerHeight(true),
11142                     "float": element.css( "float" )
11143                 },
11144                 wrapper = $( "<div></div>" )
11145                     .addClass( "ui-effects-wrapper" )
11146                     .css({
11147                         fontSize: "100%",
11148                         background: "transparent",
11149                         border: "none",
11150                         margin: 0,
11151                         padding: 0
11152                     }),
11153                 // Store the size in case width/height are defined in % - Fixes #5245
11154                 size = {
11155                     width: element.width(),
11156                     height: element.height()
11157                 },
11158                 active = document.activeElement;
11159
11160             element.wrap( wrapper );
11161
11162             // Fixes #7595 - Elements lose focus when wrapped.
11163             if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
11164                 $( active ).focus();
11165             }
11166
11167             wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
11168
11169             // transfer positioning properties to the wrapper
11170             if ( element.css( "position" ) === "static" ) {
11171                 wrapper.css({ position: "relative" });
11172                 element.css({ position: "relative" });
11173             } else {
11174                 $.extend( props, {
11175                     position: element.css( "position" ),
11176                     zIndex: element.css( "z-index" )
11177                 });
11178                 $.each([ "top", "left", "bottom", "right" ], function(i, pos) {
11179                     props[ pos ] = element.css( pos );
11180                     if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
11181                         props[ pos ] = "auto";
11182                     }
11183                 });
11184                 element.css({
11185                     position: "relative",
11186                     top: 0,
11187                     left: 0,
11188                     right: "auto",
11189                     bottom: "auto"
11190                 });
11191             }
11192             element.css(size);
11193
11194             return wrapper.css( props ).show();
11195         },
11196
11197         removeWrapper: function( element ) {
11198             var active = document.activeElement;
11199
11200             if ( element.parent().is( ".ui-effects-wrapper" ) ) {
11201                 element.parent().replaceWith( element );
11202
11203                 // Fixes #7595 - Elements lose focus when wrapped.
11204                 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
11205                     $( active ).focus();
11206                 }
11207             }
11208
11209
11210             return element;
11211         }
11212     });
11213
11214     // return an effect options object for the given parameters:
11215     function _normalizeArguments( effect, options, speed, callback ) {
11216
11217         // short path for passing an effect options object:
11218         if ( $.isPlainObject( effect ) ) {
11219             return effect;
11220         }
11221
11222         // convert to an object
11223         effect = { effect: effect };
11224
11225         // catch (effect)
11226         if ( options === undefined ) {
11227             options = {};
11228         }
11229
11230         // catch (effect, callback)
11231         if ( $.isFunction( options ) ) {
11232             callback = options;
11233             speed = null;
11234             options = {};
11235         }
11236
11237         // catch (effect, speed, ?)
11238         if ( $.type( options ) === "number" || $.fx.speeds[ options ]) {
11239             callback = speed;
11240             speed = options;
11241             options = {};
11242         }
11243
11244         // catch (effect, options, callback)
11245         if ( $.isFunction( speed ) ) {
11246             callback = speed;
11247             speed = null;
11248         }
11249
11250         // add options to effect
11251         if ( options ) {
11252             $.extend( effect, options );
11253         }
11254
11255         speed = speed || options.duration;
11256         effect.duration = $.fx.off ? 0 : typeof speed === "number"
11257             ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default;
11258
11259         effect.complete = callback || options.complete;
11260
11261         return effect;
11262     }
11263
11264     function standardSpeed( speed ) {
11265         // valid standard speeds
11266         if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
11267             return true;
11268         }
11269
11270         // invalid strings - treat as "normal" speed
11271         if ( typeof speed === "string" && !$.jqplot.effects.effect[ speed ] ) {
11272             // TODO: remove in 2.0 (#7115)
11273             if ( backCompat && $.jqplot.effects[ speed ] ) {
11274                 return false;
11275             }
11276             return true;
11277         }
11278
11279         return false;
11280     }
11281
11282     $.fn.extend({
11283         jqplotEffect: function( effect, options, speed, callback ) {
11284             var args = _normalizeArguments.apply( this, arguments ),
11285                 mode = args.mode,
11286                 queue = args.queue,
11287                 effectMethod = $.jqplot.effects.effect[ args.effect ],
11288
11289                 // DEPRECATED: remove in 2.0 (#7115)
11290                 oldEffectMethod = !effectMethod && backCompat && $.jqplot.effects[ args.effect ];
11291
11292             if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
11293                 // delegate to the original method (e.g., .show()) if possible
11294                 if ( mode ) {
11295                     return this[ mode ]( args.duration, args.complete );
11296                 } else {
11297                     return this.each( function() {
11298                         if ( args.complete ) {
11299                             args.complete.call( this );
11300                         }
11301                     });
11302                 }
11303             }
11304
11305             function run( next ) {
11306                 var elem = $( this ),
11307                     complete = args.complete,
11308                     mode = args.mode;
11309
11310                 function done() {
11311                     if ( $.isFunction( complete ) ) {
11312                         complete.call( elem[0] );
11313                     }
11314                     if ( $.isFunction( next ) ) {
11315                         next();
11316                     }
11317                 }
11318
11319                 // if the element is hiddden and mode is hide,
11320                 // or element is visible and mode is show
11321                 if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
11322                     done();
11323                 } else {
11324                     effectMethod.call( elem[0], args, done );
11325                 }
11326             }
11327
11328             // TODO: remove this check in 2.0, effectMethod will always be true
11329             if ( effectMethod ) {
11330                 return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
11331             } else {
11332                 // DEPRECATED: remove in 2.0 (#7115)
11333                 return oldEffectMethod.call(this, {
11334                     options: args,
11335                     duration: args.duration,
11336                     callback: args.complete,
11337                     mode: args.mode
11338                 });
11339             }
11340         }
11341     });
11342
11343
11344
11345
11346     var rvertical = /up|down|vertical/,
11347         rpositivemotion = /up|left|vertical|horizontal/;
11348
11349     $.jqplot.effects.effect.blind = function( o, done ) {
11350         // Create element
11351         var el = $( this ),
11352             props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
11353             mode = $.jqplot.effects.setMode( el, o.mode || "hide" ),
11354             direction = o.direction || "up",
11355             vertical = rvertical.test( direction ),
11356             ref = vertical ? "height" : "width",
11357             ref2 = vertical ? "top" : "left",
11358             motion = rpositivemotion.test( direction ),
11359             animation = {},
11360             show = mode === "show",
11361             wrapper, distance, top;
11362
11363         // // if already wrapped, the wrapper's properties are my property. #6245
11364         if ( el.parent().is( ".ui-effects-wrapper" ) ) {
11365             $.jqplot.effects.save( el.parent(), props );
11366         } else {
11367             $.jqplot.effects.save( el, props );
11368         }
11369         el.show();
11370         top = parseInt(el.css('top'), 10);
11371         wrapper = $.jqplot.effects.createWrapper( el ).css({
11372             overflow: "hidden"
11373         });
11374
11375         distance = vertical ? wrapper[ ref ]() + top : wrapper[ ref ]();
11376
11377         animation[ ref ] = show ? String(distance) : '0';
11378         if ( !motion ) {
11379             el
11380                 .css( vertical ? "bottom" : "right", 0 )
11381                 .css( vertical ? "top" : "left", "" )
11382                 .css({ position: "absolute" });
11383             animation[ ref2 ] = show ? '0' : String(distance);
11384         }
11385
11386         // // start at 0 if we are showing
11387         if ( show ) {
11388             wrapper.css( ref, 0 );
11389             if ( ! motion ) {
11390                 wrapper.css( ref2, distance );
11391             }
11392         }
11393
11394         // // Animate
11395         wrapper.animate( animation, {
11396             duration: o.duration,
11397             easing: o.easing,
11398             queue: false,
11399             complete: function() {
11400                 if ( mode === "hide" ) {
11401                     el.hide();
11402                 }
11403                 $.jqplot.effects.restore( el, props );
11404                 $.jqplot.effects.removeWrapper( el );
11405                 done();
11406             }
11407         });
11408
11409     };
11410
11411 })(jQuery);