blob: 48dd8ab7dcbb4ecab7e28814c9b47336a47455bd [file] [log] [blame]
vinayakb60c6a8e2011-11-08 18:12:15 +00001/**
2 * Title: jqPlot Charts
3 *
4 * Pure JavaScript plotting plugin for jQuery.
5 *
6 * About: Version
7 *
8 * 1.0.0b2_r947
9 *
10 * About: Copyright & License
11 *
12 * Copyright (c) 2009-2011 Chris Leonello
13 * jqPlot is currently available for use in all personal or commercial projects
14 * under both the MIT and GPL version 2.0 licenses. This means that you can
15 * choose the license that best suits your project and use it accordingly.
16 *
17 * See <GPL Version 2> and <MIT License> contained within this distribution for further information.
18 *
19 * The author would appreciate an email letting him know of any substantial
20 * use of jqPlot. You can reach the author at: chris at jqplot dot com
21 * or see http://www.jqplot.com/info.php. This is, of course, not required.
22 *
23 * If you are feeling kind and generous, consider supporting the project by
24 * making a donation at: http://www.jqplot.com/donate.php.
25 *
26 * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
27 *
28 * version 2007.04.27
29 * author Ash Searle
30 * http://hexmen.com/blog/2007/03/printf-sprintf/
31 * http://hexmen.com/js/sprintf.js
32 * The author (Ash Searle) has placed this code in the public domain:
33 * "This code is unrestricted: you are free to use it however you like."
34 *
35 *
36 * About: Introduction
37 *
38 * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution.
39 * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally
40 * the excanvas script for IE support in your web page:
41 *
42 * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
43 * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script>
44 * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script>
45 * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" />
46 *
47 * jqPlot can be customized by overriding the defaults of any of the objects which make
48 * up the plot. The general usage of jqplot is:
49 *
50 * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject});
51 *
52 * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file.
53 *
54 * An actual call to $.jqplot() may look like the
55 * examples below:
56 *
57 * > chart = $.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]);
58 *
59 * or
60 *
61 * > dataArray = [34,12,43,55,77];
62 * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}});
63 *
64 * For more inforrmation, see <jqPlot Usage>.
65 *
66 * About: Usage
67 *
68 * See <jqPlot Usage>
69 *
70 * About: Available Options
71 *
72 * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!)
73 *
74 * About: Options Usage
75 *
76 * See <Options Tutorial>
77 *
78 * About: Changes
79 *
80 * See <Change Log>
81 *
82 */
83
84(function($) {
85 // make sure undefined is undefined
86 var undefined;
87
88 $.fn.emptyForce = function() {
89 for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) {
90 // Remove element nodes and prevent memory leaks
91 if ( elem.nodeType === 1 ) {
92 jQuery.cleanData( elem.getElementsByTagName("*") );
93 }
94
95 // Remove any remaining nodes
96 if ($.jqplot_use_excanvas) {
97 elem.outerHTML = "";
98 }
99 else {
100 while ( elem.firstChild ) {
101 elem.removeChild( elem.firstChild );
102 }
103 }
104
105 elem = null;
106 }
107
108 return $(this);
109 };
110
111 $.fn.removeChildForce = function(parent) {
112 while ( parent.firstChild ) {
113 this.removeChildForce( parent.firstChild );
114 parent.removeChild( parent.firstChild );
115 }
116 };
117
118
119 /**
120 * Namespace: $.jqplot
121 * jQuery function called by the user to create a plot.
122 *
123 * Parameters:
124 * target - ID of target element to render the plot into.
125 * data - an array of data series.
126 * options - user defined options object. See the individual classes for available options.
127 *
128 * Properties:
129 * config - object to hold configuration information for jqPlot plot object.
130 *
131 * attributes:
132 * enablePlugins - False to disable plugins by default. Plugins must then be explicitly
133 * enabled in the individual plot options. Default: false.
134 * This property sets the "show" property of certain plugins to true or false.
135 * Only plugins that can be immediately active upon loading are affected. This includes
136 * non-renderer plugins like cursor, dragable, highlighter, and trendline.
137 * defaultHeight - Default height for plots where no css height specification exists. This
138 * is a jqplot wide default.
139 * defaultWidth - Default height for plots where no css height specification exists. This
140 * is a jqplot wide default.
141 */
142
143 $.jqplot = function(target, data, options) {
144 var _data, _options;
145
146 if (options == null) {
147 if (jQuery.isArray(data)) {
148 _data = data;
149 _options = null;
150 }
151
152 else if (typeof(data) === 'object') {
153 _data = null;
154 _options = data;
155 }
156 }
157 else {
158 _data = data;
159 _options = options;
160 }
161 var plot = new jqPlot();
162 // remove any error class that may be stuck on target.
163 $('#'+target).removeClass('jqplot-error');
164
165 if ($.jqplot.config.catchErrors) {
166 try {
167 plot.init(target, _data, _options);
168 plot.draw();
169 plot.themeEngine.init.call(plot);
170 return plot;
171 }
172 catch(e) {
173 var msg = $.jqplot.config.errorMessage || e.message;
174 $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>');
175 $('#'+target).addClass('jqplot-error');
176 document.getElementById(target).style.background = $.jqplot.config.errorBackground;
177 document.getElementById(target).style.border = $.jqplot.config.errorBorder;
178 document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily;
179 document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize;
180 document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle;
181 document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight;
182 }
183 }
184 else {
185 plot.init(target, _data, _options);
186 plot.draw();
187 plot.themeEngine.init.call(plot);
188 return plot;
189 }
190 };
191
192 $.jqplot.version = "1.0.0b2_r947";
193
194 // canvas manager to reuse canvases on the plot.
195 // Should help solve problem of canvases not being freed and
196 // problem of waiting forever for firefox to decide to free memory.
197 $.jqplot.CanvasManager = function() {
198 // canvases are managed globally so that they can be reused
199 // across plots after they have been freed
200 if (typeof $.jqplot.CanvasManager.canvases == 'undefined') {
201 $.jqplot.CanvasManager.canvases = [];
202 $.jqplot.CanvasManager.free = [];
203 }
204
205 var myCanvases = [];
206
207 this.getCanvas = function() {
208 var canvas;
209 var makeNew = true;
210
211 if (!$.jqplot.use_excanvas) {
212 for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) {
213 if ($.jqplot.CanvasManager.free[i] === true) {
214 makeNew = false;
215 canvas = $.jqplot.CanvasManager.canvases[i];
216 // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse');
217 $.jqplot.CanvasManager.free[i] = false;
218 myCanvases.push(i);
219 break;
220 }
221 }
222 }
223
224 if (makeNew) {
225 canvas = document.createElement('canvas');
226 myCanvases.push($.jqplot.CanvasManager.canvases.length);
227 $.jqplot.CanvasManager.canvases.push(canvas);
228 $.jqplot.CanvasManager.free.push(false);
229 }
230
231 return canvas;
232 };
233
234 // this method has to be used after settings the dimesions
235 // on the element returned by getCanvas()
236 this.initCanvas = function(canvas) {
237 if ($.jqplot.use_excanvas) {
238 return window.G_vmlCanvasManager.initElement(canvas);
239 }
240 return canvas;
241 };
242
243 this.freeAllCanvases = function() {
244 for (var i = 0, l=myCanvases.length; i < l; i++) {
245 this.freeCanvas(myCanvases[i]);
246 }
247 myCanvases = [];
248 };
249
250 this.freeCanvas = function(idx) {
251 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
252 // excanvas can't be reused, but properly unset
253 window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]);
254 $.jqplot.CanvasManager.canvases[idx] = null;
255 }
256 else {
257 var canvas = $.jqplot.CanvasManager.canvases[idx];
258 canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
259 $(canvas).unbind().removeAttr('class').removeAttr('style');
260 // Style attributes seemed to be still hanging around. wierd. Some ticks
261 // still retained a left: 0px attribute after reusing a canvas.
262 $(canvas).css({left: '', top: '', position: ''});
263 // setting size to 0 may save memory of unused canvases?
264 canvas.width = 0;
265 canvas.height = 0;
266 $.jqplot.CanvasManager.free[idx] = true;
267 }
268 };
269
270 };
271
272
273 // Convienence function that won't hang IE or FF without FireBug.
274 $.jqplot.log = function() {
275 if (window.console) {
276 window.console.log.apply(window.console, arguments);
277 }
278 };
279
280 $.jqplot.config = {
281 enablePlugins:false,
282 defaultHeight:300,
283 defaultWidth:400,
284 UTCAdjust:false,
285 timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000),
286 errorMessage: '',
287 errorBackground: '',
288 errorBorder: '',
289 errorFontFamily: '',
290 errorFontSize: '',
291 errorFontStyle: '',
292 errorFontWeight: '',
293 catchErrors: false,
294 defaultTickFormatString: "%.1f",
295 defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
296 defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"],
297 dashLength: 4,
298 gapLength: 4,
299 dotGapLength: 2.5,
300 srcLocation: 'jqplot/src/',
301 pluginLocation: 'jqplot/src/plugins/'
302 };
303
304
305 $.jqplot.arrayMax = function( array ){
306 return Math.max.apply( Math, array );
307 };
308
309 $.jqplot.arrayMin = function( array ){
310 return Math.min.apply( Math, array );
311 };
312
313 $.jqplot.enablePlugins = $.jqplot.config.enablePlugins;
314
315 // canvas related tests taken from modernizer:
316 // Copyright (c) 2009 - 2010 Faruk Ates.
317 // http://www.modernizr.com
318
319 $.jqplot.support_canvas = function() {
320 if (typeof $.jqplot.support_canvas.result == 'undefined') {
321 $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext;
322 }
323 return $.jqplot.support_canvas.result;
324 };
325
326 $.jqplot.support_canvas_text = function() {
327 if (typeof $.jqplot.support_canvas_text.result == 'undefined') {
328 if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) {
329 $.jqplot.support_canvas_text.result = true;
330 }
331 else {
332 $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function');
333 }
334
335 }
336 return $.jqplot.support_canvas_text.result;
337 };
338
339 $.jqplot.use_excanvas = ($.browser.msie && !$.jqplot.support_canvas()) ? true : false;
340
341 /**
342 *
343 * Hooks: jqPlot Pugin Hooks
344 *
345 * $.jqplot.preInitHooks - called before initialization.
346 * $.jqplot.postInitHooks - called after initialization.
347 * $.jqplot.preParseOptionsHooks - called before user options are parsed.
348 * $.jqplot.postParseOptionsHooks - called after user options are parsed.
349 * $.jqplot.preDrawHooks - called before plot draw.
350 * $.jqplot.postDrawHooks - called after plot draw.
351 * $.jqplot.preDrawSeriesHooks - called before each series is drawn.
352 * $.jqplot.postDrawSeriesHooks - called after each series is drawn.
353 * $.jqplot.preDrawLegendHooks - called before the legend is drawn.
354 * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins
355 * can add rows to the legend table.
356 * $.jqplot.preSeriesInitHooks - called before series is initialized.
357 * $.jqplot.postSeriesInitHooks - called after series is initialized.
358 * $.jqplot.preParseSeriesOptionsHooks - called before series related options
359 * are parsed.
360 * $.jqplot.postParseSeriesOptionsHooks - called after series related options
361 * are parsed.
362 * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds
363 * listeners to the event canvas which lays on top of the grid area.
364 * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn.
365 * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn.
366 *
367 */
368
369 $.jqplot.preInitHooks = [];
370 $.jqplot.postInitHooks = [];
371 $.jqplot.preParseOptionsHooks = [];
372 $.jqplot.postParseOptionsHooks = [];
373 $.jqplot.preDrawHooks = [];
374 $.jqplot.postDrawHooks = [];
375 $.jqplot.preDrawSeriesHooks = [];
376 $.jqplot.postDrawSeriesHooks = [];
377 $.jqplot.preDrawLegendHooks = [];
378 $.jqplot.addLegendRowHooks = [];
379 $.jqplot.preSeriesInitHooks = [];
380 $.jqplot.postSeriesInitHooks = [];
381 $.jqplot.preParseSeriesOptionsHooks = [];
382 $.jqplot.postParseSeriesOptionsHooks = [];
383 $.jqplot.eventListenerHooks = [];
384 $.jqplot.preDrawSeriesShadowHooks = [];
385 $.jqplot.postDrawSeriesShadowHooks = [];
386
387 // A superclass holding some common properties and methods.
388 $.jqplot.ElemContainer = function() {
389 this._elem;
390 this._plotWidth;
391 this._plotHeight;
392 this._plotDimensions = {height:null, width:null};
393 };
394
395 $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) {
396 this._offsets = offsets;
397 var klass = clss || 'jqplot';
398 var elem = document.createElement(el);
399 this._elem = $(elem);
400 this._elem.addClass(klass);
401 this._elem.css(cssopts);
402 this._elem.attr(attrib);
403 // avoid memory leak;
404 elem = null;
405 return this._elem;
406 };
407
408 $.jqplot.ElemContainer.prototype.getWidth = function() {
409 if (this._elem) {
410 return this._elem.outerWidth(true);
411 }
412 else {
413 return null;
414 }
415 };
416
417 $.jqplot.ElemContainer.prototype.getHeight = function() {
418 if (this._elem) {
419 return this._elem.outerHeight(true);
420 }
421 else {
422 return null;
423 }
424 };
425
426 $.jqplot.ElemContainer.prototype.getPosition = function() {
427 if (this._elem) {
428 return this._elem.position();
429 }
430 else {
431 return {top:null, left:null, bottom:null, right:null};
432 }
433 };
434
435 $.jqplot.ElemContainer.prototype.getTop = function() {
436 return this.getPosition().top;
437 };
438
439 $.jqplot.ElemContainer.prototype.getLeft = function() {
440 return this.getPosition().left;
441 };
442
443 $.jqplot.ElemContainer.prototype.getBottom = function() {
444 return this._elem.css('bottom');
445 };
446
447 $.jqplot.ElemContainer.prototype.getRight = function() {
448 return this._elem.css('right');
449 };
450
451
452 /**
453 * Class: Axis
454 * An individual axis object. Cannot be instantiated directly, but created
455 * by the Plot oject. Axis properties can be set or overriden by the
456 * options passed in from the user.
457 *
458 */
459 function Axis(name) {
460 $.jqplot.ElemContainer.call(this);
461 // Group: Properties
462 //
463 // Axes options are specified within an axes object at the top level of the
464 // plot options like so:
465 // > {
466 // > axes: {
467 // > xaxis: {min: 5},
468 // > yaxis: {min: 2, max: 8, numberTicks:4},
469 // > x2axis: {pad: 1.5},
470 // > y2axis: {ticks:[22, 44, 66, 88]}
471 // > }
472 // > }
473 // There are 2 x axes, 'xaxis' and 'x2axis', and
474 // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ... Any or all of which may be specified.
475 this.name = name;
476 this._series = [];
477 // prop: show
478 // Wether to display the axis on the graph.
479 this.show = false;
480 // prop: tickRenderer
481 // A class of a rendering engine for creating the ticks labels displayed on the plot,
482 // See <$.jqplot.AxisTickRenderer>.
483 this.tickRenderer = $.jqplot.AxisTickRenderer;
484 // prop: tickOptions
485 // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options.
486 this.tickOptions = {};
487 // prop: labelRenderer
488 // A class of a rendering engine for creating an axis label.
489 this.labelRenderer = $.jqplot.AxisLabelRenderer;
490 // prop: labelOptions
491 // Options passed to the label renderer.
492 this.labelOptions = {};
493 // prop: label
494 // Label for the axis
495 this.label = null;
496 // prop: showLabel
497 // true to show the axis label.
498 this.showLabel = true;
499 // prop: min
500 // minimum value of the axis (in data units, not pixels).
501 this.min = null;
502 // prop: max
503 // maximum value of the axis (in data units, not pixels).
504 this.max = null;
505 // prop: autoscale
506 // DEPRECATED
507 // the default scaling algorithm produces superior results.
508 this.autoscale = false;
509 // prop: pad
510 // Padding to extend the range above and below the data bounds.
511 // The data range is multiplied by this factor to determine minimum and maximum axis bounds.
512 // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0.
513 this.pad = 1.2;
514 // prop: padMax
515 // Padding to extend the range above data bounds.
516 // The top of the data range is multiplied by this factor to determine maximum axis bounds.
517 // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0.
518 this.padMax = null;
519 // prop: padMin
520 // Padding to extend the range below data bounds.
521 // The bottom of the data range is multiplied by this factor to determine minimum axis bounds.
522 // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0.
523 this.padMin = null;
524 // prop: ticks
525 // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis.
526 // If no label is specified, the value is formatted into an appropriate label.
527 this.ticks = [];
528 // prop: numberTicks
529 // Desired number of ticks. Default is to compute automatically.
530 this.numberTicks;
531 // prop: tickInterval
532 // number of units between ticks. Mutually exclusive with numberTicks.
533 this.tickInterval;
534 // prop: renderer
535 // A class of a rendering engine that handles tick generation,
536 // scaling input data to pixel grid units and drawing the axis element.
537 this.renderer = $.jqplot.LinearAxisRenderer;
538 // prop: rendererOptions
539 // renderer specific options. See <$.jqplot.LinearAxisRenderer> for options.
540 this.rendererOptions = {};
541 // prop: showTicks
542 // Wether to show the ticks (both marks and labels) or not.
543 // Will not override showMark and showLabel options if specified on the ticks themselves.
544 this.showTicks = true;
545 // prop: showTickMarks
546 // Wether to show the tick marks (line crossing grid) or not.
547 // Overridden by showTicks and showMark option of tick itself.
548 this.showTickMarks = true;
549 // prop: showMinorTicks
550 // Wether or not to show minor ticks. This is renderer dependent.
551 this.showMinorTicks = true;
552 // prop: drawMajorGridlines
553 // True to draw gridlines for major axis ticks.
554 this.drawMajorGridlines = true;
555 // prop: drawMinorGridlines
556 // True to draw gridlines for minor ticks.
557 this.drawMinorGridlines = false;
558 // prop: drawMajorTickMarks
559 // True to draw tick marks for major axis ticks.
560 this.drawMajorTickMarks = true;
561 // prop: drawMinorTickMarks
562 // True to draw tick marks for minor ticks. This is renderer dependent.
563 this.drawMinorTickMarks = true;
564 // prop: useSeriesColor
565 // Use the color of the first series associated with this axis for the
566 // tick marks and line bordering this axis.
567 this.useSeriesColor = false;
568 // prop: borderWidth
569 // width of line stroked at the border of the axis. Defaults
570 // to the width of the grid boarder.
571 this.borderWidth = null;
572 // prop: borderColor
573 // color of the border adjacent to the axis. Defaults to grid border color.
574 this.borderColor = null;
575 // minimum and maximum values on the axis.
576 this._dataBounds = {min:null, max:null};
577 // statistics (min, max, mean) as well as actual data intervals for each series attached to axis.
578 // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis.
579 this._intervalStats = [];
580 // pixel position from the top left of the min value and max value on the axis.
581 this._offsets = {min:null, max:null};
582 this._ticks=[];
583 this._label = null;
584 // prop: syncTicks
585 // true to try and synchronize tick spacing across multiple axes so that ticks and
586 // grid lines line up. This has an impact on autoscaling algorithm, however.
587 // In general, autoscaling an individual axis will work better if it does not
588 // have to sync ticks.
589 this.syncTicks = null;
590 // prop: tickSpacing
591 // Approximate pixel spacing between ticks on graph. Used during autoscaling.
592 // This number will be an upper bound, actual spacing will be less.
593 this.tickSpacing = 75;
594 // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks
595 // so they can be restored if altered by plugins.
596 this._min = null;
597 this._max = null;
598 this._tickInterval = null;
599 this._numberTicks = null;
600 this.__ticks = null;
601 // hold original user options.
602 this._options = {};
603 }
604
605 Axis.prototype = new $.jqplot.ElemContainer();
606 Axis.prototype.constructor = Axis;
607
608 Axis.prototype.init = function() {
609 this.renderer = new this.renderer();
610 // set the axis name
611 this.tickOptions.axis = this.name;
612 // if showMark or showLabel tick options not specified, use value of axis option.
613 // showTicks overrides showTickMarks.
614 if (this.tickOptions.showMark == null) {
615 this.tickOptions.showMark = this.showTicks;
616 }
617 if (this.tickOptions.showMark == null) {
618 this.tickOptions.showMark = this.showTickMarks;
619 }
620 if (this.tickOptions.showLabel == null) {
621 this.tickOptions.showLabel = this.showTicks;
622 }
623
624 if (this.label == null || this.label == '') {
625 this.showLabel = false;
626 }
627 else {
628 this.labelOptions.label = this.label;
629 }
630 if (this.showLabel == false) {
631 this.labelOptions.show = false;
632 }
633 // set the default padMax, padMin if not specified
634 // special check, if no padding desired, padding
635 // should be set to 1.0
636 if (this.pad == 0) {
637 this.pad = 1.0;
638 }
639 if (this.padMax == 0) {
640 this.padMax = 1.0;
641 }
642 if (this.padMin == 0) {
643 this.padMin = 1.0;
644 }
645 if (this.padMax == null) {
646 this.padMax = (this.pad-1)/2 + 1;
647 }
648 if (this.padMin == null) {
649 this.padMin = (this.pad-1)/2 + 1;
650 }
651 // now that padMin and padMax are correctly set, reset pad in case user has supplied
652 // padMin and/or padMax
653 this.pad = this.padMax + this.padMin - 1;
654 if (this.min != null || this.max != null) {
655 this.autoscale = false;
656 }
657 // if not set, sync ticks for y axes but not x by default.
658 if (this.syncTicks == null && this.name.indexOf('y') > -1) {
659 this.syncTicks = true;
660 }
661 else if (this.syncTicks == null){
662 this.syncTicks = false;
663 }
664 this.renderer.init.call(this, this.rendererOptions);
665
666 };
667
668 Axis.prototype.draw = function(ctx, plot) {
669 // Memory Leaks patch
670 if (this.__ticks) {
671 this.__ticks = null;
672 }
673
674 return this.renderer.draw.call(this, ctx, plot);
675
676 };
677
678 Axis.prototype.set = function() {
679 this.renderer.set.call(this);
680 };
681
682 Axis.prototype.pack = function(pos, offsets) {
683 if (this.show) {
684 this.renderer.pack.call(this, pos, offsets);
685 }
686 // these properties should all be available now.
687 if (this._min == null) {
688 this._min = this.min;
689 this._max = this.max;
690 this._tickInterval = this.tickInterval;
691 this._numberTicks = this.numberTicks;
692 this.__ticks = this._ticks;
693 }
694 };
695
696 // reset the axis back to original values if it has been scaled, zoomed, etc.
697 Axis.prototype.reset = function() {
698 this.renderer.reset.call(this);
699 };
700
701 Axis.prototype.resetScale = function(opts) {
702 $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts);
703 this.resetDataBounds();
704 };
705
706 Axis.prototype.resetDataBounds = function() {
707 // Go through all the series attached to this axis and find
708 // the min/max bounds for this axis.
709 var db = this._dataBounds;
710 db.min = null;
711 db.max = null;
712 var l, s, d;
713 // check for when to force min 0 on bar series plots.
714 var doforce = (this.show) ? true : false;
715 for (var i=0; i<this._series.length; i++) {
716 s = this._series[i];
717 d = s._plotData;
718 if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') {
719 d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]];
720 }
721
722 var minyidx = 1, maxyidx = 1;
723
724 if (s._type != null && s._type == 'ohlc') {
725 minyidx = 3;
726 maxyidx = 2;
727 }
728
729 for (var j=0, l=d.length; j<l; j++) {
730 if (this.name == 'xaxis' || this.name == 'x2axis') {
731 if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
732 db.min = d[j][0];
733 }
734 if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
735 db.max = d[j][0];
736 }
737 }
738 else {
739 if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) {
740 db.min = d[j][minyidx];
741 }
742 if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) {
743 db.max = d[j][maxyidx];
744 }
745 }
746 }
747
748 // Hack to not pad out bottom of bar plots unless user has specified a padding.
749 // every series will have a chance to set doforce to false. once it is set to
750 // false, it cannot be reset to true.
751 // If any series attached to axis is not a bar, wont force 0.
752 if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) {
753 doforce = false;
754 }
755
756 else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) {
757 doforce = false;
758 }
759
760 else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) {
761 if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') {
762 if (this._options.pad != null || this._options.padMin != null) {
763 doforce = false;
764 }
765 }
766
767 else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) {
768 if (this._options.pad != null || this._options.padMin != null) {
769 doforce = false;
770 }
771 }
772
773 }
774 }
775
776 if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) {
777 this.padMin = 1.0;
778 this.forceTickAt0 = true;
779 }
780 };
781
782 /**
783 * Class: Legend
784 * Legend object. Cannot be instantiated directly, but created
785 * by the Plot oject. Legend properties can be set or overriden by the
786 * options passed in from the user.
787 */
788 function Legend(options) {
789 $.jqplot.ElemContainer.call(this);
790 // Group: Properties
791
792 // prop: show
793 // Wether to display the legend on the graph.
794 this.show = false;
795 // prop: location
796 // Placement of the legend. one of the compass directions: nw, n, ne, e, se, s, sw, w
797 this.location = 'ne';
798 // prop: labels
799 // Array of labels to use. By default the renderer will look for labels on the series.
800 // Labels specified in this array will override labels specified on the series.
801 this.labels = [];
802 // prop: showLabels
803 // true to show the label text on the legend.
804 this.showLabels = true;
805 // prop: showSwatch
806 // true to show the color swatches on the legend.
807 this.showSwatches = true;
808 // prop: placement
809 // "insideGrid" places legend inside the grid area of the plot.
810 // "outsideGrid" places the legend outside the grid but inside the plot container,
811 // shrinking the grid to accomodate the legend.
812 // "inside" synonym for "insideGrid",
813 // "outside" places the legend ouside the grid area, but does not shrink the grid which
814 // can cause the legend to overflow the plot container.
815 this.placement = "insideGrid";
816 // prop: xoffset
817 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc.
818 // properties or via CSS margin styling of the .jqplot-table-legend class.
819 this.xoffset = 0;
820 // prop: yoffset
821 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc.
822 // properties or via CSS margin styling of the .jqplot-table-legend class.
823 this.yoffset = 0;
824 // prop: border
825 // css spec for the border around the legend box.
826 this.border;
827 // prop: background
828 // css spec for the background of the legend box.
829 this.background;
830 // prop: textColor
831 // css color spec for the legend text.
832 this.textColor;
833 // prop: fontFamily
834 // css font-family spec for the legend text.
835 this.fontFamily;
836 // prop: fontSize
837 // css font-size spec for the legend text.
838 this.fontSize ;
839 // prop: rowSpacing
840 // css padding-top spec for the rows in the legend.
841 this.rowSpacing = '0.5em';
842 // renderer
843 // A class that will create a DOM object for the legend,
844 // see <$.jqplot.TableLegendRenderer>.
845 this.renderer = $.jqplot.TableLegendRenderer;
846 // prop: rendererOptions
847 // renderer specific options passed to the renderer.
848 this.rendererOptions = {};
849 // prop: predraw
850 // Wether to draw the legend before the series or not.
851 // Used with series specific legend renderers for pie, donut, mekko charts, etc.
852 this.preDraw = false;
853 // prop: marginTop
854 // CSS margin for the legend DOM element. This will set an element
855 // CSS style for the margin which will override any style sheet setting.
856 // The default will be taken from the stylesheet.
857 this.marginTop = null;
858 // prop: marginRight
859 // CSS margin for the legend DOM element. This will set an element
860 // CSS style for the margin which will override any style sheet setting.
861 // The default will be taken from the stylesheet.
862 this.marginRight = null;
863 // prop: marginBottom
864 // CSS margin for the legend DOM element. This will set an element
865 // CSS style for the margin which will override any style sheet setting.
866 // The default will be taken from the stylesheet.
867 this.marginBottom = null;
868 // prop: marginLeft
869 // CSS margin for the legend DOM element. This will set an element
870 // CSS style for the margin which will override any style sheet setting.
871 // The default will be taken from the stylesheet.
872 this.marginLeft = null;
873 // prop: escapeHtml
874 // True to escape special characters with their html entity equivalents
875 // in legend text. "<" becomes &lt; and so on, so html tags are not rendered.
876 this.escapeHtml = false;
877 this._series = [];
878
879 $.extend(true, this, options);
880 }
881
882 Legend.prototype = new $.jqplot.ElemContainer();
883 Legend.prototype.constructor = Legend;
884
885 Legend.prototype.setOptions = function(options) {
886 $.extend(true, this, options);
887
888 // Try to emulate deprecated behaviour
889 // if user has specified xoffset or yoffset, copy these to
890 // the margin properties.
891
892 if (this.placement == 'inside') {
893 this.placement = 'insideGrid';
894 }
895
896 if (this.xoffset >0) {
897 if (this.placement == 'insideGrid') {
898 switch (this.location) {
899 case 'nw':
900 case 'w':
901 case 'sw':
902 if (this.marginLeft == null) {
903 this.marginLeft = this.xoffset + 'px';
904 }
905 this.marginRight = '0px';
906 break;
907 case 'ne':
908 case 'e':
909 case 'se':
910 default:
911 if (this.marginRight == null) {
912 this.marginRight = this.xoffset + 'px';
913 }
914 this.marginLeft = '0px';
915 break;
916 }
917 }
918 else if (this.placement == 'outside') {
919 switch (this.location) {
920 case 'nw':
921 case 'w':
922 case 'sw':
923 if (this.marginRight == null) {
924 this.marginRight = this.xoffset + 'px';
925 }
926 this.marginLeft = '0px';
927 break;
928 case 'ne':
929 case 'e':
930 case 'se':
931 default:
932 if (this.marginLeft == null) {
933 this.marginLeft = this.xoffset + 'px';
934 }
935 this.marginRight = '0px';
936 break;
937 }
938 }
939 this.xoffset = 0;
940 }
941
942 if (this.yoffset >0) {
943 if (this.placement == 'outside') {
944 switch (this.location) {
945 case 'sw':
946 case 's':
947 case 'se':
948 if (this.marginTop == null) {
949 this.marginTop = this.yoffset + 'px';
950 }
951 this.marginBottom = '0px';
952 break;
953 case 'ne':
954 case 'n':
955 case 'nw':
956 default:
957 if (this.marginBottom == null) {
958 this.marginBottom = this.yoffset + 'px';
959 }
960 this.marginTop = '0px';
961 break;
962 }
963 }
964 else if (this.placement == 'insideGrid') {
965 switch (this.location) {
966 case 'sw':
967 case 's':
968 case 'se':
969 if (this.marginBottom == null) {
970 this.marginBottom = this.yoffset + 'px';
971 }
972 this.marginTop = '0px';
973 break;
974 case 'ne':
975 case 'n':
976 case 'nw':
977 default:
978 if (this.marginTop == null) {
979 this.marginTop = this.yoffset + 'px';
980 }
981 this.marginBottom = '0px';
982 break;
983 }
984 }
985 this.yoffset = 0;
986 }
987
988 // TO-DO:
989 // Handle case where offsets are < 0.
990 //
991 };
992
993 Legend.prototype.init = function() {
994 this.renderer = new this.renderer();
995 this.renderer.init.call(this, this.rendererOptions);
996 };
997
998 Legend.prototype.draw = function(offsets) {
999 for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){
1000 $.jqplot.preDrawLegendHooks[i].call(this, offsets);
1001 }
1002 return this.renderer.draw.call(this, offsets);
1003 };
1004
1005 Legend.prototype.pack = function(offsets) {
1006 this.renderer.pack.call(this, offsets);
1007 };
1008
1009 /**
1010 * Class: Title
1011 * Plot Title object. Cannot be instantiated directly, but created
1012 * by the Plot oject. Title properties can be set or overriden by the
1013 * options passed in from the user.
1014 *
1015 * Parameters:
1016 * text - text of the title.
1017 */
1018 function Title(text) {
1019 $.jqplot.ElemContainer.call(this);
1020 // Group: Properties
1021
1022 // prop: text
1023 // text of the title;
1024 this.text = text;
1025 // prop: show
1026 // wether or not to show the title
1027 this.show = true;
1028 // prop: fontFamily
1029 // css font-family spec for the text.
1030 this.fontFamily;
1031 // prop: fontSize
1032 // css font-size spec for the text.
1033 this.fontSize ;
1034 // prop: textAlign
1035 // css text-align spec for the text.
1036 this.textAlign;
1037 // prop: textColor
1038 // css color spec for the text.
1039 this.textColor;
1040 // prop: renderer
1041 // A class for creating a DOM element for the title,
1042 // see <$.jqplot.DivTitleRenderer>.
1043 this.renderer = $.jqplot.DivTitleRenderer;
1044 // prop: rendererOptions
1045 // renderer specific options passed to the renderer.
1046 this.rendererOptions = {};
1047 // prop: escapeHtml
1048 // True to escape special characters with their html entity equivalents
1049 // in title text. "<" becomes &lt; and so on, so html tags are not rendered.
1050 this.escapeHtml = false;
1051 }
1052
1053 Title.prototype = new $.jqplot.ElemContainer();
1054 Title.prototype.constructor = Title;
1055
1056 Title.prototype.init = function() {
1057 this.renderer = new this.renderer();
1058 this.renderer.init.call(this, this.rendererOptions);
1059 };
1060
1061 Title.prototype.draw = function(width) {
1062 return this.renderer.draw.call(this, width);
1063 };
1064
1065 Title.prototype.pack = function() {
1066 this.renderer.pack.call(this);
1067 };
1068
1069
1070 /**
1071 * Class: Series
1072 * An individual data series object. Cannot be instantiated directly, but created
1073 * by the Plot oject. Series properties can be set or overriden by the
1074 * options passed in from the user.
1075 */
1076 function Series() {
1077 $.jqplot.ElemContainer.call(this);
1078 // Group: Properties
1079 // Properties will be assigned from a series array at the top level of the
1080 // options. If you had two series and wanted to change the color and line
1081 // width of the first and set the second to use the secondary y axis with
1082 // no shadow and supply custom labels for each:
1083 // > {
1084 // > series:[
1085 // > {color: '#ff4466', lineWidth: 5, label:'good line'},
1086 // > {yaxis: 'y2axis', shadow: false, label:'bad line'}
1087 // > ]
1088 // > }
1089
1090 // prop: show
1091 // wether or not to draw the series.
1092 this.show = true;
1093 // prop: xaxis
1094 // which x axis to use with this series, either 'xaxis' or 'x2axis'.
1095 this.xaxis = 'xaxis';
1096 this._xaxis;
1097 // prop: yaxis
1098 // which y axis to use with this series, either 'yaxis' or 'y2axis'.
1099 this.yaxis = 'yaxis';
1100 this._yaxis;
1101 this.gridBorderWidth = 2.0;
1102 // prop: renderer
1103 // A class of a renderer which will draw the series,
1104 // see <$.jqplot.LineRenderer>.
1105 this.renderer = $.jqplot.LineRenderer;
1106 // prop: rendererOptions
1107 // Options to pass on to the renderer.
1108 this.rendererOptions = {};
1109 this.data = [];
1110 this.gridData = [];
1111 // prop: label
1112 // Line label to use in the legend.
1113 this.label = '';
1114 // prop: showLabel
1115 // true to show label for this series in the legend.
1116 this.showLabel = true;
1117 // prop: color
1118 // css color spec for the series
1119 this.color;
1120 // prop: negativeColor
1121 // css color spec used for filled (area) plots that are filled to zero and
1122 // the "useNegativeColors" option is true.
1123 this.negativeColor;
1124 // prop: lineWidth
1125 // width of the line in pixels. May have different meanings depending on renderer.
1126 this.lineWidth = 2.5;
1127 // prop: lineJoin
1128 // Canvas lineJoin style between segments of series.
1129 this.lineJoin = 'round';
1130 // prop: lineCap
1131 // Canvas lineCap style at ends of line.
1132 this.lineCap = 'round';
1133 // prop: linePattern
1134 // line pattern 'dashed', 'dotted', 'solid', some combination
1135 // of '-' and '.' characters such as '.-.' or a numerical array like
1136 // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line,
1137 // [1, 10, 20, 10] to draw a dot-dash line, and so on.
1138 this.linePattern = 'solid';
1139 this.shadow = true;
1140 // prop: shadowAngle
1141 // Shadow angle in degrees
1142 this.shadowAngle = 45;
1143 // prop: shadowOffset
1144 // Shadow offset from line in pixels
1145 this.shadowOffset = 1.25;
1146 // prop: shadowDepth
1147 // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
1148 this.shadowDepth = 3;
1149 // prop: shadowAlpha
1150 // Alpha channel transparency of shadow. 0 = transparent.
1151 this.shadowAlpha = '0.1';
1152 // prop: breakOnNull
1153 // Wether line segments should be be broken at null value.
1154 // False will join point on either side of line.
1155 this.breakOnNull = false;
1156 // prop: markerRenderer
1157 // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points,
1158 // see <$.jqplot.MarkerRenderer>.
1159 this.markerRenderer = $.jqplot.MarkerRenderer;
1160 // prop: markerOptions
1161 // renderer specific options to pass to the markerRenderer,
1162 // see <$.jqplot.MarkerRenderer>.
1163 this.markerOptions = {};
1164 // prop: showLine
1165 // wether to actually draw the line or not. Series will still be renderered, even if no line is drawn.
1166 this.showLine = true;
1167 // prop: showMarker
1168 // wether or not to show the markers at the data points.
1169 this.showMarker = true;
1170 // prop: index
1171 // 0 based index of this series in the plot series array.
1172 this.index;
1173 // prop: fill
1174 // true or false, wether to fill under lines or in bars.
1175 // May not be implemented in all renderers.
1176 this.fill = false;
1177 // prop: fillColor
1178 // CSS color spec to use for fill under line. Defaults to line color.
1179 this.fillColor;
1180 // prop: fillAlpha
1181 // Alpha transparency to apply to the fill under the line.
1182 // Use this to adjust alpha separate from fill color.
1183 this.fillAlpha;
1184 // prop: fillAndStroke
1185 // If true will stroke the line (with color this.color) as well as fill under it.
1186 // Applies only when fill is true.
1187 this.fillAndStroke = false;
1188 // prop: disableStack
1189 // true to not stack this series with other series in the plot.
1190 // To render properly, non-stacked series must come after any stacked series
1191 // in the plot's data series array. So, the plot's data series array would look like:
1192 // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...]
1193 // disableStack will put a gap in the stacking order of series, and subsequent
1194 // stacked series will not fill down through the non-stacked series and will
1195 // most likely not stack properly on top of the non-stacked series.
1196 this.disableStack = false;
1197 // _stack is set by the Plot if the plot is a stacked chart.
1198 // will stack lines or bars on top of one another to build a "mountain" style chart.
1199 // May not be implemented in all renderers.
1200 this._stack = false;
1201 // prop: neighborThreshold
1202 // how close or far (in pixels) the cursor must be from a point marker to detect the point.
1203 this.neighborThreshold = 4;
1204 // prop: fillToZero
1205 // true will force bar and filled series to fill toward zero on the fill Axis.
1206 this.fillToZero = false;
1207 // prop: fillToValue
1208 // fill a filled series to this value on the fill axis.
1209 // Works in conjunction with fillToZero, so that must be true.
1210 this.fillToValue = 0;
1211 // prop: fillAxis
1212 // Either 'x' or 'y'. Which axis to fill the line toward if fillToZero is true.
1213 // 'y' means fill up/down to 0 on the y axis for this series.
1214 this.fillAxis = 'y';
1215 // prop: useNegativeColors
1216 // true to color negative values differently in filled and bar charts.
1217 this.useNegativeColors = true;
1218 this._stackData = [];
1219 // _plotData accounts for stacking. If plots not stacked, _plotData and data are same. If
1220 // stacked, _plotData is accumulation of stacking data.
1221 this._plotData = [];
1222 // _plotValues hold the individual x and y values that will be plotted for this series.
1223 this._plotValues = {x:[], y:[]};
1224 // statistics about the intervals between data points. Used for auto scaling.
1225 this._intervals = {x:{}, y:{}};
1226 // data from the previous series, for stacked charts.
1227 this._prevPlotData = [];
1228 this._prevGridData = [];
1229 this._stackAxis = 'y';
1230 this._primaryAxis = '_xaxis';
1231 // give each series a canvas to draw on. This should allow for redrawing speedups.
1232 this.canvas = new $.jqplot.GenericCanvas();
1233 this.shadowCanvas = new $.jqplot.GenericCanvas();
1234 this.plugins = {};
1235 // sum of y values in this series.
1236 this._sumy = 0;
1237 this._sumx = 0;
1238 this._type = '';
1239 }
1240
1241 Series.prototype = new $.jqplot.ElemContainer();
1242 Series.prototype.constructor = Series;
1243
1244 Series.prototype.init = function(index, gridbw, plot) {
1245 // weed out any null values in the data.
1246 this.index = index;
1247 this.gridBorderWidth = gridbw;
1248 var d = this.data;
1249 var temp = [], i;
1250 for (i=0; i<d.length; i++) {
1251 if (! this.breakOnNull) {
1252 if (d[i] == null || d[i][0] == null || d[i][1] == null) {
1253 continue;
1254 }
1255 else {
1256 temp.push(d[i]);
1257 }
1258 }
1259 else {
1260 // TODO: figure out what to do with null values
1261 // probably involve keeping nulls in data array
1262 // and then updating renderers to break line
1263 // when it hits null value.
1264 // For now, just keep value.
1265 temp.push(d[i]);
1266 }
1267 }
1268 this.data = temp;
1269
1270 // parse the renderer options and apply default colors if not provided
1271 if (!this.color && this.show) {
1272 this.color = plot.colorGenerator.get(this.index);
1273 }
1274 if (!this.negativeColor && this.show) {
1275 this.negativeColor = plot.negativeColorGenerator.get(this.index);
1276 }
1277
1278
1279 if (!this.fillColor) {
1280 this.fillColor = this.color;
1281 }
1282 if (this.fillAlpha) {
1283 var comp = $.jqplot.normalize2rgb(this.fillColor);
1284 var comp = $.jqplot.getColorComponents(comp);
1285 this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')';
1286 }
1287 this.renderer = new this.renderer();
1288 this.renderer.init.call(this, this.rendererOptions, plot);
1289 this.markerRenderer = new this.markerRenderer();
1290 if (!this.markerOptions.color) {
1291 this.markerOptions.color = this.color;
1292 }
1293 if (this.markerOptions.show == null) {
1294 this.markerOptions.show = this.showMarker;
1295 }
1296 this.showMarker = this.markerOptions.show;
1297 // the markerRenderer is called within it's own scaope, don't want to overwrite series options!!
1298 this.markerRenderer.init(this.markerOptions);
1299 };
1300
1301 // data - optional data point array to draw using this series renderer
1302 // gridData - optional grid data point array to draw using this series renderer
1303 // stackData - array of cumulative data for stacked plots.
1304 Series.prototype.draw = function(sctx, opts, plot) {
1305 var options = (opts == undefined) ? {} : opts;
1306 sctx = (sctx == undefined) ? this.canvas._ctx : sctx;
1307
1308 var j, data, gridData;
1309
1310 // hooks get called even if series not shown
1311 // we don't clear canvas here, it would wipe out all other series as well.
1312 for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) {
1313 $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options);
1314 }
1315 if (this.show) {
1316 this.renderer.setGridData.call(this, plot);
1317 if (!options.preventJqPlotSeriesDrawTrigger) {
1318 $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]);
1319 }
1320 data = [];
1321 if (options.data) {
1322 data = options.data;
1323 }
1324 else if (!this._stack) {
1325 data = this.data;
1326 }
1327 else {
1328 data = this._plotData;
1329 }
1330 gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
1331
1332 if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) {
1333 gridData = this.renderer._smoothedData;
1334 }
1335
1336 this.renderer.draw.call(this, sctx, gridData, options, plot);
1337 }
1338
1339 for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
1340 $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options);
1341 }
1342
1343 sctx = opts = plot = j = data = gridData = null;
1344 };
1345
1346 Series.prototype.drawShadow = function(sctx, opts, plot) {
1347 var options = (opts == undefined) ? {} : opts;
1348 sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx;
1349
1350 var j, data, gridData;
1351
1352 // hooks get called even if series not shown
1353 // we don't clear canvas here, it would wipe out all other series as well.
1354 for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) {
1355 $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options);
1356 }
1357 if (this.shadow) {
1358 this.renderer.setGridData.call(this, plot);
1359
1360 data = [];
1361 if (options.data) {
1362 data = options.data;
1363 }
1364 else if (!this._stack) {
1365 data = this.data;
1366 }
1367 else {
1368 data = this._plotData;
1369 }
1370 gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
1371
1372 this.renderer.drawShadow.call(this, sctx, gridData, options);
1373 }
1374
1375 for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) {
1376 $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options);
1377 }
1378
1379 sctx = opts = plot = j = data = gridData = null;
1380
1381 };
1382
1383 // toggles series display on plot, e.g. show/hide series
1384 Series.prototype.toggleDisplay = function(ev) {
1385 var s, speed;
1386 if (ev.data.series) {
1387 s = ev.data.series;
1388 }
1389 else {
1390 s = this;
1391 }
1392 if (ev.data.speed) {
1393 speed = ev.data.speed;
1394 }
1395 if (speed) {
1396 if (s.canvas._elem.is(':hidden')) {
1397 s.canvas._elem.removeClass('jqplot-series-hidden');
1398 if (s.shadowCanvas._elem) {
1399 s.shadowCanvas._elem.fadeIn(speed);
1400 }
1401 s.canvas._elem.fadeIn(speed);
1402 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed);
1403 }
1404 else {
1405 s.canvas._elem.addClass('jqplot-series-hidden');
1406 if (s.shadowCanvas._elem) {
1407 s.shadowCanvas._elem.fadeOut(speed);
1408 }
1409 s.canvas._elem.fadeOut(speed);
1410 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed);
1411 }
1412 }
1413 else {
1414 if (s.canvas._elem.is(':hidden')) {
1415 s.canvas._elem.removeClass('jqplot-series-hidden');
1416 if (s.shadowCanvas._elem) {
1417 s.shadowCanvas._elem.show();
1418 }
1419 s.canvas._elem.show();
1420 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show();
1421 }
1422 else {
1423 s.canvas._elem.addClass('jqplot-series-hidden');
1424 if (s.shadowCanvas._elem) {
1425 s.shadowCanvas._elem.hide();
1426 }
1427 s.canvas._elem.hide();
1428 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide();
1429 }
1430 }
1431 };
1432
1433
1434
1435 /**
1436 * Class: Grid
1437 *
1438 * Object representing the grid on which the plot is drawn. The grid in this
1439 * context is the area bounded by the axes, the area which will contain the series.
1440 * Note, the series are drawn on their own canvas.
1441 * The Grid object cannot be instantiated directly, but is created by the Plot oject.
1442 * Grid properties can be set or overriden by the options passed in from the user.
1443 */
1444 function Grid() {
1445 $.jqplot.ElemContainer.call(this);
1446 // Group: Properties
1447
1448 // prop: drawGridlines
1449 // wether to draw the gridlines on the plot.
1450 this.drawGridlines = true;
1451 // prop: gridLineColor
1452 // color of the grid lines.
1453 this.gridLineColor = '#cccccc';
1454 // prop: gridLineWidth
1455 // width of the grid lines.
1456 this.gridLineWidth = 1.0;
1457 // prop: background
1458 // css spec for the background color.
1459 this.background = '#fffdf6';
1460 // prop: borderColor
1461 // css spec for the color of the grid border.
1462 this.borderColor = '#999999';
1463 // prop: borderWidth
1464 // width of the border in pixels.
1465 this.borderWidth = 2.0;
1466 // prop: drawBorder
1467 // True to draw border around grid.
1468 this.drawBorder = true;
1469 // prop: shadow
1470 // wether to show a shadow behind the grid.
1471 this.shadow = true;
1472 // prop: shadowAngle
1473 // shadow angle in degrees
1474 this.shadowAngle = 45;
1475 // prop: shadowOffset
1476 // Offset of each shadow stroke from the border in pixels
1477 this.shadowOffset = 1.5;
1478 // prop: shadowWidth
1479 // width of the stoke for the shadow
1480 this.shadowWidth = 3;
1481 // prop: shadowDepth
1482 // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
1483 this.shadowDepth = 3;
1484 // prop: shadowColor
1485 // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form
1486 this.shadowColor = null;
1487 // prop: shadowAlpha
1488 // Alpha channel transparency of shadow. 0 = transparent.
1489 this.shadowAlpha = '0.07';
1490 this._left;
1491 this._top;
1492 this._right;
1493 this._bottom;
1494 this._width;
1495 this._height;
1496 this._axes = [];
1497 // prop: renderer
1498 // Instance of a renderer which will actually render the grid,
1499 // see <$.jqplot.CanvasGridRenderer>.
1500 this.renderer = $.jqplot.CanvasGridRenderer;
1501 // prop: rendererOptions
1502 // Options to pass on to the renderer,
1503 // see <$.jqplot.CanvasGridRenderer>.
1504 this.rendererOptions = {};
1505 this._offsets = {top:null, bottom:null, left:null, right:null};
1506 }
1507
1508 Grid.prototype = new $.jqplot.ElemContainer();
1509 Grid.prototype.constructor = Grid;
1510
1511 Grid.prototype.init = function() {
1512 this.renderer = new this.renderer();
1513 this.renderer.init.call(this, this.rendererOptions);
1514 };
1515
1516 Grid.prototype.createElement = function(offsets,plot) {
1517 this._offsets = offsets;
1518 return this.renderer.createElement.call(this, plot);
1519 };
1520
1521 Grid.prototype.draw = function() {
1522 this.renderer.draw.call(this);
1523 };
1524
1525 $.jqplot.GenericCanvas = function() {
1526 $.jqplot.ElemContainer.call(this);
1527 this._ctx;
1528 };
1529
1530 $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer();
1531 $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas;
1532
1533 $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) {
1534 this._offsets = offsets;
1535 var klass = 'jqplot';
1536 if (clss != undefined) {
1537 klass = clss;
1538 }
1539 var elem;
1540
1541 elem = plot.canvasManager.getCanvas();
1542
1543 // if new plotDimensions supplied, use them.
1544 if (plotDimensions != null) {
1545 this._plotDimensions = plotDimensions;
1546 }
1547
1548 elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right;
1549 elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom;
1550 this._elem = $(elem);
1551 this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top });
1552
1553 this._elem.addClass(klass);
1554
1555 elem = plot.canvasManager.initCanvas(elem);
1556
1557 elem = null;
1558 return this._elem;
1559 };
1560
1561 $.jqplot.GenericCanvas.prototype.setContext = function() {
1562 this._ctx = this._elem.get(0).getContext("2d");
1563 return this._ctx;
1564 };
1565
1566 // Memory Leaks patch
1567 $.jqplot.GenericCanvas.prototype.resetCanvas = function() {
1568 if (this._elem) {
1569 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
1570 window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
1571 }
1572
1573 //this._elem.remove();
1574 this._elem.emptyForce();
1575 }
1576
1577 this._ctx = null;
1578 };
1579
1580 $.jqplot.HooksManager = function () {
1581 this.hooks =[];
1582 };
1583
1584 $.jqplot.HooksManager.prototype.addOnce = function(fn) {
1585 var havehook = false, i;
1586 for (i=0; i<this.hooks.length; i++) {
1587 if (this.hooks[i][0] == fn) {
1588 havehook = true;
1589 }
1590 }
1591 if (!havehook) {
1592 this.hooks.push(fn);
1593 }
1594 };
1595
1596 $.jqplot.HooksManager.prototype.add = function(fn) {
1597 this.hooks.push(fn);
1598 };
1599
1600 $.jqplot.EventListenerManager = function () {
1601 this.hooks =[];
1602 };
1603
1604 $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) {
1605 var havehook = false, h, i;
1606 for (i=0; i<this.hooks.length; i++) {
1607 h = this.hooks[i];
1608 if (h[0] == ev && h[1] == fn) {
1609 havehook = true;
1610 }
1611 }
1612 if (!havehook) {
1613 this.hooks.push([ev, fn]);
1614 }
1615 };
1616
1617 $.jqplot.EventListenerManager.prototype.add = function(ev, fn) {
1618 this.hooks.push([ev, fn]);
1619 };
1620
1621
1622 var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
1623
1624 /**
1625 * Class: jqPlot
1626 * Plot object returned by call to $.jqplot. Handles parsing user options,
1627 * creating sub objects (Axes, legend, title, series) and rendering the plot.
1628 */
1629 function jqPlot() {
1630 // Group: Properties
1631 // These properties are specified at the top of the options object
1632 // like so:
1633 // > {
1634 // > axesDefaults:{min:0},
1635 // > series:[{color:'#6633dd'}],
1636 // > title: 'A Plot'
1637 // > }
1638 //
1639 // prop: data
1640 // user's data. Data should *NOT* be specified in the options object,
1641 // but be passed in as the second argument to the $.jqplot() function.
1642 // The data property is described here soley for reference.
1643 // The data should be in the form of an array of 2D or 1D arrays like
1644 // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ].
1645 this.data = [];
1646 // prop: dataRenderer
1647 // A callable which can be used to preprocess data passed into the plot.
1648 // Will be called with 2 arguments, the plot data and a reference to the plot.
1649 this.dataRenderer;
1650 // prop: dataRendererOptions
1651 // Options that will be passed to the dataRenderer.
1652 // Can be of any type.
1653 this.dataRendererOptions;
1654 // prop: noDataIndicator
1655 // Options to set up a mock plot with a data loading indicator if no data is specified.
1656 this.noDataIndicator = {
1657 show: false,
1658 indicator: 'Loading Data...',
1659 axes: {
1660 xaxis: {
1661 min: 0,
1662 max: 10,
1663 tickInterval: 2,
1664 show: true
1665 },
1666 yaxis: {
1667 min: 0,
1668 max: 12,
1669 tickInterval: 3,
1670 show: true
1671 }
1672 }
1673 };
1674 // The id of the dom element to render the plot into
1675 this.targetId = null;
1676 // the jquery object for the dom target.
1677 this.target = null;
1678 this.defaults = {
1679 // prop: axesDefaults
1680 // default options that will be applied to all axes.
1681 // see <Axis> for axes options.
1682 axesDefaults: {},
1683 axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}},
1684 // prop: seriesDefaults
1685 // default options that will be applied to all series.
1686 // see <Series> for series options.
1687 seriesDefaults: {},
1688 series:[]
1689 };
1690 // prop: series
1691 // Array of series object options.
1692 // see <Series> for series specific options.
1693 this.series = [];
1694 // prop: axes
1695 // up to 4 axes are supported, each with it's own options,
1696 // See <Axis> for axis specific options.
1697 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')};
1698 // prop: grid
1699 // See <Grid> for grid specific options.
1700 this.grid = new Grid();
1701 // prop: legend
1702 // see <$.jqplot.TableLegendRenderer>
1703 this.legend = new Legend();
1704 this.baseCanvas = new $.jqplot.GenericCanvas();
1705 // array of series indicies. Keep track of order
1706 // which series canvases are displayed, lowest
1707 // to highest, back to front.
1708 this.seriesStack = [];
1709 this.previousSeriesStack = [];
1710 this.eventCanvas = new $.jqplot.GenericCanvas();
1711 this._width = null;
1712 this._height = null;
1713 this._plotDimensions = {height:null, width:null};
1714 this._gridPadding = {top:null, right:null, bottom:null, left:null};
1715 this._defaultGridPadding = {top:10, right:10, bottom:23, left:10};
1716 // a shortcut for axis syncTicks options. Not implemented yet.
1717 this.syncXTicks = true;
1718 // a shortcut for axis syncTicks options. Not implemented yet.
1719 this.syncYTicks = true;
1720 // prop: seriesColors
1721 // Ann array of CSS color specifications that will be applied, in order,
1722 // to the series in the plot. Colors will wrap around so, if their
1723 // are more series than colors, colors will be reused starting at the
1724 // beginning. For pie charts, this specifies the colors of the slices.
1725 this.seriesColors = $.jqplot.config.defaultColors;
1726 this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors;
1727 // prop: sortData
1728 // false to not sort the data passed in by the user.
1729 // Many bar, stakced and other graphs as well as many plugins depend on
1730 // having sorted data.
1731 this.sortData = true;
1732 var seriesColorsIndex = 0;
1733 // prop textColor
1734 // css spec for the css color attribute. Default for the entire plot.
1735 this.textColor;
1736 // prop; fontFamily
1737 // css spec for the font-family attribute. Default for the entire plot.
1738 this.fontFamily;
1739 // prop: fontSize
1740 // css spec for the font-size attribute. Default for the entire plot.
1741 this.fontSize;
1742 // prop: title
1743 // Title object. See <Title> for specific options. As a shortcut, you
1744 // can specify the title option as just a string like: title: 'My Plot'
1745 // and this will create a new title object with the specified text.
1746 this.title = new Title();
1747 // container to hold all of the merged options. Convienence for plugins.
1748 this.options = {};
1749 // prop: stackSeries
1750 // true or false, creates a stack or "mountain" plot.
1751 // Not all series renderers may implement this option.
1752 this.stackSeries = false;
1753 // prop: defaultAxisStart
1754 // 1-D data series are internally converted into 2-D [x,y] data point arrays
1755 // by jqPlot. This is the default starting value for the missing x or y value.
1756 // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...])
1757 // starting at this value.
1758 this.defaultAxisStart = 1;
1759 // array to hold the cumulative stacked series data.
1760 // used to ajust the individual series data, which won't have access to other
1761 // series data.
1762 this._stackData = [];
1763 // array that holds the data to be plotted. This will be the series data
1764 // merged with the the appropriate data from _stackData according to the stackAxis.
1765 this._plotData = [];
1766 // Namespece to hold plugins. Generally non-renderer plugins add themselves to here.
1767 this.plugins = {};
1768 // Count how many times the draw method has been called while the plot is visible.
1769 // Mostly used to test if plot has never been dran (=0), has been successfully drawn
1770 // into a visible container once (=1) or draw more than once into a visible container.
1771 // Can use this in tests to see if plot has been visibly drawn at least one time.
1772 // After plot has been visibly drawn once, it generally doesn't need redrawn if its
1773 // container is hidden and shown.
1774 this._drawCount = 0;
1775 // this.doCustomEventBinding = true;
1776 // prop: drawIfHidden
1777 // True to execute the draw method even if the plot target is hidden.
1778 // Generally, this should be false. Most plot elements will not be sized/
1779 // positioned correclty if renderered into a hidden container. To render into
1780 // a hidden container, call the replot method when the container is shown.
1781 this.drawIfHidden = false;
1782 // true to intercept right click events and fire a 'jqplotRightClick' event.
1783 // this will also block the context menu.
1784 this.captureRightClick = false;
1785 this.themeEngine = new $.jqplot.ThemeEngine();
1786 // sum of y values for all series in plot.
1787 // used in mekko chart.
1788 this._sumy = 0;
1789 this._sumx = 0;
1790 this.preInitHooks = new $.jqplot.HooksManager();
1791 this.postInitHooks = new $.jqplot.HooksManager();
1792 this.preParseOptionsHooks = new $.jqplot.HooksManager();
1793 this.postParseOptionsHooks = new $.jqplot.HooksManager();
1794 this.preDrawHooks = new $.jqplot.HooksManager();
1795 this.postDrawHooks = new $.jqplot.HooksManager();
1796 this.preDrawSeriesHooks = new $.jqplot.HooksManager();
1797 this.postDrawSeriesHooks = new $.jqplot.HooksManager();
1798 this.preDrawLegendHooks = new $.jqplot.HooksManager();
1799 this.addLegendRowHooks = new $.jqplot.HooksManager();
1800 this.preSeriesInitHooks = new $.jqplot.HooksManager();
1801 this.postSeriesInitHooks = new $.jqplot.HooksManager();
1802 this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager();
1803 this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager();
1804 this.eventListenerHooks = new $.jqplot.EventListenerManager();
1805 this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager();
1806 this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager();
1807
1808 this.colorGenerator = new $.jqplot.ColorGenerator();
1809 this.negativeColorGenerator = new $.jqplot.ColorGenerator();
1810
1811 this.canvasManager = new $.jqplot.CanvasManager();
1812
1813 // Group: methods
1814 //
1815 // method: init
1816 // sets the plot target, checks data and applies user
1817 // options to plot.
1818 this.init = function(target, data, options) {
1819 options = options || {};
1820 for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
1821 $.jqplot.preInitHooks[i].call(this, target, data, options);
1822 }
1823
1824 for (var i=0; i<this.preInitHooks.hooks.length; i++) {
1825 this.preInitHooks.hooks[i].call(this, target, data, options);
1826 }
1827
1828 this.targetId = '#'+target;
1829 this.target = $('#'+target);
1830 // remove any error class that may be stuck on target.
1831 this.target.removeClass('jqplot-error');
1832 if (!this.target.get(0)) {
1833 throw "No plot target specified";
1834 }
1835
1836 // make sure the target is positioned by some means and set css
1837 if (this.target.css('position') == 'static') {
1838 this.target.css('position', 'relative');
1839 }
1840 if (!this.target.hasClass('jqplot-target')) {
1841 this.target.addClass('jqplot-target');
1842 }
1843
1844 // if no height or width specified, use a default.
1845 if (!this.target.height()) {
1846 var h;
1847 if (options && options.height) {
1848 h = parseInt(options.height, 10);
1849 }
1850 else if (this.target.attr('data-height')) {
1851 h = parseInt(this.target.attr('data-height'), 10);
1852 }
1853 else {
1854 h = parseInt($.jqplot.config.defaultHeight, 10);
1855 }
1856 this._height = h;
1857 this.target.css('height', h+'px');
1858 }
1859 else {
1860 this._height = h = this.target.height();
1861 }
1862 if (!this.target.width()) {
1863 var w;
1864 if (options && options.width) {
1865 w = parseInt(options.width, 10);
1866 }
1867 else if (this.target.attr('data-width')) {
1868 w = parseInt(this.target.attr('data-width'), 10);
1869 }
1870 else {
1871 w = parseInt($.jqplot.config.defaultWidth, 10);
1872 }
1873 this._width = w;
1874 this.target.css('width', w+'px');
1875 }
1876 else {
1877 this._width = w = this.target.width();
1878 }
1879
1880 this._plotDimensions.height = this._height;
1881 this._plotDimensions.width = this._width;
1882 this.grid._plotDimensions = this._plotDimensions;
1883 this.title._plotDimensions = this._plotDimensions;
1884 this.baseCanvas._plotDimensions = this._plotDimensions;
1885 this.eventCanvas._plotDimensions = this._plotDimensions;
1886 this.legend._plotDimensions = this._plotDimensions;
1887 if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
1888 throw "Canvas dimension not set";
1889 }
1890
1891 if (options.dataRenderer && jQuery.isFunction(options.dataRenderer)) {
1892 if (options.dataRendererOptions) {
1893 this.dataRendererOptions = options.dataRendererOptions;
1894 }
1895 this.dataRenderer = options.dataRenderer;
1896 data = this.dataRenderer(data, this, this.dataRendererOptions);
1897 }
1898
1899 if (options.noDataIndicator && jQuery.isPlainObject(options.noDataIndicator)) {
1900 $.extend(true, this.noDataIndicator, options.noDataIndicator);
1901 }
1902
1903 if (data == null || jQuery.isArray(data) == false || data.length == 0 || jQuery.isArray(data[0]) == false || data[0].length == 0) {
1904
1905 if (this.noDataIndicator.show == false) {
1906 throw{
1907 name: "DataError",
1908 message: "No data to plot."
1909 };
1910 }
1911
1912 else {
1913 // have to be descructive here in order for plot to not try and render series.
1914 // This means that $.jqplot() will have to be called again when there is data.
1915 //delete options.series;
1916
1917 for (var ax in this.noDataIndicator.axes) {
1918 for (var prop in this.noDataIndicator.axes[ax]) {
1919 this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop];
1920 }
1921 }
1922
1923 this.postDrawHooks.add(function() {
1924 var eh = this.eventCanvas.getHeight();
1925 var ew = this.eventCanvas.getWidth();
1926 var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>');
1927 this.target.append(temp);
1928 temp.height(eh);
1929 temp.width(ew);
1930 temp.css('top', this.eventCanvas._offsets.top);
1931 temp.css('left', this.eventCanvas._offsets.left);
1932
1933 var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>');
1934 temp.append(temp2);
1935 temp2.html(this.noDataIndicator.indicator);
1936 var th = temp2.height();
1937 var tw = temp2.width();
1938 temp2.height(th);
1939 temp2.width(tw);
1940 temp2.css('top', (eh - th)/2 + 'px');
1941 });
1942
1943 }
1944 }
1945
1946 this.data = data;
1947
1948 this.parseOptions(options);
1949
1950 if (this.textColor) {
1951 this.target.css('color', this.textColor);
1952 }
1953 if (this.fontFamily) {
1954 this.target.css('font-family', this.fontFamily);
1955 }
1956 if (this.fontSize) {
1957 this.target.css('font-size', this.fontSize);
1958 }
1959
1960 this.title.init();
1961 this.legend.init();
1962 this._sumy = 0;
1963 this._sumx = 0;
1964 for (var i=0; i<this.series.length; i++) {
1965 // set default stacking order for series canvases
1966 this.seriesStack.push(i);
1967 this.previousSeriesStack.push(i);
1968 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
1969 this.series[i].canvas._plotDimensions = this._plotDimensions;
1970 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
1971 $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this);
1972 }
1973 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
1974 this.preSeriesInitHooks.hooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this);
1975 }
1976 this.populatePlotData(this.series[i], i);
1977 this.series[i]._plotDimensions = this._plotDimensions;
1978 this.series[i].init(i, this.grid.borderWidth, this);
1979 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
1980 $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this);
1981 }
1982 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
1983 this.postSeriesInitHooks.hooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this);
1984 }
1985 this._sumy += this.series[i]._sumy;
1986 this._sumx += this.series[i]._sumx;
1987 }
1988
1989 for (var i=0; i<12; i++) {
1990 name = _axisNames[i];
1991 this.axes[name]._plotDimensions = this._plotDimensions;
1992 this.axes[name].init();
1993 if (this.axes[name].borderColor == null) {
1994 if (name.charAt(0) !== 'x' && this.axes[name].useSeriesColor === true && this.axes[name].show) {
1995 this.axes[name].borderColor = this.axes[name]._series[0].color;
1996 }
1997 else {
1998 this.axes[name].borderColor = this.grid.borderColor;
1999 }
2000 }
2001 }
2002
2003 if (this.sortData) {
2004 sortData(this.series);
2005 }
2006 this.grid.init();
2007 this.grid._axes = this.axes;
2008
2009 this.legend._series = this.series;
2010
2011 for (var i=0; i<$.jqplot.postInitHooks.length; i++) {
2012 $.jqplot.postInitHooks[i].call(this, target, data, options);
2013 }
2014
2015 for (var i=0; i<this.postInitHooks.hooks.length; i++) {
2016 this.postInitHooks.hooks[i].call(this, target, data, options);
2017 }
2018 };
2019
2020 // method: resetAxesScale
2021 // Reset the specified axes min, max, numberTicks and tickInterval properties to null
2022 // or reset these properties on all axes if no list of axes is provided.
2023 //
2024 // Parameters:
2025 // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset.
2026 this.resetAxesScale = function(axes, options) {
2027 var opts = options || {};
2028 var ax = axes || this.axes;
2029 if (ax === true) {
2030 ax = this.axes;
2031 }
2032 if (jQuery.isArray(ax)) {
2033 for (var i = 0; i < ax.length; i++) {
2034 this.axes[ax[i]].resetScale(opts[ax[i]]);
2035 }
2036 }
2037 else if (typeof(ax) === 'object') {
2038 for (var name in ax) {
2039 this.axes[name].resetScale(opts[name]);
2040 }
2041 }
2042 };
2043 // method: reInitialize
2044 // reinitialize plot for replotting.
2045 // not called directly.
2046 this.reInitialize = function () {
2047 // Plot should be visible and have a height and width.
2048 // If plot doesn't have height and width for some
2049 // reason, set it by other means. Plot must not have
2050 // a display:none attribute, however.
2051
2052 //
2053 // Wont have options here
2054 /*
2055 if (!this.target.height()) {
2056 var h;
2057 if (options && options.height) {
2058 h = parseInt(options.height, 10);
2059 }
2060 else if (this.target.attr('data-height')) {
2061 h = parseInt(this.target.attr('data-height'), 10);
2062 }
2063 else {
2064 h = parseInt($.jqplot.config.defaultHeight, 10);
2065 }
2066 this._height = h;
2067 this.target.css('height', h+'px');
2068 }
2069 else {
2070 this._height = this.target.height();
2071 }
2072 if (!this.target.width()) {
2073 var w;
2074 if (options && options.width) {
2075 w = parseInt(options.width, 10);
2076 }
2077 else if (this.target.attr('data-width')) {
2078 w = parseInt(this.target.attr('data-width'), 10);
2079 }
2080 else {
2081 w = parseInt($.jqplot.config.defaultWidth, 10);
2082 }
2083 this._width = w;
2084 this.target.css('width', w+'px');
2085 }
2086 else {
2087 this._width = this.target.width();
2088 }
2089 */
2090
2091 this._height = this.target.height();
2092 this._width = this.target.width();
2093
2094 if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2095 throw "Target dimension not set";
2096 }
2097
2098 this._plotDimensions.height = this._height;
2099 this._plotDimensions.width = this._width;
2100 this.grid._plotDimensions = this._plotDimensions;
2101 this.title._plotDimensions = this._plotDimensions;
2102 this.baseCanvas._plotDimensions = this._plotDimensions;
2103 this.eventCanvas._plotDimensions = this._plotDimensions;
2104 this.legend._plotDimensions = this._plotDimensions;
2105
2106 for (var n in this.axes) {
2107 this.axes[n]._plotWidth = this._width;
2108 this.axes[n]._plotHeight = this._height;
2109 }
2110
2111 this.title._plotWidth = this._width;
2112
2113 if (this.textColor) {
2114 this.target.css('color', this.textColor);
2115 }
2116 if (this.fontFamily) {
2117 this.target.css('font-family', this.fontFamily);
2118 }
2119 if (this.fontSize) {
2120 this.target.css('font-size', this.fontSize);
2121 }
2122
2123 this._sumy = 0;
2124 this._sumx = 0;
2125 for (var i=0; i<this.series.length; i++) {
2126 this.populatePlotData(this.series[i], i);
2127 if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) {
2128 this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this);
2129 }
2130 this.series[i]._plotDimensions = this._plotDimensions;
2131 this.series[i].canvas._plotDimensions = this._plotDimensions;
2132 //this.series[i].init(i, this.grid.borderWidth);
2133 this._sumy += this.series[i]._sumy;
2134 this._sumx += this.series[i]._sumx;
2135 }
2136
2137 for (var j=0; j<12; j++) {
2138 name = _axisNames[j];
2139 // Memory Leaks patch : clear ticks elements
2140 var t = this.axes[name]._ticks;
2141 for (var i = 0; i < t.length; i++) {
2142 var el = t[i]._elem;
2143 if (el) {
2144 // if canvas renderer
2145 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
2146 window.G_vmlCanvasManager.uninitElement(el.get(0));
2147 }
2148 el.emptyForce();
2149 el = null;
2150 t._elem = null;
2151 }
2152 }
2153 t = null;
2154
2155 this.axes[name]._plotDimensions = this._plotDimensions;
2156 this.axes[name]._ticks = [];
2157 // this.axes[name].renderer.init.call(this.axes[name], {});
2158 }
2159
2160 if (this.sortData) {
2161 sortData(this.series);
2162 }
2163
2164 this.grid._axes = this.axes;
2165
2166 this.legend._series = this.series;
2167 };
2168
2169 // sort the series data in increasing order.
2170 function sortData(series) {
2171 var d, sd, pd, ppd, ret;
2172 for (var i=0; i<series.length; i++) {
2173 // d = series[i].data;
2174 // sd = series[i]._stackData;
2175 // pd = series[i]._plotData;
2176 // ppd = series[i]._prevPlotData;
2177 var check;
2178 var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData];
2179 for (var n=0; n<4; n++) {
2180 check = true;
2181 d = bat[n];
2182 if (series[i]._stackAxis == 'x') {
2183 for (var j = 0; j < d.length; j++) {
2184 if (typeof(d[j][1]) != "number") {
2185 check = false;
2186 break;
2187 }
2188 }
2189 if (check) {
2190 d.sort(function(a,b) { return a[1] - b[1]; });
2191 // sd.sort(function(a,b) { return a[1] - b[1]; });
2192 // pd.sort(function(a,b) { return a[1] - b[1]; });
2193 // ppd.sort(function(a,b) { return a[1] - b[1]; });
2194 }
2195 }
2196 else {
2197 for (var j = 0; j < d.length; j++) {
2198 if (typeof(d[j][0]) != "number") {
2199 check = false;
2200 break;
2201 }
2202 }
2203 if (check) {
2204 d.sort(function(a,b) { return a[0] - b[0]; });
2205 // sd.sort(function(a,b) { return a[0] - b[0]; });
2206 // pd.sort(function(a,b) { return a[0] - b[0]; });
2207 // ppd.sort(function(a,b) { return a[0] - b[0]; });
2208 }
2209 }
2210 }
2211
2212 }
2213 }
2214
2215 // populate the _stackData and _plotData arrays for the plot and the series.
2216 this.populatePlotData = function(series, index) {
2217 // if a stacked chart, compute the stacked data
2218 this._plotData = [];
2219 this._stackData = [];
2220 series._stackData = [];
2221 series._plotData = [];
2222 var plotValues = {x:[], y:[]};
2223 if (this.stackSeries && !series.disableStack) {
2224 series._stack = true;
2225 var sidx = series._stackAxis == 'x' ? 0 : 1;
2226 var idx = sidx ? 0 : 1;
2227 // push the current data into stackData
2228 //this._stackData.push(this.series[i].data);
2229 var temp = $.extend(true, [], series.data);
2230 // create the data that will be plotted for this series
2231 var plotdata = $.extend(true, [], series.data);
2232 // for first series, nothing to add to stackData.
2233 for (var j=0; j<index; j++) {
2234 var cd = this.series[j].data;
2235 for (var k=0; k<cd.length; k++) {
2236 temp[k][0] += cd[k][0];
2237 temp[k][1] += cd[k][1];
2238 // only need to sum up the stack axis column of data
2239 plotdata[k][sidx] += cd[k][sidx];
2240 }
2241 }
2242 for (var i=0; i<plotdata.length; i++) {
2243 plotValues.x.push(plotdata[i][0]);
2244 plotValues.y.push(plotdata[i][1]);
2245 }
2246 this._plotData.push(plotdata);
2247 this._stackData.push(temp);
2248 series._stackData = temp;
2249 series._plotData = plotdata;
2250 series._plotValues = plotValues;
2251 }
2252 else {
2253 for (var i=0; i<series.data.length; i++) {
2254 plotValues.x.push(series.data[i][0]);
2255 plotValues.y.push(series.data[i][1]);
2256 }
2257 this._stackData.push(series.data);
2258 this.series[index]._stackData = series.data;
2259 this._plotData.push(series.data);
2260 series._plotData = series.data;
2261 series._plotValues = plotValues;
2262 }
2263 if (index>0) {
2264 series._prevPlotData = this.series[index-1]._plotData;
2265 }
2266 series._sumy = 0;
2267 series._sumx = 0;
2268 for (i=series.data.length-1; i>-1; i--) {
2269 series._sumy += series.data[i][1];
2270 series._sumx += series.data[i][0];
2271 }
2272 };
2273
2274 // this.setData = function(seriesIndex, newdata) {
2275 // // if newdata is null, assume all data is passed in as first argument
2276 // if (newdata == null) {
2277
2278 // }
2279 // };
2280
2281 // function to safely return colors from the color array and wrap around at the end.
2282 this.getNextSeriesColor = (function(t) {
2283 var idx = 0;
2284 var sc = t.seriesColors;
2285
2286 return function () {
2287 if (idx < sc.length) {
2288 return sc[idx++];
2289 }
2290 else {
2291 idx = 0;
2292 return sc[idx++];
2293 }
2294 };
2295 })(this);
2296
2297 this.parseOptions = function(options){
2298 for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) {
2299 this.preParseOptionsHooks.hooks[i].call(this, options);
2300 }
2301 for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) {
2302 $.jqplot.preParseOptionsHooks[i].call(this, options);
2303 }
2304 this.options = $.extend(true, {}, this.defaults, options);
2305 this.stackSeries = this.options.stackSeries;
2306 if (this.options.seriesColors) {
2307 this.seriesColors = this.options.seriesColors;
2308 }
2309 if (this.options.negativeSeriesColors) {
2310 this.negativeSeriesColors = this.options.negativeSeriesColors;
2311 }
2312 if (this.options.captureRightClick) {
2313 this.captureRightClick = this.options.captureRightClick;
2314 }
2315 this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart;
2316 this.colorGenerator.setColors(this.seriesColors);
2317 this.negativeColorGenerator.setColors(this.negativeSeriesColors);
2318 // var cg = new this.colorGenerator(this.seriesColors);
2319 // var ncg = new this.colorGenerator(this.negativeSeriesColors);
2320 // this._gridPadding = this.options.gridPadding;
2321 $.extend(true, this._gridPadding, this.options.gridPadding);
2322 this.sortData = (this.options.sortData != null) ? this.options.sortData : this.sortData;
2323 for (var i=0; i<12; i++) {
2324 var n = _axisNames[i];
2325 var axis = this.axes[n];
2326 axis._options = $.extend(true, {}, this.options.axesDefaults, this.options.axes[n]);
2327 $.extend(true, axis, this.options.axesDefaults, this.options.axes[n]);
2328 axis._plotWidth = this._width;
2329 axis._plotHeight = this._height;
2330 }
2331 // if (this.data.length == 0) {
2332 // this.data = [];
2333 // for (var i=0; i<this.options.series.length; i++) {
2334 // this.data.push(this.options.series.data);
2335 // }
2336 // }
2337
2338 var normalizeData = function(data, dir, start) {
2339 // return data as an array of point arrays,
2340 // in form [[x1,y1...], [x2,y2...], ...]
2341 var temp = [];
2342 var i;
2343 dir = dir || 'vertical';
2344 if (!jQuery.isArray(data[0])) {
2345 // we have a series of scalars. One line with just y values.
2346 // turn the scalar list of data into a data array of form:
2347 // [[1, data[0]], [2, data[1]], ...]
2348 for (i=0; i<data.length; i++) {
2349 if (dir == 'vertical') {
2350 temp.push([start + i, data[i]]);
2351 }
2352 else {
2353 temp.push([data[i], start+i]);
2354 }
2355 }
2356 }
2357 else {
2358 // we have a properly formatted data series, copy it.
2359 $.extend(true, temp, data);
2360 }
2361 return temp;
2362 };
2363
2364 var colorIndex = 0;
2365 for (var i=0; i<this.data.length; i++) {
2366 var temp = new Series();
2367 for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) {
2368 $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
2369 }
2370 for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) {
2371 this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
2372 }
2373 $.extend(true, temp, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i]);
2374 var dir = 'vertical';
2375 if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal' && temp.transposeData === true) {
2376 dir = 'horizontal';
2377 }
2378 temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart);
2379 switch (temp.xaxis) {
2380 case 'xaxis':
2381 temp._xaxis = this.axes.xaxis;
2382 break;
2383 case 'x2axis':
2384 temp._xaxis = this.axes.x2axis;
2385 break;
2386 default:
2387 break;
2388 }
2389 temp._yaxis = this.axes[temp.yaxis];
2390 temp._xaxis._series.push(temp);
2391 temp._yaxis._series.push(temp);
2392 if (temp.show) {
2393 temp._xaxis.show = true;
2394 temp._yaxis.show = true;
2395 }
2396
2397 // // parse the renderer options and apply default colors if not provided
2398 // if (!temp.color && temp.show != false) {
2399 // temp.color = cg.next();
2400 // colorIndex = cg.getIndex() - 1;;
2401 // }
2402 // if (!temp.negativeColor && temp.show != false) {
2403 // temp.negativeColor = ncg.get(colorIndex);
2404 // ncg.setIndex(colorIndex);
2405 // }
2406 if (!temp.label) {
2407 temp.label = 'Series '+ (i+1).toString();
2408 }
2409 // temp.rendererOptions.show = temp.show;
2410 // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions);
2411 this.series.push(temp);
2412 for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) {
2413 $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
2414 }
2415 for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) {
2416 this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
2417 }
2418 }
2419
2420 // copy the grid and title options into this object.
2421 $.extend(true, this.grid, this.options.grid);
2422 // if axis border properties aren't set, set default.
2423 for (var i=0; i<12; i++) {
2424 var n = _axisNames[i];
2425 var axis = this.axes[n];
2426 if (axis.borderWidth == null) {
2427 axis.borderWidth =this.grid.borderWidth;
2428 }
2429 }
2430
2431 if (typeof this.options.title == 'string') {
2432 this.title.text = this.options.title;
2433 }
2434 else if (typeof this.options.title == 'object') {
2435 $.extend(true, this.title, this.options.title);
2436 }
2437 this.title._plotWidth = this._width;
2438 this.legend.setOptions(this.options.legend);
2439
2440 for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
2441 $.jqplot.postParseOptionsHooks[i].call(this, options);
2442 }
2443 for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) {
2444 this.postParseOptionsHooks.hooks[i].call(this, options);
2445 }
2446 };
2447
2448 // method: destroy
2449 // Releases all resources occupied by the plot
2450 this.destroy = function() {
2451 this.canvasManager.freeAllCanvases();
2452 if (this.eventCanvas && this.eventCanvas._elem) {
2453 this.eventCanvas._elem.unbind();
2454 }
2455 // Couple of posts on Stack Overflow indicate that empty() doesn't
2456 // always cear up the dom and release memory. Sometimes setting
2457 // innerHTML property to null is needed. Particularly on IE, may
2458 // have to directly set it to null, bypassing jQuery.
2459 this.target.empty();
2460
2461 this.target[0].innerHTML = '';
2462 };
2463
2464 // method: replot
2465 // Does a reinitialization of the plot followed by
2466 // a redraw. Method could be used to interactively
2467 // change plot characteristics and then replot.
2468 //
2469 // Parameters:
2470 // options - Options used for replotting.
2471 //
2472 // Properties:
2473 // clear - false to not clear (empty) the plot container before replotting (default: true).
2474 // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves.
2475 // optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false).
2476 this.replot = function(options) {
2477 var opts = options || {};
2478 var clear = opts.clear || true;
2479 var resetAxes = opts.resetAxes || false;
2480 this.target.trigger('jqplotPreReplot');
2481
2482 if (clear) {
2483 this.destroy();
2484 }
2485 this.reInitialize();
2486 if (resetAxes) {
2487 this.resetAxesScale(resetAxes, opts.axes);
2488 }
2489 this.draw();
2490 this.target.trigger('jqplotPostReplot');
2491 };
2492
2493 // method: redraw
2494 // Empties the plot target div and redraws the plot.
2495 // This enables plot data and properties to be changed
2496 // and then to comletely clear the plot and redraw.
2497 // redraw *will not* reinitialize any plot elements.
2498 // That is, axes will not be autoscaled and defaults
2499 // will not be reapplied to any plot elements. redraw
2500 // is used primarily with zooming.
2501 //
2502 // Parameters:
2503 // clear - false to not clear (empty) the plot container before redrawing (default: true).
2504 this.redraw = function(clear) {
2505 clear = (clear != null) ? clear : true;
2506 this.target.trigger('jqplotPreRedraw');
2507 if (clear) {
2508 this.canvasManager.freeAllCanvases();
2509 this.eventCanvas._elem.unbind();
2510 // Dont think I bind any events to the target, this shouldn't be necessary.
2511 // It will remove user's events.
2512 // this.target.unbind();
2513 this.target.empty();
2514 }
2515 for (var ax in this.axes) {
2516 this.axes[ax]._ticks = [];
2517 }
2518 for (var i=0; i<this.series.length; i++) {
2519 this.populatePlotData(this.series[i], i);
2520 }
2521 this._sumy = 0;
2522 this._sumx = 0;
2523 for (i=0; i<this.series.length; i++) {
2524 this._sumy += this.series[i]._sumy;
2525 this._sumx += this.series[i]._sumx;
2526 }
2527 this.draw();
2528 this.target.trigger('jqplotPostRedraw');
2529 };
2530
2531 // method: draw
2532 // Draws all elements of the plot into the container.
2533 // Does not clear the container before drawing.
2534 this.draw = function(){
2535 if (this.drawIfHidden || this.target.is(':visible')) {
2536 this.target.trigger('jqplotPreDraw');
2537 var i, j;
2538 for (i=0; i<$.jqplot.preDrawHooks.length; i++) {
2539 $.jqplot.preDrawHooks[i].call(this);
2540 }
2541 for (i=0; i<this.preDrawHooks.hooks.length; i++) {
2542 this.preDrawHooks.hooks[i].call(this);
2543 }
2544 // create an underlying canvas to be used for special features.
2545 this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this));
2546 this.baseCanvas.setContext();
2547 this.target.append(this.title.draw());
2548 this.title.pack({top:0, left:0});
2549
2550 // make room for the legend between the grid and the edge.
2551 var legendElem = this.legend.draw();
2552
2553 var gridPadding = {top:0, left:0, bottom:0, right:0};
2554
2555 if (this.legend.placement == "outsideGrid") {
2556 // temporarily append the legend to get dimensions
2557 this.target.append(legendElem);
2558 switch (this.legend.location) {
2559 case 'n':
2560 gridPadding.top += this.legend.getHeight();
2561 break;
2562 case 's':
2563 gridPadding.bottom += this.legend.getHeight();
2564 break;
2565 case 'ne':
2566 case 'e':
2567 case 'se':
2568 gridPadding.right += this.legend.getWidth();
2569 break;
2570 case 'nw':
2571 case 'w':
2572 case 'sw':
2573 gridPadding.left += this.legend.getWidth();
2574 break;
2575 default: // same as 'ne'
2576 gridPadding.right += this.legend.getWidth();
2577 break;
2578 }
2579 legendElem = legendElem.detach();
2580 }
2581
2582 var ax = this.axes;
2583 // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed.
2584 for (i=0; i<12; i++) {
2585 name = _axisNames[i];
2586 this.target.append(ax[name].draw(this.baseCanvas._ctx, this));
2587 ax[name].set();
2588 }
2589 if (ax.yaxis.show) {
2590 gridPadding.left += ax.yaxis.getWidth();
2591 }
2592 var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
2593 var rapad = [0, 0, 0, 0, 0, 0, 0, 0];
2594 var gpr = 0;
2595 var n;
2596 for (n=0; n<8; n++) {
2597 if (ax[ra[n]].show) {
2598 gpr += ax[ra[n]].getWidth();
2599 rapad[n] = gpr;
2600 }
2601 }
2602 gridPadding.right += gpr;
2603 if (ax.x2axis.show) {
2604 gridPadding.top += ax.x2axis.getHeight();
2605 }
2606 if (this.title.show) {
2607 gridPadding.top += this.title.getHeight();
2608 }
2609 if (ax.xaxis.show) {
2610 gridPadding.bottom += ax.xaxis.getHeight();
2611 }
2612
2613 // end of gridPadding adjustments.
2614
2615 // if user passed in gridDimensions option, check against calculated gridPadding
2616 if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) {
2617 var gdw = parseInt(this.options.gridDimensions.width, 10) || 0;
2618 var gdh = parseInt(this.options.gridDimensions.height, 10) || 0;
2619 var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2;
2620 var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2;
2621
2622 if (heightAdj >= 0 && widthAdj >= 0) {
2623 gridPadding.top += heightAdj;
2624 gridPadding.bottom += heightAdj;
2625 gridPadding.left += widthAdj;
2626 gridPadding.right += widthAdj;
2627 }
2628 }
2629 var arr = ['top', 'bottom', 'left', 'right'];
2630 for (var n in arr) {
2631 if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) {
2632 this._gridPadding[arr[n]] = gridPadding[arr[n]];
2633 }
2634 else if (this._gridPadding[arr[n]] == null) {
2635 this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]];
2636 }
2637 }
2638
2639 var legendPadding = (this.legend.placement == 'outsideGrid') ? {top:this.title.getHeight(), left: 0, right: 0, bottom: 0} : this._gridPadding;
2640
2641 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});
2642 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});
2643 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});
2644 for (i=8; i>0; i--) {
2645 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});
2646 }
2647 var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0;
2648 ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
2649
2650 this.target.append(this.grid.createElement(this._gridPadding, this));
2651 this.grid.draw();
2652
2653 // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars.
2654 for (i=0; i<this.series.length; i++) {
2655 // draw series in order of stacking. This affects only
2656 // order in which canvases are added to dom.
2657 j = this.seriesStack[i];
2658 this.target.append(this.series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this));
2659 this.series[j].shadowCanvas.setContext();
2660 this.series[j].shadowCanvas._elem.data('seriesIndex', j);
2661 }
2662
2663 for (i=0; i<this.series.length; i++) {
2664 // draw series in order of stacking. This affects only
2665 // order in which canvases are added to dom.
2666 j = this.seriesStack[i];
2667 this.target.append(this.series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this));
2668 this.series[j].canvas.setContext();
2669 this.series[j].canvas._elem.data('seriesIndex', j);
2670 }
2671 // Need to use filled canvas to capture events in IE.
2672 // Also, canvas seems to block selection of other elements in document on FF.
2673 this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this));
2674 this.eventCanvas.setContext();
2675 this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)';
2676 this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height);
2677
2678 // bind custom event handlers to regular events.
2679 this.bindCustomEvents();
2680
2681 // draw legend before series if the series needs to know the legend dimensions.
2682 if (this.legend.preDraw) {
2683 this.eventCanvas._elem.before(legendElem);
2684 this.legend.pack(legendPadding);
2685 if (this.legend._elem) {
2686 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}});
2687 }
2688 else {
2689 this.drawSeries();
2690 }
2691 }
2692 else { // draw series before legend
2693 this.drawSeries();
2694 if (this.series.length) {
2695 $(this.series[this.series.length-1].canvas._elem).after(legendElem);
2696 }
2697 this.legend.pack(legendPadding);
2698 }
2699
2700 // register event listeners on the overlay canvas
2701 for (var i=0; i<$.jqplot.eventListenerHooks.length; i++) {
2702 // in the handler, this will refer to the eventCanvas dom element.
2703 // make sure there are references back into plot objects.
2704 this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
2705 }
2706
2707 // register event listeners on the overlay canvas
2708 for (var i=0; i<this.eventListenerHooks.hooks.length; i++) {
2709 // in the handler, this will refer to the eventCanvas dom element.
2710 // make sure there are references back into plot objects.
2711 this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]);
2712 }
2713
2714 for (var i=0; i<$.jqplot.postDrawHooks.length; i++) {
2715 $.jqplot.postDrawHooks[i].call(this);
2716 }
2717
2718 for (var i=0; i<this.postDrawHooks.hooks.length; i++) {
2719 this.postDrawHooks.hooks[i].call(this);
2720 }
2721
2722 if (this.target.is(':visible')) {
2723 this._drawCount += 1;
2724 }
2725
2726 this.target.trigger('jqplotPostDraw', [this]);
2727 }
2728 };
2729
2730 this.bindCustomEvents = function() {
2731 this.eventCanvas._elem.bind('click', {plot:this}, this.onClick);
2732 this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
2733 this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown);
2734 this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove);
2735 this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
2736 this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
2737 if (this.captureRightClick) {
2738 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick);
2739 this.eventCanvas._elem.get(0).oncontextmenu = function() {
2740 return false;
2741 };
2742 }
2743 else {
2744 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp);
2745 }
2746 };
2747
2748 function getEventPosition(ev) {
2749 var plot = ev.data.plot;
2750 var go = plot.eventCanvas._elem.offset();
2751 var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
2752 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};
2753 var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
2754 var ax = plot.axes;
2755 var n, axis;
2756 for (n=11; n>0; n--) {
2757 axis = an[n-1];
2758 if (ax[axis].show) {
2759 dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
2760 }
2761 }
2762
2763 return {offsets:go, gridPos:gridPos, dataPos:dataPos};
2764 }
2765
2766
2767 // function to check if event location is over a area area
2768 function checkIntersection(gridpos, plot) {
2769 var series = plot.series;
2770 var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang;
2771 var d0, d, p, pp, points, bw;
2772 var threshold, t;
2773 for (k=plot.seriesStack.length-1; k>=0; k--) {
2774 i = plot.seriesStack[k];
2775 s = series[i];
2776 switch (s.renderer.constructor) {
2777 case $.jqplot.BarRenderer:
2778 case $.jqplot.PyramidRenderer:
2779 x = gridpos.x;
2780 y = gridpos.y;
2781 for (j=0; j<s._barPoints.length; j++) {
2782 points = s._barPoints[j];
2783 p = s.gridData[j];
2784 if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
2785 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
2786 }
2787 }
2788 break;
2789
2790 case $.jqplot.DonutRenderer:
2791 sa = s.startAngle/180*Math.PI;
2792 x = gridpos.x - s._center[0];
2793 y = gridpos.y - s._center[1];
2794 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
2795 if (x > 0 && -y >= 0) {
2796 theta = 2*Math.PI - Math.atan(-y/x);
2797 }
2798 else if (x > 0 && -y < 0) {
2799 theta = -Math.atan(-y/x);
2800 }
2801 else if (x < 0) {
2802 theta = Math.PI - Math.atan(-y/x);
2803 }
2804 else if (x == 0 && -y > 0) {
2805 theta = 3*Math.PI/2;
2806 }
2807 else if (x == 0 && -y < 0) {
2808 theta = Math.PI/2;
2809 }
2810 else if (x == 0 && y == 0) {
2811 theta = 0;
2812 }
2813 if (sa) {
2814 theta -= sa;
2815 if (theta < 0) {
2816 theta += 2*Math.PI;
2817 }
2818 else if (theta > 2*Math.PI) {
2819 theta -= 2*Math.PI;
2820 }
2821 }
2822
2823 sm = s.sliceMargin/180*Math.PI;
2824 if (r < s._radius && r > s._innerRadius) {
2825 for (j=0; j<s.gridData.length; j++) {
2826 minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
2827 maxang = s.gridData[j][1];
2828 if (theta > minang && theta < maxang) {
2829 return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]};
2830 }
2831 }
2832 }
2833 break;
2834
2835 case $.jqplot.PieRenderer:
2836 sa = s.startAngle/180*Math.PI;
2837 x = gridpos.x - s._center[0];
2838 y = gridpos.y - s._center[1];
2839 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
2840 if (x > 0 && -y >= 0) {
2841 theta = 2*Math.PI - Math.atan(-y/x);
2842 }
2843 else if (x > 0 && -y < 0) {
2844 theta = -Math.atan(-y/x);
2845 }
2846 else if (x < 0) {
2847 theta = Math.PI - Math.atan(-y/x);
2848 }
2849 else if (x == 0 && -y > 0) {
2850 theta = 3*Math.PI/2;
2851 }
2852 else if (x == 0 && -y < 0) {
2853 theta = Math.PI/2;
2854 }
2855 else if (x == 0 && y == 0) {
2856 theta = 0;
2857 }
2858 if (sa) {
2859 theta -= sa;
2860 if (theta < 0) {
2861 theta += 2*Math.PI;
2862 }
2863 else if (theta > 2*Math.PI) {
2864 theta -= 2*Math.PI;
2865 }
2866 }
2867
2868 sm = s.sliceMargin/180*Math.PI;
2869 if (r < s._radius) {
2870 for (j=0; j<s.gridData.length; j++) {
2871 minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
2872 maxang = s.gridData[j][1];
2873 if (theta > minang && theta < maxang) {
2874 return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]};
2875 }
2876 }
2877 }
2878 break;
2879
2880 case $.jqplot.BubbleRenderer:
2881 x = gridpos.x;
2882 y = gridpos.y;
2883 var ret = null;
2884
2885 if (s.show) {
2886 for (var j=0; j<s.gridData.length; j++) {
2887 p = s.gridData[j];
2888 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
2889 if (d <= p[2] && (d <= d0 || d0 == null)) {
2890 d0 = d;
2891 ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
2892 }
2893 }
2894 if (ret != null) {
2895 return ret;
2896 }
2897 }
2898 break;
2899
2900 case $.jqplot.FunnelRenderer:
2901 x = gridpos.x;
2902 y = gridpos.y;
2903 var v = s._vertices,
2904 vfirst = v[0],
2905 vlast = v[v.length-1],
2906 lex,
2907 rex,
2908 cv;
2909
2910 // equations of right and left sides, returns x, y values given height of section (y value and 2 points)
2911
2912 function findedge (l, p1 , p2) {
2913 var m = (p1[1] - p2[1])/(p1[0] - p2[0]);
2914 var b = p1[1] - m*p1[0];
2915 var y = l + p1[1];
2916
2917 return [(y - b)/m, y];
2918 }
2919
2920 // check each section
2921 lex = findedge(y, vfirst[0], vlast[3]);
2922 rex = findedge(y, vfirst[1], vlast[2]);
2923 for (j=0; j<v.length; j++) {
2924 cv = v[j];
2925 if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) {
2926 return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]};
2927 }
2928 }
2929 break;
2930
2931 case $.jqplot.LineRenderer:
2932 x = gridpos.x;
2933 y = gridpos.y;
2934 r = s.renderer;
2935 if (s.show) {
2936 if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) {
2937 // first check if it is in bounding box
2938 var inside = false;
2939 if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) {
2940 // now check the crossing number
2941
2942 var numPoints = s._areaPoints.length;
2943 var ii;
2944 var j = numPoints-1;
2945
2946 for(var ii=0; ii < numPoints; ii++) {
2947 var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]];
2948 var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]];
2949
2950 if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y) {
2951 if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) {
2952 inside = !inside;
2953 }
2954 }
2955
2956 j = ii;
2957 }
2958 }
2959 if (inside) {
2960 return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints};
2961 }
2962 break;
2963
2964 }
2965
2966 else {
2967 t = s.markerRenderer.size/2+s.neighborThreshold;
2968 threshold = (t > 0) ? t : 0;
2969 for (var j=0; j<s.gridData.length; j++) {
2970 p = s.gridData[j];
2971 // neighbor looks different to OHLC chart.
2972 if (r.constructor == $.jqplot.OHLCRenderer) {
2973 if (r.candleStick) {
2974 var yp = s._yaxis.series_u2p;
2975 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])) {
2976 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
2977 }
2978 }
2979 // if an open hi low close chart
2980 else if (!r.hlc){
2981 var yp = s._yaxis.series_u2p;
2982 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
2983 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
2984 }
2985 }
2986 // a hi low close chart
2987 else {
2988 var yp = s._yaxis.series_u2p;
2989 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
2990 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
2991 }
2992 }
2993
2994 }
2995 else if (p[0] != null && p[1] != null){
2996 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
2997 if (d <= threshold && (d <= d0 || d0 == null)) {
2998 d0 = d;
2999 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3000 }
3001 }
3002 }
3003 }
3004 }
3005 break;
3006
3007 default:
3008 x = gridpos.x;
3009 y = gridpos.y;
3010 r = s.renderer;
3011 if (s.show) {
3012 t = s.markerRenderer.size/2+s.neighborThreshold;
3013 threshold = (t > 0) ? t : 0;
3014 for (var j=0; j<s.gridData.length; j++) {
3015 p = s.gridData[j];
3016 // neighbor looks different to OHLC chart.
3017 if (r.constructor == $.jqplot.OHLCRenderer) {
3018 if (r.candleStick) {
3019 var yp = s._yaxis.series_u2p;
3020 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])) {
3021 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3022 }
3023 }
3024 // if an open hi low close chart
3025 else if (!r.hlc){
3026 var yp = s._yaxis.series_u2p;
3027 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3028 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3029 }
3030 }
3031 // a hi low close chart
3032 else {
3033 var yp = s._yaxis.series_u2p;
3034 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
3035 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3036 }
3037 }
3038
3039 }
3040 else {
3041 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3042 if (d <= threshold && (d <= d0 || d0 == null)) {
3043 d0 = d;
3044 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3045 }
3046 }
3047 }
3048 }
3049 break;
3050 }
3051 }
3052
3053 return null;
3054 }
3055
3056
3057
3058 this.onClick = function(ev) {
3059 // Event passed in is normalized and will have data attribute.
3060 // Event passed out is unnormalized.
3061 var positions = getEventPosition(ev);
3062 var p = ev.data.plot;
3063 var neighbor = checkIntersection(positions.gridPos, p);
3064 var evt = jQuery.Event('jqplotClick');
3065 evt.pageX = ev.pageX;
3066 evt.pageY = ev.pageY;
3067 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3068 };
3069
3070 this.onDblClick = function(ev) {
3071 // Event passed in is normalized and will have data attribute.
3072 // Event passed out is unnormalized.
3073 var positions = getEventPosition(ev);
3074 var p = ev.data.plot;
3075 var neighbor = checkIntersection(positions.gridPos, p);
3076 var evt = jQuery.Event('jqplotDblClick');
3077 evt.pageX = ev.pageX;
3078 evt.pageY = ev.pageY;
3079 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3080 };
3081
3082 this.onMouseDown = function(ev) {
3083 var positions = getEventPosition(ev);
3084 var p = ev.data.plot;
3085 var neighbor = checkIntersection(positions.gridPos, p);
3086 var evt = jQuery.Event('jqplotMouseDown');
3087 evt.pageX = ev.pageX;
3088 evt.pageY = ev.pageY;
3089 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3090 };
3091
3092 this.onMouseUp = function(ev) {
3093 var positions = getEventPosition(ev);
3094 var evt = jQuery.Event('jqplotMouseUp');
3095 evt.pageX = ev.pageX;
3096 evt.pageY = ev.pageY;
3097 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]);
3098 };
3099
3100 this.onRightClick = function(ev) {
3101 var positions = getEventPosition(ev);
3102 var p = ev.data.plot;
3103 var neighbor = checkIntersection(positions.gridPos, p);
3104 if (p.captureRightClick) {
3105 if (ev.which == 3) {
3106 var evt = jQuery.Event('jqplotRightClick');
3107 evt.pageX = ev.pageX;
3108 evt.pageY = ev.pageY;
3109 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3110 }
3111 else {
3112 var evt = jQuery.Event('jqplotMouseUp');
3113 evt.pageX = ev.pageX;
3114 evt.pageY = ev.pageY;
3115 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3116 }
3117 }
3118 };
3119
3120 this.onMouseMove = function(ev) {
3121 var positions = getEventPosition(ev);
3122 var p = ev.data.plot;
3123 var neighbor = checkIntersection(positions.gridPos, p);
3124 var evt = jQuery.Event('jqplotMouseMove');
3125 evt.pageX = ev.pageX;
3126 evt.pageY = ev.pageY;
3127 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3128 };
3129
3130 this.onMouseEnter = function(ev) {
3131 var positions = getEventPosition(ev);
3132 var p = ev.data.plot;
3133 var evt = jQuery.Event('jqplotMouseEnter');
3134 evt.pageX = ev.pageX;
3135 evt.pageY = ev.pageY;
3136 evt.relatedTarget = ev.relatedTarget;
3137 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
3138 };
3139
3140 this.onMouseLeave = function(ev) {
3141 var positions = getEventPosition(ev);
3142 var p = ev.data.plot;
3143 var evt = jQuery.Event('jqplotMouseLeave');
3144 evt.pageX = ev.pageX;
3145 evt.pageY = ev.pageY;
3146 evt.relatedTarget = ev.relatedTarget;
3147 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
3148 };
3149
3150 // method: drawSeries
3151 // Redraws all or just one series on the plot. No axis scaling
3152 // is performed and no other elements on the plot are redrawn.
3153 // options is an options object to pass on to the series renderers.
3154 // It can be an empty object {}. idx is the series index
3155 // to redraw if only one series is to be redrawn.
3156 this.drawSeries = function(options, idx){
3157 var i, series, ctx;
3158 // if only one argument passed in and it is a number, use it ad idx.
3159 idx = (typeof(options) === "number" && idx == null) ? options : idx;
3160 options = (typeof(options) === "object") ? options : {};
3161 // draw specified series
3162 if (idx != undefined) {
3163 series = this.series[idx];
3164 ctx = series.shadowCanvas._ctx;
3165 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3166 series.drawShadow(ctx, options, this);
3167 ctx = series.canvas._ctx;
3168 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3169 series.draw(ctx, options, this);
3170 if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) {
3171 if (idx < this.series.length - 1) {
3172 this.drawSeries(idx+1);
3173 }
3174 }
3175 }
3176
3177 else {
3178 // if call series drawShadow method first, in case all series shadows
3179 // should be drawn before any series. This will ensure, like for
3180 // stacked bar plots, that shadows don't overlap series.
3181 for (i=0; i<this.series.length; i++) {
3182 // first clear the canvas
3183 series = this.series[i];
3184 ctx = series.shadowCanvas._ctx;
3185 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3186 series.drawShadow(ctx, options, this);
3187 ctx = series.canvas._ctx;
3188 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3189 series.draw(ctx, options, this);
3190 }
3191 }
3192 options = idx = i = series = ctx = null;
3193 };
3194
3195 // method: moveSeriesToFront
3196 // This method requires jQuery 1.4+
3197 // Moves the specified series canvas in front of all other series canvases.
3198 // This effectively "draws" the specified series on top of all other series,
3199 // although it is performed through DOM manipulation, no redrawing is performed.
3200 //
3201 // Parameters:
3202 // idx - 0 based index of the series to move. This will be the index of the series
3203 // as it was first passed into the jqplot function.
3204 this.moveSeriesToFront = function (idx) {
3205 idx = parseInt(idx, 10);
3206 var stackIndex = $.inArray(idx, this.seriesStack);
3207 // if already in front, return
3208 if (stackIndex == -1) {
3209 return;
3210 }
3211 if (stackIndex == this.seriesStack.length -1) {
3212 this.previousSeriesStack = this.seriesStack.slice(0);
3213 return;
3214 }
3215 var opidx = this.seriesStack[this.seriesStack.length -1];
3216 var serelem = this.series[idx].canvas._elem.detach();
3217 var shadelem = this.series[idx].shadowCanvas._elem.detach();
3218 this.series[opidx].shadowCanvas._elem.after(shadelem);
3219 this.series[opidx].canvas._elem.after(serelem);
3220 this.previousSeriesStack = this.seriesStack.slice(0);
3221 this.seriesStack.splice(stackIndex, 1);
3222 this.seriesStack.push(idx);
3223 };
3224
3225 // method: moveSeriesToBack
3226 // This method requires jQuery 1.4+
3227 // Moves the specified series canvas behind all other series canvases.
3228 //
3229 // Parameters:
3230 // idx - 0 based index of the series to move. This will be the index of the series
3231 // as it was first passed into the jqplot function.
3232 this.moveSeriesToBack = function (idx) {
3233 idx = parseInt(idx, 10);
3234 var stackIndex = $.inArray(idx, this.seriesStack);
3235 // if already in back, return
3236 if (stackIndex == 0 || stackIndex == -1) {
3237 return;
3238 }
3239 var opidx = this.seriesStack[0];
3240 var serelem = this.series[idx].canvas._elem.detach();
3241 var shadelem = this.series[idx].shadowCanvas._elem.detach();
3242 this.series[opidx].shadowCanvas._elem.before(shadelem);
3243 this.series[opidx].canvas._elem.before(serelem);
3244 this.previousSeriesStack = this.seriesStack.slice(0);
3245 this.seriesStack.splice(stackIndex, 1);
3246 this.seriesStack.unshift(idx);
3247 };
3248
3249 // method: restorePreviousSeriesOrder
3250 // This method requires jQuery 1.4+
3251 // Restore the series canvas order to its previous state.
3252 // Useful to put a series back where it belongs after moving
3253 // it to the front.
3254 this.restorePreviousSeriesOrder = function () {
3255 var i, j, serelem, shadelem, temp, move, keep;
3256 // if no change, return.
3257 if (this.seriesStack == this.previousSeriesStack) {
3258 return;
3259 }
3260 for (i=1; i<this.previousSeriesStack.length; i++) {
3261 move = this.previousSeriesStack[i];
3262 keep = this.previousSeriesStack[i-1];
3263 serelem = this.series[move].canvas._elem.detach();
3264 shadelem = this.series[move].shadowCanvas._elem.detach();
3265 this.series[keep].shadowCanvas._elem.after(shadelem);
3266 this.series[keep].canvas._elem.after(serelem);
3267 }
3268 temp = this.seriesStack.slice(0);
3269 this.seriesStack = this.previousSeriesStack.slice(0);
3270 this.previousSeriesStack = temp;
3271 };
3272
3273 // method: restoreOriginalSeriesOrder
3274 // This method requires jQuery 1.4+
3275 // Restore the series canvas order to its original order
3276 // when the plot was created.
3277 this.restoreOriginalSeriesOrder = function () {
3278 var i, j, arr=[], serelem, shadelem;
3279 for (i=0; i<this.series.length; i++) {
3280 arr.push(i);
3281 }
3282 if (this.seriesStack == arr) {
3283 return;
3284 }
3285 this.previousSeriesStack = this.seriesStack.slice(0);
3286 this.seriesStack = arr;
3287 for (i=1; i<this.seriesStack.length; i++) {
3288 serelem = this.series[i].canvas._elem.detach();
3289 shadelem = this.series[i].shadowCanvas._elem.detach();
3290 this.series[i-1].shadowCanvas._elem.after(shadelem);
3291 this.series[i-1].canvas._elem.after(serelem);
3292 }
3293 };
3294
3295 this.activateTheme = function (name) {
3296 this.themeEngine.activate(this, name);
3297 };
3298 }
3299
3300
3301 // conpute a highlight color or array of highlight colors from given colors.
3302 $.jqplot.computeHighlightColors = function(colors) {
3303 var ret;
3304 if (jQuery.isArray(colors)) {
3305 ret = [];
3306 for (var i=0; i<colors.length; i++){
3307 var rgba = $.jqplot.getColorComponents(colors[i]);
3308 var newrgb = [rgba[0], rgba[1], rgba[2]];
3309 var sum = newrgb[0] + newrgb[1] + newrgb[2];
3310 for (var j=0; j<3; j++) {
3311 // when darkening, lowest color component can be is 60.
3312 newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
3313 newrgb[j] = parseInt(newrgb[j], 10);
3314 (newrgb[j] > 255) ? 255 : newrgb[j];
3315 }
3316 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
3317 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
3318 newrgb[3] = 0.3 + 0.35 * rgba[3];
3319 ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')');
3320 }
3321 }
3322 else {
3323 var rgba = $.jqplot.getColorComponents(colors);
3324 var newrgb = [rgba[0], rgba[1], rgba[2]];
3325 var sum = newrgb[0] + newrgb[1] + newrgb[2];
3326 for (var j=0; j<3; j++) {
3327 // when darkening, lowest color component can be is 60.
3328 // newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
3329 // newrgb[j] = parseInt(newrgb[j], 10);
3330 newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
3331 newrgb[j] = parseInt(newrgb[j], 10);
3332 (newrgb[j] > 255) ? 255 : newrgb[j];
3333 }
3334 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
3335 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
3336 newrgb[3] = 0.3 + 0.35 * rgba[3];
3337 ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')';
3338 }
3339 return ret;
3340 };
3341
3342 $.jqplot.ColorGenerator = function(colors) {
3343 colors = colors || $.jqplot.config.defaultColors;
3344 var idx = 0;
3345
3346 this.next = function () {
3347 if (idx < colors.length) {
3348 return colors[idx++];
3349 }
3350 else {
3351 idx = 0;
3352 return colors[idx++];
3353 }
3354 };
3355
3356 this.previous = function () {
3357 if (idx > 0) {
3358 return colors[idx--];
3359 }
3360 else {
3361 idx = colors.length-1;
3362 return colors[idx];
3363 }
3364 };
3365
3366 // get a color by index without advancing pointer.
3367 this.get = function(i) {
3368 var idx = i - colors.length * Math.floor(i/colors.length);
3369 return colors[idx];
3370 };
3371
3372 this.setColors = function(c) {
3373 colors = c;
3374 };
3375
3376 this.reset = function() {
3377 idx = 0;
3378 };
3379
3380 this.getIndex = function() {
3381 return idx;
3382 };
3383
3384 this.setIndex = function(index) {
3385 idx = index;
3386 };
3387 };
3388
3389 // convert a hex color string to rgb string.
3390 // h - 3 or 6 character hex string, with or without leading #
3391 // a - optional alpha
3392 $.jqplot.hex2rgb = function(h, a) {
3393 h = h.replace('#', '');
3394 if (h.length == 3) {
3395 h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2);
3396 }
3397 var rgb;
3398 rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16);
3399 if (a) {
3400 rgb += ', '+a;
3401 }
3402 rgb += ')';
3403 return rgb;
3404 };
3405
3406 // convert an rgb color spec to a hex spec. ignore any alpha specification.
3407 $.jqplot.rgb2hex = function(s) {
3408 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/;
3409 var m = s.match(pat);
3410 var h = '#';
3411 for (var i=1; i<4; i++) {
3412 var temp;
3413 if (m[i].search(/%/) != -1) {
3414 temp = parseInt(255*m[i]/100, 10).toString(16);
3415 if (temp.length == 1) {
3416 temp = '0'+temp;
3417 }
3418 }
3419 else {
3420 temp = parseInt(m[i], 10).toString(16);
3421 if (temp.length == 1) {
3422 temp = '0'+temp;
3423 }
3424 }
3425 h += temp;
3426 }
3427 return h;
3428 };
3429
3430 // given a css color spec, return an rgb css color spec
3431 $.jqplot.normalize2rgb = function(s, a) {
3432 if (s.search(/^ *rgba?\(/) != -1) {
3433 return s;
3434 }
3435 else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) {
3436 return $.jqplot.hex2rgb(s, a);
3437 }
3438 else {
3439 throw 'invalid color spec';
3440 }
3441 };
3442
3443 // extract the r, g, b, a color components out of a css color spec.
3444 $.jqplot.getColorComponents = function(s) {
3445 // check to see if a color keyword.
3446 s = $.jqplot.colorKeywordMap[s] || s;
3447 var rgb = $.jqplot.normalize2rgb(s);
3448 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/;
3449 var m = rgb.match(pat);
3450 var ret = [];
3451 for (var i=1; i<4; i++) {
3452 if (m[i].search(/%/) != -1) {
3453 ret[i-1] = parseInt(255*m[i]/100, 10);
3454 }
3455 else {
3456 ret[i-1] = parseInt(m[i], 10);
3457 }
3458 }
3459 ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0;
3460 return ret;
3461 };
3462
3463 $.jqplot.colorKeywordMap = {
3464 aliceblue: 'rgb(240, 248, 255)',
3465 antiquewhite: 'rgb(250, 235, 215)',
3466 aqua: 'rgb( 0, 255, 255)',
3467 aquamarine: 'rgb(127, 255, 212)',
3468 azure: 'rgb(240, 255, 255)',
3469 beige: 'rgb(245, 245, 220)',
3470 bisque: 'rgb(255, 228, 196)',
3471 black: 'rgb( 0, 0, 0)',
3472 blanchedalmond: 'rgb(255, 235, 205)',
3473 blue: 'rgb( 0, 0, 255)',
3474 blueviolet: 'rgb(138, 43, 226)',
3475 brown: 'rgb(165, 42, 42)',
3476 burlywood: 'rgb(222, 184, 135)',
3477 cadetblue: 'rgb( 95, 158, 160)',
3478 chartreuse: 'rgb(127, 255, 0)',
3479 chocolate: 'rgb(210, 105, 30)',
3480 coral: 'rgb(255, 127, 80)',
3481 cornflowerblue: 'rgb(100, 149, 237)',
3482 cornsilk: 'rgb(255, 248, 220)',
3483 crimson: 'rgb(220, 20, 60)',
3484 cyan: 'rgb( 0, 255, 255)',
3485 darkblue: 'rgb( 0, 0, 139)',
3486 darkcyan: 'rgb( 0, 139, 139)',
3487 darkgoldenrod: 'rgb(184, 134, 11)',
3488 darkgray: 'rgb(169, 169, 169)',
3489 darkgreen: 'rgb( 0, 100, 0)',
3490 darkgrey: 'rgb(169, 169, 169)',
3491 darkkhaki: 'rgb(189, 183, 107)',
3492 darkmagenta: 'rgb(139, 0, 139)',
3493 darkolivegreen: 'rgb( 85, 107, 47)',
3494 darkorange: 'rgb(255, 140, 0)',
3495 darkorchid: 'rgb(153, 50, 204)',
3496 darkred: 'rgb(139, 0, 0)',
3497 darksalmon: 'rgb(233, 150, 122)',
3498 darkseagreen: 'rgb(143, 188, 143)',
3499 darkslateblue: 'rgb( 72, 61, 139)',
3500 darkslategray: 'rgb( 47, 79, 79)',
3501 darkslategrey: 'rgb( 47, 79, 79)',
3502 darkturquoise: 'rgb( 0, 206, 209)',
3503 darkviolet: 'rgb(148, 0, 211)',
3504 deeppink: 'rgb(255, 20, 147)',
3505 deepskyblue: 'rgb( 0, 191, 255)',
3506 dimgray: 'rgb(105, 105, 105)',
3507 dimgrey: 'rgb(105, 105, 105)',
3508 dodgerblue: 'rgb( 30, 144, 255)',
3509 firebrick: 'rgb(178, 34, 34)',
3510 floralwhite: 'rgb(255, 250, 240)',
3511 forestgreen: 'rgb( 34, 139, 34)',
3512 fuchsia: 'rgb(255, 0, 255)',
3513 gainsboro: 'rgb(220, 220, 220)',
3514 ghostwhite: 'rgb(248, 248, 255)',
3515 gold: 'rgb(255, 215, 0)',
3516 goldenrod: 'rgb(218, 165, 32)',
3517 gray: 'rgb(128, 128, 128)',
3518 grey: 'rgb(128, 128, 128)',
3519 green: 'rgb( 0, 128, 0)',
3520 greenyellow: 'rgb(173, 255, 47)',
3521 honeydew: 'rgb(240, 255, 240)',
3522 hotpink: 'rgb(255, 105, 180)',
3523 indianred: 'rgb(205, 92, 92)',
3524 indigo: 'rgb( 75, 0, 130)',
3525 ivory: 'rgb(255, 255, 240)',
3526 khaki: 'rgb(240, 230, 140)',
3527 lavender: 'rgb(230, 230, 250)',
3528 lavenderblush: 'rgb(255, 240, 245)',
3529 lawngreen: 'rgb(124, 252, 0)',
3530 lemonchiffon: 'rgb(255, 250, 205)',
3531 lightblue: 'rgb(173, 216, 230)',
3532 lightcoral: 'rgb(240, 128, 128)',
3533 lightcyan: 'rgb(224, 255, 255)',
3534 lightgoldenrodyellow: 'rgb(250, 250, 210)',
3535 lightgray: 'rgb(211, 211, 211)',
3536 lightgreen: 'rgb(144, 238, 144)',
3537 lightgrey: 'rgb(211, 211, 211)',
3538 lightpink: 'rgb(255, 182, 193)',
3539 lightsalmon: 'rgb(255, 160, 122)',
3540 lightseagreen: 'rgb( 32, 178, 170)',
3541 lightskyblue: 'rgb(135, 206, 250)',
3542 lightslategray: 'rgb(119, 136, 153)',
3543 lightslategrey: 'rgb(119, 136, 153)',
3544 lightsteelblue: 'rgb(176, 196, 222)',
3545 lightyellow: 'rgb(255, 255, 224)',
3546 lime: 'rgb( 0, 255, 0)',
3547 limegreen: 'rgb( 50, 205, 50)',
3548 linen: 'rgb(250, 240, 230)',
3549 magenta: 'rgb(255, 0, 255)',
3550 maroon: 'rgb(128, 0, 0)',
3551 mediumaquamarine: 'rgb(102, 205, 170)',
3552 mediumblue: 'rgb( 0, 0, 205)',
3553 mediumorchid: 'rgb(186, 85, 211)',
3554 mediumpurple: 'rgb(147, 112, 219)',
3555 mediumseagreen: 'rgb( 60, 179, 113)',
3556 mediumslateblue: 'rgb(123, 104, 238)',
3557 mediumspringgreen: 'rgb( 0, 250, 154)',
3558 mediumturquoise: 'rgb( 72, 209, 204)',
3559 mediumvioletred: 'rgb(199, 21, 133)',
3560 midnightblue: 'rgb( 25, 25, 112)',
3561 mintcream: 'rgb(245, 255, 250)',
3562 mistyrose: 'rgb(255, 228, 225)',
3563 moccasin: 'rgb(255, 228, 181)',
3564 navajowhite: 'rgb(255, 222, 173)',
3565 navy: 'rgb( 0, 0, 128)',
3566 oldlace: 'rgb(253, 245, 230)',
3567 olive: 'rgb(128, 128, 0)',
3568 olivedrab: 'rgb(107, 142, 35)',
3569 orange: 'rgb(255, 165, 0)',
3570 orangered: 'rgb(255, 69, 0)',
3571 orchid: 'rgb(218, 112, 214)',
3572 palegoldenrod: 'rgb(238, 232, 170)',
3573 palegreen: 'rgb(152, 251, 152)',
3574 paleturquoise: 'rgb(175, 238, 238)',
3575 palevioletred: 'rgb(219, 112, 147)',
3576 papayawhip: 'rgb(255, 239, 213)',
3577 peachpuff: 'rgb(255, 218, 185)',
3578 peru: 'rgb(205, 133, 63)',
3579 pink: 'rgb(255, 192, 203)',
3580 plum: 'rgb(221, 160, 221)',
3581 powderblue: 'rgb(176, 224, 230)',
3582 purple: 'rgb(128, 0, 128)',
3583 red: 'rgb(255, 0, 0)',
3584 rosybrown: 'rgb(188, 143, 143)',
3585 royalblue: 'rgb( 65, 105, 225)',
3586 saddlebrown: 'rgb(139, 69, 19)',
3587 salmon: 'rgb(250, 128, 114)',
3588 sandybrown: 'rgb(244, 164, 96)',
3589 seagreen: 'rgb( 46, 139, 87)',
3590 seashell: 'rgb(255, 245, 238)',
3591 sienna: 'rgb(160, 82, 45)',
3592 silver: 'rgb(192, 192, 192)',
3593 skyblue: 'rgb(135, 206, 235)',
3594 slateblue: 'rgb(106, 90, 205)',
3595 slategray: 'rgb(112, 128, 144)',
3596 slategrey: 'rgb(112, 128, 144)',
3597 snow: 'rgb(255, 250, 250)',
3598 springgreen: 'rgb( 0, 255, 127)',
3599 steelblue: 'rgb( 70, 130, 180)',
3600 tan: 'rgb(210, 180, 140)',
3601 teal: 'rgb( 0, 128, 128)',
3602 thistle: 'rgb(216, 191, 216)',
3603 tomato: 'rgb(255, 99, 71)',
3604 turquoise: 'rgb( 64, 224, 208)',
3605 violet: 'rgb(238, 130, 238)',
3606 wheat: 'rgb(245, 222, 179)',
3607 white: 'rgb(255, 255, 255)',
3608 whitesmoke: 'rgb(245, 245, 245)',
3609 yellow: 'rgb(255, 255, 0)',
3610 yellowgreen: 'rgb(154, 205, 50)'
3611 };
3612
3613
3614
3615 // class: $.jqplot.AxisLabelRenderer
3616 // Renderer to place labels on the axes.
3617 $.jqplot.AxisLabelRenderer = function(options) {
3618 // Group: Properties
3619 $.jqplot.ElemContainer.call(this);
3620 // name of the axis associated with this tick
3621 this.axis;
3622 // prop: show
3623 // wether or not to show the tick (mark and label).
3624 this.show = true;
3625 // prop: label
3626 // The text or html for the label.
3627 this.label = '';
3628 this.fontFamily = null;
3629 this.fontSize = null;
3630 this.textColor = null;
3631 this._elem;
3632 // prop: escapeHTML
3633 // true to escape HTML entities in the label.
3634 this.escapeHTML = false;
3635
3636 $.extend(true, this, options);
3637 };
3638
3639 $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer();
3640 $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer;
3641
3642 $.jqplot.AxisLabelRenderer.prototype.init = function(options) {
3643 $.extend(true, this, options);
3644 };
3645
3646 $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) {
3647 // Memory Leaks patch
3648 if (this._elem) {
3649 this._elem.emptyForce();
3650 this._elem = null;
3651 }
3652
3653 this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>');
3654
3655 if (Number(this.label)) {
3656 this._elem.css('white-space', 'nowrap');
3657 }
3658
3659 if (!this.escapeHTML) {
3660 this._elem.html(this.label);
3661 }
3662 else {
3663 this._elem.text(this.label);
3664 }
3665 if (this.fontFamily) {
3666 this._elem.css('font-family', this.fontFamily);
3667 }
3668 if (this.fontSize) {
3669 this._elem.css('font-size', this.fontSize);
3670 }
3671 if (this.textColor) {
3672 this._elem.css('color', this.textColor);
3673 }
3674
3675 return this._elem;
3676 };
3677
3678 $.jqplot.AxisLabelRenderer.prototype.pack = function() {
3679 };
3680
3681 // class: $.jqplot.AxisTickRenderer
3682 // A "tick" object showing the value of a tick/gridline on the plot.
3683 $.jqplot.AxisTickRenderer = function(options) {
3684 // Group: Properties
3685 $.jqplot.ElemContainer.call(this);
3686 // prop: mark
3687 // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null.
3688 this.mark = 'outside';
3689 // name of the axis associated with this tick
3690 this.axis;
3691 // prop: showMark
3692 // wether or not to show the mark on the axis.
3693 this.showMark = true;
3694 // prop: showGridline
3695 // wether or not to draw the gridline on the grid at this tick.
3696 this.showGridline = true;
3697 // prop: isMinorTick
3698 // if this is a minor tick.
3699 this.isMinorTick = false;
3700 // prop: size
3701 // Length of the tick beyond the grid in pixels.
3702 // DEPRECATED: This has been superceeded by markSize
3703 this.size = 4;
3704 // prop: markSize
3705 // Length of the tick marks in pixels. For 'cross' style, length
3706 // will be stoked above and below axis, so total length will be twice this.
3707 this.markSize = 6;
3708 // prop: show
3709 // wether or not to show the tick (mark and label).
3710 // Setting this to false requires more testing. It is recommended
3711 // to set showLabel and showMark to false instead.
3712 this.show = true;
3713 // prop: showLabel
3714 // wether or not to show the label.
3715 this.showLabel = true;
3716 this.label = null;
3717 this.value = null;
3718 this._styles = {};
3719 // prop: formatter
3720 // A class of a formatter for the tick text. sprintf by default.
3721 this.formatter = $.jqplot.DefaultTickFormatter;
3722 // prop: prefix
3723 // String to prepend to the tick label.
3724 // Prefix is prepended to the formatted tick label.
3725 this.prefix = '';
3726 // prop: formatString
3727 // string passed to the formatter.
3728 this.formatString = '';
3729 // prop: fontFamily
3730 // css spec for the font-family css attribute.
3731 this.fontFamily;
3732 // prop: fontSize
3733 // css spec for the font-size css attribute.
3734 this.fontSize;
3735 // prop: textColor
3736 // css spec for the color attribute.
3737 this.textColor;
3738 // prop: escapeHTML
3739 // true to escape HTML entities in the label.
3740 this.escapeHTML = false;
3741 this._elem;
3742 this._breakTick = false;
3743
3744 $.extend(true, this, options);
3745 };
3746
3747 $.jqplot.AxisTickRenderer.prototype.init = function(options) {
3748 $.extend(true, this, options);
3749 };
3750
3751 $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
3752 $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;
3753
3754 $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
3755 this.value = value;
3756 this.axis = axisName;
3757 if (isMinor) {
3758 this.isMinorTick = true;
3759 }
3760 return this;
3761 };
3762
3763 $.jqplot.AxisTickRenderer.prototype.draw = function() {
3764 if (this.label === null) {
3765 this.label = this.prefix + this.formatter(this.formatString, this.value);
3766 }
3767 var style = {position: 'absolute'};
3768 if (Number(this.label)) {
3769 style['whitSpace'] = 'nowrap';
3770 }
3771
3772 // Memory Leaks patch
3773 if (this._elem) {
3774 this._elem.emptyForce();
3775 this._elem = null;
3776 }
3777
3778 this._elem = $(document.createElement('div'));
3779 this._elem.addClass("jqplot-"+this.axis+"-tick");
3780
3781 if (!this.escapeHTML) {
3782 this._elem.html(this.label);
3783 }
3784 else {
3785 this._elem.text(this.label);
3786 }
3787
3788 this._elem.css(style);
3789
3790 for (var s in this._styles) {
3791 this._elem.css(s, this._styles[s]);
3792 }
3793 if (this.fontFamily) {
3794 this._elem.css('font-family', this.fontFamily);
3795 }
3796 if (this.fontSize) {
3797 this._elem.css('font-size', this.fontSize);
3798 }
3799 if (this.textColor) {
3800 this._elem.css('color', this.textColor);
3801 }
3802 if (this._breakTick) {
3803 this._elem.addClass('jqplot-breakTick');
3804 }
3805
3806 return this._elem;
3807 };
3808
3809 $.jqplot.DefaultTickFormatter = function (format, val) {
3810 if (typeof val == 'number') {
3811 if (!format) {
3812 format = $.jqplot.config.defaultTickFormatString;
3813 }
3814 return $.jqplot.sprintf(format, val);
3815 }
3816 else {
3817 return String(val);
3818 }
3819 };
3820
3821 $.jqplot.AxisTickRenderer.prototype.pack = function() {
3822 };
3823
3824 // Class: $.jqplot.CanvasGridRenderer
3825 // The default jqPlot grid renderer, creating a grid on a canvas element.
3826 // The renderer has no additional options beyond the <Grid> class.
3827 $.jqplot.CanvasGridRenderer = function(){
3828 this.shadowRenderer = new $.jqplot.ShadowRenderer();
3829 };
3830
3831 // called with context of Grid object
3832 $.jqplot.CanvasGridRenderer.prototype.init = function(options) {
3833 this._ctx;
3834 $.extend(true, this, options);
3835 // set the shadow renderer options
3836 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};
3837 this.renderer.shadowRenderer.init(sopts);
3838 };
3839
3840 // called with context of Grid.
3841 $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) {
3842 var elem;
3843 // Memory Leaks patch
3844 if (this._elem) {
3845 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
3846 elem = this._elem.get(0);
3847 window.G_vmlCanvasManager.uninitElement(elem);
3848 elem = null;
3849 }
3850
3851 this._elem.emptyForce();
3852 this._elem = null;
3853 }
3854
3855 elem = plot.canvasManager.getCanvas();
3856
3857 var w = this._plotDimensions.width;
3858 var h = this._plotDimensions.height;
3859 elem.width = w;
3860 elem.height = h;
3861 this._elem = $(elem);
3862 this._elem.addClass('jqplot-grid-canvas');
3863 this._elem.css({ position: 'absolute', left: 0, top: 0 });
3864
3865 elem = plot.canvasManager.initCanvas(elem);
3866
3867 this._top = this._offsets.top;
3868 this._bottom = h - this._offsets.bottom;
3869 this._left = this._offsets.left;
3870 this._right = w - this._offsets.right;
3871 this._width = this._right - this._left;
3872 this._height = this._bottom - this._top;
3873 // avoid memory leak
3874 elem = null;
3875 return this._elem;
3876 };
3877
3878 $.jqplot.CanvasGridRenderer.prototype.draw = function() {
3879 this._ctx = this._elem.get(0).getContext("2d");
3880 var ctx = this._ctx;
3881 var axes = this._axes;
3882 // Add the grid onto the grid canvas. This is the bottom most layer.
3883 ctx.save();
3884 ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
3885 ctx.fillStyle = this.backgroundColor || this.background;
3886 ctx.fillRect(this._left, this._top, this._width, this._height);
3887
3888 ctx.save();
3889 ctx.lineJoin = 'miter';
3890 ctx.lineCap = 'butt';
3891 ctx.lineWidth = this.gridLineWidth;
3892 ctx.strokeStyle = this.gridLineColor;
3893 var b, e, s, m;
3894 var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis'];
3895 for (var i=4; i>0; i--) {
3896 var name = ax[i-1];
3897 var axis = axes[name];
3898 var ticks = axis._ticks;
3899 var numticks = ticks.length;
3900 if (axis.show) {
3901 if (axis.drawBaseline) {
3902 var bopts = {};
3903 if (axis.baselineWidth !== null) {
3904 bopts.lineWidth = axis.baselineWidth;
3905 }
3906 if (axis.baselineColor !== null) {
3907 bopts.strokeStyle = axis.baselineColor;
3908 }
3909 switch (name) {
3910 case 'xaxis':
3911 drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
3912 break;
3913 case 'yaxis':
3914 drawLine (this._left, this._bottom, this._left, this._top, bopts);
3915 break;
3916 case 'x2axis':
3917 drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
3918 break;
3919 case 'y2axis':
3920 drawLine (this._right, this._bottom, this._right, this._top, bopts);
3921 break;
3922 }
3923 }
3924 for (var j=numticks; j>0; j--) {
3925 var t = ticks[j-1];
3926 if (t.show) {
3927 var pos = Math.round(axis.u2p(t.value)) + 0.5;
3928 switch (name) {
3929 case 'xaxis':
3930 // draw the grid line if we should
3931 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
3932 drawLine(pos, this._top, pos, this._bottom);
3933 }
3934 // draw the mark
3935 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
3936 s = t.markSize;
3937 m = t.mark;
3938 var pos = Math.round(axis.u2p(t.value)) + 0.5;
3939 switch (m) {
3940 case 'outside':
3941 b = this._bottom;
3942 e = this._bottom+s;
3943 break;
3944 case 'inside':
3945 b = this._bottom-s;
3946 e = this._bottom;
3947 break;
3948 case 'cross':
3949 b = this._bottom-s;
3950 e = this._bottom+s;
3951 break;
3952 default:
3953 b = this._bottom;
3954 e = this._bottom+s;
3955 break;
3956 }
3957 // draw the shadow
3958 if (this.shadow) {
3959 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});
3960 }
3961 // draw the line
3962 drawLine(pos, b, pos, e);
3963 }
3964 break;
3965 case 'yaxis':
3966 // draw the grid line
3967 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
3968 drawLine(this._right, pos, this._left, pos);
3969 }
3970 // draw the mark
3971 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
3972 s = t.markSize;
3973 m = t.mark;
3974 var pos = Math.round(axis.u2p(t.value)) + 0.5;
3975 switch (m) {
3976 case 'outside':
3977 b = this._left-s;
3978 e = this._left;
3979 break;
3980 case 'inside':
3981 b = this._left;
3982 e = this._left+s;
3983 break;
3984 case 'cross':
3985 b = this._left-s;
3986 e = this._left+s;
3987 break;
3988 default:
3989 b = this._left-s;
3990 e = this._left;
3991 break;
3992 }
3993 // draw the shadow
3994 if (this.shadow) {
3995 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});
3996 }
3997 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
3998 }
3999 break;
4000 case 'x2axis':
4001 // draw the grid line
4002 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4003 drawLine(pos, this._bottom, pos, this._top);
4004 }
4005 // draw the mark
4006 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4007 s = t.markSize;
4008 m = t.mark;
4009 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4010 switch (m) {
4011 case 'outside':
4012 b = this._top-s;
4013 e = this._top;
4014 break;
4015 case 'inside':
4016 b = this._top;
4017 e = this._top+s;
4018 break;
4019 case 'cross':
4020 b = this._top-s;
4021 e = this._top+s;
4022 break;
4023 default:
4024 b = this._top-s;
4025 e = this._top;
4026 break;
4027 }
4028 // draw the shadow
4029 if (this.shadow) {
4030 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});
4031 }
4032 drawLine(pos, b, pos, e);
4033 }
4034 break;
4035 case 'y2axis':
4036 // draw the grid line
4037 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4038 drawLine(this._left, pos, this._right, pos);
4039 }
4040 // draw the mark
4041 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4042 s = t.markSize;
4043 m = t.mark;
4044 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4045 switch (m) {
4046 case 'outside':
4047 b = this._right;
4048 e = this._right+s;
4049 break;
4050 case 'inside':
4051 b = this._right-s;
4052 e = this._right;
4053 break;
4054 case 'cross':
4055 b = this._right-s;
4056 e = this._right+s;
4057 break;
4058 default:
4059 b = this._right;
4060 e = this._right+s;
4061 break;
4062 }
4063 // draw the shadow
4064 if (this.shadow) {
4065 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});
4066 }
4067 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4068 }
4069 break;
4070 default:
4071 break;
4072 }
4073 }
4074 }
4075 t = null;
4076 }
4077 axis = null;
4078 ticks = null;
4079 }
4080 // Now draw grid lines for additional y axes
4081 //////
4082 // TO DO: handle yMidAxis
4083 //////
4084 ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
4085 for (var i=7; i>0; i--) {
4086 var axis = axes[ax[i-1]];
4087 var ticks = axis._ticks;
4088 if (axis.show) {
4089 var tn = ticks[axis.numberTicks-1];
4090 var t0 = ticks[0];
4091 var left = axis.getLeft();
4092 var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]];
4093 // draw the shadow
4094 if (this.shadow) {
4095 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false});
4096 }
4097 // draw the line
4098 drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth});
4099 // draw the tick marks
4100 for (var j=ticks.length; j>0; j--) {
4101 var t = ticks[j-1];
4102 s = t.markSize;
4103 m = t.mark;
4104 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4105 if (t.showMark && t.mark) {
4106 switch (m) {
4107 case 'outside':
4108 b = left;
4109 e = left+s;
4110 break;
4111 case 'inside':
4112 b = left-s;
4113 e = left;
4114 break;
4115 case 'cross':
4116 b = left-s;
4117 e = left+s;
4118 break;
4119 default:
4120 b = left;
4121 e = left+s;
4122 break;
4123 }
4124 points = [[b,pos], [e,pos]];
4125 // draw the shadow
4126 if (this.shadow) {
4127 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
4128 }
4129 // draw the line
4130 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4131 }
4132 t = null;
4133 }
4134 t0 = null;
4135 }
4136 axis = null;
4137 ticks = null;
4138 }
4139
4140 ctx.restore();
4141
4142 function drawLine(bx, by, ex, ey, opts) {
4143 ctx.save();
4144 opts = opts || {};
4145 if (opts.lineWidth == null || opts.lineWidth != 0){
4146 $.extend(true, ctx, opts);
4147 ctx.beginPath();
4148 ctx.moveTo(bx, by);
4149 ctx.lineTo(ex, ey);
4150 ctx.stroke();
4151 ctx.restore();
4152 }
4153 }
4154
4155 if (this.shadow) {
4156 var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
4157 this.renderer.shadowRenderer.draw(ctx, points);
4158 }
4159 // Now draw border around grid. Use axis border definitions. start at
4160 // upper left and go clockwise.
4161 if (this.borderWidth != 0 && this.drawBorder) {
4162 drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
4163 drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
4164 drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
4165 drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
4166 }
4167 // ctx.lineWidth = this.borderWidth;
4168 // ctx.strokeStyle = this.borderColor;
4169 // ctx.strokeRect(this._left, this._top, this._width, this._height);
4170
4171 ctx.restore();
4172 ctx = null;
4173 axes = null;
4174 };
4175
4176 // Class: $.jqplot.DivTitleRenderer
4177 // The default title renderer for jqPlot. This class has no options beyond the <Title> class.
4178 $.jqplot.DivTitleRenderer = function() {
4179 };
4180
4181 $.jqplot.DivTitleRenderer.prototype.init = function(options) {
4182 $.extend(true, this, options);
4183 };
4184
4185 $.jqplot.DivTitleRenderer.prototype.draw = function() {
4186 // Memory Leaks patch
4187 if (this._elem) {
4188 this._elem.emptyForce();
4189 this._elem = null;
4190 }
4191
4192 var r = this.renderer;
4193 var elem = document.createElement('div');
4194 this._elem = $(elem);
4195 this._elem.addClass('jqplot-title');
4196
4197 if (!this.text) {
4198 this.show = false;
4199 this._elem.height(0);
4200 this._elem.width(0);
4201 }
4202 else if (this.text) {
4203 var color;
4204 if (this.color) {
4205 color = this.color;
4206 }
4207 else if (this.textColor) {
4208 color = this.textColor;
4209 }
4210
4211 // don't trust that a stylesheet is present, set the position.
4212 var styles = {position:'absolute', top:'0px', left:'0px'};
4213
4214 if (this._plotWidth) {
4215 styles['width'] = this._plotWidth+'px';
4216 }
4217 if (this.fontSize) {
4218 styles['fontSize'] = this.fontSize;
4219 }
4220 if (typeof this.textAlign === 'string') {
4221 styles['textAlign'] = this.textAlign;
4222 }
4223 else {
4224 styles['textAlign'] = 'center';
4225 }
4226 if (color) {
4227 styles['color'] = color;
4228 }
4229 if (this.paddingBottom) {
4230 styles['paddingBottom'] = this.paddingBottom;
4231 }
4232 if (this.fontFamily) {
4233 styles['fontFamily'] = this.fontFamily;
4234 }
4235
4236 this._elem.css(styles);
4237 if (this.escapeHtml) {
4238 this._elem.text(this.text);
4239 }
4240 else {
4241 this._elem.html(this.text);
4242 }
4243
4244
4245 // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : '';
4246 // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
4247 // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;';
4248 // styletext += (color) ? 'color:'+color+';' : '';
4249 // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : '';
4250 // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>');
4251 // if (this.fontFamily) {
4252 // this._elem.css('font-family', this.fontFamily);
4253 // }
4254 }
4255
4256 elem = null;
4257
4258 return this._elem;
4259 };
4260
4261 $.jqplot.DivTitleRenderer.prototype.pack = function() {
4262 // nothing to do here
4263 };
4264
4265
4266 var dotlen = 0.1;
4267
4268 $.jqplot.LinePattern = function (ctx, pattern) {
4269
4270 var defaultLinePatterns = {
4271 dotted: [ dotlen, $.jqplot.config.dotGapLength ],
4272 dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ],
4273 solid: null
4274 };
4275
4276 if (typeof pattern === 'string') {
4277 if (pattern[0] === '.' || pattern[0] === '-') {
4278 var s = pattern;
4279 pattern = [];
4280 for (var i=0, imax=s.length; i<imax; i++) {
4281 if (s[i] === '.') {
4282 pattern.push( dotlen );
4283 }
4284 else if (s[i] === '-') {
4285 pattern.push( $.jqplot.config.dashLength );
4286 }
4287 else {
4288 continue;
4289 }
4290 pattern.push( $.jqplot.config.gapLength );
4291 }
4292 }
4293 else {
4294 pattern = defaultLinePatterns[pattern];
4295 }
4296 }
4297
4298 if (!(pattern && pattern.length)) {
4299 return ctx;
4300 }
4301
4302 var patternIndex = 0;
4303 var patternDistance = pattern[0];
4304 var px = 0;
4305 var py = 0;
4306 var pathx0 = 0;
4307 var pathy0 = 0;
4308
4309 var moveTo = function (x, y) {
4310 ctx.moveTo( x, y );
4311 px = x;
4312 py = y;
4313 pathx0 = x;
4314 pathy0 = y;
4315 };
4316
4317 var lineTo = function (x, y) {
4318 var scale = ctx.lineWidth;
4319 var dx = x - px;
4320 var dy = y - py;
4321 var dist = Math.sqrt(dx*dx+dy*dy);
4322 if ((dist > 0) && (scale > 0)) {
4323 dx /= dist;
4324 dy /= dist;
4325 while (true) {
4326 var dp = scale * patternDistance;
4327 if (dp < dist) {
4328 px += dp * dx;
4329 py += dp * dy;
4330 if ((patternIndex & 1) == 0) {
4331 ctx.lineTo( px, py );
4332 }
4333 else {
4334 ctx.moveTo( px, py );
4335 }
4336 dist -= dp;
4337 patternIndex++;
4338 if (patternIndex >= pattern.length) {
4339 patternIndex = 0;
4340 }
4341 patternDistance = pattern[patternIndex];
4342 }
4343 else {
4344 px = x;
4345 py = y;
4346 if ((patternIndex & 1) == 0) {
4347 ctx.lineTo( px, py );
4348 }
4349 else {
4350 ctx.moveTo( px, py );
4351 }
4352 patternDistance -= dist / scale;
4353 break;
4354 }
4355 }
4356 }
4357 };
4358
4359 var beginPath = function () {
4360 ctx.beginPath();
4361 };
4362
4363 var closePath = function () {
4364 lineTo( pathx0, pathy0 );
4365 };
4366
4367 return {
4368 moveTo: moveTo,
4369 lineTo: lineTo,
4370 beginPath: beginPath,
4371 closePath: closePath
4372 };
4373 };
4374
4375 // Class: $.jqplot.LineRenderer
4376 // The default line renderer for jqPlot, this class has no options beyond the <Series> class.
4377 // Draws series as a line.
4378 $.jqplot.LineRenderer = function(){
4379 this.shapeRenderer = new $.jqplot.ShapeRenderer();
4380 this.shadowRenderer = new $.jqplot.ShadowRenderer();
4381 };
4382
4383 // called with scope of series.
4384 $.jqplot.LineRenderer.prototype.init = function(options, plot) {
4385 // Group: Properties
4386 //
4387 options = options || {};
4388 this._type='line';
4389 // prop: smooth
4390 // True to draw a smoothed (interpolated) line through the data points
4391 // with automatically computed number of smoothing points.
4392 // Set to an integer number > 2 to specify number of smoothing points
4393 // to use between each data point.
4394 this.renderer.smooth = false; // true or a number > 2 for smoothing.
4395 this.renderer.tension = null; // null to auto compute or a number typically > 6. Fewer points requires higher tension.
4396 // prop: constrainSmoothing
4397 // True to use a more accurate smoothing algorithm that will
4398 // not overshoot any data points. False to allow overshoot but
4399 // produce a smoother looking line.
4400 this.renderer.constrainSmoothing = true;
4401 // this is smoothed data in grid coordinates, like gridData
4402 this.renderer._smoothedData = [];
4403 // this is smoothed data in plot units (plot coordinates), like plotData.
4404 this.renderer._smoothedPlotData = [];
4405 this.renderer._hiBandGridData = [];
4406 this.renderer._lowBandGridData = [];
4407 this.renderer._hiBandSmoothedData = [];
4408 this.renderer._lowBandSmoothedData = [];
4409
4410 // prop: bandData
4411 // Data used to draw error bands or confidence intervals above/below a line.
4412 //
4413 // bandData can be input in 3 forms. jqPlot will figure out which is the
4414 // low band line and which is the high band line for all forms:
4415 //
4416 // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where
4417 // [yl1, yl2, ...] are y values of the lower line and
4418 // [yu1, yu2, ...] are y values of the upper line.
4419 // In this case there must be the same number of y data points as data points
4420 // in the series and the bands will inherit the x values of the series.
4421 //
4422 // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]]
4423 // where [xl1, yl1] are x,y data points for the lower line and
4424 // [xh1, yh1] are x,y data points for the high line.
4425 // x values do not have to correspond to the x values of the series and can
4426 // be of any arbitrary length.
4427 //
4428 // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where
4429 // there must be 3 or more arrays and there must be the same number of arrays
4430 // as there are data points in the series. In this case,
4431 // [yl1, yu1] specifies the lower and upper y values for the 1st
4432 // data point and so on. The bands will inherit the x
4433 // values from the series.
4434 this.renderer.bandData = [];
4435
4436 // Group: bands
4437 // Banding around line, e.g error bands or confidence intervals.
4438 this.renderer.bands = {
4439 // prop: show
4440 // true to show the bands. If bandData or interval is
4441 // supplied, show will be set to true by default.
4442 show: false,
4443 hiData: [],
4444 lowData: [],
4445 // prop: color
4446 // color of lines at top and bottom of bands [default: series color].
4447 color: this.color,
4448 // prop: showLines
4449 // True to show lines at top and bottom of bands [default: false].
4450 showLines: false,
4451 // prop: fill
4452 // True to fill area between bands [default: true].
4453 fill: true,
4454 // prop: fillColor
4455 // css color spec for filled area. [default: series color].
4456 fillColor: null,
4457 _min: null,
4458 _max: null,
4459 // prop: interval
4460 // User specified interval above and below line for bands [default: '3%''].
4461 // Can be a value like 3 or a string like '3%'
4462 // or an upper/lower array like [1, -2] or ['2%', '-1.5%']
4463 interval: '3%'
4464 };
4465
4466
4467 var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor};
4468
4469 delete (options.highlightMouseOver);
4470 delete (options.highlightMouseDown);
4471 delete (options.highlightColor);
4472
4473 $.extend(true, this.renderer, options);
4474
4475 this.renderer.options = options;
4476
4477 // if we are given some band data, and bands aren't explicity set to false in options, turn them on.
4478 if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) {
4479 this.renderer.bands.show = true;
4480 }
4481
4482 // if we are given an interval, and bands aren't explicity set to false in options, turn them on.
4483 else if (options.bands && options.bands.show == null && options.bands.interval != null) {
4484 this.renderer.bands.show = true;
4485 }
4486
4487 // if plot is filled, turn off bands.
4488 if (this.fill) {
4489 this.renderer.bands.show = false;
4490 }
4491
4492 if (this.renderer.bands.show) {
4493 this.renderer.initBands.call(this, this.renderer.options, plot);
4494 }
4495
4496
4497 // smoothing is not compatible with stacked lines, disable
4498 if (this._stack) {
4499 this.renderer.smooth = false;
4500 }
4501
4502 // set the shape renderer options
4503 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};
4504 this.renderer.shapeRenderer.init(opts);
4505
4506 var shadow_offset = options.shadowOffset;
4507 // set the shadow renderer options
4508 if (shadow_offset == null) {
4509 // scale the shadowOffset to the width of the line.
4510 if (this.lineWidth > 2.5) {
4511 shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
4512 // var shadow_offset = this.shadowOffset;
4513 }
4514 // for skinny lines, don't make such a big shadow.
4515 else {
4516 shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
4517 }
4518 }
4519
4520 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};
4521 this.renderer.shadowRenderer.init(sopts);
4522 this._areaPoints = [];
4523 this._boundingBox = [[],[]];
4524
4525 if (!this.isTrendline && this.fill || this.renderer.bands.show) {
4526 // Group: Properties
4527 //
4528 // prop: highlightMouseOver
4529 // True to highlight area on a filled plot when moused over.
4530 // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
4531 this.highlightMouseOver = true;
4532 // prop: highlightMouseDown
4533 // True to highlight when a mouse button is pressed over an area on a filled plot.
4534 // This will be disabled if highlightMouseOver is true.
4535 this.highlightMouseDown = false;
4536 // prop: highlightColor
4537 // color to use when highlighting an area on a filled plot.
4538 this.highlightColor = null;
4539 // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
4540 if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) {
4541 lopts.highlightMouseOver = false;
4542 }
4543
4544 $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor});
4545
4546 if (!this.highlightColor) {
4547 var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor;
4548 this.highlightColor = $.jqplot.computeHighlightColors(fc);
4549 }
4550 // turn off (disable) the highlighter plugin
4551 if (this.highlighter) {
4552 this.highlighter.show = false;
4553 }
4554 }
4555
4556 if (!this.isTrendline && plot) {
4557 plot.plugins.lineRenderer = {};
4558 plot.postInitHooks.addOnce(postInit);
4559 plot.postDrawHooks.addOnce(postPlotDraw);
4560 plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
4561 plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
4562 plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
4563 plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
4564 plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
4565 }
4566
4567 };
4568
4569 $.jqplot.LineRenderer.prototype.initBands = function(options, plot) {
4570 // use bandData if no data specified in bands option
4571 //var bd = this.renderer.bandData;
4572 var bd = options.bandData || [];
4573 var bands = this.renderer.bands;
4574 bands.hiData = [];
4575 bands.lowData = [];
4576 var data = this.data;
4577 bands._max = null;
4578 bands._min = null;
4579 // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values.
4580 if (bd.length == 2) {
4581 // Do we have an array of x,y values?
4582 // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]]
4583 if ($.isArray(bd[0][0])) {
4584 // since an arbitrary array of points, spin through all of them to determine max and min lines.
4585
4586 var p;
4587 var bdminidx = 0, bdmaxidx = 0;
4588 for (var i = 0, l = bd[0].length; i<l; i++) {
4589 p = bd[0][i];
4590 if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
4591 bands._max = p[1];
4592 }
4593 if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
4594 bands._min = p[1];
4595 }
4596 }
4597 for (var i = 0, l = bd[1].length; i<l; i++) {
4598 p = bd[1][i];
4599 if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
4600 bands._max = p[1];
4601 bdmaxidx = 1;
4602 }
4603 if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
4604 bands._min = p[1];
4605 bdminidx = 1;
4606 }
4607 }
4608
4609 if (bdmaxidx === bdminidx) {
4610 bands.show = false;
4611 }
4612
4613 bands.hiData = bd[bdmaxidx];
4614 bands.lowData = bd[bdminidx];
4615 }
4616 // else data is arrays of y values
4617 // like [[1,4,3], [3,6,5]]
4618 // must have same number of band data points as points in series
4619 else if (bd[0].length === data.length && bd[1].length === data.length) {
4620 var hi = (bd[0][0] > bd[1][0]) ? 0 : 1;
4621 var low = (hi) ? 0 : 1;
4622 for (var i=0, l=data.length; i < l; i++) {
4623 bands.hiData.push([data[i][0], bd[hi][i]]);
4624 bands.lowData.push([data[i][0], bd[low][i]]);
4625 }
4626 }
4627
4628 // we don't have proper data array, don't show bands.
4629 else {
4630 bands.show = false;
4631 }
4632 }
4633
4634 // if more than 2 arrays, have arrays of [ylow, yhi] values.
4635 // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]]
4636 // this is assumed to be of the latter form.
4637 else if (bd.length > 2 && !$.isArray(bd[0][0])) {
4638 var hi = (bd[0][0] > bd[0][1]) ? 0 : 1;
4639 var low = (hi) ? 0 : 1;
4640 for (var i=0, l=bd.length; i<l; i++) {
4641 bands.hiData.push([data[i][0], bd[i][hi]]);
4642 bands.lowData.push([data[i][0], bd[i][low]]);
4643 }
4644 }
4645
4646 // don't have proper data, auto calculate
4647 else {
4648 var intrv = bands.interval;
4649 var a = null;
4650 var b = null;
4651 var afunc = null;
4652 var bfunc = null;
4653
4654 if ($.isArray(intrv)) {
4655 a = intrv[0];
4656 b = intrv[1];
4657 }
4658 else {
4659 a = intrv;
4660 }
4661
4662 if (isNaN(a)) {
4663 // we have a string
4664 if (a.charAt(a.length - 1) === '%') {
4665 afunc = 'multiply';
4666 a = parseFloat(a)/100 + 1;
4667 }
4668 }
4669
4670 else {
4671 a = parseFloat(a);
4672 afunc = 'add';
4673 }
4674
4675 if (b !== null && isNaN(b)) {
4676 // we have a string
4677 if (b.charAt(b.length - 1) === '%') {
4678 bfunc = 'multiply';
4679 b = parseFloat(b)/100 + 1;
4680 }
4681 }
4682
4683 else if (b !== null) {
4684 b = parseFloat(b);
4685 bfunc = 'add';
4686 }
4687
4688 if (a !== null) {
4689 if (b === null) {
4690 b = -a;
4691 bfunc = afunc;
4692 if (bfunc === 'multiply') {
4693 b += 2;
4694 }
4695 }
4696
4697 // make sure a always applies to hi band.
4698 if (a < b) {
4699 var temp = a;
4700 a = b;
4701 b = temp;
4702 temp = afunc;
4703 afunc = bfunc;
4704 bfunc = temp;
4705 }
4706
4707 for (var i=0, l = data.length; i < l; i++) {
4708 switch (afunc) {
4709 case 'add':
4710 bands.hiData.push([data[i][0], data[i][1] + a]);
4711 break;
4712 case 'multiply':
4713 bands.hiData.push([data[i][0], data[i][1] * a]);
4714 break;
4715 }
4716 switch (bfunc) {
4717 case 'add':
4718 bands.lowData.push([data[i][0], data[i][1] + b]);
4719 break;
4720 case 'multiply':
4721 bands.lowData.push([data[i][0], data[i][1] * b]);
4722 break;
4723 }
4724 }
4725 }
4726
4727 else {
4728 bands.show = false;
4729 }
4730 }
4731
4732 var hd = bands.hiData;
4733 var ld = bands.lowData;
4734 for (var i = 0, l = hd.length; i<l; i++) {
4735 if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) {
4736 bands._max = hd[i][1];
4737 }
4738 }
4739 for (var i = 0, l = ld.length; i<l; i++) {
4740 if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) {
4741 bands._min = ld[i][1];
4742 }
4743 }
4744
4745 // one last check for proper data
4746 // these don't apply any more since allowing arbitrary x,y values
4747 // if (bands.hiData.length != bands.lowData.length) {
4748 // bands.show = false;
4749 // }
4750
4751 // if (bands.hiData.length != this.data.length) {
4752 // bands.show = false;
4753 // }
4754
4755 if (bands.fillColor === null) {
4756 var c = $.jqplot.getColorComponents(bands.color);
4757 // now adjust alpha to differentiate fill
4758 c[3] = c[3] * 0.5;
4759 bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
4760 }
4761 };
4762
4763 function getSteps (d, f) {
4764 return (3.4182054+f) * Math.pow(d, -0.3534992);
4765 }
4766
4767 function computeSteps (d1, d2) {
4768 var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2));
4769 return 5.7648 * Math.log(s) + 7.4456;
4770 }
4771
4772 function tanh (x) {
4773 var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1);
4774 return a;
4775 }
4776
4777 //////////
4778 // computeConstrainedSmoothedData
4779 // An implementation of the constrained cubic spline interpolation
4780 // method as presented in:
4781 //
4782 // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications
4783 // http://www.korf.co.uk/spline.pdf
4784 //
4785 // The implementation below borrows heavily from the sample Visual Basic
4786 // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls
4787 //
4788 /////////
4789
4790 // called with scope of series
4791 function computeConstrainedSmoothedData (gd) {
4792 var smooth = this.renderer.smooth;
4793 var dim = this.canvas.getWidth();
4794 var xp = this._xaxis.series_p2u;
4795 var yp = this._yaxis.series_p2u;
4796 var steps =null;
4797 var _steps = null;
4798 var dist = gd.length/dim;
4799 var _smoothedData = [];
4800 var _smoothedPlotData = [];
4801
4802 if (!isNaN(parseFloat(smooth))) {
4803 steps = parseFloat(smooth);
4804 }
4805 else {
4806 steps = getSteps(dist, 0.5);
4807 }
4808
4809 var yy = [];
4810 var xx = [];
4811
4812 for (var i=0, l = gd.length; i<l; i++) {
4813 yy.push(gd[i][1]);
4814 xx.push(gd[i][0]);
4815 }
4816
4817 function dxx(x1, x0) {
4818 if (x1 - x0 == 0) {
4819 return Math.pow(10,10);
4820 }
4821 else {
4822 return x1 - x0;
4823 }
4824 }
4825
4826 var A, B, C, D;
4827 // loop through each line segment. Have # points - 1 line segments. Nmber segments starting at 1.
4828 var nmax = gd.length - 1;
4829 for (var num = 1, gdl = gd.length; num<gdl; num++) {
4830 var gxx = [];
4831 var ggxx = [];
4832 // point at each end of segment.
4833 for (var j = 0; j < 2; j++) {
4834 var i = num - 1 + j; // point number, 0 to # points.
4835
4836 if (i == 0 || i == nmax) {
4837 gxx[j] = Math.pow(10, 10);
4838 }
4839 else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) {
4840 gxx[j] = 0;
4841 }
4842 else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) {
4843 gxx[j] = 0;
4844 }
4845 else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) {
4846 gxx[j] = 0;
4847 }
4848
4849 else {
4850 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]));
4851 }
4852 }
4853
4854 // Reset first derivative (slope) at first and last point
4855 if (num == 1) {
4856 // First point has 0 2nd derivative
4857 gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2;
4858 }
4859 else if (num == nmax) {
4860 // Last point has 0 2nd derivative
4861 gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2;
4862 }
4863
4864 // Calc second derivative at points
4865 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);
4866 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);
4867
4868 // Calc constants for cubic interpolation
4869 D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]);
4870 C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]);
4871 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]);
4872 A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3);
4873
4874 var increment = (xx[num] - xx[num - 1]) / steps;
4875 var temp, tempx;
4876
4877 for (var j = 0, l = steps; j < l; j++) {
4878 temp = [];
4879 tempx = xx[num - 1] + j * increment;
4880 temp.push(tempx);
4881 temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3));
4882 _smoothedData.push(temp);
4883 _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]);
4884 }
4885 }
4886
4887 _smoothedData.push(gd[i]);
4888 _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]);
4889
4890 return [_smoothedData, _smoothedPlotData];
4891 }
4892
4893 ///////
4894 // computeHermiteSmoothedData
4895 // A hermite spline smoothing of the plot data.
4896 // This implementation is derived from the one posted
4897 // by krypin on the jqplot-users mailing list:
4898 //
4899 // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1
4900 //
4901 // with a blog post:
4902 //
4903 // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/
4904 //
4905 // and download of the original plugin:
4906 //
4907 // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js
4908 //////////
4909
4910 // called with scope of series
4911 function computeHermiteSmoothedData (gd) {
4912 var smooth = this.renderer.smooth;
4913 var tension = this.renderer.tension;
4914 var dim = this.canvas.getWidth();
4915 var xp = this._xaxis.series_p2u;
4916 var yp = this._yaxis.series_p2u;
4917 var steps =null;
4918 var _steps = null;
4919 var a = null;
4920 var a1 = null;
4921 var a2 = null;
4922 var slope = null;
4923 var slope2 = null;
4924 var temp = null;
4925 var t, s, h1, h2, h3, h4;
4926 var TiX, TiY, Ti1X, Ti1Y;
4927 var pX, pY, p;
4928 var sd = [];
4929 var spd = [];
4930 var dist = gd.length/dim;
4931 var min, max, stretch, scale, shift;
4932 var _smoothedData = [];
4933 var _smoothedPlotData = [];
4934 if (!isNaN(parseFloat(smooth))) {
4935 steps = parseFloat(smooth);
4936 }
4937 else {
4938 steps = getSteps(dist, 0.5);
4939 }
4940 if (!isNaN(parseFloat(tension))) {
4941 tension = parseFloat(tension);
4942 }
4943
4944 for (var i=0, l = gd.length-1; i < l; i++) {
4945
4946 if (tension === null) {
4947 slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0]));
4948
4949 min = 0.3;
4950 max = 0.6;
4951 stretch = (max - min)/2.0;
4952 scale = 2.5;
4953 shift = -1.4;
4954
4955 temp = slope/scale + shift;
4956
4957 a1 = stretch * tanh(temp) - stretch * tanh(shift) + min;
4958
4959 // if have both left and right line segments, will use minimum tension.
4960 if (i > 0) {
4961 slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0]));
4962 }
4963 temp = slope2/scale + shift;
4964
4965 a2 = stretch * tanh(temp) - stretch * tanh(shift) + min;
4966
4967 a = (a1 + a2)/2.0;
4968
4969 }
4970 else {
4971 a = tension;
4972 }
4973 for (t=0; t < steps; t++) {
4974 s = t / steps;
4975 h1 = (1 + 2*s)*Math.pow((1-s),2);
4976 h2 = s*Math.pow((1-s),2);
4977 h3 = Math.pow(s,2)*(3-2*s);
4978 h4 = Math.pow(s,2)*(s-1);
4979
4980 if (gd[i-1]) {
4981 TiX = a * (gd[i+1][0] - gd[i-1][0]);
4982 TiY = a * (gd[i+1][1] - gd[i-1][1]);
4983 } else {
4984 TiX = a * (gd[i+1][0] - gd[i][0]);
4985 TiY = a * (gd[i+1][1] - gd[i][1]);
4986 }
4987 if (gd[i+2]) {
4988 Ti1X = a * (gd[i+2][0] - gd[i][0]);
4989 Ti1Y = a * (gd[i+2][1] - gd[i][1]);
4990 } else {
4991 Ti1X = a * (gd[i+1][0] - gd[i][0]);
4992 Ti1Y = a * (gd[i+1][1] - gd[i][1]);
4993 }
4994
4995 pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X;
4996 pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y;
4997 p = [pX, pY];
4998
4999 _smoothedData.push(p);
5000 _smoothedPlotData.push([xp(pX), yp(pY)]);
5001 }
5002 }
5003 _smoothedData.push(gd[l]);
5004 _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]);
5005
5006 return [_smoothedData, _smoothedPlotData];
5007 }
5008
5009 // setGridData
5010 // converts the user data values to grid coordinates and stores them
5011 // in the gridData array.
5012 // Called with scope of a series.
5013 $.jqplot.LineRenderer.prototype.setGridData = function(plot) {
5014 // recalculate the grid data
5015 var xp = this._xaxis.series_u2p;
5016 var yp = this._yaxis.series_u2p;
5017 var data = this._plotData;
5018 var pdata = this._prevPlotData;
5019 this.gridData = [];
5020 this._prevGridData = [];
5021 this.renderer._smoothedData = [];
5022 this.renderer._smoothedPlotData = [];
5023 this.renderer._hiBandGridData = [];
5024 this.renderer._lowBandGridData = [];
5025 this.renderer._hiBandSmoothedData = [];
5026 this.renderer._lowBandSmoothedData = [];
5027 var bands = this.renderer.bands;
5028 var hasNull = false;
5029 for (var i=0, l=this.data.length; i < l; i++) {
5030 // if not a line series or if no nulls in data, push the converted point onto the array.
5031 if (data[i][0] != null && data[i][1] != null) {
5032 this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
5033 }
5034 // else if there is a null, preserve it.
5035 else if (data[i][0] == null) {
5036 hasNull = true;
5037 this.gridData.push([null, yp.call(this._yaxis, data[i][1])]);
5038 }
5039 else if (data[i][1] == null) {
5040 hasNull = true;
5041 this.gridData.push([xp.call(this._xaxis, data[i][0]), null]);
5042 }
5043 // if not a line series or if no nulls in data, push the converted point onto the array.
5044 if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) {
5045 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]);
5046 }
5047 // else if there is a null, preserve it.
5048 else if (pdata[i] != null && pdata[i][0] == null) {
5049 this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]);
5050 }
5051 else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) {
5052 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]);
5053 }
5054 }
5055
5056 // don't do smoothing or bands on broken lines.
5057 if (hasNull) {
5058 this.renderer.smooth = false;
5059 if (this._type === 'line') {
5060 bands.show = false;
5061 }
5062 }
5063
5064 if (this._type === 'line' && bands.show) {
5065 for (var i=0, l=bands.hiData.length; i<l; i++) {
5066 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
5067 }
5068 for (var i=0, l=bands.lowData.length; i<l; i++) {
5069 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
5070 }
5071 }
5072
5073 // calculate smoothed data if enough points and no nulls
5074 if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) {
5075 var ret;
5076 if (this.renderer.constrainSmoothing) {
5077 ret = computeConstrainedSmoothedData.call(this, this.gridData);
5078 this.renderer._smoothedData = ret[0];
5079 this.renderer._smoothedPlotData = ret[1];
5080
5081 if (bands.show) {
5082 ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
5083 this.renderer._hiBandSmoothedData = ret[0];
5084 ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
5085 this.renderer._lowBandSmoothedData = ret[0];
5086 }
5087
5088 ret = null;
5089 }
5090 else {
5091 ret = computeHermiteSmoothedData.call(this, this.gridData);
5092 this.renderer._smoothedData = ret[0];
5093 this.renderer._smoothedPlotData = ret[1];
5094
5095 if (bands.show) {
5096 ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
5097 this.renderer._hiBandSmoothedData = ret[0];
5098 ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
5099 this.renderer._lowBandSmoothedData = ret[0];
5100 }
5101
5102 ret = null;
5103 }
5104 }
5105 };
5106
5107 // makeGridData
5108 // converts any arbitrary data values to grid coordinates and
5109 // returns them. This method exists so that plugins can use a series'
5110 // linerenderer to generate grid data points without overwriting the
5111 // grid data associated with that series.
5112 // Called with scope of a series.
5113 $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) {
5114 // recalculate the grid data
5115 var xp = this._xaxis.series_u2p;
5116 var yp = this._yaxis.series_u2p;
5117 var gd = [];
5118 var pgd = [];
5119 this.renderer._smoothedData = [];
5120 this.renderer._smoothedPlotData = [];
5121 this.renderer._hiBandGridData = [];
5122 this.renderer._lowBandGridData = [];
5123 this.renderer._hiBandSmoothedData = [];
5124 this.renderer._lowBandSmoothedData = [];
5125 var bands = this.renderer.bands;
5126 var hasNull = false;
5127 for (var i=0; i<data.length; i++) {
5128 // if not a line series or if no nulls in data, push the converted point onto the array.
5129 if (data[i][0] != null && data[i][1] != null) {
5130 gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
5131 }
5132 // else if there is a null, preserve it.
5133 else if (data[i][0] == null) {
5134 hasNull = true;
5135 gd.push([null, yp.call(this._yaxis, data[i][1])]);
5136 }
5137 else if (data[i][1] == null) {
5138 hasNull = true;
5139 gd.push([xp.call(this._xaxis, data[i][0]), null]);
5140 }
5141 }
5142
5143 // don't do smoothing or bands on broken lines.
5144 if (hasNull) {
5145 this.renderer.smooth = false;
5146 if (this._type === 'line') {
5147 bands.show = false;
5148 }
5149 }
5150
5151 if (this._type === 'line' && bands.show) {
5152 for (var i=0, l=bands.hiData.length; i<l; i++) {
5153 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
5154 }
5155 for (var i=0, l=bands.lowData.length; i<l; i++) {
5156 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
5157 }
5158 }
5159
5160 if (this._type === 'line' && this.renderer.smooth && gd.length > 2) {
5161 var ret;
5162 if (this.renderer.constrainSmoothing) {
5163 ret = computeConstrainedSmoothedData.call(this, gd);
5164 this.renderer._smoothedData = ret[0];
5165 this.renderer._smoothedPlotData = ret[1];
5166
5167 if (bands.show) {
5168 ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
5169 this.renderer._hiBandSmoothedData = ret[0];
5170 ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
5171 this.renderer._lowBandSmoothedData = ret[0];
5172 }
5173
5174 ret = null;
5175 }
5176 else {
5177 ret = computeHermiteSmoothedData.call(this, gd);
5178 this.renderer._smoothedData = ret[0];
5179 this.renderer._smoothedPlotData = ret[1];
5180
5181 if (bands.show) {
5182 ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
5183 this.renderer._hiBandSmoothedData = ret[0];
5184 ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
5185 this.renderer._lowBandSmoothedData = ret[0];
5186 }
5187
5188 ret = null;
5189 }
5190 }
5191 return gd;
5192 };
5193
5194
5195 // called within scope of series.
5196 $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
5197 var i;
5198 // get a copy of the options, so we don't modify the original object.
5199 var opts = $.extend(true, {}, options);
5200 var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
5201 var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
5202 var fill = (opts.fill != undefined) ? opts.fill : this.fill;
5203 var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
5204 var xmin, ymin, xmax, ymax;
5205 ctx.save();
5206 if (gd.length) {
5207 if (showLine) {
5208 // if we fill, we'll have to add points to close the curve.
5209 if (fill) {
5210 if (this.fillToZero) {
5211 // have to break line up into shapes at axis crossings
5212 var negativeColor = this.negativeColor;
5213 if (! this.useNegativeColors) {
5214 negativeColor = opts.fillStyle;
5215 }
5216 var isnegative = false;
5217 var posfs = opts.fillStyle;
5218
5219 // if stoking line as well as filling, get a copy of line data.
5220 if (fillAndStroke) {
5221 var fasgd = gd.slice(0);
5222 }
5223 // if not stacked, fill down to axis
5224 if (this.index == 0 || !this._stack) {
5225
5226 var tempgd = [];
5227 var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData;
5228 this._areaPoints = [];
5229 var pyzero = this._yaxis.series_u2p(this.fillToValue);
5230 var pxzero = this._xaxis.series_u2p(this.fillToValue);
5231
5232 opts.closePath = true;
5233
5234 if (this.fillAxis == 'y') {
5235 tempgd.push([gd[0][0], pyzero]);
5236 this._areaPoints.push([gd[0][0], pyzero]);
5237
5238 for (var i=0; i<gd.length-1; i++) {
5239 tempgd.push(gd[i]);
5240 this._areaPoints.push(gd[i]);
5241 // do we have an axis crossing?
5242 if (pd[i][1] * pd[i+1][1] < 0) {
5243 if (pd[i][1] < 0) {
5244 isnegative = true;
5245 opts.fillStyle = negativeColor;
5246 }
5247 else {
5248 isnegative = false;
5249 opts.fillStyle = posfs;
5250 }
5251
5252 var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
5253 tempgd.push([xintercept, pyzero]);
5254 this._areaPoints.push([xintercept, pyzero]);
5255 // now draw this shape and shadow.
5256 if (shadow) {
5257 this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
5258 }
5259 this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
5260 // now empty temp array and continue
5261 tempgd = [[xintercept, pyzero]];
5262 // this._areaPoints = [[xintercept, pyzero]];
5263 }
5264 }
5265 if (pd[gd.length-1][1] < 0) {
5266 isnegative = true;
5267 opts.fillStyle = negativeColor;
5268 }
5269 else {
5270 isnegative = false;
5271 opts.fillStyle = posfs;
5272 }
5273 tempgd.push(gd[gd.length-1]);
5274 this._areaPoints.push(gd[gd.length-1]);
5275 tempgd.push([gd[gd.length-1][0], pyzero]);
5276 this._areaPoints.push([gd[gd.length-1][0], pyzero]);
5277 }
5278 // now draw the last area.
5279 if (shadow) {
5280 this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
5281 }
5282 this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
5283
5284
5285 // var gridymin = this._yaxis.series_u2p(0);
5286 // // IE doesn't return new length on unshift
5287 // gd.unshift([gd[0][0], gridymin]);
5288 // len = gd.length;
5289 // gd.push([gd[len - 1][0], gridymin]);
5290 }
5291 // if stacked, fill to line below
5292 else {
5293 var prev = this._prevGridData;
5294 for (var i=prev.length; i>0; i--) {
5295 gd.push(prev[i-1]);
5296 // this._areaPoints.push(prev[i-1]);
5297 }
5298 if (shadow) {
5299 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5300 }
5301 this._areaPoints = gd;
5302 this.renderer.shapeRenderer.draw(ctx, gd, opts);
5303 }
5304 }
5305 /////////////////////////
5306 // Not filled to zero
5307 ////////////////////////
5308 else {
5309 // if stoking line as well as filling, get a copy of line data.
5310 if (fillAndStroke) {
5311 var fasgd = gd.slice(0);
5312 }
5313 // if not stacked, fill down to axis
5314 if (this.index == 0 || !this._stack) {
5315 // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
5316 var gridymin = ctx.canvas.height;
5317 // IE doesn't return new length on unshift
5318 gd.unshift([gd[0][0], gridymin]);
5319 var len = gd.length;
5320 gd.push([gd[len - 1][0], gridymin]);
5321 }
5322 // if stacked, fill to line below
5323 else {
5324 var prev = this._prevGridData;
5325 for (var i=prev.length; i>0; i--) {
5326 gd.push(prev[i-1]);
5327 }
5328 }
5329 this._areaPoints = gd;
5330
5331 if (shadow) {
5332 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5333 }
5334
5335 this.renderer.shapeRenderer.draw(ctx, gd, opts);
5336 }
5337 if (fillAndStroke) {
5338 var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false});
5339 this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
5340 //////////
5341 // TODO: figure out some way to do shadows nicely
5342 // if (shadow) {
5343 // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts);
5344 // }
5345 // now draw the markers
5346 if (this.markerRenderer.show) {
5347 if (this.renderer.smooth) {
5348 fasgd = this.gridData;
5349 }
5350 for (i=0; i<fasgd.length; i++) {
5351 this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions);
5352 }
5353 }
5354 }
5355 }
5356 else {
5357
5358 if (this.renderer.bands.show) {
5359 var bdat;
5360 var bopts = $.extend(true, {}, opts);
5361 if (this.renderer.bands.showLines) {
5362 bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData;
5363 this.renderer.shapeRenderer.draw(ctx, bdat, opts);
5364 bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData;
5365 this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
5366 }
5367
5368 if (this.renderer.bands.fill) {
5369 if (this.renderer.smooth) {
5370 bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse());
5371 }
5372 else {
5373 bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse());
5374 }
5375 this._areaPoints = bdat;
5376 bopts.closePath = true;
5377 bopts.fill = true;
5378 bopts.fillStyle = this.renderer.bands.fillColor;
5379 this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
5380 }
5381 }
5382
5383 if (shadow) {
5384 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5385 }
5386
5387 this.renderer.shapeRenderer.draw(ctx, gd, opts);
5388 }
5389 }
5390 // calculate the bounding box
5391 var xmin = xmax = ymin = ymax = null;
5392 for (i=0; i<this._areaPoints.length; i++) {
5393 var p = this._areaPoints[i];
5394 if (xmin > p[0] || xmin == null) {
5395 xmin = p[0];
5396 }
5397 if (ymax < p[1] || ymax == null) {
5398 ymax = p[1];
5399 }
5400 if (xmax < p[0] || xmax == null) {
5401 xmax = p[0];
5402 }
5403 if (ymin > p[1] || ymin == null) {
5404 ymin = p[1];
5405 }
5406 }
5407
5408 if (this.type === 'line' && this.renderer.bands.show) {
5409 ymax = this._yaxis.series_u2p(this.renderer.bands._min);
5410 ymin = this._yaxis.series_u2p(this.renderer.bands._max);
5411 }
5412
5413 this._boundingBox = [[xmin, ymax], [xmax, ymin]];
5414
5415 // now draw the markers
5416 if (this.markerRenderer.show && !fill) {
5417 if (this.renderer.smooth) {
5418 gd = this.gridData;
5419 }
5420 for (i=0; i<gd.length; i++) {
5421 if (gd[i][0] != null && gd[i][1] != null) {
5422 this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions);
5423 }
5424 }
5425 }
5426 }
5427
5428 ctx.restore();
5429 };
5430
5431 $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) {
5432 // This is a no-op, shadows drawn with lines.
5433 };
5434
5435 // called with scope of plot.
5436 // make sure to not leave anything highlighted.
5437 function postInit(target, data, options) {
5438 for (var i=0; i<this.series.length; i++) {
5439 if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
5440 // don't allow mouseover and mousedown at same time.
5441 if (this.series[i].highlightMouseOver) {
5442 this.series[i].highlightMouseDown = false;
5443 }
5444 }
5445 }
5446 }
5447
5448 // called within context of plot
5449 // create a canvas which we can draw on.
5450 // insert it before the eventCanvas, so eventCanvas will still capture events.
5451 function postPlotDraw() {
5452 // Memory Leaks patch
5453 if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) {
5454 this.plugins.lineRenderer.highlightCanvas.resetCanvas();
5455 this.plugins.lineRenderer.highlightCanvas = null;
5456 }
5457
5458 this.plugins.lineRenderer.highlightedSeriesIndex = null;
5459 this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
5460
5461 this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this));
5462 this.plugins.lineRenderer.highlightCanvas.setContext();
5463 this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
5464 }
5465
5466 function highlight (plot, sidx, pidx, points) {
5467 var s = plot.series[sidx];
5468 var canvas = plot.plugins.lineRenderer.highlightCanvas;
5469 canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
5470 s._highlightedPoint = pidx;
5471 plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
5472 var opts = {fillStyle: s.highlightColor};
5473 if (s.type === 'line' && s.renderer.bands.show) {
5474 opts.fill = true;
5475 opts.closePath = true;
5476 }
5477 s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
5478 canvas = null;
5479 }
5480
5481 function unhighlight (plot) {
5482 var canvas = plot.plugins.lineRenderer.highlightCanvas;
5483 canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
5484 for (var i=0; i<plot.series.length; i++) {
5485 plot.series[i]._highlightedPoint = null;
5486 }
5487 plot.plugins.lineRenderer.highlightedSeriesIndex = null;
5488 plot.target.trigger('jqplotDataUnhighlight');
5489 canvas = null;
5490 }
5491
5492
5493 function handleMove(ev, gridpos, datapos, neighbor, plot) {
5494 if (neighbor) {
5495 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
5496 var evt1 = jQuery.Event('jqplotDataMouseOver');
5497 evt1.pageX = ev.pageX;
5498 evt1.pageY = ev.pageY;
5499 plot.target.trigger(evt1, ins);
5500 if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
5501 var evt = jQuery.Event('jqplotDataHighlight');
5502 evt.pageX = ev.pageX;
5503 evt.pageY = ev.pageY;
5504 plot.target.trigger(evt, ins);
5505 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
5506 }
5507 }
5508 else if (neighbor == null) {
5509 unhighlight (plot);
5510 }
5511 }
5512
5513 function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
5514 if (neighbor) {
5515 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
5516 if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
5517 var evt = jQuery.Event('jqplotDataHighlight');
5518 evt.pageX = ev.pageX;
5519 evt.pageY = ev.pageY;
5520 plot.target.trigger(evt, ins);
5521 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
5522 }
5523 }
5524 else if (neighbor == null) {
5525 unhighlight (plot);
5526 }
5527 }
5528
5529 function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
5530 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
5531 if (idx != null && plot.series[idx].highlightMouseDown) {
5532 unhighlight(plot);
5533 }
5534 }
5535
5536 function handleClick(ev, gridpos, datapos, neighbor, plot) {
5537 if (neighbor) {
5538 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
5539 var evt = jQuery.Event('jqplotDataClick');
5540 evt.pageX = ev.pageX;
5541 evt.pageY = ev.pageY;
5542 plot.target.trigger(evt, ins);
5543 }
5544 }
5545
5546 function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
5547 if (neighbor) {
5548 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
5549 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
5550 if (idx != null && plot.series[idx].highlightMouseDown) {
5551 unhighlight(plot);
5552 }
5553 var evt = jQuery.Event('jqplotDataRightClick');
5554 evt.pageX = ev.pageX;
5555 evt.pageY = ev.pageY;
5556 plot.target.trigger(evt, ins);
5557 }
5558 }
5559
5560
5561 // class: $.jqplot.LinearAxisRenderer
5562 // The default jqPlot axis renderer, creating a numeric axis.
5563 $.jqplot.LinearAxisRenderer = function() {
5564 };
5565
5566 // called with scope of axis object.
5567 $.jqplot.LinearAxisRenderer.prototype.init = function(options){
5568 // prop: breakPoints
5569 // EXPERIMENTAL!! Use at your own risk!
5570 // Works only with linear axes and the default tick renderer.
5571 // Array of [start, stop] points to create a broken axis.
5572 // Broken axes have a "jump" in them, which is an immediate
5573 // transition from a smaller value to a larger value.
5574 // Currently, axis ticks MUST be manually assigned if using breakPoints
5575 // by using the axis ticks array option.
5576 this.breakPoints = null;
5577 // prop: breakTickLabel
5578 // Label to use at the axis break if breakPoints are specified.
5579 this.breakTickLabel = "&asymp;";
5580 // prop: drawBaseline
5581 // True to draw the axis baseline.
5582 this.drawBaseline = true;
5583 // prop: baselineWidth
5584 // width of the baseline in pixels.
5585 this.baselineWidth = null;
5586 // prop: baselineColor
5587 // CSS color spec for the baseline.
5588 this.baselineColor = null;
5589 // prop: forceTickAt0
5590 // This will ensure that there is always a tick mark at 0.
5591 // If data range is strictly positive or negative,
5592 // this will force 0 to be inside the axis bounds unless
5593 // the appropriate axis pad (pad, padMin or padMax) is set
5594 // to 0, then this will force an axis min or max value at 0.
5595 // This has know effect when any of the following options
5596 // are set: autoscale, min, max, numberTicks or tickInterval.
5597 this.forceTickAt0 = false;
5598 // prop: forceTickAt100
5599 // This will ensure that there is always a tick mark at 100.
5600 // If data range is strictly above or below 100,
5601 // this will force 100 to be inside the axis bounds unless
5602 // the appropriate axis pad (pad, padMin or padMax) is set
5603 // to 0, then this will force an axis min or max value at 100.
5604 // This has know effect when any of the following options
5605 // are set: autoscale, min, max, numberTicks or tickInterval.
5606 this.forceTickAt100 = false;
5607 // prop: tickInset
5608 // Controls the amount to inset the first and last ticks from
5609 // the edges of the grid, in multiples of the tick interval.
5610 // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
5611 // tick interval, etc.
5612 this.tickInset = 0;
5613 // prop: minorTicks
5614 // Number of ticks to add between "major" ticks.
5615 // Major ticks are ticks supplied by user or auto computed.
5616 // Minor ticks cannot be created by user.
5617 this.minorTicks = 0;
5618 this.alignTicks = false;
5619 this._autoFormatString = '';
5620 this._overrideFormatString = false;
5621 this._scalefact = 1.0;
5622 $.extend(true, this, options);
5623 if (this.breakPoints) {
5624 if (!$.isArray(this.breakPoints)) {
5625 this.breakPoints = null;
5626 }
5627 else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) {
5628 this.breakPoints = null;
5629 }
5630 }
5631 if (this.numberTicks != null && this.numberTicks < 2) {
5632 this.numberTicks = 2;
5633 }
5634 this.resetDataBounds();
5635 };
5636
5637 // called with scope of axis
5638 $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) {
5639 if (this.show) {
5640 // populate the axis label and value properties.
5641 // createTicks is a method on the renderer, but
5642 // call it within the scope of the axis.
5643 this.renderer.createTicks.call(this, plot);
5644 // fill a div with axes labels in the right direction.
5645 // Need to pregenerate each axis to get it's bounds and
5646 // position it and the labels correctly on the plot.
5647 var dim=0;
5648 var temp;
5649 // Added for theming.
5650 if (this._elem) {
5651 // Memory Leaks patch
5652 //this._elem.empty();
5653 this._elem.emptyForce();
5654 this._elem = null;
5655 }
5656
5657 this._elem = $(document.createElement('div'));
5658 this._elem.addClass('jqplot-axis jqplot-'+this.name);
5659 this._elem.css('position', 'absolute');
5660
5661
5662 if (this.name == 'xaxis' || this.name == 'x2axis') {
5663 this._elem.width(this._plotDimensions.width);
5664 }
5665 else {
5666 this._elem.height(this._plotDimensions.height);
5667 }
5668
5669 // create a _label object.
5670 this.labelOptions.axis = this.name;
5671 this._label = new this.labelRenderer(this.labelOptions);
5672 if (this._label.show) {
5673 var elem = this._label.draw(ctx, plot);
5674 elem.appendTo(this._elem);
5675 elem = null;
5676 }
5677
5678 var t = this._ticks;
5679 var tick;
5680 for (var i=0; i<t.length; i++) {
5681 tick = t[i];
5682 if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
5683 this._elem.append(tick.draw(ctx, plot));
5684 }
5685 }
5686 tick = null;
5687 t = null;
5688 }
5689 return this._elem;
5690 };
5691
5692 // called with scope of an axis
5693 $.jqplot.LinearAxisRenderer.prototype.reset = function() {
5694 this.min = this._options.min;
5695 this.max = this._options.max;
5696 this.tickInterval = this._options.tickInterval;
5697 this.numberTicks = this._options.numberTicks;
5698 this._autoFormatString = '';
5699 if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
5700 this.tickOptions.formatString = '';
5701 }
5702
5703 // this._ticks = this.__ticks;
5704 };
5705
5706 // called with scope of axis
5707 $.jqplot.LinearAxisRenderer.prototype.set = function() {
5708 var dim = 0;
5709 var temp;
5710 var w = 0;
5711 var h = 0;
5712 var lshow = (this._label == null) ? false : this._label.show;
5713 if (this.show) {
5714 var t = this._ticks;
5715 var tick;
5716 for (var i=0; i<t.length; i++) {
5717 tick = t[i];
5718 if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
5719 if (this.name == 'xaxis' || this.name == 'x2axis') {
5720 temp = tick._elem.outerHeight(true);
5721 }
5722 else {
5723 temp = tick._elem.outerWidth(true);
5724 }
5725 if (temp > dim) {
5726 dim = temp;
5727 }
5728 }
5729 }
5730 tick = null;
5731 t = null;
5732
5733 if (lshow) {
5734 w = this._label._elem.outerWidth(true);
5735 h = this._label._elem.outerHeight(true);
5736 }
5737 if (this.name == 'xaxis') {
5738 dim = dim + h;
5739 this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
5740 }
5741 else if (this.name == 'x2axis') {
5742 dim = dim + h;
5743 this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
5744 }
5745 else if (this.name == 'yaxis') {
5746 dim = dim + w;
5747 this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
5748 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
5749 this._label._elem.css('width', w+'px');
5750 }
5751 }
5752 else {
5753 dim = dim + w;
5754 this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
5755 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
5756 this._label._elem.css('width', w+'px');
5757 }
5758 }
5759 }
5760 };
5761
5762 // called with scope of axis
5763 $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) {
5764 // we're are operating on an axis here
5765 var ticks = this._ticks;
5766 var userTicks = this.ticks;
5767 var name = this.name;
5768 // databounds were set on axis initialization.
5769 var db = this._dataBounds;
5770 var dim, interval;
5771 var min, max;
5772 var pos1, pos2;
5773 var tt, i;
5774 // get a copy of user's settings for min/max.
5775 var userMin = this.min;
5776 var userMax = this.max;
5777 var userNT = this.numberTicks;
5778 var userTI = this.tickInterval;
5779
5780 // if we already have ticks, use them.
5781 // ticks must be in order of increasing value.
5782
5783 if (userTicks.length) {
5784 // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
5785 for (i=0; i<userTicks.length; i++){
5786 var ut = userTicks[i];
5787 var t = new this.tickRenderer(this.tickOptions);
5788 if ($.isArray(ut)) {
5789 t.value = ut[0];
5790 if (this.breakPoints) {
5791 if (ut[0] == this.breakPoints[0]) {
5792 t.label = this.breakTickLabel;
5793 t._breakTick = true;
5794 t.showGridline = false;
5795 t.showMark = false;
5796 }
5797 else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) {
5798 t.show = false;
5799 t.showGridline = false;
5800 t.label = ut[1];
5801 }
5802 else {
5803 t.label = ut[1];
5804 }
5805 }
5806 else {
5807 t.label = ut[1];
5808 }
5809 t.setTick(ut[0], this.name);
5810 this._ticks.push(t);
5811 }
5812
5813 else if ($.isPlainObject(ut)) {
5814 $.extend(true, t, ut);
5815 t.axis = this.name;
5816 this._ticks.push(t);
5817 }
5818
5819 else {
5820 t.value = ut;
5821 if (this.breakPoints) {
5822 if (ut == this.breakPoints[0]) {
5823 t.label = this.breakTickLabel;
5824 t._breakTick = true;
5825 t.showGridline = false;
5826 t.showMark = false;
5827 }
5828 else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) {
5829 t.show = false;
5830 t.showGridline = false;
5831 }
5832 }
5833 t.setTick(ut, this.name);
5834 this._ticks.push(t);
5835 }
5836 }
5837 this.numberTicks = userTicks.length;
5838 this.min = this._ticks[0].value;
5839 this.max = this._ticks[this.numberTicks-1].value;
5840 this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
5841 }
5842
5843 // we don't have any ticks yet, let's make some!
5844 else {
5845 if (name == 'xaxis' || name == 'x2axis') {
5846 dim = this._plotDimensions.width;
5847 }
5848 else {
5849 dim = this._plotDimensions.height;
5850 }
5851
5852 var _numberTicks = this.numberTicks;
5853
5854 // if aligning this axis, use number of ticks from previous axis.
5855 // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
5856 if (this.alignTicks) {
5857 if (this.name === 'x2axis' && plot.axes.xaxis.show) {
5858 _numberTicks = plot.axes.xaxis.numberTicks;
5859 }
5860 else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) {
5861 _numberTicks = plot.axes.yaxis.numberTicks;
5862 }
5863 }
5864
5865 // // if min, max and number of ticks specified, user can't specify interval.
5866 // if (!this.autoscale && this.min != null && this.max != null && this.numberTicks != null) {
5867 // console.log('doing this');
5868 // this.tickInterval = null;
5869 // }
5870
5871 // if max, min, and interval specified and interval won't fit, ignore interval.
5872 // if (this.min != null && this.max != null && this.tickInterval != null) {
5873 // if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) {
5874 // this.tickInterval = null;
5875 // }
5876 // }
5877
5878 min = ((this.min != null) ? this.min : db.min);
5879 max = ((this.max != null) ? this.max : db.max);
5880
5881 var range = max - min;
5882 var rmin, rmax;
5883 var temp;
5884
5885 if (this.tickOptions == null || !this.tickOptions.formatString) {
5886 this._overrideFormatString = true;
5887 }
5888
5889 // Doing complete autoscaling
5890 if (this.min == null && this.max == null && this.tickInterval == null && !this.autoscale) {
5891 // Check if user must have tick at 0 or 100 and ensure they are in range.
5892 // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range.
5893 if (this.forceTickAt0) {
5894 if (min > 0) {
5895 min = 0;
5896 }
5897 if (max < 0) {
5898 max = 0;
5899 }
5900 }
5901
5902 if (this.forceTickAt100) {
5903 if (min > 100) {
5904 min = 100;
5905 }
5906 if (max < 100) {
5907 max = 100;
5908 }
5909 }
5910
5911 var threshold = 30;
5912 var tdim = Math.max(dim, threshold+1);
5913 this._scalefact = (tdim-threshold)/300.0;
5914 var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks);
5915 // calculate a padded max and min, points should be less than these
5916 // so that they aren't too close to the edges of the plot.
5917 // User can adjust how much padding is allowed with pad, padMin and PadMax options.
5918 var tumin = min + range*(this.padMin - 1);
5919 var tumax = max - range*(this.padMax - 1);
5920
5921 // if they're equal, we shouldn't have to do anything, right?
5922 // if (min <=tumin || max >= tumax) {
5923 if (min <tumin || max > tumax) {
5924 tumin = min - range*(this.padMin - 1);
5925 tumax = max + range*(this.padMax - 1);
5926 ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks);
5927 }
5928
5929 this.min = ret[0];
5930 this.max = ret[1];
5931 // if numberTicks specified, it should return the same.
5932 this.numberTicks = ret[2];
5933 this._autoFormatString = ret[3];
5934 this.tickInterval = ret[4];
5935 }
5936
5937 // User has specified some axis scale related option, can use auto algorithm
5938 else {
5939
5940 // if min and max are same, space them out a bit
5941 if (min == max) {
5942 var adj = 0.05;
5943 if (min > 0) {
5944 adj = Math.max(Math.log(min)/Math.LN10, 0.05);
5945 }
5946 min -= adj;
5947 max += adj;
5948 }
5949
5950 // autoscale. Can't autoscale if min or max is supplied.
5951 // Will use numberTicks and tickInterval if supplied. Ticks
5952 // across multiple axes may not line up depending on how
5953 // bars are to be plotted.
5954 if (this.autoscale && this.min == null && this.max == null) {
5955 var rrange, ti, margin;
5956 var forceMinZero = false;
5957 var forceZeroLine = false;
5958 var intervals = {min:null, max:null, average:null, stddev:null};
5959 // if any series are bars, or if any are fill to zero, and if this
5960 // is the axis to fill toward, check to see if we can start axis at zero.
5961 for (var i=0; i<this._series.length; i++) {
5962 var s = this._series[i];
5963 var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name;
5964 // check to see if this is the fill axis
5965 if (this.name == faname) {
5966 var vals = s._plotValues[s.fillAxis];
5967 var vmin = vals[0];
5968 var vmax = vals[0];
5969 for (var j=1; j<vals.length; j++) {
5970 if (vals[j] < vmin) {
5971 vmin = vals[j];
5972 }
5973 else if (vals[j] > vmax) {
5974 vmax = vals[j];
5975 }
5976 }
5977 var dp = (vmax - vmin) / vmax;
5978 // is this sries a bar?
5979 if (s.renderer.constructor == $.jqplot.BarRenderer) {
5980 // if no negative values and could also check range.
5981 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
5982 forceMinZero = true;
5983 }
5984 else {
5985 forceMinZero = false;
5986 if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) {
5987 forceZeroLine = true;
5988 }
5989 else {
5990 forceZeroLine = false;
5991 }
5992 }
5993 }
5994
5995 // if not a bar and filling, use appropriate method.
5996 else if (s.fill) {
5997 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
5998 forceMinZero = true;
5999 }
6000 else if (vmin < 0 && vmax > 0 && s.fillToZero) {
6001 forceMinZero = false;
6002 forceZeroLine = true;
6003 }
6004 else {
6005 forceMinZero = false;
6006 forceZeroLine = false;
6007 }
6008 }
6009
6010 // if not a bar and not filling, only change existing state
6011 // if it doesn't make sense
6012 else if (vmin < 0) {
6013 forceMinZero = false;
6014 }
6015 }
6016 }
6017
6018 // check if we need make axis min at 0.
6019 if (forceMinZero) {
6020 // compute number of ticks
6021 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6022 this.min = 0;
6023 userMin = 0;
6024 // what order is this range?
6025 // what tick interval does that give us?
6026 ti = max/(this.numberTicks-1);
6027 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6028 if (ti/temp == parseInt(ti/temp, 10)) {
6029 ti += temp;
6030 }
6031 this.tickInterval = Math.ceil(ti/temp) * temp;
6032 this.max = this.tickInterval * (this.numberTicks - 1);
6033 }
6034
6035 // check if we need to make sure there is a tick at 0.
6036 else if (forceZeroLine) {
6037 // compute number of ticks
6038 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6039 var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1));
6040 var ntmax = this.numberTicks - 1 - ntmin;
6041 ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax));
6042 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6043 this.tickInterval = Math.ceil(ti/temp) * temp;
6044 this.max = this.tickInterval * ntmax;
6045 this.min = -this.tickInterval * ntmin;
6046 }
6047
6048 // if nothing else, do autoscaling which will try to line up ticks across axes.
6049 else {
6050 if (this.numberTicks == null){
6051 if (this.tickInterval) {
6052 this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
6053 }
6054 else {
6055 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6056 }
6057 }
6058
6059 if (this.tickInterval == null) {
6060 // get a tick interval
6061 ti = range/(this.numberTicks - 1);
6062
6063 if (ti < 1) {
6064 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6065 }
6066 else {
6067 temp = 1;
6068 }
6069 this.tickInterval = Math.ceil(ti*temp*this.pad)/temp;
6070 }
6071 else {
6072 temp = 1 / this.tickInterval;
6073 }
6074
6075 // try to compute a nicer, more even tick interval
6076 // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10));
6077 // this.tickInterval = Math.ceil(ti/temp) * temp;
6078 rrange = this.tickInterval * (this.numberTicks - 1);
6079 margin = (rrange - range)/2;
6080
6081 if (this.min == null) {
6082 this.min = Math.floor(temp*(min-margin))/temp;
6083 }
6084 if (this.max == null) {
6085 this.max = this.min + rrange;
6086 }
6087 }
6088
6089 // Compute a somewhat decent format string if it is needed.
6090 // get precision of interval and determine a format string.
6091 var sf = $.jqplot.getSignificantFigures(this.tickInterval);
6092
6093 var fstr;
6094
6095 // if we have only a whole number, use integer formatting
6096 if (sf.digitsLeft >= sf.significantDigits) {
6097 fstr = '%d';
6098 }
6099
6100 else {
6101 var temp = Math.max(0, 5 - sf.digitsLeft);
6102 temp = Math.min(temp, sf.digitsRight);
6103 fstr = '%.'+ temp + 'f';
6104 }
6105
6106 this._autoFormatString = fstr;
6107 }
6108
6109 // Use the default algorithm which pads each axis to make the chart
6110 // centered nicely on the grid.
6111 else {
6112
6113 rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1);
6114 rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1);
6115 range = rmax - rmin;
6116
6117 if (this.numberTicks == null){
6118 // if tickInterval is specified by user, we will ignore computed maximum.
6119 // max will be equal or greater to fit even # of ticks.
6120 if (this.tickInterval != null) {
6121 this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1;
6122 }
6123 else if (dim > 100) {
6124 this.numberTicks = parseInt(3+(dim-100)/75, 10);
6125 }
6126 else {
6127 this.numberTicks = 2;
6128 }
6129 }
6130
6131 if (this.tickInterval == null) {
6132 this.tickInterval = range / (this.numberTicks-1);
6133 }
6134
6135 if (this.max == null) {
6136 rmax = rmin + this.tickInterval*(this.numberTicks - 1);
6137 }
6138 if (this.min == null) {
6139 rmin = rmax - this.tickInterval*(this.numberTicks - 1);
6140 }
6141
6142 // get precision of interval and determine a format string.
6143 var sf = $.jqplot.getSignificantFigures(this.tickInterval);
6144
6145 var fstr;
6146
6147 // if we have only a whole number, use integer formatting
6148 if (sf.digitsLeft >= sf.significantDigits) {
6149 fstr = '%d';
6150 }
6151
6152 else {
6153 var temp = Math.max(0, 5 - sf.digitsLeft);
6154 temp = Math.min(temp, sf.digitsRight);
6155 fstr = '%.'+ temp + 'f';
6156 }
6157
6158
6159 this._autoFormatString = fstr;
6160
6161 this.min = rmin;
6162 this.max = rmax;
6163 }
6164
6165 if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') {
6166 // fix for misleading tick display with small range and low precision.
6167 range = this.max - this.min;
6168 // figure out precision
6169 var temptick = new this.tickRenderer(this.tickOptions);
6170 // use the tick formatString or, the default.
6171 var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString;
6172 var fs = fs.match($.jqplot.sprintf.regex)[0];
6173 var precision = 0;
6174 if (fs) {
6175 if (fs.search(/[fFeEgGpP]/) > -1) {
6176 var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/);
6177 if (m) {
6178 precision = parseInt(m[1], 10);
6179 }
6180 else {
6181 precision = 6;
6182 }
6183 }
6184 else if (fs.search(/[di]/) > -1) {
6185 precision = 0;
6186 }
6187 // fact will be <= 1;
6188 var fact = Math.pow(10, -precision);
6189 if (this.tickInterval < fact) {
6190 // need to correct underrange
6191 if (userNT == null && userTI == null) {
6192 this.tickInterval = fact;
6193 if (userMax == null && userMin == null) {
6194 // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact;
6195 this.min = Math.floor(this._dataBounds.min/fact) * fact;
6196 if (this.min == this._dataBounds.min) {
6197 this.min = this._dataBounds.min - this.tickInterval;
6198 }
6199 // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact;
6200 this.max = Math.ceil(this._dataBounds.max/fact) * fact;
6201 if (this.max == this._dataBounds.max) {
6202 this.max = this._dataBounds.max + this.tickInterval;
6203 }
6204 var n = (this.max - this.min)/this.tickInterval;
6205 n = n.toFixed(11);
6206 n = Math.ceil(n);
6207 this.numberTicks = n + 1;
6208 }
6209 else if (userMax == null) {
6210 // add one tick for top of range.
6211 var n = (this._dataBounds.max - this.min) / this.tickInterval;
6212 n = n.toFixed(11);
6213 this.numberTicks = Math.ceil(n) + 2;
6214 this.max = this.min + this.tickInterval * (this.numberTicks-1);
6215 }
6216 else if (userMin == null) {
6217 // add one tick for bottom of range.
6218 var n = (this.max - this._dataBounds.min) / this.tickInterval;
6219 n = n.toFixed(11);
6220 this.numberTicks = Math.ceil(n) + 2;
6221 this.min = this.max - this.tickInterval * (this.numberTicks-1);
6222 }
6223 else {
6224 // calculate a number of ticks so max is within axis scale
6225 this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1;
6226 // if user's min and max don't fit evenly in ticks, adjust.
6227 // This takes care of cases such as user min set to 0, max set to 3.5 but tick
6228 // format string set to %d (integer ticks)
6229 this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision);
6230 this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision);
6231 // this.max = this.min + this.tickInterval*(this.numberTicks-1);
6232 this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1;
6233 }
6234 }
6235 }
6236 }
6237 }
6238
6239 }
6240
6241 if (this._overrideFormatString && this._autoFormatString != '') {
6242 this.tickOptions = this.tickOptions || {};
6243 this.tickOptions.formatString = this._autoFormatString;
6244 }
6245
6246 var t, to;
6247 for (var i=0; i<this.numberTicks; i++){
6248 tt = this.min + i * this.tickInterval;
6249 t = new this.tickRenderer(this.tickOptions);
6250 // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
6251
6252 t.setTick(tt, this.name);
6253 this._ticks.push(t);
6254
6255 if (i < this.numberTicks - 1) {
6256 for (var j=0; j<this.minorTicks; j++) {
6257 tt += this.tickInterval/(this.minorTicks+1);
6258 to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true});
6259 t = new this.tickRenderer(to);
6260 this._ticks.push(t);
6261 }
6262 }
6263 t = null;
6264 }
6265 }
6266
6267 if (this.tickInset) {
6268 this.min = this.min - this.tickInset * this.tickInterval;
6269 this.max = this.max + this.tickInset * this.tickInterval;
6270 }
6271
6272 ticks = null;
6273 };
6274
6275 // Used to reset just the values of the ticks and then repack, which will
6276 // recalculate the positioning functions. It is assuemd that the
6277 // number of ticks is the same and the values of the new array are at the
6278 // proper interval.
6279 // This method needs to be called with the scope of an axis object, like:
6280 //
6281 // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr);
6282 //
6283 $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) {
6284 if ($.isArray(opts) && opts.length == this._ticks.length) {
6285 var t;
6286 for (var i=0; i<opts.length; i++) {
6287 t = this._ticks[i];
6288 t.value = opts[i];
6289 t.label = t.formatter(t.formatString, opts[i]);
6290 t.label = t.prefix + t.label;
6291 t._elem.html(t.label);
6292 }
6293 t = null;
6294 this.min = $.jqplot.arrayMin(opts);
6295 this.max = $.jqplot.arrayMax(opts);
6296 this.pack();
6297 }
6298 // Not implemented yet.
6299 // else if ($.isPlainObject(opts)) {
6300 //
6301 // }
6302 };
6303
6304 // called with scope of axis
6305 $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) {
6306 // Add defaults for repacking from resetTickValues function.
6307 pos = pos || {};
6308 offsets = offsets || this._offsets;
6309
6310 var ticks = this._ticks;
6311 var max = this.max;
6312 var min = this.min;
6313 var offmax = offsets.max;
6314 var offmin = offsets.min;
6315 var lshow = (this._label == null) ? false : this._label.show;
6316
6317 for (var p in pos) {
6318 this._elem.css(p, pos[p]);
6319 }
6320
6321 this._offsets = offsets;
6322 // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
6323 var pixellength = offmax - offmin;
6324 var unitlength = max - min;
6325
6326 // point to unit and unit to point conversions references to Plot DOM element top left corner.
6327 if (this.breakPoints) {
6328 unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0];
6329
6330 this.p2u = function(p){
6331 return (p - offmin) * unitlength / pixellength + min;
6332 };
6333
6334 this.u2p = function(u){
6335 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6336 u = this.breakPoints[0];
6337 }
6338 if (u <= this.breakPoints[0]) {
6339 return (u - min) * pixellength / unitlength + offmin;
6340 }
6341 else {
6342 return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin;
6343 }
6344 };
6345
6346 if (this.name.charAt(0) == 'x'){
6347 this.series_u2p = function(u){
6348 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6349 u = this.breakPoints[0];
6350 }
6351 if (u <= this.breakPoints[0]) {
6352 return (u - min) * pixellength / unitlength;
6353 }
6354 else {
6355 return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength;
6356 }
6357 };
6358 this.series_p2u = function(p){
6359 return p * unitlength / pixellength + min;
6360 };
6361 }
6362
6363 else {
6364 this.series_u2p = function(u){
6365 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6366 u = this.breakPoints[0];
6367 }
6368 if (u >= this.breakPoints[1]) {
6369 return (u - max) * pixellength / unitlength;
6370 }
6371 else {
6372 return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength;
6373 }
6374 };
6375 this.series_p2u = function(p){
6376 return p * unitlength / pixellength + max;
6377 };
6378 }
6379 }
6380 else {
6381 this.p2u = function(p){
6382 return (p - offmin) * unitlength / pixellength + min;
6383 };
6384
6385 this.u2p = function(u){
6386 return (u - min) * pixellength / unitlength + offmin;
6387 };
6388
6389 if (this.name == 'xaxis' || this.name == 'x2axis'){
6390 this.series_u2p = function(u){
6391 return (u - min) * pixellength / unitlength;
6392 };
6393 this.series_p2u = function(p){
6394 return p * unitlength / pixellength + min;
6395 };
6396 }
6397
6398 else {
6399 this.series_u2p = function(u){
6400 return (u - max) * pixellength / unitlength;
6401 };
6402 this.series_p2u = function(p){
6403 return p * unitlength / pixellength + max;
6404 };
6405 }
6406 }
6407
6408 if (this.show) {
6409 if (this.name == 'xaxis' || this.name == 'x2axis') {
6410 for (var i=0; i<ticks.length; i++) {
6411 var t = ticks[i];
6412 if (t.show && t.showLabel) {
6413 var shim;
6414
6415 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
6416 // will need to adjust auto positioning based on which axis this is.
6417 var temp = (this.name == 'xaxis') ? 1 : -1;
6418 switch (t.labelPosition) {
6419 case 'auto':
6420 // position at end
6421 if (temp * t.angle < 0) {
6422 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6423 }
6424 // position at start
6425 else {
6426 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6427 }
6428 break;
6429 case 'end':
6430 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6431 break;
6432 case 'start':
6433 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6434 break;
6435 case 'middle':
6436 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6437 break;
6438 default:
6439 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6440 break;
6441 }
6442 }
6443 else {
6444 shim = -t.getWidth()/2;
6445 }
6446 var val = this.u2p(t.value) + shim + 'px';
6447 t._elem.css('left', val);
6448 t.pack();
6449 }
6450 }
6451 if (lshow) {
6452 var w = this._label._elem.outerWidth(true);
6453 this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
6454 if (this.name == 'xaxis') {
6455 this._label._elem.css('bottom', '0px');
6456 }
6457 else {
6458 this._label._elem.css('top', '0px');
6459 }
6460 this._label.pack();
6461 }
6462 }
6463 else {
6464 for (var i=0; i<ticks.length; i++) {
6465 var t = ticks[i];
6466 if (t.show && t.showLabel) {
6467 var shim;
6468 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
6469 // will need to adjust auto positioning based on which axis this is.
6470 var temp = (this.name == 'yaxis') ? 1 : -1;
6471 switch (t.labelPosition) {
6472 case 'auto':
6473 // position at end
6474 case 'end':
6475 if (temp * t.angle < 0) {
6476 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
6477 }
6478 else {
6479 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
6480 }
6481 break;
6482 case 'start':
6483 if (t.angle > 0) {
6484 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
6485 }
6486 else {
6487 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
6488 }
6489 break;
6490 case 'middle':
6491 // if (t.angle > 0) {
6492 // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6493 // }
6494 // else {
6495 // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6496 // }
6497 shim = -t.getHeight()/2;
6498 break;
6499 default:
6500 shim = -t.getHeight()/2;
6501 break;
6502 }
6503 }
6504 else {
6505 shim = -t.getHeight()/2;
6506 }
6507
6508 var val = this.u2p(t.value) + shim + 'px';
6509 t._elem.css('top', val);
6510 t.pack();
6511 }
6512 }
6513 if (lshow) {
6514 var h = this._label._elem.outerHeight(true);
6515 this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
6516 if (this.name == 'yaxis') {
6517 this._label._elem.css('left', '0px');
6518 }
6519 else {
6520 this._label._elem.css('right', '0px');
6521 }
6522 this._label.pack();
6523 }
6524 }
6525 }
6526
6527 ticks = null;
6528 };
6529
6530
6531 /**
6532 * The following code was generaously given to me a while back by Scott Prahl.
6533 * He did a good job at computing axes min, max and number of ticks for the
6534 * case where the user has not set any scale related parameters (tickInterval,
6535 * numberTicks, min or max). I had ignored this use case for a long time,
6536 * focusing on the more difficult case where user has set some option controlling
6537 * tick generation. Anyway, about time I got this into jqPlot.
6538 * Thanks Scott!!
6539 */
6540
6541 /**
6542 * Copyright (c) 2010 Scott Prahl
6543 * The next three routines are currently available for use in all personal
6544 * or commercial projects under both the MIT and GPL version 2.0 licenses.
6545 * This means that you can choose the license that best suits your project
6546 * and use it accordingly.
6547 */
6548
6549 // A good format string depends on the interval. If the interval is greater
6550 // than 1 then there is no need to show any decimal digits. If it is < 1.0, then
6551 // use the magnitude of the interval to determine the number of digits to show.
6552 function bestFormatString (interval)
6553 {
6554 var fstr;
6555 interval = Math.abs(interval);
6556 if (interval >= 10) {
6557 fstr = '%d';
6558 }
6559
6560 else if (interval > 1) {
6561 if (interval === parseInt(interval)) {
6562 fstr = '%d';
6563 }
6564 else {
6565 fstr = '%.1f';
6566 }
6567 }
6568
6569 else {
6570 var expv = -Math.floor(Math.log(interval)/Math.LN10);
6571 fstr = '%.' + expv + 'f';
6572 }
6573
6574 return fstr;
6575 }
6576
6577 var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
6578
6579 var _getLowerFactor = function(f) {
6580 var i = _factors.indexOf(f);
6581 if (i > 0) {
6582 return _factors[i-1];
6583 }
6584 else {
6585 return _factors[_factors.length - 1] / 100;
6586 }
6587 };
6588
6589 var _getHigherFactor = function(f) {
6590 var i = _factors.indexOf(f);
6591 if (i < _factors.length-1) {
6592 return _factors[i+1];
6593 }
6594 else {
6595 return _factors[0] * 100;
6596 }
6597 };
6598
6599 // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
6600 // it is based soley on the range and number of ticks. So if user specifies
6601 // number of ticks, use this.
6602 function bestInterval(range, numberTicks) {
6603 var minimum = range / (numberTicks - 1);
6604 var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
6605 var residual = minimum / magnitude;
6606 var interval;
6607 // "nicest" ranges are 1, 2, 5 or powers of these.
6608 // for magnitudes below 1, only allow these.
6609 if (magnitude < 1) {
6610 if (residual > 5) {
6611 interval = 10 * magnitude;
6612 }
6613 else if (residual > 2) {
6614 interval = 5 * magnitude;
6615 }
6616 else if (residual > 1) {
6617 interval = 2 * magnitude;
6618 }
6619 else {
6620 interval = magnitude;
6621 }
6622 }
6623 // for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
6624 // this helps a lot with poor choices for number of ticks.
6625 else {
6626 if (residual > 5) {
6627 interval = 10 * magnitude;
6628 }
6629 else if (residual > 4) {
6630 interval = 5 * magnitude;
6631 }
6632 else if (residual > 3) {
6633 interval = 4 * magnitude;
6634 }
6635 else if (residual > 2) {
6636 interval = 3 * magnitude;
6637 }
6638 else if (residual > 1) {
6639 interval = 2 * magnitude;
6640 }
6641 else {
6642 interval = magnitude;
6643 }
6644 }
6645
6646 return interval;
6647 }
6648
6649 // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
6650 // it is based soley on the range of data, number of ticks must be computed later.
6651 function bestLinearInterval(range, scalefact) {
6652 var expv = Math.floor(Math.log(range)/Math.LN10);
6653 var magnitude = Math.pow(10, expv);
6654 // 0 < f < 10
6655 var f = range / magnitude;
6656 var fact;
6657 // for large plots, scalefact will decrease f and increase number of ticks.
6658 // for small plots, scalefact will increase f and decrease number of ticks.
6659 f = f/scalefact;
6660
6661 // for large plots, smaller interval, more ticks.
6662 if (f<=0.38) {
6663 fact = 0.1;
6664 }
6665 else if (f<=1.6) {
6666 fact = 0.2;
6667 }
6668 else if (f<=4.0) {
6669 fact = 0.5;
6670 }
6671 else if (f<=8.0) {
6672 fact = 1.0;
6673 }
6674 // for very small plots, larger interval, less ticks in number ticks
6675 else if (f<=16.0) {
6676 fact = 2;
6677 }
6678 else {
6679 fact = 5;
6680 }
6681
6682 return fact*magnitude;
6683 }
6684
6685 function bestLinearComponents(range, scalefact) {
6686 var expv = Math.floor(Math.log(range)/Math.LN10);
6687 var magnitude = Math.pow(10, expv);
6688 // 0 < f < 10
6689 var f = range / magnitude;
6690 var interval;
6691 var fact;
6692 // for large plots, scalefact will decrease f and increase number of ticks.
6693 // for small plots, scalefact will increase f and decrease number of ticks.
6694 f = f/scalefact;
6695
6696 // for large plots, smaller interval, more ticks.
6697 if (f<=0.38) {
6698 fact = 0.1;
6699 }
6700 else if (f<=1.6) {
6701 fact = 0.2;
6702 }
6703 else if (f<=4.0) {
6704 fact = 0.5;
6705 }
6706 else if (f<=8.0) {
6707 fact = 1.0;
6708 }
6709 // for very small plots, larger interval, less ticks in number ticks
6710 else if (f<=16.0) {
6711 fact = 2;
6712 }
6713 // else if (f<=20.0) {
6714 // fact = 3;
6715 // }
6716 // else if (f<=24.0) {
6717 // fact = 4;
6718 // }
6719 else {
6720 fact = 5;
6721 }
6722
6723 interval = fact * magnitude;
6724
6725 return [interval, fact, magnitude];
6726 }
6727
6728 // Given the min and max for a dataset, return suitable endpoints
6729 // for the graphing, a good number for the number of ticks, and a
6730 // format string so that extraneous digits are not displayed.
6731 // returned is an array containing [min, max, nTicks, format]
6732 $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks) {
6733 // if endpoints are equal try to include zero otherwise include one
6734 if (axis_min === axis_max) {
6735 axis_max = (axis_max) ? 0 : 1;
6736 }
6737
6738 scalefact = scalefact || 1.0;
6739
6740 // make sure range is positive
6741 if (axis_max < axis_min) {
6742 var a = axis_max;
6743 axis_max = axis_min;
6744 axis_min = a;
6745 }
6746
6747 var r = [];
6748 var ss = bestLinearInterval(axis_max - axis_min, scalefact);
6749
6750 if (numberTicks == null) {
6751
6752 // Figure out the axis min, max and number of ticks
6753 // the min and max will be some multiple of the tick interval,
6754 // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
6755 // axis min is negative, 0 will be a tick.
6756 r[0] = Math.floor(axis_min / ss) * ss; // min
6757 r[1] = Math.ceil(axis_max / ss) * ss; // max
6758 r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks
6759 r[3] = bestFormatString(ss); // format string
6760 r[4] = ss; // tick Interval
6761 }
6762
6763 else {
6764 var tempr = [];
6765
6766 // Figure out the axis min, max and number of ticks
6767 // the min and max will be some multiple of the tick interval,
6768 // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
6769 // axis min is negative, 0 will be a tick.
6770 tempr[0] = Math.floor(axis_min / ss) * ss; // min
6771 tempr[1] = Math.ceil(axis_max / ss) * ss; // max
6772 tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks
6773 tempr[3] = bestFormatString(ss); // format string
6774 tempr[4] = ss; // tick Interval
6775
6776 // first, see if we happen to get the right number of ticks
6777 if (tempr[2] === numberTicks) {
6778 r = tempr;
6779 }
6780
6781 else {
6782
6783 var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
6784
6785 r[0] = tempr[0];
6786 r[2] = numberTicks;
6787 r[4] = newti;
6788 r[3] = bestFormatString(newti);
6789 r[1] = r[0] + (r[2] - 1) * r[4]; // max
6790 }
6791 }
6792
6793 return r;
6794 };
6795
6796 $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
6797 $.jqplot.LinearTickGenerator.bestInterval = bestInterval;
6798 $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
6799
6800
6801 // class: $.jqplot.MarkerRenderer
6802 // The default jqPlot marker renderer, rendering the points on the line.
6803 $.jqplot.MarkerRenderer = function(options){
6804 // Group: Properties
6805
6806 // prop: show
6807 // wether or not to show the marker.
6808 this.show = true;
6809 // prop: style
6810 // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare
6811 this.style = 'filledCircle';
6812 // prop: lineWidth
6813 // size of the line for non-filled markers.
6814 this.lineWidth = 2;
6815 // prop: size
6816 // Size of the marker (diameter or circle, length of edge of square, etc.)
6817 this.size = 9.0;
6818 // prop: color
6819 // color of marker. Will be set to color of series by default on init.
6820 this.color = '#666666';
6821 // prop: shadow
6822 // wether or not to draw a shadow on the line
6823 this.shadow = true;
6824 // prop: shadowAngle
6825 // Shadow angle in degrees
6826 this.shadowAngle = 45;
6827 // prop: shadowOffset
6828 // Shadow offset from line in pixels
6829 this.shadowOffset = 1;
6830 // prop: shadowDepth
6831 // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
6832 this.shadowDepth = 3;
6833 // prop: shadowAlpha
6834 // Alpha channel transparency of shadow. 0 = transparent.
6835 this.shadowAlpha = '0.07';
6836 // prop: shadowRenderer
6837 // Renderer that will draws the shadows on the marker.
6838 this.shadowRenderer = new $.jqplot.ShadowRenderer();
6839 // prop: shapeRenderer
6840 // Renderer that will draw the marker.
6841 this.shapeRenderer = new $.jqplot.ShapeRenderer();
6842
6843 $.extend(true, this, options);
6844 };
6845
6846 $.jqplot.MarkerRenderer.prototype.init = function(options) {
6847 $.extend(true, this, options);
6848 var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true};
6849 if (this.style.indexOf('filled') != -1) {
6850 sdopt.fill = true;
6851 }
6852 if (this.style.indexOf('ircle') != -1) {
6853 sdopt.isarc = true;
6854 sdopt.closePath = false;
6855 }
6856 this.shadowRenderer.init(sdopt);
6857
6858 var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true};
6859 if (this.style.indexOf('filled') != -1) {
6860 shopt.fill = true;
6861 }
6862 if (this.style.indexOf('ircle') != -1) {
6863 shopt.isarc = true;
6864 shopt.closePath = false;
6865 }
6866 this.shapeRenderer.init(shopt);
6867 };
6868
6869 $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) {
6870 var stretch = 1.2;
6871 var dx = this.size/2/stretch;
6872 var dy = this.size/2*stretch;
6873 var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]];
6874 if (this.shadow) {
6875 this.shadowRenderer.draw(ctx, points);
6876 }
6877 this.shapeRenderer.draw(ctx, points, options);
6878 };
6879
6880 $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) {
6881 var stretch = 1.0;
6882 var dx = this.size/2*stretch;
6883 var dy = this.size/2*stretch;
6884 var points1 = [[x, y-dy], [x, y+dy]];
6885 var points2 = [[x+dx, y], [x-dx, y]];
6886 var opts = $.extend(true, {}, this.options, {closePath:false});
6887 if (this.shadow) {
6888 this.shadowRenderer.draw(ctx, points1, {closePath:false});
6889 this.shadowRenderer.draw(ctx, points2, {closePath:false});
6890 }
6891 this.shapeRenderer.draw(ctx, points1, opts);
6892 this.shapeRenderer.draw(ctx, points2, opts);
6893 };
6894
6895 $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) {
6896 var stretch = 1.0;
6897 var dx = this.size/2*stretch;
6898 var dy = this.size/2*stretch;
6899 var opts = $.extend(true, {}, this.options, {closePath:false});
6900 var points1 = [[x-dx, y-dy], [x+dx, y+dy]];
6901 var points2 = [[x-dx, y+dy], [x+dx, y-dy]];
6902 if (this.shadow) {
6903 this.shadowRenderer.draw(ctx, points1, {closePath:false});
6904 this.shadowRenderer.draw(ctx, points2, {closePath:false});
6905 }
6906 this.shapeRenderer.draw(ctx, points1, opts);
6907 this.shapeRenderer.draw(ctx, points2, opts);
6908 };
6909
6910 $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) {
6911 var stretch = 1.0;
6912 var dx = this.size/2*stretch;
6913 var dy = this.size/2*stretch;
6914 var points = [[x-dx, y], [x+dx, y]];
6915 if (this.shadow) {
6916 this.shadowRenderer.draw(ctx, points);
6917 }
6918 this.shapeRenderer.draw(ctx, points, options);
6919 };
6920
6921 $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) {
6922 var points = [p1, p2];
6923 if (this.shadow) {
6924 this.shadowRenderer.draw(ctx, points);
6925 }
6926 this.shapeRenderer.draw(ctx, points, options);
6927 };
6928
6929 $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) {
6930 var stretch = 1.0;
6931 var dx = this.size/2/stretch;
6932 var dy = this.size/2*stretch;
6933 var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]];
6934 if (this.shadow) {
6935 this.shadowRenderer.draw(ctx, points);
6936 }
6937 this.shapeRenderer.draw(ctx, points, options);
6938 };
6939
6940 $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) {
6941 var radius = this.size/2;
6942 var end = 2*Math.PI;
6943 var points = [x, y, radius, 0, end, true];
6944 if (this.shadow) {
6945 this.shadowRenderer.draw(ctx, points);
6946 }
6947 this.shapeRenderer.draw(ctx, points, options);
6948 };
6949
6950 $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) {
6951 options = options || {};
6952 // hack here b/c shape renderer uses canvas based color style options
6953 // and marker uses css style names.
6954 if (options.show == null || options.show != false) {
6955 if (options.color && !options.fillStyle) {
6956 options.fillStyle = options.color;
6957 }
6958 if (options.color && !options.strokeStyle) {
6959 options.strokeStyle = options.color;
6960 }
6961 switch (this.style) {
6962 case 'diamond':
6963 this.drawDiamond(x,y,ctx, false, options);
6964 break;
6965 case 'filledDiamond':
6966 this.drawDiamond(x,y,ctx, true, options);
6967 break;
6968 case 'circle':
6969 this.drawCircle(x,y,ctx, false, options);
6970 break;
6971 case 'filledCircle':
6972 this.drawCircle(x,y,ctx, true, options);
6973 break;
6974 case 'square':
6975 this.drawSquare(x,y,ctx, false, options);
6976 break;
6977 case 'filledSquare':
6978 this.drawSquare(x,y,ctx, true, options);
6979 break;
6980 case 'x':
6981 this.drawX(x,y,ctx, true, options);
6982 break;
6983 case 'plus':
6984 this.drawPlus(x,y,ctx, true, options);
6985 break;
6986 case 'dash':
6987 this.drawDash(x,y,ctx, true, options);
6988 break;
6989 case 'line':
6990 this.drawLine(x, y, ctx, false, options);
6991 break;
6992 default:
6993 this.drawDiamond(x,y,ctx, false, options);
6994 break;
6995 }
6996 }
6997 };
6998
6999 // class: $.jqplot.shadowRenderer
7000 // The default jqPlot shadow renderer, rendering shadows behind shapes.
7001 $.jqplot.ShadowRenderer = function(options){
7002 // Group: Properties
7003
7004 // prop: angle
7005 // Angle of the shadow in degrees. Measured counter-clockwise from the x axis.
7006 this.angle = 45;
7007 // prop: offset
7008 // Pixel offset at the given shadow angle of each shadow stroke from the last stroke.
7009 this.offset = 1;
7010 // prop: alpha
7011 // alpha transparency of shadow stroke.
7012 this.alpha = 0.07;
7013 // prop: lineWidth
7014 // width of the shadow line stroke.
7015 this.lineWidth = 1.5;
7016 // prop: lineJoin
7017 // How line segments of the shadow are joined.
7018 this.lineJoin = 'miter';
7019 // prop: lineCap
7020 // how ends of the shadow line are rendered.
7021 this.lineCap = 'round';
7022 // prop; closePath
7023 // whether line path segment is closed upon itself.
7024 this.closePath = false;
7025 // prop: fill
7026 // whether to fill the shape.
7027 this.fill = false;
7028 // prop: depth
7029 // how many times the shadow is stroked. Each stroke will be offset by offset at angle degrees.
7030 this.depth = 3;
7031 this.strokeStyle = 'rgba(0,0,0,0.1)';
7032 // prop: isarc
7033 // wether the shadow is an arc or not.
7034 this.isarc = false;
7035
7036 $.extend(true, this, options);
7037 };
7038
7039 $.jqplot.ShadowRenderer.prototype.init = function(options) {
7040 $.extend(true, this, options);
7041 };
7042
7043 // function: draw
7044 // draws an transparent black (i.e. gray) shadow.
7045 //
7046 // ctx - canvas drawing context
7047 // points - array of points or [x, y, radius, start angle (rad), end angle (rad)]
7048 $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) {
7049 ctx.save();
7050 var opts = (options != null) ? options : {};
7051 var fill = (opts.fill != null) ? opts.fill : this.fill;
7052 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
7053 var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
7054 var offset = (opts.offset != null) ? opts.offset : this.offset;
7055 var alpha = (opts.alpha != null) ? opts.alpha : this.alpha;
7056 var depth = (opts.depth != null) ? opts.depth : this.depth;
7057 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
7058 var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
7059 ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth;
7060 ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin;
7061 ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap;
7062 ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')';
7063 ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')';
7064 for (var j=0; j<depth; j++) {
7065 var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
7066 ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset);
7067 ctxPattern.beginPath();
7068 if (isarc) {
7069 ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
7070 }
7071 else if (fillRect) {
7072 if (fillRect) {
7073 ctx.fillRect(points[0], points[1], points[2], points[3]);
7074 }
7075 }
7076 else if (points && points.length){
7077 var move = true;
7078 for (var i=0; i<points.length; i++) {
7079 // skip to the first non-null point and move to it.
7080 if (points[i][0] != null && points[i][1] != null) {
7081 if (move) {
7082 ctxPattern.moveTo(points[i][0], points[i][1]);
7083 move = false;
7084 }
7085 else {
7086 ctxPattern.lineTo(points[i][0], points[i][1]);
7087 }
7088 }
7089 else {
7090 move = true;
7091 }
7092 }
7093
7094 }
7095 if (closePath) {
7096 ctxPattern.closePath();
7097 }
7098 if (fill) {
7099 ctx.fill();
7100 }
7101 else {
7102 ctx.stroke();
7103 }
7104 }
7105 ctx.restore();
7106 };
7107
7108 // class: $.jqplot.shapeRenderer
7109 // The default jqPlot shape renderer. Given a set of points will
7110 // plot them and either stroke a line (fill = false) or fill them (fill = true).
7111 // If a filled shape is desired, closePath = true must also be set to close
7112 // the shape.
7113 $.jqplot.ShapeRenderer = function(options){
7114
7115 this.lineWidth = 1.5;
7116 // prop: linePattern
7117 // line pattern 'dashed', 'dotted', 'solid', some combination
7118 // of '-' and '.' characters such as '.-.' or a numerical array like
7119 // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line,
7120 // [1, 10, 20, 10] to draw a dot-dash line, and so on.
7121 this.linePattern = 'solid';
7122 // prop: lineJoin
7123 // How line segments of the shadow are joined.
7124 this.lineJoin = 'miter';
7125 // prop: lineCap
7126 // how ends of the shadow line are rendered.
7127 this.lineCap = 'round';
7128 // prop; closePath
7129 // whether line path segment is closed upon itself.
7130 this.closePath = false;
7131 // prop: fill
7132 // whether to fill the shape.
7133 this.fill = false;
7134 // prop: isarc
7135 // wether the shadow is an arc or not.
7136 this.isarc = false;
7137 // prop: fillRect
7138 // true to draw shape as a filled rectangle.
7139 this.fillRect = false;
7140 // prop: strokeRect
7141 // true to draw shape as a stroked rectangle.
7142 this.strokeRect = false;
7143 // prop: clearRect
7144 // true to cear a rectangle.
7145 this.clearRect = false;
7146 // prop: strokeStyle
7147 // css color spec for the stoke style
7148 this.strokeStyle = '#999999';
7149 // prop: fillStyle
7150 // css color spec for the fill style.
7151 this.fillStyle = '#999999';
7152
7153 $.extend(true, this, options);
7154 };
7155
7156 $.jqplot.ShapeRenderer.prototype.init = function(options) {
7157 $.extend(true, this, options);
7158 };
7159
7160 // function: draw
7161 // draws the shape.
7162 //
7163 // ctx - canvas drawing context
7164 // points - array of points for shapes or
7165 // [x, y, width, height] for rectangles or
7166 // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs.
7167 $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) {
7168 ctx.save();
7169 var opts = (options != null) ? options : {};
7170 var fill = (opts.fill != null) ? opts.fill : this.fill;
7171 var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
7172 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
7173 var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect;
7174 var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
7175 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
7176 var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
7177 var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
7178 ctx.lineWidth = opts.lineWidth || this.lineWidth;
7179 ctx.lineJoin = opts.lineJoin || this.lineJoin;
7180 ctx.lineCap = opts.lineCap || this.lineCap;
7181 ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
7182 ctx.fillStyle = opts.fillStyle || this.fillStyle;
7183 ctx.beginPath();
7184 if (isarc) {
7185 ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
7186 if (closePath) {
7187 ctx.closePath();
7188 }
7189 if (fill) {
7190 ctx.fill();
7191 }
7192 else {
7193 ctx.stroke();
7194 }
7195 ctx.restore();
7196 return;
7197 }
7198 else if (clearRect) {
7199 ctx.clearRect(points[0], points[1], points[2], points[3]);
7200 ctx.restore();
7201 return;
7202 }
7203 else if (fillRect || strokeRect) {
7204 if (fillRect) {
7205 ctx.fillRect(points[0], points[1], points[2], points[3]);
7206 }
7207 if (strokeRect) {
7208 ctx.strokeRect(points[0], points[1], points[2], points[3]);
7209 ctx.restore();
7210 return;
7211 }
7212 }
7213 else if (points && points.length){
7214 var move = true;
7215 for (var i=0; i<points.length; i++) {
7216 // skip to the first non-null point and move to it.
7217 if (points[i][0] != null && points[i][1] != null) {
7218 if (move) {
7219 ctxPattern.moveTo(points[i][0], points[i][1]);
7220 move = false;
7221 }
7222 else {
7223 ctxPattern.lineTo(points[i][0], points[i][1]);
7224 }
7225 }
7226 else {
7227 move = true;
7228 }
7229 }
7230 if (closePath) {
7231 ctxPattern.closePath();
7232 }
7233 if (fill) {
7234 ctx.fill();
7235 }
7236 else {
7237 ctx.stroke();
7238 }
7239 }
7240 ctx.restore();
7241 };
7242
7243 // class $.jqplot.TableLegendRenderer
7244 // The default legend renderer for jqPlot.
7245 $.jqplot.TableLegendRenderer = function(){
7246 //
7247 };
7248
7249 $.jqplot.TableLegendRenderer.prototype.init = function(options) {
7250 $.extend(true, this, options);
7251 };
7252
7253 $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) {
7254 var rs = (pad) ? this.rowSpacing+'px' : '0px';
7255 var tr;
7256 var td;
7257 var elem;
7258 var div0;
7259 var div1;
7260 elem = document.createElement('tr');
7261 tr = $(elem);
7262 tr.addClass('jqplot-table-legend');
7263 elem = null;
7264
7265 if (reverse){
7266 tr.prependTo(this._elem);
7267 }
7268
7269 else{
7270 tr.appendTo(this._elem);
7271 }
7272
7273 if (this.showSwatches) {
7274 td = $(document.createElement('td'));
7275 td.addClass('jqplot-table-legend jqplot-table-legend-swatch');
7276 td.css({textAlign: 'center', paddingTop: rs});
7277
7278 div0 = $(document.createElement('div'));
7279 div0.addClass('jqplot-table-legend-swatch-outline');
7280 div1 = $(document.createElement('div'));
7281 div1.addClass('jqplot-table-legend-swatch');
7282 div1.css({backgroundColor: color, borderColor: color});
7283
7284 tr.append(td.append(div0.append(div1)));
7285
7286 // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
7287 // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+
7288 // '</div></td>').appendTo(tr);
7289 }
7290 if (this.showLabels) {
7291 td = $(document.createElement('td'));
7292 td.addClass('jqplot-table-legend jqplot-table-legend-label');
7293 td.css('paddingTop', rs);
7294 tr.append(td);
7295
7296 // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
7297 // elem.appendTo(tr);
7298 if (this.escapeHtml) {
7299 td.text(label);
7300 }
7301 else {
7302 td.html(label);
7303 }
7304 }
7305 td = null;
7306 div0 = null;
7307 div1 = null;
7308 tr = null;
7309 elem = null;
7310 };
7311
7312 // called with scope of legend
7313 $.jqplot.TableLegendRenderer.prototype.draw = function() {
7314 if (this._elem) {
7315 this._elem.emptyForce();
7316 this._elem = null;
7317 }
7318
7319 if (this.show) {
7320 var series = this._series;
7321 // make a table. one line label per row.
7322 var elem = document.createElement('table');
7323 this._elem = $(elem);
7324 this._elem.addClass('jqplot-table-legend');
7325
7326 var ss = {position:'absolute'};
7327 if (this.background) {
7328 ss['background'] = this.background;
7329 }
7330 if (this.border) {
7331 ss['border'] = this.border;
7332 }
7333 if (this.fontSize) {
7334 ss['fontSize'] = this.fontSize;
7335 }
7336 if (this.fontFamily) {
7337 ss['fontFamily'] = this.fontFamily;
7338 }
7339 if (this.textColor) {
7340 ss['textColor'] = this.textColor;
7341 }
7342 if (this.marginTop != null) {
7343 ss['marginTop'] = this.marginTop;
7344 }
7345 if (this.marginBottom != null) {
7346 ss['marginBottom'] = this.marginBottom;
7347 }
7348 if (this.marginLeft != null) {
7349 ss['marginLeft'] = this.marginLeft;
7350 }
7351 if (this.marginRight != null) {
7352 ss['marginRight'] = this.marginRight;
7353 }
7354
7355
7356 var pad = false,
7357 reverse = false,
7358 s;
7359 for (var i = 0; i< series.length; i++) {
7360 s = series[i];
7361 if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){
7362 reverse = true;
7363 }
7364 if (s.show && s.showLabel) {
7365 var lt = this.labels[i] || s.label.toString();
7366 if (lt) {
7367 var color = s.color;
7368 if (reverse && i < series.length - 1){
7369 pad = true;
7370 }
7371 else if (reverse && i == series.length - 1){
7372 pad = false;
7373 }
7374 this.renderer.addrow.call(this, lt, color, pad, reverse);
7375 pad = true;
7376 }
7377 // let plugins add more rows to legend. Used by trend line plugin.
7378 for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
7379 var item = $.jqplot.addLegendRowHooks[j].call(this, s);
7380 if (item) {
7381 this.renderer.addrow.call(this, item.label, item.color, pad);
7382 pad = true;
7383 }
7384 }
7385 lt = null;
7386 }
7387 }
7388 }
7389 return this._elem;
7390 };
7391
7392 $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) {
7393 if (this.show) {
7394 if (this.placement == 'insideGrid') {
7395 switch (this.location) {
7396 case 'nw':
7397 var a = offsets.left;
7398 var b = offsets.top;
7399 this._elem.css('left', a);
7400 this._elem.css('top', b);
7401 break;
7402 case 'n':
7403 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
7404 var b = offsets.top;
7405 this._elem.css('left', a);
7406 this._elem.css('top', b);
7407 break;
7408 case 'ne':
7409 var a = offsets.right;
7410 var b = offsets.top;
7411 this._elem.css({right:a, top:b});
7412 break;
7413 case 'e':
7414 var a = offsets.right;
7415 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
7416 this._elem.css({right:a, top:b});
7417 break;
7418 case 'se':
7419 var a = offsets.right;
7420 var b = offsets.bottom;
7421 this._elem.css({right:a, bottom:b});
7422 break;
7423 case 's':
7424 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
7425 var b = offsets.bottom;
7426 this._elem.css({left:a, bottom:b});
7427 break;
7428 case 'sw':
7429 var a = offsets.left;
7430 var b = offsets.bottom;
7431 this._elem.css({left:a, bottom:b});
7432 break;
7433 case 'w':
7434 var a = offsets.left;
7435 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
7436 this._elem.css({left:a, top:b});
7437 break;
7438 default: // same as 'se'
7439 var a = offsets.right;
7440 var b = offsets.bottom;
7441 this._elem.css({right:a, bottom:b});
7442 break;
7443 }
7444
7445 }
7446 else if (this.placement == 'outside'){
7447 switch (this.location) {
7448 case 'nw':
7449 var a = this._plotDimensions.width - offsets.left;
7450 var b = offsets.top;
7451 this._elem.css('right', a);
7452 this._elem.css('top', b);
7453 break;
7454 case 'n':
7455 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
7456 var b = this._plotDimensions.height - offsets.top;
7457 this._elem.css('left', a);
7458 this._elem.css('bottom', b);
7459 break;
7460 case 'ne':
7461 var a = this._plotDimensions.width - offsets.right;
7462 var b = offsets.top;
7463 this._elem.css({left:a, top:b});
7464 break;
7465 case 'e':
7466 var a = this._plotDimensions.width - offsets.right;
7467 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
7468 this._elem.css({left:a, top:b});
7469 break;
7470 case 'se':
7471 var a = this._plotDimensions.width - offsets.right;
7472 var b = offsets.bottom;
7473 this._elem.css({left:a, bottom:b});
7474 break;
7475 case 's':
7476 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
7477 var b = this._plotDimensions.height - offsets.bottom;
7478 this._elem.css({left:a, top:b});
7479 break;
7480 case 'sw':
7481 var a = this._plotDimensions.width - offsets.left;
7482 var b = offsets.bottom;
7483 this._elem.css({right:a, bottom:b});
7484 break;
7485 case 'w':
7486 var a = this._plotDimensions.width - offsets.left;
7487 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
7488 this._elem.css({right:a, top:b});
7489 break;
7490 default: // same as 'se'
7491 var a = offsets.right;
7492 var b = offsets.bottom;
7493 this._elem.css({right:a, bottom:b});
7494 break;
7495 }
7496 }
7497 else {
7498 switch (this.location) {
7499 case 'nw':
7500 this._elem.css({left:0, top:offsets.top});
7501 break;
7502 case 'n':
7503 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
7504 this._elem.css({left: a, top:offsets.top});
7505 break;
7506 case 'ne':
7507 this._elem.css({right:0, top:offsets.top});
7508 break;
7509 case 'e':
7510 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
7511 this._elem.css({right:offsets.right, top:b});
7512 break;
7513 case 'se':
7514 this._elem.css({right:offsets.right, bottom:offsets.bottom});
7515 break;
7516 case 's':
7517 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
7518 this._elem.css({left: a, bottom:offsets.bottom});
7519 break;
7520 case 'sw':
7521 this._elem.css({left:offsets.left, bottom:offsets.bottom});
7522 break;
7523 case 'w':
7524 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
7525 this._elem.css({left:offsets.left, top:b});
7526 break;
7527 default: // same as 'se'
7528 this._elem.css({right:offsets.right, bottom:offsets.bottom});
7529 break;
7530 }
7531 }
7532 }
7533 };
7534
7535 /**
7536 * Class: $.jqplot.ThemeEngine
7537 * Theme Engine provides a programatic way to change some of the more
7538 * common jqplot styling options such as fonts, colors and grid options.
7539 * A theme engine instance is created with each plot. The theme engine
7540 * manages a collection of themes which can be modified, added to, or
7541 * applied to the plot.
7542 *
7543 * The themeEngine class is not instantiated directly.
7544 * When a plot is initialized, the current plot options are scanned
7545 * an a default theme named "Default" is created. This theme is
7546 * used as the basis for other themes added to the theme engine and
7547 * is always available.
7548 *
7549 * A theme is a simple javascript object with styling parameters for
7550 * various entities of the plot. A theme has the form:
7551 *
7552 *
7553 * > {
7554 * > _name:f "Default",
7555 * > target: {
7556 * > backgroundColor: "transparent"
7557 * > },
7558 * > legend: {
7559 * > textColor: null,
7560 * > fontFamily: null,
7561 * > fontSize: null,
7562 * > border: null,
7563 * > background: null
7564 * > },
7565 * > title: {
7566 * > textColor: "rgb(102, 102, 102)",
7567 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
7568 * > fontSize: "19.2px",
7569 * > textAlign: "center"
7570 * > },
7571 * > seriesStyles: {},
7572 * > series: [{
7573 * > color: "#4bb2c5",
7574 * > lineWidth: 2.5,
7575 * > linePattern: "solid",
7576 * > shadow: true,
7577 * > fillColor: "#4bb2c5",
7578 * > showMarker: true,
7579 * > markerOptions: {
7580 * > color: "#4bb2c5",
7581 * > show: true,
7582 * > style: 'filledCircle',
7583 * > lineWidth: 1.5,
7584 * > size: 4,
7585 * > shadow: true
7586 * > }
7587 * > }],
7588 * > grid: {
7589 * > drawGridlines: true,
7590 * > gridLineColor: "#cccccc",
7591 * > gridLineWidth: 1,
7592 * > backgroundColor: "#fffdf6",
7593 * > borderColor: "#999999",
7594 * > borderWidth: 2,
7595 * > shadow: true
7596 * > },
7597 * > axesStyles: {
7598 * > label: {},
7599 * > ticks: {}
7600 * > },
7601 * > axes: {
7602 * > xaxis: {
7603 * > borderColor: "#999999",
7604 * > borderWidth: 2,
7605 * > ticks: {
7606 * > show: true,
7607 * > showGridline: true,
7608 * > showLabel: true,
7609 * > showMark: true,
7610 * > size: 4,
7611 * > textColor: "",
7612 * > whiteSpace: "nowrap",
7613 * > fontSize: "12px",
7614 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
7615 * > },
7616 * > label: {
7617 * > textColor: "rgb(102, 102, 102)",
7618 * > whiteSpace: "normal",
7619 * > fontSize: "14.6667px",
7620 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
7621 * > fontWeight: "400"
7622 * > }
7623 * > },
7624 * > yaxis: {
7625 * > borderColor: "#999999",
7626 * > borderWidth: 2,
7627 * > ticks: {
7628 * > show: true,
7629 * > showGridline: true,
7630 * > showLabel: true,
7631 * > showMark: true,
7632 * > size: 4,
7633 * > textColor: "",
7634 * > whiteSpace: "nowrap",
7635 * > fontSize: "12px",
7636 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
7637 * > },
7638 * > label: {
7639 * > textColor: null,
7640 * > whiteSpace: null,
7641 * > fontSize: null,
7642 * > fontFamily: null,
7643 * > fontWeight: null
7644 * > }
7645 * > },
7646 * > x2axis: {...
7647 * > },
7648 * > ...
7649 * > y9axis: {...
7650 * > }
7651 * > }
7652 * > }
7653 *
7654 * "seriesStyles" is a style object that will be applied to all series in the plot.
7655 * It will forcibly override any styles applied on the individual series. "axesStyles" is
7656 * a style object that will be applied to all axes in the plot. It will also forcibly
7657 * override any styles on the individual axes.
7658 *
7659 * The example shown above has series options for a line series. Options for other
7660 * series types are shown below:
7661 *
7662 * Bar Series:
7663 *
7664 * > {
7665 * > color: "#4bb2c5",
7666 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
7667 * > lineWidth: 2.5,
7668 * > shadow: true,
7669 * > barPadding: 2,
7670 * > barMargin: 10,
7671 * > barWidth: 15.09375,
7672 * > 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)"]
7673 * > }
7674 *
7675 * Pie Series:
7676 *
7677 * > {
7678 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
7679 * > padding: 20,
7680 * > sliceMargin: 0,
7681 * > fill: true,
7682 * > shadow: true,
7683 * > startAngle: 0,
7684 * > lineWidth: 2.5,
7685 * > 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)"]
7686 * > }
7687 *
7688 * Funnel Series:
7689 *
7690 * > {
7691 * > color: "#4bb2c5",
7692 * > lineWidth: 2,
7693 * > shadow: true,
7694 * > padding: {
7695 * > top: 20,
7696 * > right: 20,
7697 * > bottom: 20,
7698 * > left: 20
7699 * > },
7700 * > sectionMargin: 6,
7701 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
7702 * > 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)"]
7703 * > }
7704 *
7705 */
7706 $.jqplot.ThemeEngine = function(){
7707 // Group: Properties
7708 //
7709 // prop: themes
7710 // hash of themes managed by the theme engine.
7711 // Indexed by theme name.
7712 this.themes = {};
7713 // prop: activeTheme
7714 // Pointer to currently active theme
7715 this.activeTheme=null;
7716
7717 };
7718
7719 // called with scope of plot
7720 $.jqplot.ThemeEngine.prototype.init = function() {
7721 // get the Default theme from the current plot settings.
7722 var th = new $.jqplot.Theme({_name:'Default'});
7723 var n, i, nn;
7724
7725 for (n in th.target) {
7726 if (n == "textColor") {
7727 th.target[n] = this.target.css('color');
7728 }
7729 else {
7730 th.target[n] = this.target.css(n);
7731 }
7732 }
7733
7734 if (this.title.show && this.title._elem) {
7735 for (n in th.title) {
7736 if (n == "textColor") {
7737 th.title[n] = this.title._elem.css('color');
7738 }
7739 else {
7740 th.title[n] = this.title._elem.css(n);
7741 }
7742 }
7743 }
7744
7745 for (n in th.grid) {
7746 th.grid[n] = this.grid[n];
7747 }
7748 if (th.grid.backgroundColor == null && this.grid.background != null) {
7749 th.grid.backgroundColor = this.grid.background;
7750 }
7751 if (this.legend.show && this.legend._elem) {
7752 for (n in th.legend) {
7753 if (n == 'textColor') {
7754 th.legend[n] = this.legend._elem.css('color');
7755 }
7756 else {
7757 th.legend[n] = this.legend._elem.css(n);
7758 }
7759 }
7760 }
7761 var s;
7762
7763 for (i=0; i<this.series.length; i++) {
7764 s = this.series[i];
7765 if (s.renderer.constructor == $.jqplot.LineRenderer) {
7766 th.series.push(new LineSeriesProperties());
7767 }
7768 else if (s.renderer.constructor == $.jqplot.BarRenderer) {
7769 th.series.push(new BarSeriesProperties());
7770 }
7771 else if (s.renderer.constructor == $.jqplot.PieRenderer) {
7772 th.series.push(new PieSeriesProperties());
7773 }
7774 else if (s.renderer.constructor == $.jqplot.DonutRenderer) {
7775 th.series.push(new DonutSeriesProperties());
7776 }
7777 else if (s.renderer.constructor == $.jqplot.FunnelRenderer) {
7778 th.series.push(new FunnelSeriesProperties());
7779 }
7780 else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) {
7781 th.series.push(new MeterSeriesProperties());
7782 }
7783 else {
7784 th.series.push({});
7785 }
7786 for (n in th.series[i]) {
7787 th.series[i][n] = s[n];
7788 }
7789 }
7790 var a, ax;
7791 for (n in this.axes) {
7792 ax = this.axes[n];
7793 a = th.axes[n] = new AxisProperties();
7794 a.borderColor = ax.borderColor;
7795 a.borderWidth = ax.borderWidth;
7796 if (ax._ticks && ax._ticks[0]) {
7797 for (nn in a.ticks) {
7798 if (ax._ticks[0].hasOwnProperty(nn)) {
7799 a.ticks[nn] = ax._ticks[0][nn];
7800 }
7801 else if (ax._ticks[0]._elem){
7802 a.ticks[nn] = ax._ticks[0]._elem.css(nn);
7803 }
7804 }
7805 }
7806 if (ax._label && ax._label.show) {
7807 for (nn in a.label) {
7808 // a.label[nn] = ax._label._elem.css(nn);
7809 if (ax._label[nn]) {
7810 a.label[nn] = ax._label[nn];
7811 }
7812 else if (ax._label._elem){
7813 if (nn == 'textColor') {
7814 a.label[nn] = ax._label._elem.css('color');
7815 }
7816 else {
7817 a.label[nn] = ax._label._elem.css(nn);
7818 }
7819 }
7820 }
7821 }
7822 }
7823 this.themeEngine._add(th);
7824 this.themeEngine.activeTheme = this.themeEngine.themes[th._name];
7825 };
7826 /**
7827 * Group: methods
7828 *
7829 * method: get
7830 *
7831 * Get and return the named theme or the active theme if no name given.
7832 *
7833 * parameter:
7834 *
7835 * name - name of theme to get.
7836 *
7837 * returns:
7838 *
7839 * Theme instance of given name.
7840 */
7841 $.jqplot.ThemeEngine.prototype.get = function(name) {
7842 if (!name) {
7843 // return the active theme
7844 return this.activeTheme;
7845 }
7846 else {
7847 return this.themes[name];
7848 }
7849 };
7850
7851 function numericalOrder(a,b) { return a-b; }
7852
7853 /**
7854 * method: getThemeNames
7855 *
7856 * Return the list of theme names in this manager in alpha-numerical order.
7857 *
7858 * parameter:
7859 *
7860 * None
7861 *
7862 * returns:
7863 *
7864 * A the list of theme names in this manager in alpha-numerical order.
7865 */
7866 $.jqplot.ThemeEngine.prototype.getThemeNames = function() {
7867 var tn = [];
7868 for (var n in this.themes) {
7869 tn.push(n);
7870 }
7871 return tn.sort(numericalOrder);
7872 };
7873
7874 /**
7875 * method: getThemes
7876 *
7877 * Return a list of themes in alpha-numerical order by name.
7878 *
7879 * parameter:
7880 *
7881 * None
7882 *
7883 * returns:
7884 *
7885 * A list of themes in alpha-numerical order by name.
7886 */
7887 $.jqplot.ThemeEngine.prototype.getThemes = function() {
7888 var tn = [];
7889 var themes = [];
7890 for (var n in this.themes) {
7891 tn.push(n);
7892 }
7893 tn.sort(numericalOrder);
7894 for (var i=0; i<tn.length; i++) {
7895 themes.push(this.themes[tn[i]]);
7896 }
7897 return themes;
7898 };
7899
7900 $.jqplot.ThemeEngine.prototype.activate = function(plot, name) {
7901 // sometimes need to redraw whole plot.
7902 var redrawPlot = false;
7903 if (!name && this.activeTheme && this.activeTheme._name) {
7904 name = this.activeTheme._name;
7905 }
7906 if (!this.themes.hasOwnProperty(name)) {
7907 throw new Error("No theme of that name");
7908 }
7909 else {
7910 var th = this.themes[name];
7911 this.activeTheme = th;
7912 var val, checkBorderColor = false, checkBorderWidth = false;
7913 var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis'];
7914
7915 for (i=0; i<arr.length; i++) {
7916 var ax = arr[i];
7917 if (th.axesStyles.borderColor != null) {
7918 plot.axes[ax].borderColor = th.axesStyles.borderColor;
7919 }
7920 if (th.axesStyles.borderWidth != null) {
7921 plot.axes[ax].borderWidth = th.axesStyles.borderWidth;
7922 }
7923 }
7924
7925 for (var axname in plot.axes) {
7926 var axis = plot.axes[axname];
7927 if (axis.show) {
7928 var thaxis = th.axes[axname] || {};
7929 var thaxstyle = th.axesStyles;
7930 var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle);
7931 val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor;
7932 if (thax.borderColor != null) {
7933 axis.borderColor = thax.borderColor;
7934 redrawPlot = true;
7935 }
7936 val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth;
7937 if (thax.borderWidth != null) {
7938 axis.borderWidth = thax.borderWidth;
7939 redrawPlot = true;
7940 }
7941 if (axis._ticks && axis._ticks[0]) {
7942 for (var nn in thax.ticks) {
7943 // val = null;
7944 // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) {
7945 // val = th.axesStyles.ticks[nn];
7946 // }
7947 // else if (thax.ticks[nn] != null){
7948 // val = thax.ticks[nn]
7949 // }
7950 val = thax.ticks[nn];
7951 if (val != null) {
7952 axis.tickOptions[nn] = val;
7953 axis._ticks = [];
7954 redrawPlot = true;
7955 }
7956 }
7957 }
7958 if (axis._label && axis._label.show) {
7959 for (var nn in thax.label) {
7960 // val = null;
7961 // if (th.axesStyles.label && th.axesStyles.label[nn] != null) {
7962 // val = th.axesStyles.label[nn];
7963 // }
7964 // else if (thax.label && thax.label[nn] != null){
7965 // val = thax.label[nn]
7966 // }
7967 val = thax.label[nn];
7968 if (val != null) {
7969 axis.labelOptions[nn] = val;
7970 redrawPlot = true;
7971 }
7972 }
7973 }
7974
7975 }
7976 }
7977
7978 for (var n in th.grid) {
7979 if (th.grid[n] != null) {
7980 plot.grid[n] = th.grid[n];
7981 }
7982 }
7983 if (!redrawPlot) {
7984 plot.grid.draw();
7985 }
7986
7987 if (plot.legend.show) {
7988 for (n in th.legend) {
7989 if (th.legend[n] != null) {
7990 plot.legend[n] = th.legend[n];
7991 }
7992 }
7993 }
7994 if (plot.title.show) {
7995 for (n in th.title) {
7996 if (th.title[n] != null) {
7997 plot.title[n] = th.title[n];
7998 }
7999 }
8000 }
8001
8002 var i;
8003 for (i=0; i<th.series.length; i++) {
8004 var opts = {};
8005 var redrawSeries = false;
8006 for (n in th.series[i]) {
8007 val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n];
8008 if (val != null) {
8009 opts[n] = val;
8010 if (n == 'color') {
8011 plot.series[i].renderer.shapeRenderer.fillStyle = val;
8012 plot.series[i].renderer.shapeRenderer.strokeStyle = val;
8013 plot.series[i][n] = val;
8014 }
8015 else if ((n == 'lineWidth') || (n == 'linePattern')) {
8016 plot.series[i].renderer.shapeRenderer[n] = val;
8017 plot.series[i][n] = val;
8018 }
8019 else if (n == 'markerOptions') {
8020 merge (plot.series[i].markerOptions, val);
8021 merge (plot.series[i].markerRenderer, val);
8022 }
8023 else {
8024 plot.series[i][n] = val;
8025 }
8026 redrawPlot = true;
8027 }
8028 }
8029 }
8030
8031 if (redrawPlot) {
8032 plot.target.empty();
8033 plot.draw();
8034 }
8035
8036 for (n in th.target) {
8037 if (th.target[n] != null) {
8038 plot.target.css(n, th.target[n]);
8039 }
8040 }
8041 }
8042
8043 };
8044
8045 $.jqplot.ThemeEngine.prototype._add = function(theme, name) {
8046 if (name) {
8047 theme._name = name;
8048 }
8049 if (!theme._name) {
8050 theme._name = Date.parse(new Date());
8051 }
8052 if (!this.themes.hasOwnProperty(theme._name)) {
8053 this.themes[theme._name] = theme;
8054 }
8055 else {
8056 throw new Error("jqplot.ThemeEngine Error: Theme already in use");
8057 }
8058 };
8059
8060 // method remove
8061 // Delete the named theme, return true on success, false on failure.
8062
8063
8064 /**
8065 * method: remove
8066 *
8067 * Remove the given theme from the themeEngine.
8068 *
8069 * parameters:
8070 *
8071 * name - name of the theme to remove.
8072 *
8073 * returns:
8074 *
8075 * true on success, false on failure.
8076 */
8077 $.jqplot.ThemeEngine.prototype.remove = function(name) {
8078 if (name == 'Default') {
8079 return false;
8080 }
8081 return delete this.themes[name];
8082 };
8083
8084 /**
8085 * method: newTheme
8086 *
8087 * Create a new theme based on the default theme, adding it the themeEngine.
8088 *
8089 * parameters:
8090 *
8091 * name - name of the new theme.
8092 * obj - optional object of styles to be applied to this new theme.
8093 *
8094 * returns:
8095 *
8096 * new Theme object.
8097 */
8098 $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) {
8099 if (typeof(name) == 'object') {
8100 obj = obj || name;
8101 name = null;
8102 }
8103 if (obj && obj._name) {
8104 name = obj._name;
8105 }
8106 else {
8107 name = name || Date.parse(new Date());
8108 }
8109 // var th = new $.jqplot.Theme(name);
8110 var th = this.copy(this.themes['Default']._name, name);
8111 $.jqplot.extend(th, obj);
8112 return th;
8113 };
8114
8115 // function clone(obj) {
8116 // return eval(obj.toSource());
8117 // }
8118
8119 function clone(obj){
8120 if(obj == null || typeof(obj) != 'object'){
8121 return obj;
8122 }
8123
8124 var temp = new obj.constructor();
8125 for(var key in obj){
8126 temp[key] = clone(obj[key]);
8127 }
8128 return temp;
8129 }
8130
8131 $.jqplot.clone = clone;
8132
8133 function merge(obj1, obj2) {
8134 if (obj2 == null || typeof(obj2) != 'object') {
8135 return;
8136 }
8137 for (var key in obj2) {
8138 if (key == 'highlightColors') {
8139 obj1[key] = clone(obj2[key]);
8140 }
8141 if (obj2[key] != null && typeof(obj2[key]) == 'object') {
8142 if (!obj1.hasOwnProperty(key)) {
8143 obj1[key] = {};
8144 }
8145 merge(obj1[key], obj2[key]);
8146 }
8147 else {
8148 obj1[key] = obj2[key];
8149 }
8150 }
8151 }
8152
8153 $.jqplot.merge = merge;
8154
8155 // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic
8156 $.jqplot.extend = function() {
8157 // copy reference to target object
8158 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
8159
8160 // Handle a deep copy situation
8161 if ( typeof target === "boolean" ) {
8162 deep = target;
8163 target = arguments[1] || {};
8164 // skip the boolean and the target
8165 i = 2;
8166 }
8167
8168 // Handle case when target is a string or something (possible in deep copy)
8169 if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) {
8170 target = {};
8171 }
8172
8173 for ( ; i < length; i++ ){
8174 // Only deal with non-null/undefined values
8175 if ( (options = arguments[ i ]) != null ) {
8176 // Extend the base object
8177 for ( var name in options ) {
8178 var src = target[ name ], copy = options[ name ];
8179
8180 // Prevent never-ending loop
8181 if ( target === copy ) {
8182 continue;
8183 }
8184
8185 // Recurse if we're merging object values
8186 if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
8187 target[ name ] = $.jqplot.extend( deep,
8188 // Never move original objects, clone them
8189 src || ( copy.length != null ? [ ] : { } )
8190 , copy );
8191 }
8192 // Don't bring in undefined values
8193 else if ( copy !== undefined ) {
8194 target[ name ] = copy;
8195 }
8196 }
8197 }
8198 }
8199 // Return the modified object
8200 return target;
8201 };
8202
8203 /**
8204 * method: rename
8205 *
8206 * Rename a theme.
8207 *
8208 * parameters:
8209 *
8210 * oldName - current name of the theme.
8211 * newName - desired name of the theme.
8212 *
8213 * returns:
8214 *
8215 * new Theme object.
8216 */
8217 $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) {
8218 if (oldName == 'Default' || newName == 'Default') {
8219 throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default");
8220 }
8221 if (this.themes.hasOwnProperty(newName)) {
8222 throw new Error ("jqplot.ThemeEngine Error: New name already in use.");
8223 }
8224 else if (this.themes.hasOwnProperty(oldName)) {
8225 var th = this.copy (oldName, newName);
8226 this.remove(oldName);
8227 return th;
8228 }
8229 throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid");
8230 };
8231
8232 /**
8233 * method: copy
8234 *
8235 * Create a copy of an existing theme in the themeEngine, adding it the themeEngine.
8236 *
8237 * parameters:
8238 *
8239 * sourceName - name of the existing theme.
8240 * targetName - name of the copy.
8241 * obj - optional object of style parameter to apply to the new theme.
8242 *
8243 * returns:
8244 *
8245 * new Theme object.
8246 */
8247 $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) {
8248 if (targetName == 'Default') {
8249 throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme");
8250 }
8251 if (!this.themes.hasOwnProperty(sourceName)) {
8252 var s = "jqplot.ThemeEngine Error: Source name invalid";
8253 throw new Error(s);
8254 }
8255 if (this.themes.hasOwnProperty(targetName)) {
8256 var s = "jqplot.ThemeEngine Error: Target name invalid";
8257 throw new Error(s);
8258 }
8259 else {
8260 var th = clone(this.themes[sourceName]);
8261 th._name = targetName;
8262 $.jqplot.extend(true, th, obj);
8263 this._add(th);
8264 return th;
8265 }
8266 };
8267
8268
8269 $.jqplot.Theme = function(name, obj) {
8270 if (typeof(name) == 'object') {
8271 obj = obj || name;
8272 name = null;
8273 }
8274 name = name || Date.parse(new Date());
8275 this._name = name;
8276 this.target = {
8277 backgroundColor: null
8278 };
8279 this.legend = {
8280 textColor: null,
8281 fontFamily: null,
8282 fontSize: null,
8283 border: null,
8284 background: null
8285 };
8286 this.title = {
8287 textColor: null,
8288 fontFamily: null,
8289 fontSize: null,
8290 textAlign: null
8291 };
8292 this.seriesStyles = {};
8293 this.series = [];
8294 this.grid = {
8295 drawGridlines: null,
8296 gridLineColor: null,
8297 gridLineWidth: null,
8298 backgroundColor: null,
8299 borderColor: null,
8300 borderWidth: null,
8301 shadow: null
8302 };
8303 this.axesStyles = {label:{}, ticks:{}};
8304 this.axes = {};
8305 if (typeof(obj) == 'string') {
8306 this._name = obj;
8307 }
8308 else if(typeof(obj) == 'object') {
8309 $.jqplot.extend(true, this, obj);
8310 }
8311 };
8312
8313 var AxisProperties = function() {
8314 this.borderColor = null;
8315 this.borderWidth = null;
8316 this.ticks = new AxisTicks();
8317 this.label = new AxisLabel();
8318 };
8319
8320 var AxisTicks = function() {
8321 this.show = null;
8322 this.showGridline = null;
8323 this.showLabel = null;
8324 this.showMark = null;
8325 this.size = null;
8326 this.textColor = null;
8327 this.whiteSpace = null;
8328 this.fontSize = null;
8329 this.fontFamily = null;
8330 };
8331
8332 var AxisLabel = function() {
8333 this.textColor = null;
8334 this.whiteSpace = null;
8335 this.fontSize = null;
8336 this.fontFamily = null;
8337 this.fontWeight = null;
8338 };
8339
8340 var LineSeriesProperties = function() {
8341 this.color=null;
8342 this.lineWidth=null;
8343 this.linePattern=null;
8344 this.shadow=null;
8345 this.fillColor=null;
8346 this.showMarker=null;
8347 this.markerOptions = new MarkerOptions();
8348 };
8349
8350 var MarkerOptions = function() {
8351 this.show = null;
8352 this.style = null;
8353 this.lineWidth = null;
8354 this.size = null;
8355 this.color = null;
8356 this.shadow = null;
8357 };
8358
8359 var BarSeriesProperties = function() {
8360 this.color=null;
8361 this.seriesColors=null;
8362 this.lineWidth=null;
8363 this.shadow=null;
8364 this.barPadding=null;
8365 this.barMargin=null;
8366 this.barWidth=null;
8367 this.highlightColors=null;
8368 };
8369
8370 var PieSeriesProperties = function() {
8371 this.seriesColors=null;
8372 this.padding=null;
8373 this.sliceMargin=null;
8374 this.fill=null;
8375 this.shadow=null;
8376 this.startAngle=null;
8377 this.lineWidth=null;
8378 this.highlightColors=null;
8379 };
8380
8381 var DonutSeriesProperties = function() {
8382 this.seriesColors=null;
8383 this.padding=null;
8384 this.sliceMargin=null;
8385 this.fill=null;
8386 this.shadow=null;
8387 this.startAngle=null;
8388 this.lineWidth=null;
8389 this.innerDiameter=null;
8390 this.thickness=null;
8391 this.ringMargin=null;
8392 this.highlightColors=null;
8393 };
8394
8395 var FunnelSeriesProperties = function() {
8396 this.color=null;
8397 this.lineWidth=null;
8398 this.shadow=null;
8399 this.padding=null;
8400 this.sectionMargin=null;
8401 this.seriesColors=null;
8402 this.highlightColors=null;
8403 };
8404
8405 var MeterSeriesProperties = function() {
8406 this.padding=null;
8407 this.backgroundColor=null;
8408 this.ringColor=null;
8409 this.tickColor=null;
8410 this.ringWidth=null;
8411 this.intervalColors=null;
8412 this.intervalInnerRadius=null;
8413 this.intervalOuterRadius=null;
8414 this.hubRadius=null;
8415 this.needleThickness=null;
8416 this.needlePad=null;
8417 };
8418
8419
8420
8421
8422 $.fn.jqplotChildText = function() {
8423 return $(this).contents().filter(function() {
8424 return this.nodeType == 3; // Node.TEXT_NODE not defined in I7
8425 }).text();
8426 };
8427
8428 // Returns font style as abbreviation for "font" property.
8429 $.fn.jqplotGetComputedFontStyle = function() {
8430 var css = window.getComputedStyle ? window.getComputedStyle(this[0]) : this[0].currentStyle;
8431 var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily'];
8432 var style = [];
8433
8434 for (var i=0 ; i < attrs.length; ++i) {
8435 var attr = String(css[attrs[i]]);
8436
8437 if (attr && attr != 'normal') {
8438 style.push(attr);
8439 }
8440 }
8441 return style.join(' ');
8442 };
8443
8444 /**
8445 * Namespace: $.fn
8446 * jQuery namespace to attach functions to jQuery elements.
8447 *
8448 */
8449
8450 $.fn.jqplotToImageCanvas = function(options) {
8451
8452 options = options || {};
8453 var x_offset = (options.x_offset == null) ? 0 : options.x_offset;
8454 var y_offset = (options.y_offset == null) ? 0 : options.y_offset;
8455 var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor;
8456
8457 if ($(this).width() == 0 || $(this).height() == 0) {
8458 return null;
8459 }
8460
8461 // excanvas and hence IE < 9 do not support toDataURL and cannot export images.
8462 if (!$.jqplot.support_canvas) {
8463 return null;
8464 }
8465
8466 var newCanvas = document.createElement("canvas");
8467 var h = $(this).outerHeight(true);
8468 var w = $(this).outerWidth(true);
8469 var offs = $(this).offset();
8470 var plotleft = offs.left;
8471 var plottop = offs.top;
8472 var transx = 0, transy = 0;
8473
8474 // have to check if any elements are hanging outside of plot area before rendering,
8475 // since changing width of canvas will erase canvas.
8476
8477 var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick',
8478 'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick',
8479 'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label',
8480 'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ];
8481
8482 var temptop, templeft, tempbottom, tempright;
8483
8484 for (var i in clses) {
8485 $(this).find('.'+clses[i]).each(function() {
8486 temptop = $(this).offset().top - plottop;
8487 templeft = $(this).offset().left - plotleft;
8488 tempright = templeft + $(this).outerWidth(true) + transx;
8489 tempbottom = temptop + $(this).outerHeight(true) + transy;
8490 if (templeft < -transx) {
8491 w = w - transx - templeft;
8492 transx = -templeft;
8493 }
8494 if (temptop < -transy) {
8495 h = h - transy - temptop;
8496 transy = - temptop;
8497 }
8498 if (tempright > w) {
8499 w = tempright;
8500 }
8501 if (tempbottom > h) {
8502 h = tempbottom;
8503 }
8504 });
8505 }
8506
8507 newCanvas.width = w + Number(x_offset);
8508 newCanvas.height = h + Number(y_offset);
8509
8510 var newContext = newCanvas.getContext("2d");
8511
8512 newContext.save();
8513 newContext.fillStyle = backgroundColor;
8514 newContext.fillRect(0,0, newCanvas.width, newCanvas.height);
8515 newContext.restore();
8516
8517 newContext.translate(transx, transy);
8518 newContext.textAlign = 'left';
8519 newContext.textBaseline = 'top';
8520
8521 function getLineheight(el) {
8522 var lineheight = parseInt($(el).css('line-height'));
8523
8524 if (isNaN(lineheight)) {
8525 lineheight = parseInt($(el).css('font-size')) * 1.2;
8526 }
8527 return lineheight;
8528 }
8529
8530 function writeWrappedText (el, context, text, left, top, canvasWidth) {
8531 var lineheight = getLineheight(el);
8532 var tagwidth = $(el).innerWidth();
8533 var tagheight = $(el).innerHeight();
8534 var words = text.split(/\s+/);
8535 var wl = words.length;
8536 var w = '';
8537 var breaks = [];
8538 var temptop = top;
8539 var templeft = left;
8540
8541 for (var i=0; i<wl; i++) {
8542 w += words[i];
8543 if (context.measureText(w).width > tagwidth) {
8544 breaks.push(i);
8545 w = '';
8546 }
8547 }
8548 if (breaks.length === 0) {
8549 // center text if necessary
8550 if ($(el).css('textAlign') === 'center') {
8551 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
8552 }
8553 context.fillText(text, templeft, top);
8554 }
8555 else {
8556 w = words.slice(0, breaks[0]).join(' ');
8557 // center text if necessary
8558 if ($(el).css('textAlign') === 'center') {
8559 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
8560 }
8561 context.fillText(w, templeft, temptop);
8562 temptop += lineheight;
8563 for (var i=1, l=breaks.length; i<l; i++) {
8564 w = words.slice(breaks[i-1], breaks[i]).join(' ');
8565 // center text if necessary
8566 if ($(el).css('textAlign') === 'center') {
8567 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
8568 }
8569 context.fillText(w, templeft, temptop);
8570 temptop += lineheight;
8571 }
8572 w = words.slice(breaks[i-1], words.length).join(' ');
8573 // center text if necessary
8574 if ($(el).css('textAlign') === 'center') {
8575 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
8576 }
8577 context.fillText(w, templeft, temptop);
8578 }
8579
8580 }
8581
8582 function _jqpToImage(el, x_offset, y_offset) {
8583 var tagname = el.tagName.toLowerCase();
8584 var p = $(el).position();
8585 var css = window.getComputedStyle ? window.getComputedStyle(el) : el.currentStyle; // for IE < 9
8586 var left = x_offset + p.left + parseInt(css.marginLeft) + parseInt(css.borderLeftWidth) + parseInt(css.paddingLeft);
8587 var top = y_offset + p.top + parseInt(css.marginTop) + parseInt(css.borderTopWidth)+ parseInt(css.paddingTop);
8588 var w = newCanvas.width;
8589 // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth')
8590
8591 if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) {
8592 $(el).children().each(function() {
8593 _jqpToImage(this, left, top);
8594 });
8595 var text = $(el).jqplotChildText();
8596
8597 if (text) {
8598 newContext.font = $(el).jqplotGetComputedFontStyle();
8599 newContext.fillStyle = $(el).css('color');
8600
8601 writeWrappedText(el, newContext, text, left, top, w);
8602 }
8603 }
8604
8605 // handle the standard table legend
8606
8607 else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) {
8608 newContext.strokeStyle = $(el).css('border-top-color');
8609 newContext.fillStyle = $(el).css('background-color');
8610 newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight());
8611 if (parseInt($(el).css('border-top-width')) > 0) {
8612 newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight());
8613 }
8614
8615 // find all the swatches
8616 $(el).find('div.jqplot-table-legend-swatch-outline').each(function() {
8617 // get the first div and stroke it
8618 var elem = $(this);
8619 newContext.strokeStyle = elem.css('border-top-color');
8620 var l = left + elem.position().left;
8621 var t = top + elem.position().top;
8622 newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight());
8623
8624 // now fill the swatch
8625
8626 l += parseInt(elem.css('padding-left'));
8627 t += parseInt(elem.css('padding-top'));
8628 var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top'));
8629 var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left'));
8630
8631 var swatch = elem.children('div.jqplot-table-legend-swatch');
8632 newContext.fillStyle = swatch.css('background-color');
8633 newContext.fillRect(l, t, w, h);
8634 });
8635
8636 // now add text
8637
8638 $(el).find('td.jqplot-table-legend-label').each(function(){
8639 var elem = $(this);
8640 var l = left + elem.position().left;
8641 var t = top + elem.position().top + parseInt(elem.css('padding-top'));
8642 newContext.font = elem.jqplotGetComputedFontStyle();
8643 newContext.fillStyle = elem.css('color');
8644 newContext.fillText(elem.text(), l, t);
8645 });
8646
8647 var elem = null;
8648 }
8649
8650 else if (tagname == 'canvas') {
8651 newContext.drawImage(el, left, top);
8652 }
8653 }
8654 $(this).children().each(function() {
8655 _jqpToImage(this, x_offset, y_offset);
8656 });
8657 return newCanvas;
8658 };
8659
8660 $.fn.jqplotToImageStr = function(options) {
8661 var imgCanvas = $(this).jqplotToImageCanvas(options);
8662 if (imgCanvas) {
8663 return imgCanvas.toDataURL("image/png");
8664 }
8665 else {
8666 return null;
8667 }
8668 };
8669
8670 // create an <img> element and return it.
8671 // Should work on canvas supporting browsers.
8672 $.fn.jqplotToImageElem = function(options) {
8673 var elem = document.createElement("img");
8674 var str = $(this).jqplotToImageStr(options);
8675 elem.src = str;
8676 return elem;
8677 };
8678
8679 // create an <img> element and return it.
8680 // Should work on canvas supporting browsers.
8681 $.fn.jqplotToImageElemStr = function(options) {
8682 var str = '<img src='+$(this).jqplotToImageStr(options)+' />';
8683 return str;
8684 };
8685
8686 // Not gauranteed to work, even on canvas supporting browsers due to
8687 // limitations with location.href and browser support.
8688 $.fn.jqplotSaveImage = function() {
8689 var imgData = $(this).jqplotToImageStr({});
8690 if (imgData) {
8691 window.location.href = imgData.replace("image/png", "image/octet-stream");
8692 }
8693
8694 };
8695
8696 // Not gauranteed to work, even on canvas supporting browsers due to
8697 // limitations with window.open and arbitrary data.
8698 $.fn.jqplotViewImage = function() {
8699 var imgStr = $(this).jqplotToImageElemStr({});
8700 var imgData = $(this).jqplotToImageStr({});
8701 if (imgStr) {
8702 var w = window.open('');
8703 w.document.open("image/png");
8704 w.document.write(imgStr);
8705 w.document.close();
8706 w = null;
8707 }
8708 };
8709
8710
8711
8712 /**
8713 * @description
8714 * <p>Object with extended date parsing and formatting capabilities.
8715 * This library borrows many concepts and ideas from the Date Instance
8716 * Methods by Ken Snyder along with some parts of Ken's actual code.</p>
8717 *
8718 * <p>jsDate takes a different approach by not extending the built-in
8719 * Date Object, improving date parsing, allowing for multiple formatting
8720 * syntaxes and multiple and more easily expandable localization.</p>
8721 *
8722 * @author Chris Leonello
8723 * @date #date#
8724 * @version #VERSION#
8725 * @copyright (c) 2010 Chris Leonello
8726 * jsDate is currently available for use in all personal or commercial projects
8727 * under both the MIT and GPL version 2.0 licenses. This means that you can
8728 * choose the license that best suits your project and use it accordingly.
8729 *
8730 * <p>Ken's origianl Date Instance Methods and copyright notice:</p>
8731 * <pre>
8732 * Ken Snyder (ken d snyder at gmail dot com)
8733 * 2008-09-10
8734 * version 2.0.2 (http://kendsnyder.com/sandbox/date/)
8735 * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
8736 * </pre>
8737 *
8738 * @class
8739 * @name jsDate
8740 * @param {String | Number | Array | Date&nbsp;Object | Options&nbsp;Object} arguments Optional arguments, either a parsable date/time string,
8741 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
8742 * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional.
8743 */
8744
8745 var jsDate = function () {
8746
8747 this.syntax = jsDate.config.syntax;
8748 this._type = "jsDate";
8749 this.utcOffset = new Date().getTimezoneOffset * 60000;
8750 this.proxy = new Date();
8751 this.options = {};
8752 this.locale = jsDate.regional.getLocale();
8753 this.formatString = '';
8754 this.defaultCentury = jsDate.config.defaultCentury;
8755
8756 switch ( arguments.length ) {
8757 case 0:
8758 break;
8759 case 1:
8760 // other objects either won't have a _type property or,
8761 // if they do, it shouldn't be set to "jsDate", so
8762 // assume it is an options argument.
8763 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
8764 var opts = this.options = arguments[0];
8765 this.syntax = opts.syntax || this.syntax;
8766 this.defaultCentury = opts.defaultCentury || this.defaultCentury;
8767 this.proxy = jsDate.createDate(opts.date);
8768 }
8769 else {
8770 this.proxy = jsDate.createDate(arguments[0]);
8771 }
8772 break;
8773 default:
8774 var a = [];
8775 for ( var i=0; i<arguments.length; i++ ) {
8776 a.push(arguments[i]);
8777 }
8778 this.proxy = new Date( this.utcOffset );
8779 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
8780 if ( a.slice(3).length ) {
8781 this.proxy.setHours.apply( this.proxy, a.slice(3) );
8782 }
8783 break;
8784 }
8785 };
8786
8787 /**
8788 * @namespace Configuration options that will be used as defaults for all instances on the page.
8789 * @property {String} defaultLocale The default locale to use [en].
8790 * @property {String} syntax The default syntax to use [perl].
8791 */
8792 jsDate.config = {
8793 defaultLocale: 'en',
8794 syntax: 'perl',
8795 defaultCentury: 1900
8796 };
8797
8798 /**
8799 * Add an arbitrary amount to the currently stored date
8800 *
8801 * @param {Number} number
8802 * @param {String} unit
8803 * @returns {jsDate}
8804 */
8805
8806 jsDate.prototype.add = function(number, unit) {
8807 var factor = multipliers[unit] || multipliers.day;
8808 if (typeof factor == 'number') {
8809 this.proxy.setTime(this.proxy.getTime() + (factor * number));
8810 } else {
8811 factor.add(this, number);
8812 }
8813 return this;
8814 };
8815
8816 /**
8817 * Create a new jqplot.date object with the same date
8818 *
8819 * @returns {jsDate}
8820 */
8821
8822 jsDate.prototype.clone = function() {
8823 return new jsDate(this.proxy.getTime());
8824 };
8825
8826 /**
8827 * Find the difference between this jsDate and another date.
8828 *
8829 * @param {String| Number| Array| jsDate&nbsp;Object| Date&nbsp;Object} dateObj
8830 * @param {String} unit
8831 * @param {Boolean} allowDecimal
8832 * @returns {Number} Number of units difference between dates.
8833 */
8834
8835 jsDate.prototype.diff = function(dateObj, unit, allowDecimal) {
8836 // ensure we have a Date object
8837 dateObj = new jsDate(dateObj);
8838 if (dateObj === null) {
8839 return null;
8840 }
8841 // get the multiplying factor integer or factor function
8842 var factor = multipliers[unit] || multipliers.day;
8843 if (typeof factor == 'number') {
8844 // multiply
8845 var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor;
8846 } else {
8847 // run function
8848 var unitDiff = factor.diff(this.proxy, dateObj.proxy);
8849 }
8850 // if decimals are not allowed, round toward zero
8851 return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff));
8852 };
8853
8854 /**
8855 * Get the abbreviated name of the current week day
8856 *
8857 * @returns {String}
8858 */
8859
8860 jsDate.prototype.getAbbrDayName = function() {
8861 return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()];
8862 };
8863
8864 /**
8865 * Get the abbreviated name of the current month
8866 *
8867 * @returns {String}
8868 */
8869
8870 jsDate.prototype.getAbbrMonthName = function() {
8871 return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()];
8872 };
8873
8874 /**
8875 * Get UPPER CASE AM or PM for the current time
8876 *
8877 * @returns {String}
8878 */
8879
8880 jsDate.prototype.getAMPM = function() {
8881 return this.proxy.getHours() >= 12 ? 'PM' : 'AM';
8882 };
8883
8884 /**
8885 * Get lower case am or pm for the current time
8886 *
8887 * @returns {String}
8888 */
8889
8890 jsDate.prototype.getAmPm = function() {
8891 return this.proxy.getHours() >= 12 ? 'pm' : 'am';
8892 };
8893
8894 /**
8895 * Get the century (19 for 20th Century)
8896 *
8897 * @returns {Integer} Century (19 for 20th century).
8898 */
8899 jsDate.prototype.getCentury = function() {
8900 return parseInt(this.proxy.getFullYear()/100, 10);
8901 };
8902
8903 /**
8904 * Implements Date functionality
8905 */
8906 jsDate.prototype.getDate = function() {
8907 return this.proxy.getDate();
8908 };
8909
8910 /**
8911 * Implements Date functionality
8912 */
8913 jsDate.prototype.getDay = function() {
8914 return this.proxy.getDay();
8915 };
8916
8917 /**
8918 * Get the Day of week 1 (Monday) thru 7 (Sunday)
8919 *
8920 * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday)
8921 */
8922 jsDate.prototype.getDayOfWeek = function() {
8923 var dow = this.proxy.getDay();
8924 return dow===0?7:dow;
8925 };
8926
8927 /**
8928 * Get the day of the year
8929 *
8930 * @returns {Integer} 1 - 366, day of the year
8931 */
8932 jsDate.prototype.getDayOfYear = function() {
8933 var d = this.proxy;
8934 var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
8935 ms += d.getTimezoneOffset()*60000;
8936 d = null;
8937 return parseInt(ms/60000/60/24, 10)+1;
8938 };
8939
8940 /**
8941 * Get the name of the current week day
8942 *
8943 * @returns {String}
8944 */
8945
8946 jsDate.prototype.getDayName = function() {
8947 return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()];
8948 };
8949
8950 /**
8951 * Get the week number of the given year, starting with the first Sunday as the first week
8952 * @returns {Integer} Week number (13 for the 13th full week of the year).
8953 */
8954 jsDate.prototype.getFullWeekOfYear = function() {
8955 var d = this.proxy;
8956 var doy = this.getDayOfYear();
8957 var rdow = 6-d.getDay();
8958 var woy = parseInt((doy+rdow)/7, 10);
8959 return woy;
8960 };
8961
8962 /**
8963 * Implements Date functionality
8964 */
8965 jsDate.prototype.getFullYear = function() {
8966 return this.proxy.getFullYear();
8967 };
8968
8969 /**
8970 * Get the GMT offset in hours and minutes (e.g. +06:30)
8971 *
8972 * @returns {String}
8973 */
8974
8975 jsDate.prototype.getGmtOffset = function() {
8976 // divide the minutes offset by 60
8977 var hours = this.proxy.getTimezoneOffset() / 60;
8978 // decide if we are ahead of or behind GMT
8979 var prefix = hours < 0 ? '+' : '-';
8980 // remove the negative sign if any
8981 hours = Math.abs(hours);
8982 // add the +/- to the padded number of hours to : to the padded minutes
8983 return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2);
8984 };
8985
8986 /**
8987 * Implements Date functionality
8988 */
8989 jsDate.prototype.getHours = function() {
8990 return this.proxy.getHours();
8991 };
8992
8993 /**
8994 * Get the current hour on a 12-hour scheme
8995 *
8996 * @returns {Integer}
8997 */
8998
8999 jsDate.prototype.getHours12 = function() {
9000 var hours = this.proxy.getHours();
9001 return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
9002 };
9003
9004
9005 jsDate.prototype.getIsoWeek = function() {
9006 var d = this.proxy;
9007 var woy = d.getWeekOfYear();
9008 var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
9009 // First week is 01 and not 00 as in the case of %U and %W,
9010 // so we add 1 to the final result except if day 1 of the year
9011 // is a Monday (then %W returns 01).
9012 // We also need to subtract 1 if the day 1 of the year is
9013 // Friday-Sunday, so the resulting equation becomes:
9014 var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
9015 if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
9016 {
9017 idow = 1;
9018 }
9019 else if(idow === 0)
9020 {
9021 d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31'));
9022 idow = d.getIsoWeek();
9023 }
9024 d = null;
9025 return idow;
9026 };
9027
9028 /**
9029 * Implements Date functionality
9030 */
9031 jsDate.prototype.getMilliseconds = function() {
9032 return this.proxy.getMilliseconds();
9033 };
9034
9035 /**
9036 * Implements Date functionality
9037 */
9038 jsDate.prototype.getMinutes = function() {
9039 return this.proxy.getMinutes();
9040 };
9041
9042 /**
9043 * Implements Date functionality
9044 */
9045 jsDate.prototype.getMonth = function() {
9046 return this.proxy.getMonth();
9047 };
9048
9049 /**
9050 * Get the name of the current month
9051 *
9052 * @returns {String}
9053 */
9054
9055 jsDate.prototype.getMonthName = function() {
9056 return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()];
9057 };
9058
9059 /**
9060 * Get the number of the current month, 1-12
9061 *
9062 * @returns {Integer}
9063 */
9064
9065 jsDate.prototype.getMonthNumber = function() {
9066 return this.proxy.getMonth() + 1;
9067 };
9068
9069 /**
9070 * Implements Date functionality
9071 */
9072 jsDate.prototype.getSeconds = function() {
9073 return this.proxy.getSeconds();
9074 };
9075
9076 /**
9077 * Return a proper two-digit year integer
9078 *
9079 * @returns {Integer}
9080 */
9081
9082 jsDate.prototype.getShortYear = function() {
9083 return this.proxy.getYear() % 100;
9084 };
9085
9086 /**
9087 * Implements Date functionality
9088 */
9089 jsDate.prototype.getTime = function() {
9090 return this.proxy.getTime();
9091 };
9092
9093 /**
9094 * Get the timezone abbreviation
9095 *
9096 * @returns {String} Abbreviation for the timezone
9097 */
9098 jsDate.prototype.getTimezoneAbbr = function() {
9099 return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1');
9100 };
9101
9102 /**
9103 * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time)
9104 *
9105 * @returns {String}
9106 */
9107 jsDate.prototype.getTimezoneName = function() {
9108 var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString());
9109 return match[1] || match[2] || 'GMT' + this.getGmtOffset();
9110 };
9111
9112 /**
9113 * Implements Date functionality
9114 */
9115 jsDate.prototype.getTimezoneOffset = function() {
9116 return this.proxy.getTimezoneOffset();
9117 };
9118
9119
9120 /**
9121 * Get the week number of the given year, starting with the first Monday as the first week
9122 * @returns {Integer} Week number (13 for the 13th week of the year).
9123 */
9124 jsDate.prototype.getWeekOfYear = function() {
9125 var doy = this.getDayOfYear();
9126 var rdow = 7 - this.getDayOfWeek();
9127 var woy = parseInt((doy+rdow)/7, 10);
9128 return woy;
9129 };
9130
9131 /**
9132 * Get the current date as a Unix timestamp
9133 *
9134 * @returns {Integer}
9135 */
9136
9137 jsDate.prototype.getUnix = function() {
9138 return Math.round(this.proxy.getTime() / 1000, 0);
9139 };
9140
9141 /**
9142 * Implements Date functionality
9143 */
9144 jsDate.prototype.getYear = function() {
9145 return this.proxy.getYear();
9146 };
9147
9148 /**
9149 * Return a date one day ahead (or any other unit)
9150 *
9151 * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond
9152 * @returns {jsDate}
9153 */
9154
9155 jsDate.prototype.next = function(unit) {
9156 unit = unit || 'day';
9157 return this.clone().add(1, unit);
9158 };
9159
9160 /**
9161 * Set the jsDate instance to a new date.
9162 *
9163 * @param {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments,
9164 * either a parsable date/time string,
9165 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
9166 * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional.
9167 */
9168 jsDate.prototype.set = function() {
9169 switch ( arguments.length ) {
9170 case 0:
9171 this.proxy = new Date();
9172 break;
9173 case 1:
9174 // other objects either won't have a _type property or,
9175 // if they do, it shouldn't be set to "jsDate", so
9176 // assume it is an options argument.
9177 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
9178 var opts = this.options = arguments[0];
9179 this.syntax = opts.syntax || this.syntax;
9180 this.defaultCentury = opts.defaultCentury || this.defaultCentury;
9181 this.proxy = jsDate.createDate(opts.date);
9182 }
9183 else {
9184 this.proxy = jsDate.createDate(arguments[0]);
9185 }
9186 break;
9187 default:
9188 var a = [];
9189 for ( var i=0; i<arguments.length; i++ ) {
9190 a.push(arguments[i]);
9191 }
9192 this.proxy = new Date( this.utcOffset );
9193 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
9194 if ( a.slice(3).length ) {
9195 this.proxy.setHours.apply( this.proxy, a.slice(3) );
9196 }
9197 break;
9198 }
9199 };
9200
9201 /**
9202 * Sets the day of the month for a specified date according to local time.
9203 * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month.
9204 */
9205 jsDate.prototype.setDate = function(n) {
9206 return this.proxy.setDate(n);
9207 };
9208
9209 /**
9210 * Sets the full year for a specified date according to local time.
9211 * @param {Integer} yearValue The numeric value of the year, for example, 1995.
9212 * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December.
9213 * @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.
9214 */
9215 jsDate.prototype.setFullYear = function() {
9216 return this.proxy.setFullYear.apply(this.proxy, arguments);
9217 };
9218
9219 /**
9220 * Sets the hours for a specified date according to local time.
9221 *
9222 * @param {Integer} hoursValue An integer between 0 and 23, representing the hour.
9223 * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes.
9224 * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds.
9225 * If you specify the secondsValue parameter, you must also specify the minutesValue.
9226 * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds.
9227 * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue.
9228 */
9229 jsDate.prototype.setHours = function() {
9230 return this.proxy.setHours.apply(this.proxy, arguments);
9231 };
9232
9233 /**
9234 * Implements Date functionality
9235 */
9236 jsDate.prototype.setMilliseconds = function(n) {
9237 return this.proxy.setMilliseconds(n);
9238 };
9239
9240 /**
9241 * Implements Date functionality
9242 */
9243 jsDate.prototype.setMinutes = function() {
9244 return this.proxy.setMinutes.apply(this.proxy, arguments);
9245 };
9246
9247 /**
9248 * Implements Date functionality
9249 */
9250 jsDate.prototype.setMonth = function() {
9251 return this.proxy.setMonth.apply(this.proxy, arguments);
9252 };
9253
9254 /**
9255 * Implements Date functionality
9256 */
9257 jsDate.prototype.setSeconds = function() {
9258 return this.proxy.setSeconds.apply(this.proxy, arguments);
9259 };
9260
9261 /**
9262 * Implements Date functionality
9263 */
9264 jsDate.prototype.setTime = function(n) {
9265 return this.proxy.setTime(n);
9266 };
9267
9268 /**
9269 * Implements Date functionality
9270 */
9271 jsDate.prototype.setYear = function() {
9272 return this.proxy.setYear.apply(this.proxy, arguments);
9273 };
9274
9275 /**
9276 * Provide a formatted string representation of this date.
9277 *
9278 * @param {String} formatString A format string.
9279 * See: {@link jsDate.formats}.
9280 * @returns {String} Date String.
9281 */
9282
9283 jsDate.prototype.strftime = function(formatString) {
9284 formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString'];
9285 return jsDate.strftime(this, formatString, this.syntax);
9286 };
9287
9288 /**
9289 * Return a String representation of this jsDate object.
9290 * @returns {String} Date string.
9291 */
9292
9293 jsDate.prototype.toString = function() {
9294 return this.proxy.toString();
9295 };
9296
9297 /**
9298 * Convert the current date to an 8-digit integer (%Y%m%d)
9299 *
9300 * @returns {Integer}
9301 */
9302
9303 jsDate.prototype.toYmdInt = function() {
9304 return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate();
9305 };
9306
9307 /**
9308 * @namespace Holds localizations for month/day names.
9309 * <p>jsDate attempts to detect locale when loaded and defaults to 'en'.
9310 * If a localization is detected which is not available, jsDate defaults to 'en'.
9311 * Additional localizations can be added after jsDate loads. After adding a localization,
9312 * call the jsDate.regional.getLocale() method. Currently, en, fr and de are defined.</p>
9313 *
9314 * <p>Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p>
9315 * <pre class="code">
9316 * jsDate.regional['en'] = {
9317 * monthNames : 'January February March April May June July August September October November December'.split(' '),
9318 * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
9319 * dayNames : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
9320 * dayNamesShort : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
9321 * };
9322 * </pre>
9323 * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the
9324 * new localizations.</p>
9325 */
9326
9327 jsDate.regional = {
9328 'en': {
9329 monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
9330 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
9331 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
9332 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
9333 formatString: '%Y-%m-%d %H:%M:%S'
9334 },
9335
9336 'fr': {
9337 monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
9338 monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'],
9339 dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
9340 dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
9341 formatString: '%Y-%m-%d %H:%M:%S'
9342 },
9343
9344 'de': {
9345 monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
9346 monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
9347 dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
9348 dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
9349 formatString: '%Y-%m-%d %H:%M:%S'
9350 },
9351
9352 'es': {
9353 monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
9354 monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
9355 dayNames: ['Domingo','Lunes','Martes','Mi&eacute;rcoles','Jueves','Viernes','S&aacute;bado'],
9356 dayNamesShort: ['Dom','Lun','Mar','Mi&eacute;','Juv','Vie','S&aacute;b'],
9357 formatString: '%Y-%m-%d %H:%M:%S'
9358 },
9359
9360 'ru': {
9361 monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
9362 monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'],
9363 dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'],
9364 dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'],
9365 formatString: '%Y-%m-%d %H:%M:%S'
9366 },
9367
9368 'ar': {
9369 monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'],
9370 monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
9371 dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'],
9372 dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'],
9373 formatString: '%Y-%m-%d %H:%M:%S'
9374 },
9375
9376 'pt': {
9377 monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
9378 monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
9379 dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
9380 dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
9381 formatString: '%Y-%m-%d %H:%M:%S'
9382 },
9383
9384 'pt-BR': {
9385 monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
9386 monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
9387 dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
9388 dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
9389 formatString: '%Y-%m-%d %H:%M:%S'
9390 }
9391
9392
9393 };
9394
9395 // Set english variants to 'en'
9396 jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
9397
9398 /**
9399 * Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en'
9400 * if it cannot figure out a locale of if the locale does not have a localization defined.
9401 * @returns {String} locale
9402 */
9403
9404 jsDate.regional.getLocale = function () {
9405 var l = jsDate.config.defaultLocale;
9406
9407 if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
9408 l = document.getElementsByTagName('html')[0].lang;
9409 if (!jsDate.regional.hasOwnProperty(l)) {
9410 l = jsDate.config.defaultLocale;
9411 }
9412 }
9413
9414 return l;
9415 };
9416
9417 // ms in day
9418 var day = 24 * 60 * 60 * 1000;
9419
9420 // padd a number with zeros
9421 var addZeros = function(num, digits) {
9422 num = String(num);
9423 var i = digits - num.length;
9424 var s = String(Math.pow(10, i)).slice(1);
9425 return s.concat(num);
9426 };
9427
9428 // representations used for calculating differences between dates.
9429 // This borrows heavily from Ken Snyder's work.
9430 var multipliers = {
9431 millisecond: 1,
9432 second: 1000,
9433 minute: 60 * 1000,
9434 hour: 60 * 60 * 1000,
9435 day: day,
9436 week: 7 * day,
9437 month: {
9438 // add a number of months
9439 add: function(d, number) {
9440 // add any years needed (increments of 12)
9441 multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
9442 // ensure that we properly wrap betwen December and January
9443 var prevMonth = d.getMonth() + (number % 12);
9444 if (prevMonth == 12) {
9445 prevMonth = 0;
9446 d.setYear(d.getFullYear() + 1);
9447 } else if (prevMonth == -1) {
9448 prevMonth = 11;
9449 d.setYear(d.getFullYear() - 1);
9450 }
9451 d.setMonth(prevMonth);
9452 },
9453 // get the number of months between two Date objects (decimal to the nearest day)
9454 diff: function(d1, d2) {
9455 // get the number of years
9456 var diffYears = d1.getFullYear() - d2.getFullYear();
9457 // get the number of remaining months
9458 var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
9459 // get the number of remaining days
9460 var diffDays = d1.getDate() - d2.getDate();
9461 // return the month difference with the days difference as a decimal
9462 return diffMonths + (diffDays / 30);
9463 }
9464 },
9465 year: {
9466 // add a number of years
9467 add: function(d, number) {
9468 d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
9469 },
9470 // get the number of years between two Date objects (decimal to the nearest day)
9471 diff: function(d1, d2) {
9472 return multipliers.month.diff(d1, d2) / 12;
9473 }
9474 }
9475 };
9476 //
9477 // Alias each multiplier with an 's' to allow 'year' and 'years' for example.
9478 // This comes from Ken Snyders work.
9479 //
9480 for (var unit in multipliers) {
9481 if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
9482 multipliers[unit + 's'] = multipliers[unit];
9483 }
9484 }
9485
9486 //
9487 // take a jsDate instance and a format code and return the formatted value.
9488 // This is a somewhat modified version of Ken Snyder's method.
9489 //
9490 var format = function(d, code, syntax) {
9491 // if shorcut codes are used, recursively expand those.
9492 if (jsDate.formats[syntax]["shortcuts"][code]) {
9493 return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
9494 } else {
9495 // get the format code function and addZeros() argument
9496 var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
9497 var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
9498 if (getter[1]) {
9499 nbr = addZeros(nbr, getter[1]);
9500 }
9501 return nbr;
9502 }
9503 };
9504
9505 /**
9506 * @static
9507 * Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes.
9508 * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p>
9509 * <pre class="code">
9510 * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
9511 * </pre>
9512 * @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.
9513 * @param {String} formatString String with embedded date formatting codes.
9514 * See: {@link jsDate.formats}.
9515 * @param {String} syntax Optional syntax to use [default perl].
9516 * @param {String} locale Optional locale to use.
9517 * @returns {String} Formatted representation of the date.
9518 */
9519 //
9520 // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
9521 //
9522 jsDate.strftime = function(d, formatString, syntax, locale) {
9523 var syn = 'perl';
9524 var loc = jsDate.regional.getLocale();
9525
9526 // check if syntax and locale are available or reversed
9527 if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
9528 syn = syntax;
9529 }
9530 else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
9531 loc = syntax;
9532 }
9533
9534 if (locale && jsDate.formats.hasOwnProperty(locale)) {
9535 syn = locale;
9536 }
9537 else if (locale && jsDate.regional.hasOwnProperty(locale)) {
9538 loc = locale;
9539 }
9540
9541 if (get_type(d) != "[object Object]" || d._type != "jsDate") {
9542 d = new jsDate(d);
9543 d.locale = loc;
9544 }
9545 if (!formatString) {
9546 formatString = d.formatString || jsDate.regional[loc]['formatString'];
9547 }
9548 // default the format string to year-month-day
9549 var source = formatString || '%Y-%m-%d',
9550 result = '',
9551 match;
9552 // replace each format code
9553 while (source.length > 0) {
9554 if (match = source.match(jsDate.formats[syn].codes.matcher)) {
9555 result += source.slice(0, match.index);
9556 result += (match[1] || '') + format(d, match[2], syn);
9557 source = source.slice(match.index + match[0].length);
9558 } else {
9559 result += source;
9560 source = '';
9561 }
9562 }
9563 return result;
9564 };
9565
9566 /**
9567 * @namespace
9568 * Namespace to hold format codes and format shortcuts. "perl" and "php" format codes
9569 * and shortcuts are defined by default. Additional codes and shortcuts can be
9570 * added like:
9571 *
9572 * <pre class="code">
9573 * jsDate.formats["perl"] = {
9574 * "codes": {
9575 * matcher: /someregex/,
9576 * Y: "fullYear", // name of "get" method without the "get",
9577 * ..., // more codes
9578 * },
9579 * "shortcuts": {
9580 * F: '%Y-%m-%d',
9581 * ..., // more shortcuts
9582 * }
9583 * };
9584 * </pre>
9585 *
9586 * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via:
9587 * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code>
9588 */
9589
9590 jsDate.formats = {
9591 ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
9592 SQL:'%Y-%m-%d %H:%M:%S'
9593 };
9594
9595 /**
9596 * Perl format codes and shortcuts for strftime.
9597 *
9598 * A hash (object) of codes where each code must be an array where the first member is
9599 * the name of a Date.prototype or jsDate.prototype function to call
9600 * and optionally a second member indicating the number to pass to addZeros()
9601 *
9602 * <p>The following format codes are defined:</p>
9603 *
9604 * <pre class="code">
9605 * Code Result Description
9606 * == Years ==
9607 * %Y 2008 Four-digit year
9608 * %y 08 Two-digit year
9609 *
9610 * == Months ==
9611 * %m 09 Two-digit month
9612 * %#m 9 One or two-digit month
9613 * %B September Full month name
9614 * %b Sep Abbreviated month name
9615 *
9616 * == Days ==
9617 * %d 05 Two-digit day of month
9618 * %#d 5 One or two-digit day of month
9619 * %e 5 One or two-digit day of month
9620 * %A Sunday Full name of the day of the week
9621 * %a Sun Abbreviated name of the day of the week
9622 * %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
9623 *
9624 * == Hours ==
9625 * %H 23 Hours in 24-hour format (two digits)
9626 * %#H 3 Hours in 24-hour integer format (one or two digits)
9627 * %I 11 Hours in 12-hour format (two digits)
9628 * %#I 3 Hours in 12-hour integer format (one or two digits)
9629 * %p PM AM or PM
9630 *
9631 * == Minutes ==
9632 * %M 09 Minutes (two digits)
9633 * %#M 9 Minutes (one or two digits)
9634 *
9635 * == Seconds ==
9636 * %S 02 Seconds (two digits)
9637 * %#S 2 Seconds (one or two digits)
9638 * %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
9639 *
9640 * == Milliseconds ==
9641 * %N 008 Milliseconds (three digits)
9642 * %#N 8 Milliseconds (one to three digits)
9643 *
9644 * == Timezone ==
9645 * %O 360 difference in minutes between local time and GMT
9646 * %Z Mountain Standard Time Name of timezone as reported by browser
9647 * %G 06:00 Hours and minutes between GMT
9648 *
9649 * == Shortcuts ==
9650 * %F 2008-03-26 %Y-%m-%d
9651 * %T 05:06:30 %H:%M:%S
9652 * %X 05:06:30 %H:%M:%S
9653 * %x 03/26/08 %m/%d/%y
9654 * %D 03/26/08 %m/%d/%y
9655 * %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
9656 * %v 3-Sep-2008 %e-%b-%Y
9657 * %R 15:31 %H:%M
9658 * %r 03:31:00 PM %I:%M:%S %p
9659 *
9660 * == Characters ==
9661 * %n \n Newline
9662 * %t \t Tab
9663 * %% % Percent Symbol
9664 * </pre>
9665 *
9666 * <p>Formatting shortcuts that will be translated into their longer version.
9667 * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p>
9668 *
9669 * <p>Format codes and format shortcuts can be redefined after the jsDate
9670 * module is imported.</p>
9671 *
9672 * <p>Note that if you redefine the whole hash (object), you must supply a "matcher"
9673 * regex for the parser. The default matcher is:</p>
9674 *
9675 * <code>/()%(#?(%|[a-z]))/i</code>
9676 *
9677 * <p>which corresponds to the Perl syntax used by default.</p>
9678 *
9679 * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p>
9680 */
9681
9682 jsDate.formats.perl = {
9683 codes: {
9684 //
9685 // 2-part regex matcher for format codes
9686 //
9687 // first match must be the character before the code (to account for escaping)
9688 // second match must be the format code character(s)
9689 //
9690 matcher: /()%(#?(%|[a-z]))/i,
9691 // year
9692 Y: 'FullYear',
9693 y: 'ShortYear.2',
9694 // month
9695 m: 'MonthNumber.2',
9696 '#m': 'MonthNumber',
9697 B: 'MonthName',
9698 b: 'AbbrMonthName',
9699 // day
9700 d: 'Date.2',
9701 '#d': 'Date',
9702 e: 'Date',
9703 A: 'DayName',
9704 a: 'AbbrDayName',
9705 w: 'Day',
9706 // hours
9707 H: 'Hours.2',
9708 '#H': 'Hours',
9709 I: 'Hours12.2',
9710 '#I': 'Hours12',
9711 p: 'AMPM',
9712 // minutes
9713 M: 'Minutes.2',
9714 '#M': 'Minutes',
9715 // seconds
9716 S: 'Seconds.2',
9717 '#S': 'Seconds',
9718 s: 'Unix',
9719 // milliseconds
9720 N: 'Milliseconds.3',
9721 '#N': 'Milliseconds',
9722 // timezone
9723 O: 'TimezoneOffset',
9724 Z: 'TimezoneName',
9725 G: 'GmtOffset'
9726 },
9727
9728 shortcuts: {
9729 // date
9730 F: '%Y-%m-%d',
9731 // time
9732 T: '%H:%M:%S',
9733 X: '%H:%M:%S',
9734 // local format date
9735 x: '%m/%d/%y',
9736 D: '%m/%d/%y',
9737 // local format extended
9738 '#c': '%a %b %e %H:%M:%S %Y',
9739 // local format short
9740 v: '%e-%b-%Y',
9741 R: '%H:%M',
9742 r: '%I:%M:%S %p',
9743 // tab and newline
9744 t: '\t',
9745 n: '\n',
9746 '%': '%'
9747 }
9748 };
9749
9750 /**
9751 * PHP format codes and shortcuts for strftime.
9752 *
9753 * A hash (object) of codes where each code must be an array where the first member is
9754 * the name of a Date.prototype or jsDate.prototype function to call
9755 * and optionally a second member indicating the number to pass to addZeros()
9756 *
9757 * <p>The following format codes are defined:</p>
9758 *
9759 * <pre class="code">
9760 * Code Result Description
9761 * === Days ===
9762 * %a Sun through Sat An abbreviated textual representation of the day
9763 * %A Sunday - Saturday A full textual representation of the day
9764 * %d 01 to 31 Two-digit day of the month (with leading zeros)
9765 * %e 1 to 31 Day of the month, with a space preceding single digits.
9766 * %j 001 to 366 Day of the year, 3 digits with leading zeros
9767 * %u 1 - 7 (Mon - Sun) ISO-8601 numeric representation of the day of the week
9768 * %w 0 - 6 (Sun - Sat) Numeric representation of the day of the week
9769 *
9770 * === Week ===
9771 * %U 13 Full Week number, starting with the first Sunday as the first week
9772 * %V 01 through 53 ISO-8601:1988 week number, starting with the first week of the year
9773 * with at least 4 weekdays, with Monday being the start of the week
9774 * %W 46 A numeric representation of the week of the year,
9775 * starting with the first Monday as the first week
9776 * === Month ===
9777 * %b Jan through Dec Abbreviated month name, based on the locale
9778 * %B January - December Full month name, based on the locale
9779 * %h Jan through Dec Abbreviated month name, based on the locale (an alias of %b)
9780 * %m 01 - 12 (Jan - Dec) Two digit representation of the month
9781 *
9782 * === Year ===
9783 * %C 19 Two digit century (year/100, truncated to an integer)
9784 * %y 09 for 2009 Two digit year
9785 * %Y 2038 Four digit year
9786 *
9787 * === Time ===
9788 * %H 00 through 23 Two digit representation of the hour in 24-hour format
9789 * %I 01 through 12 Two digit representation of the hour in 12-hour format
9790 * %l 1 through 12 Hour in 12-hour format, with a space preceeding single digits
9791 * %M 00 through 59 Two digit representation of the minute
9792 * %p AM/PM UPPER-CASE 'AM' or 'PM' based on the given time
9793 * %P am/pm lower-case 'am' or 'pm' based on the given time
9794 * %r 09:34:17 PM Same as %I:%M:%S %p
9795 * %R 00:35 Same as %H:%M
9796 * %S 00 through 59 Two digit representation of the second
9797 * %T 21:34:17 Same as %H:%M:%S
9798 * %X 03:59:16 Preferred time representation based on locale, without the date
9799 * %z -0500 or EST Either the time zone offset from UTC or the abbreviation
9800 * %Z -0500 or EST The time zone offset/abbreviation option NOT given by %z
9801 *
9802 * === Time and Date ===
9803 * %D 02/05/09 Same as %m/%d/%y
9804 * %F 2009-02-05 Same as %Y-%m-%d (commonly used in database datestamps)
9805 * %s 305815200 Unix Epoch Time timestamp (same as the time() function)
9806 * %x 02/05/09 Preferred date representation, without the time
9807 *
9808 * === Miscellaneous ===
9809 * %n --- A newline character (\n)
9810 * %t --- A Tab character (\t)
9811 * %% --- A literal percentage character (%)
9812 * </pre>
9813 */
9814
9815 jsDate.formats.php = {
9816 codes: {
9817 //
9818 // 2-part regex matcher for format codes
9819 //
9820 // first match must be the character before the code (to account for escaping)
9821 // second match must be the format code character(s)
9822 //
9823 matcher: /()%((%|[a-z]))/i,
9824 // day
9825 a: 'AbbrDayName',
9826 A: 'DayName',
9827 d: 'Date.2',
9828 e: 'Date',
9829 j: 'DayOfYear.3',
9830 u: 'DayOfWeek',
9831 w: 'Day',
9832 // week
9833 U: 'FullWeekOfYear.2',
9834 V: 'IsoWeek.2',
9835 W: 'WeekOfYear.2',
9836 // month
9837 b: 'AbbrMonthName',
9838 B: 'MonthName',
9839 m: 'MonthNumber.2',
9840 h: 'AbbrMonthName',
9841 // year
9842 C: 'Century.2',
9843 y: 'ShortYear.2',
9844 Y: 'FullYear',
9845 // time
9846 H: 'Hours.2',
9847 I: 'Hours12.2',
9848 l: 'Hours12',
9849 p: 'AMPM',
9850 P: 'AmPm',
9851 M: 'Minutes.2',
9852 S: 'Seconds.2',
9853 s: 'Unix',
9854 O: 'TimezoneOffset',
9855 z: 'GmtOffset',
9856 Z: 'TimezoneAbbr'
9857 },
9858
9859 shortcuts: {
9860 D: '%m/%d/%y',
9861 F: '%Y-%m-%d',
9862 T: '%H:%M:%S',
9863 X: '%H:%M:%S',
9864 x: '%m/%d/%y',
9865 R: '%H:%M',
9866 r: '%I:%M:%S %p',
9867 t: '\t',
9868 n: '\n',
9869 '%': '%'
9870 }
9871 };
9872 //
9873 // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
9874 // I use his idea of a set of parsers which can be regular expressions or functions,
9875 // iterating through those, and then seeing if Date.parse() will create a date.
9876 // The parser expressions and functions are a little different and some bugs have been
9877 // worked out. Also, a lot of "pre-parsing" is done to fix implementation
9878 // variations of Date.parse() between browsers.
9879 //
9880 jsDate.createDate = function(date) {
9881 // if passing in multiple arguments, try Date constructor
9882 if (date == null) {
9883 return new Date();
9884 }
9885 // If the passed value is already a date object, return it
9886 if (date instanceof Date) {
9887 return date;
9888 }
9889 // if (typeof date == 'number') return new Date(date * 1000);
9890 // If the passed value is an integer, interpret it as a javascript timestamp
9891 if (typeof date == 'number') {
9892 return new Date(date);
9893 }
9894
9895 // Before passing strings into Date.parse(), have to normalize them for certain conditions.
9896 // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.
9897 //
9898 // For example:
9899 // * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.
9900 // * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
9901 // * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
9902
9903 // remove leading and trailing spaces
9904 var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
9905
9906 // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
9907 parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
9908
9909 /////////
9910 // Need to check for '15-Dec-09' also.
9911 // FF will not parse, but Chrome will.
9912 // Chrome will set date to 2009 as well.
9913 /////////
9914
9915 // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
9916 parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
9917
9918 // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century.
9919 var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
9920 if (match && match.length > 3) {
9921 var m3 = parseFloat(match[3]);
9922 var ny = jsDate.config.defaultCentury + m3;
9923 ny = String(ny);
9924
9925 // now replace 2 digit year with 4 digit year
9926 parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
9927
9928 }
9929
9930 // Check for '1/19/70 8:14PM'
9931 // where starts with mm/dd/yy or yy/mm/dd and have something after
9932 // Check if 1st postiion is greater than 31, assume it is year.
9933 // Assme all 2 digit years are 1900's.
9934 // Finally, change them into US style mm/dd/yyyy representations.
9935 match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
9936
9937 function h1(parsable, match) {
9938 var m1 = parseFloat(match[1]);
9939 var m2 = parseFloat(match[2]);
9940 var m3 = parseFloat(match[3]);
9941 var cent = jsDate.config.defaultCentury;
9942 var ny, nd, nm, str;
9943
9944 if (m1 > 31) { // first number is a year
9945 nd = m3;
9946 nm = m2;
9947 ny = cent + m1;
9948 }
9949
9950 else { // last number is the year
9951 nd = m2;
9952 nm = m1;
9953 ny = cent + m3;
9954 }
9955
9956 str = nm+'/'+nd+'/'+ny;
9957
9958 // now replace 2 digit year with 4 digit year
9959 return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
9960
9961 }
9962
9963 if (match && match.length > 3) {
9964 parsable = h1(parsable, match);
9965 }
9966
9967 // Now check for '1/19/70' with nothing after and do as above
9968 var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
9969
9970 if (match && match.length > 3) {
9971 parsable = h1(parsable, match);
9972 }
9973
9974
9975 var i = 0;
9976 var length = jsDate.matchers.length;
9977 var pattern,
9978 ms,
9979 current = parsable,
9980 obj;
9981 while (i < length) {
9982 ms = Date.parse(current);
9983 if (!isNaN(ms)) {
9984 return new Date(ms);
9985 }
9986 pattern = jsDate.matchers[i];
9987 if (typeof pattern == 'function') {
9988 obj = pattern.call(jsDate, current);
9989 if (obj instanceof Date) {
9990 return obj;
9991 }
9992 } else {
9993 current = parsable.replace(pattern[0], pattern[1]);
9994 }
9995 i++;
9996 }
9997 return NaN;
9998 };
9999
10000
10001 /**
10002 * @static
10003 * Handy static utility function to return the number of days in a given month.
10004 * @param {Integer} year Year
10005 * @param {Integer} month Month (1-12)
10006 * @returns {Integer} Number of days in the month.
10007 */
10008 //
10009 // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
10010 //
10011 jsDate.daysInMonth = function(year, month) {
10012 if (month == 2) {
10013 return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
10014 }
10015 return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
10016 };
10017
10018
10019 //
10020 // An Array of regular expressions or functions that will attempt to match the date string.
10021 // Functions are called with scope of a jsDate instance.
10022 //
10023 jsDate.matchers = [
10024 // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
10025 [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
10026 // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
10027 [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
10028 // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
10029 function(str) {
10030 var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
10031 // opt. date hour opt. minute opt. second opt. msec opt. am or pm
10032 if (match) {
10033 if (match[1]) {
10034 var d = this.createDate(match[1]);
10035 if (isNaN(d)) {
10036 return;
10037 }
10038 } else {
10039 var d = new Date();
10040 d.setMilliseconds(0);
10041 }
10042 var hour = parseFloat(match[2]);
10043 if (match[6]) {
10044 hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
10045 }
10046 d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
10047 return d;
10048 }
10049 else {
10050 return str;
10051 }
10052 },
10053 // Handle ISO timestamp with time zone.
10054 function(str) {
10055 var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
10056 if (match) {
10057 if (match[1]) {
10058 var d = this.createDate(match[1]);
10059 if (isNaN(d)) {
10060 return;
10061 }
10062 } else {
10063 var d = new Date();
10064 d.setMilliseconds(0);
10065 }
10066 var hour = parseFloat(match[2]);
10067 d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
10068 return d;
10069 }
10070 else {
10071 return str;
10072 }
10073 },
10074 // Try to match ambiguous strings like 12/8/22.
10075 // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
10076 // This may be redundant with pre processing of date already performed.
10077 function(str) {
10078 var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
10079 if (match) {
10080 var d = new Date();
10081 var cent = jsDate.config.defaultCentury;
10082 var m1 = parseFloat(match[1]);
10083 var m3 = parseFloat(match[3]);
10084 var ny, nd, nm;
10085 if (m1 > 31) { // first number is a year
10086 nd = m3;
10087 ny = cent + m1;
10088 }
10089
10090 else { // last number is the year
10091 nd = m1;
10092 ny = cent + m3;
10093 }
10094
10095 var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]);
10096
10097 if (nm == -1) {
10098 nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]);
10099 }
10100
10101 d.setFullYear(ny, nm, nd);
10102 d.setHours(0,0,0,0);
10103 return d;
10104 }
10105
10106 else {
10107 return str;
10108 }
10109 }
10110 ];
10111
10112 //
10113 // I think John Reisig published this method on his blog, ejohn.
10114 //
10115 function inArray( elem, array ) {
10116 if ( array.indexOf ) {
10117 return array.indexOf( elem );
10118 }
10119
10120 for ( var i = 0, length = array.length; i < length; i++ ) {
10121 if ( array[ i ] === elem ) {
10122 return i;
10123 }
10124 }
10125
10126 return -1;
10127 }
10128
10129 //
10130 // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
10131 //
10132 function get_type(thing){
10133 if(thing===null) return "[object Null]"; // special case
10134 return Object.prototype.toString.call(thing);
10135 }
10136
10137 $.jsDate = jsDate;
10138
10139
10140 /**
10141 * JavaScript printf/sprintf functions.
10142 *
10143 * This code has been adapted from the publicly available sprintf methods
10144 * by Ash Searle. His original header follows:
10145 *
10146 * This code is unrestricted: you are free to use it however you like.
10147 *
10148 * The functions should work as expected, performing left or right alignment,
10149 * truncating strings, outputting numbers with a required precision etc.
10150 *
10151 * For complex cases, these functions follow the Perl implementations of
10152 * (s)printf, allowing arguments to be passed out-of-order, and to set the
10153 * precision or length of the output based on arguments instead of fixed
10154 * numbers.
10155 *
10156 * See http://perldoc.perl.org/functions/sprintf.html for more information.
10157 *
10158 * Implemented:
10159 * - zero and space-padding
10160 * - right and left-alignment,
10161 * - base X prefix (binary, octal and hex)
10162 * - positive number prefix
10163 * - (minimum) width
10164 * - precision / truncation / maximum width
10165 * - out of order arguments
10166 *
10167 * Not implemented (yet):
10168 * - vector flag
10169 * - size (bytes, words, long-words etc.)
10170 *
10171 * Will not implement:
10172 * - %n or %p (no pass-by-reference in JavaScript)
10173 *
10174 * @version 2007.04.27
10175 * @author Ash Searle
10176 *
10177 * You can see the original work and comments on his blog:
10178 * http://hexmen.com/blog/2007/03/printf-sprintf/
10179 * http://hexmen.com/js/sprintf.js
10180 */
10181
10182 /**
10183 * @Modifications 2009.05.26
10184 * @author Chris Leonello
10185 *
10186 * Added %p %P specifier
10187 * Acts like %g or %G but will not add more significant digits to the output than present in the input.
10188 * Example:
10189 * Format: '%.3p', Input: 0.012, Output: 0.012
10190 * Format: '%.3g', Input: 0.012, Output: 0.0120
10191 * Format: '%.4p', Input: 12.0, Output: 12.0
10192 * Format: '%.4g', Input: 12.0, Output: 12.00
10193 * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5
10194 * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5
10195 *
10196 * Example:
10197 * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23)
10198 * "23.35, 43"
10199 * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524)
10200 * "no value: , decimal with thousands separator: 433,524"
10201 */
10202 $.jqplot.sprintf = function() {
10203 function pad(str, len, chr, leftJustify) {
10204 var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
10205 return leftJustify ? str + padding : padding + str;
10206
10207 }
10208
10209 function thousand_separate(value) {
10210 var value_str = new String(value);
10211 for (var i=10; i>0; i--) {
10212 if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break;
10213 }
10214 return value_str;
10215 }
10216
10217 function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) {
10218 var diff = minWidth - value.length;
10219 if (diff > 0) {
10220 var spchar = ' ';
10221 if (htmlSpace) { spchar = '&nbsp;'; }
10222 if (leftJustify || !zeroPad) {
10223 value = pad(value, minWidth, spchar, leftJustify);
10224 } else {
10225 value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
10226 }
10227 }
10228 return value;
10229 }
10230
10231 function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
10232 // Note: casts negative numbers to positive ones
10233 var number = value >>> 0;
10234 prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
10235 value = prefix + pad(number.toString(base), precision || 0, '0', false);
10236 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10237 }
10238
10239 function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
10240 if (precision != null) {
10241 value = value.slice(0, precision);
10242 }
10243 return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace);
10244 }
10245
10246 var a = arguments, i = 0, format = a[i++];
10247
10248 return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
10249 if (substring == '%%') { return '%'; }
10250
10251 // parse flags
10252 var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false;
10253 for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
10254 case ' ': positivePrefix = ' '; break;
10255 case '+': positivePrefix = '+'; break;
10256 case '-': leftJustify = true; break;
10257 case '0': zeroPad = true; break;
10258 case '#': prefixBaseX = true; break;
10259 case '&': htmlSpace = true; break;
10260 case '\'': thousandSeparation = true; break;
10261 }
10262
10263 // parameters may be null, undefined, empty-string or real valued
10264 // we want to ignore null, undefined and empty-string values
10265
10266 if (!minWidth) {
10267 minWidth = 0;
10268 }
10269 else if (minWidth == '*') {
10270 minWidth = +a[i++];
10271 }
10272 else if (minWidth.charAt(0) == '*') {
10273 minWidth = +a[minWidth.slice(1, -1)];
10274 }
10275 else {
10276 minWidth = +minWidth;
10277 }
10278
10279 // Note: undocumented perl feature:
10280 if (minWidth < 0) {
10281 minWidth = -minWidth;
10282 leftJustify = true;
10283 }
10284
10285 if (!isFinite(minWidth)) {
10286 throw new Error('$.jqplot.sprintf: (minimum-)width must be finite');
10287 }
10288
10289 if (!precision) {
10290 precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
10291 }
10292 else if (precision == '*') {
10293 precision = +a[i++];
10294 }
10295 else if (precision.charAt(0) == '*') {
10296 precision = +a[precision.slice(1, -1)];
10297 }
10298 else {
10299 precision = +precision;
10300 }
10301
10302 // grab value using valueIndex if required?
10303 var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
10304
10305 switch (type) {
10306 case 's': {
10307 if (value == null) {
10308 return '';
10309 }
10310 return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
10311 }
10312 case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
10313 case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace);
10314 case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10315 case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10316 case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase();
10317 case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10318 case 'i': {
10319 var number = parseInt(+value, 10);
10320 if (isNaN(number)) {
10321 return '';
10322 }
10323 var prefix = number < 0 ? '-' : positivePrefix;
10324 var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
10325 value = prefix + pad(number_str, precision, '0', false);
10326 //value = prefix + pad(String(Math.abs(number)), precision, '0', false);
10327 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10328 }
10329 case 'd': {
10330 var number = Math.round(+value);
10331 if (isNaN(number)) {
10332 return '';
10333 }
10334 var prefix = number < 0 ? '-' : positivePrefix;
10335 var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
10336 value = prefix + pad(number_str, precision, '0', false);
10337 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10338 }
10339 case 'e':
10340 case 'E':
10341 case 'f':
10342 case 'F':
10343 case 'g':
10344 case 'G':
10345 {
10346 var number = +value;
10347 if (isNaN(number)) {
10348 return '';
10349 }
10350 var prefix = number < 0 ? '-' : positivePrefix;
10351 var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
10352 var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
10353 var number_str = Math.abs(number)[method](precision);
10354 number_str = thousandSeparation ? thousand_separate(number_str): number_str;
10355 value = prefix + number_str;
10356 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
10357 }
10358 case 'p':
10359 case 'P':
10360 {
10361 // make sure number is a number
10362 var number = +value;
10363 if (isNaN(number)) {
10364 return '';
10365 }
10366 var prefix = number < 0 ? '-' : positivePrefix;
10367
10368 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
10369 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
10370 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
10371
10372 if (Math.abs(number) < 1) {
10373 if (sd + zeros <= precision) {
10374 value = prefix + Math.abs(number).toPrecision(sd);
10375 }
10376 else {
10377 if (sd <= precision - 1) {
10378 value = prefix + Math.abs(number).toExponential(sd-1);
10379 }
10380 else {
10381 value = prefix + Math.abs(number).toExponential(precision-1);
10382 }
10383 }
10384 }
10385 else {
10386 var prec = (sd <= precision) ? sd : precision;
10387 value = prefix + Math.abs(number).toPrecision(prec);
10388 }
10389 var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2];
10390 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
10391 }
10392 case 'n': return '';
10393 default: return substring;
10394 }
10395 });
10396 };
10397
10398 $.jqplot.sprintf.thousandsSeparator = ',';
10399
10400 $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g;
10401
10402 $.jqplot.getSignificantFigures = function(number) {
10403 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
10404 // total significant digits
10405 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
10406 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
10407 // exponent
10408 var expn = parseInt(parts[1]);
10409 // digits to the left of the decimal place
10410 var dleft = (expn + 1 > 0) ? expn + 1 : 0;
10411 // digits to the right of the decimal place
10412 var dright = (sd <= dleft) ? 0 : sd - expn - 1;
10413 return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ;
10414 };
10415
10416 $.jqplot.getPrecision = function(number) {
10417 var arr = $.jqplot.getSignificantFigures(number);
10418 var p = arr[1] - 1 - parseInt(arr[0][1]);
10419 return p;
10420 };
10421
10422})(jQuery);