PluginProbe
HAL / 2.1.1
HAL v2.1.1
trunk 1.0 1.1 1.2 1.3 1.4 1.4.2 1.4.3 1.4.4 2.0 2.0.1 2.0.10 2.0.2 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.2 2.3 2.4 2.4.1 2.4.2 2.5 All 30 releases
hal / trunk / js / jquery.jqplot.js

jquery.jqplot.js in HAL 2.1.1, at trunk/js/jquery.jqplot.js

11,413 lines 455.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Title: jqPlot Charts
3 *
4 * Pure JavaScript plotting plugin for jQuery.
5 *
6 * About: Version
7 *
8 * version: 1.0.8
9 * revision: 1250
10 *
11 * About: Copyright & License
12 *
13 * Copyright (c) 2009-2013 Chris Leonello
14 * jqPlot is currently available for use in all personal or commercial projects
15 * under both the MIT and GPL version 2.0 licenses. This means that you can
16 * choose the license that best suits your project and use it accordingly.
17 *
18 * See <GPL Version 2> and <MIT License> contained within this distribution for further information.
19 *
20 * The author would appreciate an email letting him know of any substantial
21 * use of jqPlot. You can reach the author at: chris at jqplot dot com
22 * or see http://www.jqplot.com/info.php. This is, of course, not required.
23 *
24 * If you are feeling kind and generous, consider supporting the project by
25 * making a donation at: http://www.jqplot.com/donate.php.
26 *
27 * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
28 *
29 * version 2007.04.27
30 * author Ash Searle
31 * http://hexmen.com/blog/2007/03/printf-sprintf/
32 * http://hexmen.com/js/sprintf.js
33 * The author (Ash Searle) has placed this code in the public domain:
34 * "This code is unrestricted: you are free to use it however you like."
35 *
36 *
37 * About: Introduction
38 *
39 * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution.
40 * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally
41 * the excanvas script for IE support in your web page:
42 *
43 * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
44 * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script>
45 * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script>
46 * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" />
47 *
48 * jqPlot can be customized by overriding the defaults of any of the objects which make
49 * up the plot. The general usage of jqplot is:
50 *
51 * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject});
52 *
53 * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file.
54 *
55 * An actual call to $.jqplot() may look like the
56 * examples below:
57 *
58 * > chart = $.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]);
59 *
60 * or
61 *
62 * > dataArray = [34,12,43,55,77];
63 * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}});
64 *
65 * For more inforrmation, see <jqPlot Usage>.
66 *
67 * About: Usage
68 *
69 * See <jqPlot Usage>
70 *
71 * About: Available Options
72 *
73 * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!)
74 *
75 * About: Options Usage
76 *
77 * See <Options Tutorial>
78 *
79 * About: Changes
80 *
81 * See <Change Log>
82 *
83 */
84
85 (function($) {
86 // make sure undefined is undefined
87 var undefined;
88
89 $.fn.emptyForce = function() {
90 for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) {
91 // Remove element nodes and prevent memory leaks
92 if ( elem.nodeType === 1 ) {
93 $.cleanData( elem.getElementsByTagName("*") );
94 }
95
96 // Remove any remaining nodes
97 if ($.jqplot.use_excanvas) {
98 elem.outerHTML = "";
99 }
100 else {
101 while ( elem.firstChild ) {
102 elem.removeChild( elem.firstChild );
103 }
104 }
105
106 elem = null;
107 }
108
109 return $(this);
110 };
111
112 $.fn.removeChildForce = function(parent) {
113 while ( parent.firstChild ) {
114 this.removeChildForce( parent.firstChild );
115 parent.removeChild( parent.firstChild );
116 }
117 };
118
119 $.fn.jqplot = function() {
120 var datas = [];
121 var options = [];
122 // see how many data arrays we have
123
124 for (var i=0, l=arguments.length; i<l; i++) {
125 if ($.isArray(arguments[i])) {
126 datas.push(arguments[i]);
127 }
128 else if ($.isPlainObject(arguments[i])) {
129 options.push(arguments[i]);
130 }
131 }
132
133 return this.each(function(index) {
134 var tid,
135 plot,
136 $this = $(this),
137 dl = datas.length,
138 ol = options.length,
139 data,
140 opts;
141
142 if (index < dl) {
143 data = datas[index];
144 }
145 else {
146 data = dl ? datas[dl-1] : null;
147 }
148
149 if (index < ol) {
150 opts = options[index];
151 }
152 else {
153 opts = ol ? options[ol-1] : null;
154 }
155
156 // does el have an id?
157 // if not assign it one.
158 tid = $this.attr('id');
159 if (tid === undefined) {
160 tid = 'jqplot_target_' + $.jqplot.targetCounter++;
161 $this.attr('id', tid);
162 }
163
164 plot = $.jqplot(tid, data, opts);
165
166 $this.data('jqplot', plot);
167 });
168 };
169
170
171 /**
172 * Namespace: $.jqplot
173 * jQuery function called by the user to create a plot.
174 *
175 * Parameters:
176 * target - ID of target element to render the plot into.
177 * data - an array of data series.
178 * options - user defined options object. See the individual classes for available options.
179 *
180 * Properties:
181 * config - object to hold configuration information for jqPlot plot object.
182 *
183 * attributes:
184 * enablePlugins - False to disable plugins by default. Plugins must then be explicitly
185 * enabled in the individual plot options. Default: false.
186 * This property sets the "show" property of certain plugins to true or false.
187 * Only plugins that can be immediately active upon loading are affected. This includes
188 * non-renderer plugins like cursor, dragable, highlighter, and trendline.
189 * defaultHeight - Default height for plots where no css height specification exists. This
190 * is a jqplot wide default.
191 * defaultWidth - Default height for plots where no css height specification exists. This
192 * is a jqplot wide default.
193 */
194
195 $.jqplot = function(target, data, options) {
196 var _data = null, _options = null;
197
198 if (arguments.length === 3) {
199 _data = data;
200 _options = options;
201 }
202
203 else if (arguments.length === 2) {
204 if ($.isArray(data)) {
205 _data = data;
206 }
207
208 else if ($.isPlainObject(data)) {
209 _options = data;
210 }
211 }
212
213 if (_data === null && _options !== null && _options.data) {
214 _data = _options.data;
215 }
216
217 var plot = new jqPlot();
218 // remove any error class that may be stuck on target.
219 $('#'+target).removeClass('jqplot-error');
220
221 if ($.jqplot.config.catchErrors) {
222 try {
223 plot.init(target, _data, _options);
224 plot.draw();
225 plot.themeEngine.init.call(plot);
226 return plot;
227 }
228 catch(e) {
229 var msg = $.jqplot.config.errorMessage || e.message;
230 $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>');
231 $('#'+target).addClass('jqplot-error');
232 document.getElementById(target).style.background = $.jqplot.config.errorBackground;
233 document.getElementById(target).style.border = $.jqplot.config.errorBorder;
234 document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily;
235 document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize;
236 document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle;
237 document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight;
238 }
239 }
240 else {
241 plot.init(target, _data, _options);
242 plot.draw();
243 plot.themeEngine.init.call(plot);
244 return plot;
245 }
246 };
247
248 $.jqplot.version = "1.0.8";
249 $.jqplot.revision = "1250";
250
251 $.jqplot.targetCounter = 1;
252
253 // canvas manager to reuse canvases on the plot.
254 // Should help solve problem of canvases not being freed and
255 // problem of waiting forever for firefox to decide to free memory.
256 $.jqplot.CanvasManager = function() {
257 // canvases are managed globally so that they can be reused
258 // across plots after they have been freed
259 if (typeof $.jqplot.CanvasManager.canvases == 'undefined') {
260 $.jqplot.CanvasManager.canvases = [];
261 $.jqplot.CanvasManager.free = [];
262 }
263
264 var myCanvases = [];
265
266 this.getCanvas = function() {
267 var canvas;
268 var makeNew = true;
269
270 if (!$.jqplot.use_excanvas) {
271 for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) {
272 if ($.jqplot.CanvasManager.free[i] === true) {
273 makeNew = false;
274 canvas = $.jqplot.CanvasManager.canvases[i];
275 // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse');
276 $.jqplot.CanvasManager.free[i] = false;
277 myCanvases.push(i);
278 break;
279 }
280 }
281 }
282
283 if (makeNew) {
284 canvas = document.createElement('canvas');
285 myCanvases.push($.jqplot.CanvasManager.canvases.length);
286 $.jqplot.CanvasManager.canvases.push(canvas);
287 $.jqplot.CanvasManager.free.push(false);
288 }
289
290 return canvas;
291 };
292
293 // this method has to be used after settings the dimesions
294 // on the element returned by getCanvas()
295 this.initCanvas = function(canvas) {
296 if ($.jqplot.use_excanvas) {
297 return window.G_vmlCanvasManager.initElement(canvas);
298 }
299 return canvas;
300 };
301
302 this.freeAllCanvases = function() {
303 for (var i = 0, l=myCanvases.length; i < l; i++) {
304 this.freeCanvas(myCanvases[i]);
305 }
306 myCanvases = [];
307 };
308
309 this.freeCanvas = function(idx) {
310 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
311 // excanvas can't be reused, but properly unset
312 window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]);
313 $.jqplot.CanvasManager.canvases[idx] = null;
314 }
315 else {
316 var canvas = $.jqplot.CanvasManager.canvases[idx];
317 canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
318 $(canvas).unbind().removeAttr('class').removeAttr('style');
319 // Style attributes seemed to be still hanging around. wierd. Some ticks
320 // still retained a left: 0px attribute after reusing a canvas.
321 $(canvas).css({left: '', top: '', position: ''});
322 // setting size to 0 may save memory of unused canvases?
323 canvas.width = 0;
324 canvas.height = 0;
325 $.jqplot.CanvasManager.free[idx] = true;
326 }
327 };
328
329 };
330
331
332 // Convienence function that won't hang IE or FF without FireBug.
333 $.jqplot.log = function() {
334 if (window.console) {
335 window.console.log.apply(window.console, arguments);
336 }
337 };
338
339 $.jqplot.config = {
340 addDomReference: false,
341 enablePlugins:false,
342 defaultHeight:300,
343 defaultWidth:400,
344 UTCAdjust:false,
345 timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000),
346 errorMessage: '',
347 errorBackground: '',
348 errorBorder: '',
349 errorFontFamily: '',
350 errorFontSize: '',
351 errorFontStyle: '',
352 errorFontWeight: '',
353 catchErrors: false,
354 defaultTickFormatString: "%.1f",
355 defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
356 defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"],
357 dashLength: 4,
358 gapLength: 4,
359 dotGapLength: 2.5,
360 srcLocation: 'jqplot/src/',
361 pluginLocation: 'jqplot/src/plugins/'
362 };
363
364
365 $.jqplot.arrayMax = function( array ){
366 return Math.max.apply( Math, array );
367 };
368
369 $.jqplot.arrayMin = function( array ){
370 return Math.min.apply( Math, array );
371 };
372
373 $.jqplot.enablePlugins = $.jqplot.config.enablePlugins;
374
375 // canvas related tests taken from modernizer:
376 // Copyright (c) 2009 - 2010 Faruk Ates.
377 // http://www.modernizr.com
378
379 $.jqplot.support_canvas = function() {
380 if (typeof $.jqplot.support_canvas.result == 'undefined') {
381 $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext;
382 }
383 return $.jqplot.support_canvas.result;
384 };
385
386 $.jqplot.support_canvas_text = function() {
387 if (typeof $.jqplot.support_canvas_text.result == 'undefined') {
388 if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) {
389 $.jqplot.support_canvas_text.result = true;
390 }
391 else {
392 $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function');
393 }
394
395 }
396 return $.jqplot.support_canvas_text.result;
397 };
398
399 $.jqplot.use_excanvas = ((!$.support.boxModel || !$.support.objectAll || !$support.leadingWhitespace) && !$.jqplot.support_canvas()) ? true : false;
400
401 /**
402 *
403 * Hooks: jqPlot Pugin Hooks
404 *
405 * $.jqplot.preInitHooks - called before initialization.
406 * $.jqplot.postInitHooks - called after initialization.
407 * $.jqplot.preParseOptionsHooks - called before user options are parsed.
408 * $.jqplot.postParseOptionsHooks - called after user options are parsed.
409 * $.jqplot.preDrawHooks - called before plot draw.
410 * $.jqplot.postDrawHooks - called after plot draw.
411 * $.jqplot.preDrawSeriesHooks - called before each series is drawn.
412 * $.jqplot.postDrawSeriesHooks - called after each series is drawn.
413 * $.jqplot.preDrawLegendHooks - called before the legend is drawn.
414 * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins
415 * can add rows to the legend table.
416 * $.jqplot.preSeriesInitHooks - called before series is initialized.
417 * $.jqplot.postSeriesInitHooks - called after series is initialized.
418 * $.jqplot.preParseSeriesOptionsHooks - called before series related options
419 * are parsed.
420 * $.jqplot.postParseSeriesOptionsHooks - called after series related options
421 * are parsed.
422 * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds
423 * listeners to the event canvas which lays on top of the grid area.
424 * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn.
425 * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn.
426 *
427 */
428
429 $.jqplot.preInitHooks = [];
430 $.jqplot.postInitHooks = [];
431 $.jqplot.preParseOptionsHooks = [];
432 $.jqplot.postParseOptionsHooks = [];
433 $.jqplot.preDrawHooks = [];
434 $.jqplot.postDrawHooks = [];
435 $.jqplot.preDrawSeriesHooks = [];
436 $.jqplot.postDrawSeriesHooks = [];
437 $.jqplot.preDrawLegendHooks = [];
438 $.jqplot.addLegendRowHooks = [];
439 $.jqplot.preSeriesInitHooks = [];
440 $.jqplot.postSeriesInitHooks = [];
441 $.jqplot.preParseSeriesOptionsHooks = [];
442 $.jqplot.postParseSeriesOptionsHooks = [];
443 $.jqplot.eventListenerHooks = [];
444 $.jqplot.preDrawSeriesShadowHooks = [];
445 $.jqplot.postDrawSeriesShadowHooks = [];
446
447 // A superclass holding some common properties and methods.
448 $.jqplot.ElemContainer = function() {
449 this._elem;
450 this._plotWidth;
451 this._plotHeight;
452 this._plotDimensions = {height:null, width:null};
453 };
454
455 $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) {
456 this._offsets = offsets;
457 var klass = clss || 'jqplot';
458 var elem = document.createElement(el);
459 this._elem = $(elem);
460 this._elem.addClass(klass);
461 this._elem.css(cssopts);
462 this._elem.attr(attrib);
463 // avoid memory leak;
464 elem = null;
465 return this._elem;
466 };
467
468 $.jqplot.ElemContainer.prototype.getWidth = function() {
469 if (this._elem) {
470 return this._elem.outerWidth(true);
471 }
472 else {
473 return null;
474 }
475 };
476
477 $.jqplot.ElemContainer.prototype.getHeight = function() {
478 if (this._elem) {
479 return this._elem.outerHeight(true);
480 }
481 else {
482 return null;
483 }
484 };
485
486 $.jqplot.ElemContainer.prototype.getPosition = function() {
487 if (this._elem) {
488 return this._elem.position();
489 }
490 else {
491 return {top:null, left:null, bottom:null, right:null};
492 }
493 };
494
495 $.jqplot.ElemContainer.prototype.getTop = function() {
496 return this.getPosition().top;
497 };
498
499 $.jqplot.ElemContainer.prototype.getLeft = function() {
500 return this.getPosition().left;
501 };
502
503 $.jqplot.ElemContainer.prototype.getBottom = function() {
504 return this._elem.css('bottom');
505 };
506
507 $.jqplot.ElemContainer.prototype.getRight = function() {
508 return this._elem.css('right');
509 };
510
511
512 /**
513 * Class: Axis
514 * An individual axis object. Cannot be instantiated directly, but created
515 * by the Plot object. Axis properties can be set or overridden by the
516 * options passed in from the user.
517 *
518 */
519 function Axis(name) {
520 $.jqplot.ElemContainer.call(this);
521 // Group: Properties
522 //
523 // Axes options are specified within an axes object at the top level of the
524 // plot options like so:
525 // > {
526 // > axes: {
527 // > xaxis: {min: 5},
528 // > yaxis: {min: 2, max: 8, numberTicks:4},
529 // > x2axis: {pad: 1.5},
530 // > y2axis: {ticks:[22, 44, 66, 88]}
531 // > }
532 // > }
533 // There are 2 x axes, 'xaxis' and 'x2axis', and
534 // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ... Any or all of which may be specified.
535 this.name = name;
536 this._series = [];
537 // prop: show
538 // Wether to display the axis on the graph.
539 this.show = false;
540 // prop: tickRenderer
541 // A class of a rendering engine for creating the ticks labels displayed on the plot,
542 // See <$.jqplot.AxisTickRenderer>.
543 this.tickRenderer = $.jqplot.AxisTickRenderer;
544 // prop: tickOptions
545 // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options.
546 this.tickOptions = {};
547 // prop: labelRenderer
548 // A class of a rendering engine for creating an axis label.
549 this.labelRenderer = $.jqplot.AxisLabelRenderer;
550 // prop: labelOptions
551 // Options passed to the label renderer.
552 this.labelOptions = {};
553 // prop: label
554 // Label for the axis
555 this.label = null;
556 // prop: showLabel
557 // true to show the axis label.
558 this.showLabel = true;
559 // prop: min
560 // minimum value of the axis (in data units, not pixels).
561 this.min = null;
562 // prop: max
563 // maximum value of the axis (in data units, not pixels).
564 this.max = null;
565 // prop: autoscale
566 // DEPRECATED
567 // the default scaling algorithm produces superior results.
568 this.autoscale = false;
569 // prop: pad
570 // Padding to extend the range above and below the data bounds.
571 // The data range is multiplied by this factor to determine minimum and maximum axis bounds.
572 // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0.
573 this.pad = 1.2;
574 // prop: padMax
575 // Padding to extend the range above data bounds.
576 // The top of the data range is multiplied by this factor to determine maximum axis bounds.
577 // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0.
578 this.padMax = null;
579 // prop: padMin
580 // Padding to extend the range below data bounds.
581 // The bottom of the data range is multiplied by this factor to determine minimum axis bounds.
582 // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0.
583 this.padMin = null;
584 // prop: ticks
585 // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis.
586 // If no label is specified, the value is formatted into an appropriate label.
587 this.ticks = [];
588 // prop: numberTicks
589 // Desired number of ticks. Default is to compute automatically.
590 this.numberTicks;
591 // prop: tickInterval
592 // number of units between ticks. Mutually exclusive with numberTicks.
593 this.tickInterval;
594 // prop: renderer
595 // A class of a rendering engine that handles tick generation,
596 // scaling input data to pixel grid units and drawing the axis element.
597 this.renderer = $.jqplot.LinearAxisRenderer;
598 // prop: rendererOptions
599 // renderer specific options. See <$.jqplot.LinearAxisRenderer> for options.
600 this.rendererOptions = {};
601 // prop: showTicks
602 // Wether to show the ticks (both marks and labels) or not.
603 // Will not override showMark and showLabel options if specified on the ticks themselves.
604 this.showTicks = true;
605 // prop: showTickMarks
606 // Wether to show the tick marks (line crossing grid) or not.
607 // Overridden by showTicks and showMark option of tick itself.
608 this.showTickMarks = true;
609 // prop: showMinorTicks
610 // Wether or not to show minor ticks. This is renderer dependent.
611 this.showMinorTicks = true;
612 // prop: drawMajorGridlines
613 // True to draw gridlines for major axis ticks.
614 this.drawMajorGridlines = true;
615 // prop: drawMinorGridlines
616 // True to draw gridlines for minor ticks.
617 this.drawMinorGridlines = false;
618 // prop: drawMajorTickMarks
619 // True to draw tick marks for major axis ticks.
620 this.drawMajorTickMarks = true;
621 // prop: drawMinorTickMarks
622 // True to draw tick marks for minor ticks. This is renderer dependent.
623 this.drawMinorTickMarks = true;
624 // prop: useSeriesColor
625 // Use the color of the first series associated with this axis for the
626 // tick marks and line bordering this axis.
627 this.useSeriesColor = false;
628 // prop: borderWidth
629 // width of line stroked at the border of the axis. Defaults
630 // to the width of the grid boarder.
631 this.borderWidth = null;
632 // prop: borderColor
633 // color of the border adjacent to the axis. Defaults to grid border color.
634 this.borderColor = null;
635 // prop: scaleToHiddenSeries
636 // True to include hidden series when computing axes bounds and scaling.
637 this.scaleToHiddenSeries = false;
638 // minimum and maximum values on the axis.
639 this._dataBounds = {min:null, max:null};
640 // statistics (min, max, mean) as well as actual data intervals for each series attached to axis.
641 // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis.
642 this._intervalStats = [];
643 // pixel position from the top left of the min value and max value on the axis.
644 this._offsets = {min:null, max:null};
645 this._ticks=[];
646 this._label = null;
647 // prop: syncTicks
648 // true to try and synchronize tick spacing across multiple axes so that ticks and
649 // grid lines line up. This has an impact on autoscaling algorithm, however.
650 // In general, autoscaling an individual axis will work better if it does not
651 // have to sync ticks.
652 this.syncTicks = null;
653 // prop: tickSpacing
654 // Approximate pixel spacing between ticks on graph. Used during autoscaling.
655 // This number will be an upper bound, actual spacing will be less.
656 this.tickSpacing = 75;
657 // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks
658 // so they can be restored if altered by plugins.
659 this._min = null;
660 this._max = null;
661 this._tickInterval = null;
662 this._numberTicks = null;
663 this.__ticks = null;
664 // hold original user options.
665 this._options = {};
666 }
667
668 Axis.prototype = new $.jqplot.ElemContainer();
669 Axis.prototype.constructor = Axis;
670
671 Axis.prototype.init = function() {
672 if ($.isFunction(this.renderer)) {
673 this.renderer = new this.renderer();
674 }
675 // set the axis name
676 this.tickOptions.axis = this.name;
677 // if showMark or showLabel tick options not specified, use value of axis option.
678 // showTicks overrides showTickMarks.
679 if (this.tickOptions.showMark == null) {
680 this.tickOptions.showMark = this.showTicks;
681 }
682 if (this.tickOptions.showMark == null) {
683 this.tickOptions.showMark = this.showTickMarks;
684 }
685 if (this.tickOptions.showLabel == null) {
686 this.tickOptions.showLabel = this.showTicks;
687 }
688
689 if (this.label == null || this.label == '') {
690 this.showLabel = false;
691 }
692 else {
693 this.labelOptions.label = this.label;
694 }
695 if (this.showLabel == false) {
696 this.labelOptions.show = false;
697 }
698 // set the default padMax, padMin if not specified
699 // special check, if no padding desired, padding
700 // should be set to 1.0
701 if (this.pad == 0) {
702 this.pad = 1.0;
703 }
704 if (this.padMax == 0) {
705 this.padMax = 1.0;
706 }
707 if (this.padMin == 0) {
708 this.padMin = 1.0;
709 }
710 if (this.padMax == null) {
711 this.padMax = (this.pad-1)/2 + 1;
712 }
713 if (this.padMin == null) {
714 this.padMin = (this.pad-1)/2 + 1;
715 }
716 // now that padMin and padMax are correctly set, reset pad in case user has supplied
717 // padMin and/or padMax
718 this.pad = this.padMax + this.padMin - 1;
719 if (this.min != null || this.max != null) {
720 this.autoscale = false;
721 }
722 // if not set, sync ticks for y axes but not x by default.
723 if (this.syncTicks == null && this.name.indexOf('y') > -1) {
724 this.syncTicks = true;
725 }
726 else if (this.syncTicks == null){
727 this.syncTicks = false;
728 }
729 this.renderer.init.call(this, this.rendererOptions);
730
731 };
732
733 Axis.prototype.draw = function(ctx, plot) {
734 // Memory Leaks patch
735 if (this.__ticks) {
736 this.__ticks = null;
737 }
738
739 return this.renderer.draw.call(this, ctx, plot);
740
741 };
742
743 Axis.prototype.set = function() {
744 this.renderer.set.call(this);
745 };
746
747 Axis.prototype.pack = function(pos, offsets) {
748 if (this.show) {
749 this.renderer.pack.call(this, pos, offsets);
750 }
751 // these properties should all be available now.
752 if (this._min == null) {
753 this._min = this.min;
754 this._max = this.max;
755 this._tickInterval = this.tickInterval;
756 this._numberTicks = this.numberTicks;
757 this.__ticks = this._ticks;
758 }
759 };
760
761 // reset the axis back to original values if it has been scaled, zoomed, etc.
762 Axis.prototype.reset = function() {
763 this.renderer.reset.call(this);
764 };
765
766 Axis.prototype.resetScale = function(opts) {
767 $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts);
768 this.resetDataBounds();
769 };
770
771 Axis.prototype.resetDataBounds = function() {
772 // Go through all the series attached to this axis and find
773 // the min/max bounds for this axis.
774 var db = this._dataBounds;
775 db.min = null;
776 db.max = null;
777 var l, s, d;
778 // check for when to force min 0 on bar series plots.
779 var doforce = (this.show) ? true : false;
780 for (var i=0; i<this._series.length; i++) {
781 s = this._series[i];
782 if (s.show || this.scaleToHiddenSeries) {
783 d = s._plotData;
784 if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') {
785 d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]];
786 }
787
788 var minyidx = 1, maxyidx = 1;
789
790 if (s._type != null && s._type == 'ohlc') {
791 minyidx = 3;
792 maxyidx = 2;
793 }
794
795 for (var j=0, l=d.length; j<l; j++) {
796 if (this.name == 'xaxis' || this.name == 'x2axis') {
797 if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) {
798 db.min = d[j][0];
799 }
800 if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) {
801 db.max = d[j][0];
802 }
803 }
804 else {
805 if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) {
806 db.min = d[j][minyidx];
807 }
808 if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) {
809 db.max = d[j][maxyidx];
810 }
811 }
812 }
813
814 // Hack to not pad out bottom of bar plots unless user has specified a padding.
815 // every series will have a chance to set doforce to false. once it is set to
816 // false, it cannot be reset to true.
817 // If any series attached to axis is not a bar, wont force 0.
818 if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) {
819 doforce = false;
820 }
821
822 else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) {
823 doforce = false;
824 }
825
826 else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) {
827 if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') {
828 if (this._options.pad != null || this._options.padMin != null) {
829 doforce = false;
830 }
831 }
832
833 else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) {
834 if (this._options.pad != null || this._options.padMin != null) {
835 doforce = false;
836 }
837 }
838
839 }
840 }
841 }
842
843 if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) {
844 this.padMin = 1.0;
845 this.forceTickAt0 = true;
846 }
847 };
848
849 /**
850 * Class: Legend
851 * Legend object. Cannot be instantiated directly, but created
852 * by the Plot object. Legend properties can be set or overridden by the
853 * options passed in from the user.
854 */
855 function Legend(options) {
856 $.jqplot.ElemContainer.call(this);
857 // Group: Properties
858
859 // prop: show
860 // Wether to display the legend on the graph.
861 this.show = false;
862 // prop: location
863 // Placement of the legend. one of the compass directions: nw, n, ne, e, se, s, sw, w
864 this.location = 'ne';
865 // prop: labels
866 // Array of labels to use. By default the renderer will look for labels on the series.
867 // Labels specified in this array will override labels specified on the series.
868 this.labels = [];
869 // prop: showLabels
870 // true to show the label text on the legend.
871 this.showLabels = true;
872 // prop: showSwatch
873 // true to show the color swatches on the legend.
874 this.showSwatches = true;
875 // prop: placement
876 // "insideGrid" places legend inside the grid area of the plot.
877 // "outsideGrid" places the legend outside the grid but inside the plot container,
878 // shrinking the grid to accomodate the legend.
879 // "inside" synonym for "insideGrid",
880 // "outside" places the legend ouside the grid area, but does not shrink the grid which
881 // can cause the legend to overflow the plot container.
882 this.placement = "insideGrid";
883 // prop: xoffset
884 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc.
885 // properties or via CSS margin styling of the .jqplot-table-legend class.
886 this.xoffset = 0;
887 // prop: yoffset
888 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc.
889 // properties or via CSS margin styling of the .jqplot-table-legend class.
890 this.yoffset = 0;
891 // prop: border
892 // css spec for the border around the legend box.
893 this.border;
894 // prop: background
895 // css spec for the background of the legend box.
896 this.background;
897 // prop: textColor
898 // css color spec for the legend text.
899 this.textColor;
900 // prop: fontFamily
901 // css font-family spec for the legend text.
902 this.fontFamily;
903 // prop: fontSize
904 // css font-size spec for the legend text.
905 this.fontSize ;
906 // prop: rowSpacing
907 // css padding-top spec for the rows in the legend.
908 this.rowSpacing = '0.5em';
909 // renderer
910 // A class that will create a DOM object for the legend,
911 // see <$.jqplot.TableLegendRenderer>.
912 this.renderer = $.jqplot.TableLegendRenderer;
913 // prop: rendererOptions
914 // renderer specific options passed to the renderer.
915 this.rendererOptions = {};
916 // prop: predraw
917 // Wether to draw the legend before the series or not.
918 // Used with series specific legend renderers for pie, donut, mekko charts, etc.
919 this.preDraw = false;
920 // prop: marginTop
921 // CSS margin for the legend DOM element. This will set an element
922 // CSS style for the margin which will override any style sheet setting.
923 // The default will be taken from the stylesheet.
924 this.marginTop = null;
925 // prop: marginRight
926 // CSS margin for the legend DOM element. This will set an element
927 // CSS style for the margin which will override any style sheet setting.
928 // The default will be taken from the stylesheet.
929 this.marginRight = null;
930 // prop: marginBottom
931 // CSS margin for the legend DOM element. This will set an element
932 // CSS style for the margin which will override any style sheet setting.
933 // The default will be taken from the stylesheet.
934 this.marginBottom = null;
935 // prop: marginLeft
936 // CSS margin for the legend DOM element. This will set an element
937 // CSS style for the margin which will override any style sheet setting.
938 // The default will be taken from the stylesheet.
939 this.marginLeft = null;
940 // prop: escapeHtml
941 // True to escape special characters with their html entity equivalents
942 // in legend text. "<" becomes &lt; and so on, so html tags are not rendered.
943 this.escapeHtml = false;
944 this._series = [];
945
946 $.extend(true, this, options);
947 }
948
949 Legend.prototype = new $.jqplot.ElemContainer();
950 Legend.prototype.constructor = Legend;
951
952 Legend.prototype.setOptions = function(options) {
953 $.extend(true, this, options);
954
955 // Try to emulate deprecated behaviour
956 // if user has specified xoffset or yoffset, copy these to
957 // the margin properties.
958
959 if (this.placement == 'inside') {
960 this.placement = 'insideGrid';
961 }
962
963 if (this.xoffset >0) {
964 if (this.placement == 'insideGrid') {
965 switch (this.location) {
966 case 'nw':
967 case 'w':
968 case 'sw':
969 if (this.marginLeft == null) {
970 this.marginLeft = this.xoffset + 'px';
971 }
972 this.marginRight = '0px';
973 break;
974 case 'ne':
975 case 'e':
976 case 'se':
977 default:
978 if (this.marginRight == null) {
979 this.marginRight = this.xoffset + 'px';
980 }
981 this.marginLeft = '0px';
982 break;
983 }
984 }
985 else if (this.placement == 'outside') {
986 switch (this.location) {
987 case 'nw':
988 case 'w':
989 case 'sw':
990 if (this.marginRight == null) {
991 this.marginRight = this.xoffset + 'px';
992 }
993 this.marginLeft = '0px';
994 break;
995 case 'ne':
996 case 'e':
997 case 'se':
998 default:
999 if (this.marginLeft == null) {
1000 this.marginLeft = this.xoffset + 'px';
1001 }
1002 this.marginRight = '0px';
1003 break;
1004 }
1005 }
1006 this.xoffset = 0;
1007 }
1008
1009 if (this.yoffset >0) {
1010 if (this.placement == 'outside') {
1011 switch (this.location) {
1012 case 'sw':
1013 case 's':
1014 case 'se':
1015 if (this.marginTop == null) {
1016 this.marginTop = this.yoffset + 'px';
1017 }
1018 this.marginBottom = '0px';
1019 break;
1020 case 'ne':
1021 case 'n':
1022 case 'nw':
1023 default:
1024 if (this.marginBottom == null) {
1025 this.marginBottom = this.yoffset + 'px';
1026 }
1027 this.marginTop = '0px';
1028 break;
1029 }
1030 }
1031 else if (this.placement == 'insideGrid') {
1032 switch (this.location) {
1033 case 'sw':
1034 case 's':
1035 case 'se':
1036 if (this.marginBottom == null) {
1037 this.marginBottom = this.yoffset + 'px';
1038 }
1039 this.marginTop = '0px';
1040 break;
1041 case 'ne':
1042 case 'n':
1043 case 'nw':
1044 default:
1045 if (this.marginTop == null) {
1046 this.marginTop = this.yoffset + 'px';
1047 }
1048 this.marginBottom = '0px';
1049 break;
1050 }
1051 }
1052 this.yoffset = 0;
1053 }
1054
1055 // TO-DO:
1056 // Handle case where offsets are < 0.
1057 //
1058 };
1059
1060 Legend.prototype.init = function() {
1061 if ($.isFunction(this.renderer)) {
1062 this.renderer = new this.renderer();
1063 }
1064 this.renderer.init.call(this, this.rendererOptions);
1065 };
1066
1067 Legend.prototype.draw = function(offsets, plot) {
1068 for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){
1069 $.jqplot.preDrawLegendHooks[i].call(this, offsets);
1070 }
1071 return this.renderer.draw.call(this, offsets, plot);
1072 };
1073
1074 Legend.prototype.pack = function(offsets) {
1075 this.renderer.pack.call(this, offsets);
1076 };
1077
1078 /**
1079 * Class: Title
1080 * Plot Title object. Cannot be instantiated directly, but created
1081 * by the Plot object. Title properties can be set or overridden by the
1082 * options passed in from the user.
1083 *
1084 * Parameters:
1085 * text - text of the title.
1086 */
1087 function Title(text) {
1088 $.jqplot.ElemContainer.call(this);
1089 // Group: Properties
1090
1091 // prop: text
1092 // text of the title;
1093 this.text = text;
1094 // prop: show
1095 // whether or not to show the title
1096 this.show = true;
1097 // prop: fontFamily
1098 // css font-family spec for the text.
1099 this.fontFamily;
1100 // prop: fontSize
1101 // css font-size spec for the text.
1102 this.fontSize ;
1103 // prop: textAlign
1104 // css text-align spec for the text.
1105 this.textAlign;
1106 // prop: textColor
1107 // css color spec for the text.
1108 this.textColor;
1109 // prop: renderer
1110 // A class for creating a DOM element for the title,
1111 // see <$.jqplot.DivTitleRenderer>.
1112 this.renderer = $.jqplot.DivTitleRenderer;
1113 // prop: rendererOptions
1114 // renderer specific options passed to the renderer.
1115 this.rendererOptions = {};
1116 // prop: escapeHtml
1117 // True to escape special characters with their html entity equivalents
1118 // in title text. "<" becomes &lt; and so on, so html tags are not rendered.
1119 this.escapeHtml = false;
1120 }
1121
1122 Title.prototype = new $.jqplot.ElemContainer();
1123 Title.prototype.constructor = Title;
1124
1125 Title.prototype.init = function() {
1126 if ($.isFunction(this.renderer)) {
1127 this.renderer = new this.renderer();
1128 }
1129 this.renderer.init.call(this, this.rendererOptions);
1130 };
1131
1132 Title.prototype.draw = function(width) {
1133 return this.renderer.draw.call(this, width);
1134 };
1135
1136 Title.prototype.pack = function() {
1137 this.renderer.pack.call(this);
1138 };
1139
1140
1141 /**
1142 * Class: Series
1143 * An individual data series object. Cannot be instantiated directly, but created
1144 * by the Plot object. Series properties can be set or overridden by the
1145 * options passed in from the user.
1146 */
1147 function Series(options) {
1148 options = options || {};
1149 $.jqplot.ElemContainer.call(this);
1150 // Group: Properties
1151 // Properties will be assigned from a series array at the top level of the
1152 // options. If you had two series and wanted to change the color and line
1153 // width of the first and set the second to use the secondary y axis with
1154 // no shadow and supply custom labels for each:
1155 // > {
1156 // > series:[
1157 // > {color: '#ff4466', lineWidth: 5, label:'good line'},
1158 // > {yaxis: 'y2axis', shadow: false, label:'bad line'}
1159 // > ]
1160 // > }
1161
1162 // prop: show
1163 // whether or not to draw the series.
1164 this.show = true;
1165 // prop: xaxis
1166 // which x axis to use with this series, either 'xaxis' or 'x2axis'.
1167 this.xaxis = 'xaxis';
1168 this._xaxis;
1169 // prop: yaxis
1170 // which y axis to use with this series, either 'yaxis' or 'y2axis'.
1171 this.yaxis = 'yaxis';
1172 this._yaxis;
1173 this.gridBorderWidth = 2.0;
1174 // prop: renderer
1175 // A class of a renderer which will draw the series,
1176 // see <$.jqplot.LineRenderer>.
1177 this.renderer = $.jqplot.LineRenderer;
1178 // prop: rendererOptions
1179 // Options to pass on to the renderer.
1180 this.rendererOptions = {};
1181 this.data = [];
1182 this.gridData = [];
1183 // prop: label
1184 // Line label to use in the legend.
1185 this.label = '';
1186 // prop: showLabel
1187 // true to show label for this series in the legend.
1188 this.showLabel = true;
1189 // prop: color
1190 // css color spec for the series
1191 this.color;
1192 // prop: negativeColor
1193 // css color spec used for filled (area) plots that are filled to zero and
1194 // the "useNegativeColors" option is true.
1195 this.negativeColor;
1196 // prop: lineWidth
1197 // width of the line in pixels. May have different meanings depending on renderer.
1198 this.lineWidth = 2.5;
1199 // prop: lineJoin
1200 // Canvas lineJoin style between segments of series.
1201 this.lineJoin = 'round';
1202 // prop: lineCap
1203 // Canvas lineCap style at ends of line.
1204 this.lineCap = 'round';
1205 // prop: linePattern
1206 // line pattern 'dashed', 'dotted', 'solid', some combination
1207 // of '-' and '.' characters such as '.-.' or a numerical array like
1208 // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line,
1209 // [1, 10, 20, 10] to draw a dot-dash line, and so on.
1210 this.linePattern = 'solid';
1211 this.shadow = true;
1212 // prop: shadowAngle
1213 // Shadow angle in degrees
1214 this.shadowAngle = 45;
1215 // prop: shadowOffset
1216 // Shadow offset from line in pixels
1217 this.shadowOffset = 1.25;
1218 // prop: shadowDepth
1219 // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
1220 this.shadowDepth = 3;
1221 // prop: shadowAlpha
1222 // Alpha channel transparency of shadow. 0 = transparent.
1223 this.shadowAlpha = '0.1';
1224 // prop: breakOnNull
1225 // Wether line segments should be be broken at null value.
1226 // False will join point on either side of line.
1227 this.breakOnNull = false;
1228 // prop: markerRenderer
1229 // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points,
1230 // see <$.jqplot.MarkerRenderer>.
1231 this.markerRenderer = $.jqplot.MarkerRenderer;
1232 // prop: markerOptions
1233 // renderer specific options to pass to the markerRenderer,
1234 // see <$.jqplot.MarkerRenderer>.
1235 this.markerOptions = {};
1236 // prop: showLine
1237 // whether to actually draw the line or not. Series will still be renderered, even if no line is drawn.
1238 this.showLine = true;
1239 // prop: showMarker
1240 // whether or not to show the markers at the data points.
1241 this.showMarker = true;
1242 // prop: index
1243 // 0 based index of this series in the plot series array.
1244 this.index;
1245 // prop: fill
1246 // true or false, whether to fill under lines or in bars.
1247 // May not be implemented in all renderers.
1248 this.fill = false;
1249 // prop: fillColor
1250 // CSS color spec to use for fill under line. Defaults to line color.
1251 this.fillColor;
1252 // prop: fillAlpha
1253 // Alpha transparency to apply to the fill under the line.
1254 // Use this to adjust alpha separate from fill color.
1255 this.fillAlpha;
1256 // prop: fillAndStroke
1257 // If true will stroke the line (with color this.color) as well as fill under it.
1258 // Applies only when fill is true.
1259 this.fillAndStroke = false;
1260 // prop: disableStack
1261 // true to not stack this series with other series in the plot.
1262 // To render properly, non-stacked series must come after any stacked series
1263 // in the plot's data series array. So, the plot's data series array would look like:
1264 // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...]
1265 // disableStack will put a gap in the stacking order of series, and subsequent
1266 // stacked series will not fill down through the non-stacked series and will
1267 // most likely not stack properly on top of the non-stacked series.
1268 this.disableStack = false;
1269 // _stack is set by the Plot if the plot is a stacked chart.
1270 // will stack lines or bars on top of one another to build a "mountain" style chart.
1271 // May not be implemented in all renderers.
1272 this._stack = false;
1273 // prop: neighborThreshold
1274 // how close or far (in pixels) the cursor must be from a point marker to detect the point.
1275 this.neighborThreshold = 4;
1276 // prop: fillToZero
1277 // true will force bar and filled series to fill toward zero on the fill Axis.
1278 this.fillToZero = false;
1279 // prop: fillToValue
1280 // fill a filled series to this value on the fill axis.
1281 // Works in conjunction with fillToZero, so that must be true.
1282 this.fillToValue = 0;
1283 // prop: fillAxis
1284 // Either 'x' or 'y'. Which axis to fill the line toward if fillToZero is true.
1285 // 'y' means fill up/down to 0 on the y axis for this series.
1286 this.fillAxis = 'y';
1287 // prop: useNegativeColors
1288 // true to color negative values differently in filled and bar charts.
1289 this.useNegativeColors = true;
1290 this._stackData = [];
1291 // _plotData accounts for stacking. If plots not stacked, _plotData and data are same. If
1292 // stacked, _plotData is accumulation of stacking data.
1293 this._plotData = [];
1294 // _plotValues hold the individual x and y values that will be plotted for this series.
1295 this._plotValues = {x:[], y:[]};
1296 // statistics about the intervals between data points. Used for auto scaling.
1297 this._intervals = {x:{}, y:{}};
1298 // data from the previous series, for stacked charts.
1299 this._prevPlotData = [];
1300 this._prevGridData = [];
1301 this._stackAxis = 'y';
1302 this._primaryAxis = '_xaxis';
1303 // give each series a canvas to draw on. This should allow for redrawing speedups.
1304 this.canvas = new $.jqplot.GenericCanvas();
1305 this.shadowCanvas = new $.jqplot.GenericCanvas();
1306 this.plugins = {};
1307 // sum of y values in this series.
1308 this._sumy = 0;
1309 this._sumx = 0;
1310 this._type = '';
1311 }
1312
1313 Series.prototype = new $.jqplot.ElemContainer();
1314 Series.prototype.constructor = Series;
1315
1316 Series.prototype.init = function(index, gridbw, plot) {
1317 // weed out any null values in the data.
1318 this.index = index;
1319 this.gridBorderWidth = gridbw;
1320 var d = this.data;
1321 var temp = [], i, l;
1322 for (i=0, l=d.length; i<l; i++) {
1323 if (! this.breakOnNull) {
1324 if (d[i] == null || d[i][0] == null || d[i][1] == null) {
1325 continue;
1326 }
1327 else {
1328 temp.push(d[i]);
1329 }
1330 }
1331 else {
1332 // TODO: figure out what to do with null values
1333 // probably involve keeping nulls in data array
1334 // and then updating renderers to break line
1335 // when it hits null value.
1336 // For now, just keep value.
1337 temp.push(d[i]);
1338 }
1339 }
1340 this.data = temp;
1341
1342 // parse the renderer options and apply default colors if not provided
1343 // Set color even if not shown, so series don't change colors when other
1344 // series on plot shown/hidden.
1345 if (!this.color) {
1346 this.color = plot.colorGenerator.get(this.index);
1347 }
1348 if (!this.negativeColor) {
1349 this.negativeColor = plot.negativeColorGenerator.get(this.index);
1350 }
1351
1352
1353 if (!this.fillColor) {
1354 this.fillColor = this.color;
1355 }
1356 if (this.fillAlpha) {
1357 var comp = $.jqplot.normalize2rgb(this.fillColor);
1358 var comp = $.jqplot.getColorComponents(comp);
1359 this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')';
1360 }
1361 if ($.isFunction(this.renderer)) {
1362 this.renderer = new this.renderer();
1363 }
1364 this.renderer.init.call(this, this.rendererOptions, plot);
1365 this.markerRenderer = new this.markerRenderer();
1366 if (!this.markerOptions.color) {
1367 this.markerOptions.color = this.color;
1368 }
1369 if (this.markerOptions.show == null) {
1370 this.markerOptions.show = this.showMarker;
1371 }
1372 this.showMarker = this.markerOptions.show;
1373 // the markerRenderer is called within its own scope, don't want to overwrite series options!!
1374 this.markerRenderer.init(this.markerOptions);
1375 };
1376
1377 // data - optional data point array to draw using this series renderer
1378 // gridData - optional grid data point array to draw using this series renderer
1379 // stackData - array of cumulative data for stacked plots.
1380 Series.prototype.draw = function(sctx, opts, plot) {
1381 var options = (opts == undefined) ? {} : opts;
1382 sctx = (sctx == undefined) ? this.canvas._ctx : sctx;
1383
1384 var j, data, gridData;
1385
1386 // hooks get called even if series not shown
1387 // we don't clear canvas here, it would wipe out all other series as well.
1388 for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) {
1389 $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options);
1390 }
1391 if (this.show) {
1392 this.renderer.setGridData.call(this, plot);
1393 if (!options.preventJqPlotSeriesDrawTrigger) {
1394 $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]);
1395 }
1396 data = [];
1397 if (options.data) {
1398 data = options.data;
1399 }
1400 else if (!this._stack) {
1401 data = this.data;
1402 }
1403 else {
1404 data = this._plotData;
1405 }
1406 gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
1407
1408 if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) {
1409 gridData = this.renderer._smoothedData;
1410 }
1411
1412 this.renderer.draw.call(this, sctx, gridData, options, plot);
1413 }
1414
1415 for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
1416 $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options, plot);
1417 }
1418
1419 sctx = opts = plot = j = data = gridData = null;
1420 };
1421
1422 Series.prototype.drawShadow = function(sctx, opts, plot) {
1423 var options = (opts == undefined) ? {} : opts;
1424 sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx;
1425
1426 var j, data, gridData;
1427
1428 // hooks get called even if series not shown
1429 // we don't clear canvas here, it would wipe out all other series as well.
1430 for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) {
1431 $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options);
1432 }
1433 if (this.shadow) {
1434 this.renderer.setGridData.call(this, plot);
1435
1436 data = [];
1437 if (options.data) {
1438 data = options.data;
1439 }
1440 else if (!this._stack) {
1441 data = this.data;
1442 }
1443 else {
1444 data = this._plotData;
1445 }
1446 gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot);
1447
1448 this.renderer.drawShadow.call(this, sctx, gridData, options, plot);
1449 }
1450
1451 for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) {
1452 $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options);
1453 }
1454
1455 sctx = opts = plot = j = data = gridData = null;
1456
1457 };
1458
1459 // toggles series display on plot, e.g. show/hide series
1460 Series.prototype.toggleDisplay = function(ev, callback) {
1461 var s, speed;
1462 if (ev.data.series) {
1463 s = ev.data.series;
1464 }
1465 else {
1466 s = this;
1467 }
1468
1469 if (ev.data.speed) {
1470 speed = ev.data.speed;
1471 }
1472 if (speed) {
1473 // this can be tricky because series may not have a canvas element if replotting.
1474 if (s.canvas._elem.is(':hidden') || !s.show) {
1475 s.show = true;
1476
1477 s.canvas._elem.removeClass('jqplot-series-hidden');
1478 if (s.shadowCanvas._elem) {
1479 s.shadowCanvas._elem.fadeIn(speed);
1480 }
1481 s.canvas._elem.fadeIn(speed, callback);
1482 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed);
1483 }
1484 else {
1485 s.show = false;
1486
1487 s.canvas._elem.addClass('jqplot-series-hidden');
1488 if (s.shadowCanvas._elem) {
1489 s.shadowCanvas._elem.fadeOut(speed);
1490 }
1491 s.canvas._elem.fadeOut(speed, callback);
1492 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed);
1493 }
1494 }
1495 else {
1496 // this can be tricky because series may not have a canvas element if replotting.
1497 if (s.canvas._elem.is(':hidden') || !s.show) {
1498 s.show = true;
1499
1500 s.canvas._elem.removeClass('jqplot-series-hidden');
1501 if (s.shadowCanvas._elem) {
1502 s.shadowCanvas._elem.show();
1503 }
1504 s.canvas._elem.show(0, callback);
1505 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show();
1506 }
1507 else {
1508 s.show = false;
1509
1510 s.canvas._elem.addClass('jqplot-series-hidden');
1511 if (s.shadowCanvas._elem) {
1512 s.shadowCanvas._elem.hide();
1513 }
1514 s.canvas._elem.hide(0, callback);
1515 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide();
1516 }
1517 }
1518 };
1519
1520
1521
1522 /**
1523 * Class: Grid
1524 *
1525 * Object representing the grid on which the plot is drawn. The grid in this
1526 * context is the area bounded by the axes, the area which will contain the series.
1527 * Note, the series are drawn on their own canvas.
1528 * The Grid object cannot be instantiated directly, but is created by the Plot object.
1529 * Grid properties can be set or overridden by the options passed in from the user.
1530 */
1531 function Grid() {
1532 $.jqplot.ElemContainer.call(this);
1533 // Group: Properties
1534
1535 // prop: drawGridlines
1536 // whether to draw the gridlines on the plot.
1537 this.drawGridlines = true;
1538 // prop: gridLineColor
1539 // color of the grid lines.
1540 this.gridLineColor = '#cccccc';
1541 // prop: gridLineWidth
1542 // width of the grid lines.
1543 this.gridLineWidth = 1.0;
1544 // prop: background
1545 // css spec for the background color.
1546 this.background = '#fffdf6';
1547 // prop: borderColor
1548 // css spec for the color of the grid border.
1549 this.borderColor = '#999999';
1550 // prop: borderWidth
1551 // width of the border in pixels.
1552 this.borderWidth = 2.0;
1553 // prop: drawBorder
1554 // True to draw border around grid.
1555 this.drawBorder = true;
1556 // prop: shadow
1557 // whether to show a shadow behind the grid.
1558 this.shadow = true;
1559 // prop: shadowAngle
1560 // shadow angle in degrees
1561 this.shadowAngle = 45;
1562 // prop: shadowOffset
1563 // Offset of each shadow stroke from the border in pixels
1564 this.shadowOffset = 1.5;
1565 // prop: shadowWidth
1566 // width of the stoke for the shadow
1567 this.shadowWidth = 3;
1568 // prop: shadowDepth
1569 // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
1570 this.shadowDepth = 3;
1571 // prop: shadowColor
1572 // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form
1573 this.shadowColor = null;
1574 // prop: shadowAlpha
1575 // Alpha channel transparency of shadow. 0 = transparent.
1576 this.shadowAlpha = '0.07';
1577 this._left;
1578 this._top;
1579 this._right;
1580 this._bottom;
1581 this._width;
1582 this._height;
1583 this._axes = [];
1584 // prop: renderer
1585 // Instance of a renderer which will actually render the grid,
1586 // see <$.jqplot.CanvasGridRenderer>.
1587 this.renderer = $.jqplot.CanvasGridRenderer;
1588 // prop: rendererOptions
1589 // Options to pass on to the renderer,
1590 // see <$.jqplot.CanvasGridRenderer>.
1591 this.rendererOptions = {};
1592 this._offsets = {top:null, bottom:null, left:null, right:null};
1593 }
1594
1595 Grid.prototype = new $.jqplot.ElemContainer();
1596 Grid.prototype.constructor = Grid;
1597
1598 Grid.prototype.init = function() {
1599 if ($.isFunction(this.renderer)) {
1600 this.renderer = new this.renderer();
1601 }
1602 this.renderer.init.call(this, this.rendererOptions);
1603 };
1604
1605 Grid.prototype.createElement = function(offsets,plot) {
1606 this._offsets = offsets;
1607 return this.renderer.createElement.call(this, plot);
1608 };
1609
1610 Grid.prototype.draw = function() {
1611 this.renderer.draw.call(this);
1612 };
1613
1614 $.jqplot.GenericCanvas = function() {
1615 $.jqplot.ElemContainer.call(this);
1616 this._ctx;
1617 };
1618
1619 $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer();
1620 $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas;
1621
1622 $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) {
1623 this._offsets = offsets;
1624 var klass = 'jqplot';
1625 if (clss != undefined) {
1626 klass = clss;
1627 }
1628 var elem;
1629
1630 elem = plot.canvasManager.getCanvas();
1631
1632 // if new plotDimensions supplied, use them.
1633 if (plotDimensions != null) {
1634 this._plotDimensions = plotDimensions;
1635 }
1636
1637 elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right;
1638 elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom;
1639 this._elem = $(elem);
1640 this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top });
1641
1642 this._elem.addClass(klass);
1643
1644 elem = plot.canvasManager.initCanvas(elem);
1645
1646 elem = null;
1647 return this._elem;
1648 };
1649
1650 $.jqplot.GenericCanvas.prototype.setContext = function() {
1651 this._ctx = this._elem.get(0).getContext("2d");
1652 return this._ctx;
1653 };
1654
1655 // Memory Leaks patch
1656 $.jqplot.GenericCanvas.prototype.resetCanvas = function() {
1657 if (this._elem) {
1658 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
1659 window.G_vmlCanvasManager.uninitElement(this._elem.get(0));
1660 }
1661
1662 //this._elem.remove();
1663 this._elem.emptyForce();
1664 }
1665
1666 this._ctx = null;
1667 };
1668
1669 $.jqplot.HooksManager = function () {
1670 this.hooks =[];
1671 this.args = [];
1672 };
1673
1674 $.jqplot.HooksManager.prototype.addOnce = function(fn, args) {
1675 args = args || [];
1676 var havehook = false;
1677 for (var i=0, l=this.hooks.length; i<l; i++) {
1678 if (this.hooks[i] == fn) {
1679 havehook = true;
1680 }
1681 }
1682 if (!havehook) {
1683 this.hooks.push(fn);
1684 this.args.push(args);
1685 }
1686 };
1687
1688 $.jqplot.HooksManager.prototype.add = function(fn, args) {
1689 args = args || [];
1690 this.hooks.push(fn);
1691 this.args.push(args);
1692 };
1693
1694 $.jqplot.EventListenerManager = function () {
1695 this.hooks =[];
1696 };
1697
1698 $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) {
1699 var havehook = false, h, i;
1700 for (var i=0, l=this.hooks.length; i<l; i++) {
1701 h = this.hooks[i];
1702 if (h[0] == ev && h[1] == fn) {
1703 havehook = true;
1704 }
1705 }
1706 if (!havehook) {
1707 this.hooks.push([ev, fn]);
1708 }
1709 };
1710
1711 $.jqplot.EventListenerManager.prototype.add = function(ev, fn) {
1712 this.hooks.push([ev, fn]);
1713 };
1714
1715
1716 var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
1717
1718 /**
1719 * Class: jqPlot
1720 * Plot object returned by call to $.jqplot. Handles parsing user options,
1721 * creating sub objects (Axes, legend, title, series) and rendering the plot.
1722 */
1723 function jqPlot() {
1724 // Group: Properties
1725 // These properties are specified at the top of the options object
1726 // like so:
1727 // > {
1728 // > axesDefaults:{min:0},
1729 // > series:[{color:'#6633dd'}],
1730 // > title: 'A Plot'
1731 // > }
1732 //
1733
1734 // prop: animate
1735 // True to animate the series on initial plot draw (renderer dependent).
1736 // Actual animation functionality must be supported in the renderer.
1737 this.animate = false;
1738 // prop: animateReplot
1739 // True to animate series after a call to the replot() method.
1740 // Use with caution! Replots can happen very frequently under
1741 // certain circumstances (e.g. resizing, dragging points) and
1742 // animation in these situations can cause problems.
1743 this.animateReplot = false;
1744 // prop: axes
1745 // up to 4 axes are supported, each with its own options,
1746 // See <Axis> for axis specific options.
1747 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')};
1748 this.baseCanvas = new $.jqplot.GenericCanvas();
1749 // true to intercept right click events and fire a 'jqplotRightClick' event.
1750 // this will also block the context menu.
1751 this.captureRightClick = false;
1752 // prop: data
1753 // user's data. Data should *NOT* be specified in the options object,
1754 // but be passed in as the second argument to the $.jqplot() function.
1755 // The data property is described here soley for reference.
1756 // The data should be in the form of an array of 2D or 1D arrays like
1757 // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ].
1758 this.data = [];
1759 // prop: dataRenderer
1760 // A callable which can be used to preprocess data passed into the plot.
1761 // Will be called with 3 arguments: the plot data, a reference to the plot,
1762 // and the value of dataRendererOptions.
1763 this.dataRenderer;
1764 // prop: dataRendererOptions
1765 // Options that will be passed to the dataRenderer.
1766 // Can be of any type.
1767 this.dataRendererOptions;
1768 this.defaults = {
1769 // prop: axesDefaults
1770 // default options that will be applied to all axes.
1771 // see <Axis> for axes options.
1772 axesDefaults: {},
1773 axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}},
1774 // prop: seriesDefaults
1775 // default options that will be applied to all series.
1776 // see <Series> for series options.
1777 seriesDefaults: {},
1778 series:[]
1779 };
1780 // prop: defaultAxisStart
1781 // 1-D data series are internally converted into 2-D [x,y] data point arrays
1782 // by jqPlot. This is the default starting value for the missing x or y value.
1783 // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...])
1784 // starting at this value.
1785 this.defaultAxisStart = 1;
1786 // this.doCustomEventBinding = true;
1787 // prop: drawIfHidden
1788 // True to execute the draw method even if the plot target is hidden.
1789 // Generally, this should be false. Most plot elements will not be sized/
1790 // positioned correclty if renderered into a hidden container. To render into
1791 // a hidden container, call the replot method when the container is shown.
1792 this.drawIfHidden = false;
1793 this.eventCanvas = new $.jqplot.GenericCanvas();
1794 // prop: fillBetween
1795 // Fill between 2 line series in a plot.
1796 // Options object:
1797 // {
1798 // series1: first index (0 based) of series in fill
1799 // series2: second index (0 based) of series in fill
1800 // color: color of fill [default fillColor of series1]
1801 // baseSeries: fill will be drawn below this series (0 based index)
1802 // fill: false to turn off fill [default true].
1803 // }
1804 this.fillBetween = {
1805 series1: null,
1806 series2: null,
1807 color: null,
1808 baseSeries: 0,
1809 fill: true
1810 };
1811 // prop; fontFamily
1812 // css spec for the font-family attribute. Default for the entire plot.
1813 this.fontFamily;
1814 // prop: fontSize
1815 // css spec for the font-size attribute. Default for the entire plot.
1816 this.fontSize;
1817 // prop: grid
1818 // See <Grid> for grid specific options.
1819 this.grid = new Grid();
1820 // prop: legend
1821 // see <$.jqplot.TableLegendRenderer>
1822 this.legend = new Legend();
1823 // prop: noDataIndicator
1824 // Options to set up a mock plot with a data loading indicator if no data is specified.
1825 this.noDataIndicator = {
1826 show: false,
1827 indicator: 'Loading Data...',
1828 axes: {
1829 xaxis: {
1830 min: 0,
1831 max: 10,
1832 tickInterval: 2,
1833 show: true
1834 },
1835 yaxis: {
1836 min: 0,
1837 max: 12,
1838 tickInterval: 3,
1839 show: true
1840 }
1841 }
1842 };
1843 // prop: negativeSeriesColors
1844 // colors to use for portions of the line below zero.
1845 this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors;
1846 // container to hold all of the merged options. Convienence for plugins.
1847 this.options = {};
1848 this.previousSeriesStack = [];
1849 // Namespace to hold plugins. Generally non-renderer plugins add themselves to here.
1850 this.plugins = {};
1851 // prop: series
1852 // Array of series object options.
1853 // see <Series> for series specific options.
1854 this.series = [];
1855 // array of series indices. Keep track of order
1856 // which series canvases are displayed, lowest
1857 // to highest, back to front.
1858 this.seriesStack = [];
1859 // prop: seriesColors
1860 // Ann array of CSS color specifications that will be applied, in order,
1861 // to the series in the plot. Colors will wrap around so, if their
1862 // are more series than colors, colors will be reused starting at the
1863 // beginning. For pie charts, this specifies the colors of the slices.
1864 this.seriesColors = $.jqplot.config.defaultColors;
1865 // prop: sortData
1866 // false to not sort the data passed in by the user.
1867 // Many bar, stacked and other graphs as well as many plugins depend on
1868 // having sorted data.
1869 this.sortData = true;
1870 // prop: stackSeries
1871 // true or false, creates a stack or "mountain" plot.
1872 // Not all series renderers may implement this option.
1873 this.stackSeries = false;
1874 // a shortcut for axis syncTicks options. Not implemented yet.
1875 this.syncXTicks = true;
1876 // a shortcut for axis syncTicks options. Not implemented yet.
1877 this.syncYTicks = true;
1878 // the jquery object for the dom target.
1879 this.target = null;
1880 // The id of the dom element to render the plot into
1881 this.targetId = null;
1882 // prop textColor
1883 // css spec for the css color attribute. Default for the entire plot.
1884 this.textColor;
1885 // prop: title
1886 // Title object. See <Title> for specific options. As a shortcut, you
1887 // can specify the title option as just a string like: title: 'My Plot'
1888 // and this will create a new title object with the specified text.
1889 this.title = new Title();
1890 // Count how many times the draw method has been called while the plot is visible.
1891 // Mostly used to test if plot has never been dran (=0), has been successfully drawn
1892 // into a visible container once (=1) or draw more than once into a visible container.
1893 // Can use this in tests to see if plot has been visibly drawn at least one time.
1894 // After plot has been visibly drawn once, it generally doesn't need redrawing if its
1895 // container is hidden and shown.
1896 this._drawCount = 0;
1897 // sum of y values for all series in plot.
1898 // used in mekko chart.
1899 this._sumy = 0;
1900 this._sumx = 0;
1901 // array to hold the cumulative stacked series data.
1902 // used to ajust the individual series data, which won't have access to other
1903 // series data.
1904 this._stackData = [];
1905 // array that holds the data to be plotted. This will be the series data
1906 // merged with the the appropriate data from _stackData according to the stackAxis.
1907 this._plotData = [];
1908 this._width = null;
1909 this._height = null;
1910 this._plotDimensions = {height:null, width:null};
1911 this._gridPadding = {top:null, right:null, bottom:null, left:null};
1912 this._defaultGridPadding = {top:10, right:10, bottom:23, left:10};
1913
1914 this._addDomReference = $.jqplot.config.addDomReference;
1915
1916 this.preInitHooks = new $.jqplot.HooksManager();
1917 this.postInitHooks = new $.jqplot.HooksManager();
1918 this.preParseOptionsHooks = new $.jqplot.HooksManager();
1919 this.postParseOptionsHooks = new $.jqplot.HooksManager();
1920 this.preDrawHooks = new $.jqplot.HooksManager();
1921 this.postDrawHooks = new $.jqplot.HooksManager();
1922 this.preDrawSeriesHooks = new $.jqplot.HooksManager();
1923 this.postDrawSeriesHooks = new $.jqplot.HooksManager();
1924 this.preDrawLegendHooks = new $.jqplot.HooksManager();
1925 this.addLegendRowHooks = new $.jqplot.HooksManager();
1926 this.preSeriesInitHooks = new $.jqplot.HooksManager();
1927 this.postSeriesInitHooks = new $.jqplot.HooksManager();
1928 this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager();
1929 this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager();
1930 this.eventListenerHooks = new $.jqplot.EventListenerManager();
1931 this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager();
1932 this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager();
1933
1934 this.colorGenerator = new $.jqplot.ColorGenerator();
1935 this.negativeColorGenerator = new $.jqplot.ColorGenerator();
1936
1937 this.canvasManager = new $.jqplot.CanvasManager();
1938
1939 this.themeEngine = new $.jqplot.ThemeEngine();
1940
1941 var seriesColorsIndex = 0;
1942
1943 // Group: methods
1944 //
1945 // method: init
1946 // sets the plot target, checks data and applies user
1947 // options to plot.
1948 this.init = function(target, data, options) {
1949 options = options || {};
1950 for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
1951 $.jqplot.preInitHooks[i].call(this, target, data, options);
1952 }
1953
1954 for (var i=0; i<this.preInitHooks.hooks.length; i++) {
1955 this.preInitHooks.hooks[i].call(this, target, data, options);
1956 }
1957
1958 this.targetId = '#'+target;
1959 this.target = $('#'+target);
1960
1961 //////
1962 // Add a reference to plot
1963 //////
1964 if (this._addDomReference) {
1965 this.target.data('jqplot', this);
1966 }
1967 // remove any error class that may be stuck on target.
1968 this.target.removeClass('jqplot-error');
1969 if (!this.target.get(0)) {
1970 throw new Error("No plot target specified");
1971 }
1972
1973 // make sure the target is positioned by some means and set css
1974 if (this.target.css('position') == 'static') {
1975 this.target.css('position', 'relative');
1976 }
1977 if (!this.target.hasClass('jqplot-target')) {
1978 this.target.addClass('jqplot-target');
1979 }
1980
1981 // if no height or width specified, use a default.
1982 if (!this.target.height()) {
1983 var h;
1984 if (options && options.height) {
1985 h = parseInt(options.height, 10);
1986 }
1987 else if (this.target.attr('data-height')) {
1988 h = parseInt(this.target.attr('data-height'), 10);
1989 }
1990 else {
1991 h = parseInt($.jqplot.config.defaultHeight, 10);
1992 }
1993 this._height = h;
1994 this.target.css('height', h+'px');
1995 }
1996 else {
1997 this._height = h = this.target.height();
1998 }
1999 if (!this.target.width()) {
2000 var w;
2001 if (options && options.width) {
2002 w = parseInt(options.width, 10);
2003 }
2004 else if (this.target.attr('data-width')) {
2005 w = parseInt(this.target.attr('data-width'), 10);
2006 }
2007 else {
2008 w = parseInt($.jqplot.config.defaultWidth, 10);
2009 }
2010 this._width = w;
2011 this.target.css('width', w+'px');
2012 }
2013 else {
2014 this._width = w = this.target.width();
2015 }
2016
2017 for (var i=0, l=_axisNames.length; i<l; i++) {
2018 this.axes[_axisNames[i]] = new Axis(_axisNames[i]);
2019 }
2020
2021 this._plotDimensions.height = this._height;
2022 this._plotDimensions.width = this._width;
2023 this.grid._plotDimensions = this._plotDimensions;
2024 this.title._plotDimensions = this._plotDimensions;
2025 this.baseCanvas._plotDimensions = this._plotDimensions;
2026 this.eventCanvas._plotDimensions = this._plotDimensions;
2027 this.legend._plotDimensions = this._plotDimensions;
2028 if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2029 throw new Error("Canvas dimension not set");
2030 }
2031
2032 if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
2033 if (options.dataRendererOptions) {
2034 this.dataRendererOptions = options.dataRendererOptions;
2035 }
2036 this.dataRenderer = options.dataRenderer;
2037 data = this.dataRenderer(data, this, this.dataRendererOptions);
2038 }
2039
2040 if (options.noDataIndicator && $.isPlainObject(options.noDataIndicator)) {
2041 $.extend(true, this.noDataIndicator, options.noDataIndicator);
2042 }
2043
2044 if (data == null || $.isArray(data) == false || data.length == 0 || $.isArray(data[0]) == false || data[0].length == 0) {
2045
2046 if (this.noDataIndicator.show == false) {
2047 throw new Error("No data specified");
2048 }
2049
2050 else {
2051 // have to be descructive here in order for plot to not try and render series.
2052 // This means that $.jqplot() will have to be called again when there is data.
2053 //delete options.series;
2054
2055 for (var ax in this.noDataIndicator.axes) {
2056 for (var prop in this.noDataIndicator.axes[ax]) {
2057 this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop];
2058 }
2059 }
2060
2061 this.postDrawHooks.add(function() {
2062 var eh = this.eventCanvas.getHeight();
2063 var ew = this.eventCanvas.getWidth();
2064 var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>');
2065 this.target.append(temp);
2066 temp.height(eh);
2067 temp.width(ew);
2068 temp.css('top', this.eventCanvas._offsets.top);
2069 temp.css('left', this.eventCanvas._offsets.left);
2070
2071 var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>');
2072 temp.append(temp2);
2073 temp2.html(this.noDataIndicator.indicator);
2074 var th = temp2.height();
2075 var tw = temp2.width();
2076 temp2.height(th);
2077 temp2.width(tw);
2078 temp2.css('top', (eh - th)/2 + 'px');
2079 });
2080
2081 }
2082 }
2083
2084 // make a copy of the data
2085 this.data = $.extend(true, [], data);
2086
2087 this.parseOptions(options);
2088
2089 if (this.textColor) {
2090 this.target.css('color', this.textColor);
2091 }
2092 if (this.fontFamily) {
2093 this.target.css('font-family', this.fontFamily);
2094 }
2095 if (this.fontSize) {
2096 this.target.css('font-size', this.fontSize);
2097 }
2098
2099 this.title.init();
2100 this.legend.init();
2101 this._sumy = 0;
2102 this._sumx = 0;
2103 this.computePlotData();
2104 for (var i=0; i<this.series.length; i++) {
2105 // set default stacking order for series canvases
2106 this.seriesStack.push(i);
2107 this.previousSeriesStack.push(i);
2108 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
2109 this.series[i].canvas._plotDimensions = this._plotDimensions;
2110 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
2111 $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2112 }
2113 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
2114 this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2115 }
2116 // this.populatePlotData(this.series[i], i);
2117 this.series[i]._plotDimensions = this._plotDimensions;
2118 this.series[i].init(i, this.grid.borderWidth, this);
2119 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
2120 $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2121 }
2122 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
2123 this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2124 }
2125 this._sumy += this.series[i]._sumy;
2126 this._sumx += this.series[i]._sumx;
2127 }
2128
2129 var name,
2130 axis;
2131 for (var i=0, l=_axisNames.length; i<l; i++) {
2132 name = _axisNames[i];
2133 axis = this.axes[name];
2134 axis._plotDimensions = this._plotDimensions;
2135 axis.init();
2136 if (this.axes[name].borderColor == null) {
2137 if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
2138 axis.borderColor = axis._series[0].color;
2139 }
2140 else {
2141 axis.borderColor = this.grid.borderColor;
2142 }
2143 }
2144 }
2145
2146 if (this.sortData) {
2147 sortData(this.series);
2148 }
2149 this.grid.init();
2150 this.grid._axes = this.axes;
2151
2152 this.legend._series = this.series;
2153
2154 for (var i=0; i<$.jqplot.postInitHooks.length; i++) {
2155 $.jqplot.postInitHooks[i].call(this, target, this.data, options);
2156 }
2157
2158 for (var i=0; i<this.postInitHooks.hooks.length; i++) {
2159 this.postInitHooks.hooks[i].call(this, target, this.data, options);
2160 }
2161 };
2162
2163 // method: resetAxesScale
2164 // Reset the specified axes min, max, numberTicks and tickInterval properties to null
2165 // or reset these properties on all axes if no list of axes is provided.
2166 //
2167 // Parameters:
2168 // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset.
2169 this.resetAxesScale = function(axes, options) {
2170 var opts = options || {};
2171 var ax = axes || this.axes;
2172 if (ax === true) {
2173 ax = this.axes;
2174 }
2175 if ($.isArray(ax)) {
2176 for (var i = 0; i < ax.length; i++) {
2177 this.axes[ax[i]].resetScale(opts[ax[i]]);
2178 }
2179 }
2180 else if (typeof(ax) === 'object') {
2181 for (var name in ax) {
2182 this.axes[name].resetScale(opts[name]);
2183 }
2184 }
2185 };
2186 // method: reInitialize
2187 // reinitialize plot for replotting.
2188 // not called directly.
2189 this.reInitialize = function (data, opts) {
2190 // Plot should be visible and have a height and width.
2191 // If plot doesn't have height and width for some
2192 // reason, set it by other means. Plot must not have
2193 // a display:none attribute, however.
2194
2195 var options = $.extend(true, {}, this.options, opts);
2196
2197 var target = this.targetId.substr(1);
2198 var tdata = (data == null) ? this.data : data;
2199
2200 for (var i=0; i<$.jqplot.preInitHooks.length; i++) {
2201 $.jqplot.preInitHooks[i].call(this, target, tdata, options);
2202 }
2203
2204 for (var i=0; i<this.preInitHooks.hooks.length; i++) {
2205 this.preInitHooks.hooks[i].call(this, target, tdata, options);
2206 }
2207
2208 this._height = this.target.height();
2209 this._width = this.target.width();
2210
2211 if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2212 throw new Error("Target dimension not set");
2213 }
2214
2215 this._plotDimensions.height = this._height;
2216 this._plotDimensions.width = this._width;
2217 this.grid._plotDimensions = this._plotDimensions;
2218 this.title._plotDimensions = this._plotDimensions;
2219 this.baseCanvas._plotDimensions = this._plotDimensions;
2220 this.eventCanvas._plotDimensions = this._plotDimensions;
2221 this.legend._plotDimensions = this._plotDimensions;
2222
2223 var name,
2224 t,
2225 j,
2226 axis;
2227
2228 for (var i=0, l=_axisNames.length; i<l; i++) {
2229 name = _axisNames[i];
2230 axis = this.axes[name];
2231
2232 // Memory Leaks patch : clear ticks elements
2233 t = axis._ticks;
2234 for (var j = 0, tlen = t.length; j < tlen; j++) {
2235 var el = t[j]._elem;
2236 if (el) {
2237 // if canvas renderer
2238 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
2239 window.G_vmlCanvasManager.uninitElement(el.get(0));
2240 }
2241 el.emptyForce();
2242 el = null;
2243 t._elem = null;
2244 }
2245 }
2246 t = null;
2247
2248 delete axis.ticks;
2249 delete axis._ticks;
2250 this.axes[name] = new Axis(name);
2251 this.axes[name]._plotWidth = this._width;
2252 this.axes[name]._plotHeight = this._height;
2253 }
2254
2255 if (data) {
2256 if (options.dataRenderer && $.isFunction(options.dataRenderer)) {
2257 if (options.dataRendererOptions) {
2258 this.dataRendererOptions = options.dataRendererOptions;
2259 }
2260 this.dataRenderer = options.dataRenderer;
2261 data = this.dataRenderer(data, this, this.dataRendererOptions);
2262 }
2263
2264 // make a copy of the data
2265 this.data = $.extend(true, [], data);
2266 }
2267
2268 if (opts) {
2269 this.parseOptions(options);
2270 }
2271
2272 this.title._plotWidth = this._width;
2273
2274 if (this.textColor) {
2275 this.target.css('color', this.textColor);
2276 }
2277 if (this.fontFamily) {
2278 this.target.css('font-family', this.fontFamily);
2279 }
2280 if (this.fontSize) {
2281 this.target.css('font-size', this.fontSize);
2282 }
2283
2284 this.title.init();
2285 this.legend.init();
2286 this._sumy = 0;
2287 this._sumx = 0;
2288
2289 this.seriesStack = [];
2290 this.previousSeriesStack = [];
2291
2292 this.computePlotData();
2293 for (var i=0, l=this.series.length; i<l; i++) {
2294 // set default stacking order for series canvases
2295 this.seriesStack.push(i);
2296 this.previousSeriesStack.push(i);
2297 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions;
2298 this.series[i].canvas._plotDimensions = this._plotDimensions;
2299 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) {
2300 $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2301 }
2302 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) {
2303 this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2304 }
2305 // this.populatePlotData(this.series[i], i);
2306 this.series[i]._plotDimensions = this._plotDimensions;
2307 this.series[i].init(i, this.grid.borderWidth, this);
2308 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) {
2309 $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2310 }
2311 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) {
2312 this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this);
2313 }
2314 this._sumy += this.series[i]._sumy;
2315 this._sumx += this.series[i]._sumx;
2316 }
2317
2318 for (var i=0, l=_axisNames.length; i<l; i++) {
2319 name = _axisNames[i];
2320 axis = this.axes[name];
2321
2322 axis._plotDimensions = this._plotDimensions;
2323 axis.init();
2324 if (axis.borderColor == null) {
2325 if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) {
2326 axis.borderColor = axis._series[0].color;
2327 }
2328 else {
2329 axis.borderColor = this.grid.borderColor;
2330 }
2331 }
2332 }
2333
2334 if (this.sortData) {
2335 sortData(this.series);
2336 }
2337 this.grid.init();
2338 this.grid._axes = this.axes;
2339
2340 this.legend._series = this.series;
2341
2342 for (var i=0, l=$.jqplot.postInitHooks.length; i<l; i++) {
2343 $.jqplot.postInitHooks[i].call(this, target, this.data, options);
2344 }
2345
2346 for (var i=0, l=this.postInitHooks.hooks.length; i<l; i++) {
2347 this.postInitHooks.hooks[i].call(this, target, this.data, options);
2348 }
2349 };
2350
2351
2352
2353 // method: quickInit
2354 //
2355 // Quick reinitialization plot for replotting.
2356 // Does not parse options ore recreate axes and series.
2357 // not called directly.
2358 this.quickInit = function () {
2359 // Plot should be visible and have a height and width.
2360 // If plot doesn't have height and width for some
2361 // reason, set it by other means. Plot must not have
2362 // a display:none attribute, however.
2363
2364 this._height = this.target.height();
2365 this._width = this.target.width();
2366
2367 if (this._height <=0 || this._width <=0 || !this._height || !this._width) {
2368 throw new Error("Target dimension not set");
2369 }
2370
2371 this._plotDimensions.height = this._height;
2372 this._plotDimensions.width = this._width;
2373 this.grid._plotDimensions = this._plotDimensions;
2374 this.title._plotDimensions = this._plotDimensions;
2375 this.baseCanvas._plotDimensions = this._plotDimensions;
2376 this.eventCanvas._plotDimensions = this._plotDimensions;
2377 this.legend._plotDimensions = this._plotDimensions;
2378
2379 for (var n in this.axes) {
2380 this.axes[n]._plotWidth = this._width;
2381 this.axes[n]._plotHeight = this._height;
2382 }
2383
2384 this.title._plotWidth = this._width;
2385
2386 if (this.textColor) {
2387 this.target.css('color', this.textColor);
2388 }
2389 if (this.fontFamily) {
2390 this.target.css('font-family', this.fontFamily);
2391 }
2392 if (this.fontSize) {
2393 this.target.css('font-size', this.fontSize);
2394 }
2395
2396 this._sumy = 0;
2397 this._sumx = 0;
2398 this.computePlotData();
2399 for (var i=0; i<this.series.length; i++) {
2400 // this.populatePlotData(this.series[i], i);
2401 if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) {
2402 this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this);
2403 }
2404 this.series[i]._plotDimensions = this._plotDimensions;
2405 this.series[i].canvas._plotDimensions = this._plotDimensions;
2406 //this.series[i].init(i, this.grid.borderWidth);
2407 this._sumy += this.series[i]._sumy;
2408 this._sumx += this.series[i]._sumx;
2409 }
2410
2411 var name;
2412
2413 for (var j=0; j<12; j++) {
2414 name = _axisNames[j];
2415 // Memory Leaks patch : clear ticks elements
2416 var t = this.axes[name]._ticks;
2417 for (var i = 0; i < t.length; i++) {
2418 var el = t[i]._elem;
2419 if (el) {
2420 // if canvas renderer
2421 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
2422 window.G_vmlCanvasManager.uninitElement(el.get(0));
2423 }
2424 el.emptyForce();
2425 el = null;
2426 t._elem = null;
2427 }
2428 }
2429 t = null;
2430
2431 this.axes[name]._plotDimensions = this._plotDimensions;
2432 this.axes[name]._ticks = [];
2433 // this.axes[name].renderer.init.call(this.axes[name], {});
2434 }
2435
2436 if (this.sortData) {
2437 sortData(this.series);
2438 }
2439
2440 this.grid._axes = this.axes;
2441
2442 this.legend._series = this.series;
2443 };
2444
2445 // sort the series data in increasing order.
2446 function sortData(series) {
2447 var d, sd, pd, ppd, ret;
2448 for (var i=0; i<series.length; i++) {
2449 var check;
2450 var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData];
2451 for (var n=0; n<4; n++) {
2452 check = true;
2453 d = bat[n];
2454 if (series[i]._stackAxis == 'x') {
2455 for (var j = 0; j < d.length; j++) {
2456 if (typeof(d[j][1]) != "number") {
2457 check = false;
2458 break;
2459 }
2460 }
2461 if (check) {
2462 d.sort(function(a,b) { return a[1] - b[1]; });
2463 }
2464 }
2465 else {
2466 for (var j = 0; j < d.length; j++) {
2467 if (typeof(d[j][0]) != "number") {
2468 check = false;
2469 break;
2470 }
2471 }
2472 if (check) {
2473 d.sort(function(a,b) { return a[0] - b[0]; });
2474 }
2475 }
2476 }
2477
2478 }
2479 }
2480
2481 this.computePlotData = function() {
2482 this._plotData = [];
2483 this._stackData = [];
2484 var series,
2485 index,
2486 l;
2487
2488
2489 for (index=0, l=this.series.length; index<l; index++) {
2490 series = this.series[index];
2491 this._plotData.push([]);
2492 this._stackData.push([]);
2493 var cd = series.data;
2494 this._plotData[index] = $.extend(true, [], cd);
2495 this._stackData[index] = $.extend(true, [], cd);
2496 series._plotData = this._plotData[index];
2497 series._stackData = this._stackData[index];
2498 var plotValues = {x:[], y:[]};
2499
2500 if (this.stackSeries && !series.disableStack) {
2501 series._stack = true;
2502 ///////////////////////////
2503 // have to check for nulls
2504 ///////////////////////////
2505 var sidx = (series._stackAxis === 'x') ? 0 : 1;
2506
2507 for (var k=0, cdl=cd.length; k<cdl; k++) {
2508 var temp = cd[k][sidx];
2509 if (temp == null) {
2510 temp = 0;
2511 }
2512 this._plotData[index][k][sidx] = temp;
2513 this._stackData[index][k][sidx] = temp;
2514
2515 if (index > 0) {
2516 for (var j=index; j--;) {
2517 var prevval = this._plotData[j][k][sidx];
2518 // only need to sum up the stack axis column of data
2519 // and only sum if it is of same sign.
2520 // if previous series isn't same sign, keep looking
2521 // at earlier series untill we find one of same sign.
2522 if (temp * prevval >= 0) {
2523 this._plotData[index][k][sidx] += prevval;
2524 this._stackData[index][k][sidx] += prevval;
2525 break;
2526 }
2527 }
2528 }
2529 }
2530
2531 }
2532 else {
2533 for (var i=0; i<series.data.length; i++) {
2534 plotValues.x.push(series.data[i][0]);
2535 plotValues.y.push(series.data[i][1]);
2536 }
2537 this._stackData.push(series.data);
2538 this.series[index]._stackData = series.data;
2539 this._plotData.push(series.data);
2540 series._plotData = series.data;
2541 series._plotValues = plotValues;
2542 }
2543 if (index>0) {
2544 series._prevPlotData = this.series[index-1]._plotData;
2545 }
2546 series._sumy = 0;
2547 series._sumx = 0;
2548 for (i=series.data.length-1; i>-1; i--) {
2549 series._sumy += series.data[i][1];
2550 series._sumx += series.data[i][0];
2551 }
2552 }
2553
2554 };
2555
2556 // populate the _stackData and _plotData arrays for the plot and the series.
2557 this.populatePlotData = function(series, index) {
2558 // if a stacked chart, compute the stacked data
2559 this._plotData = [];
2560 this._stackData = [];
2561 series._stackData = [];
2562 series._plotData = [];
2563 var plotValues = {x:[], y:[]};
2564 if (this.stackSeries && !series.disableStack) {
2565 series._stack = true;
2566 var sidx = (series._stackAxis === 'x') ? 0 : 1;
2567 // var idx = sidx ? 0 : 1;
2568 // push the current data into stackData
2569 //this._stackData.push(this.series[i].data);
2570 var temp = $.extend(true, [], series.data);
2571 // create the data that will be plotted for this series
2572 var plotdata = $.extend(true, [], series.data);
2573 var tempx, tempy, dval, stackval, comparator;
2574 // for first series, nothing to add to stackData.
2575 for (var j=0; j<index; j++) {
2576 var cd = this.series[j].data;
2577 for (var k=0; k<cd.length; k++) {
2578 dval = cd[k];
2579 tempx = (dval[0] != null) ? dval[0] : 0;
2580 tempy = (dval[1] != null) ? dval[1] : 0;
2581 temp[k][0] += tempx;
2582 temp[k][1] += tempy;
2583 stackval = (sidx) ? tempy : tempx;
2584 // only need to sum up the stack axis column of data
2585 // and only sum if it is of same sign.
2586 if (series.data[k][sidx] * stackval >= 0) {
2587 plotdata[k][sidx] += stackval;
2588 }
2589 }
2590 }
2591 for (var i=0; i<plotdata.length; i++) {
2592 plotValues.x.push(plotdata[i][0]);
2593 plotValues.y.push(plotdata[i][1]);
2594 }
2595 this._plotData.push(plotdata);
2596 this._stackData.push(temp);
2597 series._stackData = temp;
2598 series._plotData = plotdata;
2599 series._plotValues = plotValues;
2600 }
2601 else {
2602 for (var i=0; i<series.data.length; i++) {
2603 plotValues.x.push(series.data[i][0]);
2604 plotValues.y.push(series.data[i][1]);
2605 }
2606 this._stackData.push(series.data);
2607 this.series[index]._stackData = series.data;
2608 this._plotData.push(series.data);
2609 series._plotData = series.data;
2610 series._plotValues = plotValues;
2611 }
2612 if (index>0) {
2613 series._prevPlotData = this.series[index-1]._plotData;
2614 }
2615 series._sumy = 0;
2616 series._sumx = 0;
2617 for (i=series.data.length-1; i>-1; i--) {
2618 series._sumy += series.data[i][1];
2619 series._sumx += series.data[i][0];
2620 }
2621 };
2622
2623 // function to safely return colors from the color array and wrap around at the end.
2624 this.getNextSeriesColor = (function(t) {
2625 var idx = 0;
2626 var sc = t.seriesColors;
2627
2628 return function () {
2629 if (idx < sc.length) {
2630 return sc[idx++];
2631 }
2632 else {
2633 idx = 0;
2634 return sc[idx++];
2635 }
2636 };
2637 })(this);
2638
2639 this.parseOptions = function(options){
2640 for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) {
2641 this.preParseOptionsHooks.hooks[i].call(this, options);
2642 }
2643 for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) {
2644 $.jqplot.preParseOptionsHooks[i].call(this, options);
2645 }
2646 this.options = $.extend(true, {}, this.defaults, options);
2647 var opts = this.options;
2648 this.animate = opts.animate;
2649 this.animateReplot = opts.animateReplot;
2650 this.stackSeries = opts.stackSeries;
2651 if ($.isPlainObject(opts.fillBetween)) {
2652
2653 var temp = ['series1', 'series2', 'color', 'baseSeries', 'fill'],
2654 tempi;
2655
2656 for (var i=0, l=temp.length; i<l; i++) {
2657 tempi = temp[i];
2658 if (opts.fillBetween[tempi] != null) {
2659 this.fillBetween[tempi] = opts.fillBetween[tempi];
2660 }
2661 }
2662 }
2663
2664 if (opts.seriesColors) {
2665 this.seriesColors = opts.seriesColors;
2666 }
2667 if (opts.negativeSeriesColors) {
2668 this.negativeSeriesColors = opts.negativeSeriesColors;
2669 }
2670 if (opts.captureRightClick) {
2671 this.captureRightClick = opts.captureRightClick;
2672 }
2673 this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart;
2674 this.colorGenerator.setColors(this.seriesColors);
2675 this.negativeColorGenerator.setColors(this.negativeSeriesColors);
2676 // var cg = new this.colorGenerator(this.seriesColors);
2677 // var ncg = new this.colorGenerator(this.negativeSeriesColors);
2678 // this._gridPadding = this.options.gridPadding;
2679 $.extend(true, this._gridPadding, opts.gridPadding);
2680 this.sortData = (opts.sortData != null) ? opts.sortData : this.sortData;
2681 for (var i=0; i<12; i++) {
2682 var n = _axisNames[i];
2683 var axis = this.axes[n];
2684 axis._options = $.extend(true, {}, opts.axesDefaults, opts.axes[n]);
2685 $.extend(true, axis, opts.axesDefaults, opts.axes[n]);
2686 axis._plotWidth = this._width;
2687 axis._plotHeight = this._height;
2688 }
2689 // if (this.data.length == 0) {
2690 // this.data = [];
2691 // for (var i=0; i<this.options.series.length; i++) {
2692 // this.data.push(this.options.series.data);
2693 // }
2694 // }
2695
2696 var normalizeData = function(data, dir, start) {
2697 // return data as an array of point arrays,
2698 // in form [[x1,y1...], [x2,y2...], ...]
2699 var temp = [];
2700 var i, l;
2701 dir = dir || 'vertical';
2702 if (!$.isArray(data[0])) {
2703 // we have a series of scalars. One line with just y values.
2704 // turn the scalar list of data into a data array of form:
2705 // [[1, data[0]], [2, data[1]], ...]
2706 for (i=0, l=data.length; i<l; i++) {
2707 if (dir == 'vertical') {
2708 temp.push([start + i, data[i]]);
2709 }
2710 else {
2711 temp.push([data[i], start+i]);
2712 }
2713 }
2714 }
2715 else {
2716 // we have a properly formatted data series, copy it.
2717 $.extend(true, temp, data);
2718 }
2719 return temp;
2720 };
2721
2722 var colorIndex = 0;
2723 this.series = [];
2724 for (var i=0; i<this.data.length; i++) {
2725 var sopts = $.extend(true, {index: i}, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i], {rendererOptions:{animation:{show: this.animate}}});
2726 // pass in options in case something needs set prior to initialization.
2727 var temp = new Series(sopts);
2728 for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) {
2729 $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
2730 }
2731 for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) {
2732 this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]);
2733 }
2734 // Now go back and apply the options to the series. Really should just do this during initializaiton, but don't want to
2735 // mess up preParseSeriesOptionsHooks at this point.
2736 $.extend(true, temp, sopts);
2737 var dir = 'vertical';
2738 if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal') {
2739 dir = 'horizontal';
2740 temp._stackAxis = 'x';
2741 temp._primaryAxis = '_yaxis';
2742 }
2743 temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart);
2744 switch (temp.xaxis) {
2745 case 'xaxis':
2746 temp._xaxis = this.axes.xaxis;
2747 break;
2748 case 'x2axis':
2749 temp._xaxis = this.axes.x2axis;
2750 break;
2751 default:
2752 break;
2753 }
2754 temp._yaxis = this.axes[temp.yaxis];
2755 temp._xaxis._series.push(temp);
2756 temp._yaxis._series.push(temp);
2757 if (temp.show) {
2758 temp._xaxis.show = true;
2759 temp._yaxis.show = true;
2760 }
2761 else {
2762 if (temp._xaxis.scaleToHiddenSeries) {
2763 temp._xaxis.show = true;
2764 }
2765 if (temp._yaxis.scaleToHiddenSeries) {
2766 temp._yaxis.show = true;
2767 }
2768 }
2769
2770 // // parse the renderer options and apply default colors if not provided
2771 // if (!temp.color && temp.show != false) {
2772 // temp.color = cg.next();
2773 // colorIndex = cg.getIndex() - 1;;
2774 // }
2775 // if (!temp.negativeColor && temp.show != false) {
2776 // temp.negativeColor = ncg.get(colorIndex);
2777 // ncg.setIndex(colorIndex);
2778 // }
2779 if (!temp.label) {
2780 temp.label = 'Series '+ (i+1).toString();
2781 }
2782 // temp.rendererOptions.show = temp.show;
2783 // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions);
2784 this.series.push(temp);
2785 for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) {
2786 $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
2787 }
2788 for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) {
2789 this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]);
2790 }
2791 }
2792
2793 // copy the grid and title options into this object.
2794 $.extend(true, this.grid, this.options.grid);
2795 // if axis border properties aren't set, set default.
2796 for (var i=0, l=_axisNames.length; i<l; i++) {
2797 var n = _axisNames[i];
2798 var axis = this.axes[n];
2799 if (axis.borderWidth == null) {
2800 axis.borderWidth =this.grid.borderWidth;
2801 }
2802 }
2803
2804 if (typeof this.options.title == 'string') {
2805 this.title.text = this.options.title;
2806 }
2807 else if (typeof this.options.title == 'object') {
2808 $.extend(true, this.title, this.options.title);
2809 }
2810 this.title._plotWidth = this._width;
2811 this.legend.setOptions(this.options.legend);
2812
2813 for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
2814 $.jqplot.postParseOptionsHooks[i].call(this, options);
2815 }
2816 for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) {
2817 this.postParseOptionsHooks.hooks[i].call(this, options);
2818 }
2819 };
2820
2821 // method: destroy
2822 // Releases all resources occupied by the plot
2823 this.destroy = function() {
2824 this.canvasManager.freeAllCanvases();
2825 if (this.eventCanvas && this.eventCanvas._elem) {
2826 this.eventCanvas._elem.unbind();
2827 }
2828 // Couple of posts on Stack Overflow indicate that empty() doesn't
2829 // always cear up the dom and release memory. Sometimes setting
2830 // innerHTML property to null is needed. Particularly on IE, may
2831 // have to directly set it to null, bypassing $.
2832 this.target.empty();
2833
2834 this.target[0].innerHTML = '';
2835 };
2836
2837 // method: replot
2838 // Does a reinitialization of the plot followed by
2839 // a redraw. Method could be used to interactively
2840 // change plot characteristics and then replot.
2841 //
2842 // Parameters:
2843 // options - Options used for replotting.
2844 //
2845 // Properties:
2846 // clear - false to not clear (empty) the plot container before replotting (default: true).
2847 // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves.
2848 // optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false).
2849 this.replot = function(options) {
2850 var opts = options || {};
2851 var data = opts.data || null;
2852 var clear = (opts.clear === false) ? false : true;
2853 var resetAxes = opts.resetAxes || false;
2854 delete opts.data;
2855 delete opts.clear;
2856 delete opts.resetAxes;
2857
2858 this.target.trigger('jqplotPreReplot');
2859
2860 if (clear) {
2861 this.destroy();
2862 }
2863 // if have data or other options, full reinit.
2864 // otherwise, quickinit.
2865 if (data || !$.isEmptyObject(opts)) {
2866 this.reInitialize(data, opts);
2867 }
2868 else {
2869 this.quickInit();
2870 }
2871
2872 if (resetAxes) {
2873 this.resetAxesScale(resetAxes, opts.axes);
2874 }
2875 this.draw();
2876 this.target.trigger('jqplotPostReplot');
2877 };
2878
2879 // method: redraw
2880 // Empties the plot target div and redraws the plot.
2881 // This enables plot data and properties to be changed
2882 // and then to comletely clear the plot and redraw.
2883 // redraw *will not* reinitialize any plot elements.
2884 // That is, axes will not be autoscaled and defaults
2885 // will not be reapplied to any plot elements. redraw
2886 // is used primarily with zooming.
2887 //
2888 // Parameters:
2889 // clear - false to not clear (empty) the plot container before redrawing (default: true).
2890 this.redraw = function(clear) {
2891 clear = (clear != null) ? clear : true;
2892 this.target.trigger('jqplotPreRedraw');
2893 if (clear) {
2894 this.canvasManager.freeAllCanvases();
2895 this.eventCanvas._elem.unbind();
2896 // Dont think I bind any events to the target, this shouldn't be necessary.
2897 // It will remove user's events.
2898 // this.target.unbind();
2899 this.target.empty();
2900 }
2901 for (var ax in this.axes) {
2902 this.axes[ax]._ticks = [];
2903 }
2904 this.computePlotData();
2905 // for (var i=0; i<this.series.length; i++) {
2906 // this.populatePlotData(this.series[i], i);
2907 // }
2908 this._sumy = 0;
2909 this._sumx = 0;
2910 for (var i=0, tsl = this.series.length; i<tsl; i++) {
2911 this._sumy += this.series[i]._sumy;
2912 this._sumx += this.series[i]._sumx;
2913 }
2914 this.draw();
2915 this.target.trigger('jqplotPostRedraw');
2916 };
2917
2918 // method: draw
2919 // Draws all elements of the plot into the container.
2920 // Does not clear the container before drawing.
2921 this.draw = function(){
2922 if (this.drawIfHidden || this.target.is(':visible')) {
2923 this.target.trigger('jqplotPreDraw');
2924 var i,
2925 j,
2926 l,
2927 tempseries;
2928 for (i=0, l=$.jqplot.preDrawHooks.length; i<l; i++) {
2929 $.jqplot.preDrawHooks[i].call(this);
2930 }
2931 for (i=0, l=this.preDrawHooks.hooks.length; i<l; i++) {
2932 this.preDrawHooks.hooks[i].apply(this, this.preDrawSeriesHooks.args[i]);
2933 }
2934 // create an underlying canvas to be used for special features.
2935 this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this));
2936 this.baseCanvas.setContext();
2937 this.target.append(this.title.draw());
2938 this.title.pack({top:0, left:0});
2939
2940 // make room for the legend between the grid and the edge.
2941 // pass a dummy offsets object and a reference to the plot.
2942 var legendElem = this.legend.draw({}, this);
2943
2944 var gridPadding = {top:0, left:0, bottom:0, right:0};
2945
2946 if (this.legend.placement == "outsideGrid") {
2947 // temporarily append the legend to get dimensions
2948 this.target.append(legendElem);
2949 switch (this.legend.location) {
2950 case 'n':
2951 gridPadding.top += this.legend.getHeight();
2952 break;
2953 case 's':
2954 gridPadding.bottom += this.legend.getHeight();
2955 break;
2956 case 'ne':
2957 case 'e':
2958 case 'se':
2959 gridPadding.right += this.legend.getWidth();
2960 break;
2961 case 'nw':
2962 case 'w':
2963 case 'sw':
2964 gridPadding.left += this.legend.getWidth();
2965 break;
2966 default: // same as 'ne'
2967 gridPadding.right += this.legend.getWidth();
2968 break;
2969 }
2970 legendElem = legendElem.detach();
2971 }
2972
2973 var ax = this.axes;
2974 var name;
2975 // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed.
2976 for (i=0; i<12; i++) {
2977 name = _axisNames[i];
2978 this.target.append(ax[name].draw(this.baseCanvas._ctx, this));
2979 ax[name].set();
2980 }
2981 if (ax.yaxis.show) {
2982 gridPadding.left += ax.yaxis.getWidth();
2983 }
2984 var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis'];
2985 var rapad = [0, 0, 0, 0, 0, 0, 0, 0];
2986 var gpr = 0;
2987 var n;
2988 for (n=0; n<8; n++) {
2989 if (ax[ra[n]].show) {
2990 gpr += ax[ra[n]].getWidth();
2991 rapad[n] = gpr;
2992 }
2993 }
2994 gridPadding.right += gpr;
2995 if (ax.x2axis.show) {
2996 gridPadding.top += ax.x2axis.getHeight();
2997 }
2998 if (this.title.show) {
2999 gridPadding.top += this.title.getHeight();
3000 }
3001 if (ax.xaxis.show) {
3002 gridPadding.bottom += ax.xaxis.getHeight();
3003 }
3004
3005 // end of gridPadding adjustments.
3006
3007 // if user passed in gridDimensions option, check against calculated gridPadding
3008 if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) {
3009 var gdw = parseInt(this.options.gridDimensions.width, 10) || 0;
3010 var gdh = parseInt(this.options.gridDimensions.height, 10) || 0;
3011 var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2;
3012 var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2;
3013
3014 if (heightAdj >= 0 && widthAdj >= 0) {
3015 gridPadding.top += heightAdj;
3016 gridPadding.bottom += heightAdj;
3017 gridPadding.left += widthAdj;
3018 gridPadding.right += widthAdj;
3019 }
3020 }
3021 var arr = ['top', 'bottom', 'left', 'right'];
3022 for (var n in arr) {
3023 if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) {
3024 this._gridPadding[arr[n]] = gridPadding[arr[n]];
3025 }
3026 else if (this._gridPadding[arr[n]] == null) {
3027 this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]];
3028 }
3029 }
3030
3031 var legendPadding = this._gridPadding;
3032
3033 if (this.legend.placement === 'outsideGrid') {
3034 legendPadding = {top:this.title.getHeight(), left: 0, right: 0, bottom: 0};
3035 if (this.legend.location === 's') {
3036 legendPadding.left = this._gridPadding.left;
3037 legendPadding.right = this._gridPadding.right;
3038 }
3039 }
3040
3041 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});
3042 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});
3043 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});
3044 for (i=8; i>0; i--) {
3045 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});
3046 }
3047 var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0;
3048 ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top});
3049
3050 this.target.append(this.grid.createElement(this._gridPadding, this));
3051 this.grid.draw();
3052
3053 var series = this.series;
3054 var seriesLength = series.length;
3055 // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars.
3056 for (i=0, l=seriesLength; i<l; i++) {
3057 // draw series in order of stacking. This affects only
3058 // order in which canvases are added to dom.
3059 j = this.seriesStack[i];
3060 this.target.append(series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this));
3061 series[j].shadowCanvas.setContext();
3062 series[j].shadowCanvas._elem.data('seriesIndex', j);
3063 }
3064
3065 for (i=0, l=seriesLength; i<l; i++) {
3066 // draw series in order of stacking. This affects only
3067 // order in which canvases are added to dom.
3068 j = this.seriesStack[i];
3069 this.target.append(series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this));
3070 series[j].canvas.setContext();
3071 series[j].canvas._elem.data('seriesIndex', j);
3072 }
3073 // Need to use filled canvas to capture events in IE.
3074 // Also, canvas seems to block selection of other elements in document on FF.
3075 this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this));
3076 this.eventCanvas.setContext();
3077 this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)';
3078 this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height);
3079
3080 // bind custom event handlers to regular events.
3081 this.bindCustomEvents();
3082
3083 // draw legend before series if the series needs to know the legend dimensions.
3084 if (this.legend.preDraw) {
3085 this.eventCanvas._elem.before(legendElem);
3086 this.legend.pack(legendPadding);
3087 if (this.legend._elem) {
3088 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}});
3089 }
3090 else {
3091 this.drawSeries();
3092 }
3093 }
3094 else { // draw series before legend
3095 this.drawSeries();
3096 if (seriesLength) {
3097 $(series[seriesLength-1].canvas._elem).after(legendElem);
3098 }
3099 this.legend.pack(legendPadding);
3100 }
3101
3102 // register event listeners on the overlay canvas
3103 for (var i=0, l=$.jqplot.eventListenerHooks.length; i<l; i++) {
3104 // in the handler, this will refer to the eventCanvas dom element.
3105 // make sure there are references back into plot objects.
3106 this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]);
3107 }
3108
3109 // register event listeners on the overlay canvas
3110 for (var i=0, l=this.eventListenerHooks.hooks.length; i<l; i++) {
3111 // in the handler, this will refer to the eventCanvas dom element.
3112 // make sure there are references back into plot objects.
3113 this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]);
3114 }
3115
3116 var fb = this.fillBetween;
3117 if (fb.fill && fb.series1 !== fb.series2 && fb.series1 < seriesLength && fb.series2 < seriesLength && series[fb.series1]._type === 'line' && series[fb.series2]._type === 'line') {
3118 this.doFillBetweenLines();
3119 }
3120
3121 for (var i=0, l=$.jqplot.postDrawHooks.length; i<l; i++) {
3122 $.jqplot.postDrawHooks[i].call(this);
3123 }
3124
3125 for (var i=0, l=this.postDrawHooks.hooks.length; i<l; i++) {
3126 this.postDrawHooks.hooks[i].apply(this, this.postDrawHooks.args[i]);
3127 }
3128
3129 if (this.target.is(':visible')) {
3130 this._drawCount += 1;
3131 }
3132
3133 var temps,
3134 tempr,
3135 sel,
3136 _els;
3137 // ughh. ideally would hide all series then show them.
3138 for (i=0, l=seriesLength; i<l; i++) {
3139 temps = series[i];
3140 tempr = temps.renderer;
3141 sel = '.jqplot-point-label.jqplot-series-'+i;
3142 if (tempr.animation && tempr.animation._supported && tempr.animation.show && (this._drawCount < 2 || this.animateReplot)) {
3143 _els = this.target.find(sel);
3144 _els.stop(true, true).hide();
3145 temps.canvas._elem.stop(true, true).hide();
3146 temps.shadowCanvas._elem.stop(true, true).hide();
3147 temps.canvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
3148 temps.shadowCanvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed);
3149 _els.fadeIn(tempr.animation.speed*0.8);
3150 }
3151 }
3152 _els = null;
3153
3154 this.target.trigger('jqplotPostDraw', [this]);
3155 }
3156 };
3157
3158 jqPlot.prototype.doFillBetweenLines = function () {
3159 var fb = this.fillBetween;
3160 var sid1 = fb.series1;
3161 var sid2 = fb.series2;
3162 // first series should always be lowest index
3163 var id1 = (sid1 < sid2) ? sid1 : sid2;
3164 var id2 = (sid2 > sid1) ? sid2 : sid1;
3165
3166 var series1 = this.series[id1];
3167 var series2 = this.series[id2];
3168
3169 if (series2.renderer.smooth) {
3170 var tempgd = series2.renderer._smoothedData.slice(0).reverse();
3171 }
3172 else {
3173 var tempgd = series2.gridData.slice(0).reverse();
3174 }
3175
3176 if (series1.renderer.smooth) {
3177 var gd = series1.renderer._smoothedData.concat(tempgd);
3178 }
3179 else {
3180 var gd = series1.gridData.concat(tempgd);
3181 }
3182
3183 var color = (fb.color !== null) ? fb.color : this.series[sid1].fillColor;
3184 var baseSeries = (fb.baseSeries !== null) ? fb.baseSeries : id1;
3185
3186 // now apply a fill to the shape on the lower series shadow canvas,
3187 // so it is behind both series.
3188 var sr = this.series[baseSeries].renderer.shapeRenderer;
3189 var opts = {fillStyle: color, fill: true, closePath: true};
3190 sr.draw(series1.shadowCanvas._ctx, gd, opts);
3191 };
3192
3193 this.bindCustomEvents = function() {
3194 this.eventCanvas._elem.bind('click', {plot:this}, this.onClick);
3195 this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick);
3196 this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown);
3197 this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove);
3198 this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter);
3199 this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave);
3200 if (this.captureRightClick) {
3201 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick);
3202 this.eventCanvas._elem.get(0).oncontextmenu = function() {
3203 return false;
3204 };
3205 }
3206 else {
3207 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp);
3208 }
3209 };
3210
3211 function getEventPosition(ev) {
3212 var plot = ev.data.plot;
3213 var go = plot.eventCanvas._elem.offset();
3214 var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
3215 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};
3216 var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
3217 var ax = plot.axes;
3218 var n, axis;
3219 for (n=11; n>0; n--) {
3220 axis = an[n-1];
3221 if (ax[axis].show) {
3222 dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
3223 }
3224 }
3225
3226 return {offsets:go, gridPos:gridPos, dataPos:dataPos};
3227 }
3228
3229
3230 // function to check if event location is over a area area
3231 function checkIntersection(gridpos, plot) {
3232 var series = plot.series;
3233 var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang;
3234 var d0, d, p, pp, points, bw, hp;
3235 var threshold, t;
3236 for (k=plot.seriesStack.length-1; k>=0; k--) {
3237 i = plot.seriesStack[k];
3238 s = series[i];
3239 hp = s._highlightThreshold;
3240 switch (s.renderer.constructor) {
3241 case $.jqplot.BarRenderer:
3242 x = gridpos.x;
3243 y = gridpos.y;
3244 for (j=0; j<s._barPoints.length; j++) {
3245 points = s._barPoints[j];
3246 p = s.gridData[j];
3247 if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) {
3248 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
3249 }
3250 }
3251 break;
3252 case $.jqplot.PyramidRenderer:
3253 x = gridpos.x;
3254 y = gridpos.y;
3255 for (j=0; j<s._barPoints.length; j++) {
3256 points = s._barPoints[j];
3257 p = s.gridData[j];
3258 if (x > points[0][0] + hp[0][0] && x < points[2][0] + hp[2][0] && y > points[2][1] && y < points[0][1]) {
3259 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]};
3260 }
3261 }
3262 break;
3263
3264 case $.jqplot.DonutRenderer:
3265 sa = s.startAngle/180*Math.PI;
3266 x = gridpos.x - s._center[0];
3267 y = gridpos.y - s._center[1];
3268 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
3269 if (x > 0 && -y >= 0) {
3270 theta = 2*Math.PI - Math.atan(-y/x);
3271 }
3272 else if (x > 0 && -y < 0) {
3273 theta = -Math.atan(-y/x);
3274 }
3275 else if (x < 0) {
3276 theta = Math.PI - Math.atan(-y/x);
3277 }
3278 else if (x == 0 && -y > 0) {
3279 theta = 3*Math.PI/2;
3280 }
3281 else if (x == 0 && -y < 0) {
3282 theta = Math.PI/2;
3283 }
3284 else if (x == 0 && y == 0) {
3285 theta = 0;
3286 }
3287 if (sa) {
3288 theta -= sa;
3289 if (theta < 0) {
3290 theta += 2*Math.PI;
3291 }
3292 else if (theta > 2*Math.PI) {
3293 theta -= 2*Math.PI;
3294 }
3295 }
3296
3297 sm = s.sliceMargin/180*Math.PI;
3298 if (r < s._radius && r > s._innerRadius) {
3299 for (j=0; j<s.gridData.length; j++) {
3300 minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
3301 maxang = s.gridData[j][1];
3302 if (theta > minang && theta < maxang) {
3303 return {seriesIndex:s.index, pointIndex:j, gridData:[gridpos.x,gridpos.y], data:s.data[j]};
3304 }
3305 }
3306 }
3307 break;
3308
3309 case $.jqplot.PieRenderer:
3310 sa = s.startAngle/180*Math.PI;
3311 x = gridpos.x - s._center[0];
3312 y = gridpos.y - s._center[1];
3313 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
3314 if (x > 0 && -y >= 0) {
3315 theta = 2*Math.PI - Math.atan(-y/x);
3316 }
3317 else if (x > 0 && -y < 0) {
3318 theta = -Math.atan(-y/x);
3319 }
3320 else if (x < 0) {
3321 theta = Math.PI - Math.atan(-y/x);
3322 }
3323 else if (x == 0 && -y > 0) {
3324 theta = 3*Math.PI/2;
3325 }
3326 else if (x == 0 && -y < 0) {
3327 theta = Math.PI/2;
3328 }
3329 else if (x == 0 && y == 0) {
3330 theta = 0;
3331 }
3332 if (sa) {
3333 theta -= sa;
3334 if (theta < 0) {
3335 theta += 2*Math.PI;
3336 }
3337 else if (theta > 2*Math.PI) {
3338 theta -= 2*Math.PI;
3339 }
3340 }
3341
3342 sm = s.sliceMargin/180*Math.PI;
3343 if (r < s._radius) {
3344 for (j=0; j<s.gridData.length; j++) {
3345 minang = (j>0) ? s.gridData[j-1][1]+sm : sm;
3346 maxang = s.gridData[j][1];
3347 if (theta > minang && theta < maxang) {
3348 return {seriesIndex:s.index, pointIndex:j, gridData:[gridpos.x,gridpos.y], data:s.data[j]};
3349 }
3350 }
3351 }
3352 break;
3353
3354 case $.jqplot.BubbleRenderer:
3355 x = gridpos.x;
3356 y = gridpos.y;
3357 var ret = null;
3358
3359 if (s.show) {
3360 for (var j=0; j<s.gridData.length; j++) {
3361 p = s.gridData[j];
3362 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3363 if (d <= p[2] && (d <= d0 || d0 == null)) {
3364 d0 = d;
3365 ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3366 }
3367 }
3368 if (ret != null) {
3369 return ret;
3370 }
3371 }
3372 break;
3373
3374 case $.jqplot.FunnelRenderer:
3375 x = gridpos.x;
3376 y = gridpos.y;
3377 var v = s._vertices,
3378 vfirst = v[0],
3379 vlast = v[v.length-1],
3380 lex,
3381 rex,
3382 cv;
3383
3384 // equations of right and left sides, returns x, y values given height of section (y value and 2 points)
3385
3386 function findedge (l, p1 , p2) {
3387 var m = (p1[1] - p2[1])/(p1[0] - p2[0]);
3388 var b = p1[1] - m*p1[0];
3389 var y = l + p1[1];
3390
3391 return [(y - b)/m, y];
3392 }
3393
3394 // check each section
3395 lex = findedge(y, vfirst[0], vlast[3]);
3396 rex = findedge(y, vfirst[1], vlast[2]);
3397 for (j=0; j<v.length; j++) {
3398 cv = v[j];
3399 if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) {
3400 return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]};
3401 }
3402 }
3403 break;
3404
3405 case $.jqplot.LineRenderer:
3406 x = gridpos.x;
3407 y = gridpos.y;
3408 r = s.renderer;
3409 if (s.show) {
3410 if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) {
3411 // first check if it is in bounding box
3412 var inside = false;
3413 if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) {
3414 // now check the crossing number
3415
3416 var numPoints = s._areaPoints.length;
3417 var ii;
3418 var j = numPoints-1;
3419
3420 for(var ii=0; ii < numPoints; ii++) {
3421 var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]];
3422 var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]];
3423
3424 if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y) {
3425 if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) {
3426 inside = !inside;
3427 }
3428 }
3429
3430 j = ii;
3431 }
3432 }
3433 if (inside) {
3434 return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints};
3435 }
3436 break;
3437
3438 }
3439
3440 else {
3441 t = s.markerRenderer.size/2+s.neighborThreshold;
3442 threshold = (t > 0) ? t : 0;
3443 for (var j=0; j<s.gridData.length; j++) {
3444 p = s.gridData[j];
3445 // neighbor looks different to OHLC chart.
3446 if (r.constructor == $.jqplot.OHLCRenderer) {
3447 if (r.candleStick) {
3448 var yp = s._yaxis.series_u2p;
3449 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])) {
3450 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3451 }
3452 }
3453 // if an open hi low close chart
3454 else if (!r.hlc){
3455 var yp = s._yaxis.series_u2p;
3456 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3457 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3458 }
3459 }
3460 // a hi low close chart
3461 else {
3462 var yp = s._yaxis.series_u2p;
3463 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
3464 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3465 }
3466 }
3467
3468 }
3469 else if (p[0] != null && p[1] != null){
3470 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3471 if (d <= threshold && (d <= d0 || d0 == null)) {
3472 d0 = d;
3473 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3474 }
3475 }
3476 }
3477 }
3478 }
3479 break;
3480
3481 default:
3482 x = gridpos.x;
3483 y = gridpos.y;
3484 r = s.renderer;
3485 if (s.show) {
3486 t = s.markerRenderer.size/2+s.neighborThreshold;
3487 threshold = (t > 0) ? t : 0;
3488 for (var j=0; j<s.gridData.length; j++) {
3489 p = s.gridData[j];
3490 // neighbor looks different to OHLC chart.
3491 if (r.constructor == $.jqplot.OHLCRenderer) {
3492 if (r.candleStick) {
3493 var yp = s._yaxis.series_u2p;
3494 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])) {
3495 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3496 }
3497 }
3498 // if an open hi low close chart
3499 else if (!r.hlc){
3500 var yp = s._yaxis.series_u2p;
3501 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) {
3502 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3503 }
3504 }
3505 // a hi low close chart
3506 else {
3507 var yp = s._yaxis.series_u2p;
3508 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) {
3509 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3510 }
3511 }
3512
3513 }
3514 else {
3515 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) );
3516 if (d <= threshold && (d <= d0 || d0 == null)) {
3517 d0 = d;
3518 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]};
3519 }
3520 }
3521 }
3522 }
3523 break;
3524 }
3525 }
3526
3527 return null;
3528 }
3529
3530
3531
3532 this.onClick = function(ev) {
3533 // Event passed in is normalized and will have data attribute.
3534 // Event passed out is unnormalized.
3535 var positions = getEventPosition(ev);
3536 var p = ev.data.plot;
3537 var neighbor = checkIntersection(positions.gridPos, p);
3538 var evt = $.Event('jqplotClick');
3539 evt.pageX = ev.pageX;
3540 evt.pageY = ev.pageY;
3541 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3542 };
3543
3544 this.onDblClick = function(ev) {
3545 // Event passed in is normalized and will have data attribute.
3546 // Event passed out is unnormalized.
3547 var positions = getEventPosition(ev);
3548 var p = ev.data.plot;
3549 var neighbor = checkIntersection(positions.gridPos, p);
3550 var evt = $.Event('jqplotDblClick');
3551 evt.pageX = ev.pageX;
3552 evt.pageY = ev.pageY;
3553 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3554 };
3555
3556 this.onMouseDown = function(ev) {
3557 var positions = getEventPosition(ev);
3558 var p = ev.data.plot;
3559 var neighbor = checkIntersection(positions.gridPos, p);
3560 var evt = $.Event('jqplotMouseDown');
3561 evt.pageX = ev.pageX;
3562 evt.pageY = ev.pageY;
3563 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3564 };
3565
3566 this.onMouseUp = function(ev) {
3567 var positions = getEventPosition(ev);
3568 var evt = $.Event('jqplotMouseUp');
3569 evt.pageX = ev.pageX;
3570 evt.pageY = ev.pageY;
3571 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]);
3572 };
3573
3574 this.onRightClick = function(ev) {
3575 var positions = getEventPosition(ev);
3576 var p = ev.data.plot;
3577 var neighbor = checkIntersection(positions.gridPos, p);
3578 if (p.captureRightClick) {
3579 if (ev.which == 3) {
3580 var evt = $.Event('jqplotRightClick');
3581 evt.pageX = ev.pageX;
3582 evt.pageY = ev.pageY;
3583 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3584 }
3585 else {
3586 var evt = $.Event('jqplotMouseUp');
3587 evt.pageX = ev.pageX;
3588 evt.pageY = ev.pageY;
3589 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3590 }
3591 }
3592 };
3593
3594 this.onMouseMove = function(ev) {
3595 var positions = getEventPosition(ev);
3596 var p = ev.data.plot;
3597 var neighbor = checkIntersection(positions.gridPos, p);
3598 var evt = $.Event('jqplotMouseMove');
3599 evt.pageX = ev.pageX;
3600 evt.pageY = ev.pageY;
3601 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]);
3602 };
3603
3604 this.onMouseEnter = function(ev) {
3605 var positions = getEventPosition(ev);
3606 var p = ev.data.plot;
3607 var evt = $.Event('jqplotMouseEnter');
3608 evt.pageX = ev.pageX;
3609 evt.pageY = ev.pageY;
3610 evt.relatedTarget = ev.relatedTarget;
3611 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
3612 };
3613
3614 this.onMouseLeave = function(ev) {
3615 var positions = getEventPosition(ev);
3616 var p = ev.data.plot;
3617 var evt = $.Event('jqplotMouseLeave');
3618 evt.pageX = ev.pageX;
3619 evt.pageY = ev.pageY;
3620 evt.relatedTarget = ev.relatedTarget;
3621 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]);
3622 };
3623
3624 // method: drawSeries
3625 // Redraws all or just one series on the plot. No axis scaling
3626 // is performed and no other elements on the plot are redrawn.
3627 // options is an options object to pass on to the series renderers.
3628 // It can be an empty object {}. idx is the series index
3629 // to redraw if only one series is to be redrawn.
3630 this.drawSeries = function(options, idx){
3631 var i, series, ctx;
3632 // if only one argument passed in and it is a number, use it ad idx.
3633 idx = (typeof(options) === "number" && idx == null) ? options : idx;
3634 options = (typeof(options) === "object") ? options : {};
3635 // draw specified series
3636 if (idx != undefined) {
3637 series = this.series[idx];
3638 ctx = series.shadowCanvas._ctx;
3639 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3640 series.drawShadow(ctx, options, this);
3641 ctx = series.canvas._ctx;
3642 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3643 series.draw(ctx, options, this);
3644 if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) {
3645 if (idx < this.series.length - 1) {
3646 this.drawSeries(idx+1);
3647 }
3648 }
3649 }
3650
3651 else {
3652 // if call series drawShadow method first, in case all series shadows
3653 // should be drawn before any series. This will ensure, like for
3654 // stacked bar plots, that shadows don't overlap series.
3655 for (i=0; i<this.series.length; i++) {
3656 // first clear the canvas
3657 series = this.series[i];
3658 ctx = series.shadowCanvas._ctx;
3659 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3660 series.drawShadow(ctx, options, this);
3661 ctx = series.canvas._ctx;
3662 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
3663 series.draw(ctx, options, this);
3664 }
3665 }
3666 options = idx = i = series = ctx = null;
3667 };
3668
3669 // method: moveSeriesToFront
3670 // This method requires jQuery 1.4+
3671 // Moves the specified series canvas in front of all other series canvases.
3672 // This effectively "draws" the specified series on top of all other series,
3673 // although it is performed through DOM manipulation, no redrawing is performed.
3674 //
3675 // Parameters:
3676 // idx - 0 based index of the series to move. This will be the index of the series
3677 // as it was first passed into the jqplot function.
3678 this.moveSeriesToFront = function (idx) {
3679 idx = parseInt(idx, 10);
3680 var stackIndex = $.inArray(idx, this.seriesStack);
3681 // if already in front, return
3682 if (stackIndex == -1) {
3683 return;
3684 }
3685 if (stackIndex == this.seriesStack.length -1) {
3686 this.previousSeriesStack = this.seriesStack.slice(0);
3687 return;
3688 }
3689 var opidx = this.seriesStack[this.seriesStack.length -1];
3690 var serelem = this.series[idx].canvas._elem.detach();
3691 var shadelem = this.series[idx].shadowCanvas._elem.detach();
3692 this.series[opidx].shadowCanvas._elem.after(shadelem);
3693 this.series[opidx].canvas._elem.after(serelem);
3694 this.previousSeriesStack = this.seriesStack.slice(0);
3695 this.seriesStack.splice(stackIndex, 1);
3696 this.seriesStack.push(idx);
3697 };
3698
3699 // method: moveSeriesToBack
3700 // This method requires jQuery 1.4+
3701 // Moves the specified series canvas behind all other series canvases.
3702 //
3703 // Parameters:
3704 // idx - 0 based index of the series to move. This will be the index of the series
3705 // as it was first passed into the jqplot function.
3706 this.moveSeriesToBack = function (idx) {
3707 idx = parseInt(idx, 10);
3708 var stackIndex = $.inArray(idx, this.seriesStack);
3709 // if already in back, return
3710 if (stackIndex == 0 || stackIndex == -1) {
3711 return;
3712 }
3713 var opidx = this.seriesStack[0];
3714 var serelem = this.series[idx].canvas._elem.detach();
3715 var shadelem = this.series[idx].shadowCanvas._elem.detach();
3716 this.series[opidx].shadowCanvas._elem.before(shadelem);
3717 this.series[opidx].canvas._elem.before(serelem);
3718 this.previousSeriesStack = this.seriesStack.slice(0);
3719 this.seriesStack.splice(stackIndex, 1);
3720 this.seriesStack.unshift(idx);
3721 };
3722
3723 // method: restorePreviousSeriesOrder
3724 // This method requires jQuery 1.4+
3725 // Restore the series canvas order to its previous state.
3726 // Useful to put a series back where it belongs after moving
3727 // it to the front.
3728 this.restorePreviousSeriesOrder = function () {
3729 var i, j, serelem, shadelem, temp, move, keep;
3730 // if no change, return.
3731 if (this.seriesStack == this.previousSeriesStack) {
3732 return;
3733 }
3734 for (i=1; i<this.previousSeriesStack.length; i++) {
3735 move = this.previousSeriesStack[i];
3736 keep = this.previousSeriesStack[i-1];
3737 serelem = this.series[move].canvas._elem.detach();
3738 shadelem = this.series[move].shadowCanvas._elem.detach();
3739 this.series[keep].shadowCanvas._elem.after(shadelem);
3740 this.series[keep].canvas._elem.after(serelem);
3741 }
3742 temp = this.seriesStack.slice(0);
3743 this.seriesStack = this.previousSeriesStack.slice(0);
3744 this.previousSeriesStack = temp;
3745 };
3746
3747 // method: restoreOriginalSeriesOrder
3748 // This method requires jQuery 1.4+
3749 // Restore the series canvas order to its original order
3750 // when the plot was created.
3751 this.restoreOriginalSeriesOrder = function () {
3752 var i, j, arr=[], serelem, shadelem;
3753 for (i=0; i<this.series.length; i++) {
3754 arr.push(i);
3755 }
3756 if (this.seriesStack == arr) {
3757 return;
3758 }
3759 this.previousSeriesStack = this.seriesStack.slice(0);
3760 this.seriesStack = arr;
3761 for (i=1; i<this.seriesStack.length; i++) {
3762 serelem = this.series[i].canvas._elem.detach();
3763 shadelem = this.series[i].shadowCanvas._elem.detach();
3764 this.series[i-1].shadowCanvas._elem.after(shadelem);
3765 this.series[i-1].canvas._elem.after(serelem);
3766 }
3767 };
3768
3769 this.activateTheme = function (name) {
3770 this.themeEngine.activate(this, name);
3771 };
3772 }
3773
3774
3775 // conpute a highlight color or array of highlight colors from given colors.
3776 $.jqplot.computeHighlightColors = function(colors) {
3777 var ret;
3778 if ($.isArray(colors)) {
3779 ret = [];
3780 for (var i=0; i<colors.length; i++){
3781 var rgba = $.jqplot.getColorComponents(colors[i]);
3782 var newrgb = [rgba[0], rgba[1], rgba[2]];
3783 var sum = newrgb[0] + newrgb[1] + newrgb[2];
3784 for (var j=0; j<3; j++) {
3785 // when darkening, lowest color component can be is 60.
3786 newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
3787 newrgb[j] = parseInt(newrgb[j], 10);
3788 (newrgb[j] > 255) ? 255 : newrgb[j];
3789 }
3790 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
3791 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
3792 newrgb[3] = 0.3 + 0.35 * rgba[3];
3793 ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')');
3794 }
3795 }
3796 else {
3797 var rgba = $.jqplot.getColorComponents(colors);
3798 var newrgb = [rgba[0], rgba[1], rgba[2]];
3799 var sum = newrgb[0] + newrgb[1] + newrgb[2];
3800 for (var j=0; j<3; j++) {
3801 // when darkening, lowest color component can be is 60.
3802 // newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]);
3803 // newrgb[j] = parseInt(newrgb[j], 10);
3804 newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90;
3805 newrgb[j] = parseInt(newrgb[j], 10);
3806 (newrgb[j] > 255) ? 255 : newrgb[j];
3807 }
3808 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5;
3809 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2;
3810 newrgb[3] = 0.3 + 0.35 * rgba[3];
3811 ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')';
3812 }
3813 return ret;
3814 };
3815
3816 $.jqplot.ColorGenerator = function(colors) {
3817 colors = colors || $.jqplot.config.defaultColors;
3818 var idx = 0;
3819
3820 this.next = function () {
3821 if (idx < colors.length) {
3822 return colors[idx++];
3823 }
3824 else {
3825 idx = 0;
3826 return colors[idx++];
3827 }
3828 };
3829
3830 this.previous = function () {
3831 if (idx > 0) {
3832 return colors[idx--];
3833 }
3834 else {
3835 idx = colors.length-1;
3836 return colors[idx];
3837 }
3838 };
3839
3840 // get a color by index without advancing pointer.
3841 this.get = function(i) {
3842 var idx = i - colors.length * Math.floor(i/colors.length);
3843 return colors[idx];
3844 };
3845
3846 this.setColors = function(c) {
3847 colors = c;
3848 };
3849
3850 this.reset = function() {
3851 idx = 0;
3852 };
3853
3854 this.getIndex = function() {
3855 return idx;
3856 };
3857
3858 this.setIndex = function(index) {
3859 idx = index;
3860 };
3861 };
3862
3863 // convert a hex color string to rgb string.
3864 // h - 3 or 6 character hex string, with or without leading #
3865 // a - optional alpha
3866 $.jqplot.hex2rgb = function(h, a) {
3867 h = h.replace('#', '');
3868 if (h.length == 3) {
3869 h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2);
3870 }
3871 var rgb;
3872 rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16);
3873 if (a) {
3874 rgb += ', '+a;
3875 }
3876 rgb += ')';
3877 return rgb;
3878 };
3879
3880 // convert an rgb color spec to a hex spec. ignore any alpha specification.
3881 $.jqplot.rgb2hex = function(s) {
3882 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/;
3883 var m = s.match(pat);
3884 var h = '#';
3885 for (var i=1; i<4; i++) {
3886 var temp;
3887 if (m[i].search(/%/) != -1) {
3888 temp = parseInt(255*m[i]/100, 10).toString(16);
3889 if (temp.length == 1) {
3890 temp = '0'+temp;
3891 }
3892 }
3893 else {
3894 temp = parseInt(m[i], 10).toString(16);
3895 if (temp.length == 1) {
3896 temp = '0'+temp;
3897 }
3898 }
3899 h += temp;
3900 }
3901 return h;
3902 };
3903
3904 // given a css color spec, return an rgb css color spec
3905 $.jqplot.normalize2rgb = function(s, a) {
3906 if (s.search(/^ *rgba?\(/) != -1) {
3907 return s;
3908 }
3909 else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) {
3910 return $.jqplot.hex2rgb(s, a);
3911 }
3912 else {
3913 throw new Error('Invalid color spec');
3914 }
3915 };
3916
3917 // extract the r, g, b, a color components out of a css color spec.
3918 $.jqplot.getColorComponents = function(s) {
3919 // check to see if a color keyword.
3920 s = $.jqplot.colorKeywordMap[s] || s;
3921 var rgb = $.jqplot.normalize2rgb(s);
3922 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/;
3923 var m = rgb.match(pat);
3924 var ret = [];
3925 for (var i=1; i<4; i++) {
3926 if (m[i].search(/%/) != -1) {
3927 ret[i-1] = parseInt(255*m[i]/100, 10);
3928 }
3929 else {
3930 ret[i-1] = parseInt(m[i], 10);
3931 }
3932 }
3933 ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0;
3934 return ret;
3935 };
3936
3937 $.jqplot.colorKeywordMap = {
3938 aliceblue: 'rgb(240, 248, 255)',
3939 antiquewhite: 'rgb(250, 235, 215)',
3940 aqua: 'rgb( 0, 255, 255)',
3941 aquamarine: 'rgb(127, 255, 212)',
3942 azure: 'rgb(240, 255, 255)',
3943 beige: 'rgb(245, 245, 220)',
3944 bisque: 'rgb(255, 228, 196)',
3945 black: 'rgb( 0, 0, 0)',
3946 blanchedalmond: 'rgb(255, 235, 205)',
3947 blue: 'rgb( 0, 0, 255)',
3948 blueviolet: 'rgb(138, 43, 226)',
3949 brown: 'rgb(165, 42, 42)',
3950 burlywood: 'rgb(222, 184, 135)',
3951 cadetblue: 'rgb( 95, 158, 160)',
3952 chartreuse: 'rgb(127, 255, 0)',
3953 chocolate: 'rgb(210, 105, 30)',
3954 coral: 'rgb(255, 127, 80)',
3955 cornflowerblue: 'rgb(100, 149, 237)',
3956 cornsilk: 'rgb(255, 248, 220)',
3957 crimson: 'rgb(220, 20, 60)',
3958 cyan: 'rgb( 0, 255, 255)',
3959 darkblue: 'rgb( 0, 0, 139)',
3960 darkcyan: 'rgb( 0, 139, 139)',
3961 darkgoldenrod: 'rgb(184, 134, 11)',
3962 darkgray: 'rgb(169, 169, 169)',
3963 darkgreen: 'rgb( 0, 100, 0)',
3964 darkgrey: 'rgb(169, 169, 169)',
3965 darkkhaki: 'rgb(189, 183, 107)',
3966 darkmagenta: 'rgb(139, 0, 139)',
3967 darkolivegreen: 'rgb( 85, 107, 47)',
3968 darkorange: 'rgb(255, 140, 0)',
3969 darkorchid: 'rgb(153, 50, 204)',
3970 darkred: 'rgb(139, 0, 0)',
3971 darksalmon: 'rgb(233, 150, 122)',
3972 darkseagreen: 'rgb(143, 188, 143)',
3973 darkslateblue: 'rgb( 72, 61, 139)',
3974 darkslategray: 'rgb( 47, 79, 79)',
3975 darkslategrey: 'rgb( 47, 79, 79)',
3976 darkturquoise: 'rgb( 0, 206, 209)',
3977 darkviolet: 'rgb(148, 0, 211)',
3978 deeppink: 'rgb(255, 20, 147)',
3979 deepskyblue: 'rgb( 0, 191, 255)',
3980 dimgray: 'rgb(105, 105, 105)',
3981 dimgrey: 'rgb(105, 105, 105)',
3982 dodgerblue: 'rgb( 30, 144, 255)',
3983 firebrick: 'rgb(178, 34, 34)',
3984 floralwhite: 'rgb(255, 250, 240)',
3985 forestgreen: 'rgb( 34, 139, 34)',
3986 fuchsia: 'rgb(255, 0, 255)',
3987 gainsboro: 'rgb(220, 220, 220)',
3988 ghostwhite: 'rgb(248, 248, 255)',
3989 gold: 'rgb(255, 215, 0)',
3990 goldenrod: 'rgb(218, 165, 32)',
3991 gray: 'rgb(128, 128, 128)',
3992 grey: 'rgb(128, 128, 128)',
3993 green: 'rgb( 0, 128, 0)',
3994 greenyellow: 'rgb(173, 255, 47)',
3995 honeydew: 'rgb(240, 255, 240)',
3996 hotpink: 'rgb(255, 105, 180)',
3997 indianred: 'rgb(205, 92, 92)',
3998 indigo: 'rgb( 75, 0, 130)',
3999 ivory: 'rgb(255, 255, 240)',
4000 khaki: 'rgb(240, 230, 140)',
4001 lavender: 'rgb(230, 230, 250)',
4002 lavenderblush: 'rgb(255, 240, 245)',
4003 lawngreen: 'rgb(124, 252, 0)',
4004 lemonchiffon: 'rgb(255, 250, 205)',
4005 lightblue: 'rgb(173, 216, 230)',
4006 lightcoral: 'rgb(240, 128, 128)',
4007 lightcyan: 'rgb(224, 255, 255)',
4008 lightgoldenrodyellow: 'rgb(250, 250, 210)',
4009 lightgray: 'rgb(211, 211, 211)',
4010 lightgreen: 'rgb(144, 238, 144)',
4011 lightgrey: 'rgb(211, 211, 211)',
4012 lightpink: 'rgb(255, 182, 193)',
4013 lightsalmon: 'rgb(255, 160, 122)',
4014 lightseagreen: 'rgb( 32, 178, 170)',
4015 lightskyblue: 'rgb(135, 206, 250)',
4016 lightslategray: 'rgb(119, 136, 153)',
4017 lightslategrey: 'rgb(119, 136, 153)',
4018 lightsteelblue: 'rgb(176, 196, 222)',
4019 lightyellow: 'rgb(255, 255, 224)',
4020 lime: 'rgb( 0, 255, 0)',
4021 limegreen: 'rgb( 50, 205, 50)',
4022 linen: 'rgb(250, 240, 230)',
4023 magenta: 'rgb(255, 0, 255)',
4024 maroon: 'rgb(128, 0, 0)',
4025 mediumaquamarine: 'rgb(102, 205, 170)',
4026 mediumblue: 'rgb( 0, 0, 205)',
4027 mediumorchid: 'rgb(186, 85, 211)',
4028 mediumpurple: 'rgb(147, 112, 219)',
4029 mediumseagreen: 'rgb( 60, 179, 113)',
4030 mediumslateblue: 'rgb(123, 104, 238)',
4031 mediumspringgreen: 'rgb( 0, 250, 154)',
4032 mediumturquoise: 'rgb( 72, 209, 204)',
4033 mediumvioletred: 'rgb(199, 21, 133)',
4034 midnightblue: 'rgb( 25, 25, 112)',
4035 mintcream: 'rgb(245, 255, 250)',
4036 mistyrose: 'rgb(255, 228, 225)',
4037 moccasin: 'rgb(255, 228, 181)',
4038 navajowhite: 'rgb(255, 222, 173)',
4039 navy: 'rgb( 0, 0, 128)',
4040 oldlace: 'rgb(253, 245, 230)',
4041 olive: 'rgb(128, 128, 0)',
4042 olivedrab: 'rgb(107, 142, 35)',
4043 orange: 'rgb(255, 165, 0)',
4044 orangered: 'rgb(255, 69, 0)',
4045 orchid: 'rgb(218, 112, 214)',
4046 palegoldenrod: 'rgb(238, 232, 170)',
4047 palegreen: 'rgb(152, 251, 152)',
4048 paleturquoise: 'rgb(175, 238, 238)',
4049 palevioletred: 'rgb(219, 112, 147)',
4050 papayawhip: 'rgb(255, 239, 213)',
4051 peachpuff: 'rgb(255, 218, 185)',
4052 peru: 'rgb(205, 133, 63)',
4053 pink: 'rgb(255, 192, 203)',
4054 plum: 'rgb(221, 160, 221)',
4055 powderblue: 'rgb(176, 224, 230)',
4056 purple: 'rgb(128, 0, 128)',
4057 red: 'rgb(255, 0, 0)',
4058 rosybrown: 'rgb(188, 143, 143)',
4059 royalblue: 'rgb( 65, 105, 225)',
4060 saddlebrown: 'rgb(139, 69, 19)',
4061 salmon: 'rgb(250, 128, 114)',
4062 sandybrown: 'rgb(244, 164, 96)',
4063 seagreen: 'rgb( 46, 139, 87)',
4064 seashell: 'rgb(255, 245, 238)',
4065 sienna: 'rgb(160, 82, 45)',
4066 silver: 'rgb(192, 192, 192)',
4067 skyblue: 'rgb(135, 206, 235)',
4068 slateblue: 'rgb(106, 90, 205)',
4069 slategray: 'rgb(112, 128, 144)',
4070 slategrey: 'rgb(112, 128, 144)',
4071 snow: 'rgb(255, 250, 250)',
4072 springgreen: 'rgb( 0, 255, 127)',
4073 steelblue: 'rgb( 70, 130, 180)',
4074 tan: 'rgb(210, 180, 140)',
4075 teal: 'rgb( 0, 128, 128)',
4076 thistle: 'rgb(216, 191, 216)',
4077 tomato: 'rgb(255, 99, 71)',
4078 turquoise: 'rgb( 64, 224, 208)',
4079 violet: 'rgb(238, 130, 238)',
4080 wheat: 'rgb(245, 222, 179)',
4081 white: 'rgb(255, 255, 255)',
4082 whitesmoke: 'rgb(245, 245, 245)',
4083 yellow: 'rgb(255, 255, 0)',
4084 yellowgreen: 'rgb(154, 205, 50)'
4085 };
4086
4087
4088
4089
4090 // class: $.jqplot.AxisLabelRenderer
4091 // Renderer to place labels on the axes.
4092 $.jqplot.AxisLabelRenderer = function(options) {
4093 // Group: Properties
4094 $.jqplot.ElemContainer.call(this);
4095 // name of the axis associated with this tick
4096 this.axis;
4097 // prop: show
4098 // whether or not to show the tick (mark and label).
4099 this.show = true;
4100 // prop: label
4101 // The text or html for the label.
4102 this.label = '';
4103 this.fontFamily = null;
4104 this.fontSize = null;
4105 this.textColor = null;
4106 this._elem;
4107 // prop: escapeHTML
4108 // true to escape HTML entities in the label.
4109 this.escapeHTML = false;
4110
4111 $.extend(true, this, options);
4112 };
4113
4114 $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer();
4115 $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer;
4116
4117 $.jqplot.AxisLabelRenderer.prototype.init = function(options) {
4118 $.extend(true, this, options);
4119 };
4120
4121 $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) {
4122 // Memory Leaks patch
4123 if (this._elem) {
4124 this._elem.emptyForce();
4125 this._elem = null;
4126 }
4127
4128 this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>');
4129
4130 if (Number(this.label)) {
4131 this._elem.css('white-space', 'nowrap');
4132 }
4133
4134 if (!this.escapeHTML) {
4135 this._elem.html(this.label);
4136 }
4137 else {
4138 this._elem.text(this.label);
4139 }
4140 if (this.fontFamily) {
4141 this._elem.css('font-family', this.fontFamily);
4142 }
4143 if (this.fontSize) {
4144 this._elem.css('font-size', this.fontSize);
4145 }
4146 if (this.textColor) {
4147 this._elem.css('color', this.textColor);
4148 }
4149
4150 return this._elem;
4151 };
4152
4153 $.jqplot.AxisLabelRenderer.prototype.pack = function() {
4154 };
4155
4156 // class: $.jqplot.AxisTickRenderer
4157 // A "tick" object showing the value of a tick/gridline on the plot.
4158 $.jqplot.AxisTickRenderer = function(options) {
4159 // Group: Properties
4160 $.jqplot.ElemContainer.call(this);
4161 // prop: mark
4162 // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null.
4163 this.mark = 'outside';
4164 // name of the axis associated with this tick
4165 this.axis;
4166 // prop: showMark
4167 // whether or not to show the mark on the axis.
4168 this.showMark = true;
4169 // prop: showGridline
4170 // whether or not to draw the gridline on the grid at this tick.
4171 this.showGridline = true;
4172 // prop: isMinorTick
4173 // if this is a minor tick.
4174 this.isMinorTick = false;
4175 // prop: size
4176 // Length of the tick beyond the grid in pixels.
4177 // DEPRECATED: This has been superceeded by markSize
4178 this.size = 4;
4179 // prop: markSize
4180 // Length of the tick marks in pixels. For 'cross' style, length
4181 // will be stoked above and below axis, so total length will be twice this.
4182 this.markSize = 6;
4183 // prop: show
4184 // whether or not to show the tick (mark and label).
4185 // Setting this to false requires more testing. It is recommended
4186 // to set showLabel and showMark to false instead.
4187 this.show = true;
4188 // prop: showLabel
4189 // whether or not to show the label.
4190 this.showLabel = true;
4191 this.label = null;
4192 this.value = null;
4193 this._styles = {};
4194 // prop: formatter
4195 // A class of a formatter for the tick text. sprintf by default.
4196 this.formatter = $.jqplot.DefaultTickFormatter;
4197 // prop: prefix
4198 // String to prepend to the tick label.
4199 // Prefix is prepended to the formatted tick label.
4200 this.prefix = '';
4201 // prop: suffix
4202 // String to append to the tick label.
4203 // Suffix is appended to the formatted tick label.
4204 this.suffix = '';
4205 // prop: formatString
4206 // string passed to the formatter.
4207 this.formatString = '';
4208 // prop: fontFamily
4209 // css spec for the font-family css attribute.
4210 this.fontFamily;
4211 // prop: fontSize
4212 // css spec for the font-size css attribute.
4213 this.fontSize;
4214 // prop: textColor
4215 // css spec for the color attribute.
4216 this.textColor;
4217 // prop: escapeHTML
4218 // true to escape HTML entities in the label.
4219 this.escapeHTML = false;
4220 this._elem;
4221 this._breakTick = false;
4222
4223 $.extend(true, this, options);
4224 };
4225
4226 $.jqplot.AxisTickRenderer.prototype.init = function(options) {
4227 $.extend(true, this, options);
4228 };
4229
4230 $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer();
4231 $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer;
4232
4233 $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) {
4234 this.value = value;
4235 this.axis = axisName;
4236 if (isMinor) {
4237 this.isMinorTick = true;
4238 }
4239 return this;
4240 };
4241
4242 $.jqplot.AxisTickRenderer.prototype.draw = function() {
4243 if (this.label === null) {
4244 this.label = this.prefix + this.formatter(this.formatString, this.value) + this.suffix;
4245 }
4246 var style = {position: 'absolute'};
4247 if (Number(this.label)) {
4248 style['whitSpace'] = 'nowrap';
4249 }
4250
4251 // Memory Leaks patch
4252 if (this._elem) {
4253 this._elem.emptyForce();
4254 this._elem = null;
4255 }
4256
4257 this._elem = $(document.createElement('div'));
4258 this._elem.addClass("jqplot-"+this.axis+"-tick");
4259
4260 if (!this.escapeHTML) {
4261 this._elem.html(this.label);
4262 }
4263 else {
4264 this._elem.text(this.label);
4265 }
4266
4267 this._elem.css(style);
4268
4269 for (var s in this._styles) {
4270 this._elem.css(s, this._styles[s]);
4271 }
4272 if (this.fontFamily) {
4273 this._elem.css('font-family', this.fontFamily);
4274 }
4275 if (this.fontSize) {
4276 this._elem.css('font-size', this.fontSize);
4277 }
4278 if (this.textColor) {
4279 this._elem.css('color', this.textColor);
4280 }
4281 if (this._breakTick) {
4282 this._elem.addClass('jqplot-breakTick');
4283 }
4284
4285 return this._elem;
4286 };
4287
4288 $.jqplot.DefaultTickFormatter = function (format, val) {
4289 if (typeof val == 'number') {
4290 if (!format) {
4291 format = $.jqplot.config.defaultTickFormatString;
4292 }
4293 return $.jqplot.sprintf(format, val);
4294 }
4295 else {
4296 return String(val);
4297 }
4298 };
4299
4300 $.jqplot.PercentTickFormatter = function (format, val) {
4301 if (typeof val == 'number') {
4302 val = 100 * val;
4303 if (!format) {
4304 format = $.jqplot.config.defaultTickFormatString;
4305 }
4306 return $.jqplot.sprintf(format, val);
4307 }
4308 else {
4309 return String(val);
4310 }
4311 };
4312
4313 $.jqplot.AxisTickRenderer.prototype.pack = function() {
4314 };
4315
4316 // Class: $.jqplot.CanvasGridRenderer
4317 // The default jqPlot grid renderer, creating a grid on a canvas element.
4318 // The renderer has no additional options beyond the <Grid> class.
4319 $.jqplot.CanvasGridRenderer = function(){
4320 this.shadowRenderer = new $.jqplot.ShadowRenderer();
4321 };
4322
4323 // called with context of Grid object
4324 $.jqplot.CanvasGridRenderer.prototype.init = function(options) {
4325 this._ctx;
4326 $.extend(true, this, options);
4327 // set the shadow renderer options
4328 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};
4329 this.renderer.shadowRenderer.init(sopts);
4330 };
4331
4332 // called with context of Grid.
4333 $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) {
4334 var elem;
4335 // Memory Leaks patch
4336 if (this._elem) {
4337 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) {
4338 elem = this._elem.get(0);
4339 window.G_vmlCanvasManager.uninitElement(elem);
4340 elem = null;
4341 }
4342
4343 this._elem.emptyForce();
4344 this._elem = null;
4345 }
4346
4347 elem = plot.canvasManager.getCanvas();
4348
4349 var w = this._plotDimensions.width;
4350 var h = this._plotDimensions.height;
4351 elem.width = w;
4352 elem.height = h;
4353 this._elem = $(elem);
4354 this._elem.addClass('jqplot-grid-canvas');
4355 this._elem.css({ position: 'absolute', left: 0, top: 0 });
4356
4357 elem = plot.canvasManager.initCanvas(elem);
4358
4359 this._top = this._offsets.top;
4360 this._bottom = h - this._offsets.bottom;
4361 this._left = this._offsets.left;
4362 this._right = w - this._offsets.right;
4363 this._width = this._right - this._left;
4364 this._height = this._bottom - this._top;
4365 // avoid memory leak
4366 elem = null;
4367 return this._elem;
4368 };
4369
4370 $.jqplot.CanvasGridRenderer.prototype.draw = function() {
4371 this._ctx = this._elem.get(0).getContext("2d");
4372 var ctx = this._ctx;
4373 var axes = this._axes;
4374 // Add the grid onto the grid canvas. This is the bottom most layer.
4375 ctx.save();
4376 ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height);
4377 ctx.fillStyle = this.backgroundColor || this.background;
4378 ctx.fillRect(this._left, this._top, this._width, this._height);
4379
4380 ctx.save();
4381 ctx.lineJoin = 'miter';
4382 ctx.lineCap = 'butt';
4383 ctx.lineWidth = this.gridLineWidth;
4384 ctx.strokeStyle = this.gridLineColor;
4385 var b, e, s, m;
4386 var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis'];
4387 for (var i=4; i>0; i--) {
4388 var name = ax[i-1];
4389 var axis = axes[name];
4390 var ticks = axis._ticks;
4391 var numticks = ticks.length;
4392 if (axis.show) {
4393 if (axis.drawBaseline) {
4394 var bopts = {};
4395 if (axis.baselineWidth !== null) {
4396 bopts.lineWidth = axis.baselineWidth;
4397 }
4398 if (axis.baselineColor !== null) {
4399 bopts.strokeStyle = axis.baselineColor;
4400 }
4401 switch (name) {
4402 case 'xaxis':
4403 drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
4404 break;
4405 case 'yaxis':
4406 drawLine (this._left, this._bottom, this._left, this._top, bopts);
4407 break;
4408 case 'x2axis':
4409 drawLine (this._left, this._bottom, this._right, this._bottom, bopts);
4410 break;
4411 case 'y2axis':
4412 drawLine (this._right, this._bottom, this._right, this._top, bopts);
4413 break;
4414 }
4415 }
4416 for (var j=numticks; j>0; j--) {
4417 var t = ticks[j-1];
4418 if (t.show) {
4419 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4420 switch (name) {
4421 case 'xaxis':
4422 // draw the grid line if we should
4423 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4424 drawLine(pos, this._top, pos, this._bottom);
4425 }
4426 // draw the mark
4427 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4428 s = t.markSize;
4429 m = t.mark;
4430 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4431 switch (m) {
4432 case 'outside':
4433 b = this._bottom;
4434 e = this._bottom+s;
4435 break;
4436 case 'inside':
4437 b = this._bottom-s;
4438 e = this._bottom;
4439 break;
4440 case 'cross':
4441 b = this._bottom-s;
4442 e = this._bottom+s;
4443 break;
4444 default:
4445 b = this._bottom;
4446 e = this._bottom+s;
4447 break;
4448 }
4449 // draw the shadow
4450 if (this.shadow) {
4451 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});
4452 }
4453 // draw the line
4454 drawLine(pos, b, pos, e);
4455 }
4456 break;
4457 case 'yaxis':
4458 // draw the grid line
4459 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4460 drawLine(this._right, pos, this._left, pos);
4461 }
4462 // draw the mark
4463 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4464 s = t.markSize;
4465 m = t.mark;
4466 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4467 switch (m) {
4468 case 'outside':
4469 b = this._left-s;
4470 e = this._left;
4471 break;
4472 case 'inside':
4473 b = this._left;
4474 e = this._left+s;
4475 break;
4476 case 'cross':
4477 b = this._left-s;
4478 e = this._left+s;
4479 break;
4480 default:
4481 b = this._left-s;
4482 e = this._left;
4483 break;
4484 }
4485 // draw the shadow
4486 if (this.shadow) {
4487 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});
4488 }
4489 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4490 }
4491 break;
4492 case 'x2axis':
4493 // draw the grid line
4494 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4495 drawLine(pos, this._bottom, pos, this._top);
4496 }
4497 // draw the mark
4498 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4499 s = t.markSize;
4500 m = t.mark;
4501 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4502 switch (m) {
4503 case 'outside':
4504 b = this._top-s;
4505 e = this._top;
4506 break;
4507 case 'inside':
4508 b = this._top;
4509 e = this._top+s;
4510 break;
4511 case 'cross':
4512 b = this._top-s;
4513 e = this._top+s;
4514 break;
4515 default:
4516 b = this._top-s;
4517 e = this._top;
4518 break;
4519 }
4520 // draw the shadow
4521 if (this.shadow) {
4522 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});
4523 }
4524 drawLine(pos, b, pos, e);
4525 }
4526 break;
4527 case 'y2axis':
4528 // draw the grid line
4529 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) {
4530 drawLine(this._left, pos, this._right, pos);
4531 }
4532 // draw the mark
4533 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) {
4534 s = t.markSize;
4535 m = t.mark;
4536 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4537 switch (m) {
4538 case 'outside':
4539 b = this._right;
4540 e = this._right+s;
4541 break;
4542 case 'inside':
4543 b = this._right-s;
4544 e = this._right;
4545 break;
4546 case 'cross':
4547 b = this._right-s;
4548 e = this._right+s;
4549 break;
4550 default:
4551 b = this._right;
4552 e = this._right+s;
4553 break;
4554 }
4555 // draw the shadow
4556 if (this.shadow) {
4557 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});
4558 }
4559 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4560 }
4561 break;
4562 default:
4563 break;
4564 }
4565 }
4566 }
4567 t = null;
4568 }
4569 axis = null;
4570 ticks = null;
4571 }
4572 // Now draw grid lines for additional y axes
4573 //////
4574 // TO DO: handle yMidAxis
4575 //////
4576 ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
4577 for (var i=7; i>0; i--) {
4578 var axis = axes[ax[i-1]];
4579 var ticks = axis._ticks;
4580 if (axis.show) {
4581 var tn = ticks[axis.numberTicks-1];
4582 var t0 = ticks[0];
4583 var left = axis.getLeft();
4584 var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]];
4585 // draw the shadow
4586 if (this.shadow) {
4587 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false});
4588 }
4589 // draw the line
4590 drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth});
4591 // draw the tick marks
4592 for (var j=ticks.length; j>0; j--) {
4593 var t = ticks[j-1];
4594 s = t.markSize;
4595 m = t.mark;
4596 var pos = Math.round(axis.u2p(t.value)) + 0.5;
4597 if (t.showMark && t.mark) {
4598 switch (m) {
4599 case 'outside':
4600 b = left;
4601 e = left+s;
4602 break;
4603 case 'inside':
4604 b = left-s;
4605 e = left;
4606 break;
4607 case 'cross':
4608 b = left-s;
4609 e = left+s;
4610 break;
4611 default:
4612 b = left;
4613 e = left+s;
4614 break;
4615 }
4616 points = [[b,pos], [e,pos]];
4617 // draw the shadow
4618 if (this.shadow) {
4619 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false});
4620 }
4621 // draw the line
4622 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor});
4623 }
4624 t = null;
4625 }
4626 t0 = null;
4627 }
4628 axis = null;
4629 ticks = null;
4630 }
4631
4632 ctx.restore();
4633
4634 function drawLine(bx, by, ex, ey, opts) {
4635 ctx.save();
4636 opts = opts || {};
4637 if (opts.lineWidth == null || opts.lineWidth != 0){
4638 $.extend(true, ctx, opts);
4639 ctx.beginPath();
4640 ctx.moveTo(bx, by);
4641 ctx.lineTo(ex, ey);
4642 ctx.stroke();
4643 ctx.restore();
4644 }
4645 }
4646
4647 if (this.shadow) {
4648 var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]];
4649 this.renderer.shadowRenderer.draw(ctx, points);
4650 }
4651 // Now draw border around grid. Use axis border definitions. start at
4652 // upper left and go clockwise.
4653 if (this.borderWidth != 0 && this.drawBorder) {
4654 drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth});
4655 drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth});
4656 drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth});
4657 drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth});
4658 }
4659 // ctx.lineWidth = this.borderWidth;
4660 // ctx.strokeStyle = this.borderColor;
4661 // ctx.strokeRect(this._left, this._top, this._width, this._height);
4662
4663 ctx.restore();
4664 ctx = null;
4665 axes = null;
4666 };
4667
4668 // Class: $.jqplot.DivTitleRenderer
4669 // The default title renderer for jqPlot. This class has no options beyond the <Title> class.
4670 $.jqplot.DivTitleRenderer = function() {
4671 };
4672
4673 $.jqplot.DivTitleRenderer.prototype.init = function(options) {
4674 $.extend(true, this, options);
4675 };
4676
4677 $.jqplot.DivTitleRenderer.prototype.draw = function() {
4678 // Memory Leaks patch
4679 if (this._elem) {
4680 this._elem.emptyForce();
4681 this._elem = null;
4682 }
4683
4684 var r = this.renderer;
4685 var elem = document.createElement('div');
4686 this._elem = $(elem);
4687 this._elem.addClass('jqplot-title');
4688
4689 if (!this.text) {
4690 this.show = false;
4691 this._elem.height(0);
4692 this._elem.width(0);
4693 }
4694 else if (this.text) {
4695 var color;
4696 if (this.color) {
4697 color = this.color;
4698 }
4699 else if (this.textColor) {
4700 color = this.textColor;
4701 }
4702
4703 // don't trust that a stylesheet is present, set the position.
4704 var styles = {position:'absolute', top:'0px', left:'0px'};
4705
4706 if (this._plotWidth) {
4707 styles['width'] = this._plotWidth+'px';
4708 }
4709 if (this.fontSize) {
4710 styles['fontSize'] = this.fontSize;
4711 }
4712 if (typeof this.textAlign === 'string') {
4713 styles['textAlign'] = this.textAlign;
4714 }
4715 else {
4716 styles['textAlign'] = 'center';
4717 }
4718 if (color) {
4719 styles['color'] = color;
4720 }
4721 if (this.paddingBottom) {
4722 styles['paddingBottom'] = this.paddingBottom;
4723 }
4724 if (this.fontFamily) {
4725 styles['fontFamily'] = this.fontFamily;
4726 }
4727
4728 this._elem.css(styles);
4729 if (this.escapeHtml) {
4730 this._elem.text(this.text);
4731 }
4732 else {
4733 this._elem.html(this.text);
4734 }
4735
4736
4737 // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : '';
4738 // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : '';
4739 // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;';
4740 // styletext += (color) ? 'color:'+color+';' : '';
4741 // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : '';
4742 // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>');
4743 // if (this.fontFamily) {
4744 // this._elem.css('font-family', this.fontFamily);
4745 // }
4746 }
4747
4748 elem = null;
4749
4750 return this._elem;
4751 };
4752
4753 $.jqplot.DivTitleRenderer.prototype.pack = function() {
4754 // nothing to do here
4755 };
4756
4757
4758 var dotlen = 0.1;
4759
4760 $.jqplot.LinePattern = function (ctx, pattern) {
4761
4762 var defaultLinePatterns = {
4763 dotted: [ dotlen, $.jqplot.config.dotGapLength ],
4764 dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ],
4765 solid: null
4766 };
4767
4768 if (typeof pattern === 'string') {
4769 if (pattern[0] === '.' || pattern[0] === '-') {
4770 var s = pattern;
4771 pattern = [];
4772 for (var i=0, imax=s.length; i<imax; i++) {
4773 if (s[i] === '.') {
4774 pattern.push( dotlen );
4775 }
4776 else if (s[i] === '-') {
4777 pattern.push( $.jqplot.config.dashLength );
4778 }
4779 else {
4780 continue;
4781 }
4782 pattern.push( $.jqplot.config.gapLength );
4783 }
4784 }
4785 else {
4786 pattern = defaultLinePatterns[pattern];
4787 }
4788 }
4789
4790 if (!(pattern && pattern.length)) {
4791 return ctx;
4792 }
4793
4794 var patternIndex = 0;
4795 var patternDistance = pattern[0];
4796 var px = 0;
4797 var py = 0;
4798 var pathx0 = 0;
4799 var pathy0 = 0;
4800
4801 var moveTo = function (x, y) {
4802 ctx.moveTo( x, y );
4803 px = x;
4804 py = y;
4805 pathx0 = x;
4806 pathy0 = y;
4807 };
4808
4809 var lineTo = function (x, y) {
4810 var scale = ctx.lineWidth;
4811 var dx = x - px;
4812 var dy = y - py;
4813 var dist = Math.sqrt(dx*dx+dy*dy);
4814 if ((dist > 0) && (scale > 0)) {
4815 dx /= dist;
4816 dy /= dist;
4817 while (true) {
4818 var dp = scale * patternDistance;
4819 if (dp < dist) {
4820 px += dp * dx;
4821 py += dp * dy;
4822 if ((patternIndex & 1) == 0) {
4823 ctx.lineTo( px, py );
4824 }
4825 else {
4826 ctx.moveTo( px, py );
4827 }
4828 dist -= dp;
4829 patternIndex++;
4830 if (patternIndex >= pattern.length) {
4831 patternIndex = 0;
4832 }
4833 patternDistance = pattern[patternIndex];
4834 }
4835 else {
4836 px = x;
4837 py = y;
4838 if ((patternIndex & 1) == 0) {
4839 ctx.lineTo( px, py );
4840 }
4841 else {
4842 ctx.moveTo( px, py );
4843 }
4844 patternDistance -= dist / scale;
4845 break;
4846 }
4847 }
4848 }
4849 };
4850
4851 var beginPath = function () {
4852 ctx.beginPath();
4853 };
4854
4855 var closePath = function () {
4856 lineTo( pathx0, pathy0 );
4857 };
4858
4859 return {
4860 moveTo: moveTo,
4861 lineTo: lineTo,
4862 beginPath: beginPath,
4863 closePath: closePath
4864 };
4865 };
4866
4867 // Class: $.jqplot.LineRenderer
4868 // The default line renderer for jqPlot, this class has no options beyond the <Series> class.
4869 // Draws series as a line.
4870 $.jqplot.LineRenderer = function(){
4871 this.shapeRenderer = new $.jqplot.ShapeRenderer();
4872 this.shadowRenderer = new $.jqplot.ShadowRenderer();
4873 };
4874
4875 // called with scope of series.
4876 $.jqplot.LineRenderer.prototype.init = function(options, plot) {
4877 // Group: Properties
4878 //
4879 options = options || {};
4880 this._type='line';
4881 this.renderer.animation = {
4882 show: false,
4883 direction: 'left',
4884 speed: 2500,
4885 _supported: true
4886 };
4887 // prop: smooth
4888 // True to draw a smoothed (interpolated) line through the data points
4889 // with automatically computed number of smoothing points.
4890 // Set to an integer number > 2 to specify number of smoothing points
4891 // to use between each data point.
4892 this.renderer.smooth = false; // true or a number > 2 for smoothing.
4893 this.renderer.tension = null; // null to auto compute or a number typically > 6. Fewer points requires higher tension.
4894 // prop: constrainSmoothing
4895 // True to use a more accurate smoothing algorithm that will
4896 // not overshoot any data points. False to allow overshoot but
4897 // produce a smoother looking line.
4898 this.renderer.constrainSmoothing = true;
4899 // this is smoothed data in grid coordinates, like gridData
4900 this.renderer._smoothedData = [];
4901 // this is smoothed data in plot units (plot coordinates), like plotData.
4902 this.renderer._smoothedPlotData = [];
4903 this.renderer._hiBandGridData = [];
4904 this.renderer._lowBandGridData = [];
4905 this.renderer._hiBandSmoothedData = [];
4906 this.renderer._lowBandSmoothedData = [];
4907
4908 // prop: bandData
4909 // Data used to draw error bands or confidence intervals above/below a line.
4910 //
4911 // bandData can be input in 3 forms. jqPlot will figure out which is the
4912 // low band line and which is the high band line for all forms:
4913 //
4914 // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where
4915 // [yl1, yl2, ...] are y values of the lower line and
4916 // [yu1, yu2, ...] are y values of the upper line.
4917 // In this case there must be the same number of y data points as data points
4918 // in the series and the bands will inherit the x values of the series.
4919 //
4920 // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]]
4921 // where [xl1, yl1] are x,y data points for the lower line and
4922 // [xh1, yh1] are x,y data points for the high line.
4923 // x values do not have to correspond to the x values of the series and can
4924 // be of any arbitrary length.
4925 //
4926 // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where
4927 // there must be 3 or more arrays and there must be the same number of arrays
4928 // as there are data points in the series. In this case,
4929 // [yl1, yu1] specifies the lower and upper y values for the 1st
4930 // data point and so on. The bands will inherit the x
4931 // values from the series.
4932 this.renderer.bandData = [];
4933
4934 // Group: bands
4935 // Banding around line, e.g error bands or confidence intervals.
4936 this.renderer.bands = {
4937 // prop: show
4938 // true to show the bands. If bandData or interval is
4939 // supplied, show will be set to true by default.
4940 show: false,
4941 hiData: [],
4942 lowData: [],
4943 // prop: color
4944 // color of lines at top and bottom of bands [default: series color].
4945 color: this.color,
4946 // prop: showLines
4947 // True to show lines at top and bottom of bands [default: false].
4948 showLines: false,
4949 // prop: fill
4950 // True to fill area between bands [default: true].
4951 fill: true,
4952 // prop: fillColor
4953 // css color spec for filled area. [default: series color].
4954 fillColor: null,
4955 _min: null,
4956 _max: null,
4957 // prop: interval
4958 // User specified interval above and below line for bands [default: '3%''].
4959 // Can be a value like 3 or a string like '3%'
4960 // or an upper/lower array like [1, -2] or ['2%', '-1.5%']
4961 interval: '3%'
4962 };
4963
4964
4965 var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor};
4966
4967 delete (options.highlightMouseOver);
4968 delete (options.highlightMouseDown);
4969 delete (options.highlightColor);
4970
4971 $.extend(true, this.renderer, options);
4972
4973 this.renderer.options = options;
4974
4975 // if we are given some band data, and bands aren't explicity set to false in options, turn them on.
4976 if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) {
4977 this.renderer.bands.show = true;
4978 }
4979
4980 // if we are given an interval, and bands aren't explicity set to false in options, turn them on.
4981 else if (options.bands && options.bands.show == null && options.bands.interval != null) {
4982 this.renderer.bands.show = true;
4983 }
4984
4985 // if plot is filled, turn off bands.
4986 if (this.fill) {
4987 this.renderer.bands.show = false;
4988 }
4989
4990 if (this.renderer.bands.show) {
4991 this.renderer.initBands.call(this, this.renderer.options, plot);
4992 }
4993
4994
4995 // smoothing is not compatible with stacked lines, disable
4996 if (this._stack) {
4997 this.renderer.smooth = false;
4998 }
4999
5000 // set the shape renderer options
5001 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};
5002 this.renderer.shapeRenderer.init(opts);
5003
5004 var shadow_offset = options.shadowOffset;
5005 // set the shadow renderer options
5006 if (shadow_offset == null) {
5007 // scale the shadowOffset to the width of the line.
5008 if (this.lineWidth > 2.5) {
5009 shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
5010 // var shadow_offset = this.shadowOffset;
5011 }
5012 // for skinny lines, don't make such a big shadow.
5013 else {
5014 shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
5015 }
5016 }
5017
5018 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};
5019 this.renderer.shadowRenderer.init(sopts);
5020 this._areaPoints = [];
5021 this._boundingBox = [[],[]];
5022
5023 if (!this.isTrendline && this.fill || this.renderer.bands.show) {
5024 // Group: Properties
5025 //
5026 // prop: highlightMouseOver
5027 // True to highlight area on a filled plot when moused over.
5028 // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
5029 this.highlightMouseOver = true;
5030 // prop: highlightMouseDown
5031 // True to highlight when a mouse button is pressed over an area on a filled plot.
5032 // This will be disabled if highlightMouseOver is true.
5033 this.highlightMouseDown = false;
5034 // prop: highlightColor
5035 // color to use when highlighting an area on a filled plot.
5036 this.highlightColor = null;
5037 // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
5038 if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) {
5039 lopts.highlightMouseOver = false;
5040 }
5041
5042 $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor});
5043
5044 if (!this.highlightColor) {
5045 var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor;
5046 this.highlightColor = $.jqplot.computeHighlightColors(fc);
5047 }
5048 // turn off (disable) the highlighter plugin
5049 if (this.highlighter) {
5050 this.highlighter.show = false;
5051 }
5052 }
5053
5054 if (!this.isTrendline && plot) {
5055 plot.plugins.lineRenderer = {};
5056 plot.postInitHooks.addOnce(postInit);
5057 plot.postDrawHooks.addOnce(postPlotDraw);
5058 plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
5059 plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
5060 plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
5061 plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
5062 plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
5063 }
5064
5065 };
5066
5067 $.jqplot.LineRenderer.prototype.initBands = function(options, plot) {
5068 // use bandData if no data specified in bands option
5069 //var bd = this.renderer.bandData;
5070 var bd = options.bandData || [];
5071 var bands = this.renderer.bands;
5072 bands.hiData = [];
5073 bands.lowData = [];
5074 var data = this.data;
5075 bands._max = null;
5076 bands._min = null;
5077 // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values.
5078 if (bd.length == 2) {
5079 // Do we have an array of x,y values?
5080 // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]]
5081 if ($.isArray(bd[0][0])) {
5082 // since an arbitrary array of points, spin through all of them to determine max and min lines.
5083
5084 var p;
5085 var bdminidx = 0, bdmaxidx = 0;
5086 for (var i = 0, l = bd[0].length; i<l; i++) {
5087 p = bd[0][i];
5088 if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
5089 bands._max = p[1];
5090 }
5091 if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
5092 bands._min = p[1];
5093 }
5094 }
5095 for (var i = 0, l = bd[1].length; i<l; i++) {
5096 p = bd[1][i];
5097 if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
5098 bands._max = p[1];
5099 bdmaxidx = 1;
5100 }
5101 if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
5102 bands._min = p[1];
5103 bdminidx = 1;
5104 }
5105 }
5106
5107 if (bdmaxidx === bdminidx) {
5108 bands.show = false;
5109 }
5110
5111 bands.hiData = bd[bdmaxidx];
5112 bands.lowData = bd[bdminidx];
5113 }
5114 // else data is arrays of y values
5115 // like [[1,4,3], [3,6,5]]
5116 // must have same number of band data points as points in series
5117 else if (bd[0].length === data.length && bd[1].length === data.length) {
5118 var hi = (bd[0][0] > bd[1][0]) ? 0 : 1;
5119 var low = (hi) ? 0 : 1;
5120 for (var i=0, l=data.length; i < l; i++) {
5121 bands.hiData.push([data[i][0], bd[hi][i]]);
5122 bands.lowData.push([data[i][0], bd[low][i]]);
5123 }
5124 }
5125
5126 // we don't have proper data array, don't show bands.
5127 else {
5128 bands.show = false;
5129 }
5130 }
5131
5132 // if more than 2 arrays, have arrays of [ylow, yhi] values.
5133 // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]]
5134 // this is assumed to be of the latter form.
5135 else if (bd.length > 2 && !$.isArray(bd[0][0])) {
5136 var hi = (bd[0][0] > bd[0][1]) ? 0 : 1;
5137 var low = (hi) ? 0 : 1;
5138 for (var i=0, l=bd.length; i<l; i++) {
5139 bands.hiData.push([data[i][0], bd[i][hi]]);
5140 bands.lowData.push([data[i][0], bd[i][low]]);
5141 }
5142 }
5143
5144 // don't have proper data, auto calculate
5145 else {
5146 var intrv = bands.interval;
5147 var a = null;
5148 var b = null;
5149 var afunc = null;
5150 var bfunc = null;
5151
5152 if ($.isArray(intrv)) {
5153 a = intrv[0];
5154 b = intrv[1];
5155 }
5156 else {
5157 a = intrv;
5158 }
5159
5160 if (isNaN(a)) {
5161 // we have a string
5162 if (a.charAt(a.length - 1) === '%') {
5163 afunc = 'multiply';
5164 a = parseFloat(a)/100 + 1;
5165 }
5166 }
5167
5168 else {
5169 a = parseFloat(a);
5170 afunc = 'add';
5171 }
5172
5173 if (b !== null && isNaN(b)) {
5174 // we have a string
5175 if (b.charAt(b.length - 1) === '%') {
5176 bfunc = 'multiply';
5177 b = parseFloat(b)/100 + 1;
5178 }
5179 }
5180
5181 else if (b !== null) {
5182 b = parseFloat(b);
5183 bfunc = 'add';
5184 }
5185
5186 if (a !== null) {
5187 if (b === null) {
5188 b = -a;
5189 bfunc = afunc;
5190 if (bfunc === 'multiply') {
5191 b += 2;
5192 }
5193 }
5194
5195 // make sure a always applies to hi band.
5196 if (a < b) {
5197 var temp = a;
5198 a = b;
5199 b = temp;
5200 temp = afunc;
5201 afunc = bfunc;
5202 bfunc = temp;
5203 }
5204
5205 for (var i=0, l = data.length; i < l; i++) {
5206 switch (afunc) {
5207 case 'add':
5208 bands.hiData.push([data[i][0], data[i][1] + a]);
5209 break;
5210 case 'multiply':
5211 bands.hiData.push([data[i][0], data[i][1] * a]);
5212 break;
5213 }
5214 switch (bfunc) {
5215 case 'add':
5216 bands.lowData.push([data[i][0], data[i][1] + b]);
5217 break;
5218 case 'multiply':
5219 bands.lowData.push([data[i][0], data[i][1] * b]);
5220 break;
5221 }
5222 }
5223 }
5224
5225 else {
5226 bands.show = false;
5227 }
5228 }
5229
5230 var hd = bands.hiData;
5231 var ld = bands.lowData;
5232 for (var i = 0, l = hd.length; i<l; i++) {
5233 if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) {
5234 bands._max = hd[i][1];
5235 }
5236 }
5237 for (var i = 0, l = ld.length; i<l; i++) {
5238 if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) {
5239 bands._min = ld[i][1];
5240 }
5241 }
5242
5243 // one last check for proper data
5244 // these don't apply any more since allowing arbitrary x,y values
5245 // if (bands.hiData.length != bands.lowData.length) {
5246 // bands.show = false;
5247 // }
5248
5249 // if (bands.hiData.length != this.data.length) {
5250 // bands.show = false;
5251 // }
5252
5253 if (bands.fillColor === null) {
5254 var c = $.jqplot.getColorComponents(bands.color);
5255 // now adjust alpha to differentiate fill
5256 c[3] = c[3] * 0.5;
5257 bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
5258 }
5259 };
5260
5261 function getSteps (d, f) {
5262 return (3.4182054+f) * Math.pow(d, -0.3534992);
5263 }
5264
5265 function computeSteps (d1, d2) {
5266 var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2));
5267 return 5.7648 * Math.log(s) + 7.4456;
5268 }
5269
5270 function tanh (x) {
5271 var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1);
5272 return a;
5273 }
5274
5275 //////////
5276 // computeConstrainedSmoothedData
5277 // An implementation of the constrained cubic spline interpolation
5278 // method as presented in:
5279 //
5280 // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications
5281 // http://www.korf.co.uk/spline.pdf
5282 //
5283 // The implementation below borrows heavily from the sample Visual Basic
5284 // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls
5285 //
5286 /////////
5287
5288 // called with scope of series
5289 function computeConstrainedSmoothedData (gd) {
5290 var smooth = this.renderer.smooth;
5291 var dim = this.canvas.getWidth();
5292 var xp = this._xaxis.series_p2u;
5293 var yp = this._yaxis.series_p2u;
5294 var steps =null;
5295 var _steps = null;
5296 var dist = gd.length/dim;
5297 var _smoothedData = [];
5298 var _smoothedPlotData = [];
5299
5300 if (!isNaN(parseFloat(smooth))) {
5301 steps = parseFloat(smooth);
5302 }
5303 else {
5304 steps = getSteps(dist, 0.5);
5305 }
5306
5307 var yy = [];
5308 var xx = [];
5309
5310 for (var i=0, l = gd.length; i<l; i++) {
5311 yy.push(gd[i][1]);
5312 xx.push(gd[i][0]);
5313 }
5314
5315 function dxx(x1, x0) {
5316 if (x1 - x0 == 0) {
5317 return Math.pow(10,10);
5318 }
5319 else {
5320 return x1 - x0;
5321 }
5322 }
5323
5324 var A, B, C, D;
5325 // loop through each line segment. Have # points - 1 line segments. Nmber segments starting at 1.
5326 var nmax = gd.length - 1;
5327 for (var num = 1, gdl = gd.length; num<gdl; num++) {
5328 var gxx = [];
5329 var ggxx = [];
5330 // point at each end of segment.
5331 for (var j = 0; j < 2; j++) {
5332 var i = num - 1 + j; // point number, 0 to # points.
5333
5334 if (i == 0 || i == nmax) {
5335 gxx[j] = Math.pow(10, 10);
5336 }
5337 else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) {
5338 gxx[j] = 0;
5339 }
5340 else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) {
5341 gxx[j] = 0;
5342 }
5343 else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) {
5344 gxx[j] = 0;
5345 }
5346
5347 else {
5348 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]));
5349 }
5350 }
5351
5352 // Reset first derivative (slope) at first and last point
5353 if (num == 1) {
5354 // First point has 0 2nd derivative
5355 gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2;
5356 }
5357 else if (num == nmax) {
5358 // Last point has 0 2nd derivative
5359 gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2;
5360 }
5361
5362 // Calc second derivative at points
5363 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);
5364 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);
5365
5366 // Calc constants for cubic interpolation
5367 D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]);
5368 C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]);
5369 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]);
5370 A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3);
5371
5372 var increment = (xx[num] - xx[num - 1]) / steps;
5373 var temp, tempx;
5374
5375 for (var j = 0, l = steps; j < l; j++) {
5376 temp = [];
5377 tempx = xx[num - 1] + j * increment;
5378 temp.push(tempx);
5379 temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3));
5380 _smoothedData.push(temp);
5381 _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]);
5382 }
5383 }
5384
5385 _smoothedData.push(gd[i]);
5386 _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]);
5387
5388 return [_smoothedData, _smoothedPlotData];
5389 }
5390
5391 ///////
5392 // computeHermiteSmoothedData
5393 // A hermite spline smoothing of the plot data.
5394 // This implementation is derived from the one posted
5395 // by krypin on the jqplot-users mailing list:
5396 //
5397 // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1
5398 //
5399 // with a blog post:
5400 //
5401 // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/
5402 //
5403 // and download of the original plugin:
5404 //
5405 // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js
5406 //////////
5407
5408 // called with scope of series
5409 function computeHermiteSmoothedData (gd) {
5410 var smooth = this.renderer.smooth;
5411 var tension = this.renderer.tension;
5412 var dim = this.canvas.getWidth();
5413 var xp = this._xaxis.series_p2u;
5414 var yp = this._yaxis.series_p2u;
5415 var steps =null;
5416 var _steps = null;
5417 var a = null;
5418 var a1 = null;
5419 var a2 = null;
5420 var slope = null;
5421 var slope2 = null;
5422 var temp = null;
5423 var t, s, h1, h2, h3, h4;
5424 var TiX, TiY, Ti1X, Ti1Y;
5425 var pX, pY, p;
5426 var sd = [];
5427 var spd = [];
5428 var dist = gd.length/dim;
5429 var min, max, stretch, scale, shift;
5430 var _smoothedData = [];
5431 var _smoothedPlotData = [];
5432 if (!isNaN(parseFloat(smooth))) {
5433 steps = parseFloat(smooth);
5434 }
5435 else {
5436 steps = getSteps(dist, 0.5);
5437 }
5438 if (!isNaN(parseFloat(tension))) {
5439 tension = parseFloat(tension);
5440 }
5441
5442 for (var i=0, l = gd.length-1; i < l; i++) {
5443
5444 if (tension === null) {
5445 slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0]));
5446
5447 min = 0.3;
5448 max = 0.6;
5449 stretch = (max - min)/2.0;
5450 scale = 2.5;
5451 shift = -1.4;
5452
5453 temp = slope/scale + shift;
5454
5455 a1 = stretch * tanh(temp) - stretch * tanh(shift) + min;
5456
5457 // if have both left and right line segments, will use minimum tension.
5458 if (i > 0) {
5459 slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0]));
5460 }
5461 temp = slope2/scale + shift;
5462
5463 a2 = stretch * tanh(temp) - stretch * tanh(shift) + min;
5464
5465 a = (a1 + a2)/2.0;
5466
5467 }
5468 else {
5469 a = tension;
5470 }
5471 for (t=0; t < steps; t++) {
5472 s = t / steps;
5473 h1 = (1 + 2*s)*Math.pow((1-s),2);
5474 h2 = s*Math.pow((1-s),2);
5475 h3 = Math.pow(s,2)*(3-2*s);
5476 h4 = Math.pow(s,2)*(s-1);
5477
5478 if (gd[i-1]) {
5479 TiX = a * (gd[i+1][0] - gd[i-1][0]);
5480 TiY = a * (gd[i+1][1] - gd[i-1][1]);
5481 } else {
5482 TiX = a * (gd[i+1][0] - gd[i][0]);
5483 TiY = a * (gd[i+1][1] - gd[i][1]);
5484 }
5485 if (gd[i+2]) {
5486 Ti1X = a * (gd[i+2][0] - gd[i][0]);
5487 Ti1Y = a * (gd[i+2][1] - gd[i][1]);
5488 } else {
5489 Ti1X = a * (gd[i+1][0] - gd[i][0]);
5490 Ti1Y = a * (gd[i+1][1] - gd[i][1]);
5491 }
5492
5493 pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X;
5494 pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y;
5495 p = [pX, pY];
5496
5497 _smoothedData.push(p);
5498 _smoothedPlotData.push([xp(pX), yp(pY)]);
5499 }
5500 }
5501 _smoothedData.push(gd[l]);
5502 _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]);
5503
5504 return [_smoothedData, _smoothedPlotData];
5505 }
5506
5507 // setGridData
5508 // converts the user data values to grid coordinates and stores them
5509 // in the gridData array.
5510 // Called with scope of a series.
5511 $.jqplot.LineRenderer.prototype.setGridData = function(plot) {
5512 // recalculate the grid data
5513 var xp = this._xaxis.series_u2p;
5514 var yp = this._yaxis.series_u2p;
5515 var data = this._plotData;
5516 var pdata = this._prevPlotData;
5517 this.gridData = [];
5518 this._prevGridData = [];
5519 this.renderer._smoothedData = [];
5520 this.renderer._smoothedPlotData = [];
5521 this.renderer._hiBandGridData = [];
5522 this.renderer._lowBandGridData = [];
5523 this.renderer._hiBandSmoothedData = [];
5524 this.renderer._lowBandSmoothedData = [];
5525 var bands = this.renderer.bands;
5526 var hasNull = false;
5527 for (var i=0, l=data.length; i < l; i++) {
5528 // if not a line series or if no nulls in data, push the converted point onto the array.
5529 if (data[i][0] != null && data[i][1] != null) {
5530 this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
5531 }
5532 // else if there is a null, preserve it.
5533 else if (data[i][0] == null) {
5534 hasNull = true;
5535 this.gridData.push([null, yp.call(this._yaxis, data[i][1])]);
5536 }
5537 else if (data[i][1] == null) {
5538 hasNull = true;
5539 this.gridData.push([xp.call(this._xaxis, data[i][0]), null]);
5540 }
5541 // if not a line series or if no nulls in data, push the converted point onto the array.
5542 if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) {
5543 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]);
5544 }
5545 // else if there is a null, preserve it.
5546 else if (pdata[i] != null && pdata[i][0] == null) {
5547 this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]);
5548 }
5549 else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) {
5550 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]);
5551 }
5552 }
5553
5554 // don't do smoothing or bands on broken lines.
5555 if (hasNull) {
5556 this.renderer.smooth = false;
5557 if (this._type === 'line') {
5558 bands.show = false;
5559 }
5560 }
5561
5562 if (this._type === 'line' && bands.show) {
5563 for (var i=0, l=bands.hiData.length; i<l; i++) {
5564 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
5565 }
5566 for (var i=0, l=bands.lowData.length; i<l; i++) {
5567 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
5568 }
5569 }
5570
5571 // calculate smoothed data if enough points and no nulls
5572 if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) {
5573 var ret;
5574 if (this.renderer.constrainSmoothing) {
5575 ret = computeConstrainedSmoothedData.call(this, this.gridData);
5576 this.renderer._smoothedData = ret[0];
5577 this.renderer._smoothedPlotData = ret[1];
5578
5579 if (bands.show) {
5580 ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
5581 this.renderer._hiBandSmoothedData = ret[0];
5582 ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
5583 this.renderer._lowBandSmoothedData = ret[0];
5584 }
5585
5586 ret = null;
5587 }
5588 else {
5589 ret = computeHermiteSmoothedData.call(this, this.gridData);
5590 this.renderer._smoothedData = ret[0];
5591 this.renderer._smoothedPlotData = ret[1];
5592
5593 if (bands.show) {
5594 ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
5595 this.renderer._hiBandSmoothedData = ret[0];
5596 ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
5597 this.renderer._lowBandSmoothedData = ret[0];
5598 }
5599
5600 ret = null;
5601 }
5602 }
5603 };
5604
5605 // makeGridData
5606 // converts any arbitrary data values to grid coordinates and
5607 // returns them. This method exists so that plugins can use a series'
5608 // linerenderer to generate grid data points without overwriting the
5609 // grid data associated with that series.
5610 // Called with scope of a series.
5611 $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) {
5612 // recalculate the grid data
5613 var xp = this._xaxis.series_u2p;
5614 var yp = this._yaxis.series_u2p;
5615 var gd = [];
5616 var pgd = [];
5617 this.renderer._smoothedData = [];
5618 this.renderer._smoothedPlotData = [];
5619 this.renderer._hiBandGridData = [];
5620 this.renderer._lowBandGridData = [];
5621 this.renderer._hiBandSmoothedData = [];
5622 this.renderer._lowBandSmoothedData = [];
5623 var bands = this.renderer.bands;
5624 var hasNull = false;
5625 for (var i=0; i<data.length; i++) {
5626 // if not a line series or if no nulls in data, push the converted point onto the array.
5627 if (data[i][0] != null && data[i][1] != null) {
5628 gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
5629 }
5630 // else if there is a null, preserve it.
5631 else if (data[i][0] == null) {
5632 hasNull = true;
5633 gd.push([null, yp.call(this._yaxis, data[i][1])]);
5634 }
5635 else if (data[i][1] == null) {
5636 hasNull = true;
5637 gd.push([xp.call(this._xaxis, data[i][0]), null]);
5638 }
5639 }
5640
5641 // don't do smoothing or bands on broken lines.
5642 if (hasNull) {
5643 this.renderer.smooth = false;
5644 if (this._type === 'line') {
5645 bands.show = false;
5646 }
5647 }
5648
5649 if (this._type === 'line' && bands.show) {
5650 for (var i=0, l=bands.hiData.length; i<l; i++) {
5651 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
5652 }
5653 for (var i=0, l=bands.lowData.length; i<l; i++) {
5654 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
5655 }
5656 }
5657
5658 if (this._type === 'line' && this.renderer.smooth && gd.length > 2) {
5659 var ret;
5660 if (this.renderer.constrainSmoothing) {
5661 ret = computeConstrainedSmoothedData.call(this, gd);
5662 this.renderer._smoothedData = ret[0];
5663 this.renderer._smoothedPlotData = ret[1];
5664
5665 if (bands.show) {
5666 ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
5667 this.renderer._hiBandSmoothedData = ret[0];
5668 ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
5669 this.renderer._lowBandSmoothedData = ret[0];
5670 }
5671
5672 ret = null;
5673 }
5674 else {
5675 ret = computeHermiteSmoothedData.call(this, gd);
5676 this.renderer._smoothedData = ret[0];
5677 this.renderer._smoothedPlotData = ret[1];
5678
5679 if (bands.show) {
5680 ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
5681 this.renderer._hiBandSmoothedData = ret[0];
5682 ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
5683 this.renderer._lowBandSmoothedData = ret[0];
5684 }
5685
5686 ret = null;
5687 }
5688 }
5689 return gd;
5690 };
5691
5692
5693 // called within scope of series.
5694 $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
5695 var i;
5696 // get a copy of the options, so we don't modify the original object.
5697 var opts = $.extend(true, {}, options);
5698 var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
5699 var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
5700 var fill = (opts.fill != undefined) ? opts.fill : this.fill;
5701 var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
5702 var xmin, ymin, xmax, ymax;
5703 ctx.save();
5704 if (gd.length) {
5705 if (showLine) {
5706 // if we fill, we'll have to add points to close the curve.
5707 if (fill) {
5708 if (this.fillToZero) {
5709 // have to break line up into shapes at axis crossings
5710 var negativeColor = this.negativeColor;
5711 if (! this.useNegativeColors) {
5712 negativeColor = opts.fillStyle;
5713 }
5714 var isnegative = false;
5715 var posfs = opts.fillStyle;
5716
5717 // if stoking line as well as filling, get a copy of line data.
5718 if (fillAndStroke) {
5719 var fasgd = gd.slice(0);
5720 }
5721 // if not stacked, fill down to axis
5722 if (this.index == 0 || !this._stack) {
5723
5724 var tempgd = [];
5725 var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData;
5726 this._areaPoints = [];
5727 var pyzero = this._yaxis.series_u2p(this.fillToValue);
5728 var pxzero = this._xaxis.series_u2p(this.fillToValue);
5729
5730 opts.closePath = true;
5731
5732 if (this.fillAxis == 'y') {
5733 tempgd.push([gd[0][0], pyzero]);
5734 this._areaPoints.push([gd[0][0], pyzero]);
5735
5736 for (var i=0; i<gd.length-1; i++) {
5737 tempgd.push(gd[i]);
5738 this._areaPoints.push(gd[i]);
5739 // do we have an axis crossing?
5740 if (pd[i][1] * pd[i+1][1] <= 0) {
5741 if (pd[i][1] < 0) {
5742 isnegative = true;
5743 opts.fillStyle = negativeColor;
5744 }
5745 else {
5746 isnegative = false;
5747 opts.fillStyle = posfs;
5748 }
5749
5750 var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
5751 tempgd.push([xintercept, pyzero]);
5752 this._areaPoints.push([xintercept, pyzero]);
5753 // now draw this shape and shadow.
5754 if (shadow) {
5755 this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
5756 }
5757 this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
5758 // now empty temp array and continue
5759 tempgd = [[xintercept, pyzero]];
5760 // this._areaPoints = [[xintercept, pyzero]];
5761 }
5762 }
5763 if (pd[gd.length-1][1] < 0) {
5764 isnegative = true;
5765 opts.fillStyle = negativeColor;
5766 }
5767 else {
5768 isnegative = false;
5769 opts.fillStyle = posfs;
5770 }
5771 tempgd.push(gd[gd.length-1]);
5772 this._areaPoints.push(gd[gd.length-1]);
5773 tempgd.push([gd[gd.length-1][0], pyzero]);
5774 this._areaPoints.push([gd[gd.length-1][0], pyzero]);
5775 }
5776 // now draw the last area.
5777 if (shadow) {
5778 this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
5779 }
5780 this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
5781
5782
5783 // var gridymin = this._yaxis.series_u2p(0);
5784 // // IE doesn't return new length on unshift
5785 // gd.unshift([gd[0][0], gridymin]);
5786 // len = gd.length;
5787 // gd.push([gd[len - 1][0], gridymin]);
5788 }
5789 // if stacked, fill to line below
5790 else {
5791 var prev = this._prevGridData;
5792 for (var i=prev.length; i>0; i--) {
5793 gd.push(prev[i-1]);
5794 // this._areaPoints.push(prev[i-1]);
5795 }
5796 if (shadow) {
5797 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5798 }
5799 this._areaPoints = gd;
5800 this.renderer.shapeRenderer.draw(ctx, gd, opts);
5801 }
5802 }
5803 /////////////////////////
5804 // Not filled to zero
5805 ////////////////////////
5806 else {
5807 // if stoking line as well as filling, get a copy of line data.
5808 if (fillAndStroke) {
5809 var fasgd = gd.slice(0);
5810 }
5811 // if not stacked, fill down to axis
5812 if (this.index == 0 || !this._stack) {
5813 // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
5814 var gridymin = ctx.canvas.height;
5815 // IE doesn't return new length on unshift
5816 gd.unshift([gd[0][0], gridymin]);
5817 var len = gd.length;
5818 gd.push([gd[len - 1][0], gridymin]);
5819 }
5820 // if stacked, fill to line below
5821 else {
5822 var prev = this._prevGridData;
5823 for (var i=prev.length; i>0; i--) {
5824 gd.push(prev[i-1]);
5825 }
5826 }
5827 this._areaPoints = gd;
5828
5829 if (shadow) {
5830 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5831 }
5832
5833 this.renderer.shapeRenderer.draw(ctx, gd, opts);
5834 }
5835 if (fillAndStroke) {
5836 var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false});
5837 this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
5838 //////////
5839 // TODO: figure out some way to do shadows nicely
5840 // if (shadow) {
5841 // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts);
5842 // }
5843 // now draw the markers
5844 if (this.markerRenderer.show) {
5845 if (this.renderer.smooth) {
5846 fasgd = this.gridData;
5847 }
5848 for (i=0; i<fasgd.length; i++) {
5849 this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions);
5850 }
5851 }
5852 }
5853 }
5854 else {
5855
5856 if (this.renderer.bands.show) {
5857 var bdat;
5858 var bopts = $.extend(true, {}, opts);
5859 if (this.renderer.bands.showLines) {
5860 bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData;
5861 this.renderer.shapeRenderer.draw(ctx, bdat, opts);
5862 bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData;
5863 this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
5864 }
5865
5866 if (this.renderer.bands.fill) {
5867 if (this.renderer.smooth) {
5868 bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse());
5869 }
5870 else {
5871 bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse());
5872 }
5873 this._areaPoints = bdat;
5874 bopts.closePath = true;
5875 bopts.fill = true;
5876 bopts.fillStyle = this.renderer.bands.fillColor;
5877 this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
5878 }
5879 }
5880
5881 if (shadow) {
5882 this.renderer.shadowRenderer.draw(ctx, gd, opts);
5883 }
5884
5885 this.renderer.shapeRenderer.draw(ctx, gd, opts);
5886 }
5887 }
5888 // calculate the bounding box
5889 var xmin = xmax = ymin = ymax = null;
5890 for (i=0; i<this._areaPoints.length; i++) {
5891 var p = this._areaPoints[i];
5892 if (xmin > p[0] || xmin == null) {
5893 xmin = p[0];
5894 }
5895 if (ymax < p[1] || ymax == null) {
5896 ymax = p[1];
5897 }
5898 if (xmax < p[0] || xmax == null) {
5899 xmax = p[0];
5900 }
5901 if (ymin > p[1] || ymin == null) {
5902 ymin = p[1];
5903 }
5904 }
5905
5906 if (this.type === 'line' && this.renderer.bands.show) {
5907 ymax = this._yaxis.series_u2p(this.renderer.bands._min);
5908 ymin = this._yaxis.series_u2p(this.renderer.bands._max);
5909 }
5910
5911 this._boundingBox = [[xmin, ymax], [xmax, ymin]];
5912
5913 // now draw the markers
5914 if (this.markerRenderer.show && !fill) {
5915 if (this.renderer.smooth) {
5916 gd = this.gridData;
5917 }
5918 for (i=0; i<gd.length; i++) {
5919 if (gd[i][0] != null && gd[i][1] != null) {
5920 this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions);
5921 }
5922 }
5923 }
5924 }
5925
5926 ctx.restore();
5927 };
5928
5929 $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) {
5930 // This is a no-op, shadows drawn with lines.
5931 };
5932
5933 // called with scope of plot.
5934 // make sure to not leave anything highlighted.
5935 function postInit(target, data, options) {
5936 for (var i=0; i<this.series.length; i++) {
5937 if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
5938 // don't allow mouseover and mousedown at same time.
5939 if (this.series[i].highlightMouseOver) {
5940 this.series[i].highlightMouseDown = false;
5941 }
5942 }
5943 }
5944 }
5945
5946 // called within context of plot
5947 // create a canvas which we can draw on.
5948 // insert it before the eventCanvas, so eventCanvas will still capture events.
5949 function postPlotDraw() {
5950 // Memory Leaks patch
5951 if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) {
5952 this.plugins.lineRenderer.highlightCanvas.resetCanvas();
5953 this.plugins.lineRenderer.highlightCanvas = null;
5954 }
5955
5956 this.plugins.lineRenderer.highlightedSeriesIndex = null;
5957 this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
5958
5959 this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this));
5960 this.plugins.lineRenderer.highlightCanvas.setContext();
5961 this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
5962 }
5963
5964 function highlight (plot, sidx, pidx, points) {
5965 var s = plot.series[sidx];
5966 var canvas = plot.plugins.lineRenderer.highlightCanvas;
5967 canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
5968 s._highlightedPoint = pidx;
5969 plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
5970 var opts = {fillStyle: s.highlightColor};
5971 if (s.type === 'line' && s.renderer.bands.show) {
5972 opts.fill = true;
5973 opts.closePath = true;
5974 }
5975 s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
5976 canvas = null;
5977 }
5978
5979 function unhighlight (plot) {
5980 var canvas = plot.plugins.lineRenderer.highlightCanvas;
5981 canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
5982 for (var i=0; i<plot.series.length; i++) {
5983 plot.series[i]._highlightedPoint = null;
5984 }
5985 plot.plugins.lineRenderer.highlightedSeriesIndex = null;
5986 plot.target.trigger('jqplotDataUnhighlight');
5987 canvas = null;
5988 }
5989
5990
5991 function handleMove(ev, gridpos, datapos, neighbor, plot) {
5992 if (neighbor) {
5993 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
5994 var evt1 = jQuery.Event('jqplotDataMouseOver');
5995 evt1.pageX = ev.pageX;
5996 evt1.pageY = ev.pageY;
5997 plot.target.trigger(evt1, ins);
5998 if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
5999 var evt = jQuery.Event('jqplotDataHighlight');
6000 evt.which = ev.which;
6001 evt.pageX = ev.pageX;
6002 evt.pageY = ev.pageY;
6003 plot.target.trigger(evt, ins);
6004 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
6005 }
6006 }
6007 else if (neighbor == null) {
6008 unhighlight (plot);
6009 }
6010 }
6011
6012 function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
6013 if (neighbor) {
6014 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
6015 if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
6016 var evt = jQuery.Event('jqplotDataHighlight');
6017 evt.which = ev.which;
6018 evt.pageX = ev.pageX;
6019 evt.pageY = ev.pageY;
6020 plot.target.trigger(evt, ins);
6021 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
6022 }
6023 }
6024 else if (neighbor == null) {
6025 unhighlight (plot);
6026 }
6027 }
6028
6029 function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
6030 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
6031 if (idx != null && plot.series[idx].highlightMouseDown) {
6032 unhighlight(plot);
6033 }
6034 }
6035
6036 function handleClick(ev, gridpos, datapos, neighbor, plot) {
6037 if (neighbor) {
6038 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
6039 var evt = jQuery.Event('jqplotDataClick');
6040 evt.which = ev.which;
6041 evt.pageX = ev.pageX;
6042 evt.pageY = ev.pageY;
6043 plot.target.trigger(evt, ins);
6044 }
6045 }
6046
6047 function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
6048 if (neighbor) {
6049 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
6050 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
6051 if (idx != null && plot.series[idx].highlightMouseDown) {
6052 unhighlight(plot);
6053 }
6054 var evt = jQuery.Event('jqplotDataRightClick');
6055 evt.which = ev.which;
6056 evt.pageX = ev.pageX;
6057 evt.pageY = ev.pageY;
6058 plot.target.trigger(evt, ins);
6059 }
6060 }
6061
6062
6063 // class: $.jqplot.LinearAxisRenderer
6064 // The default jqPlot axis renderer, creating a numeric axis.
6065 $.jqplot.LinearAxisRenderer = function() {
6066 };
6067
6068 // called with scope of axis object.
6069 $.jqplot.LinearAxisRenderer.prototype.init = function(options){
6070 // prop: breakPoints
6071 // EXPERIMENTAL!! Use at your own risk!
6072 // Works only with linear axes and the default tick renderer.
6073 // Array of [start, stop] points to create a broken axis.
6074 // Broken axes have a "jump" in them, which is an immediate
6075 // transition from a smaller value to a larger value.
6076 // Currently, axis ticks MUST be manually assigned if using breakPoints
6077 // by using the axis ticks array option.
6078 this.breakPoints = null;
6079 // prop: breakTickLabel
6080 // Label to use at the axis break if breakPoints are specified.
6081 this.breakTickLabel = "&asymp;";
6082 // prop: drawBaseline
6083 // True to draw the axis baseline.
6084 this.drawBaseline = true;
6085 // prop: baselineWidth
6086 // width of the baseline in pixels.
6087 this.baselineWidth = null;
6088 // prop: baselineColor
6089 // CSS color spec for the baseline.
6090 this.baselineColor = null;
6091 // prop: forceTickAt0
6092 // This will ensure that there is always a tick mark at 0.
6093 // If data range is strictly positive or negative,
6094 // this will force 0 to be inside the axis bounds unless
6095 // the appropriate axis pad (pad, padMin or padMax) is set
6096 // to 0, then this will force an axis min or max value at 0.
6097 // This has know effect when any of the following options
6098 // are set: autoscale, min, max, numberTicks or tickInterval.
6099 this.forceTickAt0 = false;
6100 // prop: forceTickAt100
6101 // This will ensure that there is always a tick mark at 100.
6102 // If data range is strictly above or below 100,
6103 // this will force 100 to be inside the axis bounds unless
6104 // the appropriate axis pad (pad, padMin or padMax) is set
6105 // to 0, then this will force an axis min or max value at 100.
6106 // This has know effect when any of the following options
6107 // are set: autoscale, min, max, numberTicks or tickInterval.
6108 this.forceTickAt100 = false;
6109 // prop: tickInset
6110 // Controls the amount to inset the first and last ticks from
6111 // the edges of the grid, in multiples of the tick interval.
6112 // 0 is no inset, 0.5 is one half a tick interval, 1 is a full
6113 // tick interval, etc.
6114 this.tickInset = 0;
6115 // prop: minorTicks
6116 // Number of ticks to add between "major" ticks.
6117 // Major ticks are ticks supplied by user or auto computed.
6118 // Minor ticks cannot be created by user.
6119 this.minorTicks = 0;
6120 // prop: alignTicks
6121 // true to align tick marks across opposed axes
6122 // such as from the y2axis to yaxis.
6123 this.alignTicks = false;
6124 this._autoFormatString = '';
6125 this._overrideFormatString = false;
6126 this._scalefact = 1.0;
6127 $.extend(true, this, options);
6128 if (this.breakPoints) {
6129 if (!$.isArray(this.breakPoints)) {
6130 this.breakPoints = null;
6131 }
6132 else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) {
6133 this.breakPoints = null;
6134 }
6135 }
6136 if (this.numberTicks != null && this.numberTicks < 2) {
6137 this.numberTicks = 2;
6138 }
6139 this.resetDataBounds();
6140 };
6141
6142 // called with scope of axis
6143 $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) {
6144 if (this.show) {
6145 // populate the axis label and value properties.
6146 // createTicks is a method on the renderer, but
6147 // call it within the scope of the axis.
6148 this.renderer.createTicks.call(this, plot);
6149 // fill a div with axes labels in the right direction.
6150 // Need to pregenerate each axis to get its bounds and
6151 // position it and the labels correctly on the plot.
6152 var dim=0;
6153 var temp;
6154 // Added for theming.
6155 if (this._elem) {
6156 // Memory Leaks patch
6157 //this._elem.empty();
6158 this._elem.emptyForce();
6159 this._elem = null;
6160 }
6161
6162 this._elem = $(document.createElement('div'));
6163 this._elem.addClass('jqplot-axis jqplot-'+this.name);
6164 this._elem.css('position', 'absolute');
6165
6166
6167 if (this.name == 'xaxis' || this.name == 'x2axis') {
6168 this._elem.width(this._plotDimensions.width);
6169 }
6170 else {
6171 this._elem.height(this._plotDimensions.height);
6172 }
6173
6174 // create a _label object.
6175 this.labelOptions.axis = this.name;
6176 this._label = new this.labelRenderer(this.labelOptions);
6177 if (this._label.show) {
6178 var elem = this._label.draw(ctx, plot);
6179 elem.appendTo(this._elem);
6180 elem = null;
6181 }
6182
6183 var t = this._ticks;
6184 var tick;
6185 for (var i=0; i<t.length; i++) {
6186 tick = t[i];
6187 if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
6188 this._elem.append(tick.draw(ctx, plot));
6189 }
6190 }
6191 tick = null;
6192 t = null;
6193 }
6194 return this._elem;
6195 };
6196
6197 // called with scope of an axis
6198 $.jqplot.LinearAxisRenderer.prototype.reset = function() {
6199 this.min = this._options.min;
6200 this.max = this._options.max;
6201 this.tickInterval = this._options.tickInterval;
6202 this.numberTicks = this._options.numberTicks;
6203 this._autoFormatString = '';
6204 if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) {
6205 this.tickOptions.formatString = '';
6206 }
6207
6208 // this._ticks = this.__ticks;
6209 };
6210
6211 // called with scope of axis
6212 $.jqplot.LinearAxisRenderer.prototype.set = function() {
6213 var dim = 0;
6214 var temp;
6215 var w = 0;
6216 var h = 0;
6217 var lshow = (this._label == null) ? false : this._label.show;
6218 if (this.show) {
6219 var t = this._ticks;
6220 var tick;
6221 for (var i=0; i<t.length; i++) {
6222 tick = t[i];
6223 if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) {
6224 if (this.name == 'xaxis' || this.name == 'x2axis') {
6225 temp = tick._elem.outerHeight(true);
6226 }
6227 else {
6228 temp = tick._elem.outerWidth(true);
6229 }
6230 if (temp > dim) {
6231 dim = temp;
6232 }
6233 }
6234 }
6235 tick = null;
6236 t = null;
6237
6238 if (lshow) {
6239 w = this._label._elem.outerWidth(true);
6240 h = this._label._elem.outerHeight(true);
6241 }
6242 if (this.name == 'xaxis') {
6243 dim = dim + h;
6244 this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'});
6245 }
6246 else if (this.name == 'x2axis') {
6247 dim = dim + h;
6248 this._elem.css({'height':dim+'px', left:'0px', top:'0px'});
6249 }
6250 else if (this.name == 'yaxis') {
6251 dim = dim + w;
6252 this._elem.css({'width':dim+'px', left:'0px', top:'0px'});
6253 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
6254 this._label._elem.css('width', w+'px');
6255 }
6256 }
6257 else {
6258 dim = dim + w;
6259 this._elem.css({'width':dim+'px', right:'0px', top:'0px'});
6260 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) {
6261 this._label._elem.css('width', w+'px');
6262 }
6263 }
6264 }
6265 };
6266
6267 // called with scope of axis
6268 $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) {
6269 // we're are operating on an axis here
6270 var ticks = this._ticks;
6271 var userTicks = this.ticks;
6272 var name = this.name;
6273 // databounds were set on axis initialization.
6274 var db = this._dataBounds;
6275 var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height;
6276 var interval;
6277 var min, max;
6278 var pos1, pos2;
6279 var tt, i;
6280 // get a copy of user's settings for min/max.
6281 var userMin = this.min;
6282 var userMax = this.max;
6283 var userNT = this.numberTicks;
6284 var userTI = this.tickInterval;
6285
6286 var threshold = 30;
6287 this._scalefact = (Math.max(dim, threshold+1) - threshold)/300.0;
6288
6289 // if we already have ticks, use them.
6290 // ticks must be in order of increasing value.
6291
6292 if (userTicks.length) {
6293 // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed
6294 for (i=0; i<userTicks.length; i++){
6295 var ut = userTicks[i];
6296 var t = new this.tickRenderer(this.tickOptions);
6297 if ($.isArray(ut)) {
6298 t.value = ut[0];
6299 if (this.breakPoints) {
6300 if (ut[0] == this.breakPoints[0]) {
6301 t.label = this.breakTickLabel;
6302 t._breakTick = true;
6303 t.showGridline = false;
6304 t.showMark = false;
6305 }
6306 else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) {
6307 t.show = false;
6308 t.showGridline = false;
6309 t.label = ut[1];
6310 }
6311 else {
6312 t.label = ut[1];
6313 }
6314 }
6315 else {
6316 t.label = ut[1];
6317 }
6318 t.setTick(ut[0], this.name);
6319 this._ticks.push(t);
6320 }
6321
6322 else if ($.isPlainObject(ut)) {
6323 $.extend(true, t, ut);
6324 t.axis = this.name;
6325 this._ticks.push(t);
6326 }
6327
6328 else {
6329 t.value = ut;
6330 if (this.breakPoints) {
6331 if (ut == this.breakPoints[0]) {
6332 t.label = this.breakTickLabel;
6333 t._breakTick = true;
6334 t.showGridline = false;
6335 t.showMark = false;
6336 }
6337 else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) {
6338 t.show = false;
6339 t.showGridline = false;
6340 }
6341 }
6342 t.setTick(ut, this.name);
6343 this._ticks.push(t);
6344 }
6345 }
6346 this.numberTicks = userTicks.length;
6347 this.min = this._ticks[0].value;
6348 this.max = this._ticks[this.numberTicks-1].value;
6349 this.tickInterval = (this.max - this.min) / (this.numberTicks - 1);
6350 }
6351
6352 // we don't have any ticks yet, let's make some!
6353 else {
6354 if (name == 'xaxis' || name == 'x2axis') {
6355 dim = this._plotDimensions.width;
6356 }
6357 else {
6358 dim = this._plotDimensions.height;
6359 }
6360
6361 var _numberTicks = this.numberTicks;
6362
6363 // if aligning this axis, use number of ticks from previous axis.
6364 // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
6365 if (this.alignTicks) {
6366 if (this.name === 'x2axis' && plot.axes.xaxis.show) {
6367 _numberTicks = plot.axes.xaxis.numberTicks;
6368 }
6369 else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) {
6370 _numberTicks = plot.axes.yaxis.numberTicks;
6371 }
6372 }
6373
6374 min = ((this.min != null) ? this.min : db.min);
6375 max = ((this.max != null) ? this.max : db.max);
6376
6377 var range = max - min;
6378 var rmin, rmax;
6379 var temp;
6380
6381 if (this.tickOptions == null || !this.tickOptions.formatString) {
6382 this._overrideFormatString = true;
6383 }
6384
6385 // Doing complete autoscaling
6386 if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) {
6387 // Check if user must have tick at 0 or 100 and ensure they are in range.
6388 // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range.
6389 if (this.forceTickAt0) {
6390 if (min > 0) {
6391 min = 0;
6392 }
6393 if (max < 0) {
6394 max = 0;
6395 }
6396 }
6397
6398 if (this.forceTickAt100) {
6399 if (min > 100) {
6400 min = 100;
6401 }
6402 if (max < 100) {
6403 max = 100;
6404 }
6405 }
6406
6407 var keepMin = false,
6408 keepMax = false;
6409
6410 if (this.min != null) {
6411 keepMin = true;
6412 }
6413
6414 else if (this.max != null) {
6415 keepMax = true;
6416 }
6417
6418 // var threshold = 30;
6419 // var tdim = Math.max(dim, threshold+1);
6420 // this._scalefact = (tdim-threshold)/300.0;
6421 var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax);
6422 // calculate a padded max and min, points should be less than these
6423 // so that they aren't too close to the edges of the plot.
6424 // User can adjust how much padding is allowed with pad, padMin and PadMax options.
6425 // If min or max is set, don't pad that end of axis.
6426 var tumin = (this.min != null) ? min : min + range*(this.padMin - 1);
6427 var tumax = (this.max != null) ? max : max - range*(this.padMax - 1);
6428
6429 // if they're equal, we shouldn't have to do anything, right?
6430 // if (min <=tumin || max >= tumax) {
6431 if (min <tumin || max > tumax) {
6432 tumin = (this.min != null) ? min : min - range*(this.padMin - 1);
6433 tumax = (this.max != null) ? max : max + range*(this.padMax - 1);
6434 ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax);
6435 }
6436
6437 this.min = ret[0];
6438 this.max = ret[1];
6439 // if numberTicks specified, it should return the same.
6440 this.numberTicks = ret[2];
6441 this._autoFormatString = ret[3];
6442 this.tickInterval = ret[4];
6443 }
6444
6445 // User has specified some axis scale related option, can use auto algorithm
6446 else {
6447
6448 // if min and max are same, space them out a bit
6449 if (min == max) {
6450 var adj = 0.05;
6451 if (min > 0) {
6452 adj = Math.max(Math.log(min)/Math.LN10, 0.05);
6453 }
6454 min -= adj;
6455 max += adj;
6456 }
6457
6458 // autoscale. Can't autoscale if min or max is supplied.
6459 // Will use numberTicks and tickInterval if supplied. Ticks
6460 // across multiple axes may not line up depending on how
6461 // bars are to be plotted.
6462 if (this.autoscale && this.min == null && this.max == null) {
6463 var rrange, ti, margin;
6464 var forceMinZero = false;
6465 var forceZeroLine = false;
6466 var intervals = {min:null, max:null, average:null, stddev:null};
6467 // if any series are bars, or if any are fill to zero, and if this
6468 // is the axis to fill toward, check to see if we can start axis at zero.
6469 for (var i=0; i<this._series.length; i++) {
6470 var s = this._series[i];
6471 var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name;
6472 // check to see if this is the fill axis
6473 if (this.name == faname) {
6474 var vals = s._plotValues[s.fillAxis];
6475 var vmin = vals[0];
6476 var vmax = vals[0];
6477 for (var j=1; j<vals.length; j++) {
6478 if (vals[j] < vmin) {
6479 vmin = vals[j];
6480 }
6481 else if (vals[j] > vmax) {
6482 vmax = vals[j];
6483 }
6484 }
6485 var dp = (vmax - vmin) / vmax;
6486 // is this sries a bar?
6487 if (s.renderer.constructor == $.jqplot.BarRenderer) {
6488 // if no negative values and could also check range.
6489 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
6490 forceMinZero = true;
6491 }
6492 else {
6493 forceMinZero = false;
6494 if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) {
6495 forceZeroLine = true;
6496 }
6497 else {
6498 forceZeroLine = false;
6499 }
6500 }
6501 }
6502
6503 // if not a bar and filling, use appropriate method.
6504 else if (s.fill) {
6505 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) {
6506 forceMinZero = true;
6507 }
6508 else if (vmin < 0 && vmax > 0 && s.fillToZero) {
6509 forceMinZero = false;
6510 forceZeroLine = true;
6511 }
6512 else {
6513 forceMinZero = false;
6514 forceZeroLine = false;
6515 }
6516 }
6517
6518 // if not a bar and not filling, only change existing state
6519 // if it doesn't make sense
6520 else if (vmin < 0) {
6521 forceMinZero = false;
6522 }
6523 }
6524 }
6525
6526 // check if we need make axis min at 0.
6527 if (forceMinZero) {
6528 // compute number of ticks
6529 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6530 this.min = 0;
6531 userMin = 0;
6532 // what order is this range?
6533 // what tick interval does that give us?
6534 ti = max/(this.numberTicks-1);
6535 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6536 if (ti/temp == parseInt(ti/temp, 10)) {
6537 ti += temp;
6538 }
6539 this.tickInterval = Math.ceil(ti/temp) * temp;
6540 this.max = this.tickInterval * (this.numberTicks - 1);
6541 }
6542
6543 // check if we need to make sure there is a tick at 0.
6544 else if (forceZeroLine) {
6545 // compute number of ticks
6546 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6547 var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1));
6548 var ntmax = this.numberTicks - 1 - ntmin;
6549 ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax));
6550 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6551 this.tickInterval = Math.ceil(ti/temp) * temp;
6552 this.max = this.tickInterval * ntmax;
6553 this.min = -this.tickInterval * ntmin;
6554 }
6555
6556 // if nothing else, do autoscaling which will try to line up ticks across axes.
6557 else {
6558 if (this.numberTicks == null){
6559 if (this.tickInterval) {
6560 this.numberTicks = 3 + Math.ceil(range / this.tickInterval);
6561 }
6562 else {
6563 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing);
6564 }
6565 }
6566
6567 if (this.tickInterval == null) {
6568 // get a tick interval
6569 ti = range/(this.numberTicks - 1);
6570
6571 if (ti < 1) {
6572 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10)));
6573 }
6574 else {
6575 temp = 1;
6576 }
6577 this.tickInterval = Math.ceil(ti*temp*this.pad)/temp;
6578 }
6579 else {
6580 temp = 1 / this.tickInterval;
6581 }
6582
6583 // try to compute a nicer, more even tick interval
6584 // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10));
6585 // this.tickInterval = Math.ceil(ti/temp) * temp;
6586 rrange = this.tickInterval * (this.numberTicks - 1);
6587 margin = (rrange - range)/2;
6588
6589 if (this.min == null) {
6590 this.min = Math.floor(temp*(min-margin))/temp;
6591 }
6592 if (this.max == null) {
6593 this.max = this.min + rrange;
6594 }
6595 }
6596
6597 // Compute a somewhat decent format string if it is needed.
6598 // get precision of interval and determine a format string.
6599 var sf = $.jqplot.getSignificantFigures(this.tickInterval);
6600
6601 var fstr;
6602
6603 // if we have only a whole number, use integer formatting
6604 if (sf.digitsLeft >= sf.significantDigits) {
6605 fstr = '%d';
6606 }
6607
6608 else {
6609 var temp = Math.max(0, 5 - sf.digitsLeft);
6610 temp = Math.min(temp, sf.digitsRight);
6611 fstr = '%.'+ temp + 'f';
6612 }
6613
6614 this._autoFormatString = fstr;
6615 }
6616
6617 // Use the default algorithm which pads each axis to make the chart
6618 // centered nicely on the grid.
6619 else {
6620
6621 rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1);
6622 rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1);
6623 range = rmax - rmin;
6624
6625 if (this.numberTicks == null){
6626 // if tickInterval is specified by user, we will ignore computed maximum.
6627 // max will be equal or greater to fit even # of ticks.
6628 if (this.tickInterval != null) {
6629 this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1;
6630 }
6631 else if (dim > 100) {
6632 this.numberTicks = parseInt(3+(dim-100)/75, 10);
6633 }
6634 else {
6635 this.numberTicks = 2;
6636 }
6637 }
6638
6639 if (this.tickInterval == null) {
6640 this.tickInterval = range / (this.numberTicks-1);
6641 }
6642
6643 if (this.max == null) {
6644 rmax = rmin + this.tickInterval*(this.numberTicks - 1);
6645 }
6646 if (this.min == null) {
6647 rmin = rmax - this.tickInterval*(this.numberTicks - 1);
6648 }
6649
6650 // get precision of interval and determine a format string.
6651 var sf = $.jqplot.getSignificantFigures(this.tickInterval);
6652
6653 var fstr;
6654
6655 // if we have only a whole number, use integer formatting
6656 if (sf.digitsLeft >= sf.significantDigits) {
6657 fstr = '%d';
6658 }
6659
6660 else {
6661 var temp = Math.max(0, 5 - sf.digitsLeft);
6662 temp = Math.min(temp, sf.digitsRight);
6663 fstr = '%.'+ temp + 'f';
6664 }
6665
6666
6667 this._autoFormatString = fstr;
6668
6669 this.min = rmin;
6670 this.max = rmax;
6671 }
6672
6673 if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') {
6674 // fix for misleading tick display with small range and low precision.
6675 range = this.max - this.min;
6676 // figure out precision
6677 var temptick = new this.tickRenderer(this.tickOptions);
6678 // use the tick formatString or, the default.
6679 var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString;
6680 var fs = fs.match($.jqplot.sprintf.regex)[0];
6681 var precision = 0;
6682 if (fs) {
6683 if (fs.search(/[fFeEgGpP]/) > -1) {
6684 var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/);
6685 if (m) {
6686 precision = parseInt(m[1], 10);
6687 }
6688 else {
6689 precision = 6;
6690 }
6691 }
6692 else if (fs.search(/[di]/) > -1) {
6693 precision = 0;
6694 }
6695 // fact will be <= 1;
6696 var fact = Math.pow(10, -precision);
6697 if (this.tickInterval < fact) {
6698 // need to correct underrange
6699 if (userNT == null && userTI == null) {
6700 this.tickInterval = fact;
6701 if (userMax == null && userMin == null) {
6702 // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact;
6703 this.min = Math.floor(this._dataBounds.min/fact) * fact;
6704 if (this.min == this._dataBounds.min) {
6705 this.min = this._dataBounds.min - this.tickInterval;
6706 }
6707 // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact;
6708 this.max = Math.ceil(this._dataBounds.max/fact) * fact;
6709 if (this.max == this._dataBounds.max) {
6710 this.max = this._dataBounds.max + this.tickInterval;
6711 }
6712 var n = (this.max - this.min)/this.tickInterval;
6713 n = n.toFixed(11);
6714 n = Math.ceil(n);
6715 this.numberTicks = n + 1;
6716 }
6717 else if (userMax == null) {
6718 // add one tick for top of range.
6719 var n = (this._dataBounds.max - this.min) / this.tickInterval;
6720 n = n.toFixed(11);
6721 this.numberTicks = Math.ceil(n) + 2;
6722 this.max = this.min + this.tickInterval * (this.numberTicks-1);
6723 }
6724 else if (userMin == null) {
6725 // add one tick for bottom of range.
6726 var n = (this.max - this._dataBounds.min) / this.tickInterval;
6727 n = n.toFixed(11);
6728 this.numberTicks = Math.ceil(n) + 2;
6729 this.min = this.max - this.tickInterval * (this.numberTicks-1);
6730 }
6731 else {
6732 // calculate a number of ticks so max is within axis scale
6733 this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1;
6734 // if user's min and max don't fit evenly in ticks, adjust.
6735 // This takes care of cases such as user min set to 0, max set to 3.5 but tick
6736 // format string set to %d (integer ticks)
6737 this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision);
6738 this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision);
6739 // this.max = this.min + this.tickInterval*(this.numberTicks-1);
6740 this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1;
6741 }
6742 }
6743 }
6744 }
6745 }
6746
6747 }
6748
6749 if (this._overrideFormatString && this._autoFormatString != '') {
6750 this.tickOptions = this.tickOptions || {};
6751 this.tickOptions.formatString = this._autoFormatString;
6752 }
6753
6754 var t, to;
6755 for (var i=0; i<this.numberTicks; i++){
6756 tt = this.min + i * this.tickInterval;
6757 t = new this.tickRenderer(this.tickOptions);
6758 // var t = new $.jqplot.AxisTickRenderer(this.tickOptions);
6759
6760 t.setTick(tt, this.name);
6761 this._ticks.push(t);
6762
6763 if (i < this.numberTicks - 1) {
6764 for (var j=0; j<this.minorTicks; j++) {
6765 tt += this.tickInterval/(this.minorTicks+1);
6766 to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true});
6767 t = new this.tickRenderer(to);
6768 this._ticks.push(t);
6769 }
6770 }
6771 t = null;
6772 }
6773 }
6774
6775 if (this.tickInset) {
6776 this.min = this.min - this.tickInset * this.tickInterval;
6777 this.max = this.max + this.tickInset * this.tickInterval;
6778 }
6779
6780 ticks = null;
6781 };
6782
6783 // Used to reset just the values of the ticks and then repack, which will
6784 // recalculate the positioning functions. It is assuemd that the
6785 // number of ticks is the same and the values of the new array are at the
6786 // proper interval.
6787 // This method needs to be called with the scope of an axis object, like:
6788 //
6789 // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr);
6790 //
6791 $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) {
6792 if ($.isArray(opts) && opts.length == this._ticks.length) {
6793 var t;
6794 for (var i=0; i<opts.length; i++) {
6795 t = this._ticks[i];
6796 t.value = opts[i];
6797 t.label = t.formatter(t.formatString, opts[i]);
6798 t.label = t.prefix + t.label;
6799 t._elem.html(t.label);
6800 }
6801 t = null;
6802 this.min = $.jqplot.arrayMin(opts);
6803 this.max = $.jqplot.arrayMax(opts);
6804 this.pack();
6805 }
6806 // Not implemented yet.
6807 // else if ($.isPlainObject(opts)) {
6808 //
6809 // }
6810 };
6811
6812 // called with scope of axis
6813 $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) {
6814 // Add defaults for repacking from resetTickValues function.
6815 pos = pos || {};
6816 offsets = offsets || this._offsets;
6817
6818 var ticks = this._ticks;
6819 var max = this.max;
6820 var min = this.min;
6821 var offmax = offsets.max;
6822 var offmin = offsets.min;
6823 var lshow = (this._label == null) ? false : this._label.show;
6824
6825 for (var p in pos) {
6826 this._elem.css(p, pos[p]);
6827 }
6828
6829 this._offsets = offsets;
6830 // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left.
6831 var pixellength = offmax - offmin;
6832 var unitlength = max - min;
6833
6834 // point to unit and unit to point conversions references to Plot DOM element top left corner.
6835 if (this.breakPoints) {
6836 unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0];
6837
6838 this.p2u = function(p){
6839 return (p - offmin) * unitlength / pixellength + min;
6840 };
6841
6842 this.u2p = function(u){
6843 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6844 u = this.breakPoints[0];
6845 }
6846 if (u <= this.breakPoints[0]) {
6847 return (u - min) * pixellength / unitlength + offmin;
6848 }
6849 else {
6850 return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin;
6851 }
6852 };
6853
6854 if (this.name.charAt(0) == 'x'){
6855 this.series_u2p = function(u){
6856 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6857 u = this.breakPoints[0];
6858 }
6859 if (u <= this.breakPoints[0]) {
6860 return (u - min) * pixellength / unitlength;
6861 }
6862 else {
6863 return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength;
6864 }
6865 };
6866 this.series_p2u = function(p){
6867 return p * unitlength / pixellength + min;
6868 };
6869 }
6870
6871 else {
6872 this.series_u2p = function(u){
6873 if (u > this.breakPoints[0] && u < this.breakPoints[1]){
6874 u = this.breakPoints[0];
6875 }
6876 if (u >= this.breakPoints[1]) {
6877 return (u - max) * pixellength / unitlength;
6878 }
6879 else {
6880 return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength;
6881 }
6882 };
6883 this.series_p2u = function(p){
6884 return p * unitlength / pixellength + max;
6885 };
6886 }
6887 }
6888 else {
6889 this.p2u = function(p){
6890 return (p - offmin) * unitlength / pixellength + min;
6891 };
6892
6893 this.u2p = function(u){
6894 return (u - min) * pixellength / unitlength + offmin;
6895 };
6896
6897 if (this.name == 'xaxis' || this.name == 'x2axis'){
6898 this.series_u2p = function(u){
6899 return (u - min) * pixellength / unitlength;
6900 };
6901 this.series_p2u = function(p){
6902 return p * unitlength / pixellength + min;
6903 };
6904 }
6905
6906 else {
6907 this.series_u2p = function(u){
6908 return (u - max) * pixellength / unitlength;
6909 };
6910 this.series_p2u = function(p){
6911 return p * unitlength / pixellength + max;
6912 };
6913 }
6914 }
6915
6916 if (this.show) {
6917 if (this.name == 'xaxis' || this.name == 'x2axis') {
6918 for (var i=0; i<ticks.length; i++) {
6919 var t = ticks[i];
6920 if (t.show && t.showLabel) {
6921 var shim;
6922
6923 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
6924 // will need to adjust auto positioning based on which axis this is.
6925 var temp = (this.name == 'xaxis') ? 1 : -1;
6926 switch (t.labelPosition) {
6927 case 'auto':
6928 // position at end
6929 if (temp * t.angle < 0) {
6930 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6931 }
6932 // position at start
6933 else {
6934 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6935 }
6936 break;
6937 case 'end':
6938 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6939 break;
6940 case 'start':
6941 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
6942 break;
6943 case 'middle':
6944 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6945 break;
6946 default:
6947 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
6948 break;
6949 }
6950 }
6951 else {
6952 shim = -t.getWidth()/2;
6953 }
6954 var val = this.u2p(t.value) + shim + 'px';
6955 t._elem.css('left', val);
6956 t.pack();
6957 }
6958 }
6959 if (lshow) {
6960 var w = this._label._elem.outerWidth(true);
6961 this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px');
6962 if (this.name == 'xaxis') {
6963 this._label._elem.css('bottom', '0px');
6964 }
6965 else {
6966 this._label._elem.css('top', '0px');
6967 }
6968 this._label.pack();
6969 }
6970 }
6971 else {
6972 for (var i=0; i<ticks.length; i++) {
6973 var t = ticks[i];
6974 if (t.show && t.showLabel) {
6975 var shim;
6976 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) {
6977 // will need to adjust auto positioning based on which axis this is.
6978 var temp = (this.name == 'yaxis') ? 1 : -1;
6979 switch (t.labelPosition) {
6980 case 'auto':
6981 // position at end
6982 case 'end':
6983 if (temp * t.angle < 0) {
6984 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
6985 }
6986 else {
6987 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
6988 }
6989 break;
6990 case 'start':
6991 if (t.angle > 0) {
6992 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2;
6993 }
6994 else {
6995 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2;
6996 }
6997 break;
6998 case 'middle':
6999 // if (t.angle > 0) {
7000 // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2;
7001 // }
7002 // else {
7003 // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2;
7004 // }
7005 shim = -t.getHeight()/2;
7006 break;
7007 default:
7008 shim = -t.getHeight()/2;
7009 break;
7010 }
7011 }
7012 else {
7013 shim = -t.getHeight()/2;
7014 }
7015
7016 var val = this.u2p(t.value) + shim + 'px';
7017 t._elem.css('top', val);
7018 t.pack();
7019 }
7020 }
7021 if (lshow) {
7022 var h = this._label._elem.outerHeight(true);
7023 this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px');
7024 if (this.name == 'yaxis') {
7025 this._label._elem.css('left', '0px');
7026 }
7027 else {
7028 this._label._elem.css('right', '0px');
7029 }
7030 this._label.pack();
7031 }
7032 }
7033 }
7034
7035 ticks = null;
7036 };
7037
7038
7039 /**
7040 * The following code was generaously given to me a while back by Scott Prahl.
7041 * He did a good job at computing axes min, max and number of ticks for the
7042 * case where the user has not set any scale related parameters (tickInterval,
7043 * numberTicks, min or max). I had ignored this use case for a long time,
7044 * focusing on the more difficult case where user has set some option controlling
7045 * tick generation. Anyway, about time I got this into jqPlot.
7046 * Thanks Scott!!
7047 */
7048
7049 /**
7050 * Copyright (c) 2010 Scott Prahl
7051 * The next three routines are currently available for use in all personal
7052 * or commercial projects under both the MIT and GPL version 2.0 licenses.
7053 * This means that you can choose the license that best suits your project
7054 * and use it accordingly.
7055 */
7056
7057 // A good format string depends on the interval. If the interval is greater
7058 // than 1 then there is no need to show any decimal digits. If it is < 1.0, then
7059 // use the magnitude of the interval to determine the number of digits to show.
7060 function bestFormatString (interval)
7061 {
7062 var fstr;
7063 interval = Math.abs(interval);
7064 if (interval >= 10) {
7065 fstr = '%d';
7066 }
7067
7068 else if (interval > 1) {
7069 if (interval === parseInt(interval, 10)) {
7070 fstr = '%d';
7071 }
7072 else {
7073 fstr = '%.1f';
7074 }
7075 }
7076
7077 else {
7078 var expv = -Math.floor(Math.log(interval)/Math.LN10);
7079 fstr = '%.' + expv + 'f';
7080 }
7081
7082 return fstr;
7083 }
7084
7085 var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5];
7086
7087 var _getLowerFactor = function(f) {
7088 var i = _factors.indexOf(f);
7089 if (i > 0) {
7090 return _factors[i-1];
7091 }
7092 else {
7093 return _factors[_factors.length - 1] / 100;
7094 }
7095 };
7096
7097 var _getHigherFactor = function(f) {
7098 var i = _factors.indexOf(f);
7099 if (i < _factors.length-1) {
7100 return _factors[i+1];
7101 }
7102 else {
7103 return _factors[0] * 100;
7104 }
7105 };
7106
7107 // Given a fixed minimum and maximum and a target number ot ticks
7108 // figure out the best interval and
7109 // return min, max, number ticks, format string and tick interval
7110 function bestConstrainedInterval(min, max, nttarget) {
7111 // run through possible number to ticks and see which interval is best
7112 var low = Math.floor(nttarget/2);
7113 var hi = Math.ceil(nttarget*1.5);
7114 var badness = Number.MAX_VALUE;
7115 var r = (max - min);
7116 var temp;
7117 var sd;
7118 var bestNT;
7119 var gsf = $.jqplot.getSignificantFigures;
7120 var fsd;
7121 var fs;
7122 var currentNT;
7123 var bestPrec;
7124
7125 for (var i=0, l=hi-low+1; i<l; i++) {
7126 currentNT = low + i;
7127 temp = r/(currentNT-1);
7128 sd = gsf(temp);
7129
7130 temp = Math.abs(nttarget - currentNT) + sd.digitsRight;
7131 if (temp < badness) {
7132 badness = temp;
7133 bestNT = currentNT;
7134 bestPrec = sd.digitsRight;
7135 }
7136 else if (temp === badness) {
7137 // let nicer ticks trump number ot ticks
7138 if (sd.digitsRight < bestPrec) {
7139 bestNT = currentNT;
7140 bestPrec = sd.digitsRight;
7141 }
7142 }
7143
7144 }
7145
7146 fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight));
7147 if (fsd === 0) {
7148 fs = '%d';
7149 }
7150 else {
7151 fs = '%.' + fsd + 'f';
7152 }
7153 temp = r / (bestNT - 1);
7154 // min, max, number ticks, format string, tick interval
7155 return [min, max, bestNT, fs, temp];
7156 }
7157
7158 // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
7159 // it is based soley on the range and number of ticks. So if user specifies
7160 // number of ticks, use this.
7161 function bestInterval(range, numberTicks) {
7162 numberTicks = numberTicks || 7;
7163 var minimum = range / (numberTicks - 1);
7164 var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10));
7165 var residual = minimum / magnitude;
7166 var interval;
7167 // "nicest" ranges are 1, 2, 5 or powers of these.
7168 // for magnitudes below 1, only allow these.
7169 if (magnitude < 1) {
7170 if (residual > 5) {
7171 interval = 10 * magnitude;
7172 }
7173 else if (residual > 2) {
7174 interval = 5 * magnitude;
7175 }
7176 else if (residual > 1) {
7177 interval = 2 * magnitude;
7178 }
7179 else {
7180 interval = magnitude;
7181 }
7182 }
7183 // for large ranges (whole integers), allow intervals like 3, 4 or powers of these.
7184 // this helps a lot with poor choices for number of ticks.
7185 else {
7186 if (residual > 5) {
7187 interval = 10 * magnitude;
7188 }
7189 else if (residual > 4) {
7190 interval = 5 * magnitude;
7191 }
7192 else if (residual > 3) {
7193 interval = 4 * magnitude;
7194 }
7195 else if (residual > 2) {
7196 interval = 3 * magnitude;
7197 }
7198 else if (residual > 1) {
7199 interval = 2 * magnitude;
7200 }
7201 else {
7202 interval = magnitude;
7203 }
7204 }
7205
7206 return interval;
7207 }
7208
7209 // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n
7210 // it is based soley on the range of data, number of ticks must be computed later.
7211 function bestLinearInterval(range, scalefact) {
7212 scalefact = scalefact || 1;
7213 var expv = Math.floor(Math.log(range)/Math.LN10);
7214 var magnitude = Math.pow(10, expv);
7215 // 0 < f < 10
7216 var f = range / magnitude;
7217 var fact;
7218 // for large plots, scalefact will decrease f and increase number of ticks.
7219 // for small plots, scalefact will increase f and decrease number of ticks.
7220 f = f/scalefact;
7221
7222 // for large plots, smaller interval, more ticks.
7223 if (f<=0.38) {
7224 fact = 0.1;
7225 }
7226 else if (f<=1.6) {
7227 fact = 0.2;
7228 }
7229 else if (f<=4.0) {
7230 fact = 0.5;
7231 }
7232 else if (f<=8.0) {
7233 fact = 1.0;
7234 }
7235 // for very small plots, larger interval, less ticks in number ticks
7236 else if (f<=16.0) {
7237 fact = 2;
7238 }
7239 else {
7240 fact = 5;
7241 }
7242
7243 return fact*magnitude;
7244 }
7245
7246 function bestLinearComponents(range, scalefact) {
7247 var expv = Math.floor(Math.log(range)/Math.LN10);
7248 var magnitude = Math.pow(10, expv);
7249 // 0 < f < 10
7250 var f = range / magnitude;
7251 var interval;
7252 var fact;
7253 // for large plots, scalefact will decrease f and increase number of ticks.
7254 // for small plots, scalefact will increase f and decrease number of ticks.
7255 f = f/scalefact;
7256
7257 // for large plots, smaller interval, more ticks.
7258 if (f<=0.38) {
7259 fact = 0.1;
7260 }
7261 else if (f<=1.6) {
7262 fact = 0.2;
7263 }
7264 else if (f<=4.0) {
7265 fact = 0.5;
7266 }
7267 else if (f<=8.0) {
7268 fact = 1.0;
7269 }
7270 // for very small plots, larger interval, less ticks in number ticks
7271 else if (f<=16.0) {
7272 fact = 2;
7273 }
7274 // else if (f<=20.0) {
7275 // fact = 3;
7276 // }
7277 // else if (f<=24.0) {
7278 // fact = 4;
7279 // }
7280 else {
7281 fact = 5;
7282 }
7283
7284 interval = fact * magnitude;
7285
7286 return [interval, fact, magnitude];
7287 }
7288
7289 // Given the min and max for a dataset, return suitable endpoints
7290 // for the graphing, a good number for the number of ticks, and a
7291 // format string so that extraneous digits are not displayed.
7292 // returned is an array containing [min, max, nTicks, format]
7293 $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) {
7294 // Set to preserve EITHER min OR max.
7295 // If min is preserved, max must be free.
7296 keepMin = (keepMin === null) ? false : keepMin;
7297 keepMax = (keepMax === null || keepMin) ? false : keepMax;
7298 // if endpoints are equal try to include zero otherwise include one
7299 if (axis_min === axis_max) {
7300 axis_max = (axis_max) ? 0 : 1;
7301 }
7302
7303 scalefact = scalefact || 1.0;
7304
7305 // make sure range is positive
7306 if (axis_max < axis_min) {
7307 var a = axis_max;
7308 axis_max = axis_min;
7309 axis_min = a;
7310 }
7311
7312 var r = [];
7313 var ss = bestLinearInterval(axis_max - axis_min, scalefact);
7314
7315 var gsf = $.jqplot.getSignificantFigures;
7316
7317 if (numberTicks == null) {
7318
7319 // Figure out the axis min, max and number of ticks
7320 // the min and max will be some multiple of the tick interval,
7321 // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
7322 // axis min is negative, 0 will be a tick.
7323 if (!keepMin && !keepMax) {
7324 r[0] = Math.floor(axis_min / ss) * ss; // min
7325 r[1] = Math.ceil(axis_max / ss) * ss; // max
7326 r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks
7327 r[3] = bestFormatString(ss); // format string
7328 r[4] = ss; // tick Interval
7329 }
7330
7331 else if (keepMin) {
7332 r[0] = axis_min; // min
7333 r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
7334 r[1] = axis_min + (r[2] - 1) * ss; // max
7335 var digitsMin = gsf(axis_min).digitsRight;
7336 var digitsSS = gsf(ss).digitsRight;
7337 if (digitsMin < digitsSS) {
7338 r[3] = bestFormatString(ss); // format string
7339 }
7340 else {
7341 r[3] = '%.' + digitsMin + 'f';
7342 }
7343 r[4] = ss; // tick Interval
7344 }
7345
7346 else if (keepMax) {
7347 r[1] = axis_max; // max
7348 r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks
7349 r[0] = axis_max - (r[2] - 1) * ss; // min
7350 var digitsMax = gsf(axis_max).digitsRight;
7351 var digitsSS = gsf(ss).digitsRight;
7352 if (digitsMax < digitsSS) {
7353 r[3] = bestFormatString(ss); // format string
7354 }
7355 else {
7356 r[3] = '%.' + digitsMax + 'f';
7357 }
7358 r[4] = ss; // tick Interval
7359 }
7360 }
7361
7362 else {
7363 var tempr = [];
7364
7365 // Figure out the axis min, max and number of ticks
7366 // the min and max will be some multiple of the tick interval,
7367 // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the
7368 // axis min is negative, 0 will be a tick.
7369 tempr[0] = Math.floor(axis_min / ss) * ss; // min
7370 tempr[1] = Math.ceil(axis_max / ss) * ss; // max
7371 tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks
7372 tempr[3] = bestFormatString(ss); // format string
7373 tempr[4] = ss; // tick Interval
7374
7375 // first, see if we happen to get the right number of ticks
7376 if (tempr[2] === numberTicks) {
7377 r = tempr;
7378 }
7379
7380 else {
7381
7382 var newti = bestInterval(tempr[1] - tempr[0], numberTicks);
7383
7384 r[0] = tempr[0]; // min
7385 r[2] = numberTicks; // number of ticks
7386 r[4] = newti; // tick interval
7387 r[3] = bestFormatString(newti); // format string
7388 r[1] = r[0] + (r[2] - 1) * r[4]; // max
7389 }
7390 }
7391
7392 return r;
7393 };
7394
7395 $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval;
7396 $.jqplot.LinearTickGenerator.bestInterval = bestInterval;
7397 $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents;
7398 $.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval;
7399
7400
7401 // class: $.jqplot.MarkerRenderer
7402 // The default jqPlot marker renderer, rendering the points on the line.
7403 $.jqplot.MarkerRenderer = function(options){
7404 // Group: Properties
7405
7406 // prop: show
7407 // whether or not to show the marker.
7408 this.show = true;
7409 // prop: style
7410 // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare
7411 this.style = 'filledCircle';
7412 // prop: lineWidth
7413 // size of the line for non-filled markers.
7414 this.lineWidth = 2;
7415 // prop: size
7416 // Size of the marker (diameter or circle, length of edge of square, etc.)
7417 this.size = 9.0;
7418 // prop: color
7419 // color of marker. Will be set to color of series by default on init.
7420 this.color = '#666666';
7421 // prop: shadow
7422 // whether or not to draw a shadow on the line
7423 this.shadow = true;
7424 // prop: shadowAngle
7425 // Shadow angle in degrees
7426 this.shadowAngle = 45;
7427 // prop: shadowOffset
7428 // Shadow offset from line in pixels
7429 this.shadowOffset = 1;
7430 // prop: shadowDepth
7431 // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
7432 this.shadowDepth = 3;
7433 // prop: shadowAlpha
7434 // Alpha channel transparency of shadow. 0 = transparent.
7435 this.shadowAlpha = '0.07';
7436 // prop: shadowRenderer
7437 // Renderer that will draws the shadows on the marker.
7438 this.shadowRenderer = new $.jqplot.ShadowRenderer();
7439 // prop: shapeRenderer
7440 // Renderer that will draw the marker.
7441 this.shapeRenderer = new $.jqplot.ShapeRenderer();
7442
7443 $.extend(true, this, options);
7444 };
7445
7446 $.jqplot.MarkerRenderer.prototype.init = function(options) {
7447 $.extend(true, this, options);
7448 var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true};
7449 if (this.style.indexOf('filled') != -1) {
7450 sdopt.fill = true;
7451 }
7452 if (this.style.indexOf('ircle') != -1) {
7453 sdopt.isarc = true;
7454 sdopt.closePath = false;
7455 }
7456 this.shadowRenderer.init(sdopt);
7457
7458 var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true};
7459 if (this.style.indexOf('filled') != -1) {
7460 shopt.fill = true;
7461 }
7462 if (this.style.indexOf('ircle') != -1) {
7463 shopt.isarc = true;
7464 shopt.closePath = false;
7465 }
7466 this.shapeRenderer.init(shopt);
7467 };
7468
7469 $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) {
7470 var stretch = 1.2;
7471 var dx = this.size/2/stretch;
7472 var dy = this.size/2*stretch;
7473 var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]];
7474 if (this.shadow) {
7475 this.shadowRenderer.draw(ctx, points);
7476 }
7477 this.shapeRenderer.draw(ctx, points, options);
7478 };
7479
7480 $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) {
7481 var stretch = 1.0;
7482 var dx = this.size/2*stretch;
7483 var dy = this.size/2*stretch;
7484 var points1 = [[x, y-dy], [x, y+dy]];
7485 var points2 = [[x+dx, y], [x-dx, y]];
7486 var opts = $.extend(true, {}, this.options, {closePath:false});
7487 if (this.shadow) {
7488 this.shadowRenderer.draw(ctx, points1, {closePath:false});
7489 this.shadowRenderer.draw(ctx, points2, {closePath:false});
7490 }
7491 this.shapeRenderer.draw(ctx, points1, opts);
7492 this.shapeRenderer.draw(ctx, points2, opts);
7493 };
7494
7495 $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) {
7496 var stretch = 1.0;
7497 var dx = this.size/2*stretch;
7498 var dy = this.size/2*stretch;
7499 var opts = $.extend(true, {}, this.options, {closePath:false});
7500 var points1 = [[x-dx, y-dy], [x+dx, y+dy]];
7501 var points2 = [[x-dx, y+dy], [x+dx, y-dy]];
7502 if (this.shadow) {
7503 this.shadowRenderer.draw(ctx, points1, {closePath:false});
7504 this.shadowRenderer.draw(ctx, points2, {closePath:false});
7505 }
7506 this.shapeRenderer.draw(ctx, points1, opts);
7507 this.shapeRenderer.draw(ctx, points2, opts);
7508 };
7509
7510 $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) {
7511 var stretch = 1.0;
7512 var dx = this.size/2*stretch;
7513 var dy = this.size/2*stretch;
7514 var points = [[x-dx, y], [x+dx, y]];
7515 if (this.shadow) {
7516 this.shadowRenderer.draw(ctx, points);
7517 }
7518 this.shapeRenderer.draw(ctx, points, options);
7519 };
7520
7521 $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) {
7522 var points = [p1, p2];
7523 if (this.shadow) {
7524 this.shadowRenderer.draw(ctx, points);
7525 }
7526 this.shapeRenderer.draw(ctx, points, options);
7527 };
7528
7529 $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) {
7530 var stretch = 1.0;
7531 var dx = this.size/2/stretch;
7532 var dy = this.size/2*stretch;
7533 var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]];
7534 if (this.shadow) {
7535 this.shadowRenderer.draw(ctx, points);
7536 }
7537 this.shapeRenderer.draw(ctx, points, options);
7538 };
7539
7540 $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) {
7541 var radius = this.size/2;
7542 var end = 2*Math.PI;
7543 var points = [x, y, radius, 0, end, true];
7544 if (this.shadow) {
7545 this.shadowRenderer.draw(ctx, points);
7546 }
7547 this.shapeRenderer.draw(ctx, points, options);
7548 };
7549
7550 $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) {
7551 options = options || {};
7552 // hack here b/c shape renderer uses canvas based color style options
7553 // and marker uses css style names.
7554 if (options.show == null || options.show != false) {
7555 if (options.color && !options.fillStyle) {
7556 options.fillStyle = options.color;
7557 }
7558 if (options.color && !options.strokeStyle) {
7559 options.strokeStyle = options.color;
7560 }
7561 switch (this.style) {
7562 case 'diamond':
7563 this.drawDiamond(x,y,ctx, false, options);
7564 break;
7565 case 'filledDiamond':
7566 this.drawDiamond(x,y,ctx, true, options);
7567 break;
7568 case 'circle':
7569 this.drawCircle(x,y,ctx, false, options);
7570 break;
7571 case 'filledCircle':
7572 this.drawCircle(x,y,ctx, true, options);
7573 break;
7574 case 'square':
7575 this.drawSquare(x,y,ctx, false, options);
7576 break;
7577 case 'filledSquare':
7578 this.drawSquare(x,y,ctx, true, options);
7579 break;
7580 case 'x':
7581 this.drawX(x,y,ctx, true, options);
7582 break;
7583 case 'plus':
7584 this.drawPlus(x,y,ctx, true, options);
7585 break;
7586 case 'dash':
7587 this.drawDash(x,y,ctx, true, options);
7588 break;
7589 case 'line':
7590 this.drawLine(x, y, ctx, false, options);
7591 break;
7592 default:
7593 this.drawDiamond(x,y,ctx, false, options);
7594 break;
7595 }
7596 }
7597 };
7598
7599 // class: $.jqplot.shadowRenderer
7600 // The default jqPlot shadow renderer, rendering shadows behind shapes.
7601 $.jqplot.ShadowRenderer = function(options){
7602 // Group: Properties
7603
7604 // prop: angle
7605 // Angle of the shadow in degrees. Measured counter-clockwise from the x axis.
7606 this.angle = 45;
7607 // prop: offset
7608 // Pixel offset at the given shadow angle of each shadow stroke from the last stroke.
7609 this.offset = 1;
7610 // prop: alpha
7611 // alpha transparency of shadow stroke.
7612 this.alpha = 0.07;
7613 // prop: lineWidth
7614 // width of the shadow line stroke.
7615 this.lineWidth = 1.5;
7616 // prop: lineJoin
7617 // How line segments of the shadow are joined.
7618 this.lineJoin = 'miter';
7619 // prop: lineCap
7620 // how ends of the shadow line are rendered.
7621 this.lineCap = 'round';
7622 // prop; closePath
7623 // whether line path segment is closed upon itself.
7624 this.closePath = false;
7625 // prop: fill
7626 // whether to fill the shape.
7627 this.fill = false;
7628 // prop: depth
7629 // how many times the shadow is stroked. Each stroke will be offset by offset at angle degrees.
7630 this.depth = 3;
7631 this.strokeStyle = 'rgba(0,0,0,0.1)';
7632 // prop: isarc
7633 // whether the shadow is an arc or not.
7634 this.isarc = false;
7635
7636 $.extend(true, this, options);
7637 };
7638
7639 $.jqplot.ShadowRenderer.prototype.init = function(options) {
7640 $.extend(true, this, options);
7641 };
7642
7643 // function: draw
7644 // draws an transparent black (i.e. gray) shadow.
7645 //
7646 // ctx - canvas drawing context
7647 // points - array of points or [x, y, radius, start angle (rad), end angle (rad)]
7648 $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) {
7649 ctx.save();
7650 var opts = (options != null) ? options : {};
7651 var fill = (opts.fill != null) ? opts.fill : this.fill;
7652 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
7653 var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
7654 var offset = (opts.offset != null) ? opts.offset : this.offset;
7655 var alpha = (opts.alpha != null) ? opts.alpha : this.alpha;
7656 var depth = (opts.depth != null) ? opts.depth : this.depth;
7657 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
7658 var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
7659 ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth;
7660 ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin;
7661 ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap;
7662 ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')';
7663 ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')';
7664 for (var j=0; j<depth; j++) {
7665 var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
7666 ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset);
7667 ctxPattern.beginPath();
7668 if (isarc) {
7669 ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
7670 }
7671 else if (fillRect) {
7672 if (fillRect) {
7673 ctx.fillRect(points[0], points[1], points[2], points[3]);
7674 }
7675 }
7676 else if (points && points.length){
7677 var move = true;
7678 for (var i=0; i<points.length; i++) {
7679 // skip to the first non-null point and move to it.
7680 if (points[i][0] != null && points[i][1] != null) {
7681 if (move) {
7682 ctxPattern.moveTo(points[i][0], points[i][1]);
7683 move = false;
7684 }
7685 else {
7686 ctxPattern.lineTo(points[i][0], points[i][1]);
7687 }
7688 }
7689 else {
7690 move = true;
7691 }
7692 }
7693
7694 }
7695 if (closePath) {
7696 ctxPattern.closePath();
7697 }
7698 if (fill) {
7699 ctx.fill();
7700 }
7701 else {
7702 ctx.stroke();
7703 }
7704 }
7705 ctx.restore();
7706 };
7707
7708 // class: $.jqplot.shapeRenderer
7709 // The default jqPlot shape renderer. Given a set of points will
7710 // plot them and either stroke a line (fill = false) or fill them (fill = true).
7711 // If a filled shape is desired, closePath = true must also be set to close
7712 // the shape.
7713 $.jqplot.ShapeRenderer = function(options){
7714
7715 this.lineWidth = 1.5;
7716 // prop: linePattern
7717 // line pattern 'dashed', 'dotted', 'solid', some combination
7718 // of '-' and '.' characters such as '.-.' or a numerical array like
7719 // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line,
7720 // [1, 10, 20, 10] to draw a dot-dash line, and so on.
7721 this.linePattern = 'solid';
7722 // prop: lineJoin
7723 // How line segments of the shadow are joined.
7724 this.lineJoin = 'miter';
7725 // prop: lineCap
7726 // how ends of the shadow line are rendered.
7727 this.lineCap = 'round';
7728 // prop; closePath
7729 // whether line path segment is closed upon itself.
7730 this.closePath = false;
7731 // prop: fill
7732 // whether to fill the shape.
7733 this.fill = false;
7734 // prop: isarc
7735 // whether the shadow is an arc or not.
7736 this.isarc = false;
7737 // prop: fillRect
7738 // true to draw shape as a filled rectangle.
7739 this.fillRect = false;
7740 // prop: strokeRect
7741 // true to draw shape as a stroked rectangle.
7742 this.strokeRect = false;
7743 // prop: clearRect
7744 // true to cear a rectangle.
7745 this.clearRect = false;
7746 // prop: strokeStyle
7747 // css color spec for the stoke style
7748 this.strokeStyle = '#999999';
7749 // prop: fillStyle
7750 // css color spec for the fill style.
7751 this.fillStyle = '#999999';
7752
7753 $.extend(true, this, options);
7754 };
7755
7756 $.jqplot.ShapeRenderer.prototype.init = function(options) {
7757 $.extend(true, this, options);
7758 };
7759
7760 // function: draw
7761 // draws the shape.
7762 //
7763 // ctx - canvas drawing context
7764 // points - array of points for shapes or
7765 // [x, y, width, height] for rectangles or
7766 // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs.
7767 $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) {
7768 ctx.save();
7769 var opts = (options != null) ? options : {};
7770 var fill = (opts.fill != null) ? opts.fill : this.fill;
7771 var closePath = (opts.closePath != null) ? opts.closePath : this.closePath;
7772 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect;
7773 var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect;
7774 var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect;
7775 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc;
7776 var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern;
7777 var ctxPattern = $.jqplot.LinePattern(ctx, linePattern);
7778 ctx.lineWidth = opts.lineWidth || this.lineWidth;
7779 ctx.lineJoin = opts.lineJoin || this.lineJoin;
7780 ctx.lineCap = opts.lineCap || this.lineCap;
7781 ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle;
7782 ctx.fillStyle = opts.fillStyle || this.fillStyle;
7783 ctx.beginPath();
7784 if (isarc) {
7785 ctx.arc(points[0], points[1], points[2], points[3], points[4], true);
7786 if (closePath) {
7787 ctx.closePath();
7788 }
7789 if (fill) {
7790 ctx.fill();
7791 }
7792 else {
7793 ctx.stroke();
7794 }
7795 ctx.restore();
7796 return;
7797 }
7798 else if (clearRect) {
7799 ctx.clearRect(points[0], points[1], points[2], points[3]);
7800 ctx.restore();
7801 return;
7802 }
7803 else if (fillRect || strokeRect) {
7804 if (fillRect) {
7805 ctx.fillRect(points[0], points[1], points[2], points[3]);
7806 }
7807 if (strokeRect) {
7808 ctx.strokeRect(points[0], points[1], points[2], points[3]);
7809 ctx.restore();
7810 return;
7811 }
7812 }
7813 else if (points && points.length){
7814 var move = true;
7815 for (var i=0; i<points.length; i++) {
7816 // skip to the first non-null point and move to it.
7817 if (points[i][0] != null && points[i][1] != null) {
7818 if (move) {
7819 ctxPattern.moveTo(points[i][0], points[i][1]);
7820 move = false;
7821 }
7822 else {
7823 ctxPattern.lineTo(points[i][0], points[i][1]);
7824 }
7825 }
7826 else {
7827 move = true;
7828 }
7829 }
7830 if (closePath) {
7831 ctxPattern.closePath();
7832 }
7833 if (fill) {
7834 ctx.fill();
7835 }
7836 else {
7837 ctx.stroke();
7838 }
7839 }
7840 ctx.restore();
7841 };
7842
7843 // class $.jqplot.TableLegendRenderer
7844 // The default legend renderer for jqPlot.
7845 $.jqplot.TableLegendRenderer = function(){
7846 //
7847 };
7848
7849 $.jqplot.TableLegendRenderer.prototype.init = function(options) {
7850 $.extend(true, this, options);
7851 };
7852
7853 $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) {
7854 var rs = (pad) ? this.rowSpacing+'px' : '0px';
7855 var tr;
7856 var td;
7857 var elem;
7858 var div0;
7859 var div1;
7860 elem = document.createElement('tr');
7861 tr = $(elem);
7862 tr.addClass('jqplot-table-legend');
7863 elem = null;
7864
7865 if (reverse){
7866 tr.prependTo(this._elem);
7867 }
7868
7869 else{
7870 tr.appendTo(this._elem);
7871 }
7872
7873 if (this.showSwatches) {
7874 td = $(document.createElement('td'));
7875 td.addClass('jqplot-table-legend jqplot-table-legend-swatch');
7876 td.css({textAlign: 'center', paddingTop: rs});
7877
7878 div0 = $(document.createElement('div'));
7879 div0.addClass('jqplot-table-legend-swatch-outline');
7880 div1 = $(document.createElement('div'));
7881 div1.addClass('jqplot-table-legend-swatch');
7882 div1.css({backgroundColor: color, borderColor: color});
7883
7884 tr.append(td.append(div0.append(div1)));
7885
7886 // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+
7887 // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+
7888 // '</div></td>').appendTo(tr);
7889 }
7890 if (this.showLabels) {
7891 td = $(document.createElement('td'));
7892 td.addClass('jqplot-table-legend jqplot-table-legend-label');
7893 td.css('paddingTop', rs);
7894 tr.append(td);
7895
7896 // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>');
7897 // elem.appendTo(tr);
7898 if (this.escapeHtml) {
7899 td.text(label);
7900 }
7901 else {
7902 td.html(label);
7903 }
7904 }
7905 td = null;
7906 div0 = null;
7907 div1 = null;
7908 tr = null;
7909 elem = null;
7910 };
7911
7912 // called with scope of legend
7913 $.jqplot.TableLegendRenderer.prototype.draw = function() {
7914 if (this._elem) {
7915 this._elem.emptyForce();
7916 this._elem = null;
7917 }
7918
7919 if (this.show) {
7920 var series = this._series;
7921 // make a table. one line label per row.
7922 var elem = document.createElement('table');
7923 this._elem = $(elem);
7924 this._elem.addClass('jqplot-table-legend');
7925
7926 var ss = {position:'absolute'};
7927 if (this.background) {
7928 ss['background'] = this.background;
7929 }
7930 if (this.border) {
7931 ss['border'] = this.border;
7932 }
7933 if (this.fontSize) {
7934 ss['fontSize'] = this.fontSize;
7935 }
7936 if (this.fontFamily) {
7937 ss['fontFamily'] = this.fontFamily;
7938 }
7939 if (this.textColor) {
7940 ss['textColor'] = this.textColor;
7941 }
7942 if (this.marginTop != null) {
7943 ss['marginTop'] = this.marginTop;
7944 }
7945 if (this.marginBottom != null) {
7946 ss['marginBottom'] = this.marginBottom;
7947 }
7948 if (this.marginLeft != null) {
7949 ss['marginLeft'] = this.marginLeft;
7950 }
7951 if (this.marginRight != null) {
7952 ss['marginRight'] = this.marginRight;
7953 }
7954
7955
7956 var pad = false,
7957 reverse = false,
7958 s;
7959 for (var i = 0; i< series.length; i++) {
7960 s = series[i];
7961 if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){
7962 reverse = true;
7963 }
7964 if (s.show && s.showLabel) {
7965 var lt = this.labels[i] || s.label.toString();
7966 if (lt) {
7967 var color = s.color;
7968 if (reverse && i < series.length - 1){
7969 pad = true;
7970 }
7971 else if (reverse && i == series.length - 1){
7972 pad = false;
7973 }
7974 this.renderer.addrow.call(this, lt, color, pad, reverse);
7975 pad = true;
7976 }
7977 // let plugins add more rows to legend. Used by trend line plugin.
7978 for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
7979 var item = $.jqplot.addLegendRowHooks[j].call(this, s);
7980 if (item) {
7981 this.renderer.addrow.call(this, item.label, item.color, pad);
7982 pad = true;
7983 }
7984 }
7985 lt = null;
7986 }
7987 }
7988 }
7989 return this._elem;
7990 };
7991
7992 $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) {
7993 if (this.show) {
7994 if (this.placement == 'insideGrid') {
7995 switch (this.location) {
7996 case 'nw':
7997 var a = offsets.left;
7998 var b = offsets.top;
7999 this._elem.css('left', a);
8000 this._elem.css('top', b);
8001 break;
8002 case 'n':
8003 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8004 var b = offsets.top;
8005 this._elem.css('left', a);
8006 this._elem.css('top', b);
8007 break;
8008 case 'ne':
8009 var a = offsets.right;
8010 var b = offsets.top;
8011 this._elem.css({right:a, top:b});
8012 break;
8013 case 'e':
8014 var a = offsets.right;
8015 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8016 this._elem.css({right:a, top:b});
8017 break;
8018 case 'se':
8019 var a = offsets.right;
8020 var b = offsets.bottom;
8021 this._elem.css({right:a, bottom:b});
8022 break;
8023 case 's':
8024 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8025 var b = offsets.bottom;
8026 this._elem.css({left:a, bottom:b});
8027 break;
8028 case 'sw':
8029 var a = offsets.left;
8030 var b = offsets.bottom;
8031 this._elem.css({left:a, bottom:b});
8032 break;
8033 case 'w':
8034 var a = offsets.left;
8035 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8036 this._elem.css({left:a, top:b});
8037 break;
8038 default: // same as 'se'
8039 var a = offsets.right;
8040 var b = offsets.bottom;
8041 this._elem.css({right:a, bottom:b});
8042 break;
8043 }
8044
8045 }
8046 else if (this.placement == 'outside'){
8047 switch (this.location) {
8048 case 'nw':
8049 var a = this._plotDimensions.width - offsets.left;
8050 var b = offsets.top;
8051 this._elem.css('right', a);
8052 this._elem.css('top', b);
8053 break;
8054 case 'n':
8055 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8056 var b = this._plotDimensions.height - offsets.top;
8057 this._elem.css('left', a);
8058 this._elem.css('bottom', b);
8059 break;
8060 case 'ne':
8061 var a = this._plotDimensions.width - offsets.right;
8062 var b = offsets.top;
8063 this._elem.css({left:a, top:b});
8064 break;
8065 case 'e':
8066 var a = this._plotDimensions.width - offsets.right;
8067 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8068 this._elem.css({left:a, top:b});
8069 break;
8070 case 'se':
8071 var a = this._plotDimensions.width - offsets.right;
8072 var b = offsets.bottom;
8073 this._elem.css({left:a, bottom:b});
8074 break;
8075 case 's':
8076 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8077 var b = this._plotDimensions.height - offsets.bottom;
8078 this._elem.css({left:a, top:b});
8079 break;
8080 case 'sw':
8081 var a = this._plotDimensions.width - offsets.left;
8082 var b = offsets.bottom;
8083 this._elem.css({right:a, bottom:b});
8084 break;
8085 case 'w':
8086 var a = this._plotDimensions.width - offsets.left;
8087 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8088 this._elem.css({right:a, top:b});
8089 break;
8090 default: // same as 'se'
8091 var a = offsets.right;
8092 var b = offsets.bottom;
8093 this._elem.css({right:a, bottom:b});
8094 break;
8095 }
8096 }
8097 else {
8098 switch (this.location) {
8099 case 'nw':
8100 this._elem.css({left:0, top:offsets.top});
8101 break;
8102 case 'n':
8103 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8104 this._elem.css({left: a, top:offsets.top});
8105 break;
8106 case 'ne':
8107 this._elem.css({right:0, top:offsets.top});
8108 break;
8109 case 'e':
8110 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8111 this._elem.css({right:offsets.right, top:b});
8112 break;
8113 case 'se':
8114 this._elem.css({right:offsets.right, bottom:offsets.bottom});
8115 break;
8116 case 's':
8117 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2;
8118 this._elem.css({left: a, bottom:offsets.bottom});
8119 break;
8120 case 'sw':
8121 this._elem.css({left:offsets.left, bottom:offsets.bottom});
8122 break;
8123 case 'w':
8124 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2;
8125 this._elem.css({left:offsets.left, top:b});
8126 break;
8127 default: // same as 'se'
8128 this._elem.css({right:offsets.right, bottom:offsets.bottom});
8129 break;
8130 }
8131 }
8132 }
8133 };
8134
8135 /**
8136 * Class: $.jqplot.ThemeEngine
8137 * Theme Engine provides a programatic way to change some of the more
8138 * common jqplot styling options such as fonts, colors and grid options.
8139 * A theme engine instance is created with each plot. The theme engine
8140 * manages a collection of themes which can be modified, added to, or
8141 * applied to the plot.
8142 *
8143 * The themeEngine class is not instantiated directly.
8144 * When a plot is initialized, the current plot options are scanned
8145 * an a default theme named "Default" is created. This theme is
8146 * used as the basis for other themes added to the theme engine and
8147 * is always available.
8148 *
8149 * A theme is a simple javascript object with styling parameters for
8150 * various entities of the plot. A theme has the form:
8151 *
8152 *
8153 * > {
8154 * > _name:f "Default",
8155 * > target: {
8156 * > backgroundColor: "transparent"
8157 * > },
8158 * > legend: {
8159 * > textColor: null,
8160 * > fontFamily: null,
8161 * > fontSize: null,
8162 * > border: null,
8163 * > background: null
8164 * > },
8165 * > title: {
8166 * > textColor: "rgb(102, 102, 102)",
8167 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
8168 * > fontSize: "19.2px",
8169 * > textAlign: "center"
8170 * > },
8171 * > seriesStyles: {},
8172 * > series: [{
8173 * > color: "#4bb2c5",
8174 * > lineWidth: 2.5,
8175 * > linePattern: "solid",
8176 * > shadow: true,
8177 * > fillColor: "#4bb2c5",
8178 * > showMarker: true,
8179 * > markerOptions: {
8180 * > color: "#4bb2c5",
8181 * > show: true,
8182 * > style: 'filledCircle',
8183 * > lineWidth: 1.5,
8184 * > size: 4,
8185 * > shadow: true
8186 * > }
8187 * > }],
8188 * > grid: {
8189 * > drawGridlines: true,
8190 * > gridLineColor: "#cccccc",
8191 * > gridLineWidth: 1,
8192 * > backgroundColor: "#fffdf6",
8193 * > borderColor: "#999999",
8194 * > borderWidth: 2,
8195 * > shadow: true
8196 * > },
8197 * > axesStyles: {
8198 * > label: {},
8199 * > ticks: {}
8200 * > },
8201 * > axes: {
8202 * > xaxis: {
8203 * > borderColor: "#999999",
8204 * > borderWidth: 2,
8205 * > ticks: {
8206 * > show: true,
8207 * > showGridline: true,
8208 * > showLabel: true,
8209 * > showMark: true,
8210 * > size: 4,
8211 * > textColor: "",
8212 * > whiteSpace: "nowrap",
8213 * > fontSize: "12px",
8214 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
8215 * > },
8216 * > label: {
8217 * > textColor: "rgb(102, 102, 102)",
8218 * > whiteSpace: "normal",
8219 * > fontSize: "14.6667px",
8220 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif",
8221 * > fontWeight: "400"
8222 * > }
8223 * > },
8224 * > yaxis: {
8225 * > borderColor: "#999999",
8226 * > borderWidth: 2,
8227 * > ticks: {
8228 * > show: true,
8229 * > showGridline: true,
8230 * > showLabel: true,
8231 * > showMark: true,
8232 * > size: 4,
8233 * > textColor: "",
8234 * > whiteSpace: "nowrap",
8235 * > fontSize: "12px",
8236 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif"
8237 * > },
8238 * > label: {
8239 * > textColor: null,
8240 * > whiteSpace: null,
8241 * > fontSize: null,
8242 * > fontFamily: null,
8243 * > fontWeight: null
8244 * > }
8245 * > },
8246 * > x2axis: {...
8247 * > },
8248 * > ...
8249 * > y9axis: {...
8250 * > }
8251 * > }
8252 * > }
8253 *
8254 * "seriesStyles" is a style object that will be applied to all series in the plot.
8255 * It will forcibly override any styles applied on the individual series. "axesStyles" is
8256 * a style object that will be applied to all axes in the plot. It will also forcibly
8257 * override any styles on the individual axes.
8258 *
8259 * The example shown above has series options for a line series. Options for other
8260 * series types are shown below:
8261 *
8262 * Bar Series:
8263 *
8264 * > {
8265 * > color: "#4bb2c5",
8266 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
8267 * > lineWidth: 2.5,
8268 * > shadow: true,
8269 * > barPadding: 2,
8270 * > barMargin: 10,
8271 * > barWidth: 15.09375,
8272 * > 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)"]
8273 * > }
8274 *
8275 * Pie Series:
8276 *
8277 * > {
8278 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
8279 * > padding: 20,
8280 * > sliceMargin: 0,
8281 * > fill: true,
8282 * > shadow: true,
8283 * > startAngle: 0,
8284 * > lineWidth: 2.5,
8285 * > 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)"]
8286 * > }
8287 *
8288 * Funnel Series:
8289 *
8290 * > {
8291 * > color: "#4bb2c5",
8292 * > lineWidth: 2,
8293 * > shadow: true,
8294 * > padding: {
8295 * > top: 20,
8296 * > right: 20,
8297 * > bottom: 20,
8298 * > left: 20
8299 * > },
8300 * > sectionMargin: 6,
8301 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"],
8302 * > 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)"]
8303 * > }
8304 *
8305 */
8306 $.jqplot.ThemeEngine = function(){
8307 // Group: Properties
8308 //
8309 // prop: themes
8310 // hash of themes managed by the theme engine.
8311 // Indexed by theme name.
8312 this.themes = {};
8313 // prop: activeTheme
8314 // Pointer to currently active theme
8315 this.activeTheme=null;
8316
8317 };
8318
8319 // called with scope of plot
8320 $.jqplot.ThemeEngine.prototype.init = function() {
8321 // get the Default theme from the current plot settings.
8322 var th = new $.jqplot.Theme({_name:'Default'});
8323 var n, i, nn;
8324
8325 for (n in th.target) {
8326 if (n == "textColor") {
8327 th.target[n] = this.target.css('color');
8328 }
8329 else {
8330 th.target[n] = this.target.css(n);
8331 }
8332 }
8333
8334 if (this.title.show && this.title._elem) {
8335 for (n in th.title) {
8336 if (n == "textColor") {
8337 th.title[n] = this.title._elem.css('color');
8338 }
8339 else {
8340 th.title[n] = this.title._elem.css(n);
8341 }
8342 }
8343 }
8344
8345 for (n in th.grid) {
8346 th.grid[n] = this.grid[n];
8347 }
8348 if (th.grid.backgroundColor == null && this.grid.background != null) {
8349 th.grid.backgroundColor = this.grid.background;
8350 }
8351 if (this.legend.show && this.legend._elem) {
8352 for (n in th.legend) {
8353 if (n == 'textColor') {
8354 th.legend[n] = this.legend._elem.css('color');
8355 }
8356 else {
8357 th.legend[n] = this.legend._elem.css(n);
8358 }
8359 }
8360 }
8361 var s;
8362
8363 for (i=0; i<this.series.length; i++) {
8364 s = this.series[i];
8365 if (s.renderer.constructor == $.jqplot.LineRenderer) {
8366 th.series.push(new LineSeriesProperties());
8367 }
8368 else if (s.renderer.constructor == $.jqplot.BarRenderer) {
8369 th.series.push(new BarSeriesProperties());
8370 }
8371 else if (s.renderer.constructor == $.jqplot.PieRenderer) {
8372 th.series.push(new PieSeriesProperties());
8373 }
8374 else if (s.renderer.constructor == $.jqplot.DonutRenderer) {
8375 th.series.push(new DonutSeriesProperties());
8376 }
8377 else if (s.renderer.constructor == $.jqplot.FunnelRenderer) {
8378 th.series.push(new FunnelSeriesProperties());
8379 }
8380 else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) {
8381 th.series.push(new MeterSeriesProperties());
8382 }
8383 else {
8384 th.series.push({});
8385 }
8386 for (n in th.series[i]) {
8387 th.series[i][n] = s[n];
8388 }
8389 }
8390 var a, ax;
8391 for (n in this.axes) {
8392 ax = this.axes[n];
8393 a = th.axes[n] = new AxisProperties();
8394 a.borderColor = ax.borderColor;
8395 a.borderWidth = ax.borderWidth;
8396 if (ax._ticks && ax._ticks[0]) {
8397 for (nn in a.ticks) {
8398 if (ax._ticks[0].hasOwnProperty(nn)) {
8399 a.ticks[nn] = ax._ticks[0][nn];
8400 }
8401 else if (ax._ticks[0]._elem){
8402 a.ticks[nn] = ax._ticks[0]._elem.css(nn);
8403 }
8404 }
8405 }
8406 if (ax._label && ax._label.show) {
8407 for (nn in a.label) {
8408 // a.label[nn] = ax._label._elem.css(nn);
8409 if (ax._label[nn]) {
8410 a.label[nn] = ax._label[nn];
8411 }
8412 else if (ax._label._elem){
8413 if (nn == 'textColor') {
8414 a.label[nn] = ax._label._elem.css('color');
8415 }
8416 else {
8417 a.label[nn] = ax._label._elem.css(nn);
8418 }
8419 }
8420 }
8421 }
8422 }
8423 this.themeEngine._add(th);
8424 this.themeEngine.activeTheme = this.themeEngine.themes[th._name];
8425 };
8426 /**
8427 * Group: methods
8428 *
8429 * method: get
8430 *
8431 * Get and return the named theme or the active theme if no name given.
8432 *
8433 * parameter:
8434 *
8435 * name - name of theme to get.
8436 *
8437 * returns:
8438 *
8439 * Theme instance of given name.
8440 */
8441 $.jqplot.ThemeEngine.prototype.get = function(name) {
8442 if (!name) {
8443 // return the active theme
8444 return this.activeTheme;
8445 }
8446 else {
8447 return this.themes[name];
8448 }
8449 };
8450
8451 function numericalOrder(a,b) { return a-b; }
8452
8453 /**
8454 * method: getThemeNames
8455 *
8456 * Return the list of theme names in this manager in alpha-numerical order.
8457 *
8458 * parameter:
8459 *
8460 * None
8461 *
8462 * returns:
8463 *
8464 * A the list of theme names in this manager in alpha-numerical order.
8465 */
8466 $.jqplot.ThemeEngine.prototype.getThemeNames = function() {
8467 var tn = [];
8468 for (var n in this.themes) {
8469 tn.push(n);
8470 }
8471 return tn.sort(numericalOrder);
8472 };
8473
8474 /**
8475 * method: getThemes
8476 *
8477 * Return a list of themes in alpha-numerical order by name.
8478 *
8479 * parameter:
8480 *
8481 * None
8482 *
8483 * returns:
8484 *
8485 * A list of themes in alpha-numerical order by name.
8486 */
8487 $.jqplot.ThemeEngine.prototype.getThemes = function() {
8488 var tn = [];
8489 var themes = [];
8490 for (var n in this.themes) {
8491 tn.push(n);
8492 }
8493 tn.sort(numericalOrder);
8494 for (var i=0; i<tn.length; i++) {
8495 themes.push(this.themes[tn[i]]);
8496 }
8497 return themes;
8498 };
8499
8500 $.jqplot.ThemeEngine.prototype.activate = function(plot, name) {
8501 // sometimes need to redraw whole plot.
8502 var redrawPlot = false;
8503 if (!name && this.activeTheme && this.activeTheme._name) {
8504 name = this.activeTheme._name;
8505 }
8506 if (!this.themes.hasOwnProperty(name)) {
8507 throw new Error("No theme of that name");
8508 }
8509 else {
8510 var th = this.themes[name];
8511 this.activeTheme = th;
8512 var val, checkBorderColor = false, checkBorderWidth = false;
8513 var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis'];
8514
8515 for (i=0; i<arr.length; i++) {
8516 var ax = arr[i];
8517 if (th.axesStyles.borderColor != null) {
8518 plot.axes[ax].borderColor = th.axesStyles.borderColor;
8519 }
8520 if (th.axesStyles.borderWidth != null) {
8521 plot.axes[ax].borderWidth = th.axesStyles.borderWidth;
8522 }
8523 }
8524
8525 for (var axname in plot.axes) {
8526 var axis = plot.axes[axname];
8527 if (axis.show) {
8528 var thaxis = th.axes[axname] || {};
8529 var thaxstyle = th.axesStyles;
8530 var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle);
8531 val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor;
8532 if (thax.borderColor != null) {
8533 axis.borderColor = thax.borderColor;
8534 redrawPlot = true;
8535 }
8536 val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth;
8537 if (thax.borderWidth != null) {
8538 axis.borderWidth = thax.borderWidth;
8539 redrawPlot = true;
8540 }
8541 if (axis._ticks && axis._ticks[0]) {
8542 for (var nn in thax.ticks) {
8543 // val = null;
8544 // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) {
8545 // val = th.axesStyles.ticks[nn];
8546 // }
8547 // else if (thax.ticks[nn] != null){
8548 // val = thax.ticks[nn]
8549 // }
8550 val = thax.ticks[nn];
8551 if (val != null) {
8552 axis.tickOptions[nn] = val;
8553 axis._ticks = [];
8554 redrawPlot = true;
8555 }
8556 }
8557 }
8558 if (axis._label && axis._label.show) {
8559 for (var nn in thax.label) {
8560 // val = null;
8561 // if (th.axesStyles.label && th.axesStyles.label[nn] != null) {
8562 // val = th.axesStyles.label[nn];
8563 // }
8564 // else if (thax.label && thax.label[nn] != null){
8565 // val = thax.label[nn]
8566 // }
8567 val = thax.label[nn];
8568 if (val != null) {
8569 axis.labelOptions[nn] = val;
8570 redrawPlot = true;
8571 }
8572 }
8573 }
8574
8575 }
8576 }
8577
8578 for (var n in th.grid) {
8579 if (th.grid[n] != null) {
8580 plot.grid[n] = th.grid[n];
8581 }
8582 }
8583 if (!redrawPlot) {
8584 plot.grid.draw();
8585 }
8586
8587 if (plot.legend.show) {
8588 for (n in th.legend) {
8589 if (th.legend[n] != null) {
8590 plot.legend[n] = th.legend[n];
8591 }
8592 }
8593 }
8594 if (plot.title.show) {
8595 for (n in th.title) {
8596 if (th.title[n] != null) {
8597 plot.title[n] = th.title[n];
8598 }
8599 }
8600 }
8601
8602 var i;
8603 for (i=0; i<th.series.length; i++) {
8604 var opts = {};
8605 var redrawSeries = false;
8606 for (n in th.series[i]) {
8607 val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n];
8608 if (val != null) {
8609 opts[n] = val;
8610 if (n == 'color') {
8611 plot.series[i].renderer.shapeRenderer.fillStyle = val;
8612 plot.series[i].renderer.shapeRenderer.strokeStyle = val;
8613 plot.series[i][n] = val;
8614 }
8615 else if ((n == 'lineWidth') || (n == 'linePattern')) {
8616 plot.series[i].renderer.shapeRenderer[n] = val;
8617 plot.series[i][n] = val;
8618 }
8619 else if (n == 'markerOptions') {
8620 merge (plot.series[i].markerOptions, val);
8621 merge (plot.series[i].markerRenderer, val);
8622 }
8623 else {
8624 plot.series[i][n] = val;
8625 }
8626 redrawPlot = true;
8627 }
8628 }
8629 }
8630
8631 if (redrawPlot) {
8632 plot.target.empty();
8633 plot.draw();
8634 }
8635
8636 for (n in th.target) {
8637 if (th.target[n] != null) {
8638 plot.target.css(n, th.target[n]);
8639 }
8640 }
8641 }
8642
8643 };
8644
8645 $.jqplot.ThemeEngine.prototype._add = function(theme, name) {
8646 if (name) {
8647 theme._name = name;
8648 }
8649 if (!theme._name) {
8650 theme._name = Date.parse(new Date());
8651 }
8652 if (!this.themes.hasOwnProperty(theme._name)) {
8653 this.themes[theme._name] = theme;
8654 }
8655 else {
8656 throw new Error("jqplot.ThemeEngine Error: Theme already in use");
8657 }
8658 };
8659
8660 // method remove
8661 // Delete the named theme, return true on success, false on failure.
8662
8663
8664 /**
8665 * method: remove
8666 *
8667 * Remove the given theme from the themeEngine.
8668 *
8669 * parameters:
8670 *
8671 * name - name of the theme to remove.
8672 *
8673 * returns:
8674 *
8675 * true on success, false on failure.
8676 */
8677 $.jqplot.ThemeEngine.prototype.remove = function(name) {
8678 if (name == 'Default') {
8679 return false;
8680 }
8681 return delete this.themes[name];
8682 };
8683
8684 /**
8685 * method: newTheme
8686 *
8687 * Create a new theme based on the default theme, adding it the themeEngine.
8688 *
8689 * parameters:
8690 *
8691 * name - name of the new theme.
8692 * obj - optional object of styles to be applied to this new theme.
8693 *
8694 * returns:
8695 *
8696 * new Theme object.
8697 */
8698 $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) {
8699 if (typeof(name) == 'object') {
8700 obj = obj || name;
8701 name = null;
8702 }
8703 if (obj && obj._name) {
8704 name = obj._name;
8705 }
8706 else {
8707 name = name || Date.parse(new Date());
8708 }
8709 // var th = new $.jqplot.Theme(name);
8710 var th = this.copy(this.themes['Default']._name, name);
8711 $.jqplot.extend(th, obj);
8712 return th;
8713 };
8714
8715 // function clone(obj) {
8716 // return eval(obj.toSource());
8717 // }
8718
8719 function clone(obj){
8720 if(obj == null || typeof(obj) != 'object'){
8721 return obj;
8722 }
8723
8724 var temp = new obj.constructor();
8725 for(var key in obj){
8726 temp[key] = clone(obj[key]);
8727 }
8728 return temp;
8729 }
8730
8731 $.jqplot.clone = clone;
8732
8733 function merge(obj1, obj2) {
8734 if (obj2 == null || typeof(obj2) != 'object') {
8735 return;
8736 }
8737 for (var key in obj2) {
8738 if (key == 'highlightColors') {
8739 obj1[key] = clone(obj2[key]);
8740 }
8741 if (obj2[key] != null && typeof(obj2[key]) == 'object') {
8742 if (!obj1.hasOwnProperty(key)) {
8743 obj1[key] = {};
8744 }
8745 merge(obj1[key], obj2[key]);
8746 }
8747 else {
8748 obj1[key] = obj2[key];
8749 }
8750 }
8751 }
8752
8753 $.jqplot.merge = merge;
8754
8755 // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic
8756 $.jqplot.extend = function() {
8757 // copy reference to target object
8758 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
8759
8760 // Handle a deep copy situation
8761 if ( typeof target === "boolean" ) {
8762 deep = target;
8763 target = arguments[1] || {};
8764 // skip the boolean and the target
8765 i = 2;
8766 }
8767
8768 // Handle case when target is a string or something (possible in deep copy)
8769 if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) {
8770 target = {};
8771 }
8772
8773 for ( ; i < length; i++ ){
8774 // Only deal with non-null/undefined values
8775 if ( (options = arguments[ i ]) != null ) {
8776 // Extend the base object
8777 for ( var name in options ) {
8778 var src = target[ name ], copy = options[ name ];
8779
8780 // Prevent never-ending loop
8781 if ( target === copy ) {
8782 continue;
8783 }
8784
8785 // Recurse if we're merging object values
8786 if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
8787 target[ name ] = $.jqplot.extend( deep,
8788 // Never move original objects, clone them
8789 src || ( copy.length != null ? [ ] : { } )
8790 , copy );
8791 }
8792 // Don't bring in undefined values
8793 else if ( copy !== undefined ) {
8794 target[ name ] = copy;
8795 }
8796 }
8797 }
8798 }
8799 // Return the modified object
8800 return target;
8801 };
8802
8803 /**
8804 * method: rename
8805 *
8806 * Rename a theme.
8807 *
8808 * parameters:
8809 *
8810 * oldName - current name of the theme.
8811 * newName - desired name of the theme.
8812 *
8813 * returns:
8814 *
8815 * new Theme object.
8816 */
8817 $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) {
8818 if (oldName == 'Default' || newName == 'Default') {
8819 throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default");
8820 }
8821 if (this.themes.hasOwnProperty(newName)) {
8822 throw new Error ("jqplot.ThemeEngine Error: New name already in use.");
8823 }
8824 else if (this.themes.hasOwnProperty(oldName)) {
8825 var th = this.copy (oldName, newName);
8826 this.remove(oldName);
8827 return th;
8828 }
8829 throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid");
8830 };
8831
8832 /**
8833 * method: copy
8834 *
8835 * Create a copy of an existing theme in the themeEngine, adding it the themeEngine.
8836 *
8837 * parameters:
8838 *
8839 * sourceName - name of the existing theme.
8840 * targetName - name of the copy.
8841 * obj - optional object of style parameter to apply to the new theme.
8842 *
8843 * returns:
8844 *
8845 * new Theme object.
8846 */
8847 $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) {
8848 if (targetName == 'Default') {
8849 throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme");
8850 }
8851 if (!this.themes.hasOwnProperty(sourceName)) {
8852 var s = "jqplot.ThemeEngine Error: Source name invalid";
8853 throw new Error(s);
8854 }
8855 if (this.themes.hasOwnProperty(targetName)) {
8856 var s = "jqplot.ThemeEngine Error: Target name invalid";
8857 throw new Error(s);
8858 }
8859 else {
8860 var th = clone(this.themes[sourceName]);
8861 th._name = targetName;
8862 $.jqplot.extend(true, th, obj);
8863 this._add(th);
8864 return th;
8865 }
8866 };
8867
8868
8869 $.jqplot.Theme = function(name, obj) {
8870 if (typeof(name) == 'object') {
8871 obj = obj || name;
8872 name = null;
8873 }
8874 name = name || Date.parse(new Date());
8875 this._name = name;
8876 this.target = {
8877 backgroundColor: null
8878 };
8879 this.legend = {
8880 textColor: null,
8881 fontFamily: null,
8882 fontSize: null,
8883 border: null,
8884 background: null
8885 };
8886 this.title = {
8887 textColor: null,
8888 fontFamily: null,
8889 fontSize: null,
8890 textAlign: null
8891 };
8892 this.seriesStyles = {};
8893 this.series = [];
8894 this.grid = {
8895 drawGridlines: null,
8896 gridLineColor: null,
8897 gridLineWidth: null,
8898 backgroundColor: null,
8899 borderColor: null,
8900 borderWidth: null,
8901 shadow: null
8902 };
8903 this.axesStyles = {label:{}, ticks:{}};
8904 this.axes = {};
8905 if (typeof(obj) == 'string') {
8906 this._name = obj;
8907 }
8908 else if(typeof(obj) == 'object') {
8909 $.jqplot.extend(true, this, obj);
8910 }
8911 };
8912
8913 var AxisProperties = function() {
8914 this.borderColor = null;
8915 this.borderWidth = null;
8916 this.ticks = new AxisTicks();
8917 this.label = new AxisLabel();
8918 };
8919
8920 var AxisTicks = function() {
8921 this.show = null;
8922 this.showGridline = null;
8923 this.showLabel = null;
8924 this.showMark = null;
8925 this.size = null;
8926 this.textColor = null;
8927 this.whiteSpace = null;
8928 this.fontSize = null;
8929 this.fontFamily = null;
8930 };
8931
8932 var AxisLabel = function() {
8933 this.textColor = null;
8934 this.whiteSpace = null;
8935 this.fontSize = null;
8936 this.fontFamily = null;
8937 this.fontWeight = null;
8938 };
8939
8940 var LineSeriesProperties = function() {
8941 this.color=null;
8942 this.lineWidth=null;
8943 this.linePattern=null;
8944 this.shadow=null;
8945 this.fillColor=null;
8946 this.showMarker=null;
8947 this.markerOptions = new MarkerOptions();
8948 };
8949
8950 var MarkerOptions = function() {
8951 this.show = null;
8952 this.style = null;
8953 this.lineWidth = null;
8954 this.size = null;
8955 this.color = null;
8956 this.shadow = null;
8957 };
8958
8959 var BarSeriesProperties = function() {
8960 this.color=null;
8961 this.seriesColors=null;
8962 this.lineWidth=null;
8963 this.shadow=null;
8964 this.barPadding=null;
8965 this.barMargin=null;
8966 this.barWidth=null;
8967 this.highlightColors=null;
8968 };
8969
8970 var PieSeriesProperties = function() {
8971 this.seriesColors=null;
8972 this.padding=null;
8973 this.sliceMargin=null;
8974 this.fill=null;
8975 this.shadow=null;
8976 this.startAngle=null;
8977 this.lineWidth=null;
8978 this.highlightColors=null;
8979 };
8980
8981 var DonutSeriesProperties = function() {
8982 this.seriesColors=null;
8983 this.padding=null;
8984 this.sliceMargin=null;
8985 this.fill=null;
8986 this.shadow=null;
8987 this.startAngle=null;
8988 this.lineWidth=null;
8989 this.innerDiameter=null;
8990 this.thickness=null;
8991 this.ringMargin=null;
8992 this.highlightColors=null;
8993 };
8994
8995 var FunnelSeriesProperties = function() {
8996 this.color=null;
8997 this.lineWidth=null;
8998 this.shadow=null;
8999 this.padding=null;
9000 this.sectionMargin=null;
9001 this.seriesColors=null;
9002 this.highlightColors=null;
9003 };
9004
9005 var MeterSeriesProperties = function() {
9006 this.padding=null;
9007 this.backgroundColor=null;
9008 this.ringColor=null;
9009 this.tickColor=null;
9010 this.ringWidth=null;
9011 this.intervalColors=null;
9012 this.intervalInnerRadius=null;
9013 this.intervalOuterRadius=null;
9014 this.hubRadius=null;
9015 this.needleThickness=null;
9016 this.needlePad=null;
9017 };
9018
9019
9020
9021
9022 $.fn.jqplotChildText = function() {
9023 return $(this).contents().filter(function() {
9024 return this.nodeType == 3; // Node.TEXT_NODE not defined in I7
9025 }).text();
9026 };
9027
9028 // Returns font style as abbreviation for "font" property.
9029 $.fn.jqplotGetComputedFontStyle = function() {
9030 var css = window.getComputedStyle ? window.getComputedStyle(this[0], "") : this[0].currentStyle;
9031 var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily'];
9032 var style = [];
9033
9034 for (var i=0 ; i < attrs.length; ++i) {
9035 var attr = String(css[attrs[i]]);
9036
9037 if (attr && attr != 'normal') {
9038 style.push(attr);
9039 }
9040 }
9041 return style.join(' ');
9042 };
9043
9044 /**
9045 * Namespace: $.fn
9046 * jQuery namespace to attach functions to jQuery elements.
9047 *
9048 */
9049
9050 $.fn.jqplotToImageCanvas = function(options) {
9051
9052 options = options || {};
9053 var x_offset = (options.x_offset == null) ? 0 : options.x_offset;
9054 var y_offset = (options.y_offset == null) ? 0 : options.y_offset;
9055 var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor;
9056
9057 if ($(this).width() == 0 || $(this).height() == 0) {
9058 return null;
9059 }
9060
9061 // excanvas and hence IE < 9 do not support toDataURL and cannot export images.
9062 if ($.jqplot.use_excanvas) {
9063 return null;
9064 }
9065
9066 var newCanvas = document.createElement("canvas");
9067 var h = $(this).outerHeight(true);
9068 var w = $(this).outerWidth(true);
9069 var offs = $(this).offset();
9070 var plotleft = offs.left;
9071 var plottop = offs.top;
9072 var transx = 0, transy = 0;
9073
9074 // have to check if any elements are hanging outside of plot area before rendering,
9075 // since changing width of canvas will erase canvas.
9076
9077 var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick',
9078 'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick',
9079 'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label',
9080 'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ];
9081
9082 var temptop, templeft, tempbottom, tempright;
9083
9084 for (var i = 0; i < clses.length; i++) {
9085 $(this).find('.'+clses[i]).each(function() {
9086 temptop = $(this).offset().top - plottop;
9087 templeft = $(this).offset().left - plotleft;
9088 tempright = templeft + $(this).outerWidth(true) + transx;
9089 tempbottom = temptop + $(this).outerHeight(true) + transy;
9090 if (templeft < -transx) {
9091 w = w - transx - templeft;
9092 transx = -templeft;
9093 }
9094 if (temptop < -transy) {
9095 h = h - transy - temptop;
9096 transy = - temptop;
9097 }
9098 if (tempright > w) {
9099 w = tempright;
9100 }
9101 if (tempbottom > h) {
9102 h = tempbottom;
9103 }
9104 });
9105 }
9106
9107 newCanvas.width = w + Number(x_offset);
9108 newCanvas.height = h + Number(y_offset);
9109
9110 var newContext = newCanvas.getContext("2d");
9111
9112 newContext.save();
9113 newContext.fillStyle = backgroundColor;
9114 newContext.fillRect(0,0, newCanvas.width, newCanvas.height);
9115 newContext.restore();
9116
9117 newContext.translate(transx, transy);
9118 newContext.textAlign = 'left';
9119 newContext.textBaseline = 'top';
9120
9121 function getLineheight(el) {
9122 var lineheight = parseInt($(el).css('line-height'), 10);
9123
9124 if (isNaN(lineheight)) {
9125 lineheight = parseInt($(el).css('font-size'), 10) * 1.2;
9126 }
9127 return lineheight;
9128 }
9129
9130 function writeWrappedText (el, context, text, left, top, canvasWidth) {
9131 var lineheight = getLineheight(el);
9132 var tagwidth = $(el).innerWidth();
9133 var tagheight = $(el).innerHeight();
9134 var words = text.split(/\s+/);
9135 var wl = words.length;
9136 var w = '';
9137 var breaks = [];
9138 var temptop = top;
9139 var templeft = left;
9140
9141 for (var i=0; i<wl; i++) {
9142 w += words[i];
9143 if (context.measureText(w).width > tagwidth) {
9144 breaks.push(i);
9145 w = '';
9146 i--;
9147 }
9148 }
9149 if (breaks.length === 0) {
9150 // center text if necessary
9151 if ($(el).css('textAlign') === 'center') {
9152 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
9153 }
9154 context.fillText(text, templeft, top);
9155 }
9156 else {
9157 w = words.slice(0, breaks[0]).join(' ');
9158 // center text if necessary
9159 if ($(el).css('textAlign') === 'center') {
9160 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
9161 }
9162 context.fillText(w, templeft, temptop);
9163 temptop += lineheight;
9164 for (var i=1, l=breaks.length; i<l; i++) {
9165 w = words.slice(breaks[i-1], breaks[i]).join(' ');
9166 // center text if necessary
9167 if ($(el).css('textAlign') === 'center') {
9168 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
9169 }
9170 context.fillText(w, templeft, temptop);
9171 temptop += lineheight;
9172 }
9173 w = words.slice(breaks[i-1], words.length).join(' ');
9174 // center text if necessary
9175 if ($(el).css('textAlign') === 'center') {
9176 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx;
9177 }
9178 context.fillText(w, templeft, temptop);
9179 }
9180
9181 }
9182
9183 function _jqpToImage(el, x_offset, y_offset) {
9184 var tagname = el.tagName.toLowerCase();
9185 var p = $(el).position();
9186 var css = window.getComputedStyle ? window.getComputedStyle(el, "") : el.currentStyle; // for IE < 9
9187 var left = x_offset + p.left + parseInt(css.marginLeft, 10) + parseInt(css.borderLeftWidth, 10) + parseInt(css.paddingLeft, 10);
9188 var top = y_offset + p.top + parseInt(css.marginTop, 10) + parseInt(css.borderTopWidth, 10)+ parseInt(css.paddingTop, 10);
9189 var w = newCanvas.width;
9190 // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth')
9191
9192 // somehow in here, for divs within divs, the width of the inner div should be used instead of the canvas.
9193
9194 if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) {
9195 $(el).children().each(function() {
9196 _jqpToImage(this, left, top);
9197 });
9198 var text = $(el).jqplotChildText();
9199
9200 if (text) {
9201 newContext.font = $(el).jqplotGetComputedFontStyle();
9202 newContext.fillStyle = $(el).css('color');
9203
9204 writeWrappedText(el, newContext, text, left, top, w);
9205 }
9206 }
9207
9208 // handle the standard table legend
9209
9210 else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) {
9211 newContext.strokeStyle = $(el).css('border-top-color');
9212 newContext.fillStyle = $(el).css('background-color');
9213 newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight());
9214 if (parseInt($(el).css('border-top-width'), 10) > 0) {
9215 newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight());
9216 }
9217
9218 // find all the swatches
9219 $(el).find('div.jqplot-table-legend-swatch-outline').each(function() {
9220 // get the first div and stroke it
9221 var elem = $(this);
9222 newContext.strokeStyle = elem.css('border-top-color');
9223 var l = left + elem.position().left;
9224 var t = top + elem.position().top;
9225 newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight());
9226
9227 // now fill the swatch
9228
9229 l += parseInt(elem.css('padding-left'), 10);
9230 t += parseInt(elem.css('padding-top'), 10);
9231 var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top'), 10);
9232 var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left'), 10);
9233
9234 var swatch = elem.children('div.jqplot-table-legend-swatch');
9235 newContext.fillStyle = swatch.css('background-color');
9236 newContext.fillRect(l, t, w, h);
9237 });
9238
9239 // now add text
9240
9241 $(el).find('td.jqplot-table-legend-label').each(function(){
9242 var elem = $(this);
9243 var l = left + elem.position().left;
9244 var t = top + elem.position().top + parseInt(elem.css('padding-top'), 10);
9245 newContext.font = elem.jqplotGetComputedFontStyle();
9246 newContext.fillStyle = elem.css('color');
9247 writeWrappedText(elem, newContext, elem.text(), l, t, w);
9248 });
9249
9250 var elem = null;
9251 }
9252
9253 else if (tagname == 'canvas') {
9254 newContext.drawImage(el, left, top);
9255 }
9256 }
9257 $(this).children().each(function() {
9258 _jqpToImage(this, x_offset, y_offset);
9259 });
9260 return newCanvas;
9261 };
9262
9263 // return the raw image data string.
9264 // Should work on canvas supporting browsers.
9265 $.fn.jqplotToImageStr = function(options) {
9266 var imgCanvas = $(this).jqplotToImageCanvas(options);
9267 if (imgCanvas) {
9268 return imgCanvas.toDataURL("image/png");
9269 }
9270 else {
9271 return null;
9272 }
9273 };
9274
9275 // return a DOM <img> element and return it.
9276 // Should work on canvas supporting browsers.
9277 $.fn.jqplotToImageElem = function(options) {
9278 var elem = document.createElement("img");
9279 var str = $(this).jqplotToImageStr(options);
9280 elem.src = str;
9281 return elem;
9282 };
9283
9284 // return a string for an <img> element and return it.
9285 // Should work on canvas supporting browsers.
9286 $.fn.jqplotToImageElemStr = function(options) {
9287 var str = '<img src='+$(this).jqplotToImageStr(options)+' />';
9288 return str;
9289 };
9290
9291 // Not guaranteed to work, even on canvas supporting browsers due to
9292 // limitations with location.href and browser support.
9293 $.fn.jqplotSaveImage = function() {
9294 var imgData = $(this).jqplotToImageStr({});
9295 if (imgData) {
9296 window.location.href = imgData.replace("image/png", "image/octet-stream");
9297 }
9298
9299 };
9300
9301 // Not guaranteed to work, even on canvas supporting browsers due to
9302 // limitations with window.open and arbitrary data.
9303 $.fn.jqplotViewImage = function() {
9304 var imgStr = $(this).jqplotToImageElemStr({});
9305 var imgData = $(this).jqplotToImageStr({});
9306 if (imgStr) {
9307 var w = window.open('');
9308 w.document.open("image/png");
9309 w.document.write(imgStr);
9310 w.document.close();
9311 w = null;
9312 }
9313 };
9314
9315
9316
9317
9318 /**
9319 * @description
9320 * <p>Object with extended date parsing and formatting capabilities.
9321 * This library borrows many concepts and ideas from the Date Instance
9322 * Methods by Ken Snyder along with some parts of Ken's actual code.</p>
9323 *
9324 * <p>jsDate takes a different approach by not extending the built-in
9325 * Date Object, improving date parsing, allowing for multiple formatting
9326 * syntaxes and multiple and more easily expandable localization.</p>
9327 *
9328 * @author Chris Leonello
9329 * @date #date#
9330 * @version #VERSION#
9331 * @copyright (c) 2010-2013 Chris Leonello
9332 * jsDate is currently available for use in all personal or commercial projects
9333 * under both the MIT and GPL version 2.0 licenses. This means that you can
9334 * choose the license that best suits your project and use it accordingly.
9335 *
9336 * <p>Ken's original Date Instance Methods and copyright notice:</p>
9337 * <pre>
9338 * Ken Snyder (ken d snyder at gmail dot com)
9339 * 2008-09-10
9340 * version 2.0.2 (http://kendsnyder.com/sandbox/date/)
9341 * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
9342 * </pre>
9343 *
9344 * @class
9345 * @name jsDate
9346 * @param {String | Number | Array | Date&nbsp;Object | Options&nbsp;Object} arguments Optional arguments, either a parsable date/time string,
9347 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
9348 * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional.
9349 */
9350
9351 var jsDate = function () {
9352
9353 this.syntax = jsDate.config.syntax;
9354 this._type = "jsDate";
9355 this.proxy = new Date();
9356 this.options = {};
9357 this.locale = jsDate.regional.getLocale();
9358 this.formatString = '';
9359 this.defaultCentury = jsDate.config.defaultCentury;
9360
9361 switch ( arguments.length ) {
9362 case 0:
9363 break;
9364 case 1:
9365 // other objects either won't have a _type property or,
9366 // if they do, it shouldn't be set to "jsDate", so
9367 // assume it is an options argument.
9368 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
9369 var opts = this.options = arguments[0];
9370 this.syntax = opts.syntax || this.syntax;
9371 this.defaultCentury = opts.defaultCentury || this.defaultCentury;
9372 this.proxy = jsDate.createDate(opts.date);
9373 }
9374 else {
9375 this.proxy = jsDate.createDate(arguments[0]);
9376 }
9377 break;
9378 default:
9379 var a = [];
9380 for ( var i=0; i<arguments.length; i++ ) {
9381 a.push(arguments[i]);
9382 }
9383 // this should be the current date/time?
9384 this.proxy = new Date();
9385 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
9386 if ( a.slice(3).length ) {
9387 this.proxy.setHours.apply( this.proxy, a.slice(3) );
9388 }
9389 break;
9390 }
9391 };
9392
9393 /**
9394 * @namespace Configuration options that will be used as defaults for all instances on the page.
9395 * @property {String} defaultLocale The default locale to use [en].
9396 * @property {String} syntax The default syntax to use [perl].
9397 * @property {Number} defaultCentury The default centry for 2 digit dates.
9398 */
9399 jsDate.config = {
9400 defaultLocale: 'en',
9401 syntax: 'perl',
9402 defaultCentury: 1900
9403 };
9404
9405 /**
9406 * Add an arbitrary amount to the currently stored date
9407 *
9408 * @param {Number} number
9409 * @param {String} unit
9410 * @returns {jsDate}
9411 */
9412
9413 jsDate.prototype.add = function(number, unit) {
9414 var factor = multipliers[unit] || multipliers.day;
9415 if (typeof factor == 'number') {
9416 this.proxy.setTime(this.proxy.getTime() + (factor * number));
9417 } else {
9418 factor.add(this, number);
9419 }
9420 return this;
9421 };
9422
9423 /**
9424 * Create a new jqplot.date object with the same date
9425 *
9426 * @returns {jsDate}
9427 */
9428
9429 jsDate.prototype.clone = function() {
9430 return new jsDate(this.proxy.getTime());
9431 };
9432
9433 /**
9434 * Get the UTC TimeZone Offset of this date in milliseconds.
9435 *
9436 * @returns {Number}
9437 */
9438
9439 jsDate.prototype.getUtcOffset = function() {
9440 return this.proxy.getTimezoneOffset() * 60000;
9441 };
9442
9443 /**
9444 * Find the difference between this jsDate and another date.
9445 *
9446 * @param {String| Number| Array| jsDate&nbsp;Object| Date&nbsp;Object} dateObj
9447 * @param {String} unit
9448 * @param {Boolean} allowDecimal
9449 * @returns {Number} Number of units difference between dates.
9450 */
9451
9452 jsDate.prototype.diff = function(dateObj, unit, allowDecimal) {
9453 // ensure we have a Date object
9454 dateObj = new jsDate(dateObj);
9455 if (dateObj === null) {
9456 return null;
9457 }
9458 // get the multiplying factor integer or factor function
9459 var factor = multipliers[unit] || multipliers.day;
9460 if (typeof factor == 'number') {
9461 // multiply
9462 var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor;
9463 } else {
9464 // run function
9465 var unitDiff = factor.diff(this.proxy, dateObj.proxy);
9466 }
9467 // if decimals are not allowed, round toward zero
9468 return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff));
9469 };
9470
9471 /**
9472 * Get the abbreviated name of the current week day
9473 *
9474 * @returns {String}
9475 */
9476
9477 jsDate.prototype.getAbbrDayName = function() {
9478 return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()];
9479 };
9480
9481 /**
9482 * Get the abbreviated name of the current month
9483 *
9484 * @returns {String}
9485 */
9486
9487 jsDate.prototype.getAbbrMonthName = function() {
9488 return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()];
9489 };
9490
9491 /**
9492 * Get UPPER CASE AM or PM for the current time
9493 *
9494 * @returns {String}
9495 */
9496
9497 jsDate.prototype.getAMPM = function() {
9498 return this.proxy.getHours() >= 12 ? 'PM' : 'AM';
9499 };
9500
9501 /**
9502 * Get lower case am or pm for the current time
9503 *
9504 * @returns {String}
9505 */
9506
9507 jsDate.prototype.getAmPm = function() {
9508 return this.proxy.getHours() >= 12 ? 'pm' : 'am';
9509 };
9510
9511 /**
9512 * Get the century (19 for 20th Century)
9513 *
9514 * @returns {Integer} Century (19 for 20th century).
9515 */
9516 jsDate.prototype.getCentury = function() {
9517 return parseInt(this.proxy.getFullYear()/100, 10);
9518 };
9519
9520 /**
9521 * Implements Date functionality
9522 */
9523 jsDate.prototype.getDate = function() {
9524 return this.proxy.getDate();
9525 };
9526
9527 /**
9528 * Implements Date functionality
9529 */
9530 jsDate.prototype.getDay = function() {
9531 return this.proxy.getDay();
9532 };
9533
9534 /**
9535 * Get the Day of week 1 (Monday) thru 7 (Sunday)
9536 *
9537 * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday)
9538 */
9539 jsDate.prototype.getDayOfWeek = function() {
9540 var dow = this.proxy.getDay();
9541 return dow===0?7:dow;
9542 };
9543
9544 /**
9545 * Get the day of the year
9546 *
9547 * @returns {Integer} 1 - 366, day of the year
9548 */
9549 jsDate.prototype.getDayOfYear = function() {
9550 var d = this.proxy;
9551 var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT');
9552 ms += d.getTimezoneOffset()*60000;
9553 d = null;
9554 return parseInt(ms/60000/60/24, 10)+1;
9555 };
9556
9557 /**
9558 * Get the name of the current week day
9559 *
9560 * @returns {String}
9561 */
9562
9563 jsDate.prototype.getDayName = function() {
9564 return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()];
9565 };
9566
9567 /**
9568 * Get the week number of the given year, starting with the first Sunday as the first week
9569 * @returns {Integer} Week number (13 for the 13th full week of the year).
9570 */
9571 jsDate.prototype.getFullWeekOfYear = function() {
9572 var d = this.proxy;
9573 var doy = this.getDayOfYear();
9574 var rdow = 6-d.getDay();
9575 var woy = parseInt((doy+rdow)/7, 10);
9576 return woy;
9577 };
9578
9579 /**
9580 * Implements Date functionality
9581 */
9582 jsDate.prototype.getFullYear = function() {
9583 return this.proxy.getFullYear();
9584 };
9585
9586 /**
9587 * Get the GMT offset in hours and minutes (e.g. +06:30)
9588 *
9589 * @returns {String}
9590 */
9591
9592 jsDate.prototype.getGmtOffset = function() {
9593 // divide the minutes offset by 60
9594 var hours = this.proxy.getTimezoneOffset() / 60;
9595 // decide if we are ahead of or behind GMT
9596 var prefix = hours < 0 ? '+' : '-';
9597 // remove the negative sign if any
9598 hours = Math.abs(hours);
9599 // add the +/- to the padded number of hours to : to the padded minutes
9600 return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2);
9601 };
9602
9603 /**
9604 * Implements Date functionality
9605 */
9606 jsDate.prototype.getHours = function() {
9607 return this.proxy.getHours();
9608 };
9609
9610 /**
9611 * Get the current hour on a 12-hour scheme
9612 *
9613 * @returns {Integer}
9614 */
9615
9616 jsDate.prototype.getHours12 = function() {
9617 var hours = this.proxy.getHours();
9618 return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours);
9619 };
9620
9621
9622 jsDate.prototype.getIsoWeek = function() {
9623 var d = this.proxy;
9624 var woy = this.getWeekOfYear();
9625 var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
9626 // First week is 01 and not 00 as in the case of %U and %W,
9627 // so we add 1 to the final result except if day 1 of the year
9628 // is a Monday (then %W returns 01).
9629 // We also need to subtract 1 if the day 1 of the year is
9630 // Friday-Sunday, so the resulting equation becomes:
9631 var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
9632 if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
9633 {
9634 idow = 1;
9635 }
9636 else if(idow === 0)
9637 {
9638 d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31'));
9639 idow = d.getIsoWeek();
9640 }
9641 d = null;
9642 return idow;
9643 };
9644
9645 /**
9646 * Implements Date functionality
9647 */
9648 jsDate.prototype.getMilliseconds = function() {
9649 return this.proxy.getMilliseconds();
9650 };
9651
9652 /**
9653 * Implements Date functionality
9654 */
9655 jsDate.prototype.getMinutes = function() {
9656 return this.proxy.getMinutes();
9657 };
9658
9659 /**
9660 * Implements Date functionality
9661 */
9662 jsDate.prototype.getMonth = function() {
9663 return this.proxy.getMonth();
9664 };
9665
9666 /**
9667 * Get the name of the current month
9668 *
9669 * @returns {String}
9670 */
9671
9672 jsDate.prototype.getMonthName = function() {
9673 return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()];
9674 };
9675
9676 /**
9677 * Get the number of the current month, 1-12
9678 *
9679 * @returns {Integer}
9680 */
9681
9682 jsDate.prototype.getMonthNumber = function() {
9683 return this.proxy.getMonth() + 1;
9684 };
9685
9686 /**
9687 * Implements Date functionality
9688 */
9689 jsDate.prototype.getSeconds = function() {
9690 return this.proxy.getSeconds();
9691 };
9692
9693 /**
9694 * Return a proper two-digit year integer
9695 *
9696 * @returns {Integer}
9697 */
9698
9699 jsDate.prototype.getShortYear = function() {
9700 return this.proxy.getYear() % 100;
9701 };
9702
9703 /**
9704 * Implements Date functionality
9705 */
9706 jsDate.prototype.getTime = function() {
9707 return this.proxy.getTime();
9708 };
9709
9710 /**
9711 * Get the timezone abbreviation
9712 *
9713 * @returns {String} Abbreviation for the timezone
9714 */
9715 jsDate.prototype.getTimezoneAbbr = function() {
9716 return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1');
9717 };
9718
9719 /**
9720 * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time)
9721 *
9722 * @returns {String}
9723 */
9724 jsDate.prototype.getTimezoneName = function() {
9725 var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString());
9726 return match[1] || match[2] || 'GMT' + this.getGmtOffset();
9727 };
9728
9729 /**
9730 * Implements Date functionality
9731 */
9732 jsDate.prototype.getTimezoneOffset = function() {
9733 return this.proxy.getTimezoneOffset();
9734 };
9735
9736
9737 /**
9738 * Get the week number of the given year, starting with the first Monday as the first week
9739 * @returns {Integer} Week number (13 for the 13th week of the year).
9740 */
9741 jsDate.prototype.getWeekOfYear = function() {
9742 var doy = this.getDayOfYear();
9743 var rdow = 7 - this.getDayOfWeek();
9744 var woy = parseInt((doy+rdow)/7, 10);
9745 return woy;
9746 };
9747
9748 /**
9749 * Get the current date as a Unix timestamp
9750 *
9751 * @returns {Integer}
9752 */
9753
9754 jsDate.prototype.getUnix = function() {
9755 return Math.round(this.proxy.getTime() / 1000, 0);
9756 };
9757
9758 /**
9759 * Implements Date functionality
9760 */
9761 jsDate.prototype.getYear = function() {
9762 return this.proxy.getYear();
9763 };
9764
9765 /**
9766 * Return a date one day ahead (or any other unit)
9767 *
9768 * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond
9769 * @returns {jsDate}
9770 */
9771
9772 jsDate.prototype.next = function(unit) {
9773 unit = unit || 'day';
9774 return this.clone().add(1, unit);
9775 };
9776
9777 /**
9778 * Set the jsDate instance to a new date.
9779 *
9780 * @param {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments,
9781 * either a parsable date/time string,
9782 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds],
9783 * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional.
9784 */
9785 jsDate.prototype.set = function() {
9786 switch ( arguments.length ) {
9787 case 0:
9788 this.proxy = new Date();
9789 break;
9790 case 1:
9791 // other objects either won't have a _type property or,
9792 // if they do, it shouldn't be set to "jsDate", so
9793 // assume it is an options argument.
9794 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") {
9795 var opts = this.options = arguments[0];
9796 this.syntax = opts.syntax || this.syntax;
9797 this.defaultCentury = opts.defaultCentury || this.defaultCentury;
9798 this.proxy = jsDate.createDate(opts.date);
9799 }
9800 else {
9801 this.proxy = jsDate.createDate(arguments[0]);
9802 }
9803 break;
9804 default:
9805 var a = [];
9806 for ( var i=0; i<arguments.length; i++ ) {
9807 a.push(arguments[i]);
9808 }
9809 // this should be the current date/time
9810 this.proxy = new Date();
9811 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) );
9812 if ( a.slice(3).length ) {
9813 this.proxy.setHours.apply( this.proxy, a.slice(3) );
9814 }
9815 break;
9816 }
9817 return this;
9818 };
9819
9820 /**
9821 * Sets the day of the month for a specified date according to local time.
9822 * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month.
9823 */
9824 jsDate.prototype.setDate = function(n) {
9825 this.proxy.setDate(n);
9826 return this;
9827 };
9828
9829 /**
9830 * Sets the full year for a specified date according to local time.
9831 * @param {Integer} yearValue The numeric value of the year, for example, 1995.
9832 * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December.
9833 * @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.
9834 */
9835 jsDate.prototype.setFullYear = function() {
9836 this.proxy.setFullYear.apply(this.proxy, arguments);
9837 return this;
9838 };
9839
9840 /**
9841 * Sets the hours for a specified date according to local time.
9842 *
9843 * @param {Integer} hoursValue An integer between 0 and 23, representing the hour.
9844 * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes.
9845 * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds.
9846 * If you specify the secondsValue parameter, you must also specify the minutesValue.
9847 * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds.
9848 * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue.
9849 */
9850 jsDate.prototype.setHours = function() {
9851 this.proxy.setHours.apply(this.proxy, arguments);
9852 return this;
9853 };
9854
9855 /**
9856 * Implements Date functionality
9857 */
9858 jsDate.prototype.setMilliseconds = function(n) {
9859 this.proxy.setMilliseconds(n);
9860 return this;
9861 };
9862
9863 /**
9864 * Implements Date functionality
9865 */
9866 jsDate.prototype.setMinutes = function() {
9867 this.proxy.setMinutes.apply(this.proxy, arguments);
9868 return this;
9869 };
9870
9871 /**
9872 * Implements Date functionality
9873 */
9874 jsDate.prototype.setMonth = function() {
9875 this.proxy.setMonth.apply(this.proxy, arguments);
9876 return this;
9877 };
9878
9879 /**
9880 * Implements Date functionality
9881 */
9882 jsDate.prototype.setSeconds = function() {
9883 this.proxy.setSeconds.apply(this.proxy, arguments);
9884 return this;
9885 };
9886
9887 /**
9888 * Implements Date functionality
9889 */
9890 jsDate.prototype.setTime = function(n) {
9891 this.proxy.setTime(n);
9892 return this;
9893 };
9894
9895 /**
9896 * Implements Date functionality
9897 */
9898 jsDate.prototype.setYear = function() {
9899 this.proxy.setYear.apply(this.proxy, arguments);
9900 return this;
9901 };
9902
9903 /**
9904 * Provide a formatted string representation of this date.
9905 *
9906 * @param {String} formatString A format string.
9907 * See: {@link jsDate.formats}.
9908 * @returns {String} Date String.
9909 */
9910
9911 jsDate.prototype.strftime = function(formatString) {
9912 formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString'];
9913 return jsDate.strftime(this, formatString, this.syntax);
9914 };
9915
9916 /**
9917 * Return a String representation of this jsDate object.
9918 * @returns {String} Date string.
9919 */
9920
9921 jsDate.prototype.toString = function() {
9922 return this.proxy.toString();
9923 };
9924
9925 /**
9926 * Convert the current date to an 8-digit integer (%Y%m%d)
9927 *
9928 * @returns {Integer}
9929 */
9930
9931 jsDate.prototype.toYmdInt = function() {
9932 return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate();
9933 };
9934
9935 /**
9936 * @namespace Holds localizations for month/day names.
9937 * <p>jsDate attempts to detect locale when loaded and defaults to 'en'.
9938 * If a localization is detected which is not available, jsDate defaults to 'en'.
9939 * Additional localizations can be added after jsDate loads. After adding a localization,
9940 * call the jsDate.regional.getLocale() method. Currently, en, fr and de are defined.</p>
9941 *
9942 * <p>Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p>
9943 * <pre class="code">
9944 * jsDate.regional['en'] = {
9945 * monthNames : 'January February March April May June July August September October November December'.split(' '),
9946 * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '),
9947 * dayNames : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '),
9948 * dayNamesShort : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ')
9949 * };
9950 * </pre>
9951 * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the
9952 * new localizations.</p>
9953 */
9954
9955 jsDate.regional = {
9956 'en': {
9957 monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
9958 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
9959 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
9960 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
9961 formatString: '%Y-%m-%d %H:%M:%S'
9962 },
9963
9964 'fr': {
9965 monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
9966 monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'],
9967 dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'],
9968 dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],
9969 formatString: '%Y-%m-%d %H:%M:%S'
9970 },
9971
9972 'de': {
9973 monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
9974 monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'],
9975 dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
9976 dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'],
9977 formatString: '%Y-%m-%d %H:%M:%S'
9978 },
9979
9980 'es': {
9981 monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],
9982 monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'],
9983 dayNames: ['Domingo','Lunes','Martes','Mi&eacute;rcoles','Jueves','Viernes','S&aacute;bado'],
9984 dayNamesShort: ['Dom','Lun','Mar','Mi&eacute;','Juv','Vie','S&aacute;b'],
9985 formatString: '%Y-%m-%d %H:%M:%S'
9986 },
9987
9988 'ru': {
9989 monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'],
9990 monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'],
9991 dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'],
9992 dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'],
9993 formatString: '%Y-%m-%d %H:%M:%S'
9994 },
9995
9996 'ar': {
9997 monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'],
9998 monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'],
9999 dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'],
10000 dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'],
10001 formatString: '%Y-%m-%d %H:%M:%S'
10002 },
10003
10004 'pt': {
10005 monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
10006 monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
10007 dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
10008 dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
10009 formatString: '%Y-%m-%d %H:%M:%S'
10010 },
10011
10012 'pt-BR': {
10013 monthNames: ['Janeiro','Fevereiro','Mar&ccedil;o','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
10014 monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'],
10015 dayNames: ['Domingo','Segunda-feira','Ter&ccedil;a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S&aacute;bado'],
10016 dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','S&aacute;b'],
10017 formatString: '%Y-%m-%d %H:%M:%S'
10018 },
10019
10020 'pl': {
10021 monthNames: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec','Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'],
10022 monthNamesShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze','Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],
10023 dayNames: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
10024 dayNamesShort: ['Ni', 'Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'Sb'],
10025 formatString: '%Y-%m-%d %H:%M:%S'
10026 },
10027
10028 'nl': {
10029 monthNames: ['Januari','Februari','Maart','April','Mei','Juni','July','Augustus','September','Oktober','November','December'],
10030 monthNamesShort: ['Jan','Feb','Mar','Apr','Mei','Jun','Jul','Aug','Sep','Okt','Nov','Dec'],
10031 dayNames:','['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'],
10032 dayNamesShort: ['Zo','Ma','Di','Wo','Do','Vr','Za'],
10033 formatString: '%Y-%m-%d %H:%M:%S'
10034 },
10035
10036 'sv': {
10037 monthNames: ['januari','februari','mars','april','maj','juni','juli','augusti','september','oktober','november','december'],
10038 monthNamesShort: ['jan','feb','mar','apr','maj','jun','jul','aug','sep','okt','nov','dec'],
10039 dayNames: ['söndag','måndag','tisdag','onsdag','torsdag','fredag','lördag'],
10040 dayNamesShort: ['sön','mån','tis','ons','tor','fre','lör'],
10041 formatString: '%Y-%m-%d %H:%M:%S'
10042 }
10043
10044 };
10045
10046 // Set english variants to 'en'
10047 jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en'];
10048
10049 /**
10050 * Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en'
10051 * if it cannot figure out a locale of if the locale does not have a localization defined.
10052 * @returns {String} locale
10053 */
10054
10055 jsDate.regional.getLocale = function () {
10056 var l = jsDate.config.defaultLocale;
10057
10058 if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) {
10059 l = document.getElementsByTagName('html')[0].lang;
10060 if (!jsDate.regional.hasOwnProperty(l)) {
10061 l = jsDate.config.defaultLocale;
10062 }
10063 }
10064
10065 return l;
10066 };
10067
10068 // ms in day
10069 var day = 24 * 60 * 60 * 1000;
10070
10071 // padd a number with zeros
10072 var addZeros = function(num, digits) {
10073 num = String(num);
10074 var i = digits - num.length;
10075 var s = String(Math.pow(10, i)).slice(1);
10076 return s.concat(num);
10077 };
10078
10079 // representations used for calculating differences between dates.
10080 // This borrows heavily from Ken Snyder's work.
10081 var multipliers = {
10082 millisecond: 1,
10083 second: 1000,
10084 minute: 60 * 1000,
10085 hour: 60 * 60 * 1000,
10086 day: day,
10087 week: 7 * day,
10088 month: {
10089 // add a number of months
10090 add: function(d, number) {
10091 // add any years needed (increments of 12)
10092 multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12));
10093 // ensure that we properly wrap betwen December and January
10094 // 11 % 12 = 11
10095 // 12 % 12 = 0
10096 var prevMonth = d.getMonth() + (number % 12);
10097 if (prevMonth == 12) {
10098 prevMonth = 0;
10099 d.setYear(d.getFullYear() + 1);
10100 } else if (prevMonth == -1) {
10101 prevMonth = 11;
10102 d.setYear(d.getFullYear() - 1);
10103 }
10104 d.setMonth(prevMonth);
10105 },
10106 // get the number of months between two Date objects (decimal to the nearest day)
10107 diff: function(d1, d2) {
10108 // get the number of years
10109 var diffYears = d1.getFullYear() - d2.getFullYear();
10110 // get the number of remaining months
10111 var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12);
10112 // get the number of remaining days
10113 var diffDays = d1.getDate() - d2.getDate();
10114 // return the month difference with the days difference as a decimal
10115 return diffMonths + (diffDays / 30);
10116 }
10117 },
10118 year: {
10119 // add a number of years
10120 add: function(d, number) {
10121 d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number));
10122 },
10123 // get the number of years between two Date objects (decimal to the nearest day)
10124 diff: function(d1, d2) {
10125 return multipliers.month.diff(d1, d2) / 12;
10126 }
10127 }
10128 };
10129 //
10130 // Alias each multiplier with an 's' to allow 'year' and 'years' for example.
10131 // This comes from Ken Snyders work.
10132 //
10133 for (var unit in multipliers) {
10134 if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :|
10135 multipliers[unit + 's'] = multipliers[unit];
10136 }
10137 }
10138
10139 //
10140 // take a jsDate instance and a format code and return the formatted value.
10141 // This is a somewhat modified version of Ken Snyder's method.
10142 //
10143 var format = function(d, code, syntax) {
10144 // if shorcut codes are used, recursively expand those.
10145 if (jsDate.formats[syntax]["shortcuts"][code]) {
10146 return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax);
10147 } else {
10148 // get the format code function and addZeros() argument
10149 var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.');
10150 var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : '';
10151 if (getter[1]) {
10152 nbr = addZeros(nbr, getter[1]);
10153 }
10154 return nbr;
10155 }
10156 };
10157
10158 /**
10159 * @static
10160 * Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes.
10161 * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p>
10162 * <pre class="code">
10163 * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S');
10164 * </pre>
10165 * @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.
10166 * @param {String} formatString String with embedded date formatting codes.
10167 * See: {@link jsDate.formats}.
10168 * @param {String} syntax Optional syntax to use [default perl].
10169 * @param {String} locale Optional locale to use.
10170 * @returns {String} Formatted representation of the date.
10171 */
10172 //
10173 // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods.
10174 //
10175 jsDate.strftime = function(d, formatString, syntax, locale) {
10176 var syn = 'perl';
10177 var loc = jsDate.regional.getLocale();
10178
10179 // check if syntax and locale are available or reversed
10180 if (syntax && jsDate.formats.hasOwnProperty(syntax)) {
10181 syn = syntax;
10182 }
10183 else if (syntax && jsDate.regional.hasOwnProperty(syntax)) {
10184 loc = syntax;
10185 }
10186
10187 if (locale && jsDate.formats.hasOwnProperty(locale)) {
10188 syn = locale;
10189 }
10190 else if (locale && jsDate.regional.hasOwnProperty(locale)) {
10191 loc = locale;
10192 }
10193
10194 if (get_type(d) != "[object Object]" || d._type != "jsDate") {
10195 d = new jsDate(d);
10196 d.locale = loc;
10197 }
10198 if (!formatString) {
10199 formatString = d.formatString || jsDate.regional[loc]['formatString'];
10200 }
10201 // default the format string to year-month-day
10202 var source = formatString || '%Y-%m-%d',
10203 result = '',
10204 match;
10205 // replace each format code
10206 while (source.length > 0) {
10207 if (match = source.match(jsDate.formats[syn].codes.matcher)) {
10208 result += source.slice(0, match.index);
10209 result += (match[1] || '') + format(d, match[2], syn);
10210 source = source.slice(match.index + match[0].length);
10211 } else {
10212 result += source;
10213 source = '';
10214 }
10215 }
10216 return result;
10217 };
10218
10219 /**
10220 * @namespace
10221 * Namespace to hold format codes and format shortcuts. "perl" and "php" format codes
10222 * and shortcuts are defined by default. Additional codes and shortcuts can be
10223 * added like:
10224 *
10225 * <pre class="code">
10226 * jsDate.formats["perl"] = {
10227 * "codes": {
10228 * matcher: /someregex/,
10229 * Y: "fullYear", // name of "get" method without the "get",
10230 * ..., // more codes
10231 * },
10232 * "shortcuts": {
10233 * F: '%Y-%m-%d',
10234 * ..., // more shortcuts
10235 * }
10236 * };
10237 * </pre>
10238 *
10239 * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via:
10240 * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code>
10241 */
10242
10243 jsDate.formats = {
10244 ISO:'%Y-%m-%dT%H:%M:%S.%N%G',
10245 SQL:'%Y-%m-%d %H:%M:%S'
10246 };
10247
10248 /**
10249 * Perl format codes and shortcuts for strftime.
10250 *
10251 * A hash (object) of codes where each code must be an array where the first member is
10252 * the name of a Date.prototype or jsDate.prototype function to call
10253 * and optionally a second member indicating the number to pass to addZeros()
10254 *
10255 * <p>The following format codes are defined:</p>
10256 *
10257 * <pre class="code">
10258 * Code Result Description
10259 * == Years ==
10260 * %Y 2008 Four-digit year
10261 * %y 08 Two-digit year
10262 *
10263 * == Months ==
10264 * %m 09 Two-digit month
10265 * %#m 9 One or two-digit month
10266 * %B September Full month name
10267 * %b Sep Abbreviated month name
10268 *
10269 * == Days ==
10270 * %d 05 Two-digit day of month
10271 * %#d 5 One or two-digit day of month
10272 * %e 5 One or two-digit day of month
10273 * %A Sunday Full name of the day of the week
10274 * %a Sun Abbreviated name of the day of the week
10275 * %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday)
10276 *
10277 * == Hours ==
10278 * %H 23 Hours in 24-hour format (two digits)
10279 * %#H 3 Hours in 24-hour integer format (one or two digits)
10280 * %I 11 Hours in 12-hour format (two digits)
10281 * %#I 3 Hours in 12-hour integer format (one or two digits)
10282 * %p PM AM or PM
10283 *
10284 * == Minutes ==
10285 * %M 09 Minutes (two digits)
10286 * %#M 9 Minutes (one or two digits)
10287 *
10288 * == Seconds ==
10289 * %S 02 Seconds (two digits)
10290 * %#S 2 Seconds (one or two digits)
10291 * %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00)
10292 *
10293 * == Milliseconds ==
10294 * %N 008 Milliseconds (three digits)
10295 * %#N 8 Milliseconds (one to three digits)
10296 *
10297 * == Timezone ==
10298 * %O 360 difference in minutes between local time and GMT
10299 * %Z Mountain Standard Time Name of timezone as reported by browser
10300 * %G 06:00 Hours and minutes between GMT
10301 *
10302 * == Shortcuts ==
10303 * %F 2008-03-26 %Y-%m-%d
10304 * %T 05:06:30 %H:%M:%S
10305 * %X 05:06:30 %H:%M:%S
10306 * %x 03/26/08 %m/%d/%y
10307 * %D 03/26/08 %m/%d/%y
10308 * %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y
10309 * %v 3-Sep-2008 %e-%b-%Y
10310 * %R 15:31 %H:%M
10311 * %r 03:31:00 PM %I:%M:%S %p
10312 *
10313 * == Characters ==
10314 * %n \n Newline
10315 * %t \t Tab
10316 * %% % Percent Symbol
10317 * </pre>
10318 *
10319 * <p>Formatting shortcuts that will be translated into their longer version.
10320 * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p>
10321 *
10322 * <p>Format codes and format shortcuts can be redefined after the jsDate
10323 * module is imported.</p>
10324 *
10325 * <p>Note that if you redefine the whole hash (object), you must supply a "matcher"
10326 * regex for the parser. The default matcher is:</p>
10327 *
10328 * <code>/()%(#?(%|[a-z]))/i</code>
10329 *
10330 * <p>which corresponds to the Perl syntax used by default.</p>
10331 *
10332 * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p>
10333 */
10334
10335 jsDate.formats.perl = {
10336 codes: {
10337 //
10338 // 2-part regex matcher for format codes
10339 //
10340 // first match must be the character before the code (to account for escaping)
10341 // second match must be the format code character(s)
10342 //
10343 matcher: /()%(#?(%|[a-z]))/i,
10344 // year
10345 Y: 'FullYear',
10346 y: 'ShortYear.2',
10347 // month
10348 m: 'MonthNumber.2',
10349 '#m': 'MonthNumber',
10350 B: 'MonthName',
10351 b: 'AbbrMonthName',
10352 // day
10353 d: 'Date.2',
10354 '#d': 'Date',
10355 e: 'Date',
10356 A: 'DayName',
10357 a: 'AbbrDayName',
10358 w: 'Day',
10359 // hours
10360 H: 'Hours.2',
10361 '#H': 'Hours',
10362 I: 'Hours12.2',
10363 '#I': 'Hours12',
10364 p: 'AMPM',
10365 // minutes
10366 M: 'Minutes.2',
10367 '#M': 'Minutes',
10368 // seconds
10369 S: 'Seconds.2',
10370 '#S': 'Seconds',
10371 s: 'Unix',
10372 // milliseconds
10373 N: 'Milliseconds.3',
10374 '#N': 'Milliseconds',
10375 // timezone
10376 O: 'TimezoneOffset',
10377 Z: 'TimezoneName',
10378 G: 'GmtOffset'
10379 },
10380
10381 shortcuts: {
10382 // date
10383 F: '%Y-%m-%d',
10384 // time
10385 T: '%H:%M:%S',
10386 X: '%H:%M:%S',
10387 // local format date
10388 x: '%m/%d/%y',
10389 D: '%m/%d/%y',
10390 // local format extended
10391 '#c': '%a %b %e %H:%M:%S %Y',
10392 // local format short
10393 v: '%e-%b-%Y',
10394 R: '%H:%M',
10395 r: '%I:%M:%S %p',
10396 // tab and newline
10397 t: '\t',
10398 n: '\n',
10399 '%': '%'
10400 }
10401 };
10402
10403 /**
10404 * PHP format codes and shortcuts for strftime.
10405 *
10406 * A hash (object) of codes where each code must be an array where the first member is
10407 * the name of a Date.prototype or jsDate.prototype function to call
10408 * and optionally a second member indicating the number to pass to addZeros()
10409 *
10410 * <p>The following format codes are defined:</p>
10411 *
10412 * <pre class="code">
10413 * Code Result Description
10414 * === Days ===
10415 * %a Sun through Sat An abbreviated textual representation of the day
10416 * %A Sunday - Saturday A full textual representation of the day
10417 * %d 01 to 31 Two-digit day of the month (with leading zeros)
10418 * %e 1 to 31 Day of the month, with a space preceding single digits.
10419 * %j 001 to 366 Day of the year, 3 digits with leading zeros
10420 * %u 1 - 7 (Mon - Sun) ISO-8601 numeric representation of the day of the week
10421 * %w 0 - 6 (Sun - Sat) Numeric representation of the day of the week
10422 *
10423 * === Week ===
10424 * %U 13 Full Week number, starting with the first Sunday as the first week
10425 * %V 01 through 53 ISO-8601:1988 week number, starting with the first week of the year
10426 * with at least 4 weekdays, with Monday being the start of the week
10427 * %W 46 A numeric representation of the week of the year,
10428 * starting with the first Monday as the first week
10429 * === Month ===
10430 * %b Jan through Dec Abbreviated month name, based on the locale
10431 * %B January - December Full month name, based on the locale
10432 * %h Jan through Dec Abbreviated month name, based on the locale (an alias of %b)
10433 * %m 01 - 12 (Jan - Dec) Two digit representation of the month
10434 *
10435 * === Year ===
10436 * %C 19 Two digit century (year/100, truncated to an integer)
10437 * %y 09 for 2009 Two digit year
10438 * %Y 2038 Four digit year
10439 *
10440 * === Time ===
10441 * %H 00 through 23 Two digit representation of the hour in 24-hour format
10442 * %I 01 through 12 Two digit representation of the hour in 12-hour format
10443 * %l 1 through 12 Hour in 12-hour format, with a space preceeding single digits
10444 * %M 00 through 59 Two digit representation of the minute
10445 * %p AM/PM UPPER-CASE 'AM' or 'PM' based on the given time
10446 * %P am/pm lower-case 'am' or 'pm' based on the given time
10447 * %r 09:34:17 PM Same as %I:%M:%S %p
10448 * %R 00:35 Same as %H:%M
10449 * %S 00 through 59 Two digit representation of the second
10450 * %T 21:34:17 Same as %H:%M:%S
10451 * %X 03:59:16 Preferred time representation based on locale, without the date
10452 * %z -0500 or EST Either the time zone offset from UTC or the abbreviation
10453 * %Z -0500 or EST The time zone offset/abbreviation option NOT given by %z
10454 *
10455 * === Time and Date ===
10456 * %D 02/05/09 Same as %m/%d/%y
10457 * %F 2009-02-05 Same as %Y-%m-%d (commonly used in database datestamps)
10458 * %s 305815200 Unix Epoch Time timestamp (same as the time() function)
10459 * %x 02/05/09 Preferred date representation, without the time
10460 *
10461 * === Miscellaneous ===
10462 * %n --- A newline character (\n)
10463 * %t --- A Tab character (\t)
10464 * %% --- A literal percentage character (%)
10465 * </pre>
10466 */
10467
10468 jsDate.formats.php = {
10469 codes: {
10470 //
10471 // 2-part regex matcher for format codes
10472 //
10473 // first match must be the character before the code (to account for escaping)
10474 // second match must be the format code character(s)
10475 //
10476 matcher: /()%((%|[a-z]))/i,
10477 // day
10478 a: 'AbbrDayName',
10479 A: 'DayName',
10480 d: 'Date.2',
10481 e: 'Date',
10482 j: 'DayOfYear.3',
10483 u: 'DayOfWeek',
10484 w: 'Day',
10485 // week
10486 U: 'FullWeekOfYear.2',
10487 V: 'IsoWeek.2',
10488 W: 'WeekOfYear.2',
10489 // month
10490 b: 'AbbrMonthName',
10491 B: 'MonthName',
10492 m: 'MonthNumber.2',
10493 h: 'AbbrMonthName',
10494 // year
10495 C: 'Century.2',
10496 y: 'ShortYear.2',
10497 Y: 'FullYear',
10498 // time
10499 H: 'Hours.2',
10500 I: 'Hours12.2',
10501 l: 'Hours12',
10502 p: 'AMPM',
10503 P: 'AmPm',
10504 M: 'Minutes.2',
10505 S: 'Seconds.2',
10506 s: 'Unix',
10507 O: 'TimezoneOffset',
10508 z: 'GmtOffset',
10509 Z: 'TimezoneAbbr'
10510 },
10511
10512 shortcuts: {
10513 D: '%m/%d/%y',
10514 F: '%Y-%m-%d',
10515 T: '%H:%M:%S',
10516 X: '%H:%M:%S',
10517 x: '%m/%d/%y',
10518 R: '%H:%M',
10519 r: '%I:%M:%S %p',
10520 t: '\t',
10521 n: '\n',
10522 '%': '%'
10523 }
10524 };
10525 //
10526 // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods.
10527 // I use his idea of a set of parsers which can be regular expressions or functions,
10528 // iterating through those, and then seeing if Date.parse() will create a date.
10529 // The parser expressions and functions are a little different and some bugs have been
10530 // worked out. Also, a lot of "pre-parsing" is done to fix implementation
10531 // variations of Date.parse() between browsers.
10532 //
10533 jsDate.createDate = function(date) {
10534 // if passing in multiple arguments, try Date constructor
10535 if (date == null) {
10536 return new Date();
10537 }
10538 // If the passed value is already a date object, return it
10539 if (date instanceof Date) {
10540 return date;
10541 }
10542 // if (typeof date == 'number') return new Date(date * 1000);
10543 // If the passed value is an integer, interpret it as a javascript timestamp
10544 if (typeof date == 'number') {
10545 return new Date(date);
10546 }
10547
10548 // Before passing strings into Date.parse(), have to normalize them for certain conditions.
10549 // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent.
10550 //
10551 // For example:
10552 // * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century.
10553 // * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse.
10554 // * Both FF, Chrome and Opera will parse '1984/1/25' into localtime.
10555
10556 // remove leading and trailing spaces
10557 var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1');
10558
10559 // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn]
10560 parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3");
10561
10562 /////////
10563 // Need to check for '15-Dec-09' also.
10564 // FF will not parse, but Chrome will.
10565 // Chrome will set date to 2009 as well.
10566 /////////
10567
10568 // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010'
10569 parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3");
10570
10571 // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century.
10572 var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i);
10573 if (match && match.length > 3) {
10574 var m3 = parseFloat(match[3]);
10575 var ny = jsDate.config.defaultCentury + m3;
10576 ny = String(ny);
10577
10578 // now replace 2 digit year with 4 digit year
10579 parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny);
10580
10581 }
10582
10583 // Check for '1/19/70 8:14PM'
10584 // where starts with mm/dd/yy or yy/mm/dd and have something after
10585 // Check if 1st postiion is greater than 31, assume it is year.
10586 // Assme all 2 digit years are 1900's.
10587 // Finally, change them into US style mm/dd/yyyy representations.
10588 match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/);
10589
10590 function h1(parsable, match) {
10591 var m1 = parseFloat(match[1]);
10592 var m2 = parseFloat(match[2]);
10593 var m3 = parseFloat(match[3]);
10594 var cent = jsDate.config.defaultCentury;
10595 var ny, nd, nm, str;
10596
10597 if (m1 > 31) { // first number is a year
10598 nd = m3;
10599 nm = m2;
10600 ny = cent + m1;
10601 }
10602
10603 else { // last number is the year
10604 nd = m2;
10605 nm = m1;
10606 ny = cent + m3;
10607 }
10608
10609 str = nm+'/'+nd+'/'+ny;
10610
10611 // now replace 2 digit year with 4 digit year
10612 return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str);
10613
10614 }
10615
10616 if (match && match.length > 3) {
10617 parsable = h1(parsable, match);
10618 }
10619
10620 // Now check for '1/19/70' with nothing after and do as above
10621 var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/);
10622
10623 if (match && match.length > 3) {
10624 parsable = h1(parsable, match);
10625 }
10626
10627
10628 var i = 0;
10629 var length = jsDate.matchers.length;
10630 var pattern,
10631 ms,
10632 current = parsable,
10633 obj;
10634 while (i < length) {
10635 ms = Date.parse(current);
10636 if (!isNaN(ms)) {
10637 return new Date(ms);
10638 }
10639 pattern = jsDate.matchers[i];
10640 if (typeof pattern == 'function') {
10641 obj = pattern.call(jsDate, current);
10642 if (obj instanceof Date) {
10643 return obj;
10644 }
10645 } else {
10646 current = parsable.replace(pattern[0], pattern[1]);
10647 }
10648 i++;
10649 }
10650 return NaN;
10651 };
10652
10653
10654 /**
10655 * @static
10656 * Handy static utility function to return the number of days in a given month.
10657 * @param {Integer} year Year
10658 * @param {Integer} month Month (1-12)
10659 * @returns {Integer} Number of days in the month.
10660 */
10661 //
10662 // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods.
10663 //
10664 jsDate.daysInMonth = function(year, month) {
10665 if (month == 2) {
10666 return new Date(year, 1, 29).getDate() == 29 ? 29 : 28;
10667 }
10668 return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month];
10669 };
10670
10671
10672 //
10673 // An Array of regular expressions or functions that will attempt to match the date string.
10674 // Functions are called with scope of a jsDate instance.
10675 //
10676 jsDate.matchers = [
10677 // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date).
10678 [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'],
10679 // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date).
10680 [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'],
10681 // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part.
10682 function(str) {
10683 var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i);
10684 // opt. date hour opt. minute opt. second opt. msec opt. am or pm
10685 if (match) {
10686 if (match[1]) {
10687 var d = this.createDate(match[1]);
10688 if (isNaN(d)) {
10689 return;
10690 }
10691 } else {
10692 var d = new Date();
10693 d.setMilliseconds(0);
10694 }
10695 var hour = parseFloat(match[2]);
10696 if (match[6]) {
10697 hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12);
10698 }
10699 d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000);
10700 return d;
10701 }
10702 else {
10703 return str;
10704 }
10705 },
10706 // Handle ISO timestamp with time zone.
10707 function(str) {
10708 var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i);
10709 if (match) {
10710 if (match[1]) {
10711 var d = this.createDate(match[1]);
10712 if (isNaN(d)) {
10713 return;
10714 }
10715 } else {
10716 var d = new Date();
10717 d.setMilliseconds(0);
10718 }
10719 var hour = parseFloat(match[2]);
10720 d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000);
10721 return d;
10722 }
10723 else {
10724 return str;
10725 }
10726 },
10727 // Try to match ambiguous strings like 12/8/22.
10728 // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's).
10729 // This may be redundant with pre processing of date already performed.
10730 function(str) {
10731 var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/);
10732 if (match) {
10733 var d = new Date();
10734 var cent = jsDate.config.defaultCentury;
10735 var m1 = parseFloat(match[1]);
10736 var m3 = parseFloat(match[3]);
10737 var ny, nd, nm;
10738 if (m1 > 31) { // first number is a year
10739 nd = m3;
10740 ny = cent + m1;
10741 }
10742
10743 else { // last number is the year
10744 nd = m1;
10745 ny = cent + m3;
10746 }
10747
10748 var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]);
10749
10750 if (nm == -1) {
10751 nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]);
10752 }
10753
10754 d.setFullYear(ny, nm, nd);
10755 d.setHours(0,0,0,0);
10756 return d;
10757 }
10758
10759 else {
10760 return str;
10761 }
10762 }
10763 ];
10764
10765 //
10766 // I think John Reisig published this method on his blog, ejohn.
10767 //
10768 function inArray( elem, array ) {
10769 if ( array.indexOf ) {
10770 return array.indexOf( elem );
10771 }
10772
10773 for ( var i = 0, length = array.length; i < length; i++ ) {
10774 if ( array[ i ] === elem ) {
10775 return i;
10776 }
10777 }
10778
10779 return -1;
10780 }
10781
10782 //
10783 // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method.
10784 //
10785 function get_type(thing){
10786 if(thing===null) return "[object Null]"; // special case
10787 return Object.prototype.toString.call(thing);
10788 }
10789
10790 $.jsDate = jsDate;
10791
10792
10793 /**
10794 * JavaScript printf/sprintf functions.
10795 *
10796 * This code has been adapted from the publicly available sprintf methods
10797 * by Ash Searle. His original header follows:
10798 *
10799 * This code is unrestricted: you are free to use it however you like.
10800 *
10801 * The functions should work as expected, performing left or right alignment,
10802 * truncating strings, outputting numbers with a required precision etc.
10803 *
10804 * For complex cases, these functions follow the Perl implementations of
10805 * (s)printf, allowing arguments to be passed out-of-order, and to set the
10806 * precision or length of the output based on arguments instead of fixed
10807 * numbers.
10808 *
10809 * See http://perldoc.perl.org/functions/sprintf.html for more information.
10810 *
10811 * Implemented:
10812 * - zero and space-padding
10813 * - right and left-alignment,
10814 * - base X prefix (binary, octal and hex)
10815 * - positive number prefix
10816 * - (minimum) width
10817 * - precision / truncation / maximum width
10818 * - out of order arguments
10819 *
10820 * Not implemented (yet):
10821 * - vector flag
10822 * - size (bytes, words, long-words etc.)
10823 *
10824 * Will not implement:
10825 * - %n or %p (no pass-by-reference in JavaScript)
10826 *
10827 * @version 2007.04.27
10828 * @author Ash Searle
10829 *
10830 * You can see the original work and comments on his blog:
10831 * http://hexmen.com/blog/2007/03/printf-sprintf/
10832 * http://hexmen.com/js/sprintf.js
10833 */
10834
10835 /**
10836 * @Modifications 2009.05.26
10837 * @author Chris Leonello
10838 *
10839 * Added %p %P specifier
10840 * Acts like %g or %G but will not add more significant digits to the output than present in the input.
10841 * Example:
10842 * Format: '%.3p', Input: 0.012, Output: 0.012
10843 * Format: '%.3g', Input: 0.012, Output: 0.0120
10844 * Format: '%.4p', Input: 12.0, Output: 12.0
10845 * Format: '%.4g', Input: 12.0, Output: 12.00
10846 * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5
10847 * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5
10848 *
10849 * Example:
10850 * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23)
10851 * "23.35, 43"
10852 * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524)
10853 * "no value: , decimal with thousands separator: 433,524"
10854 */
10855 $.jqplot.sprintf = function() {
10856 function pad(str, len, chr, leftJustify) {
10857 var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
10858 return leftJustify ? str + padding : padding + str;
10859
10860 }
10861
10862 function thousand_separate(value) {
10863 var value_str = new String(value);
10864 for (var i=10; i>0; i--) {
10865 if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break;
10866 }
10867 return value_str;
10868 }
10869
10870 function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) {
10871 var diff = minWidth - value.length;
10872 if (diff > 0) {
10873 var spchar = ' ';
10874 if (htmlSpace) { spchar = '&nbsp;'; }
10875 if (leftJustify || !zeroPad) {
10876 value = pad(value, minWidth, spchar, leftJustify);
10877 } else {
10878 value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
10879 }
10880 }
10881 return value;
10882 }
10883
10884 function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
10885 // Note: casts negative numbers to positive ones
10886 var number = value >>> 0;
10887 prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
10888 value = prefix + pad(number.toString(base), precision || 0, '0', false);
10889 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10890 }
10891
10892 function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) {
10893 if (precision != null) {
10894 value = value.slice(0, precision);
10895 }
10896 return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace);
10897 }
10898
10899 var a = arguments, i = 0, format = a[i++];
10900
10901 return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) {
10902 if (substring == '%%') { return '%'; }
10903
10904 // parse flags
10905 var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false;
10906 for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
10907 case ' ': positivePrefix = ' '; break;
10908 case '+': positivePrefix = '+'; break;
10909 case '-': leftJustify = true; break;
10910 case '0': zeroPad = true; break;
10911 case '#': prefixBaseX = true; break;
10912 case '&': htmlSpace = true; break;
10913 case '\'': thousandSeparation = true; break;
10914 }
10915
10916 // parameters may be null, undefined, empty-string or real valued
10917 // we want to ignore null, undefined and empty-string values
10918
10919 if (!minWidth) {
10920 minWidth = 0;
10921 }
10922 else if (minWidth == '*') {
10923 minWidth = +a[i++];
10924 }
10925 else if (minWidth.charAt(0) == '*') {
10926 minWidth = +a[minWidth.slice(1, -1)];
10927 }
10928 else {
10929 minWidth = +minWidth;
10930 }
10931
10932 // Note: undocumented perl feature:
10933 if (minWidth < 0) {
10934 minWidth = -minWidth;
10935 leftJustify = true;
10936 }
10937
10938 if (!isFinite(minWidth)) {
10939 throw new Error('$.jqplot.sprintf: (minimum-)width must be finite');
10940 }
10941
10942 if (!precision) {
10943 precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
10944 }
10945 else if (precision == '*') {
10946 precision = +a[i++];
10947 }
10948 else if (precision.charAt(0) == '*') {
10949 precision = +a[precision.slice(1, -1)];
10950 }
10951 else {
10952 precision = +precision;
10953 }
10954
10955 // grab value using valueIndex if required?
10956 var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
10957
10958 switch (type) {
10959 case 's': {
10960 if (value == null) {
10961 return '';
10962 }
10963 return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
10964 }
10965 case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace);
10966 case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace);
10967 case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10968 case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10969 case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase();
10970 case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace);
10971 case 'i': {
10972 var number = parseInt(+value, 10);
10973 if (isNaN(number)) {
10974 return '';
10975 }
10976 var prefix = number < 0 ? '-' : positivePrefix;
10977 var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
10978 value = prefix + pad(number_str, precision, '0', false);
10979 //value = prefix + pad(String(Math.abs(number)), precision, '0', false);
10980 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10981 }
10982 case 'd': {
10983 var number = Math.round(+value);
10984 if (isNaN(number)) {
10985 return '';
10986 }
10987 var prefix = number < 0 ? '-' : positivePrefix;
10988 var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number));
10989 value = prefix + pad(number_str, precision, '0', false);
10990 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace);
10991 }
10992 case 'e':
10993 case 'E':
10994 case 'f':
10995 case 'F':
10996 case 'g':
10997 case 'G':
10998 {
10999 var number = +value;
11000 if (isNaN(number)) {
11001 return '';
11002 }
11003 var prefix = number < 0 ? '-' : positivePrefix;
11004 var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
11005 var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
11006 var number_str = Math.abs(number)[method](precision);
11007
11008 // Apply the decimal mark properly by splitting the number by the
11009 // decimalMark, applying thousands separator, and then placing it
11010 // back in.
11011 var parts = number_str.toString().split('.');
11012 parts[0] = thousandSeparation ? thousand_separate(parts[0]) : parts[0];
11013 number_str = parts.join($.jqplot.sprintf.decimalMark);
11014
11015 value = prefix + number_str;
11016 var justified = justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
11017
11018 return justified;
11019 }
11020 case 'p':
11021 case 'P':
11022 {
11023 // make sure number is a number
11024 var number = +value;
11025 if (isNaN(number)) {
11026 return '';
11027 }
11028 var prefix = number < 0 ? '-' : positivePrefix;
11029
11030 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
11031 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : String(number).length;
11032 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
11033
11034 if (Math.abs(number) < 1) {
11035 if (sd + zeros <= precision) {
11036 value = prefix + Math.abs(number).toPrecision(sd);
11037 }
11038 else {
11039 if (sd <= precision - 1) {
11040 value = prefix + Math.abs(number).toExponential(sd-1);
11041 }
11042 else {
11043 value = prefix + Math.abs(number).toExponential(precision-1);
11044 }
11045 }
11046 }
11047 else {
11048 var prec = (sd <= precision) ? sd : precision;
11049 value = prefix + Math.abs(number).toPrecision(prec);
11050 }
11051 var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2];
11052 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform]();
11053 }
11054 case 'n': return '';
11055 default: return substring;
11056 }
11057 });
11058 };
11059
11060 $.jqplot.sprintf.thousandsSeparator = ',';
11061 // Specifies the decimal mark for floating point values. By default a period '.'
11062 // is used. If you change this value to for example a comma be sure to also
11063 // change the thousands separator or else this won't work since a simple String
11064 // replace is used (replacing all periods with the mark specified here).
11065 $.jqplot.sprintf.decimalMark = '.';
11066
11067 $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g;
11068
11069 $.jqplot.getSignificantFigures = function(number) {
11070 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/);
11071 // total significant digits
11072 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length;
11073 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0;
11074 // exponent
11075 var expn = parseInt(parts[1], 10);
11076 // digits to the left of the decimal place
11077 var dleft = (expn + 1 > 0) ? expn + 1 : 0;
11078 // digits to the right of the decimal place
11079 var dright = (sd <= dleft) ? 0 : sd - expn - 1;
11080 return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ;
11081 };
11082
11083 $.jqplot.getPrecision = function(number) {
11084 return $.jqplot.getSignificantFigures(number).digitsRight;
11085 };
11086
11087
11088
11089
11090 var backCompat = $.uiBackCompat !== false;
11091
11092 $.jqplot.effects = {
11093 effect: {}
11094 };
11095
11096 // prefix used for storing data on .data()
11097 var dataSpace = "jqplot.storage.";
11098
11099 /******************************************************************************/
11100 /*********************************** EFFECTS **********************************/
11101 /******************************************************************************/
11102
11103 $.extend( $.jqplot.effects, {
11104 version: "1.9pre",
11105
11106 // Saves a set of properties in a data storage
11107 save: function( element, set ) {
11108 for( var i=0; i < set.length; i++ ) {
11109 if ( set[ i ] !== null ) {
11110 element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
11111 }
11112 }
11113 },
11114
11115 // Restores a set of previously saved properties from a data storage
11116 restore: function( element, set ) {
11117 for( var i=0; i < set.length; i++ ) {
11118 if ( set[ i ] !== null ) {
11119 element.css( set[ i ], element.data( dataSpace + set[ i ] ) );
11120 }
11121 }
11122 },
11123
11124 setMode: function( el, mode ) {
11125 if (mode === "toggle") {
11126 mode = el.is( ":hidden" ) ? "show" : "hide";
11127 }
11128 return mode;
11129 },
11130
11131 // Wraps the element around a wrapper that copies position properties
11132 createWrapper: function( element ) {
11133
11134 // if the element is already wrapped, return it
11135 if ( element.parent().is( ".ui-effects-wrapper" )) {
11136 return element.parent();
11137 }
11138
11139 // wrap the element
11140 var props = {
11141 width: element.outerWidth(true),
11142 height: element.outerHeight(true),
11143 "float": element.css( "float" )
11144 },
11145 wrapper = $( "<div></div>" )
11146 .addClass( "ui-effects-wrapper" )
11147 .css({
11148 fontSize: "100%",
11149 background: "transparent",
11150 border: "none",
11151 margin: 0,
11152 padding: 0
11153 }),
11154 // Store the size in case width/height are defined in % - Fixes #5245
11155 size = {
11156 width: element.width(),
11157 height: element.height()
11158 },
11159 active = document.activeElement;
11160
11161 element.wrap( wrapper );
11162
11163 // Fixes #7595 - Elements lose focus when wrapped.
11164 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
11165 $( active ).focus();
11166 }
11167
11168 wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element
11169
11170 // transfer positioning properties to the wrapper
11171 if ( element.css( "position" ) === "static" ) {
11172 wrapper.css({ position: "relative" });
11173 element.css({ position: "relative" });
11174 } else {
11175 $.extend( props, {
11176 position: element.css( "position" ),
11177 zIndex: element.css( "z-index" )
11178 });
11179 $.each([ "top", "left", "bottom", "right" ], function(i, pos) {
11180 props[ pos ] = element.css( pos );
11181 if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
11182 props[ pos ] = "auto";
11183 }
11184 });
11185 element.css({
11186 position: "relative",
11187 top: 0,
11188 left: 0,
11189 right: "auto",
11190 bottom: "auto"
11191 });
11192 }
11193 element.css(size);
11194
11195 return wrapper.css( props ).show();
11196 },
11197
11198 removeWrapper: function( element ) {
11199 var active = document.activeElement;
11200
11201 if ( element.parent().is( ".ui-effects-wrapper" ) ) {
11202 element.parent().replaceWith( element );
11203
11204 // Fixes #7595 - Elements lose focus when wrapped.
11205 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
11206 $( active ).focus();
11207 }
11208 }
11209
11210
11211 return element;
11212 }
11213 });
11214
11215 // return an effect options object for the given parameters:
11216 function _normalizeArguments( effect, options, speed, callback ) {
11217
11218 // short path for passing an effect options object:
11219 if ( $.isPlainObject( effect ) ) {
11220 return effect;
11221 }
11222
11223 // convert to an object
11224 effect = { effect: effect };
11225
11226 // catch (effect)
11227 if ( options === undefined ) {
11228 options = {};
11229 }
11230
11231 // catch (effect, callback)
11232 if ( $.isFunction( options ) ) {
11233 callback = options;
11234 speed = null;
11235 options = {};
11236 }
11237
11238 // catch (effect, speed, ?)
11239 if ( $.type( options ) === "number" || $.fx.speeds[ options ]) {
11240 callback = speed;
11241 speed = options;
11242 options = {};
11243 }
11244
11245 // catch (effect, options, callback)
11246 if ( $.isFunction( speed ) ) {
11247 callback = speed;
11248 speed = null;
11249 }
11250
11251 // add options to effect
11252 if ( options ) {
11253 $.extend( effect, options );
11254 }
11255
11256 speed = speed || options.duration;
11257 effect.duration = $.fx.off ? 0 : typeof speed === "number"
11258 ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default;
11259
11260 effect.complete = callback || options.complete;
11261
11262 return effect;
11263 }
11264
11265 function standardSpeed( speed ) {
11266 // valid standard speeds
11267 if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) {
11268 return true;
11269 }
11270
11271 // invalid strings - treat as "normal" speed
11272 if ( typeof speed === "string" && !$.jqplot.effects.effect[ speed ] ) {
11273 // TODO: remove in 2.0 (#7115)
11274 if ( backCompat && $.jqplot.effects[ speed ] ) {
11275 return false;
11276 }
11277 return true;
11278 }
11279
11280 return false;
11281 }
11282
11283 $.fn.extend({
11284 jqplotEffect: function( effect, options, speed, callback ) {
11285 var args = _normalizeArguments.apply( this, arguments ),
11286 mode = args.mode,
11287 queue = args.queue,
11288 effectMethod = $.jqplot.effects.effect[ args.effect ],
11289
11290 // DEPRECATED: remove in 2.0 (#7115)
11291 oldEffectMethod = !effectMethod && backCompat && $.jqplot.effects[ args.effect ];
11292
11293 if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) {
11294 // delegate to the original method (e.g., .show()) if possible
11295 if ( mode ) {
11296 return this[ mode ]( args.duration, args.complete );
11297 } else {
11298 return this.each( function() {
11299 if ( args.complete ) {
11300 args.complete.call( this );
11301 }
11302 });
11303 }
11304 }
11305
11306 function run( next ) {
11307 var elem = $( this ),
11308 complete = args.complete,
11309 mode = args.mode;
11310
11311 function done() {
11312 if ( $.isFunction( complete ) ) {
11313 complete.call( elem[0] );
11314 }
11315 if ( $.isFunction( next ) ) {
11316 next();
11317 }
11318 }
11319
11320 // if the element is hiddden and mode is hide,
11321 // or element is visible and mode is show
11322 if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
11323 done();
11324 } else {
11325 effectMethod.call( elem[0], args, done );
11326 }
11327 }
11328
11329 // TODO: remove this check in 2.0, effectMethod will always be true
11330 if ( effectMethod ) {
11331 return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
11332 } else {
11333 // DEPRECATED: remove in 2.0 (#7115)
11334 return oldEffectMethod.call(this, {
11335 options: args,
11336 duration: args.duration,
11337 callback: args.complete,
11338 mode: args.mode
11339 });
11340 }
11341 }
11342 });
11343
11344
11345
11346
11347 var rvertical = /up|down|vertical/,
11348 rpositivemotion = /up|left|vertical|horizontal/;
11349
11350 $.jqplot.effects.effect.blind = function( o, done ) {
11351 // Create element
11352 var el = $( this ),
11353 props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
11354 mode = $.jqplot.effects.setMode( el, o.mode || "hide" ),
11355 direction = o.direction || "up",
11356 vertical = rvertical.test( direction ),
11357 ref = vertical ? "height" : "width",
11358 ref2 = vertical ? "top" : "left",
11359 motion = rpositivemotion.test( direction ),
11360 animation = {},
11361 show = mode === "show",
11362 wrapper, distance, top;
11363
11364 // // if already wrapped, the wrapper's properties are my property. #6245
11365 if ( el.parent().is( ".ui-effects-wrapper" ) ) {
11366 $.jqplot.effects.save( el.parent(), props );
11367 } else {
11368 $.jqplot.effects.save( el, props );
11369 }
11370 el.show();
11371 top = parseInt(el.css('top'), 10);
11372 wrapper = $.jqplot.effects.createWrapper( el ).css({
11373 overflow: "hidden"
11374 });
11375
11376 distance = vertical ? wrapper[ ref ]() + top : wrapper[ ref ]();
11377
11378 animation[ ref ] = show ? String(distance) : '0';
11379 if ( !motion ) {
11380 el
11381 .css( vertical ? "bottom" : "right", 0 )
11382 .css( vertical ? "top" : "left", "" )
11383 .css({ position: "absolute" });
11384 animation[ ref2 ] = show ? '0' : String(distance);
11385 }
11386
11387 // // start at 0 if we are showing
11388 if ( show ) {
11389 wrapper.css( ref, 0 );
11390 if ( ! motion ) {
11391 wrapper.css( ref2, distance );
11392 }
11393 }
11394
11395 // // Animate
11396 wrapper.animate( animation, {
11397 duration: o.duration,
11398 easing: o.easing,
11399 queue: false,
11400 complete: function() {
11401 if ( mode === "hide" ) {
11402 el.hide();
11403 }
11404 $.jqplot.effects.restore( el, props );
11405 $.jqplot.effects.removeWrapper( el );
11406 done();
11407 }
11408 });
11409
11410 };
11411
11412 })(jQuery);
11413