[ Index ] |
PHP Cross Reference of vtigercrm-6.1.0 |
[Summary view] [Print] [Text view]
1 /** 2 * Title: jqPlot Charts 3 * 4 * Pure JavaScript plotting plugin for jQuery. 5 * 6 * About: Version 7 * 8 * version: 1.0.2 9 * revision: 1108 10 * 11 * About: Copyright & License 12 * 13 * Copyright (c) 2009-2011 Chris Leonello 14 * jqPlot is currently available for use in all personal or commercial projects 15 * under both the MIT and GPL version 2.0 licenses. This means that you can 16 * choose the license that best suits your project and use it accordingly. 17 * 18 * See <GPL Version 2> and <MIT License> contained within this distribution for further information. 19 * 20 * The author would appreciate an email letting him know of any substantial 21 * use of jqPlot. You can reach the author at: chris at jqplot dot com 22 * or see http://www.jqplot.com/info.php. This is, of course, not required. 23 * 24 * If you are feeling kind and generous, consider supporting the project by 25 * making a donation at: http://www.jqplot.com/donate.php. 26 * 27 * sprintf functions contained in jqplot.sprintf.js by Ash Searle: 28 * 29 * version 2007.04.27 30 * author Ash Searle 31 * http://hexmen.com/blog/2007/03/printf-sprintf/ 32 * http://hexmen.com/js/sprintf.js 33 * The author (Ash Searle) has placed this code in the public domain: 34 * "This code is unrestricted: you are free to use it however you like." 35 * 36 * 37 * About: Introduction 38 * 39 * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution. 40 * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally 41 * the excanvas script for IE support in your web page: 42 * 43 * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]--> 44 * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script> 45 * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script> 46 * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" /> 47 * 48 * jqPlot can be customized by overriding the defaults of any of the objects which make 49 * up the plot. The general usage of jqplot is: 50 * 51 * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject}); 52 * 53 * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file. 54 * 55 * An actual call to $.jqplot() may look like the 56 * examples below: 57 * 58 * > chart = $.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]); 59 * 60 * or 61 * 62 * > dataArray = [34,12,43,55,77]; 63 * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}}); 64 * 65 * For more inforrmation, see <jqPlot Usage>. 66 * 67 * About: Usage 68 * 69 * See <jqPlot Usage> 70 * 71 * About: Available Options 72 * 73 * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!) 74 * 75 * About: Options Usage 76 * 77 * See <Options Tutorial> 78 * 79 * About: Changes 80 * 81 * See <Change Log> 82 * 83 */ 84 85 (function($) { 86 // make sure undefined is undefined 87 var undefined; 88 89 $.fn.emptyForce = function() { 90 for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) { 91 // Remove element nodes and prevent memory leaks 92 if ( elem.nodeType === 1 ) { 93 $.cleanData( elem.getElementsByTagName("*") ); 94 } 95 96 // Remove any remaining nodes 97 if ($.jqplot.use_excanvas) { 98 elem.outerHTML = ""; 99 } 100 else { 101 while ( elem.firstChild ) { 102 elem.removeChild( elem.firstChild ); 103 } 104 } 105 106 elem = null; 107 } 108 109 return $(this); 110 }; 111 112 $.fn.removeChildForce = function(parent) { 113 while ( parent.firstChild ) { 114 this.removeChildForce( parent.firstChild ); 115 parent.removeChild( parent.firstChild ); 116 } 117 }; 118 119 $.fn.jqplot = function() { 120 var datas = []; 121 var options = []; 122 // see how many data arrays we have 123 for (var i=0, l=arguments.length; i<l; i++) { 124 if ($.isArray(arguments[i])) { 125 datas.push(arguments[i]); 126 } 127 else if ($.isPlainObject(arguments[i])) { 128 options.push(arguments[i]); 129 } 130 } 131 132 return this.each(function(index) { 133 var tid, 134 plot, 135 $this = $(this), 136 dl = datas.length, 137 ol = options.length, 138 data, 139 opts; 140 141 if (index < dl) { 142 data = datas[index]; 143 } 144 else { 145 data = dl ? datas[dl-1] : null; 146 } 147 148 if (index < ol) { 149 opts = options[index]; 150 } 151 else { 152 opts = ol ? options[ol-1] : null; 153 } 154 155 // does el have an id? 156 // if not assign it one. 157 tid = $this.attr('id'); 158 if (tid === undefined) { 159 tid = 'jqplot_target_' + $.jqplot.targetCounter++; 160 $this.attr('id', tid); 161 } 162 163 plot = $.jqplot(tid, data, opts); 164 165 $this.data('jqplot', plot); 166 }); 167 }; 168 169 170 /** 171 * Namespace: $.jqplot 172 * jQuery function called by the user to create a plot. 173 * 174 * Parameters: 175 * target - ID of target element to render the plot into. 176 * data - an array of data series. 177 * options - user defined options object. See the individual classes for available options. 178 * 179 * Properties: 180 * config - object to hold configuration information for jqPlot plot object. 181 * 182 * attributes: 183 * enablePlugins - False to disable plugins by default. Plugins must then be explicitly 184 * enabled in the individual plot options. Default: false. 185 * This property sets the "show" property of certain plugins to true or false. 186 * Only plugins that can be immediately active upon loading are affected. This includes 187 * non-renderer plugins like cursor, dragable, highlighter, and trendline. 188 * defaultHeight - Default height for plots where no css height specification exists. This 189 * is a jqplot wide default. 190 * defaultWidth - Default height for plots where no css height specification exists. This 191 * is a jqplot wide default. 192 */ 193 194 $.jqplot = function(target, data, options) { 195 var _data = null, _options = null; 196 197 if (arguments.length === 3) { 198 _data = data; 199 _options = options; 200 } 201 202 else if (arguments.length === 2) { 203 if ($.isArray(data)) { 204 _data = data; 205 } 206 207 else if ($.isPlainObject(data)) { 208 _options = data; 209 } 210 } 211 212 if (_data === null && _options !== null && _options.data) { 213 _data = _options.data; 214 } 215 216 var plot = new jqPlot(); 217 // remove any error class that may be stuck on target. 218 $('#'+target).removeClass('jqplot-error'); 219 220 if ($.jqplot.config.catchErrors) { 221 try { 222 plot.init(target, _data, _options); 223 plot.draw(); 224 plot.themeEngine.init.call(plot); 225 return plot; 226 } 227 catch(e) { 228 var msg = $.jqplot.config.errorMessage || e.message; 229 $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>'); 230 $('#'+target).addClass('jqplot-error'); 231 document.getElementById(target).style.background = $.jqplot.config.errorBackground; 232 document.getElementById(target).style.border = $.jqplot.config.errorBorder; 233 document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily; 234 document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize; 235 document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle; 236 document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight; 237 } 238 } 239 else { 240 plot.init(target, _data, _options); 241 plot.draw(); 242 plot.themeEngine.init.call(plot); 243 return plot; 244 } 245 }; 246 247 $.jqplot.version = "1.0.2"; 248 $.jqplot.revision = "1108"; 249 250 $.jqplot.targetCounter = 1; 251 252 // canvas manager to reuse canvases on the plot. 253 // Should help solve problem of canvases not being freed and 254 // problem of waiting forever for firefox to decide to free memory. 255 $.jqplot.CanvasManager = function() { 256 // canvases are managed globally so that they can be reused 257 // across plots after they have been freed 258 if (typeof $.jqplot.CanvasManager.canvases == 'undefined') { 259 $.jqplot.CanvasManager.canvases = []; 260 $.jqplot.CanvasManager.free = []; 261 } 262 263 var myCanvases = []; 264 265 this.getCanvas = function() { 266 var canvas; 267 var makeNew = true; 268 269 if (!$.jqplot.use_excanvas) { 270 for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) { 271 if ($.jqplot.CanvasManager.free[i] === true) { 272 makeNew = false; 273 canvas = $.jqplot.CanvasManager.canvases[i]; 274 // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse'); 275 $.jqplot.CanvasManager.free[i] = false; 276 myCanvases.push(i); 277 break; 278 } 279 } 280 } 281 282 if (makeNew) { 283 canvas = document.createElement('canvas'); 284 myCanvases.push($.jqplot.CanvasManager.canvases.length); 285 $.jqplot.CanvasManager.canvases.push(canvas); 286 $.jqplot.CanvasManager.free.push(false); 287 } 288 289 return canvas; 290 }; 291 292 // this method has to be used after settings the dimesions 293 // on the element returned by getCanvas() 294 this.initCanvas = function(canvas) { 295 if ($.jqplot.use_excanvas) { 296 return window.G_vmlCanvasManager.initElement(canvas); 297 } 298 return canvas; 299 }; 300 301 this.freeAllCanvases = function() { 302 for (var i = 0, l=myCanvases.length; i < l; i++) { 303 this.freeCanvas(myCanvases[i]); 304 } 305 myCanvases = []; 306 }; 307 308 this.freeCanvas = function(idx) { 309 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { 310 // excanvas can't be reused, but properly unset 311 window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]); 312 $.jqplot.CanvasManager.canvases[idx] = null; 313 } 314 else { 315 var canvas = $.jqplot.CanvasManager.canvases[idx]; 316 canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); 317 $(canvas).unbind().removeAttr('class').removeAttr('style'); 318 // Style attributes seemed to be still hanging around. wierd. Some ticks 319 // still retained a left: 0px attribute after reusing a canvas. 320 $(canvas).css({left: '', top: '', position: ''}); 321 // setting size to 0 may save memory of unused canvases? 322 canvas.width = 0; 323 canvas.height = 0; 324 $.jqplot.CanvasManager.free[idx] = true; 325 } 326 }; 327 328 }; 329 330 331 // Convienence function that won't hang IE or FF without FireBug. 332 $.jqplot.log = function() { 333 if (window.console) { 334 window.console.log.apply(window.console, arguments); 335 } 336 }; 337 338 $.jqplot.config = { 339 addDomReference: false, 340 enablePlugins:false, 341 defaultHeight:300, 342 defaultWidth:400, 343 UTCAdjust:false, 344 timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000), 345 errorMessage: '', 346 errorBackground: '', 347 errorBorder: '', 348 errorFontFamily: '', 349 errorFontSize: '', 350 errorFontStyle: '', 351 errorFontWeight: '', 352 catchErrors: false, 353 defaultTickFormatString: "%.1f", 354 defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 355 defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"], 356 dashLength: 4, 357 gapLength: 4, 358 dotGapLength: 2.5, 359 srcLocation: 'jqplot/src/', 360 pluginLocation: 'jqplot/src/plugins/' 361 }; 362 363 364 $.jqplot.arrayMax = function( array ){ 365 return Math.max.apply( Math, array ); 366 }; 367 368 $.jqplot.arrayMin = function( array ){ 369 return Math.min.apply( Math, array ); 370 }; 371 372 $.jqplot.enablePlugins = $.jqplot.config.enablePlugins; 373 374 // canvas related tests taken from modernizer: 375 // Copyright (c) 2009 - 2010 Faruk Ates. 376 // http://www.modernizr.com 377 378 $.jqplot.support_canvas = function() { 379 if (typeof $.jqplot.support_canvas.result == 'undefined') { 380 $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext; 381 } 382 return $.jqplot.support_canvas.result; 383 }; 384 385 $.jqplot.support_canvas_text = function() { 386 if (typeof $.jqplot.support_canvas_text.result == 'undefined') { 387 if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) { 388 $.jqplot.support_canvas_text.result = true; 389 } 390 else { 391 $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function'); 392 } 393 394 } 395 return $.jqplot.support_canvas_text.result; 396 }; 397 398 $.jqplot.use_excanvas = ($.browser.msie && !$.jqplot.support_canvas()) ? true : false; 399 400 /** 401 * 402 * Hooks: jqPlot Pugin Hooks 403 * 404 * $.jqplot.preInitHooks - called before initialization. 405 * $.jqplot.postInitHooks - called after initialization. 406 * $.jqplot.preParseOptionsHooks - called before user options are parsed. 407 * $.jqplot.postParseOptionsHooks - called after user options are parsed. 408 * $.jqplot.preDrawHooks - called before plot draw. 409 * $.jqplot.postDrawHooks - called after plot draw. 410 * $.jqplot.preDrawSeriesHooks - called before each series is drawn. 411 * $.jqplot.postDrawSeriesHooks - called after each series is drawn. 412 * $.jqplot.preDrawLegendHooks - called before the legend is drawn. 413 * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins 414 * can add rows to the legend table. 415 * $.jqplot.preSeriesInitHooks - called before series is initialized. 416 * $.jqplot.postSeriesInitHooks - called after series is initialized. 417 * $.jqplot.preParseSeriesOptionsHooks - called before series related options 418 * are parsed. 419 * $.jqplot.postParseSeriesOptionsHooks - called after series related options 420 * are parsed. 421 * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds 422 * listeners to the event canvas which lays on top of the grid area. 423 * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn. 424 * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn. 425 * 426 */ 427 428 $.jqplot.preInitHooks = []; 429 $.jqplot.postInitHooks = []; 430 $.jqplot.preParseOptionsHooks = []; 431 $.jqplot.postParseOptionsHooks = []; 432 $.jqplot.preDrawHooks = []; 433 $.jqplot.postDrawHooks = []; 434 $.jqplot.preDrawSeriesHooks = []; 435 $.jqplot.postDrawSeriesHooks = []; 436 $.jqplot.preDrawLegendHooks = []; 437 $.jqplot.addLegendRowHooks = []; 438 $.jqplot.preSeriesInitHooks = []; 439 $.jqplot.postSeriesInitHooks = []; 440 $.jqplot.preParseSeriesOptionsHooks = []; 441 $.jqplot.postParseSeriesOptionsHooks = []; 442 $.jqplot.eventListenerHooks = []; 443 $.jqplot.preDrawSeriesShadowHooks = []; 444 $.jqplot.postDrawSeriesShadowHooks = []; 445 446 // A superclass holding some common properties and methods. 447 $.jqplot.ElemContainer = function() { 448 this._elem; 449 this._plotWidth; 450 this._plotHeight; 451 this._plotDimensions = {height:null, width:null}; 452 }; 453 454 $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) { 455 this._offsets = offsets; 456 var klass = clss || 'jqplot'; 457 var elem = document.createElement(el); 458 this._elem = $(elem); 459 this._elem.addClass(klass); 460 this._elem.css(cssopts); 461 this._elem.attr(attrib); 462 // avoid memory leak; 463 elem = null; 464 return this._elem; 465 }; 466 467 $.jqplot.ElemContainer.prototype.getWidth = function() { 468 if (this._elem) { 469 return this._elem.outerWidth(true); 470 } 471 else { 472 return null; 473 } 474 }; 475 476 $.jqplot.ElemContainer.prototype.getHeight = function() { 477 if (this._elem) { 478 return this._elem.outerHeight(true); 479 } 480 else { 481 return null; 482 } 483 }; 484 485 $.jqplot.ElemContainer.prototype.getPosition = function() { 486 if (this._elem) { 487 return this._elem.position(); 488 } 489 else { 490 return {top:null, left:null, bottom:null, right:null}; 491 } 492 }; 493 494 $.jqplot.ElemContainer.prototype.getTop = function() { 495 return this.getPosition().top; 496 }; 497 498 $.jqplot.ElemContainer.prototype.getLeft = function() { 499 return this.getPosition().left; 500 }; 501 502 $.jqplot.ElemContainer.prototype.getBottom = function() { 503 return this._elem.css('bottom'); 504 }; 505 506 $.jqplot.ElemContainer.prototype.getRight = function() { 507 return this._elem.css('right'); 508 }; 509 510 511 /** 512 * Class: Axis 513 * An individual axis object. Cannot be instantiated directly, but created 514 * by the Plot oject. Axis properties can be set or overriden by the 515 * options passed in from the user. 516 * 517 */ 518 function Axis(name) { 519 $.jqplot.ElemContainer.call(this); 520 // Group: Properties 521 // 522 // Axes options are specified within an axes object at the top level of the 523 // plot options like so: 524 // > { 525 // > axes: { 526 // > xaxis: {min: 5}, 527 // > yaxis: {min: 2, max: 8, numberTicks:4}, 528 // > x2axis: {pad: 1.5}, 529 // > y2axis: {ticks:[22, 44, 66, 88]} 530 // > } 531 // > } 532 // There are 2 x axes, 'xaxis' and 'x2axis', and 533 // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ... Any or all of which may be specified. 534 this.name = name; 535 this._series = []; 536 // prop: show 537 // Wether to display the axis on the graph. 538 this.show = false; 539 // prop: tickRenderer 540 // A class of a rendering engine for creating the ticks labels displayed on the plot, 541 // See <$.jqplot.AxisTickRenderer>. 542 this.tickRenderer = $.jqplot.AxisTickRenderer; 543 // prop: tickOptions 544 // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options. 545 this.tickOptions = {}; 546 // prop: labelRenderer 547 // A class of a rendering engine for creating an axis label. 548 this.labelRenderer = $.jqplot.AxisLabelRenderer; 549 // prop: labelOptions 550 // Options passed to the label renderer. 551 this.labelOptions = {}; 552 // prop: label 553 // Label for the axis 554 this.label = null; 555 // prop: showLabel 556 // true to show the axis label. 557 this.showLabel = true; 558 // prop: min 559 // minimum value of the axis (in data units, not pixels). 560 this.min = null; 561 // prop: max 562 // maximum value of the axis (in data units, not pixels). 563 this.max = null; 564 // prop: autoscale 565 // DEPRECATED 566 // the default scaling algorithm produces superior results. 567 this.autoscale = false; 568 // prop: pad 569 // Padding to extend the range above and below the data bounds. 570 // The data range is multiplied by this factor to determine minimum and maximum axis bounds. 571 // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0. 572 this.pad = 1.2; 573 // prop: padMax 574 // Padding to extend the range above data bounds. 575 // The top of the data range is multiplied by this factor to determine maximum axis bounds. 576 // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0. 577 this.padMax = null; 578 // prop: padMin 579 // Padding to extend the range below data bounds. 580 // The bottom of the data range is multiplied by this factor to determine minimum axis bounds. 581 // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0. 582 this.padMin = null; 583 // prop: ticks 584 // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis. 585 // If no label is specified, the value is formatted into an appropriate label. 586 this.ticks = []; 587 // prop: numberTicks 588 // Desired number of ticks. Default is to compute automatically. 589 this.numberTicks; 590 // prop: tickInterval 591 // number of units between ticks. Mutually exclusive with numberTicks. 592 this.tickInterval; 593 // prop: renderer 594 // A class of a rendering engine that handles tick generation, 595 // scaling input data to pixel grid units and drawing the axis element. 596 this.renderer = $.jqplot.LinearAxisRenderer; 597 // prop: rendererOptions 598 // renderer specific options. See <$.jqplot.LinearAxisRenderer> for options. 599 this.rendererOptions = {}; 600 // prop: showTicks 601 // Wether to show the ticks (both marks and labels) or not. 602 // Will not override showMark and showLabel options if specified on the ticks themselves. 603 this.showTicks = true; 604 // prop: showTickMarks 605 // Wether to show the tick marks (line crossing grid) or not. 606 // Overridden by showTicks and showMark option of tick itself. 607 this.showTickMarks = true; 608 // prop: showMinorTicks 609 // Wether or not to show minor ticks. This is renderer dependent. 610 this.showMinorTicks = true; 611 // prop: drawMajorGridlines 612 // True to draw gridlines for major axis ticks. 613 this.drawMajorGridlines = true; 614 // prop: drawMinorGridlines 615 // True to draw gridlines for minor ticks. 616 this.drawMinorGridlines = false; 617 // prop: drawMajorTickMarks 618 // True to draw tick marks for major axis ticks. 619 this.drawMajorTickMarks = true; 620 // prop: drawMinorTickMarks 621 // True to draw tick marks for minor ticks. This is renderer dependent. 622 this.drawMinorTickMarks = true; 623 // prop: useSeriesColor 624 // Use the color of the first series associated with this axis for the 625 // tick marks and line bordering this axis. 626 this.useSeriesColor = false; 627 // prop: borderWidth 628 // width of line stroked at the border of the axis. Defaults 629 // to the width of the grid boarder. 630 this.borderWidth = null; 631 // prop: borderColor 632 // color of the border adjacent to the axis. Defaults to grid border color. 633 this.borderColor = null; 634 // prop: scaleToHiddenSeries 635 // True to include hidden series when computing axes bounds and scaling. 636 this.scaleToHiddenSeries = false; 637 // minimum and maximum values on the axis. 638 this._dataBounds = {min:null, max:null}; 639 // statistics (min, max, mean) as well as actual data intervals for each series attached to axis. 640 // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis. 641 this._intervalStats = []; 642 // pixel position from the top left of the min value and max value on the axis. 643 this._offsets = {min:null, max:null}; 644 this._ticks=[]; 645 this._label = null; 646 // prop: syncTicks 647 // true to try and synchronize tick spacing across multiple axes so that ticks and 648 // grid lines line up. This has an impact on autoscaling algorithm, however. 649 // In general, autoscaling an individual axis will work better if it does not 650 // have to sync ticks. 651 this.syncTicks = null; 652 // prop: tickSpacing 653 // Approximate pixel spacing between ticks on graph. Used during autoscaling. 654 // This number will be an upper bound, actual spacing will be less. 655 this.tickSpacing = 75; 656 // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks 657 // so they can be restored if altered by plugins. 658 this._min = null; 659 this._max = null; 660 this._tickInterval = null; 661 this._numberTicks = null; 662 this.__ticks = null; 663 // hold original user options. 664 this._options = {}; 665 } 666 667 Axis.prototype = new $.jqplot.ElemContainer(); 668 Axis.prototype.constructor = Axis; 669 670 Axis.prototype.init = function() { 671 if ($.isFunction(this.renderer)) { 672 this.renderer = new this.renderer(); 673 } 674 // set the axis name 675 this.tickOptions.axis = this.name; 676 // if showMark or showLabel tick options not specified, use value of axis option. 677 // showTicks overrides showTickMarks. 678 if (this.tickOptions.showMark == null) { 679 this.tickOptions.showMark = this.showTicks; 680 } 681 if (this.tickOptions.showMark == null) { 682 this.tickOptions.showMark = this.showTickMarks; 683 } 684 if (this.tickOptions.showLabel == null) { 685 this.tickOptions.showLabel = this.showTicks; 686 } 687 688 if (this.label == null || this.label == '') { 689 this.showLabel = false; 690 } 691 else { 692 this.labelOptions.label = this.label; 693 } 694 if (this.showLabel == false) { 695 this.labelOptions.show = false; 696 } 697 // set the default padMax, padMin if not specified 698 // special check, if no padding desired, padding 699 // should be set to 1.0 700 if (this.pad == 0) { 701 this.pad = 1.0; 702 } 703 if (this.padMax == 0) { 704 this.padMax = 1.0; 705 } 706 if (this.padMin == 0) { 707 this.padMin = 1.0; 708 } 709 if (this.padMax == null) { 710 this.padMax = (this.pad-1)/2 + 1; 711 } 712 if (this.padMin == null) { 713 this.padMin = (this.pad-1)/2 + 1; 714 } 715 // now that padMin and padMax are correctly set, reset pad in case user has supplied 716 // padMin and/or padMax 717 this.pad = this.padMax + this.padMin - 1; 718 if (this.min != null || this.max != null) { 719 this.autoscale = false; 720 } 721 // if not set, sync ticks for y axes but not x by default. 722 if (this.syncTicks == null && this.name.indexOf('y') > -1) { 723 this.syncTicks = true; 724 } 725 else if (this.syncTicks == null){ 726 this.syncTicks = false; 727 } 728 this.renderer.init.call(this, this.rendererOptions); 729 730 }; 731 732 Axis.prototype.draw = function(ctx, plot) { 733 // Memory Leaks patch 734 if (this.__ticks) { 735 this.__ticks = null; 736 } 737 738 return this.renderer.draw.call(this, ctx, plot); 739 740 }; 741 742 Axis.prototype.set = function() { 743 this.renderer.set.call(this); 744 }; 745 746 Axis.prototype.pack = function(pos, offsets) { 747 if (this.show) { 748 this.renderer.pack.call(this, pos, offsets); 749 } 750 // these properties should all be available now. 751 if (this._min == null) { 752 this._min = this.min; 753 this._max = this.max; 754 this._tickInterval = this.tickInterval; 755 this._numberTicks = this.numberTicks; 756 this.__ticks = this._ticks; 757 } 758 }; 759 760 // reset the axis back to original values if it has been scaled, zoomed, etc. 761 Axis.prototype.reset = function() { 762 this.renderer.reset.call(this); 763 }; 764 765 Axis.prototype.resetScale = function(opts) { 766 $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts); 767 this.resetDataBounds(); 768 }; 769 770 Axis.prototype.resetDataBounds = function() { 771 // Go through all the series attached to this axis and find 772 // the min/max bounds for this axis. 773 var db = this._dataBounds; 774 db.min = null; 775 db.max = null; 776 var l, s, d; 777 // check for when to force min 0 on bar series plots. 778 var doforce = (this.show) ? true : false; 779 for (var i=0; i<this._series.length; i++) { 780 s = this._series[i]; 781 if (s.show || this.scaleToHiddenSeries) { 782 d = s._plotData; 783 if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') { 784 d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]]; 785 } 786 787 var minyidx = 1, maxyidx = 1; 788 789 if (s._type != null && s._type == 'ohlc') { 790 minyidx = 3; 791 maxyidx = 2; 792 } 793 794 for (var j=0, l=d.length; j<l; j++) { 795 if (this.name == 'xaxis' || this.name == 'x2axis') { 796 if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) { 797 db.min = d[j][0]; 798 } 799 if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) { 800 db.max = d[j][0]; 801 } 802 } 803 else { 804 if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) { 805 db.min = d[j][minyidx]; 806 } 807 if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) { 808 db.max = d[j][maxyidx]; 809 } 810 } 811 } 812 813 // Hack to not pad out bottom of bar plots unless user has specified a padding. 814 // every series will have a chance to set doforce to false. once it is set to 815 // false, it cannot be reset to true. 816 // If any series attached to axis is not a bar, wont force 0. 817 if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) { 818 doforce = false; 819 } 820 821 else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) { 822 doforce = false; 823 } 824 825 else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) { 826 if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') { 827 if (this._options.pad != null || this._options.padMin != null) { 828 doforce = false; 829 } 830 } 831 832 else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) { 833 if (this._options.pad != null || this._options.padMin != null) { 834 doforce = false; 835 } 836 } 837 838 } 839 } 840 } 841 842 if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) { 843 this.padMin = 1.0; 844 this.forceTickAt0 = true; 845 } 846 }; 847 848 /** 849 * Class: Legend 850 * Legend object. Cannot be instantiated directly, but created 851 * by the Plot oject. Legend properties can be set or overriden by the 852 * options passed in from the user. 853 */ 854 function Legend(options) { 855 $.jqplot.ElemContainer.call(this); 856 // Group: Properties 857 858 // prop: show 859 // Wether to display the legend on the graph. 860 this.show = false; 861 // prop: location 862 // Placement of the legend. one of the compass directions: nw, n, ne, e, se, s, sw, w 863 this.location = 'ne'; 864 // prop: labels 865 // Array of labels to use. By default the renderer will look for labels on the series. 866 // Labels specified in this array will override labels specified on the series. 867 this.labels = []; 868 // prop: showLabels 869 // true to show the label text on the legend. 870 this.showLabels = true; 871 // prop: showSwatch 872 // true to show the color swatches on the legend. 873 this.showSwatches = true; 874 // prop: placement 875 // "insideGrid" places legend inside the grid area of the plot. 876 // "outsideGrid" places the legend outside the grid but inside the plot container, 877 // shrinking the grid to accomodate the legend. 878 // "inside" synonym for "insideGrid", 879 // "outside" places the legend ouside the grid area, but does not shrink the grid which 880 // can cause the legend to overflow the plot container. 881 this.placement = "insideGrid"; 882 // prop: xoffset 883 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc. 884 // properties or via CSS margin styling of the .jqplot-table-legend class. 885 this.xoffset = 0; 886 // prop: yoffset 887 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc. 888 // properties or via CSS margin styling of the .jqplot-table-legend class. 889 this.yoffset = 0; 890 // prop: border 891 // css spec for the border around the legend box. 892 this.border; 893 // prop: background 894 // css spec for the background of the legend box. 895 this.background; 896 // prop: textColor 897 // css color spec for the legend text. 898 this.textColor; 899 // prop: fontFamily 900 // css font-family spec for the legend text. 901 this.fontFamily; 902 // prop: fontSize 903 // css font-size spec for the legend text. 904 this.fontSize ; 905 // prop: rowSpacing 906 // css padding-top spec for the rows in the legend. 907 this.rowSpacing = '0.5em'; 908 // renderer 909 // A class that will create a DOM object for the legend, 910 // see <$.jqplot.TableLegendRenderer>. 911 this.renderer = $.jqplot.TableLegendRenderer; 912 // prop: rendererOptions 913 // renderer specific options passed to the renderer. 914 this.rendererOptions = {}; 915 // prop: predraw 916 // Wether to draw the legend before the series or not. 917 // Used with series specific legend renderers for pie, donut, mekko charts, etc. 918 this.preDraw = false; 919 // prop: marginTop 920 // CSS margin for the legend DOM element. This will set an element 921 // CSS style for the margin which will override any style sheet setting. 922 // The default will be taken from the stylesheet. 923 this.marginTop = null; 924 // prop: marginRight 925 // CSS margin for the legend DOM element. This will set an element 926 // CSS style for the margin which will override any style sheet setting. 927 // The default will be taken from the stylesheet. 928 this.marginRight = null; 929 // prop: marginBottom 930 // CSS margin for the legend DOM element. This will set an element 931 // CSS style for the margin which will override any style sheet setting. 932 // The default will be taken from the stylesheet. 933 this.marginBottom = null; 934 // prop: marginLeft 935 // CSS margin for the legend DOM element. This will set an element 936 // CSS style for the margin which will override any style sheet setting. 937 // The default will be taken from the stylesheet. 938 this.marginLeft = null; 939 // prop: escapeHtml 940 // True to escape special characters with their html entity equivalents 941 // in legend text. "<" becomes < and so on, so html tags are not rendered. 942 this.escapeHtml = false; 943 this._series = []; 944 945 $.extend(true, this, options); 946 } 947 948 Legend.prototype = new $.jqplot.ElemContainer(); 949 Legend.prototype.constructor = Legend; 950 951 Legend.prototype.setOptions = function(options) { 952 $.extend(true, this, options); 953 954 // Try to emulate deprecated behaviour 955 // if user has specified xoffset or yoffset, copy these to 956 // the margin properties. 957 958 if (this.placement == 'inside') { 959 this.placement = 'insideGrid'; 960 } 961 962 if (this.xoffset >0) { 963 if (this.placement == 'insideGrid') { 964 switch (this.location) { 965 case 'nw': 966 case 'w': 967 case 'sw': 968 if (this.marginLeft == null) { 969 this.marginLeft = this.xoffset + 'px'; 970 } 971 this.marginRight = '0px'; 972 break; 973 case 'ne': 974 case 'e': 975 case 'se': 976 default: 977 if (this.marginRight == null) { 978 this.marginRight = this.xoffset + 'px'; 979 } 980 this.marginLeft = '0px'; 981 break; 982 } 983 } 984 else if (this.placement == 'outside') { 985 switch (this.location) { 986 case 'nw': 987 case 'w': 988 case 'sw': 989 if (this.marginRight == null) { 990 this.marginRight = this.xoffset + 'px'; 991 } 992 this.marginLeft = '0px'; 993 break; 994 case 'ne': 995 case 'e': 996 case 'se': 997 default: 998 if (this.marginLeft == null) { 999 this.marginLeft = this.xoffset + 'px'; 1000 } 1001 this.marginRight = '0px'; 1002 break; 1003 } 1004 } 1005 this.xoffset = 0; 1006 } 1007 1008 if (this.yoffset >0) { 1009 if (this.placement == 'outside') { 1010 switch (this.location) { 1011 case 'sw': 1012 case 's': 1013 case 'se': 1014 if (this.marginTop == null) { 1015 this.marginTop = this.yoffset + 'px'; 1016 } 1017 this.marginBottom = '0px'; 1018 break; 1019 case 'ne': 1020 case 'n': 1021 case 'nw': 1022 default: 1023 if (this.marginBottom == null) { 1024 this.marginBottom = this.yoffset + 'px'; 1025 } 1026 this.marginTop = '0px'; 1027 break; 1028 } 1029 } 1030 else if (this.placement == 'insideGrid') { 1031 switch (this.location) { 1032 case 'sw': 1033 case 's': 1034 case 'se': 1035 if (this.marginBottom == null) { 1036 this.marginBottom = this.yoffset + 'px'; 1037 } 1038 this.marginTop = '0px'; 1039 break; 1040 case 'ne': 1041 case 'n': 1042 case 'nw': 1043 default: 1044 if (this.marginTop == null) { 1045 this.marginTop = this.yoffset + 'px'; 1046 } 1047 this.marginBottom = '0px'; 1048 break; 1049 } 1050 } 1051 this.yoffset = 0; 1052 } 1053 1054 // TO-DO: 1055 // Handle case where offsets are < 0. 1056 // 1057 }; 1058 1059 Legend.prototype.init = function() { 1060 if ($.isFunction(this.renderer)) { 1061 this.renderer = new this.renderer(); 1062 } 1063 this.renderer.init.call(this, this.rendererOptions); 1064 }; 1065 1066 Legend.prototype.draw = function(offsets, plot) { 1067 for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){ 1068 $.jqplot.preDrawLegendHooks[i].call(this, offsets); 1069 } 1070 return this.renderer.draw.call(this, offsets, plot); 1071 }; 1072 1073 Legend.prototype.pack = function(offsets) { 1074 this.renderer.pack.call(this, offsets); 1075 }; 1076 1077 /** 1078 * Class: Title 1079 * Plot Title object. Cannot be instantiated directly, but created 1080 * by the Plot oject. Title properties can be set or overriden by the 1081 * options passed in from the user. 1082 * 1083 * Parameters: 1084 * text - text of the title. 1085 */ 1086 function Title(text) { 1087 $.jqplot.ElemContainer.call(this); 1088 // Group: Properties 1089 1090 // prop: text 1091 // text of the title; 1092 this.text = text; 1093 // prop: show 1094 // wether or not to show the title 1095 this.show = true; 1096 // prop: fontFamily 1097 // css font-family spec for the text. 1098 this.fontFamily; 1099 // prop: fontSize 1100 // css font-size spec for the text. 1101 this.fontSize ; 1102 // prop: textAlign 1103 // css text-align spec for the text. 1104 this.textAlign; 1105 // prop: textColor 1106 // css color spec for the text. 1107 this.textColor; 1108 // prop: renderer 1109 // A class for creating a DOM element for the title, 1110 // see <$.jqplot.DivTitleRenderer>. 1111 this.renderer = $.jqplot.DivTitleRenderer; 1112 // prop: rendererOptions 1113 // renderer specific options passed to the renderer. 1114 this.rendererOptions = {}; 1115 // prop: escapeHtml 1116 // True to escape special characters with their html entity equivalents 1117 // in title text. "<" becomes < and so on, so html tags are not rendered. 1118 this.escapeHtml = false; 1119 } 1120 1121 Title.prototype = new $.jqplot.ElemContainer(); 1122 Title.prototype.constructor = Title; 1123 1124 Title.prototype.init = function() { 1125 if ($.isFunction(this.renderer)) { 1126 this.renderer = new this.renderer(); 1127 } 1128 this.renderer.init.call(this, this.rendererOptions); 1129 }; 1130 1131 Title.prototype.draw = function(width) { 1132 return this.renderer.draw.call(this, width); 1133 }; 1134 1135 Title.prototype.pack = function() { 1136 this.renderer.pack.call(this); 1137 }; 1138 1139 1140 /** 1141 * Class: Series 1142 * An individual data series object. Cannot be instantiated directly, but created 1143 * by the Plot oject. Series properties can be set or overriden by the 1144 * options passed in from the user. 1145 */ 1146 function Series() { 1147 $.jqplot.ElemContainer.call(this); 1148 // Group: Properties 1149 // Properties will be assigned from a series array at the top level of the 1150 // options. If you had two series and wanted to change the color and line 1151 // width of the first and set the second to use the secondary y axis with 1152 // no shadow and supply custom labels for each: 1153 // > { 1154 // > series:[ 1155 // > {color: '#ff4466', lineWidth: 5, label:'good line'}, 1156 // > {yaxis: 'y2axis', shadow: false, label:'bad line'} 1157 // > ] 1158 // > } 1159 1160 // prop: show 1161 // wether or not to draw the series. 1162 this.show = true; 1163 // prop: xaxis 1164 // which x axis to use with this series, either 'xaxis' or 'x2axis'. 1165 this.xaxis = 'xaxis'; 1166 this._xaxis; 1167 // prop: yaxis 1168 // which y axis to use with this series, either 'yaxis' or 'y2axis'. 1169 this.yaxis = 'yaxis'; 1170 this._yaxis; 1171 this.gridBorderWidth = 2.0; 1172 // prop: renderer 1173 // A class of a renderer which will draw the series, 1174 // see <$.jqplot.LineRenderer>. 1175 this.renderer = $.jqplot.LineRenderer; 1176 // prop: rendererOptions 1177 // Options to pass on to the renderer. 1178 this.rendererOptions = {}; 1179 this.data = []; 1180 this.gridData = []; 1181 // prop: label 1182 // Line label to use in the legend. 1183 this.label = ''; 1184 // prop: showLabel 1185 // true to show label for this series in the legend. 1186 this.showLabel = true; 1187 // prop: color 1188 // css color spec for the series 1189 this.color; 1190 // prop: negativeColor 1191 // css color spec used for filled (area) plots that are filled to zero and 1192 // the "useNegativeColors" option is true. 1193 this.negativeColor; 1194 // prop: lineWidth 1195 // width of the line in pixels. May have different meanings depending on renderer. 1196 this.lineWidth = 2.5; 1197 // prop: lineJoin 1198 // Canvas lineJoin style between segments of series. 1199 this.lineJoin = 'round'; 1200 // prop: lineCap 1201 // Canvas lineCap style at ends of line. 1202 this.lineCap = 'round'; 1203 // prop: linePattern 1204 // line pattern 'dashed', 'dotted', 'solid', some combination 1205 // of '-' and '.' characters such as '.-.' or a numerical array like 1206 // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, 1207 // [1, 10, 20, 10] to draw a dot-dash line, and so on. 1208 this.linePattern = 'solid'; 1209 this.shadow = true; 1210 // prop: shadowAngle 1211 // Shadow angle in degrees 1212 this.shadowAngle = 45; 1213 // prop: shadowOffset 1214 // Shadow offset from line in pixels 1215 this.shadowOffset = 1.25; 1216 // prop: shadowDepth 1217 // Number of times shadow is stroked, each stroke offset shadowOffset from the last. 1218 this.shadowDepth = 3; 1219 // prop: shadowAlpha 1220 // Alpha channel transparency of shadow. 0 = transparent. 1221 this.shadowAlpha = '0.1'; 1222 // prop: breakOnNull 1223 // Wether line segments should be be broken at null value. 1224 // False will join point on either side of line. 1225 this.breakOnNull = false; 1226 // prop: markerRenderer 1227 // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points, 1228 // see <$.jqplot.MarkerRenderer>. 1229 this.markerRenderer = $.jqplot.MarkerRenderer; 1230 // prop: markerOptions 1231 // renderer specific options to pass to the markerRenderer, 1232 // see <$.jqplot.MarkerRenderer>. 1233 this.markerOptions = {}; 1234 // prop: showLine 1235 // wether to actually draw the line or not. Series will still be renderered, even if no line is drawn. 1236 this.showLine = true; 1237 // prop: showMarker 1238 // wether or not to show the markers at the data points. 1239 this.showMarker = true; 1240 // prop: index 1241 // 0 based index of this series in the plot series array. 1242 this.index; 1243 // prop: fill 1244 // true or false, wether to fill under lines or in bars. 1245 // May not be implemented in all renderers. 1246 this.fill = false; 1247 // prop: fillColor 1248 // CSS color spec to use for fill under line. Defaults to line color. 1249 this.fillColor; 1250 // prop: fillAlpha 1251 // Alpha transparency to apply to the fill under the line. 1252 // Use this to adjust alpha separate from fill color. 1253 this.fillAlpha; 1254 // prop: fillAndStroke 1255 // If true will stroke the line (with color this.color) as well as fill under it. 1256 // Applies only when fill is true. 1257 this.fillAndStroke = false; 1258 // prop: disableStack 1259 // true to not stack this series with other series in the plot. 1260 // To render properly, non-stacked series must come after any stacked series 1261 // in the plot's data series array. So, the plot's data series array would look like: 1262 // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...] 1263 // disableStack will put a gap in the stacking order of series, and subsequent 1264 // stacked series will not fill down through the non-stacked series and will 1265 // most likely not stack properly on top of the non-stacked series. 1266 this.disableStack = false; 1267 // _stack is set by the Plot if the plot is a stacked chart. 1268 // will stack lines or bars on top of one another to build a "mountain" style chart. 1269 // May not be implemented in all renderers. 1270 this._stack = false; 1271 // prop: neighborThreshold 1272 // how close or far (in pixels) the cursor must be from a point marker to detect the point. 1273 this.neighborThreshold = 4; 1274 // prop: fillToZero 1275 // true will force bar and filled series to fill toward zero on the fill Axis. 1276 this.fillToZero = false; 1277 // prop: fillToValue 1278 // fill a filled series to this value on the fill axis. 1279 // Works in conjunction with fillToZero, so that must be true. 1280 this.fillToValue = 0; 1281 // prop: fillAxis 1282 // Either 'x' or 'y'. Which axis to fill the line toward if fillToZero is true. 1283 // 'y' means fill up/down to 0 on the y axis for this series. 1284 this.fillAxis = 'y'; 1285 // prop: useNegativeColors 1286 // true to color negative values differently in filled and bar charts. 1287 this.useNegativeColors = true; 1288 this._stackData = []; 1289 // _plotData accounts for stacking. If plots not stacked, _plotData and data are same. If 1290 // stacked, _plotData is accumulation of stacking data. 1291 this._plotData = []; 1292 // _plotValues hold the individual x and y values that will be plotted for this series. 1293 this._plotValues = {x:[], y:[]}; 1294 // statistics about the intervals between data points. Used for auto scaling. 1295 this._intervals = {x:{}, y:{}}; 1296 // data from the previous series, for stacked charts. 1297 this._prevPlotData = []; 1298 this._prevGridData = []; 1299 this._stackAxis = 'y'; 1300 this._primaryAxis = '_xaxis'; 1301 // give each series a canvas to draw on. This should allow for redrawing speedups. 1302 this.canvas = new $.jqplot.GenericCanvas(); 1303 this.shadowCanvas = new $.jqplot.GenericCanvas(); 1304 this.plugins = {}; 1305 // sum of y values in this series. 1306 this._sumy = 0; 1307 this._sumx = 0; 1308 this._type = ''; 1309 } 1310 1311 Series.prototype = new $.jqplot.ElemContainer(); 1312 Series.prototype.constructor = Series; 1313 1314 Series.prototype.init = function(index, gridbw, plot) { 1315 // weed out any null values in the data. 1316 this.index = index; 1317 this.gridBorderWidth = gridbw; 1318 var d = this.data; 1319 var temp = [], i; 1320 for (i=0; i<d.length; i++) { 1321 if (! this.breakOnNull) { 1322 if (d[i] == null || d[i][0] == null || d[i][1] == null) { 1323 continue; 1324 } 1325 else { 1326 temp.push(d[i]); 1327 } 1328 } 1329 else { 1330 // TODO: figure out what to do with null values 1331 // probably involve keeping nulls in data array 1332 // and then updating renderers to break line 1333 // when it hits null value. 1334 // For now, just keep value. 1335 temp.push(d[i]); 1336 } 1337 } 1338 this.data = temp; 1339 1340 // parse the renderer options and apply default colors if not provided 1341 // Set color even if not shown, so series don't change colors when other 1342 // series on plot shown/hidden. 1343 if (!this.color) { 1344 this.color = plot.colorGenerator.get(this.index); 1345 } 1346 if (!this.negativeColor) { 1347 this.negativeColor = plot.negativeColorGenerator.get(this.index); 1348 } 1349 1350 1351 if (!this.fillColor) { 1352 this.fillColor = this.color; 1353 } 1354 if (this.fillAlpha) { 1355 var comp = $.jqplot.normalize2rgb(this.fillColor); 1356 var comp = $.jqplot.getColorComponents(comp); 1357 this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')'; 1358 } 1359 if ($.isFunction(this.renderer)) { 1360 this.renderer = new this.renderer(); 1361 } 1362 this.renderer.init.call(this, this.rendererOptions, plot); 1363 this.markerRenderer = new this.markerRenderer(); 1364 if (!this.markerOptions.color) { 1365 this.markerOptions.color = this.color; 1366 } 1367 if (this.markerOptions.show == null) { 1368 this.markerOptions.show = this.showMarker; 1369 } 1370 this.showMarker = this.markerOptions.show; 1371 // the markerRenderer is called within it's own scaope, don't want to overwrite series options!! 1372 this.markerRenderer.init(this.markerOptions); 1373 }; 1374 1375 // data - optional data point array to draw using this series renderer 1376 // gridData - optional grid data point array to draw using this series renderer 1377 // stackData - array of cumulative data for stacked plots. 1378 Series.prototype.draw = function(sctx, opts, plot) { 1379 var options = (opts == undefined) ? {} : opts; 1380 sctx = (sctx == undefined) ? this.canvas._ctx : sctx; 1381 1382 var j, data, gridData; 1383 1384 // hooks get called even if series not shown 1385 // we don't clear canvas here, it would wipe out all other series as well. 1386 for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) { 1387 $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options); 1388 } 1389 if (this.show) { 1390 this.renderer.setGridData.call(this, plot); 1391 if (!options.preventJqPlotSeriesDrawTrigger) { 1392 $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]); 1393 } 1394 data = []; 1395 if (options.data) { 1396 data = options.data; 1397 } 1398 else if (!this._stack) { 1399 data = this.data; 1400 } 1401 else { 1402 data = this._plotData; 1403 } 1404 gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot); 1405 1406 if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) { 1407 gridData = this.renderer._smoothedData; 1408 } 1409 1410 this.renderer.draw.call(this, sctx, gridData, options, plot); 1411 } 1412 1413 for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) { 1414 $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options, plot); 1415 } 1416 1417 sctx = opts = plot = j = data = gridData = null; 1418 }; 1419 1420 Series.prototype.drawShadow = function(sctx, opts, plot) { 1421 var options = (opts == undefined) ? {} : opts; 1422 sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx; 1423 1424 var j, data, gridData; 1425 1426 // hooks get called even if series not shown 1427 // we don't clear canvas here, it would wipe out all other series as well. 1428 for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) { 1429 $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options); 1430 } 1431 if (this.shadow) { 1432 this.renderer.setGridData.call(this, plot); 1433 1434 data = []; 1435 if (options.data) { 1436 data = options.data; 1437 } 1438 else if (!this._stack) { 1439 data = this.data; 1440 } 1441 else { 1442 data = this._plotData; 1443 } 1444 gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot); 1445 1446 this.renderer.drawShadow.call(this, sctx, gridData, options, plot); 1447 } 1448 1449 for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) { 1450 $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options); 1451 } 1452 1453 sctx = opts = plot = j = data = gridData = null; 1454 1455 }; 1456 1457 // toggles series display on plot, e.g. show/hide series 1458 Series.prototype.toggleDisplay = function(ev, callback) { 1459 var s, speed; 1460 if (ev.data.series) { 1461 s = ev.data.series; 1462 } 1463 else { 1464 s = this; 1465 } 1466 1467 if (ev.data.speed) { 1468 speed = ev.data.speed; 1469 } 1470 if (speed) { 1471 // this can be tricky because series may not have a canvas element if replotting. 1472 if (s.canvas._elem.is(':hidden') || !s.show) { 1473 s.show = true; 1474 1475 s.canvas._elem.removeClass('jqplot-series-hidden'); 1476 if (s.shadowCanvas._elem) { 1477 s.shadowCanvas._elem.fadeIn(speed); 1478 } 1479 s.canvas._elem.fadeIn(speed, callback); 1480 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed); 1481 } 1482 else { 1483 s.show = false; 1484 1485 s.canvas._elem.addClass('jqplot-series-hidden'); 1486 if (s.shadowCanvas._elem) { 1487 s.shadowCanvas._elem.fadeOut(speed); 1488 } 1489 s.canvas._elem.fadeOut(speed, callback); 1490 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed); 1491 } 1492 } 1493 else { 1494 // this can be tricky because series may not have a canvas element if replotting. 1495 if (s.canvas._elem.is(':hidden') || !s.show) { 1496 s.show = true; 1497 1498 s.canvas._elem.removeClass('jqplot-series-hidden'); 1499 if (s.shadowCanvas._elem) { 1500 s.shadowCanvas._elem.show(); 1501 } 1502 s.canvas._elem.show(0, callback); 1503 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show(); 1504 } 1505 else { 1506 s.show = false; 1507 1508 s.canvas._elem.addClass('jqplot-series-hidden'); 1509 if (s.shadowCanvas._elem) { 1510 s.shadowCanvas._elem.hide(); 1511 } 1512 s.canvas._elem.hide(0, callback); 1513 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide(); 1514 } 1515 } 1516 }; 1517 1518 1519 1520 /** 1521 * Class: Grid 1522 * 1523 * Object representing the grid on which the plot is drawn. The grid in this 1524 * context is the area bounded by the axes, the area which will contain the series. 1525 * Note, the series are drawn on their own canvas. 1526 * The Grid object cannot be instantiated directly, but is created by the Plot oject. 1527 * Grid properties can be set or overriden by the options passed in from the user. 1528 */ 1529 function Grid() { 1530 $.jqplot.ElemContainer.call(this); 1531 // Group: Properties 1532 1533 // prop: drawGridlines 1534 // wether to draw the gridlines on the plot. 1535 this.drawGridlines = true; 1536 // prop: gridLineColor 1537 // color of the grid lines. 1538 this.gridLineColor = '#cccccc'; 1539 // prop: gridLineWidth 1540 // width of the grid lines. 1541 this.gridLineWidth = 1.0; 1542 // prop: background 1543 // css spec for the background color. 1544 this.background = '#fffdf6'; 1545 // prop: borderColor 1546 // css spec for the color of the grid border. 1547 this.borderColor = '#999999'; 1548 // prop: borderWidth 1549 // width of the border in pixels. 1550 this.borderWidth = 2.0; 1551 // prop: drawBorder 1552 // True to draw border around grid. 1553 this.drawBorder = true; 1554 // prop: shadow 1555 // wether to show a shadow behind the grid. 1556 this.shadow = true; 1557 // prop: shadowAngle 1558 // shadow angle in degrees 1559 this.shadowAngle = 45; 1560 // prop: shadowOffset 1561 // Offset of each shadow stroke from the border in pixels 1562 this.shadowOffset = 1.5; 1563 // prop: shadowWidth 1564 // width of the stoke for the shadow 1565 this.shadowWidth = 3; 1566 // prop: shadowDepth 1567 // Number of times shadow is stroked, each stroke offset shadowOffset from the last. 1568 this.shadowDepth = 3; 1569 // prop: shadowColor 1570 // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form 1571 this.shadowColor = null; 1572 // prop: shadowAlpha 1573 // Alpha channel transparency of shadow. 0 = transparent. 1574 this.shadowAlpha = '0.07'; 1575 this._left; 1576 this._top; 1577 this._right; 1578 this._bottom; 1579 this._width; 1580 this._height; 1581 this._axes = []; 1582 // prop: renderer 1583 // Instance of a renderer which will actually render the grid, 1584 // see <$.jqplot.CanvasGridRenderer>. 1585 this.renderer = $.jqplot.CanvasGridRenderer; 1586 // prop: rendererOptions 1587 // Options to pass on to the renderer, 1588 // see <$.jqplot.CanvasGridRenderer>. 1589 this.rendererOptions = {}; 1590 this._offsets = {top:null, bottom:null, left:null, right:null}; 1591 } 1592 1593 Grid.prototype = new $.jqplot.ElemContainer(); 1594 Grid.prototype.constructor = Grid; 1595 1596 Grid.prototype.init = function() { 1597 if ($.isFunction(this.renderer)) { 1598 this.renderer = new this.renderer(); 1599 } 1600 this.renderer.init.call(this, this.rendererOptions); 1601 }; 1602 1603 Grid.prototype.createElement = function(offsets,plot) { 1604 this._offsets = offsets; 1605 return this.renderer.createElement.call(this, plot); 1606 }; 1607 1608 Grid.prototype.draw = function() { 1609 this.renderer.draw.call(this); 1610 }; 1611 1612 $.jqplot.GenericCanvas = function() { 1613 $.jqplot.ElemContainer.call(this); 1614 this._ctx; 1615 }; 1616 1617 $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer(); 1618 $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas; 1619 1620 $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) { 1621 this._offsets = offsets; 1622 var klass = 'jqplot'; 1623 if (clss != undefined) { 1624 klass = clss; 1625 } 1626 var elem; 1627 1628 elem = plot.canvasManager.getCanvas(); 1629 1630 // if new plotDimensions supplied, use them. 1631 if (plotDimensions != null) { 1632 this._plotDimensions = plotDimensions; 1633 } 1634 1635 elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right; 1636 elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom; 1637 this._elem = $(elem); 1638 this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top }); 1639 1640 this._elem.addClass(klass); 1641 1642 elem = plot.canvasManager.initCanvas(elem); 1643 1644 elem = null; 1645 return this._elem; 1646 }; 1647 1648 $.jqplot.GenericCanvas.prototype.setContext = function() { 1649 this._ctx = this._elem.get(0).getContext("2d"); 1650 return this._ctx; 1651 }; 1652 1653 // Memory Leaks patch 1654 $.jqplot.GenericCanvas.prototype.resetCanvas = function() { 1655 if (this._elem) { 1656 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { 1657 window.G_vmlCanvasManager.uninitElement(this._elem.get(0)); 1658 } 1659 1660 //this._elem.remove(); 1661 this._elem.emptyForce(); 1662 } 1663 1664 this._ctx = null; 1665 }; 1666 1667 $.jqplot.HooksManager = function () { 1668 this.hooks =[]; 1669 this.args = []; 1670 }; 1671 1672 $.jqplot.HooksManager.prototype.addOnce = function(fn, args) { 1673 args = args || []; 1674 var havehook = false; 1675 for (var i=0, l=this.hooks.length; i<l; i++) { 1676 if (this.hooks[i] == fn) { 1677 havehook = true; 1678 } 1679 } 1680 if (!havehook) { 1681 this.hooks.push(fn); 1682 this.args.push(args); 1683 } 1684 }; 1685 1686 $.jqplot.HooksManager.prototype.add = function(fn, args) { 1687 args = args || []; 1688 this.hooks.push(fn); 1689 this.args.push(args); 1690 }; 1691 1692 $.jqplot.EventListenerManager = function () { 1693 this.hooks =[]; 1694 }; 1695 1696 $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) { 1697 var havehook = false, h, i; 1698 for (var i=0, l=this.hooks.length; i<l; i++) { 1699 h = this.hooks[i]; 1700 if (h[0] == ev && h[1] == fn) { 1701 havehook = true; 1702 } 1703 } 1704 if (!havehook) { 1705 this.hooks.push([ev, fn]); 1706 } 1707 }; 1708 1709 $.jqplot.EventListenerManager.prototype.add = function(ev, fn) { 1710 this.hooks.push([ev, fn]); 1711 }; 1712 1713 1714 var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; 1715 1716 /** 1717 * Class: jqPlot 1718 * Plot object returned by call to $.jqplot. Handles parsing user options, 1719 * creating sub objects (Axes, legend, title, series) and rendering the plot. 1720 */ 1721 function jqPlot() { 1722 // Group: Properties 1723 // These properties are specified at the top of the options object 1724 // like so: 1725 // > { 1726 // > axesDefaults:{min:0}, 1727 // > series:[{color:'#6633dd'}], 1728 // > title: 'A Plot' 1729 // > } 1730 // 1731 1732 // prop: animate 1733 // True to animate the series on initial plot draw (renderer dependent). 1734 // Actual animation functionality must be supported in the renderer. 1735 this.animate = false; 1736 // prop: animateReplot 1737 // True to animate series after a call to the replot() method. 1738 // Use with caution! Replots can happen very frequently under 1739 // certain circumstances (e.g. resizing, dragging points) and 1740 // animation in these situations can cause problems. 1741 this.animateReplot = false; 1742 // prop: axes 1743 // up to 4 axes are supported, each with it's own options, 1744 // See <Axis> for axis specific options. 1745 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')}; 1746 this.baseCanvas = new $.jqplot.GenericCanvas(); 1747 // true to intercept right click events and fire a 'jqplotRightClick' event. 1748 // this will also block the context menu. 1749 this.captureRightClick = false; 1750 // prop: data 1751 // user's data. Data should *NOT* be specified in the options object, 1752 // but be passed in as the second argument to the $.jqplot() function. 1753 // The data property is described here soley for reference. 1754 // The data should be in the form of an array of 2D or 1D arrays like 1755 // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ]. 1756 this.data = []; 1757 // prop: dataRenderer 1758 // A callable which can be used to preprocess data passed into the plot. 1759 // Will be called with 2 arguments, the plot data and a reference to the plot. 1760 this.dataRenderer; 1761 // prop: dataRendererOptions 1762 // Options that will be passed to the dataRenderer. 1763 // Can be of any type. 1764 this.dataRendererOptions; 1765 this.defaults = { 1766 // prop: axesDefaults 1767 // default options that will be applied to all axes. 1768 // see <Axis> for axes options. 1769 axesDefaults: {}, 1770 axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}}, 1771 // prop: seriesDefaults 1772 // default options that will be applied to all series. 1773 // see <Series> for series options. 1774 seriesDefaults: {}, 1775 series:[] 1776 }; 1777 // prop: defaultAxisStart 1778 // 1-D data series are internally converted into 2-D [x,y] data point arrays 1779 // by jqPlot. This is the default starting value for the missing x or y value. 1780 // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...]) 1781 // starting at this value. 1782 this.defaultAxisStart = 1; 1783 // this.doCustomEventBinding = true; 1784 // prop: drawIfHidden 1785 // True to execute the draw method even if the plot target is hidden. 1786 // Generally, this should be false. Most plot elements will not be sized/ 1787 // positioned correclty if renderered into a hidden container. To render into 1788 // a hidden container, call the replot method when the container is shown. 1789 this.drawIfHidden = false; 1790 this.eventCanvas = new $.jqplot.GenericCanvas(); 1791 // prop: fillBetween 1792 // Fill between 2 line series in a plot. 1793 // Options object: 1794 // { 1795 // series1: first index (0 based) of series in fill 1796 // series2: second index (0 based) of series in fill 1797 // color: color of fill [default fillColor of series1] 1798 // baseSeries: fill will be drawn below this series (0 based index) 1799 // fill: false to turn off fill [default true]. 1800 // } 1801 this.fillBetween = { 1802 series1: null, 1803 series2: null, 1804 color: null, 1805 baseSeries: 0, 1806 fill: true 1807 }; 1808 // prop; fontFamily 1809 // css spec for the font-family attribute. Default for the entire plot. 1810 this.fontFamily; 1811 // prop: fontSize 1812 // css spec for the font-size attribute. Default for the entire plot. 1813 this.fontSize; 1814 // prop: grid 1815 // See <Grid> for grid specific options. 1816 this.grid = new Grid(); 1817 // prop: legend 1818 // see <$.jqplot.TableLegendRenderer> 1819 this.legend = new Legend(); 1820 // prop: noDataIndicator 1821 // Options to set up a mock plot with a data loading indicator if no data is specified. 1822 this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors; 1823 this.noDataIndicator = { 1824 show: false, 1825 indicator: 'Loading Data...', 1826 axes: { 1827 xaxis: { 1828 min: 0, 1829 max: 10, 1830 tickInterval: 2, 1831 show: true 1832 }, 1833 yaxis: { 1834 min: 0, 1835 max: 12, 1836 tickInterval: 3, 1837 show: true 1838 } 1839 } 1840 }; 1841 // container to hold all of the merged options. Convienence for plugins. 1842 this.options = {}; 1843 this.previousSeriesStack = []; 1844 // Namespece to hold plugins. Generally non-renderer plugins add themselves to here. 1845 this.plugins = {}; 1846 // prop: series 1847 // Array of series object options. 1848 // see <Series> for series specific options. 1849 this.series = []; 1850 // array of series indicies. Keep track of order 1851 // which series canvases are displayed, lowest 1852 // to highest, back to front. 1853 this.seriesStack = []; 1854 // prop: seriesColors 1855 // Ann array of CSS color specifications that will be applied, in order, 1856 // to the series in the plot. Colors will wrap around so, if their 1857 // are more series than colors, colors will be reused starting at the 1858 // beginning. For pie charts, this specifies the colors of the slices. 1859 this.seriesColors = $.jqplot.config.defaultColors; 1860 // prop: sortData 1861 // false to not sort the data passed in by the user. 1862 // Many bar, stakced and other graphs as well as many plugins depend on 1863 // having sorted data. 1864 this.sortData = true; 1865 // prop: stackSeries 1866 // true or false, creates a stack or "mountain" plot. 1867 // Not all series renderers may implement this option. 1868 this.stackSeries = false; 1869 // a shortcut for axis syncTicks options. Not implemented yet. 1870 this.syncXTicks = true; 1871 // a shortcut for axis syncTicks options. Not implemented yet. 1872 this.syncYTicks = true; 1873 // the jquery object for the dom target. 1874 this.target = null; 1875 // The id of the dom element to render the plot into 1876 this.targetId = null; 1877 // prop textColor 1878 // css spec for the css color attribute. Default for the entire plot. 1879 this.textColor; 1880 // prop: title 1881 // Title object. See <Title> for specific options. As a shortcut, you 1882 // can specify the title option as just a string like: title: 'My Plot' 1883 // and this will create a new title object with the specified text. 1884 this.title = new Title(); 1885 // Count how many times the draw method has been called while the plot is visible. 1886 // Mostly used to test if plot has never been dran (=0), has been successfully drawn 1887 // into a visible container once (=1) or draw more than once into a visible container. 1888 // Can use this in tests to see if plot has been visibly drawn at least one time. 1889 // After plot has been visibly drawn once, it generally doesn't need redrawn if its 1890 // container is hidden and shown. 1891 this._drawCount = 0; 1892 // sum of y values for all series in plot. 1893 // used in mekko chart. 1894 this._sumy = 0; 1895 this._sumx = 0; 1896 // array to hold the cumulative stacked series data. 1897 // used to ajust the individual series data, which won't have access to other 1898 // series data. 1899 this._stackData = []; 1900 // array that holds the data to be plotted. This will be the series data 1901 // merged with the the appropriate data from _stackData according to the stackAxis. 1902 this._plotData = []; 1903 this._width = null; 1904 this._height = null; 1905 this._plotDimensions = {height:null, width:null}; 1906 this._gridPadding = {top:null, right:null, bottom:null, left:null}; 1907 this._defaultGridPadding = {top:10, right:10, bottom:23, left:10}; 1908 1909 this._addDomReference = $.jqplot.config.addDomReference; 1910 1911 this.preInitHooks = new $.jqplot.HooksManager(); 1912 this.postInitHooks = new $.jqplot.HooksManager(); 1913 this.preParseOptionsHooks = new $.jqplot.HooksManager(); 1914 this.postParseOptionsHooks = new $.jqplot.HooksManager(); 1915 this.preDrawHooks = new $.jqplot.HooksManager(); 1916 this.postDrawHooks = new $.jqplot.HooksManager(); 1917 this.preDrawSeriesHooks = new $.jqplot.HooksManager(); 1918 this.postDrawSeriesHooks = new $.jqplot.HooksManager(); 1919 this.preDrawLegendHooks = new $.jqplot.HooksManager(); 1920 this.addLegendRowHooks = new $.jqplot.HooksManager(); 1921 this.preSeriesInitHooks = new $.jqplot.HooksManager(); 1922 this.postSeriesInitHooks = new $.jqplot.HooksManager(); 1923 this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager(); 1924 this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager(); 1925 this.eventListenerHooks = new $.jqplot.EventListenerManager(); 1926 this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager(); 1927 this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager(); 1928 1929 this.colorGenerator = new $.jqplot.ColorGenerator(); 1930 this.negativeColorGenerator = new $.jqplot.ColorGenerator(); 1931 1932 this.canvasManager = new $.jqplot.CanvasManager(); 1933 1934 this.themeEngine = new $.jqplot.ThemeEngine(); 1935 1936 var seriesColorsIndex = 0; 1937 1938 // Group: methods 1939 // 1940 // method: init 1941 // sets the plot target, checks data and applies user 1942 // options to plot. 1943 this.init = function(target, data, options) { 1944 options = options || {}; 1945 for (var i=0; i<$.jqplot.preInitHooks.length; i++) { 1946 $.jqplot.preInitHooks[i].call(this, target, data, options); 1947 } 1948 1949 for (var i=0; i<this.preInitHooks.hooks.length; i++) { 1950 this.preInitHooks.hooks[i].call(this, target, data, options); 1951 } 1952 1953 this.targetId = '#'+target; 1954 this.target = $('#'+target); 1955 1956 ////// 1957 // Add a reference to plot 1958 ////// 1959 if (this._addDomReference) { 1960 this.target.data('jqplot', this); 1961 } 1962 // remove any error class that may be stuck on target. 1963 this.target.removeClass('jqplot-error'); 1964 if (!this.target.get(0)) { 1965 throw "No plot target specified"; 1966 } 1967 1968 // make sure the target is positioned by some means and set css 1969 if (this.target.css('position') == 'static') { 1970 this.target.css('position', 'relative'); 1971 } 1972 if (!this.target.hasClass('jqplot-target')) { 1973 this.target.addClass('jqplot-target'); 1974 } 1975 1976 // if no height or width specified, use a default. 1977 if (!this.target.height()) { 1978 var h; 1979 if (options && options.height) { 1980 h = parseInt(options.height, 10); 1981 } 1982 else if (this.target.attr('data-height')) { 1983 h = parseInt(this.target.attr('data-height'), 10); 1984 } 1985 else { 1986 h = parseInt($.jqplot.config.defaultHeight, 10); 1987 } 1988 this._height = h; 1989 this.target.css('height', h+'px'); 1990 } 1991 else { 1992 this._height = h = this.target.height(); 1993 } 1994 if (!this.target.width()) { 1995 var w; 1996 if (options && options.width) { 1997 w = parseInt(options.width, 10); 1998 } 1999 else if (this.target.attr('data-width')) { 2000 w = parseInt(this.target.attr('data-width'), 10); 2001 } 2002 else { 2003 w = parseInt($.jqplot.config.defaultWidth, 10); 2004 } 2005 this._width = w; 2006 this.target.css('width', w+'px'); 2007 } 2008 else { 2009 this._width = w = this.target.width(); 2010 } 2011 2012 for (var i=0, l=_axisNames.length; i<l; i++) { 2013 this.axes[_axisNames[i]] = new Axis(_axisNames[i]); 2014 } 2015 2016 this._plotDimensions.height = this._height; 2017 this._plotDimensions.width = this._width; 2018 this.grid._plotDimensions = this._plotDimensions; 2019 this.title._plotDimensions = this._plotDimensions; 2020 this.baseCanvas._plotDimensions = this._plotDimensions; 2021 this.eventCanvas._plotDimensions = this._plotDimensions; 2022 this.legend._plotDimensions = this._plotDimensions; 2023 if (this._height <=0 || this._width <=0 || !this._height || !this._width) { 2024 throw "Canvas dimension not set"; 2025 } 2026 2027 if (options.dataRenderer && $.isFunction(options.dataRenderer)) { 2028 if (options.dataRendererOptions) { 2029 this.dataRendererOptions = options.dataRendererOptions; 2030 } 2031 this.dataRenderer = options.dataRenderer; 2032 data = this.dataRenderer(data, this, this.dataRendererOptions); 2033 } 2034 2035 if (options.noDataIndicator && $.isPlainObject(options.noDataIndicator)) { 2036 $.extend(true, this.noDataIndicator, options.noDataIndicator); 2037 } 2038 2039 if (data == null || $.isArray(data) == false || data.length == 0 || $.isArray(data[0]) == false || data[0].length == 0) { 2040 2041 if (this.noDataIndicator.show == false) { 2042 throw "No Data"; 2043 } 2044 2045 else { 2046 // have to be descructive here in order for plot to not try and render series. 2047 // This means that $.jqplot() will have to be called again when there is data. 2048 //delete options.series; 2049 2050 for (var ax in this.noDataIndicator.axes) { 2051 for (var prop in this.noDataIndicator.axes[ax]) { 2052 this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop]; 2053 } 2054 } 2055 2056 this.postDrawHooks.add(function() { 2057 var eh = this.eventCanvas.getHeight(); 2058 var ew = this.eventCanvas.getWidth(); 2059 var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>'); 2060 this.target.append(temp); 2061 temp.height(eh); 2062 temp.width(ew); 2063 temp.css('top', this.eventCanvas._offsets.top); 2064 temp.css('left', this.eventCanvas._offsets.left); 2065 2066 var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>'); 2067 temp.append(temp2); 2068 temp2.html(this.noDataIndicator.indicator); 2069 var th = temp2.height(); 2070 var tw = temp2.width(); 2071 temp2.height(th); 2072 temp2.width(tw); 2073 temp2.css('top', (eh - th)/2 + 'px'); 2074 }); 2075 2076 } 2077 } 2078 2079 // make a copy of the data 2080 this.data = $.extend(true, [], data); 2081 2082 this.parseOptions(options); 2083 2084 if (this.textColor) { 2085 this.target.css('color', this.textColor); 2086 } 2087 if (this.fontFamily) { 2088 this.target.css('font-family', this.fontFamily); 2089 } 2090 if (this.fontSize) { 2091 this.target.css('font-size', this.fontSize); 2092 } 2093 2094 this.title.init(); 2095 this.legend.init(); 2096 this._sumy = 0; 2097 this._sumx = 0; 2098 for (var i=0; i<this.series.length; i++) { 2099 // set default stacking order for series canvases 2100 this.seriesStack.push(i); 2101 this.previousSeriesStack.push(i); 2102 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions; 2103 this.series[i].canvas._plotDimensions = this._plotDimensions; 2104 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) { 2105 $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2106 } 2107 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) { 2108 this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2109 } 2110 this.populatePlotData(this.series[i], i); 2111 this.series[i]._plotDimensions = this._plotDimensions; 2112 this.series[i].init(i, this.grid.borderWidth, this); 2113 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) { 2114 $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2115 } 2116 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) { 2117 this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2118 } 2119 this._sumy += this.series[i]._sumy; 2120 this._sumx += this.series[i]._sumx; 2121 } 2122 2123 var name, 2124 axis; 2125 for (var i=0, l=_axisNames.length; i<l; i++) { 2126 name = _axisNames[i]; 2127 axis = this.axes[name]; 2128 axis._plotDimensions = this._plotDimensions; 2129 axis.init(); 2130 if (this.axes[name].borderColor == null) { 2131 if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) { 2132 axis.borderColor = axis._series[0].color; 2133 } 2134 else { 2135 axis.borderColor = this.grid.borderColor; 2136 } 2137 } 2138 } 2139 2140 if (this.sortData) { 2141 sortData(this.series); 2142 } 2143 this.grid.init(); 2144 this.grid._axes = this.axes; 2145 2146 this.legend._series = this.series; 2147 2148 for (var i=0; i<$.jqplot.postInitHooks.length; i++) { 2149 $.jqplot.postInitHooks[i].call(this, target, this.data, options); 2150 } 2151 2152 for (var i=0; i<this.postInitHooks.hooks.length; i++) { 2153 this.postInitHooks.hooks[i].call(this, target, this.data, options); 2154 } 2155 }; 2156 2157 // method: resetAxesScale 2158 // Reset the specified axes min, max, numberTicks and tickInterval properties to null 2159 // or reset these properties on all axes if no list of axes is provided. 2160 // 2161 // Parameters: 2162 // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset. 2163 this.resetAxesScale = function(axes, options) { 2164 var opts = options || {}; 2165 var ax = axes || this.axes; 2166 if (ax === true) { 2167 ax = this.axes; 2168 } 2169 if ($.isArray(ax)) { 2170 for (var i = 0; i < ax.length; i++) { 2171 this.axes[ax[i]].resetScale(opts[ax[i]]); 2172 } 2173 } 2174 else if (typeof(ax) === 'object') { 2175 for (var name in ax) { 2176 this.axes[name].resetScale(opts[name]); 2177 } 2178 } 2179 }; 2180 // method: reInitialize 2181 // reinitialize plot for replotting. 2182 // not called directly. 2183 this.reInitialize = function (data, opts) { 2184 // Plot should be visible and have a height and width. 2185 // If plot doesn't have height and width for some 2186 // reason, set it by other means. Plot must not have 2187 // a display:none attribute, however. 2188 2189 var options = $.extend(true, {}, this.options, opts); 2190 2191 var target = this.targetId.substr(1); 2192 var tdata = (data == null) ? this.data : data; 2193 2194 for (var i=0; i<$.jqplot.preInitHooks.length; i++) { 2195 $.jqplot.preInitHooks[i].call(this, target, tdata, options); 2196 } 2197 2198 for (var i=0; i<this.preInitHooks.hooks.length; i++) { 2199 this.preInitHooks.hooks[i].call(this, target, tdata, options); 2200 } 2201 2202 this._height = this.target.height(); 2203 this._width = this.target.width(); 2204 2205 if (this._height <=0 || this._width <=0 || !this._height || !this._width) { 2206 throw "Target dimension not set"; 2207 } 2208 2209 this._plotDimensions.height = this._height; 2210 this._plotDimensions.width = this._width; 2211 this.grid._plotDimensions = this._plotDimensions; 2212 this.title._plotDimensions = this._plotDimensions; 2213 this.baseCanvas._plotDimensions = this._plotDimensions; 2214 this.eventCanvas._plotDimensions = this._plotDimensions; 2215 this.legend._plotDimensions = this._plotDimensions; 2216 2217 var name, 2218 t, 2219 j, 2220 axis; 2221 2222 for (var i=0, l=_axisNames.length; i<l; i++) { 2223 name = _axisNames[i]; 2224 axis = this.axes[name]; 2225 2226 // Memory Leaks patch : clear ticks elements 2227 t = axis._ticks; 2228 for (var j = 0, tlen = t.length; j < tlen; j++) { 2229 var el = t[j]._elem; 2230 if (el) { 2231 // if canvas renderer 2232 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { 2233 window.G_vmlCanvasManager.uninitElement(el.get(0)); 2234 } 2235 el.emptyForce(); 2236 el = null; 2237 t._elem = null; 2238 } 2239 } 2240 t = null; 2241 2242 delete axis.ticks; 2243 delete axis._ticks; 2244 this.axes[name] = new Axis(name); 2245 this.axes[name]._plotWidth = this._width; 2246 this.axes[name]._plotHeight = this._height; 2247 } 2248 2249 if (data) { 2250 if (options.dataRenderer && $.isFunction(options.dataRenderer)) { 2251 if (options.dataRendererOptions) { 2252 this.dataRendererOptions = options.dataRendererOptions; 2253 } 2254 this.dataRenderer = options.dataRenderer; 2255 data = this.dataRenderer(data, this, this.dataRendererOptions); 2256 } 2257 2258 // make a copy of the data 2259 this.data = $.extend(true, [], data); 2260 } 2261 2262 if (opts) { 2263 this.parseOptions(options); 2264 } 2265 2266 this.title._plotWidth = this._width; 2267 2268 if (this.textColor) { 2269 this.target.css('color', this.textColor); 2270 } 2271 if (this.fontFamily) { 2272 this.target.css('font-family', this.fontFamily); 2273 } 2274 if (this.fontSize) { 2275 this.target.css('font-size', this.fontSize); 2276 } 2277 2278 this.title.init(); 2279 this.legend.init(); 2280 this._sumy = 0; 2281 this._sumx = 0; 2282 2283 this.seriesStack = []; 2284 this.previousSeriesStack = []; 2285 2286 for (var i=0, l=this.series.length; i<l; i++) { 2287 // set default stacking order for series canvases 2288 this.seriesStack.push(i); 2289 this.previousSeriesStack.push(i); 2290 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions; 2291 this.series[i].canvas._plotDimensions = this._plotDimensions; 2292 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) { 2293 $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2294 } 2295 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) { 2296 this.preSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2297 } 2298 this.populatePlotData(this.series[i], i); 2299 this.series[i]._plotDimensions = this._plotDimensions; 2300 this.series[i].init(i, this.grid.borderWidth, this); 2301 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) { 2302 $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2303 } 2304 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) { 2305 this.postSeriesInitHooks.hooks[j].call(this.series[i], target, this.data, this.options.seriesDefaults, this.options.series[i], this); 2306 } 2307 this._sumy += this.series[i]._sumy; 2308 this._sumx += this.series[i]._sumx; 2309 } 2310 2311 for (var i=0, l=_axisNames.length; i<l; i++) { 2312 name = _axisNames[i]; 2313 axis = this.axes[name]; 2314 2315 axis._plotDimensions = this._plotDimensions; 2316 axis.init(); 2317 if (axis.borderColor == null) { 2318 if (name.charAt(0) !== 'x' && axis.useSeriesColor === true && axis.show) { 2319 axis.borderColor = axis._series[0].color; 2320 } 2321 else { 2322 axis.borderColor = this.grid.borderColor; 2323 } 2324 } 2325 } 2326 2327 if (this.sortData) { 2328 sortData(this.series); 2329 } 2330 this.grid.init(); 2331 this.grid._axes = this.axes; 2332 2333 this.legend._series = this.series; 2334 2335 for (var i=0, l=$.jqplot.postInitHooks.length; i<l; i++) { 2336 $.jqplot.postInitHooks[i].call(this, target, this.data, options); 2337 } 2338 2339 for (var i=0, l=this.postInitHooks.hooks.length; i<l; i++) { 2340 this.postInitHooks.hooks[i].call(this, target, this.data, options); 2341 } 2342 }; 2343 2344 2345 2346 // method: quickInit 2347 // 2348 // Quick reinitialization plot for replotting. 2349 // Does not parse options ore recreate axes and series. 2350 // not called directly. 2351 this.quickInit = function () { 2352 // Plot should be visible and have a height and width. 2353 // If plot doesn't have height and width for some 2354 // reason, set it by other means. Plot must not have 2355 // a display:none attribute, however. 2356 2357 this._height = this.target.height(); 2358 this._width = this.target.width(); 2359 2360 if (this._height <=0 || this._width <=0 || !this._height || !this._width) { 2361 throw "Target dimension not set"; 2362 } 2363 2364 this._plotDimensions.height = this._height; 2365 this._plotDimensions.width = this._width; 2366 this.grid._plotDimensions = this._plotDimensions; 2367 this.title._plotDimensions = this._plotDimensions; 2368 this.baseCanvas._plotDimensions = this._plotDimensions; 2369 this.eventCanvas._plotDimensions = this._plotDimensions; 2370 this.legend._plotDimensions = this._plotDimensions; 2371 2372 for (var n in this.axes) { 2373 this.axes[n]._plotWidth = this._width; 2374 this.axes[n]._plotHeight = this._height; 2375 } 2376 2377 this.title._plotWidth = this._width; 2378 2379 if (this.textColor) { 2380 this.target.css('color', this.textColor); 2381 } 2382 if (this.fontFamily) { 2383 this.target.css('font-family', this.fontFamily); 2384 } 2385 if (this.fontSize) { 2386 this.target.css('font-size', this.fontSize); 2387 } 2388 2389 this._sumy = 0; 2390 this._sumx = 0; 2391 for (var i=0; i<this.series.length; i++) { 2392 this.populatePlotData(this.series[i], i); 2393 if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) { 2394 this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this); 2395 } 2396 this.series[i]._plotDimensions = this._plotDimensions; 2397 this.series[i].canvas._plotDimensions = this._plotDimensions; 2398 //this.series[i].init(i, this.grid.borderWidth); 2399 this._sumy += this.series[i]._sumy; 2400 this._sumx += this.series[i]._sumx; 2401 } 2402 2403 var name; 2404 2405 for (var j=0; j<12; j++) { 2406 name = _axisNames[j]; 2407 // Memory Leaks patch : clear ticks elements 2408 var t = this.axes[name]._ticks; 2409 for (var i = 0; i < t.length; i++) { 2410 var el = t[i]._elem; 2411 if (el) { 2412 // if canvas renderer 2413 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { 2414 window.G_vmlCanvasManager.uninitElement(el.get(0)); 2415 } 2416 el.emptyForce(); 2417 el = null; 2418 t._elem = null; 2419 } 2420 } 2421 t = null; 2422 2423 this.axes[name]._plotDimensions = this._plotDimensions; 2424 this.axes[name]._ticks = []; 2425 // this.axes[name].renderer.init.call(this.axes[name], {}); 2426 } 2427 2428 if (this.sortData) { 2429 sortData(this.series); 2430 } 2431 2432 this.grid._axes = this.axes; 2433 2434 this.legend._series = this.series; 2435 }; 2436 2437 // sort the series data in increasing order. 2438 function sortData(series) { 2439 var d, sd, pd, ppd, ret; 2440 for (var i=0; i<series.length; i++) { 2441 var check; 2442 var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData]; 2443 for (var n=0; n<4; n++) { 2444 check = true; 2445 d = bat[n]; 2446 if (series[i]._stackAxis == 'x') { 2447 for (var j = 0; j < d.length; j++) { 2448 if (typeof(d[j][1]) != "number") { 2449 check = false; 2450 break; 2451 } 2452 } 2453 if (check) { 2454 d.sort(function(a,b) { return a[1] - b[1]; }); 2455 } 2456 } 2457 else { 2458 for (var j = 0; j < d.length; j++) { 2459 if (typeof(d[j][0]) != "number") { 2460 check = false; 2461 break; 2462 } 2463 } 2464 if (check) { 2465 d.sort(function(a,b) { return a[0] - b[0]; }); 2466 } 2467 } 2468 } 2469 2470 } 2471 } 2472 2473 // populate the _stackData and _plotData arrays for the plot and the series. 2474 this.populatePlotData = function(series, index) { 2475 // if a stacked chart, compute the stacked data 2476 this._plotData = []; 2477 this._stackData = []; 2478 series._stackData = []; 2479 series._plotData = []; 2480 var plotValues = {x:[], y:[]}; 2481 if (this.stackSeries && !series.disableStack) { 2482 series._stack = true; 2483 var sidx = series._stackAxis == 'x' ? 0 : 1; 2484 var idx = sidx ? 0 : 1; 2485 // push the current data into stackData 2486 //this._stackData.push(this.series[i].data); 2487 var temp = $.extend(true, [], series.data); 2488 // create the data that will be plotted for this series 2489 var plotdata = $.extend(true, [], series.data); 2490 // for first series, nothing to add to stackData. 2491 for (var j=0; j<index; j++) { 2492 var cd = this.series[j].data; 2493 for (var k=0; k<cd.length; k++) { 2494 temp[k][0] += cd[k][0]; 2495 temp[k][1] += cd[k][1]; 2496 // only need to sum up the stack axis column of data 2497 // and only sum if it is of same sign. 2498 if (series.data[k][sidx] * cd[k][sidx] >= 0) { 2499 plotdata[k][sidx] += cd[k][sidx]; 2500 } 2501 } 2502 } 2503 for (var i=0; i<plotdata.length; i++) { 2504 plotValues.x.push(plotdata[i][0]); 2505 plotValues.y.push(plotdata[i][1]); 2506 } 2507 this._plotData.push(plotdata); 2508 this._stackData.push(temp); 2509 series._stackData = temp; 2510 series._plotData = plotdata; 2511 series._plotValues = plotValues; 2512 } 2513 else { 2514 for (var i=0; i<series.data.length; i++) { 2515 plotValues.x.push(series.data[i][0]); 2516 plotValues.y.push(series.data[i][1]); 2517 } 2518 this._stackData.push(series.data); 2519 this.series[index]._stackData = series.data; 2520 this._plotData.push(series.data); 2521 series._plotData = series.data; 2522 series._plotValues = plotValues; 2523 } 2524 if (index>0) { 2525 series._prevPlotData = this.series[index-1]._plotData; 2526 } 2527 series._sumy = 0; 2528 series._sumx = 0; 2529 for (i=series.data.length-1; i>-1; i--) { 2530 series._sumy += series.data[i][1]; 2531 series._sumx += series.data[i][0]; 2532 } 2533 }; 2534 2535 // function to safely return colors from the color array and wrap around at the end. 2536 this.getNextSeriesColor = (function(t) { 2537 var idx = 0; 2538 var sc = t.seriesColors; 2539 2540 return function () { 2541 if (idx < sc.length) { 2542 return sc[idx++]; 2543 } 2544 else { 2545 idx = 0; 2546 return sc[idx++]; 2547 } 2548 }; 2549 })(this); 2550 2551 this.parseOptions = function(options){ 2552 for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) { 2553 this.preParseOptionsHooks.hooks[i].call(this, options); 2554 } 2555 for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) { 2556 $.jqplot.preParseOptionsHooks[i].call(this, options); 2557 } 2558 this.options = $.extend(true, {}, this.defaults, options); 2559 var opts = this.options; 2560 this.animate = opts.animate; 2561 this.animateReplot = opts.animateReplot; 2562 this.stackSeries = opts.stackSeries; 2563 if ($.isPlainObject(opts.fillBetween)) { 2564 2565 var temp = ['series1', 'series2', 'color', 'baseSeries', 'fill'], 2566 tempi; 2567 2568 for (var i=0, l=temp.length; i<l; i++) { 2569 tempi = temp[i]; 2570 if (opts.fillBetween[tempi] != null) { 2571 this.fillBetween[tempi] = opts.fillBetween[tempi]; 2572 } 2573 } 2574 } 2575 2576 if (opts.seriesColors) { 2577 this.seriesColors = opts.seriesColors; 2578 } 2579 if (opts.negativeSeriesColors) { 2580 this.negativeSeriesColors = opts.negativeSeriesColors; 2581 } 2582 if (opts.captureRightClick) { 2583 this.captureRightClick = opts.captureRightClick; 2584 } 2585 this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart; 2586 this.colorGenerator.setColors(this.seriesColors); 2587 this.negativeColorGenerator.setColors(this.negativeSeriesColors); 2588 // var cg = new this.colorGenerator(this.seriesColors); 2589 // var ncg = new this.colorGenerator(this.negativeSeriesColors); 2590 // this._gridPadding = this.options.gridPadding; 2591 $.extend(true, this._gridPadding, opts.gridPadding); 2592 this.sortData = (opts.sortData != null) ? opts.sortData : this.sortData; 2593 for (var i=0; i<12; i++) { 2594 var n = _axisNames[i]; 2595 var axis = this.axes[n]; 2596 axis._options = $.extend(true, {}, opts.axesDefaults, opts.axes[n]); 2597 $.extend(true, axis, opts.axesDefaults, opts.axes[n]); 2598 axis._plotWidth = this._width; 2599 axis._plotHeight = this._height; 2600 } 2601 // if (this.data.length == 0) { 2602 // this.data = []; 2603 // for (var i=0; i<this.options.series.length; i++) { 2604 // this.data.push(this.options.series.data); 2605 // } 2606 // } 2607 2608 var normalizeData = function(data, dir, start) { 2609 // return data as an array of point arrays, 2610 // in form [[x1,y1...], [x2,y2...], ...] 2611 var temp = []; 2612 var i; 2613 dir = dir || 'vertical'; 2614 if (!$.isArray(data[0])) { 2615 // we have a series of scalars. One line with just y values. 2616 // turn the scalar list of data into a data array of form: 2617 // [[1, data[0]], [2, data[1]], ...] 2618 for (i=0; i<data.length; i++) { 2619 if (dir == 'vertical') { 2620 temp.push([start + i, data[i]]); 2621 } 2622 else { 2623 temp.push([data[i], start+i]); 2624 } 2625 } 2626 } 2627 else { 2628 // we have a properly formatted data series, copy it. 2629 $.extend(true, temp, data); 2630 } 2631 return temp; 2632 }; 2633 2634 var colorIndex = 0; 2635 this.series = []; 2636 for (var i=0; i<this.data.length; i++) { 2637 var temp = new Series(); 2638 for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) { 2639 $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]); 2640 } 2641 for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) { 2642 this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]); 2643 } 2644 $.extend(true, temp, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i], {rendererOptions:{animation:{show: this.animate}}}); 2645 var dir = 'vertical'; 2646 if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal' && temp.transposeData === true) { 2647 dir = 'horizontal'; 2648 } 2649 temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart); 2650 switch (temp.xaxis) { 2651 case 'xaxis': 2652 temp._xaxis = this.axes.xaxis; 2653 break; 2654 case 'x2axis': 2655 temp._xaxis = this.axes.x2axis; 2656 break; 2657 default: 2658 break; 2659 } 2660 temp._yaxis = this.axes[temp.yaxis]; 2661 temp._xaxis._series.push(temp); 2662 temp._yaxis._series.push(temp); 2663 if (temp.show) { 2664 temp._xaxis.show = true; 2665 temp._yaxis.show = true; 2666 } 2667 else { 2668 if (temp._xaxis.scaleToHiddenSeries) { 2669 temp._xaxis.show = true; 2670 } 2671 if (temp._yaxis.scaleToHiddenSeries) { 2672 temp._yaxis.show = true; 2673 } 2674 } 2675 2676 // // parse the renderer options and apply default colors if not provided 2677 // if (!temp.color && temp.show != false) { 2678 // temp.color = cg.next(); 2679 // colorIndex = cg.getIndex() - 1;; 2680 // } 2681 // if (!temp.negativeColor && temp.show != false) { 2682 // temp.negativeColor = ncg.get(colorIndex); 2683 // ncg.setIndex(colorIndex); 2684 // } 2685 if (!temp.label) { 2686 temp.label = 'Series '+ (i+1).toString(); 2687 } 2688 // temp.rendererOptions.show = temp.show; 2689 // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions); 2690 this.series.push(temp); 2691 for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) { 2692 $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]); 2693 } 2694 for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) { 2695 this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]); 2696 } 2697 } 2698 2699 // copy the grid and title options into this object. 2700 $.extend(true, this.grid, this.options.grid); 2701 // if axis border properties aren't set, set default. 2702 for (var i=0, l=_axisNames.length; i<l; i++) { 2703 var n = _axisNames[i]; 2704 var axis = this.axes[n]; 2705 if (axis.borderWidth == null) { 2706 axis.borderWidth =this.grid.borderWidth; 2707 } 2708 } 2709 2710 if (typeof this.options.title == 'string') { 2711 this.title.text = this.options.title; 2712 } 2713 else if (typeof this.options.title == 'object') { 2714 $.extend(true, this.title, this.options.title); 2715 } 2716 this.title._plotWidth = this._width; 2717 this.legend.setOptions(this.options.legend); 2718 2719 for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) { 2720 $.jqplot.postParseOptionsHooks[i].call(this, options); 2721 } 2722 for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) { 2723 this.postParseOptionsHooks.hooks[i].call(this, options); 2724 } 2725 }; 2726 2727 // method: destroy 2728 // Releases all resources occupied by the plot 2729 this.destroy = function() { 2730 this.canvasManager.freeAllCanvases(); 2731 if (this.eventCanvas && this.eventCanvas._elem) { 2732 this.eventCanvas._elem.unbind(); 2733 } 2734 // Couple of posts on Stack Overflow indicate that empty() doesn't 2735 // always cear up the dom and release memory. Sometimes setting 2736 // innerHTML property to null is needed. Particularly on IE, may 2737 // have to directly set it to null, bypassing $. 2738 this.target.empty(); 2739 2740 this.target[0].innerHTML = ''; 2741 }; 2742 2743 // method: replot 2744 // Does a reinitialization of the plot followed by 2745 // a redraw. Method could be used to interactively 2746 // change plot characteristics and then replot. 2747 // 2748 // Parameters: 2749 // options - Options used for replotting. 2750 // 2751 // Properties: 2752 // clear - false to not clear (empty) the plot container before replotting (default: true). 2753 // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves. 2754 // optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false). 2755 this.replot = function(options) { 2756 var opts = options || {}; 2757 var data = opts.data || null; 2758 var clear = (opts.clear === false) ? false : true; 2759 var resetAxes = opts.resetAxes || false; 2760 delete opts.data; 2761 delete opts.clear; 2762 delete opts.resetAxes; 2763 2764 this.target.trigger('jqplotPreReplot'); 2765 2766 if (clear) { 2767 this.destroy(); 2768 } 2769 // if have data or other options, full reinit. 2770 // otherwise, quickinit. 2771 if (data || !$.isEmptyObject(opts)) { 2772 this.reInitialize(data, opts); 2773 } 2774 else { 2775 this.quickInit(); 2776 } 2777 2778 if (resetAxes) { 2779 this.resetAxesScale(resetAxes, opts.axes); 2780 } 2781 this.draw(); 2782 this.target.trigger('jqplotPostReplot'); 2783 }; 2784 2785 // method: redraw 2786 // Empties the plot target div and redraws the plot. 2787 // This enables plot data and properties to be changed 2788 // and then to comletely clear the plot and redraw. 2789 // redraw *will not* reinitialize any plot elements. 2790 // That is, axes will not be autoscaled and defaults 2791 // will not be reapplied to any plot elements. redraw 2792 // is used primarily with zooming. 2793 // 2794 // Parameters: 2795 // clear - false to not clear (empty) the plot container before redrawing (default: true). 2796 this.redraw = function(clear) { 2797 clear = (clear != null) ? clear : true; 2798 this.target.trigger('jqplotPreRedraw'); 2799 if (clear) { 2800 this.canvasManager.freeAllCanvases(); 2801 this.eventCanvas._elem.unbind(); 2802 // Dont think I bind any events to the target, this shouldn't be necessary. 2803 // It will remove user's events. 2804 // this.target.unbind(); 2805 this.target.empty(); 2806 } 2807 for (var ax in this.axes) { 2808 this.axes[ax]._ticks = []; 2809 } 2810 for (var i=0; i<this.series.length; i++) { 2811 this.populatePlotData(this.series[i], i); 2812 } 2813 this._sumy = 0; 2814 this._sumx = 0; 2815 for (i=0; i<this.series.length; i++) { 2816 this._sumy += this.series[i]._sumy; 2817 this._sumx += this.series[i]._sumx; 2818 } 2819 this.draw(); 2820 this.target.trigger('jqplotPostRedraw'); 2821 }; 2822 2823 // method: draw 2824 // Draws all elements of the plot into the container. 2825 // Does not clear the container before drawing. 2826 this.draw = function(){ 2827 if (this.drawIfHidden || this.target.is(':visible')) { 2828 this.target.trigger('jqplotPreDraw'); 2829 var i, 2830 j, 2831 l, 2832 tempseries; 2833 for (i=0, l=$.jqplot.preDrawHooks.length; i<l; i++) { 2834 $.jqplot.preDrawHooks[i].call(this); 2835 } 2836 for (i=0, l=this.preDrawHooks.length; i<l; i++) { 2837 this.preDrawHooks.hooks[i].apply(this, this.preDrawSeriesHooks.args[i]); 2838 } 2839 // create an underlying canvas to be used for special features. 2840 this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this)); 2841 this.baseCanvas.setContext(); 2842 this.target.append(this.title.draw()); 2843 this.title.pack({top:0, left:0}); 2844 2845 // make room for the legend between the grid and the edge. 2846 // pass a dummy offsets object and a reference to the plot. 2847 var legendElem = this.legend.draw({}, this); 2848 2849 var gridPadding = {top:0, left:0, bottom:0, right:0}; 2850 2851 if (this.legend.placement == "outsideGrid") { 2852 // temporarily append the legend to get dimensions 2853 this.target.append(legendElem); 2854 switch (this.legend.location) { 2855 case 'n': 2856 gridPadding.top += this.legend.getHeight(); 2857 break; 2858 case 's': 2859 gridPadding.bottom += this.legend.getHeight(); 2860 break; 2861 case 'ne': 2862 case 'e': 2863 case 'se': 2864 gridPadding.right += this.legend.getWidth(); 2865 break; 2866 case 'nw': 2867 case 'w': 2868 case 'sw': 2869 gridPadding.left += this.legend.getWidth(); 2870 break; 2871 default: // same as 'ne' 2872 gridPadding.right += this.legend.getWidth(); 2873 break; 2874 } 2875 legendElem = legendElem.detach(); 2876 } 2877 2878 var ax = this.axes; 2879 var name; 2880 // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed. 2881 for (i=0; i<12; i++) { 2882 name = _axisNames[i]; 2883 this.target.append(ax[name].draw(this.baseCanvas._ctx, this)); 2884 ax[name].set(); 2885 } 2886 if (ax.yaxis.show) { 2887 gridPadding.left += ax.yaxis.getWidth(); 2888 } 2889 var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; 2890 var rapad = [0, 0, 0, 0, 0, 0, 0, 0]; 2891 var gpr = 0; 2892 var n; 2893 for (n=0; n<8; n++) { 2894 if (ax[ra[n]].show) { 2895 gpr += ax[ra[n]].getWidth(); 2896 rapad[n] = gpr; 2897 } 2898 } 2899 gridPadding.right += gpr; 2900 if (ax.x2axis.show) { 2901 gridPadding.top += ax.x2axis.getHeight(); 2902 } 2903 if (this.title.show) { 2904 gridPadding.top += this.title.getHeight(); 2905 } 2906 if (ax.xaxis.show) { 2907 gridPadding.bottom += ax.xaxis.getHeight(); 2908 } 2909 2910 // end of gridPadding adjustments. 2911 2912 // if user passed in gridDimensions option, check against calculated gridPadding 2913 if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) { 2914 var gdw = parseInt(this.options.gridDimensions.width, 10) || 0; 2915 var gdh = parseInt(this.options.gridDimensions.height, 10) || 0; 2916 var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2; 2917 var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2; 2918 2919 if (heightAdj >= 0 && widthAdj >= 0) { 2920 gridPadding.top += heightAdj; 2921 gridPadding.bottom += heightAdj; 2922 gridPadding.left += widthAdj; 2923 gridPadding.right += widthAdj; 2924 } 2925 } 2926 var arr = ['top', 'bottom', 'left', 'right']; 2927 for (var n in arr) { 2928 if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) { 2929 this._gridPadding[arr[n]] = gridPadding[arr[n]]; 2930 } 2931 else if (this._gridPadding[arr[n]] == null) { 2932 this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]]; 2933 } 2934 } 2935 2936 var legendPadding = this._gridPadding; 2937 2938 if (this.legend.placement === 'outsideGrid') { 2939 legendPadding = {top:this.title.getHeight(), left: 0, right: 0, bottom: 0}; 2940 if (this.legend.location === 's') { 2941 legendPadding.left = this._gridPadding.left; 2942 legendPadding.right = this._gridPadding.right; 2943 } 2944 } 2945 2946 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}); 2947 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}); 2948 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}); 2949 for (i=8; i>0; i--) { 2950 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}); 2951 } 2952 var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0; 2953 ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top}); 2954 2955 this.target.append(this.grid.createElement(this._gridPadding, this)); 2956 this.grid.draw(); 2957 2958 var series = this.series; 2959 var seriesLength = series.length; 2960 // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars. 2961 for (i=0, l=seriesLength; i<l; i++) { 2962 // draw series in order of stacking. This affects only 2963 // order in which canvases are added to dom. 2964 j = this.seriesStack[i]; 2965 this.target.append(series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this)); 2966 series[j].shadowCanvas.setContext(); 2967 series[j].shadowCanvas._elem.data('seriesIndex', j); 2968 } 2969 2970 for (i=0, l=seriesLength; i<l; i++) { 2971 // draw series in order of stacking. This affects only 2972 // order in which canvases are added to dom. 2973 j = this.seriesStack[i]; 2974 this.target.append(series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this)); 2975 series[j].canvas.setContext(); 2976 series[j].canvas._elem.data('seriesIndex', j); 2977 } 2978 // Need to use filled canvas to capture events in IE. 2979 // Also, canvas seems to block selection of other elements in document on FF. 2980 this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this)); 2981 this.eventCanvas.setContext(); 2982 this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)'; 2983 this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height); 2984 2985 // bind custom event handlers to regular events. 2986 this.bindCustomEvents(); 2987 2988 // draw legend before series if the series needs to know the legend dimensions. 2989 if (this.legend.preDraw) { 2990 this.eventCanvas._elem.before(legendElem); 2991 this.legend.pack(legendPadding); 2992 if (this.legend._elem) { 2993 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}}); 2994 } 2995 else { 2996 this.drawSeries(); 2997 } 2998 } 2999 else { // draw series before legend 3000 this.drawSeries(); 3001 if (seriesLength) { 3002 $(series[seriesLength-1].canvas._elem).after(legendElem); 3003 } 3004 this.legend.pack(legendPadding); 3005 } 3006 3007 // register event listeners on the overlay canvas 3008 for (var i=0, l=$.jqplot.eventListenerHooks.length; i<l; i++) { 3009 // in the handler, this will refer to the eventCanvas dom element. 3010 // make sure there are references back into plot objects. 3011 this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]); 3012 } 3013 3014 // register event listeners on the overlay canvas 3015 for (var i=0, l=this.eventListenerHooks.hooks.length; i<l; i++) { 3016 // in the handler, this will refer to the eventCanvas dom element. 3017 // make sure there are references back into plot objects. 3018 this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]); 3019 } 3020 3021 var fb = this.fillBetween; 3022 if (fb.fill && fb.series1 !== fb.series2 && fb.series1 < seriesLength && fb.series2 < seriesLength && series[fb.series1]._type === 'line' && series[fb.series2]._type === 'line') { 3023 this.doFillBetweenLines(); 3024 } 3025 3026 for (var i=0, l=$.jqplot.postDrawHooks.length; i<l; i++) { 3027 $.jqplot.postDrawHooks[i].call(this); 3028 } 3029 3030 for (var i=0, l=this.postDrawHooks.hooks.length; i<l; i++) { 3031 this.postDrawHooks.hooks[i].apply(this, this.postDrawHooks.args[i]); 3032 } 3033 3034 if (this.target.is(':visible')) { 3035 this._drawCount += 1; 3036 } 3037 3038 var temps, 3039 tempr, 3040 sel, 3041 _els; 3042 // ughh. ideally would hide all series then show them. 3043 for (i=0, l=seriesLength; i<l; i++) { 3044 temps = series[i]; 3045 tempr = temps.renderer; 3046 sel = '.jqplot-point-label.jqplot-series-'+i; 3047 if (tempr.animation && tempr.animation._supported && tempr.animation.show && (this._drawCount < 2 || this.animateReplot)) { 3048 _els = this.target.find(sel); 3049 _els.stop(true, true).hide(); 3050 temps.canvas._elem.stop(true, true).hide(); 3051 temps.shadowCanvas._elem.stop(true, true).hide(); 3052 temps.canvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed); 3053 temps.shadowCanvas._elem.jqplotEffect('blind', {mode: 'show', direction: tempr.animation.direction}, tempr.animation.speed); 3054 _els.fadeIn(tempr.animation.speed*0.8); 3055 } 3056 } 3057 _els = null; 3058 3059 this.target.trigger('jqplotPostDraw', [this]); 3060 } 3061 }; 3062 3063 jqPlot.prototype.doFillBetweenLines = function () { 3064 var fb = this.fillBetween; 3065 var sid1 = fb.series1; 3066 var sid2 = fb.series2; 3067 // first series should always be lowest index 3068 var id1 = (sid1 < sid2) ? sid1 : sid2; 3069 var id2 = (sid2 > sid1) ? sid2 : sid1; 3070 3071 var series1 = this.series[id1]; 3072 var series2 = this.series[id2]; 3073 3074 if (series2.renderer.smooth) { 3075 var tempgd = series2.renderer._smoothedData.slice(0).reverse(); 3076 } 3077 else { 3078 var tempgd = series2.gridData.slice(0).reverse(); 3079 } 3080 3081 if (series1.renderer.smooth) { 3082 var gd = series1.renderer._smoothedData.concat(tempgd); 3083 } 3084 else { 3085 var gd = series1.gridData.concat(tempgd); 3086 } 3087 3088 var color = (fb.color !== null) ? fb.color : this.series[sid1].fillColor; 3089 var baseSeries = (fb.baseSeries !== null) ? fb.baseSeries : id1; 3090 3091 // now apply a fill to the shape on the lower series shadow canvas, 3092 // so it is behind both series. 3093 var sr = this.series[baseSeries].renderer.shapeRenderer; 3094 var opts = {fillStyle: color, fill: true, closePath: true}; 3095 sr.draw(series1.shadowCanvas._ctx, gd, opts); 3096 }; 3097 3098 this.bindCustomEvents = function() { 3099 this.eventCanvas._elem.bind('click', {plot:this}, this.onClick); 3100 this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick); 3101 this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown); 3102 this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove); 3103 this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter); 3104 this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave); 3105 if (this.captureRightClick) { 3106 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick); 3107 this.eventCanvas._elem.get(0).oncontextmenu = function() { 3108 return false; 3109 }; 3110 } 3111 else { 3112 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp); 3113 } 3114 }; 3115 3116 function getEventPosition(ev) { 3117 var plot = ev.data.plot; 3118 var go = plot.eventCanvas._elem.offset(); 3119 var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top}; 3120 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}; 3121 var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis']; 3122 var ax = plot.axes; 3123 var n, axis; 3124 for (n=11; n>0; n--) { 3125 axis = an[n-1]; 3126 if (ax[axis].show) { 3127 dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]); 3128 } 3129 } 3130 3131 return {offsets:go, gridPos:gridPos, dataPos:dataPos}; 3132 } 3133 3134 3135 // function to check if event location is over a area area 3136 function checkIntersection(gridpos, plot) { 3137 var series = plot.series; 3138 var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang; 3139 var d0, d, p, pp, points, bw, hp; 3140 var threshold, t; 3141 for (k=plot.seriesStack.length-1; k>=0; k--) { 3142 i = plot.seriesStack[k]; 3143 s = series[i]; 3144 hp = s._highlightThreshold; 3145 switch (s.renderer.constructor) { 3146 case $.jqplot.BarRenderer: 3147 x = gridpos.x; 3148 y = gridpos.y; 3149 for (j=0; j<s._barPoints.length; j++) { 3150 points = s._barPoints[j]; 3151 p = s.gridData[j]; 3152 if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) { 3153 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]}; 3154 } 3155 } 3156 break; 3157 case $.jqplot.PyramidRenderer: 3158 x = gridpos.x; 3159 y = gridpos.y; 3160 for (j=0; j<s._barPoints.length; j++) { 3161 points = s._barPoints[j]; 3162 p = s.gridData[j]; 3163 if (x > points[0][0] + hp[0][0] && x < points[2][0] + hp[2][0] && y > points[2][1] && y < points[0][1]) { 3164 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]}; 3165 } 3166 } 3167 break; 3168 3169 case $.jqplot.DonutRenderer: 3170 sa = s.startAngle/180*Math.PI; 3171 x = gridpos.x - s._center[0]; 3172 y = gridpos.y - s._center[1]; 3173 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); 3174 if (x > 0 && -y >= 0) { 3175 theta = 2*Math.PI - Math.atan(-y/x); 3176 } 3177 else if (x > 0 && -y < 0) { 3178 theta = -Math.atan(-y/x); 3179 } 3180 else if (x < 0) { 3181 theta = Math.PI - Math.atan(-y/x); 3182 } 3183 else if (x == 0 && -y > 0) { 3184 theta = 3*Math.PI/2; 3185 } 3186 else if (x == 0 && -y < 0) { 3187 theta = Math.PI/2; 3188 } 3189 else if (x == 0 && y == 0) { 3190 theta = 0; 3191 } 3192 if (sa) { 3193 theta -= sa; 3194 if (theta < 0) { 3195 theta += 2*Math.PI; 3196 } 3197 else if (theta > 2*Math.PI) { 3198 theta -= 2*Math.PI; 3199 } 3200 } 3201 3202 sm = s.sliceMargin/180*Math.PI; 3203 if (r < s._radius && r > s._innerRadius) { 3204 for (j=0; j<s.gridData.length; j++) { 3205 minang = (j>0) ? s.gridData[j-1][1]+sm : sm; 3206 maxang = s.gridData[j][1]; 3207 if (theta > minang && theta < maxang) { 3208 return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]}; 3209 } 3210 } 3211 } 3212 break; 3213 3214 case $.jqplot.PieRenderer: 3215 sa = s.startAngle/180*Math.PI; 3216 x = gridpos.x - s._center[0]; 3217 y = gridpos.y - s._center[1]; 3218 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); 3219 if (x > 0 && -y >= 0) { 3220 theta = 2*Math.PI - Math.atan(-y/x); 3221 } 3222 else if (x > 0 && -y < 0) { 3223 theta = -Math.atan(-y/x); 3224 } 3225 else if (x < 0) { 3226 theta = Math.PI - Math.atan(-y/x); 3227 } 3228 else if (x == 0 && -y > 0) { 3229 theta = 3*Math.PI/2; 3230 } 3231 else if (x == 0 && -y < 0) { 3232 theta = Math.PI/2; 3233 } 3234 else if (x == 0 && y == 0) { 3235 theta = 0; 3236 } 3237 if (sa) { 3238 theta -= sa; 3239 if (theta < 0) { 3240 theta += 2*Math.PI; 3241 } 3242 else if (theta > 2*Math.PI) { 3243 theta -= 2*Math.PI; 3244 } 3245 } 3246 3247 sm = s.sliceMargin/180*Math.PI; 3248 if (r < s._radius) { 3249 for (j=0; j<s.gridData.length; j++) { 3250 minang = (j>0) ? s.gridData[j-1][1]+sm : sm; 3251 maxang = s.gridData[j][1]; 3252 if (theta > minang && theta < maxang) { 3253 return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]}; 3254 } 3255 } 3256 } 3257 break; 3258 3259 case $.jqplot.BubbleRenderer: 3260 x = gridpos.x; 3261 y = gridpos.y; 3262 var ret = null; 3263 3264 if (s.show) { 3265 for (var j=0; j<s.gridData.length; j++) { 3266 p = s.gridData[j]; 3267 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); 3268 if (d <= p[2] && (d <= d0 || d0 == null)) { 3269 d0 = d; 3270 ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3271 } 3272 } 3273 if (ret != null) { 3274 return ret; 3275 } 3276 } 3277 break; 3278 3279 case $.jqplot.FunnelRenderer: 3280 x = gridpos.x; 3281 y = gridpos.y; 3282 var v = s._vertices, 3283 vfirst = v[0], 3284 vlast = v[v.length-1], 3285 lex, 3286 rex, 3287 cv; 3288 3289 // equations of right and left sides, returns x, y values given height of section (y value and 2 points) 3290 3291 function findedge (l, p1 , p2) { 3292 var m = (p1[1] - p2[1])/(p1[0] - p2[0]); 3293 var b = p1[1] - m*p1[0]; 3294 var y = l + p1[1]; 3295 3296 return [(y - b)/m, y]; 3297 } 3298 3299 // check each section 3300 lex = findedge(y, vfirst[0], vlast[3]); 3301 rex = findedge(y, vfirst[1], vlast[2]); 3302 for (j=0; j<v.length; j++) { 3303 cv = v[j]; 3304 if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) { 3305 return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]}; 3306 } 3307 } 3308 break; 3309 3310 case $.jqplot.LineRenderer: 3311 x = gridpos.x; 3312 y = gridpos.y; 3313 r = s.renderer; 3314 if (s.show) { 3315 if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) { 3316 // first check if it is in bounding box 3317 var inside = false; 3318 if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) { 3319 // now check the crossing number 3320 3321 var numPoints = s._areaPoints.length; 3322 var ii; 3323 var j = numPoints-1; 3324 3325 for(var ii=0; ii < numPoints; ii++) { 3326 var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]]; 3327 var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]]; 3328 3329 if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y) { 3330 if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) { 3331 inside = !inside; 3332 } 3333 } 3334 3335 j = ii; 3336 } 3337 } 3338 if (inside) { 3339 return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints}; 3340 } 3341 break; 3342 3343 } 3344 3345 else { 3346 t = s.markerRenderer.size/2+s.neighborThreshold; 3347 threshold = (t > 0) ? t : 0; 3348 for (var j=0; j<s.gridData.length; j++) { 3349 p = s.gridData[j]; 3350 // neighbor looks different to OHLC chart. 3351 if (r.constructor == $.jqplot.OHLCRenderer) { 3352 if (r.candleStick) { 3353 var yp = s._yaxis.series_u2p; 3354 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])) { 3355 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3356 } 3357 } 3358 // if an open hi low close chart 3359 else if (!r.hlc){ 3360 var yp = s._yaxis.series_u2p; 3361 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { 3362 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3363 } 3364 } 3365 // a hi low close chart 3366 else { 3367 var yp = s._yaxis.series_u2p; 3368 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { 3369 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3370 } 3371 } 3372 3373 } 3374 else if (p[0] != null && p[1] != null){ 3375 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); 3376 if (d <= threshold && (d <= d0 || d0 == null)) { 3377 d0 = d; 3378 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3379 } 3380 } 3381 } 3382 } 3383 } 3384 break; 3385 3386 default: 3387 x = gridpos.x; 3388 y = gridpos.y; 3389 r = s.renderer; 3390 if (s.show) { 3391 t = s.markerRenderer.size/2+s.neighborThreshold; 3392 threshold = (t > 0) ? t : 0; 3393 for (var j=0; j<s.gridData.length; j++) { 3394 p = s.gridData[j]; 3395 // neighbor looks different to OHLC chart. 3396 if (r.constructor == $.jqplot.OHLCRenderer) { 3397 if (r.candleStick) { 3398 var yp = s._yaxis.series_u2p; 3399 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])) { 3400 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3401 } 3402 } 3403 // if an open hi low close chart 3404 else if (!r.hlc){ 3405 var yp = s._yaxis.series_u2p; 3406 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { 3407 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3408 } 3409 } 3410 // a hi low close chart 3411 else { 3412 var yp = s._yaxis.series_u2p; 3413 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { 3414 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3415 } 3416 } 3417 3418 } 3419 else { 3420 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); 3421 if (d <= threshold && (d <= d0 || d0 == null)) { 3422 d0 = d; 3423 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 3424 } 3425 } 3426 } 3427 } 3428 break; 3429 } 3430 } 3431 3432 return null; 3433 } 3434 3435 3436 3437 this.onClick = function(ev) { 3438 // Event passed in is normalized and will have data attribute. 3439 // Event passed out is unnormalized. 3440 var positions = getEventPosition(ev); 3441 var p = ev.data.plot; 3442 var neighbor = checkIntersection(positions.gridPos, p); 3443 var evt = $.Event('jqplotClick'); 3444 evt.pageX = ev.pageX; 3445 evt.pageY = ev.pageY; 3446 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 3447 }; 3448 3449 this.onDblClick = function(ev) { 3450 // Event passed in is normalized and will have data attribute. 3451 // Event passed out is unnormalized. 3452 var positions = getEventPosition(ev); 3453 var p = ev.data.plot; 3454 var neighbor = checkIntersection(positions.gridPos, p); 3455 var evt = $.Event('jqplotDblClick'); 3456 evt.pageX = ev.pageX; 3457 evt.pageY = ev.pageY; 3458 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 3459 }; 3460 3461 this.onMouseDown = function(ev) { 3462 var positions = getEventPosition(ev); 3463 var p = ev.data.plot; 3464 var neighbor = checkIntersection(positions.gridPos, p); 3465 var evt = $.Event('jqplotMouseDown'); 3466 evt.pageX = ev.pageX; 3467 evt.pageY = ev.pageY; 3468 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 3469 }; 3470 3471 this.onMouseUp = function(ev) { 3472 var positions = getEventPosition(ev); 3473 var evt = $.Event('jqplotMouseUp'); 3474 evt.pageX = ev.pageX; 3475 evt.pageY = ev.pageY; 3476 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]); 3477 }; 3478 3479 this.onRightClick = function(ev) { 3480 var positions = getEventPosition(ev); 3481 var p = ev.data.plot; 3482 var neighbor = checkIntersection(positions.gridPos, p); 3483 if (p.captureRightClick) { 3484 if (ev.which == 3) { 3485 var evt = $.Event('jqplotRightClick'); 3486 evt.pageX = ev.pageX; 3487 evt.pageY = ev.pageY; 3488 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 3489 } 3490 else { 3491 var evt = $.Event('jqplotMouseUp'); 3492 evt.pageX = ev.pageX; 3493 evt.pageY = ev.pageY; 3494 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 3495 } 3496 } 3497 }; 3498 3499 this.onMouseMove = function(ev) { 3500 var positions = getEventPosition(ev); 3501 var p = ev.data.plot; 3502 var neighbor = checkIntersection(positions.gridPos, p); 3503 var evt = $.Event('jqplotMouseMove'); 3504 evt.pageX = ev.pageX; 3505 evt.pageY = ev.pageY; 3506 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 3507 }; 3508 3509 this.onMouseEnter = function(ev) { 3510 var positions = getEventPosition(ev); 3511 var p = ev.data.plot; 3512 var evt = $.Event('jqplotMouseEnter'); 3513 evt.pageX = ev.pageX; 3514 evt.pageY = ev.pageY; 3515 evt.relatedTarget = ev.relatedTarget; 3516 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]); 3517 }; 3518 3519 this.onMouseLeave = function(ev) { 3520 var positions = getEventPosition(ev); 3521 var p = ev.data.plot; 3522 var evt = $.Event('jqplotMouseLeave'); 3523 evt.pageX = ev.pageX; 3524 evt.pageY = ev.pageY; 3525 evt.relatedTarget = ev.relatedTarget; 3526 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]); 3527 }; 3528 3529 // method: drawSeries 3530 // Redraws all or just one series on the plot. No axis scaling 3531 // is performed and no other elements on the plot are redrawn. 3532 // options is an options object to pass on to the series renderers. 3533 // It can be an empty object {}. idx is the series index 3534 // to redraw if only one series is to be redrawn. 3535 this.drawSeries = function(options, idx){ 3536 var i, series, ctx; 3537 // if only one argument passed in and it is a number, use it ad idx. 3538 idx = (typeof(options) === "number" && idx == null) ? options : idx; 3539 options = (typeof(options) === "object") ? options : {}; 3540 // draw specified series 3541 if (idx != undefined) { 3542 series = this.series[idx]; 3543 ctx = series.shadowCanvas._ctx; 3544 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 3545 series.drawShadow(ctx, options, this); 3546 ctx = series.canvas._ctx; 3547 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 3548 series.draw(ctx, options, this); 3549 if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) { 3550 if (idx < this.series.length - 1) { 3551 this.drawSeries(idx+1); 3552 } 3553 } 3554 } 3555 3556 else { 3557 // if call series drawShadow method first, in case all series shadows 3558 // should be drawn before any series. This will ensure, like for 3559 // stacked bar plots, that shadows don't overlap series. 3560 for (i=0; i<this.series.length; i++) { 3561 // first clear the canvas 3562 series = this.series[i]; 3563 ctx = series.shadowCanvas._ctx; 3564 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 3565 series.drawShadow(ctx, options, this); 3566 ctx = series.canvas._ctx; 3567 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 3568 series.draw(ctx, options, this); 3569 } 3570 } 3571 options = idx = i = series = ctx = null; 3572 }; 3573 3574 // method: moveSeriesToFront 3575 // This method requires jQuery 1.4+ 3576 // Moves the specified series canvas in front of all other series canvases. 3577 // This effectively "draws" the specified series on top of all other series, 3578 // although it is performed through DOM manipulation, no redrawing is performed. 3579 // 3580 // Parameters: 3581 // idx - 0 based index of the series to move. This will be the index of the series 3582 // as it was first passed into the jqplot function. 3583 this.moveSeriesToFront = function (idx) { 3584 idx = parseInt(idx, 10); 3585 var stackIndex = $.inArray(idx, this.seriesStack); 3586 // if already in front, return 3587 if (stackIndex == -1) { 3588 return; 3589 } 3590 if (stackIndex == this.seriesStack.length -1) { 3591 this.previousSeriesStack = this.seriesStack.slice(0); 3592 return; 3593 } 3594 var opidx = this.seriesStack[this.seriesStack.length -1]; 3595 var serelem = this.series[idx].canvas._elem.detach(); 3596 var shadelem = this.series[idx].shadowCanvas._elem.detach(); 3597 this.series[opidx].shadowCanvas._elem.after(shadelem); 3598 this.series[opidx].canvas._elem.after(serelem); 3599 this.previousSeriesStack = this.seriesStack.slice(0); 3600 this.seriesStack.splice(stackIndex, 1); 3601 this.seriesStack.push(idx); 3602 }; 3603 3604 // method: moveSeriesToBack 3605 // This method requires jQuery 1.4+ 3606 // Moves the specified series canvas behind all other series canvases. 3607 // 3608 // Parameters: 3609 // idx - 0 based index of the series to move. This will be the index of the series 3610 // as it was first passed into the jqplot function. 3611 this.moveSeriesToBack = function (idx) { 3612 idx = parseInt(idx, 10); 3613 var stackIndex = $.inArray(idx, this.seriesStack); 3614 // if already in back, return 3615 if (stackIndex == 0 || stackIndex == -1) { 3616 return; 3617 } 3618 var opidx = this.seriesStack[0]; 3619 var serelem = this.series[idx].canvas._elem.detach(); 3620 var shadelem = this.series[idx].shadowCanvas._elem.detach(); 3621 this.series[opidx].shadowCanvas._elem.before(shadelem); 3622 this.series[opidx].canvas._elem.before(serelem); 3623 this.previousSeriesStack = this.seriesStack.slice(0); 3624 this.seriesStack.splice(stackIndex, 1); 3625 this.seriesStack.unshift(idx); 3626 }; 3627 3628 // method: restorePreviousSeriesOrder 3629 // This method requires jQuery 1.4+ 3630 // Restore the series canvas order to its previous state. 3631 // Useful to put a series back where it belongs after moving 3632 // it to the front. 3633 this.restorePreviousSeriesOrder = function () { 3634 var i, j, serelem, shadelem, temp, move, keep; 3635 // if no change, return. 3636 if (this.seriesStack == this.previousSeriesStack) { 3637 return; 3638 } 3639 for (i=1; i<this.previousSeriesStack.length; i++) { 3640 move = this.previousSeriesStack[i]; 3641 keep = this.previousSeriesStack[i-1]; 3642 serelem = this.series[move].canvas._elem.detach(); 3643 shadelem = this.series[move].shadowCanvas._elem.detach(); 3644 this.series[keep].shadowCanvas._elem.after(shadelem); 3645 this.series[keep].canvas._elem.after(serelem); 3646 } 3647 temp = this.seriesStack.slice(0); 3648 this.seriesStack = this.previousSeriesStack.slice(0); 3649 this.previousSeriesStack = temp; 3650 }; 3651 3652 // method: restoreOriginalSeriesOrder 3653 // This method requires jQuery 1.4+ 3654 // Restore the series canvas order to its original order 3655 // when the plot was created. 3656 this.restoreOriginalSeriesOrder = function () { 3657 var i, j, arr=[], serelem, shadelem; 3658 for (i=0; i<this.series.length; i++) { 3659 arr.push(i); 3660 } 3661 if (this.seriesStack == arr) { 3662 return; 3663 } 3664 this.previousSeriesStack = this.seriesStack.slice(0); 3665 this.seriesStack = arr; 3666 for (i=1; i<this.seriesStack.length; i++) { 3667 serelem = this.series[i].canvas._elem.detach(); 3668 shadelem = this.series[i].shadowCanvas._elem.detach(); 3669 this.series[i-1].shadowCanvas._elem.after(shadelem); 3670 this.series[i-1].canvas._elem.after(serelem); 3671 } 3672 }; 3673 3674 this.activateTheme = function (name) { 3675 this.themeEngine.activate(this, name); 3676 }; 3677 } 3678 3679 3680 // conpute a highlight color or array of highlight colors from given colors. 3681 $.jqplot.computeHighlightColors = function(colors) { 3682 var ret; 3683 if ($.isArray(colors)) { 3684 ret = []; 3685 for (var i=0; i<colors.length; i++){ 3686 var rgba = $.jqplot.getColorComponents(colors[i]); 3687 var newrgb = [rgba[0], rgba[1], rgba[2]]; 3688 var sum = newrgb[0] + newrgb[1] + newrgb[2]; 3689 for (var j=0; j<3; j++) { 3690 // when darkening, lowest color component can be is 60. 3691 newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90; 3692 newrgb[j] = parseInt(newrgb[j], 10); 3693 (newrgb[j] > 255) ? 255 : newrgb[j]; 3694 } 3695 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5; 3696 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2; 3697 newrgb[3] = 0.3 + 0.35 * rgba[3]; 3698 ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')'); 3699 } 3700 } 3701 else { 3702 var rgba = $.jqplot.getColorComponents(colors); 3703 var newrgb = [rgba[0], rgba[1], rgba[2]]; 3704 var sum = newrgb[0] + newrgb[1] + newrgb[2]; 3705 for (var j=0; j<3; j++) { 3706 // when darkening, lowest color component can be is 60. 3707 // newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]); 3708 // newrgb[j] = parseInt(newrgb[j], 10); 3709 newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90; 3710 newrgb[j] = parseInt(newrgb[j], 10); 3711 (newrgb[j] > 255) ? 255 : newrgb[j]; 3712 } 3713 // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5; 3714 // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2; 3715 newrgb[3] = 0.3 + 0.35 * rgba[3]; 3716 ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')'; 3717 } 3718 return ret; 3719 }; 3720 3721 $.jqplot.ColorGenerator = function(colors) { 3722 colors = colors || $.jqplot.config.defaultColors; 3723 var idx = 0; 3724 3725 this.next = function () { 3726 if (idx < colors.length) { 3727 return colors[idx++]; 3728 } 3729 else { 3730 idx = 0; 3731 return colors[idx++]; 3732 } 3733 }; 3734 3735 this.previous = function () { 3736 if (idx > 0) { 3737 return colors[idx--]; 3738 } 3739 else { 3740 idx = colors.length-1; 3741 return colors[idx]; 3742 } 3743 }; 3744 3745 // get a color by index without advancing pointer. 3746 this.get = function(i) { 3747 var idx = i - colors.length * Math.floor(i/colors.length); 3748 return colors[idx]; 3749 }; 3750 3751 this.setColors = function(c) { 3752 colors = c; 3753 }; 3754 3755 this.reset = function() { 3756 idx = 0; 3757 }; 3758 3759 this.getIndex = function() { 3760 return idx; 3761 }; 3762 3763 this.setIndex = function(index) { 3764 idx = index; 3765 }; 3766 }; 3767 3768 // convert a hex color string to rgb string. 3769 // h - 3 or 6 character hex string, with or without leading # 3770 // a - optional alpha 3771 $.jqplot.hex2rgb = function(h, a) { 3772 h = h.replace('#', ''); 3773 if (h.length == 3) { 3774 h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2); 3775 } 3776 var rgb; 3777 rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16); 3778 if (a) { 3779 rgb += ', '+a; 3780 } 3781 rgb += ')'; 3782 return rgb; 3783 }; 3784 3785 // convert an rgb color spec to a hex spec. ignore any alpha specification. 3786 $.jqplot.rgb2hex = function(s) { 3787 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/; 3788 var m = s.match(pat); 3789 var h = '#'; 3790 for (var i=1; i<4; i++) { 3791 var temp; 3792 if (m[i].search(/%/) != -1) { 3793 temp = parseInt(255*m[i]/100, 10).toString(16); 3794 if (temp.length == 1) { 3795 temp = '0'+temp; 3796 } 3797 } 3798 else { 3799 temp = parseInt(m[i], 10).toString(16); 3800 if (temp.length == 1) { 3801 temp = '0'+temp; 3802 } 3803 } 3804 h += temp; 3805 } 3806 return h; 3807 }; 3808 3809 // given a css color spec, return an rgb css color spec 3810 $.jqplot.normalize2rgb = function(s, a) { 3811 if (s.search(/^ *rgba?\(/) != -1) { 3812 return s; 3813 } 3814 else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) { 3815 return $.jqplot.hex2rgb(s, a); 3816 } 3817 else { 3818 throw 'invalid color spec'; 3819 } 3820 }; 3821 3822 // extract the r, g, b, a color components out of a css color spec. 3823 $.jqplot.getColorComponents = function(s) { 3824 // check to see if a color keyword. 3825 s = $.jqplot.colorKeywordMap[s] || s; 3826 var rgb = $.jqplot.normalize2rgb(s); 3827 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/; 3828 var m = rgb.match(pat); 3829 var ret = []; 3830 for (var i=1; i<4; i++) { 3831 if (m[i].search(/%/) != -1) { 3832 ret[i-1] = parseInt(255*m[i]/100, 10); 3833 } 3834 else { 3835 ret[i-1] = parseInt(m[i], 10); 3836 } 3837 } 3838 ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0; 3839 return ret; 3840 }; 3841 3842 $.jqplot.colorKeywordMap = { 3843 aliceblue: 'rgb(240, 248, 255)', 3844 antiquewhite: 'rgb(250, 235, 215)', 3845 aqua: 'rgb( 0, 255, 255)', 3846 aquamarine: 'rgb(127, 255, 212)', 3847 azure: 'rgb(240, 255, 255)', 3848 beige: 'rgb(245, 245, 220)', 3849 bisque: 'rgb(255, 228, 196)', 3850 black: 'rgb( 0, 0, 0)', 3851 blanchedalmond: 'rgb(255, 235, 205)', 3852 blue: 'rgb( 0, 0, 255)', 3853 blueviolet: 'rgb(138, 43, 226)', 3854 brown: 'rgb(165, 42, 42)', 3855 burlywood: 'rgb(222, 184, 135)', 3856 cadetblue: 'rgb( 95, 158, 160)', 3857 chartreuse: 'rgb(127, 255, 0)', 3858 chocolate: 'rgb(210, 105, 30)', 3859 coral: 'rgb(255, 127, 80)', 3860 cornflowerblue: 'rgb(100, 149, 237)', 3861 cornsilk: 'rgb(255, 248, 220)', 3862 crimson: 'rgb(220, 20, 60)', 3863 cyan: 'rgb( 0, 255, 255)', 3864 darkblue: 'rgb( 0, 0, 139)', 3865 darkcyan: 'rgb( 0, 139, 139)', 3866 darkgoldenrod: 'rgb(184, 134, 11)', 3867 darkgray: 'rgb(169, 169, 169)', 3868 darkgreen: 'rgb( 0, 100, 0)', 3869 darkgrey: 'rgb(169, 169, 169)', 3870 darkkhaki: 'rgb(189, 183, 107)', 3871 darkmagenta: 'rgb(139, 0, 139)', 3872 darkolivegreen: 'rgb( 85, 107, 47)', 3873 darkorange: 'rgb(255, 140, 0)', 3874 darkorchid: 'rgb(153, 50, 204)', 3875 darkred: 'rgb(139, 0, 0)', 3876 darksalmon: 'rgb(233, 150, 122)', 3877 darkseagreen: 'rgb(143, 188, 143)', 3878 darkslateblue: 'rgb( 72, 61, 139)', 3879 darkslategray: 'rgb( 47, 79, 79)', 3880 darkslategrey: 'rgb( 47, 79, 79)', 3881 darkturquoise: 'rgb( 0, 206, 209)', 3882 darkviolet: 'rgb(148, 0, 211)', 3883 deeppink: 'rgb(255, 20, 147)', 3884 deepskyblue: 'rgb( 0, 191, 255)', 3885 dimgray: 'rgb(105, 105, 105)', 3886 dimgrey: 'rgb(105, 105, 105)', 3887 dodgerblue: 'rgb( 30, 144, 255)', 3888 firebrick: 'rgb(178, 34, 34)', 3889 floralwhite: 'rgb(255, 250, 240)', 3890 forestgreen: 'rgb( 34, 139, 34)', 3891 fuchsia: 'rgb(255, 0, 255)', 3892 gainsboro: 'rgb(220, 220, 220)', 3893 ghostwhite: 'rgb(248, 248, 255)', 3894 gold: 'rgb(255, 215, 0)', 3895 goldenrod: 'rgb(218, 165, 32)', 3896 gray: 'rgb(128, 128, 128)', 3897 grey: 'rgb(128, 128, 128)', 3898 green: 'rgb( 0, 128, 0)', 3899 greenyellow: 'rgb(173, 255, 47)', 3900 honeydew: 'rgb(240, 255, 240)', 3901 hotpink: 'rgb(255, 105, 180)', 3902 indianred: 'rgb(205, 92, 92)', 3903 indigo: 'rgb( 75, 0, 130)', 3904 ivory: 'rgb(255, 255, 240)', 3905 khaki: 'rgb(240, 230, 140)', 3906 lavender: 'rgb(230, 230, 250)', 3907 lavenderblush: 'rgb(255, 240, 245)', 3908 lawngreen: 'rgb(124, 252, 0)', 3909 lemonchiffon: 'rgb(255, 250, 205)', 3910 lightblue: 'rgb(173, 216, 230)', 3911 lightcoral: 'rgb(240, 128, 128)', 3912 lightcyan: 'rgb(224, 255, 255)', 3913 lightgoldenrodyellow: 'rgb(250, 250, 210)', 3914 lightgray: 'rgb(211, 211, 211)', 3915 lightgreen: 'rgb(144, 238, 144)', 3916 lightgrey: 'rgb(211, 211, 211)', 3917 lightpink: 'rgb(255, 182, 193)', 3918 lightsalmon: 'rgb(255, 160, 122)', 3919 lightseagreen: 'rgb( 32, 178, 170)', 3920 lightskyblue: 'rgb(135, 206, 250)', 3921 lightslategray: 'rgb(119, 136, 153)', 3922 lightslategrey: 'rgb(119, 136, 153)', 3923 lightsteelblue: 'rgb(176, 196, 222)', 3924 lightyellow: 'rgb(255, 255, 224)', 3925 lime: 'rgb( 0, 255, 0)', 3926 limegreen: 'rgb( 50, 205, 50)', 3927 linen: 'rgb(250, 240, 230)', 3928 magenta: 'rgb(255, 0, 255)', 3929 maroon: 'rgb(128, 0, 0)', 3930 mediumaquamarine: 'rgb(102, 205, 170)', 3931 mediumblue: 'rgb( 0, 0, 205)', 3932 mediumorchid: 'rgb(186, 85, 211)', 3933 mediumpurple: 'rgb(147, 112, 219)', 3934 mediumseagreen: 'rgb( 60, 179, 113)', 3935 mediumslateblue: 'rgb(123, 104, 238)', 3936 mediumspringgreen: 'rgb( 0, 250, 154)', 3937 mediumturquoise: 'rgb( 72, 209, 204)', 3938 mediumvioletred: 'rgb(199, 21, 133)', 3939 midnightblue: 'rgb( 25, 25, 112)', 3940 mintcream: 'rgb(245, 255, 250)', 3941 mistyrose: 'rgb(255, 228, 225)', 3942 moccasin: 'rgb(255, 228, 181)', 3943 navajowhite: 'rgb(255, 222, 173)', 3944 navy: 'rgb( 0, 0, 128)', 3945 oldlace: 'rgb(253, 245, 230)', 3946 olive: 'rgb(128, 128, 0)', 3947 olivedrab: 'rgb(107, 142, 35)', 3948 orange: 'rgb(255, 165, 0)', 3949 orangered: 'rgb(255, 69, 0)', 3950 orchid: 'rgb(218, 112, 214)', 3951 palegoldenrod: 'rgb(238, 232, 170)', 3952 palegreen: 'rgb(152, 251, 152)', 3953 paleturquoise: 'rgb(175, 238, 238)', 3954 palevioletred: 'rgb(219, 112, 147)', 3955 papayawhip: 'rgb(255, 239, 213)', 3956 peachpuff: 'rgb(255, 218, 185)', 3957 peru: 'rgb(205, 133, 63)', 3958 pink: 'rgb(255, 192, 203)', 3959 plum: 'rgb(221, 160, 221)', 3960 powderblue: 'rgb(176, 224, 230)', 3961 purple: 'rgb(128, 0, 128)', 3962 red: 'rgb(255, 0, 0)', 3963 rosybrown: 'rgb(188, 143, 143)', 3964 royalblue: 'rgb( 65, 105, 225)', 3965 saddlebrown: 'rgb(139, 69, 19)', 3966 salmon: 'rgb(250, 128, 114)', 3967 sandybrown: 'rgb(244, 164, 96)', 3968 seagreen: 'rgb( 46, 139, 87)', 3969 seashell: 'rgb(255, 245, 238)', 3970 sienna: 'rgb(160, 82, 45)', 3971 silver: 'rgb(192, 192, 192)', 3972 skyblue: 'rgb(135, 206, 235)', 3973 slateblue: 'rgb(106, 90, 205)', 3974 slategray: 'rgb(112, 128, 144)', 3975 slategrey: 'rgb(112, 128, 144)', 3976 snow: 'rgb(255, 250, 250)', 3977 springgreen: 'rgb( 0, 255, 127)', 3978 steelblue: 'rgb( 70, 130, 180)', 3979 tan: 'rgb(210, 180, 140)', 3980 teal: 'rgb( 0, 128, 128)', 3981 thistle: 'rgb(216, 191, 216)', 3982 tomato: 'rgb(255, 99, 71)', 3983 turquoise: 'rgb( 64, 224, 208)', 3984 violet: 'rgb(238, 130, 238)', 3985 wheat: 'rgb(245, 222, 179)', 3986 white: 'rgb(255, 255, 255)', 3987 whitesmoke: 'rgb(245, 245, 245)', 3988 yellow: 'rgb(255, 255, 0)', 3989 yellowgreen: 'rgb(154, 205, 50)' 3990 }; 3991 3992 3993 3994 // class: $.jqplot.AxisLabelRenderer 3995 // Renderer to place labels on the axes. 3996 $.jqplot.AxisLabelRenderer = function(options) { 3997 // Group: Properties 3998 $.jqplot.ElemContainer.call(this); 3999 // name of the axis associated with this tick 4000 this.axis; 4001 // prop: show 4002 // wether or not to show the tick (mark and label). 4003 this.show = true; 4004 // prop: label 4005 // The text or html for the label. 4006 this.label = ''; 4007 this.fontFamily = null; 4008 this.fontSize = null; 4009 this.textColor = null; 4010 this._elem; 4011 // prop: escapeHTML 4012 // true to escape HTML entities in the label. 4013 this.escapeHTML = false; 4014 4015 $.extend(true, this, options); 4016 }; 4017 4018 $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer(); 4019 $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer; 4020 4021 $.jqplot.AxisLabelRenderer.prototype.init = function(options) { 4022 $.extend(true, this, options); 4023 }; 4024 4025 $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) { 4026 // Memory Leaks patch 4027 if (this._elem) { 4028 this._elem.emptyForce(); 4029 this._elem = null; 4030 } 4031 4032 this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>'); 4033 4034 if (Number(this.label)) { 4035 this._elem.css('white-space', 'nowrap'); 4036 } 4037 4038 if (!this.escapeHTML) { 4039 this._elem.html(this.label); 4040 } 4041 else { 4042 this._elem.text(this.label); 4043 } 4044 if (this.fontFamily) { 4045 this._elem.css('font-family', this.fontFamily); 4046 } 4047 if (this.fontSize) { 4048 this._elem.css('font-size', this.fontSize); 4049 } 4050 if (this.textColor) { 4051 this._elem.css('color', this.textColor); 4052 } 4053 4054 return this._elem; 4055 }; 4056 4057 $.jqplot.AxisLabelRenderer.prototype.pack = function() { 4058 }; 4059 4060 // class: $.jqplot.AxisTickRenderer 4061 // A "tick" object showing the value of a tick/gridline on the plot. 4062 $.jqplot.AxisTickRenderer = function(options) { 4063 // Group: Properties 4064 $.jqplot.ElemContainer.call(this); 4065 // prop: mark 4066 // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null. 4067 this.mark = 'outside'; 4068 // name of the axis associated with this tick 4069 this.axis; 4070 // prop: showMark 4071 // wether or not to show the mark on the axis. 4072 this.showMark = true; 4073 // prop: showGridline 4074 // wether or not to draw the gridline on the grid at this tick. 4075 this.showGridline = true; 4076 // prop: isMinorTick 4077 // if this is a minor tick. 4078 this.isMinorTick = false; 4079 // prop: size 4080 // Length of the tick beyond the grid in pixels. 4081 // DEPRECATED: This has been superceeded by markSize 4082 this.size = 4; 4083 // prop: markSize 4084 // Length of the tick marks in pixels. For 'cross' style, length 4085 // will be stoked above and below axis, so total length will be twice this. 4086 this.markSize = 6; 4087 // prop: show 4088 // wether or not to show the tick (mark and label). 4089 // Setting this to false requires more testing. It is recommended 4090 // to set showLabel and showMark to false instead. 4091 this.show = true; 4092 // prop: showLabel 4093 // wether or not to show the label. 4094 this.showLabel = true; 4095 this.label = null; 4096 this.value = null; 4097 this._styles = {}; 4098 // prop: formatter 4099 // A class of a formatter for the tick text. sprintf by default. 4100 this.formatter = $.jqplot.DefaultTickFormatter; 4101 // prop: prefix 4102 // String to prepend to the tick label. 4103 // Prefix is prepended to the formatted tick label. 4104 this.prefix = ''; 4105 // prop: suffix 4106 // String to append to the tick label. 4107 // Suffix is appended to the formatted tick label. 4108 this.suffix = ''; 4109 // prop: formatString 4110 // string passed to the formatter. 4111 this.formatString = ''; 4112 // prop: fontFamily 4113 // css spec for the font-family css attribute. 4114 this.fontFamily; 4115 // prop: fontSize 4116 // css spec for the font-size css attribute. 4117 this.fontSize; 4118 // prop: textColor 4119 // css spec for the color attribute. 4120 this.textColor; 4121 // prop: escapeHTML 4122 // true to escape HTML entities in the label. 4123 this.escapeHTML = false; 4124 this._elem; 4125 this._breakTick = false; 4126 4127 $.extend(true, this, options); 4128 }; 4129 4130 $.jqplot.AxisTickRenderer.prototype.init = function(options) { 4131 $.extend(true, this, options); 4132 }; 4133 4134 $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer(); 4135 $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer; 4136 4137 $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) { 4138 this.value = value; 4139 this.axis = axisName; 4140 if (isMinor) { 4141 this.isMinorTick = true; 4142 } 4143 return this; 4144 }; 4145 4146 $.jqplot.AxisTickRenderer.prototype.draw = function() { 4147 if (this.label === null) { 4148 this.label = this.prefix + this.formatter(this.formatString, this.value) + this.suffix; 4149 } 4150 var style = {position: 'absolute'}; 4151 if (Number(this.label)) { 4152 style['whitSpace'] = 'nowrap'; 4153 } 4154 4155 // Memory Leaks patch 4156 if (this._elem) { 4157 this._elem.emptyForce(); 4158 this._elem = null; 4159 } 4160 4161 this._elem = $(document.createElement('div')); 4162 this._elem.addClass("jqplot-"+this.axis+"-tick"); 4163 4164 if (!this.escapeHTML) { 4165 this._elem.html(this.label); 4166 } 4167 else { 4168 this._elem.text(this.label); 4169 } 4170 4171 this._elem.css(style); 4172 4173 for (var s in this._styles) { 4174 this._elem.css(s, this._styles[s]); 4175 } 4176 if (this.fontFamily) { 4177 this._elem.css('font-family', this.fontFamily); 4178 } 4179 if (this.fontSize) { 4180 this._elem.css('font-size', this.fontSize); 4181 } 4182 if (this.textColor) { 4183 this._elem.css('color', this.textColor); 4184 } 4185 if (this._breakTick) { 4186 this._elem.addClass('jqplot-breakTick'); 4187 } 4188 4189 return this._elem; 4190 }; 4191 4192 $.jqplot.DefaultTickFormatter = function (format, val) { 4193 if (typeof val == 'number') { 4194 if (!format) { 4195 format = $.jqplot.config.defaultTickFormatString; 4196 } 4197 return $.jqplot.sprintf(format, val); 4198 } 4199 else { 4200 return String(val); 4201 } 4202 }; 4203 4204 $.jqplot.PercentTickFormatter = function (format, val) { 4205 if (typeof val == 'number') { 4206 val = 100 * val; 4207 if (!format) { 4208 format = $.jqplot.config.defaultTickFormatString; 4209 } 4210 return $.jqplot.sprintf(format, val); 4211 } 4212 else { 4213 return String(val); 4214 } 4215 }; 4216 4217 $.jqplot.AxisTickRenderer.prototype.pack = function() { 4218 }; 4219 4220 // Class: $.jqplot.CanvasGridRenderer 4221 // The default jqPlot grid renderer, creating a grid on a canvas element. 4222 // The renderer has no additional options beyond the <Grid> class. 4223 $.jqplot.CanvasGridRenderer = function(){ 4224 this.shadowRenderer = new $.jqplot.ShadowRenderer(); 4225 }; 4226 4227 // called with context of Grid object 4228 $.jqplot.CanvasGridRenderer.prototype.init = function(options) { 4229 this._ctx; 4230 $.extend(true, this, options); 4231 // set the shadow renderer options 4232 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}; 4233 this.renderer.shadowRenderer.init(sopts); 4234 }; 4235 4236 // called with context of Grid. 4237 $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) { 4238 var elem; 4239 // Memory Leaks patch 4240 if (this._elem) { 4241 if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { 4242 elem = this._elem.get(0); 4243 window.G_vmlCanvasManager.uninitElement(elem); 4244 elem = null; 4245 } 4246 4247 this._elem.emptyForce(); 4248 this._elem = null; 4249 } 4250 4251 elem = plot.canvasManager.getCanvas(); 4252 4253 var w = this._plotDimensions.width; 4254 var h = this._plotDimensions.height; 4255 elem.width = w; 4256 elem.height = h; 4257 this._elem = $(elem); 4258 this._elem.addClass('jqplot-grid-canvas'); 4259 this._elem.css({ position: 'absolute', left: 0, top: 0 }); 4260 4261 elem = plot.canvasManager.initCanvas(elem); 4262 4263 this._top = this._offsets.top; 4264 this._bottom = h - this._offsets.bottom; 4265 this._left = this._offsets.left; 4266 this._right = w - this._offsets.right; 4267 this._width = this._right - this._left; 4268 this._height = this._bottom - this._top; 4269 // avoid memory leak 4270 elem = null; 4271 return this._elem; 4272 }; 4273 4274 $.jqplot.CanvasGridRenderer.prototype.draw = function() { 4275 this._ctx = this._elem.get(0).getContext("2d"); 4276 var ctx = this._ctx; 4277 var axes = this._axes; 4278 // Add the grid onto the grid canvas. This is the bottom most layer. 4279 ctx.save(); 4280 ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height); 4281 ctx.fillStyle = this.backgroundColor || this.background; 4282 ctx.fillRect(this._left, this._top, this._width, this._height); 4283 4284 ctx.save(); 4285 ctx.lineJoin = 'miter'; 4286 ctx.lineCap = 'butt'; 4287 ctx.lineWidth = this.gridLineWidth; 4288 ctx.strokeStyle = this.gridLineColor; 4289 var b, e, s, m; 4290 var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis']; 4291 for (var i=4; i>0; i--) { 4292 var name = ax[i-1]; 4293 var axis = axes[name]; 4294 var ticks = axis._ticks; 4295 var numticks = ticks.length; 4296 if (axis.show) { 4297 if (axis.drawBaseline) { 4298 var bopts = {}; 4299 if (axis.baselineWidth !== null) { 4300 bopts.lineWidth = axis.baselineWidth; 4301 } 4302 if (axis.baselineColor !== null) { 4303 bopts.strokeStyle = axis.baselineColor; 4304 } 4305 switch (name) { 4306 case 'xaxis': 4307 drawLine (this._left, this._bottom, this._right, this._bottom, bopts); 4308 break; 4309 case 'yaxis': 4310 drawLine (this._left, this._bottom, this._left, this._top, bopts); 4311 break; 4312 case 'x2axis': 4313 drawLine (this._left, this._bottom, this._right, this._bottom, bopts); 4314 break; 4315 case 'y2axis': 4316 drawLine (this._right, this._bottom, this._right, this._top, bopts); 4317 break; 4318 } 4319 } 4320 for (var j=numticks; j>0; j--) { 4321 var t = ticks[j-1]; 4322 if (t.show) { 4323 var pos = Math.round(axis.u2p(t.value)) + 0.5; 4324 switch (name) { 4325 case 'xaxis': 4326 // draw the grid line if we should 4327 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { 4328 drawLine(pos, this._top, pos, this._bottom); 4329 } 4330 // draw the mark 4331 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { 4332 s = t.markSize; 4333 m = t.mark; 4334 var pos = Math.round(axis.u2p(t.value)) + 0.5; 4335 switch (m) { 4336 case 'outside': 4337 b = this._bottom; 4338 e = this._bottom+s; 4339 break; 4340 case 'inside': 4341 b = this._bottom-s; 4342 e = this._bottom; 4343 break; 4344 case 'cross': 4345 b = this._bottom-s; 4346 e = this._bottom+s; 4347 break; 4348 default: 4349 b = this._bottom; 4350 e = this._bottom+s; 4351 break; 4352 } 4353 // draw the shadow 4354 if (this.shadow) { 4355 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}); 4356 } 4357 // draw the line 4358 drawLine(pos, b, pos, e); 4359 } 4360 break; 4361 case 'yaxis': 4362 // draw the grid line 4363 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { 4364 drawLine(this._right, pos, this._left, pos); 4365 } 4366 // draw the mark 4367 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { 4368 s = t.markSize; 4369 m = t.mark; 4370 var pos = Math.round(axis.u2p(t.value)) + 0.5; 4371 switch (m) { 4372 case 'outside': 4373 b = this._left-s; 4374 e = this._left; 4375 break; 4376 case 'inside': 4377 b = this._left; 4378 e = this._left+s; 4379 break; 4380 case 'cross': 4381 b = this._left-s; 4382 e = this._left+s; 4383 break; 4384 default: 4385 b = this._left-s; 4386 e = this._left; 4387 break; 4388 } 4389 // draw the shadow 4390 if (this.shadow) { 4391 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}); 4392 } 4393 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); 4394 } 4395 break; 4396 case 'x2axis': 4397 // draw the grid line 4398 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { 4399 drawLine(pos, this._bottom, pos, this._top); 4400 } 4401 // draw the mark 4402 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { 4403 s = t.markSize; 4404 m = t.mark; 4405 var pos = Math.round(axis.u2p(t.value)) + 0.5; 4406 switch (m) { 4407 case 'outside': 4408 b = this._top-s; 4409 e = this._top; 4410 break; 4411 case 'inside': 4412 b = this._top; 4413 e = this._top+s; 4414 break; 4415 case 'cross': 4416 b = this._top-s; 4417 e = this._top+s; 4418 break; 4419 default: 4420 b = this._top-s; 4421 e = this._top; 4422 break; 4423 } 4424 // draw the shadow 4425 if (this.shadow) { 4426 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}); 4427 } 4428 drawLine(pos, b, pos, e); 4429 } 4430 break; 4431 case 'y2axis': 4432 // draw the grid line 4433 if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { 4434 drawLine(this._left, pos, this._right, pos); 4435 } 4436 // draw the mark 4437 if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { 4438 s = t.markSize; 4439 m = t.mark; 4440 var pos = Math.round(axis.u2p(t.value)) + 0.5; 4441 switch (m) { 4442 case 'outside': 4443 b = this._right; 4444 e = this._right+s; 4445 break; 4446 case 'inside': 4447 b = this._right-s; 4448 e = this._right; 4449 break; 4450 case 'cross': 4451 b = this._right-s; 4452 e = this._right+s; 4453 break; 4454 default: 4455 b = this._right; 4456 e = this._right+s; 4457 break; 4458 } 4459 // draw the shadow 4460 if (this.shadow) { 4461 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}); 4462 } 4463 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); 4464 } 4465 break; 4466 default: 4467 break; 4468 } 4469 } 4470 } 4471 t = null; 4472 } 4473 axis = null; 4474 ticks = null; 4475 } 4476 // Now draw grid lines for additional y axes 4477 ////// 4478 // TO DO: handle yMidAxis 4479 ////// 4480 ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis']; 4481 for (var i=7; i>0; i--) { 4482 var axis = axes[ax[i-1]]; 4483 var ticks = axis._ticks; 4484 if (axis.show) { 4485 var tn = ticks[axis.numberTicks-1]; 4486 var t0 = ticks[0]; 4487 var left = axis.getLeft(); 4488 var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]]; 4489 // draw the shadow 4490 if (this.shadow) { 4491 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false}); 4492 } 4493 // draw the line 4494 drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth}); 4495 // draw the tick marks 4496 for (var j=ticks.length; j>0; j--) { 4497 var t = ticks[j-1]; 4498 s = t.markSize; 4499 m = t.mark; 4500 var pos = Math.round(axis.u2p(t.value)) + 0.5; 4501 if (t.showMark && t.mark) { 4502 switch (m) { 4503 case 'outside': 4504 b = left; 4505 e = left+s; 4506 break; 4507 case 'inside': 4508 b = left-s; 4509 e = left; 4510 break; 4511 case 'cross': 4512 b = left-s; 4513 e = left+s; 4514 break; 4515 default: 4516 b = left; 4517 e = left+s; 4518 break; 4519 } 4520 points = [[b,pos], [e,pos]]; 4521 // draw the shadow 4522 if (this.shadow) { 4523 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false}); 4524 } 4525 // draw the line 4526 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); 4527 } 4528 t = null; 4529 } 4530 t0 = null; 4531 } 4532 axis = null; 4533 ticks = null; 4534 } 4535 4536 ctx.restore(); 4537 4538 function drawLine(bx, by, ex, ey, opts) { 4539 ctx.save(); 4540 opts = opts || {}; 4541 if (opts.lineWidth == null || opts.lineWidth != 0){ 4542 $.extend(true, ctx, opts); 4543 ctx.beginPath(); 4544 ctx.moveTo(bx, by); 4545 ctx.lineTo(ex, ey); 4546 ctx.stroke(); 4547 ctx.restore(); 4548 } 4549 } 4550 4551 if (this.shadow) { 4552 var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]]; 4553 this.renderer.shadowRenderer.draw(ctx, points); 4554 } 4555 // Now draw border around grid. Use axis border definitions. start at 4556 // upper left and go clockwise. 4557 if (this.borderWidth != 0 && this.drawBorder) { 4558 drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth}); 4559 drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth}); 4560 drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth}); 4561 drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth}); 4562 } 4563 // ctx.lineWidth = this.borderWidth; 4564 // ctx.strokeStyle = this.borderColor; 4565 // ctx.strokeRect(this._left, this._top, this._width, this._height); 4566 4567 ctx.restore(); 4568 ctx = null; 4569 axes = null; 4570 }; 4571 4572 // Class: $.jqplot.DivTitleRenderer 4573 // The default title renderer for jqPlot. This class has no options beyond the <Title> class. 4574 $.jqplot.DivTitleRenderer = function() { 4575 }; 4576 4577 $.jqplot.DivTitleRenderer.prototype.init = function(options) { 4578 $.extend(true, this, options); 4579 }; 4580 4581 $.jqplot.DivTitleRenderer.prototype.draw = function() { 4582 // Memory Leaks patch 4583 if (this._elem) { 4584 this._elem.emptyForce(); 4585 this._elem = null; 4586 } 4587 4588 var r = this.renderer; 4589 var elem = document.createElement('div'); 4590 this._elem = $(elem); 4591 this._elem.addClass('jqplot-title'); 4592 4593 if (!this.text) { 4594 this.show = false; 4595 this._elem.height(0); 4596 this._elem.width(0); 4597 } 4598 else if (this.text) { 4599 var color; 4600 if (this.color) { 4601 color = this.color; 4602 } 4603 else if (this.textColor) { 4604 color = this.textColor; 4605 } 4606 4607 // don't trust that a stylesheet is present, set the position. 4608 var styles = {position:'absolute', top:'0px', left:'0px'}; 4609 4610 if (this._plotWidth) { 4611 styles['width'] = this._plotWidth+'px'; 4612 } 4613 if (this.fontSize) { 4614 styles['fontSize'] = this.fontSize; 4615 } 4616 if (typeof this.textAlign === 'string') { 4617 styles['textAlign'] = this.textAlign; 4618 } 4619 else { 4620 styles['textAlign'] = 'center'; 4621 } 4622 if (color) { 4623 styles['color'] = color; 4624 } 4625 if (this.paddingBottom) { 4626 styles['paddingBottom'] = this.paddingBottom; 4627 } 4628 if (this.fontFamily) { 4629 styles['fontFamily'] = this.fontFamily; 4630 } 4631 4632 this._elem.css(styles); 4633 if (this.escapeHtml) { 4634 this._elem.text(this.text); 4635 } 4636 else { 4637 this._elem.html(this.text); 4638 } 4639 4640 4641 // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : ''; 4642 // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : ''; 4643 // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;'; 4644 // styletext += (color) ? 'color:'+color+';' : ''; 4645 // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : ''; 4646 // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>'); 4647 // if (this.fontFamily) { 4648 // this._elem.css('font-family', this.fontFamily); 4649 // } 4650 } 4651 4652 elem = null; 4653 4654 return this._elem; 4655 }; 4656 4657 $.jqplot.DivTitleRenderer.prototype.pack = function() { 4658 // nothing to do here 4659 }; 4660 4661 4662 var dotlen = 0.1; 4663 4664 $.jqplot.LinePattern = function (ctx, pattern) { 4665 4666 var defaultLinePatterns = { 4667 dotted: [ dotlen, $.jqplot.config.dotGapLength ], 4668 dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ], 4669 solid: null 4670 }; 4671 4672 if (typeof pattern === 'string') { 4673 if (pattern[0] === '.' || pattern[0] === '-') { 4674 var s = pattern; 4675 pattern = []; 4676 for (var i=0, imax=s.length; i<imax; i++) { 4677 if (s[i] === '.') { 4678 pattern.push( dotlen ); 4679 } 4680 else if (s[i] === '-') { 4681 pattern.push( $.jqplot.config.dashLength ); 4682 } 4683 else { 4684 continue; 4685 } 4686 pattern.push( $.jqplot.config.gapLength ); 4687 } 4688 } 4689 else { 4690 pattern = defaultLinePatterns[pattern]; 4691 } 4692 } 4693 4694 if (!(pattern && pattern.length)) { 4695 return ctx; 4696 } 4697 4698 var patternIndex = 0; 4699 var patternDistance = pattern[0]; 4700 var px = 0; 4701 var py = 0; 4702 var pathx0 = 0; 4703 var pathy0 = 0; 4704 4705 var moveTo = function (x, y) { 4706 ctx.moveTo( x, y ); 4707 px = x; 4708 py = y; 4709 pathx0 = x; 4710 pathy0 = y; 4711 }; 4712 4713 var lineTo = function (x, y) { 4714 var scale = ctx.lineWidth; 4715 var dx = x - px; 4716 var dy = y - py; 4717 var dist = Math.sqrt(dx*dx+dy*dy); 4718 if ((dist > 0) && (scale > 0)) { 4719 dx /= dist; 4720 dy /= dist; 4721 while (true) { 4722 var dp = scale * patternDistance; 4723 if (dp < dist) { 4724 px += dp * dx; 4725 py += dp * dy; 4726 if ((patternIndex & 1) == 0) { 4727 ctx.lineTo( px, py ); 4728 } 4729 else { 4730 ctx.moveTo( px, py ); 4731 } 4732 dist -= dp; 4733 patternIndex++; 4734 if (patternIndex >= pattern.length) { 4735 patternIndex = 0; 4736 } 4737 patternDistance = pattern[patternIndex]; 4738 } 4739 else { 4740 px = x; 4741 py = y; 4742 if ((patternIndex & 1) == 0) { 4743 ctx.lineTo( px, py ); 4744 } 4745 else { 4746 ctx.moveTo( px, py ); 4747 } 4748 patternDistance -= dist / scale; 4749 break; 4750 } 4751 } 4752 } 4753 }; 4754 4755 var beginPath = function () { 4756 ctx.beginPath(); 4757 }; 4758 4759 var closePath = function () { 4760 lineTo( pathx0, pathy0 ); 4761 }; 4762 4763 return { 4764 moveTo: moveTo, 4765 lineTo: lineTo, 4766 beginPath: beginPath, 4767 closePath: closePath 4768 }; 4769 }; 4770 4771 // Class: $.jqplot.LineRenderer 4772 // The default line renderer for jqPlot, this class has no options beyond the <Series> class. 4773 // Draws series as a line. 4774 $.jqplot.LineRenderer = function(){ 4775 this.shapeRenderer = new $.jqplot.ShapeRenderer(); 4776 this.shadowRenderer = new $.jqplot.ShadowRenderer(); 4777 }; 4778 4779 // called with scope of series. 4780 $.jqplot.LineRenderer.prototype.init = function(options, plot) { 4781 // Group: Properties 4782 // 4783 options = options || {}; 4784 this._type='line'; 4785 this.renderer.animation = { 4786 show: false, 4787 direction: 'left', 4788 speed: 2500, 4789 _supported: true 4790 }; 4791 // prop: smooth 4792 // True to draw a smoothed (interpolated) line through the data points 4793 // with automatically computed number of smoothing points. 4794 // Set to an integer number > 2 to specify number of smoothing points 4795 // to use between each data point. 4796 this.renderer.smooth = false; // true or a number > 2 for smoothing. 4797 this.renderer.tension = null; // null to auto compute or a number typically > 6. Fewer points requires higher tension. 4798 // prop: constrainSmoothing 4799 // True to use a more accurate smoothing algorithm that will 4800 // not overshoot any data points. False to allow overshoot but 4801 // produce a smoother looking line. 4802 this.renderer.constrainSmoothing = true; 4803 // this is smoothed data in grid coordinates, like gridData 4804 this.renderer._smoothedData = []; 4805 // this is smoothed data in plot units (plot coordinates), like plotData. 4806 this.renderer._smoothedPlotData = []; 4807 this.renderer._hiBandGridData = []; 4808 this.renderer._lowBandGridData = []; 4809 this.renderer._hiBandSmoothedData = []; 4810 this.renderer._lowBandSmoothedData = []; 4811 4812 // prop: bandData 4813 // Data used to draw error bands or confidence intervals above/below a line. 4814 // 4815 // bandData can be input in 3 forms. jqPlot will figure out which is the 4816 // low band line and which is the high band line for all forms: 4817 // 4818 // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where 4819 // [yl1, yl2, ...] are y values of the lower line and 4820 // [yu1, yu2, ...] are y values of the upper line. 4821 // In this case there must be the same number of y data points as data points 4822 // in the series and the bands will inherit the x values of the series. 4823 // 4824 // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]] 4825 // where [xl1, yl1] are x,y data points for the lower line and 4826 // [xh1, yh1] are x,y data points for the high line. 4827 // x values do not have to correspond to the x values of the series and can 4828 // be of any arbitrary length. 4829 // 4830 // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where 4831 // there must be 3 or more arrays and there must be the same number of arrays 4832 // as there are data points in the series. In this case, 4833 // [yl1, yu1] specifies the lower and upper y values for the 1st 4834 // data point and so on. The bands will inherit the x 4835 // values from the series. 4836 this.renderer.bandData = []; 4837 4838 // Group: bands 4839 // Banding around line, e.g error bands or confidence intervals. 4840 this.renderer.bands = { 4841 // prop: show 4842 // true to show the bands. If bandData or interval is 4843 // supplied, show will be set to true by default. 4844 show: false, 4845 hiData: [], 4846 lowData: [], 4847 // prop: color 4848 // color of lines at top and bottom of bands [default: series color]. 4849 color: this.color, 4850 // prop: showLines 4851 // True to show lines at top and bottom of bands [default: false]. 4852 showLines: false, 4853 // prop: fill 4854 // True to fill area between bands [default: true]. 4855 fill: true, 4856 // prop: fillColor 4857 // css color spec for filled area. [default: series color]. 4858 fillColor: null, 4859 _min: null, 4860 _max: null, 4861 // prop: interval 4862 // User specified interval above and below line for bands [default: '3%'']. 4863 // Can be a value like 3 or a string like '3%' 4864 // or an upper/lower array like [1, -2] or ['2%', '-1.5%'] 4865 interval: '3%' 4866 }; 4867 4868 4869 var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor}; 4870 4871 delete (options.highlightMouseOver); 4872 delete (options.highlightMouseDown); 4873 delete (options.highlightColor); 4874 4875 $.extend(true, this.renderer, options); 4876 4877 this.renderer.options = options; 4878 4879 // if we are given some band data, and bands aren't explicity set to false in options, turn them on. 4880 if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) { 4881 this.renderer.bands.show = true; 4882 } 4883 4884 // if we are given an interval, and bands aren't explicity set to false in options, turn them on. 4885 else if (options.bands && options.bands.show == null && options.bands.interval != null) { 4886 this.renderer.bands.show = true; 4887 } 4888 4889 // if plot is filled, turn off bands. 4890 if (this.fill) { 4891 this.renderer.bands.show = false; 4892 } 4893 4894 if (this.renderer.bands.show) { 4895 this.renderer.initBands.call(this, this.renderer.options, plot); 4896 } 4897 4898 4899 // smoothing is not compatible with stacked lines, disable 4900 if (this._stack) { 4901 this.renderer.smooth = false; 4902 } 4903 4904 // set the shape renderer options 4905 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}; 4906 this.renderer.shapeRenderer.init(opts); 4907 4908 var shadow_offset = options.shadowOffset; 4909 // set the shadow renderer options 4910 if (shadow_offset == null) { 4911 // scale the shadowOffset to the width of the line. 4912 if (this.lineWidth > 2.5) { 4913 shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6); 4914 // var shadow_offset = this.shadowOffset; 4915 } 4916 // for skinny lines, don't make such a big shadow. 4917 else { 4918 shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163; 4919 } 4920 } 4921 4922 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}; 4923 this.renderer.shadowRenderer.init(sopts); 4924 this._areaPoints = []; 4925 this._boundingBox = [[],[]]; 4926 4927 if (!this.isTrendline && this.fill || this.renderer.bands.show) { 4928 // Group: Properties 4929 // 4930 // prop: highlightMouseOver 4931 // True to highlight area on a filled plot when moused over. 4932 // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot. 4933 this.highlightMouseOver = true; 4934 // prop: highlightMouseDown 4935 // True to highlight when a mouse button is pressed over an area on a filled plot. 4936 // This will be disabled if highlightMouseOver is true. 4937 this.highlightMouseDown = false; 4938 // prop: highlightColor 4939 // color to use when highlighting an area on a filled plot. 4940 this.highlightColor = null; 4941 // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver 4942 if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) { 4943 lopts.highlightMouseOver = false; 4944 } 4945 4946 $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor}); 4947 4948 if (!this.highlightColor) { 4949 var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor; 4950 this.highlightColor = $.jqplot.computeHighlightColors(fc); 4951 } 4952 // turn off (disable) the highlighter plugin 4953 if (this.highlighter) { 4954 this.highlighter.show = false; 4955 } 4956 } 4957 4958 if (!this.isTrendline && plot) { 4959 plot.plugins.lineRenderer = {}; 4960 plot.postInitHooks.addOnce(postInit); 4961 plot.postDrawHooks.addOnce(postPlotDraw); 4962 plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove); 4963 plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown); 4964 plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp); 4965 plot.eventListenerHooks.addOnce('jqplotClick', handleClick); 4966 plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick); 4967 } 4968 4969 }; 4970 4971 $.jqplot.LineRenderer.prototype.initBands = function(options, plot) { 4972 // use bandData if no data specified in bands option 4973 //var bd = this.renderer.bandData; 4974 var bd = options.bandData || []; 4975 var bands = this.renderer.bands; 4976 bands.hiData = []; 4977 bands.lowData = []; 4978 var data = this.data; 4979 bands._max = null; 4980 bands._min = null; 4981 // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values. 4982 if (bd.length == 2) { 4983 // Do we have an array of x,y values? 4984 // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]] 4985 if ($.isArray(bd[0][0])) { 4986 // since an arbitrary array of points, spin through all of them to determine max and min lines. 4987 4988 var p; 4989 var bdminidx = 0, bdmaxidx = 0; 4990 for (var i = 0, l = bd[0].length; i<l; i++) { 4991 p = bd[0][i]; 4992 if ((p[1] != null && p[1] > bands._max) || bands._max == null) { 4993 bands._max = p[1]; 4994 } 4995 if ((p[1] != null && p[1] < bands._min) || bands._min == null) { 4996 bands._min = p[1]; 4997 } 4998 } 4999 for (var i = 0, l = bd[1].length; i<l; i++) { 5000 p = bd[1][i]; 5001 if ((p[1] != null && p[1] > bands._max) || bands._max == null) { 5002 bands._max = p[1]; 5003 bdmaxidx = 1; 5004 } 5005 if ((p[1] != null && p[1] < bands._min) || bands._min == null) { 5006 bands._min = p[1]; 5007 bdminidx = 1; 5008 } 5009 } 5010 5011 if (bdmaxidx === bdminidx) { 5012 bands.show = false; 5013 } 5014 5015 bands.hiData = bd[bdmaxidx]; 5016 bands.lowData = bd[bdminidx]; 5017 } 5018 // else data is arrays of y values 5019 // like [[1,4,3], [3,6,5]] 5020 // must have same number of band data points as points in series 5021 else if (bd[0].length === data.length && bd[1].length === data.length) { 5022 var hi = (bd[0][0] > bd[1][0]) ? 0 : 1; 5023 var low = (hi) ? 0 : 1; 5024 for (var i=0, l=data.length; i < l; i++) { 5025 bands.hiData.push([data[i][0], bd[hi][i]]); 5026 bands.lowData.push([data[i][0], bd[low][i]]); 5027 } 5028 } 5029 5030 // we don't have proper data array, don't show bands. 5031 else { 5032 bands.show = false; 5033 } 5034 } 5035 5036 // if more than 2 arrays, have arrays of [ylow, yhi] values. 5037 // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]] 5038 // this is assumed to be of the latter form. 5039 else if (bd.length > 2 && !$.isArray(bd[0][0])) { 5040 var hi = (bd[0][0] > bd[0][1]) ? 0 : 1; 5041 var low = (hi) ? 0 : 1; 5042 for (var i=0, l=bd.length; i<l; i++) { 5043 bands.hiData.push([data[i][0], bd[i][hi]]); 5044 bands.lowData.push([data[i][0], bd[i][low]]); 5045 } 5046 } 5047 5048 // don't have proper data, auto calculate 5049 else { 5050 var intrv = bands.interval; 5051 var a = null; 5052 var b = null; 5053 var afunc = null; 5054 var bfunc = null; 5055 5056 if ($.isArray(intrv)) { 5057 a = intrv[0]; 5058 b = intrv[1]; 5059 } 5060 else { 5061 a = intrv; 5062 } 5063 5064 if (isNaN(a)) { 5065 // we have a string 5066 if (a.charAt(a.length - 1) === '%') { 5067 afunc = 'multiply'; 5068 a = parseFloat(a)/100 + 1; 5069 } 5070 } 5071 5072 else { 5073 a = parseFloat(a); 5074 afunc = 'add'; 5075 } 5076 5077 if (b !== null && isNaN(b)) { 5078 // we have a string 5079 if (b.charAt(b.length - 1) === '%') { 5080 bfunc = 'multiply'; 5081 b = parseFloat(b)/100 + 1; 5082 } 5083 } 5084 5085 else if (b !== null) { 5086 b = parseFloat(b); 5087 bfunc = 'add'; 5088 } 5089 5090 if (a !== null) { 5091 if (b === null) { 5092 b = -a; 5093 bfunc = afunc; 5094 if (bfunc === 'multiply') { 5095 b += 2; 5096 } 5097 } 5098 5099 // make sure a always applies to hi band. 5100 if (a < b) { 5101 var temp = a; 5102 a = b; 5103 b = temp; 5104 temp = afunc; 5105 afunc = bfunc; 5106 bfunc = temp; 5107 } 5108 5109 for (var i=0, l = data.length; i < l; i++) { 5110 switch (afunc) { 5111 case 'add': 5112 bands.hiData.push([data[i][0], data[i][1] + a]); 5113 break; 5114 case 'multiply': 5115 bands.hiData.push([data[i][0], data[i][1] * a]); 5116 break; 5117 } 5118 switch (bfunc) { 5119 case 'add': 5120 bands.lowData.push([data[i][0], data[i][1] + b]); 5121 break; 5122 case 'multiply': 5123 bands.lowData.push([data[i][0], data[i][1] * b]); 5124 break; 5125 } 5126 } 5127 } 5128 5129 else { 5130 bands.show = false; 5131 } 5132 } 5133 5134 var hd = bands.hiData; 5135 var ld = bands.lowData; 5136 for (var i = 0, l = hd.length; i<l; i++) { 5137 if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) { 5138 bands._max = hd[i][1]; 5139 } 5140 } 5141 for (var i = 0, l = ld.length; i<l; i++) { 5142 if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) { 5143 bands._min = ld[i][1]; 5144 } 5145 } 5146 5147 // one last check for proper data 5148 // these don't apply any more since allowing arbitrary x,y values 5149 // if (bands.hiData.length != bands.lowData.length) { 5150 // bands.show = false; 5151 // } 5152 5153 // if (bands.hiData.length != this.data.length) { 5154 // bands.show = false; 5155 // } 5156 5157 if (bands.fillColor === null) { 5158 var c = $.jqplot.getColorComponents(bands.color); 5159 // now adjust alpha to differentiate fill 5160 c[3] = c[3] * 0.5; 5161 bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')'; 5162 } 5163 }; 5164 5165 function getSteps (d, f) { 5166 return (3.4182054+f) * Math.pow(d, -0.3534992); 5167 } 5168 5169 function computeSteps (d1, d2) { 5170 var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2)); 5171 return 5.7648 * Math.log(s) + 7.4456; 5172 } 5173 5174 function tanh (x) { 5175 var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1); 5176 return a; 5177 } 5178 5179 ////////// 5180 // computeConstrainedSmoothedData 5181 // An implementation of the constrained cubic spline interpolation 5182 // method as presented in: 5183 // 5184 // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications 5185 // http://www.korf.co.uk/spline.pdf 5186 // 5187 // The implementation below borrows heavily from the sample Visual Basic 5188 // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls 5189 // 5190 ///////// 5191 5192 // called with scope of series 5193 function computeConstrainedSmoothedData (gd) { 5194 var smooth = this.renderer.smooth; 5195 var dim = this.canvas.getWidth(); 5196 var xp = this._xaxis.series_p2u; 5197 var yp = this._yaxis.series_p2u; 5198 var steps =null; 5199 var _steps = null; 5200 var dist = gd.length/dim; 5201 var _smoothedData = []; 5202 var _smoothedPlotData = []; 5203 5204 if (!isNaN(parseFloat(smooth))) { 5205 steps = parseFloat(smooth); 5206 } 5207 else { 5208 steps = getSteps(dist, 0.5); 5209 } 5210 5211 var yy = []; 5212 var xx = []; 5213 5214 for (var i=0, l = gd.length; i<l; i++) { 5215 yy.push(gd[i][1]); 5216 xx.push(gd[i][0]); 5217 } 5218 5219 function dxx(x1, x0) { 5220 if (x1 - x0 == 0) { 5221 return Math.pow(10,10); 5222 } 5223 else { 5224 return x1 - x0; 5225 } 5226 } 5227 5228 var A, B, C, D; 5229 // loop through each line segment. Have # points - 1 line segments. Nmber segments starting at 1. 5230 var nmax = gd.length - 1; 5231 for (var num = 1, gdl = gd.length; num<gdl; num++) { 5232 var gxx = []; 5233 var ggxx = []; 5234 // point at each end of segment. 5235 for (var j = 0; j < 2; j++) { 5236 var i = num - 1 + j; // point number, 0 to # points. 5237 5238 if (i == 0 || i == nmax) { 5239 gxx[j] = Math.pow(10, 10); 5240 } 5241 else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) { 5242 gxx[j] = 0; 5243 } 5244 else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) { 5245 gxx[j] = 0; 5246 } 5247 else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) { 5248 gxx[j] = 0; 5249 } 5250 5251 else { 5252 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])); 5253 } 5254 } 5255 5256 // Reset first derivative (slope) at first and last point 5257 if (num == 1) { 5258 // First point has 0 2nd derivative 5259 gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2; 5260 } 5261 else if (num == nmax) { 5262 // Last point has 0 2nd derivative 5263 gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2; 5264 } 5265 5266 // Calc second derivative at points 5267 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); 5268 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); 5269 5270 // Calc constants for cubic interpolation 5271 D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]); 5272 C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]); 5273 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]); 5274 A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3); 5275 5276 var increment = (xx[num] - xx[num - 1]) / steps; 5277 var temp, tempx; 5278 5279 for (var j = 0, l = steps; j < l; j++) { 5280 temp = []; 5281 tempx = xx[num - 1] + j * increment; 5282 temp.push(tempx); 5283 temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3)); 5284 _smoothedData.push(temp); 5285 _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]); 5286 } 5287 } 5288 5289 _smoothedData.push(gd[i]); 5290 _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]); 5291 5292 return [_smoothedData, _smoothedPlotData]; 5293 } 5294 5295 /////// 5296 // computeHermiteSmoothedData 5297 // A hermite spline smoothing of the plot data. 5298 // This implementation is derived from the one posted 5299 // by krypin on the jqplot-users mailing list: 5300 // 5301 // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1 5302 // 5303 // with a blog post: 5304 // 5305 // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/ 5306 // 5307 // and download of the original plugin: 5308 // 5309 // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js 5310 ////////// 5311 5312 // called with scope of series 5313 function computeHermiteSmoothedData (gd) { 5314 var smooth = this.renderer.smooth; 5315 var tension = this.renderer.tension; 5316 var dim = this.canvas.getWidth(); 5317 var xp = this._xaxis.series_p2u; 5318 var yp = this._yaxis.series_p2u; 5319 var steps =null; 5320 var _steps = null; 5321 var a = null; 5322 var a1 = null; 5323 var a2 = null; 5324 var slope = null; 5325 var slope2 = null; 5326 var temp = null; 5327 var t, s, h1, h2, h3, h4; 5328 var TiX, TiY, Ti1X, Ti1Y; 5329 var pX, pY, p; 5330 var sd = []; 5331 var spd = []; 5332 var dist = gd.length/dim; 5333 var min, max, stretch, scale, shift; 5334 var _smoothedData = []; 5335 var _smoothedPlotData = []; 5336 if (!isNaN(parseFloat(smooth))) { 5337 steps = parseFloat(smooth); 5338 } 5339 else { 5340 steps = getSteps(dist, 0.5); 5341 } 5342 if (!isNaN(parseFloat(tension))) { 5343 tension = parseFloat(tension); 5344 } 5345 5346 for (var i=0, l = gd.length-1; i < l; i++) { 5347 5348 if (tension === null) { 5349 slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0])); 5350 5351 min = 0.3; 5352 max = 0.6; 5353 stretch = (max - min)/2.0; 5354 scale = 2.5; 5355 shift = -1.4; 5356 5357 temp = slope/scale + shift; 5358 5359 a1 = stretch * tanh(temp) - stretch * tanh(shift) + min; 5360 5361 // if have both left and right line segments, will use minimum tension. 5362 if (i > 0) { 5363 slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0])); 5364 } 5365 temp = slope2/scale + shift; 5366 5367 a2 = stretch * tanh(temp) - stretch * tanh(shift) + min; 5368 5369 a = (a1 + a2)/2.0; 5370 5371 } 5372 else { 5373 a = tension; 5374 } 5375 for (t=0; t < steps; t++) { 5376 s = t / steps; 5377 h1 = (1 + 2*s)*Math.pow((1-s),2); 5378 h2 = s*Math.pow((1-s),2); 5379 h3 = Math.pow(s,2)*(3-2*s); 5380 h4 = Math.pow(s,2)*(s-1); 5381 5382 if (gd[i-1]) { 5383 TiX = a * (gd[i+1][0] - gd[i-1][0]); 5384 TiY = a * (gd[i+1][1] - gd[i-1][1]); 5385 } else { 5386 TiX = a * (gd[i+1][0] - gd[i][0]); 5387 TiY = a * (gd[i+1][1] - gd[i][1]); 5388 } 5389 if (gd[i+2]) { 5390 Ti1X = a * (gd[i+2][0] - gd[i][0]); 5391 Ti1Y = a * (gd[i+2][1] - gd[i][1]); 5392 } else { 5393 Ti1X = a * (gd[i+1][0] - gd[i][0]); 5394 Ti1Y = a * (gd[i+1][1] - gd[i][1]); 5395 } 5396 5397 pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X; 5398 pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y; 5399 p = [pX, pY]; 5400 5401 _smoothedData.push(p); 5402 _smoothedPlotData.push([xp(pX), yp(pY)]); 5403 } 5404 } 5405 _smoothedData.push(gd[l]); 5406 _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]); 5407 5408 return [_smoothedData, _smoothedPlotData]; 5409 } 5410 5411 // setGridData 5412 // converts the user data values to grid coordinates and stores them 5413 // in the gridData array. 5414 // Called with scope of a series. 5415 $.jqplot.LineRenderer.prototype.setGridData = function(plot) { 5416 // recalculate the grid data 5417 var xp = this._xaxis.series_u2p; 5418 var yp = this._yaxis.series_u2p; 5419 var data = this._plotData; 5420 var pdata = this._prevPlotData; 5421 this.gridData = []; 5422 this._prevGridData = []; 5423 this.renderer._smoothedData = []; 5424 this.renderer._smoothedPlotData = []; 5425 this.renderer._hiBandGridData = []; 5426 this.renderer._lowBandGridData = []; 5427 this.renderer._hiBandSmoothedData = []; 5428 this.renderer._lowBandSmoothedData = []; 5429 var bands = this.renderer.bands; 5430 var hasNull = false; 5431 for (var i=0, l=data.length; i < l; i++) { 5432 // if not a line series or if no nulls in data, push the converted point onto the array. 5433 if (data[i][0] != null && data[i][1] != null) { 5434 this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]); 5435 } 5436 // else if there is a null, preserve it. 5437 else if (data[i][0] == null) { 5438 hasNull = true; 5439 this.gridData.push([null, yp.call(this._yaxis, data[i][1])]); 5440 } 5441 else if (data[i][1] == null) { 5442 hasNull = true; 5443 this.gridData.push([xp.call(this._xaxis, data[i][0]), null]); 5444 } 5445 // if not a line series or if no nulls in data, push the converted point onto the array. 5446 if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) { 5447 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]); 5448 } 5449 // else if there is a null, preserve it. 5450 else if (pdata[i] != null && pdata[i][0] == null) { 5451 this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]); 5452 } 5453 else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) { 5454 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]); 5455 } 5456 } 5457 5458 // don't do smoothing or bands on broken lines. 5459 if (hasNull) { 5460 this.renderer.smooth = false; 5461 if (this._type === 'line') { 5462 bands.show = false; 5463 } 5464 } 5465 5466 if (this._type === 'line' && bands.show) { 5467 for (var i=0, l=bands.hiData.length; i<l; i++) { 5468 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]); 5469 } 5470 for (var i=0, l=bands.lowData.length; i<l; i++) { 5471 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]); 5472 } 5473 } 5474 5475 // calculate smoothed data if enough points and no nulls 5476 if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) { 5477 var ret; 5478 if (this.renderer.constrainSmoothing) { 5479 ret = computeConstrainedSmoothedData.call(this, this.gridData); 5480 this.renderer._smoothedData = ret[0]; 5481 this.renderer._smoothedPlotData = ret[1]; 5482 5483 if (bands.show) { 5484 ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData); 5485 this.renderer._hiBandSmoothedData = ret[0]; 5486 ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData); 5487 this.renderer._lowBandSmoothedData = ret[0]; 5488 } 5489 5490 ret = null; 5491 } 5492 else { 5493 ret = computeHermiteSmoothedData.call(this, this.gridData); 5494 this.renderer._smoothedData = ret[0]; 5495 this.renderer._smoothedPlotData = ret[1]; 5496 5497 if (bands.show) { 5498 ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData); 5499 this.renderer._hiBandSmoothedData = ret[0]; 5500 ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData); 5501 this.renderer._lowBandSmoothedData = ret[0]; 5502 } 5503 5504 ret = null; 5505 } 5506 } 5507 }; 5508 5509 // makeGridData 5510 // converts any arbitrary data values to grid coordinates and 5511 // returns them. This method exists so that plugins can use a series' 5512 // linerenderer to generate grid data points without overwriting the 5513 // grid data associated with that series. 5514 // Called with scope of a series. 5515 $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) { 5516 // recalculate the grid data 5517 var xp = this._xaxis.series_u2p; 5518 var yp = this._yaxis.series_u2p; 5519 var gd = []; 5520 var pgd = []; 5521 this.renderer._smoothedData = []; 5522 this.renderer._smoothedPlotData = []; 5523 this.renderer._hiBandGridData = []; 5524 this.renderer._lowBandGridData = []; 5525 this.renderer._hiBandSmoothedData = []; 5526 this.renderer._lowBandSmoothedData = []; 5527 var bands = this.renderer.bands; 5528 var hasNull = false; 5529 for (var i=0; i<data.length; i++) { 5530 // if not a line series or if no nulls in data, push the converted point onto the array. 5531 if (data[i][0] != null && data[i][1] != null) { 5532 gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]); 5533 } 5534 // else if there is a null, preserve it. 5535 else if (data[i][0] == null) { 5536 hasNull = true; 5537 gd.push([null, yp.call(this._yaxis, data[i][1])]); 5538 } 5539 else if (data[i][1] == null) { 5540 hasNull = true; 5541 gd.push([xp.call(this._xaxis, data[i][0]), null]); 5542 } 5543 } 5544 5545 // don't do smoothing or bands on broken lines. 5546 if (hasNull) { 5547 this.renderer.smooth = false; 5548 if (this._type === 'line') { 5549 bands.show = false; 5550 } 5551 } 5552 5553 if (this._type === 'line' && bands.show) { 5554 for (var i=0, l=bands.hiData.length; i<l; i++) { 5555 this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]); 5556 } 5557 for (var i=0, l=bands.lowData.length; i<l; i++) { 5558 this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]); 5559 } 5560 } 5561 5562 if (this._type === 'line' && this.renderer.smooth && gd.length > 2) { 5563 var ret; 5564 if (this.renderer.constrainSmoothing) { 5565 ret = computeConstrainedSmoothedData.call(this, gd); 5566 this.renderer._smoothedData = ret[0]; 5567 this.renderer._smoothedPlotData = ret[1]; 5568 5569 if (bands.show) { 5570 ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData); 5571 this.renderer._hiBandSmoothedData = ret[0]; 5572 ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData); 5573 this.renderer._lowBandSmoothedData = ret[0]; 5574 } 5575 5576 ret = null; 5577 } 5578 else { 5579 ret = computeHermiteSmoothedData.call(this, gd); 5580 this.renderer._smoothedData = ret[0]; 5581 this.renderer._smoothedPlotData = ret[1]; 5582 5583 if (bands.show) { 5584 ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData); 5585 this.renderer._hiBandSmoothedData = ret[0]; 5586 ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData); 5587 this.renderer._lowBandSmoothedData = ret[0]; 5588 } 5589 5590 ret = null; 5591 } 5592 } 5593 return gd; 5594 }; 5595 5596 5597 // called within scope of series. 5598 $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) { 5599 var i; 5600 // get a copy of the options, so we don't modify the original object. 5601 var opts = $.extend(true, {}, options); 5602 var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow; 5603 var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine; 5604 var fill = (opts.fill != undefined) ? opts.fill : this.fill; 5605 var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke; 5606 var xmin, ymin, xmax, ymax; 5607 ctx.save(); 5608 if (gd.length) { 5609 if (showLine) { 5610 // if we fill, we'll have to add points to close the curve. 5611 if (fill) { 5612 if (this.fillToZero) { 5613 // have to break line up into shapes at axis crossings 5614 var negativeColor = this.negativeColor; 5615 if (! this.useNegativeColors) { 5616 negativeColor = opts.fillStyle; 5617 } 5618 var isnegative = false; 5619 var posfs = opts.fillStyle; 5620 5621 // if stoking line as well as filling, get a copy of line data. 5622 if (fillAndStroke) { 5623 var fasgd = gd.slice(0); 5624 } 5625 // if not stacked, fill down to axis 5626 if (this.index == 0 || !this._stack) { 5627 5628 var tempgd = []; 5629 var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData; 5630 this._areaPoints = []; 5631 var pyzero = this._yaxis.series_u2p(this.fillToValue); 5632 var pxzero = this._xaxis.series_u2p(this.fillToValue); 5633 5634 opts.closePath = true; 5635 5636 if (this.fillAxis == 'y') { 5637 tempgd.push([gd[0][0], pyzero]); 5638 this._areaPoints.push([gd[0][0], pyzero]); 5639 5640 for (var i=0; i<gd.length-1; i++) { 5641 tempgd.push(gd[i]); 5642 this._areaPoints.push(gd[i]); 5643 // do we have an axis crossing? 5644 if (pd[i][1] * pd[i+1][1] < 0) { 5645 if (pd[i][1] < 0) { 5646 isnegative = true; 5647 opts.fillStyle = negativeColor; 5648 } 5649 else { 5650 isnegative = false; 5651 opts.fillStyle = posfs; 5652 } 5653 5654 var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]); 5655 tempgd.push([xintercept, pyzero]); 5656 this._areaPoints.push([xintercept, pyzero]); 5657 // now draw this shape and shadow. 5658 if (shadow) { 5659 this.renderer.shadowRenderer.draw(ctx, tempgd, opts); 5660 } 5661 this.renderer.shapeRenderer.draw(ctx, tempgd, opts); 5662 // now empty temp array and continue 5663 tempgd = [[xintercept, pyzero]]; 5664 // this._areaPoints = [[xintercept, pyzero]]; 5665 } 5666 } 5667 if (pd[gd.length-1][1] < 0) { 5668 isnegative = true; 5669 opts.fillStyle = negativeColor; 5670 } 5671 else { 5672 isnegative = false; 5673 opts.fillStyle = posfs; 5674 } 5675 tempgd.push(gd[gd.length-1]); 5676 this._areaPoints.push(gd[gd.length-1]); 5677 tempgd.push([gd[gd.length-1][0], pyzero]); 5678 this._areaPoints.push([gd[gd.length-1][0], pyzero]); 5679 } 5680 // now draw the last area. 5681 if (shadow) { 5682 this.renderer.shadowRenderer.draw(ctx, tempgd, opts); 5683 } 5684 this.renderer.shapeRenderer.draw(ctx, tempgd, opts); 5685 5686 5687 // var gridymin = this._yaxis.series_u2p(0); 5688 // // IE doesn't return new length on unshift 5689 // gd.unshift([gd[0][0], gridymin]); 5690 // len = gd.length; 5691 // gd.push([gd[len - 1][0], gridymin]); 5692 } 5693 // if stacked, fill to line below 5694 else { 5695 var prev = this._prevGridData; 5696 for (var i=prev.length; i>0; i--) { 5697 gd.push(prev[i-1]); 5698 // this._areaPoints.push(prev[i-1]); 5699 } 5700 if (shadow) { 5701 this.renderer.shadowRenderer.draw(ctx, gd, opts); 5702 } 5703 this._areaPoints = gd; 5704 this.renderer.shapeRenderer.draw(ctx, gd, opts); 5705 } 5706 } 5707 ///////////////////////// 5708 // Not filled to zero 5709 //////////////////////// 5710 else { 5711 // if stoking line as well as filling, get a copy of line data. 5712 if (fillAndStroke) { 5713 var fasgd = gd.slice(0); 5714 } 5715 // if not stacked, fill down to axis 5716 if (this.index == 0 || !this._stack) { 5717 // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2; 5718 var gridymin = ctx.canvas.height; 5719 // IE doesn't return new length on unshift 5720 gd.unshift([gd[0][0], gridymin]); 5721 var len = gd.length; 5722 gd.push([gd[len - 1][0], gridymin]); 5723 } 5724 // if stacked, fill to line below 5725 else { 5726 var prev = this._prevGridData; 5727 for (var i=prev.length; i>0; i--) { 5728 gd.push(prev[i-1]); 5729 } 5730 } 5731 this._areaPoints = gd; 5732 5733 if (shadow) { 5734 this.renderer.shadowRenderer.draw(ctx, gd, opts); 5735 } 5736 5737 this.renderer.shapeRenderer.draw(ctx, gd, opts); 5738 } 5739 if (fillAndStroke) { 5740 var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false}); 5741 this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts); 5742 ////////// 5743 // TODO: figure out some way to do shadows nicely 5744 // if (shadow) { 5745 // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts); 5746 // } 5747 // now draw the markers 5748 if (this.markerRenderer.show) { 5749 if (this.renderer.smooth) { 5750 fasgd = this.gridData; 5751 } 5752 for (i=0; i<fasgd.length; i++) { 5753 this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions); 5754 } 5755 } 5756 } 5757 } 5758 else { 5759 5760 if (this.renderer.bands.show) { 5761 var bdat; 5762 var bopts = $.extend(true, {}, opts); 5763 if (this.renderer.bands.showLines) { 5764 bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData; 5765 this.renderer.shapeRenderer.draw(ctx, bdat, opts); 5766 bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData; 5767 this.renderer.shapeRenderer.draw(ctx, bdat, bopts); 5768 } 5769 5770 if (this.renderer.bands.fill) { 5771 if (this.renderer.smooth) { 5772 bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse()); 5773 } 5774 else { 5775 bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse()); 5776 } 5777 this._areaPoints = bdat; 5778 bopts.closePath = true; 5779 bopts.fill = true; 5780 bopts.fillStyle = this.renderer.bands.fillColor; 5781 this.renderer.shapeRenderer.draw(ctx, bdat, bopts); 5782 } 5783 } 5784 5785 if (shadow) { 5786 this.renderer.shadowRenderer.draw(ctx, gd, opts); 5787 } 5788 5789 this.renderer.shapeRenderer.draw(ctx, gd, opts); 5790 } 5791 } 5792 // calculate the bounding box 5793 var xmin = xmax = ymin = ymax = null; 5794 for (i=0; i<this._areaPoints.length; i++) { 5795 var p = this._areaPoints[i]; 5796 if (xmin > p[0] || xmin == null) { 5797 xmin = p[0]; 5798 } 5799 if (ymax < p[1] || ymax == null) { 5800 ymax = p[1]; 5801 } 5802 if (xmax < p[0] || xmax == null) { 5803 xmax = p[0]; 5804 } 5805 if (ymin > p[1] || ymin == null) { 5806 ymin = p[1]; 5807 } 5808 } 5809 5810 if (this.type === 'line' && this.renderer.bands.show) { 5811 ymax = this._yaxis.series_u2p(this.renderer.bands._min); 5812 ymin = this._yaxis.series_u2p(this.renderer.bands._max); 5813 } 5814 5815 this._boundingBox = [[xmin, ymax], [xmax, ymin]]; 5816 5817 // now draw the markers 5818 if (this.markerRenderer.show && !fill) { 5819 if (this.renderer.smooth) { 5820 gd = this.gridData; 5821 } 5822 for (i=0; i<gd.length; i++) { 5823 if (gd[i][0] != null && gd[i][1] != null) { 5824 this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions); 5825 } 5826 } 5827 } 5828 } 5829 5830 ctx.restore(); 5831 }; 5832 5833 $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) { 5834 // This is a no-op, shadows drawn with lines. 5835 }; 5836 5837 // called with scope of plot. 5838 // make sure to not leave anything highlighted. 5839 function postInit(target, data, options) { 5840 for (var i=0; i<this.series.length; i++) { 5841 if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) { 5842 // don't allow mouseover and mousedown at same time. 5843 if (this.series[i].highlightMouseOver) { 5844 this.series[i].highlightMouseDown = false; 5845 } 5846 } 5847 } 5848 } 5849 5850 // called within context of plot 5851 // create a canvas which we can draw on. 5852 // insert it before the eventCanvas, so eventCanvas will still capture events. 5853 function postPlotDraw() { 5854 // Memory Leaks patch 5855 if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) { 5856 this.plugins.lineRenderer.highlightCanvas.resetCanvas(); 5857 this.plugins.lineRenderer.highlightCanvas = null; 5858 } 5859 5860 this.plugins.lineRenderer.highlightedSeriesIndex = null; 5861 this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas(); 5862 5863 this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this)); 5864 this.plugins.lineRenderer.highlightCanvas.setContext(); 5865 this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); }); 5866 } 5867 5868 function highlight (plot, sidx, pidx, points) { 5869 var s = plot.series[sidx]; 5870 var canvas = plot.plugins.lineRenderer.highlightCanvas; 5871 canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height); 5872 s._highlightedPoint = pidx; 5873 plot.plugins.lineRenderer.highlightedSeriesIndex = sidx; 5874 var opts = {fillStyle: s.highlightColor}; 5875 if (s.type === 'line' && s.renderer.bands.show) { 5876 opts.fill = true; 5877 opts.closePath = true; 5878 } 5879 s.renderer.shapeRenderer.draw(canvas._ctx, points, opts); 5880 canvas = null; 5881 } 5882 5883 function unhighlight (plot) { 5884 var canvas = plot.plugins.lineRenderer.highlightCanvas; 5885 canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height); 5886 for (var i=0; i<plot.series.length; i++) { 5887 plot.series[i]._highlightedPoint = null; 5888 } 5889 plot.plugins.lineRenderer.highlightedSeriesIndex = null; 5890 plot.target.trigger('jqplotDataUnhighlight'); 5891 canvas = null; 5892 } 5893 5894 5895 function handleMove(ev, gridpos, datapos, neighbor, plot) { 5896 if (neighbor) { 5897 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 5898 var evt1 = jQuery.Event('jqplotDataMouseOver'); 5899 evt1.pageX = ev.pageX; 5900 evt1.pageY = ev.pageY; 5901 plot.target.trigger(evt1, ins); 5902 if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) { 5903 var evt = jQuery.Event('jqplotDataHighlight'); 5904 evt.which = ev.which; 5905 evt.pageX = ev.pageX; 5906 evt.pageY = ev.pageY; 5907 plot.target.trigger(evt, ins); 5908 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); 5909 } 5910 } 5911 else if (neighbor == null) { 5912 unhighlight (plot); 5913 } 5914 } 5915 5916 function handleMouseDown(ev, gridpos, datapos, neighbor, plot) { 5917 if (neighbor) { 5918 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 5919 if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) { 5920 var evt = jQuery.Event('jqplotDataHighlight'); 5921 evt.which = ev.which; 5922 evt.pageX = ev.pageX; 5923 evt.pageY = ev.pageY; 5924 plot.target.trigger(evt, ins); 5925 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); 5926 } 5927 } 5928 else if (neighbor == null) { 5929 unhighlight (plot); 5930 } 5931 } 5932 5933 function handleMouseUp(ev, gridpos, datapos, neighbor, plot) { 5934 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex; 5935 if (idx != null && plot.series[idx].highlightMouseDown) { 5936 unhighlight(plot); 5937 } 5938 } 5939 5940 function handleClick(ev, gridpos, datapos, neighbor, plot) { 5941 if (neighbor) { 5942 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 5943 var evt = jQuery.Event('jqplotDataClick'); 5944 evt.which = ev.which; 5945 evt.pageX = ev.pageX; 5946 evt.pageY = ev.pageY; 5947 plot.target.trigger(evt, ins); 5948 } 5949 } 5950 5951 function handleRightClick(ev, gridpos, datapos, neighbor, plot) { 5952 if (neighbor) { 5953 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 5954 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex; 5955 if (idx != null && plot.series[idx].highlightMouseDown) { 5956 unhighlight(plot); 5957 } 5958 var evt = jQuery.Event('jqplotDataRightClick'); 5959 evt.which = ev.which; 5960 evt.pageX = ev.pageX; 5961 evt.pageY = ev.pageY; 5962 plot.target.trigger(evt, ins); 5963 } 5964 } 5965 5966 5967 // class: $.jqplot.LinearAxisRenderer 5968 // The default jqPlot axis renderer, creating a numeric axis. 5969 $.jqplot.LinearAxisRenderer = function() { 5970 }; 5971 5972 // called with scope of axis object. 5973 $.jqplot.LinearAxisRenderer.prototype.init = function(options){ 5974 // prop: breakPoints 5975 // EXPERIMENTAL!! Use at your own risk! 5976 // Works only with linear axes and the default tick renderer. 5977 // Array of [start, stop] points to create a broken axis. 5978 // Broken axes have a "jump" in them, which is an immediate 5979 // transition from a smaller value to a larger value. 5980 // Currently, axis ticks MUST be manually assigned if using breakPoints 5981 // by using the axis ticks array option. 5982 this.breakPoints = null; 5983 // prop: breakTickLabel 5984 // Label to use at the axis break if breakPoints are specified. 5985 this.breakTickLabel = "≈"; 5986 // prop: drawBaseline 5987 // True to draw the axis baseline. 5988 this.drawBaseline = true; 5989 // prop: baselineWidth 5990 // width of the baseline in pixels. 5991 this.baselineWidth = null; 5992 // prop: baselineColor 5993 // CSS color spec for the baseline. 5994 this.baselineColor = null; 5995 // prop: forceTickAt0 5996 // This will ensure that there is always a tick mark at 0. 5997 // If data range is strictly positive or negative, 5998 // this will force 0 to be inside the axis bounds unless 5999 // the appropriate axis pad (pad, padMin or padMax) is set 6000 // to 0, then this will force an axis min or max value at 0. 6001 // This has know effect when any of the following options 6002 // are set: autoscale, min, max, numberTicks or tickInterval. 6003 this.forceTickAt0 = false; 6004 // prop: forceTickAt100 6005 // This will ensure that there is always a tick mark at 100. 6006 // If data range is strictly above or below 100, 6007 // this will force 100 to be inside the axis bounds unless 6008 // the appropriate axis pad (pad, padMin or padMax) is set 6009 // to 0, then this will force an axis min or max value at 100. 6010 // This has know effect when any of the following options 6011 // are set: autoscale, min, max, numberTicks or tickInterval. 6012 this.forceTickAt100 = false; 6013 // prop: tickInset 6014 // Controls the amount to inset the first and last ticks from 6015 // the edges of the grid, in multiples of the tick interval. 6016 // 0 is no inset, 0.5 is one half a tick interval, 1 is a full 6017 // tick interval, etc. 6018 this.tickInset = 0; 6019 // prop: minorTicks 6020 // Number of ticks to add between "major" ticks. 6021 // Major ticks are ticks supplied by user or auto computed. 6022 // Minor ticks cannot be created by user. 6023 this.minorTicks = 0; 6024 // prop: alignTicks 6025 // true to align tick marks across opposed axes 6026 // such as from the y2axis to yaxis. 6027 this.alignTicks = false; 6028 this._autoFormatString = ''; 6029 this._overrideFormatString = false; 6030 this._scalefact = 1.0; 6031 $.extend(true, this, options); 6032 if (this.breakPoints) { 6033 if (!$.isArray(this.breakPoints)) { 6034 this.breakPoints = null; 6035 } 6036 else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) { 6037 this.breakPoints = null; 6038 } 6039 } 6040 if (this.numberTicks != null && this.numberTicks < 2) { 6041 this.numberTicks = 2; 6042 } 6043 this.resetDataBounds(); 6044 }; 6045 6046 // called with scope of axis 6047 $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) { 6048 if (this.show) { 6049 // populate the axis label and value properties. 6050 // createTicks is a method on the renderer, but 6051 // call it within the scope of the axis. 6052 this.renderer.createTicks.call(this, plot); 6053 // fill a div with axes labels in the right direction. 6054 // Need to pregenerate each axis to get it's bounds and 6055 // position it and the labels correctly on the plot. 6056 var dim=0; 6057 var temp; 6058 // Added for theming. 6059 if (this._elem) { 6060 // Memory Leaks patch 6061 //this._elem.empty(); 6062 this._elem.emptyForce(); 6063 this._elem = null; 6064 } 6065 6066 this._elem = $(document.createElement('div')); 6067 this._elem.addClass('jqplot-axis jqplot-'+this.name); 6068 this._elem.css('position', 'absolute'); 6069 6070 6071 if (this.name == 'xaxis' || this.name == 'x2axis') { 6072 this._elem.width(this._plotDimensions.width); 6073 } 6074 else { 6075 this._elem.height(this._plotDimensions.height); 6076 } 6077 6078 // create a _label object. 6079 this.labelOptions.axis = this.name; 6080 this._label = new this.labelRenderer(this.labelOptions); 6081 if (this._label.show) { 6082 var elem = this._label.draw(ctx, plot); 6083 elem.appendTo(this._elem); 6084 elem = null; 6085 } 6086 6087 var t = this._ticks; 6088 var tick; 6089 for (var i=0; i<t.length; i++) { 6090 tick = t[i]; 6091 if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) { 6092 this._elem.append(tick.draw(ctx, plot)); 6093 } 6094 } 6095 tick = null; 6096 t = null; 6097 } 6098 return this._elem; 6099 }; 6100 6101 // called with scope of an axis 6102 $.jqplot.LinearAxisRenderer.prototype.reset = function() { 6103 this.min = this._options.min; 6104 this.max = this._options.max; 6105 this.tickInterval = this._options.tickInterval; 6106 this.numberTicks = this._options.numberTicks; 6107 this._autoFormatString = ''; 6108 if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) { 6109 this.tickOptions.formatString = ''; 6110 } 6111 6112 // this._ticks = this.__ticks; 6113 }; 6114 6115 // called with scope of axis 6116 $.jqplot.LinearAxisRenderer.prototype.set = function() { 6117 var dim = 0; 6118 var temp; 6119 var w = 0; 6120 var h = 0; 6121 var lshow = (this._label == null) ? false : this._label.show; 6122 if (this.show) { 6123 var t = this._ticks; 6124 var tick; 6125 for (var i=0; i<t.length; i++) { 6126 tick = t[i]; 6127 if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) { 6128 if (this.name == 'xaxis' || this.name == 'x2axis') { 6129 temp = tick._elem.outerHeight(true); 6130 } 6131 else { 6132 temp = tick._elem.outerWidth(true); 6133 } 6134 if (temp > dim) { 6135 dim = temp; 6136 } 6137 } 6138 } 6139 tick = null; 6140 t = null; 6141 6142 if (lshow) { 6143 w = this._label._elem.outerWidth(true); 6144 h = this._label._elem.outerHeight(true); 6145 } 6146 if (this.name == 'xaxis') { 6147 dim = dim + h; 6148 this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'}); 6149 } 6150 else if (this.name == 'x2axis') { 6151 dim = dim + h; 6152 this._elem.css({'height':dim+'px', left:'0px', top:'0px'}); 6153 } 6154 else if (this.name == 'yaxis') { 6155 dim = dim + w; 6156 this._elem.css({'width':dim+'px', left:'0px', top:'0px'}); 6157 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { 6158 this._label._elem.css('width', w+'px'); 6159 } 6160 } 6161 else { 6162 dim = dim + w; 6163 this._elem.css({'width':dim+'px', right:'0px', top:'0px'}); 6164 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { 6165 this._label._elem.css('width', w+'px'); 6166 } 6167 } 6168 } 6169 }; 6170 6171 // called with scope of axis 6172 $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) { 6173 // we're are operating on an axis here 6174 var ticks = this._ticks; 6175 var userTicks = this.ticks; 6176 var name = this.name; 6177 // databounds were set on axis initialization. 6178 var db = this._dataBounds; 6179 var dim = (this.name.charAt(0) === 'x') ? this._plotDimensions.width : this._plotDimensions.height; 6180 var interval; 6181 var min, max; 6182 var pos1, pos2; 6183 var tt, i; 6184 // get a copy of user's settings for min/max. 6185 var userMin = this.min; 6186 var userMax = this.max; 6187 var userNT = this.numberTicks; 6188 var userTI = this.tickInterval; 6189 6190 var threshold = 30; 6191 this._scalefact = (Math.max(dim, threshold+1) - threshold)/300.0; 6192 6193 // if we already have ticks, use them. 6194 // ticks must be in order of increasing value. 6195 6196 if (userTicks.length) { 6197 // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed 6198 for (i=0; i<userTicks.length; i++){ 6199 var ut = userTicks[i]; 6200 var t = new this.tickRenderer(this.tickOptions); 6201 if ($.isArray(ut)) { 6202 t.value = ut[0]; 6203 if (this.breakPoints) { 6204 if (ut[0] == this.breakPoints[0]) { 6205 t.label = this.breakTickLabel; 6206 t._breakTick = true; 6207 t.showGridline = false; 6208 t.showMark = false; 6209 } 6210 else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) { 6211 t.show = false; 6212 t.showGridline = false; 6213 t.label = ut[1]; 6214 } 6215 else { 6216 t.label = ut[1]; 6217 } 6218 } 6219 else { 6220 t.label = ut[1]; 6221 } 6222 t.setTick(ut[0], this.name); 6223 this._ticks.push(t); 6224 } 6225 6226 else if ($.isPlainObject(ut)) { 6227 $.extend(true, t, ut); 6228 t.axis = this.name; 6229 this._ticks.push(t); 6230 } 6231 6232 else { 6233 t.value = ut; 6234 if (this.breakPoints) { 6235 if (ut == this.breakPoints[0]) { 6236 t.label = this.breakTickLabel; 6237 t._breakTick = true; 6238 t.showGridline = false; 6239 t.showMark = false; 6240 } 6241 else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) { 6242 t.show = false; 6243 t.showGridline = false; 6244 } 6245 } 6246 t.setTick(ut, this.name); 6247 this._ticks.push(t); 6248 } 6249 } 6250 this.numberTicks = userTicks.length; 6251 this.min = this._ticks[0].value; 6252 this.max = this._ticks[this.numberTicks-1].value; 6253 this.tickInterval = (this.max - this.min) / (this.numberTicks - 1); 6254 } 6255 6256 // we don't have any ticks yet, let's make some! 6257 else { 6258 if (name == 'xaxis' || name == 'x2axis') { 6259 dim = this._plotDimensions.width; 6260 } 6261 else { 6262 dim = this._plotDimensions.height; 6263 } 6264 6265 var _numberTicks = this.numberTicks; 6266 6267 // if aligning this axis, use number of ticks from previous axis. 6268 // Do I need to reset somehow if alignTicks is changed and then graph is replotted?? 6269 if (this.alignTicks) { 6270 if (this.name === 'x2axis' && plot.axes.xaxis.show) { 6271 _numberTicks = plot.axes.xaxis.numberTicks; 6272 } 6273 else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) { 6274 _numberTicks = plot.axes.yaxis.numberTicks; 6275 } 6276 } 6277 6278 min = ((this.min != null) ? this.min : db.min); 6279 max = ((this.max != null) ? this.max : db.max); 6280 6281 var range = max - min; 6282 var rmin, rmax; 6283 var temp; 6284 6285 if (this.tickOptions == null || !this.tickOptions.formatString) { 6286 this._overrideFormatString = true; 6287 } 6288 6289 // Doing complete autoscaling 6290 if (this.min == null || this.max == null && this.tickInterval == null && !this.autoscale) { 6291 // Check if user must have tick at 0 or 100 and ensure they are in range. 6292 // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range. 6293 if (this.forceTickAt0) { 6294 if (min > 0) { 6295 min = 0; 6296 } 6297 if (max < 0) { 6298 max = 0; 6299 } 6300 } 6301 6302 if (this.forceTickAt100) { 6303 if (min > 100) { 6304 min = 100; 6305 } 6306 if (max < 100) { 6307 max = 100; 6308 } 6309 } 6310 6311 var keepMin = false, 6312 keepMax = false; 6313 6314 if (this.min != null) { 6315 keepMin = true; 6316 } 6317 6318 else if (this.max != null) { 6319 keepMax = true; 6320 } 6321 6322 // var threshold = 30; 6323 // var tdim = Math.max(dim, threshold+1); 6324 // this._scalefact = (tdim-threshold)/300.0; 6325 var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks, keepMin, keepMax); 6326 // calculate a padded max and min, points should be less than these 6327 // so that they aren't too close to the edges of the plot. 6328 // User can adjust how much padding is allowed with pad, padMin and PadMax options. 6329 // If min or max is set, don't pad that end of axis. 6330 var tumin = (this.min != null) ? min : min + range*(this.padMin - 1); 6331 var tumax = (this.max != null) ? max : max - range*(this.padMax - 1); 6332 6333 // if they're equal, we shouldn't have to do anything, right? 6334 // if (min <=tumin || max >= tumax) { 6335 if (min <tumin || max > tumax) { 6336 tumin = (this.min != null) ? min : min - range*(this.padMin - 1); 6337 tumax = (this.max != null) ? max : max + range*(this.padMax - 1); 6338 ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks, keepMin, keepMax); 6339 } 6340 6341 this.min = ret[0]; 6342 this.max = ret[1]; 6343 // if numberTicks specified, it should return the same. 6344 this.numberTicks = ret[2]; 6345 this._autoFormatString = ret[3]; 6346 this.tickInterval = ret[4]; 6347 } 6348 6349 // User has specified some axis scale related option, can use auto algorithm 6350 else { 6351 6352 // if min and max are same, space them out a bit 6353 if (min == max) { 6354 var adj = 0.05; 6355 if (min > 0) { 6356 adj = Math.max(Math.log(min)/Math.LN10, 0.05); 6357 } 6358 min -= adj; 6359 max += adj; 6360 } 6361 6362 // autoscale. Can't autoscale if min or max is supplied. 6363 // Will use numberTicks and tickInterval if supplied. Ticks 6364 // across multiple axes may not line up depending on how 6365 // bars are to be plotted. 6366 if (this.autoscale && this.min == null && this.max == null) { 6367 var rrange, ti, margin; 6368 var forceMinZero = false; 6369 var forceZeroLine = false; 6370 var intervals = {min:null, max:null, average:null, stddev:null}; 6371 // if any series are bars, or if any are fill to zero, and if this 6372 // is the axis to fill toward, check to see if we can start axis at zero. 6373 for (var i=0; i<this._series.length; i++) { 6374 var s = this._series[i]; 6375 var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name; 6376 // check to see if this is the fill axis 6377 if (this.name == faname) { 6378 var vals = s._plotValues[s.fillAxis]; 6379 var vmin = vals[0]; 6380 var vmax = vals[0]; 6381 for (var j=1; j<vals.length; j++) { 6382 if (vals[j] < vmin) { 6383 vmin = vals[j]; 6384 } 6385 else if (vals[j] > vmax) { 6386 vmax = vals[j]; 6387 } 6388 } 6389 var dp = (vmax - vmin) / vmax; 6390 // is this sries a bar? 6391 if (s.renderer.constructor == $.jqplot.BarRenderer) { 6392 // if no negative values and could also check range. 6393 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { 6394 forceMinZero = true; 6395 } 6396 else { 6397 forceMinZero = false; 6398 if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) { 6399 forceZeroLine = true; 6400 } 6401 else { 6402 forceZeroLine = false; 6403 } 6404 } 6405 } 6406 6407 // if not a bar and filling, use appropriate method. 6408 else if (s.fill) { 6409 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { 6410 forceMinZero = true; 6411 } 6412 else if (vmin < 0 && vmax > 0 && s.fillToZero) { 6413 forceMinZero = false; 6414 forceZeroLine = true; 6415 } 6416 else { 6417 forceMinZero = false; 6418 forceZeroLine = false; 6419 } 6420 } 6421 6422 // if not a bar and not filling, only change existing state 6423 // if it doesn't make sense 6424 else if (vmin < 0) { 6425 forceMinZero = false; 6426 } 6427 } 6428 } 6429 6430 // check if we need make axis min at 0. 6431 if (forceMinZero) { 6432 // compute number of ticks 6433 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); 6434 this.min = 0; 6435 userMin = 0; 6436 // what order is this range? 6437 // what tick interval does that give us? 6438 ti = max/(this.numberTicks-1); 6439 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); 6440 if (ti/temp == parseInt(ti/temp, 10)) { 6441 ti += temp; 6442 } 6443 this.tickInterval = Math.ceil(ti/temp) * temp; 6444 this.max = this.tickInterval * (this.numberTicks - 1); 6445 } 6446 6447 // check if we need to make sure there is a tick at 0. 6448 else if (forceZeroLine) { 6449 // compute number of ticks 6450 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); 6451 var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1)); 6452 var ntmax = this.numberTicks - 1 - ntmin; 6453 ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax)); 6454 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); 6455 this.tickInterval = Math.ceil(ti/temp) * temp; 6456 this.max = this.tickInterval * ntmax; 6457 this.min = -this.tickInterval * ntmin; 6458 } 6459 6460 // if nothing else, do autoscaling which will try to line up ticks across axes. 6461 else { 6462 if (this.numberTicks == null){ 6463 if (this.tickInterval) { 6464 this.numberTicks = 3 + Math.ceil(range / this.tickInterval); 6465 } 6466 else { 6467 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); 6468 } 6469 } 6470 6471 if (this.tickInterval == null) { 6472 // get a tick interval 6473 ti = range/(this.numberTicks - 1); 6474 6475 if (ti < 1) { 6476 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); 6477 } 6478 else { 6479 temp = 1; 6480 } 6481 this.tickInterval = Math.ceil(ti*temp*this.pad)/temp; 6482 } 6483 else { 6484 temp = 1 / this.tickInterval; 6485 } 6486 6487 // try to compute a nicer, more even tick interval 6488 // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10)); 6489 // this.tickInterval = Math.ceil(ti/temp) * temp; 6490 rrange = this.tickInterval * (this.numberTicks - 1); 6491 margin = (rrange - range)/2; 6492 6493 if (this.min == null) { 6494 this.min = Math.floor(temp*(min-margin))/temp; 6495 } 6496 if (this.max == null) { 6497 this.max = this.min + rrange; 6498 } 6499 } 6500 6501 // Compute a somewhat decent format string if it is needed. 6502 // get precision of interval and determine a format string. 6503 var sf = $.jqplot.getSignificantFigures(this.tickInterval); 6504 6505 var fstr; 6506 6507 // if we have only a whole number, use integer formatting 6508 if (sf.digitsLeft >= sf.significantDigits) { 6509 fstr = '%d'; 6510 } 6511 6512 else { 6513 var temp = Math.max(0, 5 - sf.digitsLeft); 6514 temp = Math.min(temp, sf.digitsRight); 6515 fstr = '%.'+ temp + 'f'; 6516 } 6517 6518 this._autoFormatString = fstr; 6519 } 6520 6521 // Use the default algorithm which pads each axis to make the chart 6522 // centered nicely on the grid. 6523 else { 6524 6525 rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1); 6526 rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1); 6527 range = rmax - rmin; 6528 6529 if (this.numberTicks == null){ 6530 // if tickInterval is specified by user, we will ignore computed maximum. 6531 // max will be equal or greater to fit even # of ticks. 6532 if (this.tickInterval != null) { 6533 this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1; 6534 } 6535 else if (dim > 100) { 6536 this.numberTicks = parseInt(3+(dim-100)/75, 10); 6537 } 6538 else { 6539 this.numberTicks = 2; 6540 } 6541 } 6542 6543 if (this.tickInterval == null) { 6544 this.tickInterval = range / (this.numberTicks-1); 6545 } 6546 6547 if (this.max == null) { 6548 rmax = rmin + this.tickInterval*(this.numberTicks - 1); 6549 } 6550 if (this.min == null) { 6551 rmin = rmax - this.tickInterval*(this.numberTicks - 1); 6552 } 6553 6554 // get precision of interval and determine a format string. 6555 var sf = $.jqplot.getSignificantFigures(this.tickInterval); 6556 6557 var fstr; 6558 6559 // if we have only a whole number, use integer formatting 6560 if (sf.digitsLeft >= sf.significantDigits) { 6561 fstr = '%d'; 6562 } 6563 6564 else { 6565 var temp = Math.max(0, 5 - sf.digitsLeft); 6566 temp = Math.min(temp, sf.digitsRight); 6567 fstr = '%.'+ temp + 'f'; 6568 } 6569 6570 6571 this._autoFormatString = fstr; 6572 6573 this.min = rmin; 6574 this.max = rmax; 6575 } 6576 6577 if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') { 6578 // fix for misleading tick display with small range and low precision. 6579 range = this.max - this.min; 6580 // figure out precision 6581 var temptick = new this.tickRenderer(this.tickOptions); 6582 // use the tick formatString or, the default. 6583 var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString; 6584 var fs = fs.match($.jqplot.sprintf.regex)[0]; 6585 var precision = 0; 6586 if (fs) { 6587 if (fs.search(/[fFeEgGpP]/) > -1) { 6588 var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/); 6589 if (m) { 6590 precision = parseInt(m[1], 10); 6591 } 6592 else { 6593 precision = 6; 6594 } 6595 } 6596 else if (fs.search(/[di]/) > -1) { 6597 precision = 0; 6598 } 6599 // fact will be <= 1; 6600 var fact = Math.pow(10, -precision); 6601 if (this.tickInterval < fact) { 6602 // need to correct underrange 6603 if (userNT == null && userTI == null) { 6604 this.tickInterval = fact; 6605 if (userMax == null && userMin == null) { 6606 // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact; 6607 this.min = Math.floor(this._dataBounds.min/fact) * fact; 6608 if (this.min == this._dataBounds.min) { 6609 this.min = this._dataBounds.min - this.tickInterval; 6610 } 6611 // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact; 6612 this.max = Math.ceil(this._dataBounds.max/fact) * fact; 6613 if (this.max == this._dataBounds.max) { 6614 this.max = this._dataBounds.max + this.tickInterval; 6615 } 6616 var n = (this.max - this.min)/this.tickInterval; 6617 n = n.toFixed(11); 6618 n = Math.ceil(n); 6619 this.numberTicks = n + 1; 6620 } 6621 else if (userMax == null) { 6622 // add one tick for top of range. 6623 var n = (this._dataBounds.max - this.min) / this.tickInterval; 6624 n = n.toFixed(11); 6625 this.numberTicks = Math.ceil(n) + 2; 6626 this.max = this.min + this.tickInterval * (this.numberTicks-1); 6627 } 6628 else if (userMin == null) { 6629 // add one tick for bottom of range. 6630 var n = (this.max - this._dataBounds.min) / this.tickInterval; 6631 n = n.toFixed(11); 6632 this.numberTicks = Math.ceil(n) + 2; 6633 this.min = this.max - this.tickInterval * (this.numberTicks-1); 6634 } 6635 else { 6636 // calculate a number of ticks so max is within axis scale 6637 this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1; 6638 // if user's min and max don't fit evenly in ticks, adjust. 6639 // This takes care of cases such as user min set to 0, max set to 3.5 but tick 6640 // format string set to %d (integer ticks) 6641 this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision); 6642 this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision); 6643 // this.max = this.min + this.tickInterval*(this.numberTicks-1); 6644 this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1; 6645 } 6646 } 6647 } 6648 } 6649 } 6650 6651 } 6652 6653 if (this._overrideFormatString && this._autoFormatString != '') { 6654 this.tickOptions = this.tickOptions || {}; 6655 this.tickOptions.formatString = this._autoFormatString; 6656 } 6657 6658 var t, to; 6659 for (var i=0; i<this.numberTicks; i++){ 6660 tt = this.min + i * this.tickInterval; 6661 t = new this.tickRenderer(this.tickOptions); 6662 // var t = new $.jqplot.AxisTickRenderer(this.tickOptions); 6663 6664 t.setTick(tt, this.name); 6665 this._ticks.push(t); 6666 6667 if (i < this.numberTicks - 1) { 6668 for (var j=0; j<this.minorTicks; j++) { 6669 tt += this.tickInterval/(this.minorTicks+1); 6670 to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true}); 6671 t = new this.tickRenderer(to); 6672 this._ticks.push(t); 6673 } 6674 } 6675 t = null; 6676 } 6677 } 6678 6679 if (this.tickInset) { 6680 this.min = this.min - this.tickInset * this.tickInterval; 6681 this.max = this.max + this.tickInset * this.tickInterval; 6682 } 6683 6684 ticks = null; 6685 }; 6686 6687 // Used to reset just the values of the ticks and then repack, which will 6688 // recalculate the positioning functions. It is assuemd that the 6689 // number of ticks is the same and the values of the new array are at the 6690 // proper interval. 6691 // This method needs to be called with the scope of an axis object, like: 6692 // 6693 // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr); 6694 // 6695 $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) { 6696 if ($.isArray(opts) && opts.length == this._ticks.length) { 6697 var t; 6698 for (var i=0; i<opts.length; i++) { 6699 t = this._ticks[i]; 6700 t.value = opts[i]; 6701 t.label = t.formatter(t.formatString, opts[i]); 6702 t.label = t.prefix + t.label; 6703 t._elem.html(t.label); 6704 } 6705 t = null; 6706 this.min = $.jqplot.arrayMin(opts); 6707 this.max = $.jqplot.arrayMax(opts); 6708 this.pack(); 6709 } 6710 // Not implemented yet. 6711 // else if ($.isPlainObject(opts)) { 6712 // 6713 // } 6714 }; 6715 6716 // called with scope of axis 6717 $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) { 6718 // Add defaults for repacking from resetTickValues function. 6719 pos = pos || {}; 6720 offsets = offsets || this._offsets; 6721 6722 var ticks = this._ticks; 6723 var max = this.max; 6724 var min = this.min; 6725 var offmax = offsets.max; 6726 var offmin = offsets.min; 6727 var lshow = (this._label == null) ? false : this._label.show; 6728 6729 for (var p in pos) { 6730 this._elem.css(p, pos[p]); 6731 } 6732 6733 this._offsets = offsets; 6734 // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left. 6735 var pixellength = offmax - offmin; 6736 var unitlength = max - min; 6737 6738 // point to unit and unit to point conversions references to Plot DOM element top left corner. 6739 if (this.breakPoints) { 6740 unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0]; 6741 6742 this.p2u = function(p){ 6743 return (p - offmin) * unitlength / pixellength + min; 6744 }; 6745 6746 this.u2p = function(u){ 6747 if (u > this.breakPoints[0] && u < this.breakPoints[1]){ 6748 u = this.breakPoints[0]; 6749 } 6750 if (u <= this.breakPoints[0]) { 6751 return (u - min) * pixellength / unitlength + offmin; 6752 } 6753 else { 6754 return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin; 6755 } 6756 }; 6757 6758 if (this.name.charAt(0) == 'x'){ 6759 this.series_u2p = function(u){ 6760 if (u > this.breakPoints[0] && u < this.breakPoints[1]){ 6761 u = this.breakPoints[0]; 6762 } 6763 if (u <= this.breakPoints[0]) { 6764 return (u - min) * pixellength / unitlength; 6765 } 6766 else { 6767 return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength; 6768 } 6769 }; 6770 this.series_p2u = function(p){ 6771 return p * unitlength / pixellength + min; 6772 }; 6773 } 6774 6775 else { 6776 this.series_u2p = function(u){ 6777 if (u > this.breakPoints[0] && u < this.breakPoints[1]){ 6778 u = this.breakPoints[0]; 6779 } 6780 if (u >= this.breakPoints[1]) { 6781 return (u - max) * pixellength / unitlength; 6782 } 6783 else { 6784 return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength; 6785 } 6786 }; 6787 this.series_p2u = function(p){ 6788 return p * unitlength / pixellength + max; 6789 }; 6790 } 6791 } 6792 else { 6793 this.p2u = function(p){ 6794 return (p - offmin) * unitlength / pixellength + min; 6795 }; 6796 6797 this.u2p = function(u){ 6798 return (u - min) * pixellength / unitlength + offmin; 6799 }; 6800 6801 if (this.name == 'xaxis' || this.name == 'x2axis'){ 6802 this.series_u2p = function(u){ 6803 return (u - min) * pixellength / unitlength; 6804 }; 6805 this.series_p2u = function(p){ 6806 return p * unitlength / pixellength + min; 6807 }; 6808 } 6809 6810 else { 6811 this.series_u2p = function(u){ 6812 return (u - max) * pixellength / unitlength; 6813 }; 6814 this.series_p2u = function(p){ 6815 return p * unitlength / pixellength + max; 6816 }; 6817 } 6818 } 6819 6820 if (this.show) { 6821 if (this.name == 'xaxis' || this.name == 'x2axis') { 6822 for (var i=0; i<ticks.length; i++) { 6823 var t = ticks[i]; 6824 if (t.show && t.showLabel) { 6825 var shim; 6826 6827 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) { 6828 // will need to adjust auto positioning based on which axis this is. 6829 var temp = (this.name == 'xaxis') ? 1 : -1; 6830 switch (t.labelPosition) { 6831 case 'auto': 6832 // position at end 6833 if (temp * t.angle < 0) { 6834 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 6835 } 6836 // position at start 6837 else { 6838 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 6839 } 6840 break; 6841 case 'end': 6842 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 6843 break; 6844 case 'start': 6845 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 6846 break; 6847 case 'middle': 6848 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 6849 break; 6850 default: 6851 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 6852 break; 6853 } 6854 } 6855 else { 6856 shim = -t.getWidth()/2; 6857 } 6858 var val = this.u2p(t.value) + shim + 'px'; 6859 t._elem.css('left', val); 6860 t.pack(); 6861 } 6862 } 6863 if (lshow) { 6864 var w = this._label._elem.outerWidth(true); 6865 this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px'); 6866 if (this.name == 'xaxis') { 6867 this._label._elem.css('bottom', '0px'); 6868 } 6869 else { 6870 this._label._elem.css('top', '0px'); 6871 } 6872 this._label.pack(); 6873 } 6874 } 6875 else { 6876 for (var i=0; i<ticks.length; i++) { 6877 var t = ticks[i]; 6878 if (t.show && t.showLabel) { 6879 var shim; 6880 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) { 6881 // will need to adjust auto positioning based on which axis this is. 6882 var temp = (this.name == 'yaxis') ? 1 : -1; 6883 switch (t.labelPosition) { 6884 case 'auto': 6885 // position at end 6886 case 'end': 6887 if (temp * t.angle < 0) { 6888 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; 6889 } 6890 else { 6891 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; 6892 } 6893 break; 6894 case 'start': 6895 if (t.angle > 0) { 6896 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; 6897 } 6898 else { 6899 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; 6900 } 6901 break; 6902 case 'middle': 6903 // if (t.angle > 0) { 6904 // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 6905 // } 6906 // else { 6907 // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 6908 // } 6909 shim = -t.getHeight()/2; 6910 break; 6911 default: 6912 shim = -t.getHeight()/2; 6913 break; 6914 } 6915 } 6916 else { 6917 shim = -t.getHeight()/2; 6918 } 6919 6920 var val = this.u2p(t.value) + shim + 'px'; 6921 t._elem.css('top', val); 6922 t.pack(); 6923 } 6924 } 6925 if (lshow) { 6926 var h = this._label._elem.outerHeight(true); 6927 this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px'); 6928 if (this.name == 'yaxis') { 6929 this._label._elem.css('left', '0px'); 6930 } 6931 else { 6932 this._label._elem.css('right', '0px'); 6933 } 6934 this._label.pack(); 6935 } 6936 } 6937 } 6938 6939 ticks = null; 6940 }; 6941 6942 6943 /** 6944 * The following code was generaously given to me a while back by Scott Prahl. 6945 * He did a good job at computing axes min, max and number of ticks for the 6946 * case where the user has not set any scale related parameters (tickInterval, 6947 * numberTicks, min or max). I had ignored this use case for a long time, 6948 * focusing on the more difficult case where user has set some option controlling 6949 * tick generation. Anyway, about time I got this into jqPlot. 6950 * Thanks Scott!! 6951 */ 6952 6953 /** 6954 * Copyright (c) 2010 Scott Prahl 6955 * The next three routines are currently available for use in all personal 6956 * or commercial projects under both the MIT and GPL version 2.0 licenses. 6957 * This means that you can choose the license that best suits your project 6958 * and use it accordingly. 6959 */ 6960 6961 // A good format string depends on the interval. If the interval is greater 6962 // than 1 then there is no need to show any decimal digits. If it is < 1.0, then 6963 // use the magnitude of the interval to determine the number of digits to show. 6964 function bestFormatString (interval) 6965 { 6966 var fstr; 6967 interval = Math.abs(interval); 6968 if (interval >= 10) { 6969 fstr = '%d'; 6970 } 6971 6972 else if (interval > 1) { 6973 if (interval === parseInt(interval, 10)) { 6974 fstr = '%d'; 6975 } 6976 else { 6977 fstr = '%.1f'; 6978 } 6979 } 6980 6981 else { 6982 var expv = -Math.floor(Math.log(interval)/Math.LN10); 6983 fstr = '%.' + expv + 'f'; 6984 } 6985 6986 return fstr; 6987 } 6988 6989 var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5]; 6990 6991 var _getLowerFactor = function(f) { 6992 var i = _factors.indexOf(f); 6993 if (i > 0) { 6994 return _factors[i-1]; 6995 } 6996 else { 6997 return _factors[_factors.length - 1] / 100; 6998 } 6999 }; 7000 7001 var _getHigherFactor = function(f) { 7002 var i = _factors.indexOf(f); 7003 if (i < _factors.length-1) { 7004 return _factors[i+1]; 7005 } 7006 else { 7007 return _factors[0] * 100; 7008 } 7009 }; 7010 7011 // Given a fixed minimum and maximum and a target number ot ticks 7012 // figure out the best interval and 7013 // return min, max, number ticks, format string and tick interval 7014 function bestConstrainedInterval(min, max, nttarget) { 7015 // run through possible number to ticks and see which interval is best 7016 var low = Math.floor(nttarget/2); 7017 var hi = Math.ceil(nttarget*1.5); 7018 var badness = Number.MAX_VALUE; 7019 var r = (max - min); 7020 var temp; 7021 var sd; 7022 var bestNT; 7023 var gsf = $.jqplot.getSignificantFigures; 7024 var fsd; 7025 var fs; 7026 var currentNT; 7027 var bestPrec; 7028 7029 for (var i=0, l=hi-low+1; i<l; i++) { 7030 currentNT = low + i; 7031 temp = r/(currentNT-1); 7032 sd = gsf(temp); 7033 7034 temp = Math.abs(nttarget - currentNT) + sd.digitsRight; 7035 if (temp < badness) { 7036 badness = temp; 7037 bestNT = currentNT; 7038 bestPrec = sd.digitsRight; 7039 } 7040 else if (temp === badness) { 7041 // let nicer ticks trump number ot ticks 7042 if (sd.digitsRight < bestPrec) { 7043 bestNT = currentNT; 7044 bestPrec = sd.digitsRight; 7045 } 7046 } 7047 7048 } 7049 7050 fsd = Math.max(bestPrec, Math.max(gsf(min).digitsRight, gsf(max).digitsRight)); 7051 if (fsd === 0) { 7052 fs = '%d'; 7053 } 7054 else { 7055 fs = '%.' + fsd + 'f'; 7056 } 7057 temp = r / (bestNT - 1); 7058 // min, max, number ticks, format string, tick interval 7059 return [min, max, bestNT, fs, temp]; 7060 } 7061 7062 // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n 7063 // it is based soley on the range and number of ticks. So if user specifies 7064 // number of ticks, use this. 7065 function bestInterval(range, numberTicks) { 7066 numberTicks = numberTicks || 7; 7067 var minimum = range / (numberTicks - 1); 7068 var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10)); 7069 var residual = minimum / magnitude; 7070 var interval; 7071 // "nicest" ranges are 1, 2, 5 or powers of these. 7072 // for magnitudes below 1, only allow these. 7073 if (magnitude < 1) { 7074 if (residual > 5) { 7075 interval = 10 * magnitude; 7076 } 7077 else if (residual > 2) { 7078 interval = 5 * magnitude; 7079 } 7080 else if (residual > 1) { 7081 interval = 2 * magnitude; 7082 } 7083 else { 7084 interval = magnitude; 7085 } 7086 } 7087 // for large ranges (whole integers), allow intervals like 3, 4 or powers of these. 7088 // this helps a lot with poor choices for number of ticks. 7089 else { 7090 if (residual > 5) { 7091 interval = 10 * magnitude; 7092 } 7093 else if (residual > 4) { 7094 interval = 5 * magnitude; 7095 } 7096 else if (residual > 3) { 7097 interval = 4 * magnitude; 7098 } 7099 else if (residual > 2) { 7100 interval = 3 * magnitude; 7101 } 7102 else if (residual > 1) { 7103 interval = 2 * magnitude; 7104 } 7105 else { 7106 interval = magnitude; 7107 } 7108 } 7109 7110 return interval; 7111 } 7112 7113 // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n 7114 // it is based soley on the range of data, number of ticks must be computed later. 7115 function bestLinearInterval(range, scalefact) { 7116 scalefact = scalefact || 1; 7117 var expv = Math.floor(Math.log(range)/Math.LN10); 7118 var magnitude = Math.pow(10, expv); 7119 // 0 < f < 10 7120 var f = range / magnitude; 7121 var fact; 7122 // for large plots, scalefact will decrease f and increase number of ticks. 7123 // for small plots, scalefact will increase f and decrease number of ticks. 7124 f = f/scalefact; 7125 7126 // for large plots, smaller interval, more ticks. 7127 if (f<=0.38) { 7128 fact = 0.1; 7129 } 7130 else if (f<=1.6) { 7131 fact = 0.2; 7132 } 7133 else if (f<=4.0) { 7134 fact = 0.5; 7135 } 7136 else if (f<=8.0) { 7137 fact = 1.0; 7138 } 7139 // for very small plots, larger interval, less ticks in number ticks 7140 else if (f<=16.0) { 7141 fact = 2; 7142 } 7143 else { 7144 fact = 5; 7145 } 7146 7147 return fact*magnitude; 7148 } 7149 7150 function bestLinearComponents(range, scalefact) { 7151 var expv = Math.floor(Math.log(range)/Math.LN10); 7152 var magnitude = Math.pow(10, expv); 7153 // 0 < f < 10 7154 var f = range / magnitude; 7155 var interval; 7156 var fact; 7157 // for large plots, scalefact will decrease f and increase number of ticks. 7158 // for small plots, scalefact will increase f and decrease number of ticks. 7159 f = f/scalefact; 7160 7161 // for large plots, smaller interval, more ticks. 7162 if (f<=0.38) { 7163 fact = 0.1; 7164 } 7165 else if (f<=1.6) { 7166 fact = 0.2; 7167 } 7168 else if (f<=4.0) { 7169 fact = 0.5; 7170 } 7171 else if (f<=8.0) { 7172 fact = 1.0; 7173 } 7174 // for very small plots, larger interval, less ticks in number ticks 7175 else if (f<=16.0) { 7176 fact = 2; 7177 } 7178 // else if (f<=20.0) { 7179 // fact = 3; 7180 // } 7181 // else if (f<=24.0) { 7182 // fact = 4; 7183 // } 7184 else { 7185 fact = 5; 7186 } 7187 7188 interval = fact * magnitude; 7189 7190 return [interval, fact, magnitude]; 7191 } 7192 7193 // Given the min and max for a dataset, return suitable endpoints 7194 // for the graphing, a good number for the number of ticks, and a 7195 // format string so that extraneous digits are not displayed. 7196 // returned is an array containing [min, max, nTicks, format] 7197 $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks, keepMin, keepMax) { 7198 // Set to preserve EITHER min OR max. 7199 // If min is preserved, max must be free. 7200 keepMin = (keepMin === null) ? false : keepMin; 7201 keepMax = (keepMax === null || keepMin) ? false : keepMax; 7202 // if endpoints are equal try to include zero otherwise include one 7203 if (axis_min === axis_max) { 7204 axis_max = (axis_max) ? 0 : 1; 7205 } 7206 7207 scalefact = scalefact || 1.0; 7208 7209 // make sure range is positive 7210 if (axis_max < axis_min) { 7211 var a = axis_max; 7212 axis_max = axis_min; 7213 axis_min = a; 7214 } 7215 7216 var r = []; 7217 var ss = bestLinearInterval(axis_max - axis_min, scalefact); 7218 7219 var gsf = $.jqplot.getSignificantFigures; 7220 7221 if (numberTicks == null) { 7222 7223 // Figure out the axis min, max and number of ticks 7224 // the min and max will be some multiple of the tick interval, 7225 // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the 7226 // axis min is negative, 0 will be a tick. 7227 if (!keepMin && !keepMax) { 7228 r[0] = Math.floor(axis_min / ss) * ss; // min 7229 r[1] = Math.ceil(axis_max / ss) * ss; // max 7230 r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks 7231 r[3] = bestFormatString(ss); // format string 7232 r[4] = ss; // tick Interval 7233 } 7234 7235 else if (keepMin) { 7236 r[0] = axis_min; // min 7237 r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks 7238 r[1] = axis_min + (r[2] - 1) * ss; // max 7239 var digitsMin = gsf(axis_min).digitsRight; 7240 var digitsSS = gsf(ss).digitsRight; 7241 if (digitsMin < digitsSS) { 7242 r[3] = bestFormatString(ss); // format string 7243 } 7244 else { 7245 r[3] = '%.' + digitsMin + 'f'; 7246 } 7247 r[4] = ss; // tick Interval 7248 } 7249 7250 else if (keepMax) { 7251 r[1] = axis_max; // max 7252 r[2] = Math.ceil((axis_max - axis_min) / ss + 1.0); // number of ticks 7253 r[0] = axis_max - (r[2] - 1) * ss; // min 7254 var digitsMax = gsf(axis_max).digitsRight; 7255 var digitsSS = gsf(ss).digitsRight; 7256 if (digitsMax < digitsSS) { 7257 r[3] = bestFormatString(ss); // format string 7258 } 7259 else { 7260 r[3] = '%.' + digitsMax + 'f'; 7261 } 7262 r[4] = ss; // tick Interval 7263 } 7264 } 7265 7266 else { 7267 var tempr = []; 7268 7269 // Figure out the axis min, max and number of ticks 7270 // the min and max will be some multiple of the tick interval, 7271 // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the 7272 // axis min is negative, 0 will be a tick. 7273 tempr[0] = Math.floor(axis_min / ss) * ss; // min 7274 tempr[1] = Math.ceil(axis_max / ss) * ss; // max 7275 tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks 7276 tempr[3] = bestFormatString(ss); // format string 7277 tempr[4] = ss; // tick Interval 7278 7279 // first, see if we happen to get the right number of ticks 7280 if (tempr[2] === numberTicks) { 7281 r = tempr; 7282 } 7283 7284 else { 7285 7286 var newti = bestInterval(tempr[1] - tempr[0], numberTicks); 7287 7288 r[0] = tempr[0]; // min 7289 r[2] = numberTicks; // number of ticks 7290 r[4] = newti; // tick interval 7291 r[3] = bestFormatString(newti); // format string 7292 r[1] = r[0] + (r[2] - 1) * r[4]; // max 7293 } 7294 } 7295 7296 return r; 7297 }; 7298 7299 $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval; 7300 $.jqplot.LinearTickGenerator.bestInterval = bestInterval; 7301 $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents; 7302 $.jqplot.LinearTickGenerator.bestConstrainedInterval = bestConstrainedInterval; 7303 7304 7305 // class: $.jqplot.MarkerRenderer 7306 // The default jqPlot marker renderer, rendering the points on the line. 7307 $.jqplot.MarkerRenderer = function(options){ 7308 // Group: Properties 7309 7310 // prop: show 7311 // wether or not to show the marker. 7312 this.show = true; 7313 // prop: style 7314 // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare 7315 this.style = 'filledCircle'; 7316 // prop: lineWidth 7317 // size of the line for non-filled markers. 7318 this.lineWidth = 2; 7319 // prop: size 7320 // Size of the marker (diameter or circle, length of edge of square, etc.) 7321 this.size = 9.0; 7322 // prop: color 7323 // color of marker. Will be set to color of series by default on init. 7324 this.color = '#666666'; 7325 // prop: shadow 7326 // wether or not to draw a shadow on the line 7327 this.shadow = true; 7328 // prop: shadowAngle 7329 // Shadow angle in degrees 7330 this.shadowAngle = 45; 7331 // prop: shadowOffset 7332 // Shadow offset from line in pixels 7333 this.shadowOffset = 1; 7334 // prop: shadowDepth 7335 // Number of times shadow is stroked, each stroke offset shadowOffset from the last. 7336 this.shadowDepth = 3; 7337 // prop: shadowAlpha 7338 // Alpha channel transparency of shadow. 0 = transparent. 7339 this.shadowAlpha = '0.07'; 7340 // prop: shadowRenderer 7341 // Renderer that will draws the shadows on the marker. 7342 this.shadowRenderer = new $.jqplot.ShadowRenderer(); 7343 // prop: shapeRenderer 7344 // Renderer that will draw the marker. 7345 this.shapeRenderer = new $.jqplot.ShapeRenderer(); 7346 7347 $.extend(true, this, options); 7348 }; 7349 7350 $.jqplot.MarkerRenderer.prototype.init = function(options) { 7351 $.extend(true, this, options); 7352 var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true}; 7353 if (this.style.indexOf('filled') != -1) { 7354 sdopt.fill = true; 7355 } 7356 if (this.style.indexOf('ircle') != -1) { 7357 sdopt.isarc = true; 7358 sdopt.closePath = false; 7359 } 7360 this.shadowRenderer.init(sdopt); 7361 7362 var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true}; 7363 if (this.style.indexOf('filled') != -1) { 7364 shopt.fill = true; 7365 } 7366 if (this.style.indexOf('ircle') != -1) { 7367 shopt.isarc = true; 7368 shopt.closePath = false; 7369 } 7370 this.shapeRenderer.init(shopt); 7371 }; 7372 7373 $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) { 7374 var stretch = 1.2; 7375 var dx = this.size/2/stretch; 7376 var dy = this.size/2*stretch; 7377 var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]]; 7378 if (this.shadow) { 7379 this.shadowRenderer.draw(ctx, points); 7380 } 7381 this.shapeRenderer.draw(ctx, points, options); 7382 }; 7383 7384 $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) { 7385 var stretch = 1.0; 7386 var dx = this.size/2*stretch; 7387 var dy = this.size/2*stretch; 7388 var points1 = [[x, y-dy], [x, y+dy]]; 7389 var points2 = [[x+dx, y], [x-dx, y]]; 7390 var opts = $.extend(true, {}, this.options, {closePath:false}); 7391 if (this.shadow) { 7392 this.shadowRenderer.draw(ctx, points1, {closePath:false}); 7393 this.shadowRenderer.draw(ctx, points2, {closePath:false}); 7394 } 7395 this.shapeRenderer.draw(ctx, points1, opts); 7396 this.shapeRenderer.draw(ctx, points2, opts); 7397 }; 7398 7399 $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) { 7400 var stretch = 1.0; 7401 var dx = this.size/2*stretch; 7402 var dy = this.size/2*stretch; 7403 var opts = $.extend(true, {}, this.options, {closePath:false}); 7404 var points1 = [[x-dx, y-dy], [x+dx, y+dy]]; 7405 var points2 = [[x-dx, y+dy], [x+dx, y-dy]]; 7406 if (this.shadow) { 7407 this.shadowRenderer.draw(ctx, points1, {closePath:false}); 7408 this.shadowRenderer.draw(ctx, points2, {closePath:false}); 7409 } 7410 this.shapeRenderer.draw(ctx, points1, opts); 7411 this.shapeRenderer.draw(ctx, points2, opts); 7412 }; 7413 7414 $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) { 7415 var stretch = 1.0; 7416 var dx = this.size/2*stretch; 7417 var dy = this.size/2*stretch; 7418 var points = [[x-dx, y], [x+dx, y]]; 7419 if (this.shadow) { 7420 this.shadowRenderer.draw(ctx, points); 7421 } 7422 this.shapeRenderer.draw(ctx, points, options); 7423 }; 7424 7425 $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) { 7426 var points = [p1, p2]; 7427 if (this.shadow) { 7428 this.shadowRenderer.draw(ctx, points); 7429 } 7430 this.shapeRenderer.draw(ctx, points, options); 7431 }; 7432 7433 $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) { 7434 var stretch = 1.0; 7435 var dx = this.size/2/stretch; 7436 var dy = this.size/2*stretch; 7437 var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]]; 7438 if (this.shadow) { 7439 this.shadowRenderer.draw(ctx, points); 7440 } 7441 this.shapeRenderer.draw(ctx, points, options); 7442 }; 7443 7444 $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) { 7445 var radius = this.size/2; 7446 var end = 2*Math.PI; 7447 var points = [x, y, radius, 0, end, true]; 7448 if (this.shadow) { 7449 this.shadowRenderer.draw(ctx, points); 7450 } 7451 this.shapeRenderer.draw(ctx, points, options); 7452 }; 7453 7454 $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) { 7455 options = options || {}; 7456 // hack here b/c shape renderer uses canvas based color style options 7457 // and marker uses css style names. 7458 if (options.show == null || options.show != false) { 7459 if (options.color && !options.fillStyle) { 7460 options.fillStyle = options.color; 7461 } 7462 if (options.color && !options.strokeStyle) { 7463 options.strokeStyle = options.color; 7464 } 7465 switch (this.style) { 7466 case 'diamond': 7467 this.drawDiamond(x,y,ctx, false, options); 7468 break; 7469 case 'filledDiamond': 7470 this.drawDiamond(x,y,ctx, true, options); 7471 break; 7472 case 'circle': 7473 this.drawCircle(x,y,ctx, false, options); 7474 break; 7475 case 'filledCircle': 7476 this.drawCircle(x,y,ctx, true, options); 7477 break; 7478 case 'square': 7479 this.drawSquare(x,y,ctx, false, options); 7480 break; 7481 case 'filledSquare': 7482 this.drawSquare(x,y,ctx, true, options); 7483 break; 7484 case 'x': 7485 this.drawX(x,y,ctx, true, options); 7486 break; 7487 case 'plus': 7488 this.drawPlus(x,y,ctx, true, options); 7489 break; 7490 case 'dash': 7491 this.drawDash(x,y,ctx, true, options); 7492 break; 7493 case 'line': 7494 this.drawLine(x, y, ctx, false, options); 7495 break; 7496 default: 7497 this.drawDiamond(x,y,ctx, false, options); 7498 break; 7499 } 7500 } 7501 }; 7502 7503 // class: $.jqplot.shadowRenderer 7504 // The default jqPlot shadow renderer, rendering shadows behind shapes. 7505 $.jqplot.ShadowRenderer = function(options){ 7506 // Group: Properties 7507 7508 // prop: angle 7509 // Angle of the shadow in degrees. Measured counter-clockwise from the x axis. 7510 this.angle = 45; 7511 // prop: offset 7512 // Pixel offset at the given shadow angle of each shadow stroke from the last stroke. 7513 this.offset = 1; 7514 // prop: alpha 7515 // alpha transparency of shadow stroke. 7516 this.alpha = 0.07; 7517 // prop: lineWidth 7518 // width of the shadow line stroke. 7519 this.lineWidth = 1.5; 7520 // prop: lineJoin 7521 // How line segments of the shadow are joined. 7522 this.lineJoin = 'miter'; 7523 // prop: lineCap 7524 // how ends of the shadow line are rendered. 7525 this.lineCap = 'round'; 7526 // prop; closePath 7527 // whether line path segment is closed upon itself. 7528 this.closePath = false; 7529 // prop: fill 7530 // whether to fill the shape. 7531 this.fill = false; 7532 // prop: depth 7533 // how many times the shadow is stroked. Each stroke will be offset by offset at angle degrees. 7534 this.depth = 3; 7535 this.strokeStyle = 'rgba(0,0,0,0.1)'; 7536 // prop: isarc 7537 // wether the shadow is an arc or not. 7538 this.isarc = false; 7539 7540 $.extend(true, this, options); 7541 }; 7542 7543 $.jqplot.ShadowRenderer.prototype.init = function(options) { 7544 $.extend(true, this, options); 7545 }; 7546 7547 // function: draw 7548 // draws an transparent black (i.e. gray) shadow. 7549 // 7550 // ctx - canvas drawing context 7551 // points - array of points or [x, y, radius, start angle (rad), end angle (rad)] 7552 $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) { 7553 ctx.save(); 7554 var opts = (options != null) ? options : {}; 7555 var fill = (opts.fill != null) ? opts.fill : this.fill; 7556 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect; 7557 var closePath = (opts.closePath != null) ? opts.closePath : this.closePath; 7558 var offset = (opts.offset != null) ? opts.offset : this.offset; 7559 var alpha = (opts.alpha != null) ? opts.alpha : this.alpha; 7560 var depth = (opts.depth != null) ? opts.depth : this.depth; 7561 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc; 7562 var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern; 7563 ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth; 7564 ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin; 7565 ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap; 7566 ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')'; 7567 ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')'; 7568 for (var j=0; j<depth; j++) { 7569 var ctxPattern = $.jqplot.LinePattern(ctx, linePattern); 7570 ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset); 7571 ctxPattern.beginPath(); 7572 if (isarc) { 7573 ctx.arc(points[0], points[1], points[2], points[3], points[4], true); 7574 } 7575 else if (fillRect) { 7576 if (fillRect) { 7577 ctx.fillRect(points[0], points[1], points[2], points[3]); 7578 } 7579 } 7580 else if (points && points.length){ 7581 var move = true; 7582 for (var i=0; i<points.length; i++) { 7583 // skip to the first non-null point and move to it. 7584 if (points[i][0] != null && points[i][1] != null) { 7585 if (move) { 7586 ctxPattern.moveTo(points[i][0], points[i][1]); 7587 move = false; 7588 } 7589 else { 7590 ctxPattern.lineTo(points[i][0], points[i][1]); 7591 } 7592 } 7593 else { 7594 move = true; 7595 } 7596 } 7597 7598 } 7599 if (closePath) { 7600 ctxPattern.closePath(); 7601 } 7602 if (fill) { 7603 ctx.fill(); 7604 } 7605 else { 7606 ctx.stroke(); 7607 } 7608 } 7609 ctx.restore(); 7610 }; 7611 7612 // class: $.jqplot.shapeRenderer 7613 // The default jqPlot shape renderer. Given a set of points will 7614 // plot them and either stroke a line (fill = false) or fill them (fill = true). 7615 // If a filled shape is desired, closePath = true must also be set to close 7616 // the shape. 7617 $.jqplot.ShapeRenderer = function(options){ 7618 7619 this.lineWidth = 1.5; 7620 // prop: linePattern 7621 // line pattern 'dashed', 'dotted', 'solid', some combination 7622 // of '-' and '.' characters such as '.-.' or a numerical array like 7623 // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, 7624 // [1, 10, 20, 10] to draw a dot-dash line, and so on. 7625 this.linePattern = 'solid'; 7626 // prop: lineJoin 7627 // How line segments of the shadow are joined. 7628 this.lineJoin = 'miter'; 7629 // prop: lineCap 7630 // how ends of the shadow line are rendered. 7631 this.lineCap = 'round'; 7632 // prop; closePath 7633 // whether line path segment is closed upon itself. 7634 this.closePath = false; 7635 // prop: fill 7636 // whether to fill the shape. 7637 this.fill = false; 7638 // prop: isarc 7639 // wether the shadow is an arc or not. 7640 this.isarc = false; 7641 // prop: fillRect 7642 // true to draw shape as a filled rectangle. 7643 this.fillRect = false; 7644 // prop: strokeRect 7645 // true to draw shape as a stroked rectangle. 7646 this.strokeRect = false; 7647 // prop: clearRect 7648 // true to cear a rectangle. 7649 this.clearRect = false; 7650 // prop: strokeStyle 7651 // css color spec for the stoke style 7652 this.strokeStyle = '#999999'; 7653 // prop: fillStyle 7654 // css color spec for the fill style. 7655 this.fillStyle = '#999999'; 7656 7657 $.extend(true, this, options); 7658 }; 7659 7660 $.jqplot.ShapeRenderer.prototype.init = function(options) { 7661 $.extend(true, this, options); 7662 }; 7663 7664 // function: draw 7665 // draws the shape. 7666 // 7667 // ctx - canvas drawing context 7668 // points - array of points for shapes or 7669 // [x, y, width, height] for rectangles or 7670 // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs. 7671 $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) { 7672 ctx.save(); 7673 var opts = (options != null) ? options : {}; 7674 var fill = (opts.fill != null) ? opts.fill : this.fill; 7675 var closePath = (opts.closePath != null) ? opts.closePath : this.closePath; 7676 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect; 7677 var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect; 7678 var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect; 7679 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc; 7680 var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern; 7681 var ctxPattern = $.jqplot.LinePattern(ctx, linePattern); 7682 ctx.lineWidth = opts.lineWidth || this.lineWidth; 7683 ctx.lineJoin = opts.lineJoin || this.lineJoin; 7684 ctx.lineCap = opts.lineCap || this.lineCap; 7685 ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle; 7686 ctx.fillStyle = opts.fillStyle || this.fillStyle; 7687 ctx.beginPath(); 7688 if (isarc) { 7689 ctx.arc(points[0], points[1], points[2], points[3], points[4], true); 7690 if (closePath) { 7691 ctx.closePath(); 7692 } 7693 if (fill) { 7694 ctx.fill(); 7695 } 7696 else { 7697 ctx.stroke(); 7698 } 7699 ctx.restore(); 7700 return; 7701 } 7702 else if (clearRect) { 7703 ctx.clearRect(points[0], points[1], points[2], points[3]); 7704 ctx.restore(); 7705 return; 7706 } 7707 else if (fillRect || strokeRect) { 7708 if (fillRect) { 7709 ctx.fillRect(points[0], points[1], points[2], points[3]); 7710 } 7711 if (strokeRect) { 7712 ctx.strokeRect(points[0], points[1], points[2], points[3]); 7713 ctx.restore(); 7714 return; 7715 } 7716 } 7717 else if (points && points.length){ 7718 var move = true; 7719 for (var i=0; i<points.length; i++) { 7720 // skip to the first non-null point and move to it. 7721 if (points[i][0] != null && points[i][1] != null) { 7722 if (move) { 7723 ctxPattern.moveTo(points[i][0], points[i][1]); 7724 move = false; 7725 } 7726 else { 7727 ctxPattern.lineTo(points[i][0], points[i][1]); 7728 } 7729 } 7730 else { 7731 move = true; 7732 } 7733 } 7734 if (closePath) { 7735 ctxPattern.closePath(); 7736 } 7737 if (fill) { 7738 ctx.fill(); 7739 } 7740 else { 7741 ctx.stroke(); 7742 } 7743 } 7744 ctx.restore(); 7745 }; 7746 7747 // class $.jqplot.TableLegendRenderer 7748 // The default legend renderer for jqPlot. 7749 $.jqplot.TableLegendRenderer = function(){ 7750 // 7751 }; 7752 7753 $.jqplot.TableLegendRenderer.prototype.init = function(options) { 7754 $.extend(true, this, options); 7755 }; 7756 7757 $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) { 7758 var rs = (pad) ? this.rowSpacing+'px' : '0px'; 7759 var tr; 7760 var td; 7761 var elem; 7762 var div0; 7763 var div1; 7764 elem = document.createElement('tr'); 7765 tr = $(elem); 7766 tr.addClass('jqplot-table-legend'); 7767 elem = null; 7768 7769 if (reverse){ 7770 tr.prependTo(this._elem); 7771 } 7772 7773 else{ 7774 tr.appendTo(this._elem); 7775 } 7776 7777 if (this.showSwatches) { 7778 td = $(document.createElement('td')); 7779 td.addClass('jqplot-table-legend jqplot-table-legend-swatch'); 7780 td.css({textAlign: 'center', paddingTop: rs}); 7781 7782 div0 = $(document.createElement('div')); 7783 div0.addClass('jqplot-table-legend-swatch-outline'); 7784 div1 = $(document.createElement('div')); 7785 div1.addClass('jqplot-table-legend-swatch'); 7786 div1.css({backgroundColor: color, borderColor: color}); 7787 7788 tr.append(td.append(div0.append(div1))); 7789 7790 // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+ 7791 // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+ 7792 // '</div></td>').appendTo(tr); 7793 } 7794 if (this.showLabels) { 7795 td = $(document.createElement('td')); 7796 td.addClass('jqplot-table-legend jqplot-table-legend-label'); 7797 td.css('paddingTop', rs); 7798 tr.append(td); 7799 7800 // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>'); 7801 // elem.appendTo(tr); 7802 if (this.escapeHtml) { 7803 td.text(label); 7804 } 7805 else { 7806 td.html(label); 7807 } 7808 } 7809 td = null; 7810 div0 = null; 7811 div1 = null; 7812 tr = null; 7813 elem = null; 7814 }; 7815 7816 // called with scope of legend 7817 $.jqplot.TableLegendRenderer.prototype.draw = function() { 7818 if (this._elem) { 7819 this._elem.emptyForce(); 7820 this._elem = null; 7821 } 7822 7823 if (this.show) { 7824 var series = this._series; 7825 // make a table. one line label per row. 7826 var elem = document.createElement('table'); 7827 this._elem = $(elem); 7828 this._elem.addClass('jqplot-table-legend'); 7829 7830 var ss = {position:'absolute'}; 7831 if (this.background) { 7832 ss['background'] = this.background; 7833 } 7834 if (this.border) { 7835 ss['border'] = this.border; 7836 } 7837 if (this.fontSize) { 7838 ss['fontSize'] = this.fontSize; 7839 } 7840 if (this.fontFamily) { 7841 ss['fontFamily'] = this.fontFamily; 7842 } 7843 if (this.textColor) { 7844 ss['textColor'] = this.textColor; 7845 } 7846 if (this.marginTop != null) { 7847 ss['marginTop'] = this.marginTop; 7848 } 7849 if (this.marginBottom != null) { 7850 ss['marginBottom'] = this.marginBottom; 7851 } 7852 if (this.marginLeft != null) { 7853 ss['marginLeft'] = this.marginLeft; 7854 } 7855 if (this.marginRight != null) { 7856 ss['marginRight'] = this.marginRight; 7857 } 7858 7859 7860 var pad = false, 7861 reverse = false, 7862 s; 7863 for (var i = 0; i< series.length; i++) { 7864 s = series[i]; 7865 if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){ 7866 reverse = true; 7867 } 7868 if (s.show && s.showLabel) { 7869 var lt = this.labels[i] || s.label.toString(); 7870 if (lt) { 7871 var color = s.color; 7872 if (reverse && i < series.length - 1){ 7873 pad = true; 7874 } 7875 else if (reverse && i == series.length - 1){ 7876 pad = false; 7877 } 7878 this.renderer.addrow.call(this, lt, color, pad, reverse); 7879 pad = true; 7880 } 7881 // let plugins add more rows to legend. Used by trend line plugin. 7882 for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) { 7883 var item = $.jqplot.addLegendRowHooks[j].call(this, s); 7884 if (item) { 7885 this.renderer.addrow.call(this, item.label, item.color, pad); 7886 pad = true; 7887 } 7888 } 7889 lt = null; 7890 } 7891 } 7892 } 7893 return this._elem; 7894 }; 7895 7896 $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) { 7897 if (this.show) { 7898 if (this.placement == 'insideGrid') { 7899 switch (this.location) { 7900 case 'nw': 7901 var a = offsets.left; 7902 var b = offsets.top; 7903 this._elem.css('left', a); 7904 this._elem.css('top', b); 7905 break; 7906 case 'n': 7907 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 7908 var b = offsets.top; 7909 this._elem.css('left', a); 7910 this._elem.css('top', b); 7911 break; 7912 case 'ne': 7913 var a = offsets.right; 7914 var b = offsets.top; 7915 this._elem.css({right:a, top:b}); 7916 break; 7917 case 'e': 7918 var a = offsets.right; 7919 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 7920 this._elem.css({right:a, top:b}); 7921 break; 7922 case 'se': 7923 var a = offsets.right; 7924 var b = offsets.bottom; 7925 this._elem.css({right:a, bottom:b}); 7926 break; 7927 case 's': 7928 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 7929 var b = offsets.bottom; 7930 this._elem.css({left:a, bottom:b}); 7931 break; 7932 case 'sw': 7933 var a = offsets.left; 7934 var b = offsets.bottom; 7935 this._elem.css({left:a, bottom:b}); 7936 break; 7937 case 'w': 7938 var a = offsets.left; 7939 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 7940 this._elem.css({left:a, top:b}); 7941 break; 7942 default: // same as 'se' 7943 var a = offsets.right; 7944 var b = offsets.bottom; 7945 this._elem.css({right:a, bottom:b}); 7946 break; 7947 } 7948 7949 } 7950 else if (this.placement == 'outside'){ 7951 switch (this.location) { 7952 case 'nw': 7953 var a = this._plotDimensions.width - offsets.left; 7954 var b = offsets.top; 7955 this._elem.css('right', a); 7956 this._elem.css('top', b); 7957 break; 7958 case 'n': 7959 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 7960 var b = this._plotDimensions.height - offsets.top; 7961 this._elem.css('left', a); 7962 this._elem.css('bottom', b); 7963 break; 7964 case 'ne': 7965 var a = this._plotDimensions.width - offsets.right; 7966 var b = offsets.top; 7967 this._elem.css({left:a, top:b}); 7968 break; 7969 case 'e': 7970 var a = this._plotDimensions.width - offsets.right; 7971 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 7972 this._elem.css({left:a, top:b}); 7973 break; 7974 case 'se': 7975 var a = this._plotDimensions.width - offsets.right; 7976 var b = offsets.bottom; 7977 this._elem.css({left:a, bottom:b}); 7978 break; 7979 case 's': 7980 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 7981 var b = this._plotDimensions.height - offsets.bottom; 7982 this._elem.css({left:a, top:b}); 7983 break; 7984 case 'sw': 7985 var a = this._plotDimensions.width - offsets.left; 7986 var b = offsets.bottom; 7987 this._elem.css({right:a, bottom:b}); 7988 break; 7989 case 'w': 7990 var a = this._plotDimensions.width - offsets.left; 7991 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 7992 this._elem.css({right:a, top:b}); 7993 break; 7994 default: // same as 'se' 7995 var a = offsets.right; 7996 var b = offsets.bottom; 7997 this._elem.css({right:a, bottom:b}); 7998 break; 7999 } 8000 } 8001 else { 8002 switch (this.location) { 8003 case 'nw': 8004 this._elem.css({left:0, top:offsets.top}); 8005 break; 8006 case 'n': 8007 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 8008 this._elem.css({left: a, top:offsets.top}); 8009 break; 8010 case 'ne': 8011 this._elem.css({right:0, top:offsets.top}); 8012 break; 8013 case 'e': 8014 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 8015 this._elem.css({right:offsets.right, top:b}); 8016 break; 8017 case 'se': 8018 this._elem.css({right:offsets.right, bottom:offsets.bottom}); 8019 break; 8020 case 's': 8021 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 8022 this._elem.css({left: a, bottom:offsets.bottom}); 8023 break; 8024 case 'sw': 8025 this._elem.css({left:offsets.left, bottom:offsets.bottom}); 8026 break; 8027 case 'w': 8028 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 8029 this._elem.css({left:offsets.left, top:b}); 8030 break; 8031 default: // same as 'se' 8032 this._elem.css({right:offsets.right, bottom:offsets.bottom}); 8033 break; 8034 } 8035 } 8036 } 8037 }; 8038 8039 /** 8040 * Class: $.jqplot.ThemeEngine 8041 * Theme Engine provides a programatic way to change some of the more 8042 * common jqplot styling options such as fonts, colors and grid options. 8043 * A theme engine instance is created with each plot. The theme engine 8044 * manages a collection of themes which can be modified, added to, or 8045 * applied to the plot. 8046 * 8047 * The themeEngine class is not instantiated directly. 8048 * When a plot is initialized, the current plot options are scanned 8049 * an a default theme named "Default" is created. This theme is 8050 * used as the basis for other themes added to the theme engine and 8051 * is always available. 8052 * 8053 * A theme is a simple javascript object with styling parameters for 8054 * various entities of the plot. A theme has the form: 8055 * 8056 * 8057 * > { 8058 * > _name:f "Default", 8059 * > target: { 8060 * > backgroundColor: "transparent" 8061 * > }, 8062 * > legend: { 8063 * > textColor: null, 8064 * > fontFamily: null, 8065 * > fontSize: null, 8066 * > border: null, 8067 * > background: null 8068 * > }, 8069 * > title: { 8070 * > textColor: "rgb(102, 102, 102)", 8071 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif", 8072 * > fontSize: "19.2px", 8073 * > textAlign: "center" 8074 * > }, 8075 * > seriesStyles: {}, 8076 * > series: [{ 8077 * > color: "#4bb2c5", 8078 * > lineWidth: 2.5, 8079 * > linePattern: "solid", 8080 * > shadow: true, 8081 * > fillColor: "#4bb2c5", 8082 * > showMarker: true, 8083 * > markerOptions: { 8084 * > color: "#4bb2c5", 8085 * > show: true, 8086 * > style: 'filledCircle', 8087 * > lineWidth: 1.5, 8088 * > size: 4, 8089 * > shadow: true 8090 * > } 8091 * > }], 8092 * > grid: { 8093 * > drawGridlines: true, 8094 * > gridLineColor: "#cccccc", 8095 * > gridLineWidth: 1, 8096 * > backgroundColor: "#fffdf6", 8097 * > borderColor: "#999999", 8098 * > borderWidth: 2, 8099 * > shadow: true 8100 * > }, 8101 * > axesStyles: { 8102 * > label: {}, 8103 * > ticks: {} 8104 * > }, 8105 * > axes: { 8106 * > xaxis: { 8107 * > borderColor: "#999999", 8108 * > borderWidth: 2, 8109 * > ticks: { 8110 * > show: true, 8111 * > showGridline: true, 8112 * > showLabel: true, 8113 * > showMark: true, 8114 * > size: 4, 8115 * > textColor: "", 8116 * > whiteSpace: "nowrap", 8117 * > fontSize: "12px", 8118 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif" 8119 * > }, 8120 * > label: { 8121 * > textColor: "rgb(102, 102, 102)", 8122 * > whiteSpace: "normal", 8123 * > fontSize: "14.6667px", 8124 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif", 8125 * > fontWeight: "400" 8126 * > } 8127 * > }, 8128 * > yaxis: { 8129 * > borderColor: "#999999", 8130 * > borderWidth: 2, 8131 * > ticks: { 8132 * > show: true, 8133 * > showGridline: true, 8134 * > showLabel: true, 8135 * > showMark: true, 8136 * > size: 4, 8137 * > textColor: "", 8138 * > whiteSpace: "nowrap", 8139 * > fontSize: "12px", 8140 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif" 8141 * > }, 8142 * > label: { 8143 * > textColor: null, 8144 * > whiteSpace: null, 8145 * > fontSize: null, 8146 * > fontFamily: null, 8147 * > fontWeight: null 8148 * > } 8149 * > }, 8150 * > x2axis: {... 8151 * > }, 8152 * > ... 8153 * > y9axis: {... 8154 * > } 8155 * > } 8156 * > } 8157 * 8158 * "seriesStyles" is a style object that will be applied to all series in the plot. 8159 * It will forcibly override any styles applied on the individual series. "axesStyles" is 8160 * a style object that will be applied to all axes in the plot. It will also forcibly 8161 * override any styles on the individual axes. 8162 * 8163 * The example shown above has series options for a line series. Options for other 8164 * series types are shown below: 8165 * 8166 * Bar Series: 8167 * 8168 * > { 8169 * > color: "#4bb2c5", 8170 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 8171 * > lineWidth: 2.5, 8172 * > shadow: true, 8173 * > barPadding: 2, 8174 * > barMargin: 10, 8175 * > barWidth: 15.09375, 8176 * > 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)"] 8177 * > } 8178 * 8179 * Pie Series: 8180 * 8181 * > { 8182 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 8183 * > padding: 20, 8184 * > sliceMargin: 0, 8185 * > fill: true, 8186 * > shadow: true, 8187 * > startAngle: 0, 8188 * > lineWidth: 2.5, 8189 * > 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)"] 8190 * > } 8191 * 8192 * Funnel Series: 8193 * 8194 * > { 8195 * > color: "#4bb2c5", 8196 * > lineWidth: 2, 8197 * > shadow: true, 8198 * > padding: { 8199 * > top: 20, 8200 * > right: 20, 8201 * > bottom: 20, 8202 * > left: 20 8203 * > }, 8204 * > sectionMargin: 6, 8205 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 8206 * > 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)"] 8207 * > } 8208 * 8209 */ 8210 $.jqplot.ThemeEngine = function(){ 8211 // Group: Properties 8212 // 8213 // prop: themes 8214 // hash of themes managed by the theme engine. 8215 // Indexed by theme name. 8216 this.themes = {}; 8217 // prop: activeTheme 8218 // Pointer to currently active theme 8219 this.activeTheme=null; 8220 8221 }; 8222 8223 // called with scope of plot 8224 $.jqplot.ThemeEngine.prototype.init = function() { 8225 // get the Default theme from the current plot settings. 8226 var th = new $.jqplot.Theme({_name:'Default'}); 8227 var n, i, nn; 8228 8229 for (n in th.target) { 8230 if (n == "textColor") { 8231 th.target[n] = this.target.css('color'); 8232 } 8233 else { 8234 th.target[n] = this.target.css(n); 8235 } 8236 } 8237 8238 if (this.title.show && this.title._elem) { 8239 for (n in th.title) { 8240 if (n == "textColor") { 8241 th.title[n] = this.title._elem.css('color'); 8242 } 8243 else { 8244 th.title[n] = this.title._elem.css(n); 8245 } 8246 } 8247 } 8248 8249 for (n in th.grid) { 8250 th.grid[n] = this.grid[n]; 8251 } 8252 if (th.grid.backgroundColor == null && this.grid.background != null) { 8253 th.grid.backgroundColor = this.grid.background; 8254 } 8255 if (this.legend.show && this.legend._elem) { 8256 for (n in th.legend) { 8257 if (n == 'textColor') { 8258 th.legend[n] = this.legend._elem.css('color'); 8259 } 8260 else { 8261 th.legend[n] = this.legend._elem.css(n); 8262 } 8263 } 8264 } 8265 var s; 8266 8267 for (i=0; i<this.series.length; i++) { 8268 s = this.series[i]; 8269 if (s.renderer.constructor == $.jqplot.LineRenderer) { 8270 th.series.push(new LineSeriesProperties()); 8271 } 8272 else if (s.renderer.constructor == $.jqplot.BarRenderer) { 8273 th.series.push(new BarSeriesProperties()); 8274 } 8275 else if (s.renderer.constructor == $.jqplot.PieRenderer) { 8276 th.series.push(new PieSeriesProperties()); 8277 } 8278 else if (s.renderer.constructor == $.jqplot.DonutRenderer) { 8279 th.series.push(new DonutSeriesProperties()); 8280 } 8281 else if (s.renderer.constructor == $.jqplot.FunnelRenderer) { 8282 th.series.push(new FunnelSeriesProperties()); 8283 } 8284 else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) { 8285 th.series.push(new MeterSeriesProperties()); 8286 } 8287 else { 8288 th.series.push({}); 8289 } 8290 for (n in th.series[i]) { 8291 th.series[i][n] = s[n]; 8292 } 8293 } 8294 var a, ax; 8295 for (n in this.axes) { 8296 ax = this.axes[n]; 8297 a = th.axes[n] = new AxisProperties(); 8298 a.borderColor = ax.borderColor; 8299 a.borderWidth = ax.borderWidth; 8300 if (ax._ticks && ax._ticks[0]) { 8301 for (nn in a.ticks) { 8302 if (ax._ticks[0].hasOwnProperty(nn)) { 8303 a.ticks[nn] = ax._ticks[0][nn]; 8304 } 8305 else if (ax._ticks[0]._elem){ 8306 a.ticks[nn] = ax._ticks[0]._elem.css(nn); 8307 } 8308 } 8309 } 8310 if (ax._label && ax._label.show) { 8311 for (nn in a.label) { 8312 // a.label[nn] = ax._label._elem.css(nn); 8313 if (ax._label[nn]) { 8314 a.label[nn] = ax._label[nn]; 8315 } 8316 else if (ax._label._elem){ 8317 if (nn == 'textColor') { 8318 a.label[nn] = ax._label._elem.css('color'); 8319 } 8320 else { 8321 a.label[nn] = ax._label._elem.css(nn); 8322 } 8323 } 8324 } 8325 } 8326 } 8327 this.themeEngine._add(th); 8328 this.themeEngine.activeTheme = this.themeEngine.themes[th._name]; 8329 }; 8330 /** 8331 * Group: methods 8332 * 8333 * method: get 8334 * 8335 * Get and return the named theme or the active theme if no name given. 8336 * 8337 * parameter: 8338 * 8339 * name - name of theme to get. 8340 * 8341 * returns: 8342 * 8343 * Theme instance of given name. 8344 */ 8345 $.jqplot.ThemeEngine.prototype.get = function(name) { 8346 if (!name) { 8347 // return the active theme 8348 return this.activeTheme; 8349 } 8350 else { 8351 return this.themes[name]; 8352 } 8353 }; 8354 8355 function numericalOrder(a,b) { return a-b; } 8356 8357 /** 8358 * method: getThemeNames 8359 * 8360 * Return the list of theme names in this manager in alpha-numerical order. 8361 * 8362 * parameter: 8363 * 8364 * None 8365 * 8366 * returns: 8367 * 8368 * A the list of theme names in this manager in alpha-numerical order. 8369 */ 8370 $.jqplot.ThemeEngine.prototype.getThemeNames = function() { 8371 var tn = []; 8372 for (var n in this.themes) { 8373 tn.push(n); 8374 } 8375 return tn.sort(numericalOrder); 8376 }; 8377 8378 /** 8379 * method: getThemes 8380 * 8381 * Return a list of themes in alpha-numerical order by name. 8382 * 8383 * parameter: 8384 * 8385 * None 8386 * 8387 * returns: 8388 * 8389 * A list of themes in alpha-numerical order by name. 8390 */ 8391 $.jqplot.ThemeEngine.prototype.getThemes = function() { 8392 var tn = []; 8393 var themes = []; 8394 for (var n in this.themes) { 8395 tn.push(n); 8396 } 8397 tn.sort(numericalOrder); 8398 for (var i=0; i<tn.length; i++) { 8399 themes.push(this.themes[tn[i]]); 8400 } 8401 return themes; 8402 }; 8403 8404 $.jqplot.ThemeEngine.prototype.activate = function(plot, name) { 8405 // sometimes need to redraw whole plot. 8406 var redrawPlot = false; 8407 if (!name && this.activeTheme && this.activeTheme._name) { 8408 name = this.activeTheme._name; 8409 } 8410 if (!this.themes.hasOwnProperty(name)) { 8411 throw new Error("No theme of that name"); 8412 } 8413 else { 8414 var th = this.themes[name]; 8415 this.activeTheme = th; 8416 var val, checkBorderColor = false, checkBorderWidth = false; 8417 var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis']; 8418 8419 for (i=0; i<arr.length; i++) { 8420 var ax = arr[i]; 8421 if (th.axesStyles.borderColor != null) { 8422 plot.axes[ax].borderColor = th.axesStyles.borderColor; 8423 } 8424 if (th.axesStyles.borderWidth != null) { 8425 plot.axes[ax].borderWidth = th.axesStyles.borderWidth; 8426 } 8427 } 8428 8429 for (var axname in plot.axes) { 8430 var axis = plot.axes[axname]; 8431 if (axis.show) { 8432 var thaxis = th.axes[axname] || {}; 8433 var thaxstyle = th.axesStyles; 8434 var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle); 8435 val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor; 8436 if (thax.borderColor != null) { 8437 axis.borderColor = thax.borderColor; 8438 redrawPlot = true; 8439 } 8440 val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth; 8441 if (thax.borderWidth != null) { 8442 axis.borderWidth = thax.borderWidth; 8443 redrawPlot = true; 8444 } 8445 if (axis._ticks && axis._ticks[0]) { 8446 for (var nn in thax.ticks) { 8447 // val = null; 8448 // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) { 8449 // val = th.axesStyles.ticks[nn]; 8450 // } 8451 // else if (thax.ticks[nn] != null){ 8452 // val = thax.ticks[nn] 8453 // } 8454 val = thax.ticks[nn]; 8455 if (val != null) { 8456 axis.tickOptions[nn] = val; 8457 axis._ticks = []; 8458 redrawPlot = true; 8459 } 8460 } 8461 } 8462 if (axis._label && axis._label.show) { 8463 for (var nn in thax.label) { 8464 // val = null; 8465 // if (th.axesStyles.label && th.axesStyles.label[nn] != null) { 8466 // val = th.axesStyles.label[nn]; 8467 // } 8468 // else if (thax.label && thax.label[nn] != null){ 8469 // val = thax.label[nn] 8470 // } 8471 val = thax.label[nn]; 8472 if (val != null) { 8473 axis.labelOptions[nn] = val; 8474 redrawPlot = true; 8475 } 8476 } 8477 } 8478 8479 } 8480 } 8481 8482 for (var n in th.grid) { 8483 if (th.grid[n] != null) { 8484 plot.grid[n] = th.grid[n]; 8485 } 8486 } 8487 if (!redrawPlot) { 8488 plot.grid.draw(); 8489 } 8490 8491 if (plot.legend.show) { 8492 for (n in th.legend) { 8493 if (th.legend[n] != null) { 8494 plot.legend[n] = th.legend[n]; 8495 } 8496 } 8497 } 8498 if (plot.title.show) { 8499 for (n in th.title) { 8500 if (th.title[n] != null) { 8501 plot.title[n] = th.title[n]; 8502 } 8503 } 8504 } 8505 8506 var i; 8507 for (i=0; i<th.series.length; i++) { 8508 var opts = {}; 8509 var redrawSeries = false; 8510 for (n in th.series[i]) { 8511 val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n]; 8512 if (val != null) { 8513 opts[n] = val; 8514 if (n == 'color') { 8515 plot.series[i].renderer.shapeRenderer.fillStyle = val; 8516 plot.series[i].renderer.shapeRenderer.strokeStyle = val; 8517 plot.series[i][n] = val; 8518 } 8519 else if ((n == 'lineWidth') || (n == 'linePattern')) { 8520 plot.series[i].renderer.shapeRenderer[n] = val; 8521 plot.series[i][n] = val; 8522 } 8523 else if (n == 'markerOptions') { 8524 merge (plot.series[i].markerOptions, val); 8525 merge (plot.series[i].markerRenderer, val); 8526 } 8527 else { 8528 plot.series[i][n] = val; 8529 } 8530 redrawPlot = true; 8531 } 8532 } 8533 } 8534 8535 if (redrawPlot) { 8536 plot.target.empty(); 8537 plot.draw(); 8538 } 8539 8540 for (n in th.target) { 8541 if (th.target[n] != null) { 8542 plot.target.css(n, th.target[n]); 8543 } 8544 } 8545 } 8546 8547 }; 8548 8549 $.jqplot.ThemeEngine.prototype._add = function(theme, name) { 8550 if (name) { 8551 theme._name = name; 8552 } 8553 if (!theme._name) { 8554 theme._name = Date.parse(new Date()); 8555 } 8556 if (!this.themes.hasOwnProperty(theme._name)) { 8557 this.themes[theme._name] = theme; 8558 } 8559 else { 8560 throw new Error("jqplot.ThemeEngine Error: Theme already in use"); 8561 } 8562 }; 8563 8564 // method remove 8565 // Delete the named theme, return true on success, false on failure. 8566 8567 8568 /** 8569 * method: remove 8570 * 8571 * Remove the given theme from the themeEngine. 8572 * 8573 * parameters: 8574 * 8575 * name - name of the theme to remove. 8576 * 8577 * returns: 8578 * 8579 * true on success, false on failure. 8580 */ 8581 $.jqplot.ThemeEngine.prototype.remove = function(name) { 8582 if (name == 'Default') { 8583 return false; 8584 } 8585 return delete this.themes[name]; 8586 }; 8587 8588 /** 8589 * method: newTheme 8590 * 8591 * Create a new theme based on the default theme, adding it the themeEngine. 8592 * 8593 * parameters: 8594 * 8595 * name - name of the new theme. 8596 * obj - optional object of styles to be applied to this new theme. 8597 * 8598 * returns: 8599 * 8600 * new Theme object. 8601 */ 8602 $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) { 8603 if (typeof(name) == 'object') { 8604 obj = obj || name; 8605 name = null; 8606 } 8607 if (obj && obj._name) { 8608 name = obj._name; 8609 } 8610 else { 8611 name = name || Date.parse(new Date()); 8612 } 8613 // var th = new $.jqplot.Theme(name); 8614 var th = this.copy(this.themes['Default']._name, name); 8615 $.jqplot.extend(th, obj); 8616 return th; 8617 }; 8618 8619 // function clone(obj) { 8620 // return eval(obj.toSource()); 8621 // } 8622 8623 function clone(obj){ 8624 if(obj == null || typeof(obj) != 'object'){ 8625 return obj; 8626 } 8627 8628 var temp = new obj.constructor(); 8629 for(var key in obj){ 8630 temp[key] = clone(obj[key]); 8631 } 8632 return temp; 8633 } 8634 8635 $.jqplot.clone = clone; 8636 8637 function merge(obj1, obj2) { 8638 if (obj2 == null || typeof(obj2) != 'object') { 8639 return; 8640 } 8641 for (var key in obj2) { 8642 if (key == 'highlightColors') { 8643 obj1[key] = clone(obj2[key]); 8644 } 8645 if (obj2[key] != null && typeof(obj2[key]) == 'object') { 8646 if (!obj1.hasOwnProperty(key)) { 8647 obj1[key] = {}; 8648 } 8649 merge(obj1[key], obj2[key]); 8650 } 8651 else { 8652 obj1[key] = obj2[key]; 8653 } 8654 } 8655 } 8656 8657 $.jqplot.merge = merge; 8658 8659 // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic 8660 $.jqplot.extend = function() { 8661 // copy reference to target object 8662 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 8663 8664 // Handle a deep copy situation 8665 if ( typeof target === "boolean" ) { 8666 deep = target; 8667 target = arguments[1] || {}; 8668 // skip the boolean and the target 8669 i = 2; 8670 } 8671 8672 // Handle case when target is a string or something (possible in deep copy) 8673 if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) { 8674 target = {}; 8675 } 8676 8677 for ( ; i < length; i++ ){ 8678 // Only deal with non-null/undefined values 8679 if ( (options = arguments[ i ]) != null ) { 8680 // Extend the base object 8681 for ( var name in options ) { 8682 var src = target[ name ], copy = options[ name ]; 8683 8684 // Prevent never-ending loop 8685 if ( target === copy ) { 8686 continue; 8687 } 8688 8689 // Recurse if we're merging object values 8690 if ( deep && copy && typeof copy === "object" && !copy.nodeType ) { 8691 target[ name ] = $.jqplot.extend( deep, 8692 // Never move original objects, clone them 8693 src || ( copy.length != null ? [ ] : { } ) 8694 , copy ); 8695 } 8696 // Don't bring in undefined values 8697 else if ( copy !== undefined ) { 8698 target[ name ] = copy; 8699 } 8700 } 8701 } 8702 } 8703 // Return the modified object 8704 return target; 8705 }; 8706 8707 /** 8708 * method: rename 8709 * 8710 * Rename a theme. 8711 * 8712 * parameters: 8713 * 8714 * oldName - current name of the theme. 8715 * newName - desired name of the theme. 8716 * 8717 * returns: 8718 * 8719 * new Theme object. 8720 */ 8721 $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) { 8722 if (oldName == 'Default' || newName == 'Default') { 8723 throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default"); 8724 } 8725 if (this.themes.hasOwnProperty(newName)) { 8726 throw new Error ("jqplot.ThemeEngine Error: New name already in use."); 8727 } 8728 else if (this.themes.hasOwnProperty(oldName)) { 8729 var th = this.copy (oldName, newName); 8730 this.remove(oldName); 8731 return th; 8732 } 8733 throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid"); 8734 }; 8735 8736 /** 8737 * method: copy 8738 * 8739 * Create a copy of an existing theme in the themeEngine, adding it the themeEngine. 8740 * 8741 * parameters: 8742 * 8743 * sourceName - name of the existing theme. 8744 * targetName - name of the copy. 8745 * obj - optional object of style parameter to apply to the new theme. 8746 * 8747 * returns: 8748 * 8749 * new Theme object. 8750 */ 8751 $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) { 8752 if (targetName == 'Default') { 8753 throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme"); 8754 } 8755 if (!this.themes.hasOwnProperty(sourceName)) { 8756 var s = "jqplot.ThemeEngine Error: Source name invalid"; 8757 throw new Error(s); 8758 } 8759 if (this.themes.hasOwnProperty(targetName)) { 8760 var s = "jqplot.ThemeEngine Error: Target name invalid"; 8761 throw new Error(s); 8762 } 8763 else { 8764 var th = clone(this.themes[sourceName]); 8765 th._name = targetName; 8766 $.jqplot.extend(true, th, obj); 8767 this._add(th); 8768 return th; 8769 } 8770 }; 8771 8772 8773 $.jqplot.Theme = function(name, obj) { 8774 if (typeof(name) == 'object') { 8775 obj = obj || name; 8776 name = null; 8777 } 8778 name = name || Date.parse(new Date()); 8779 this._name = name; 8780 this.target = { 8781 backgroundColor: null 8782 }; 8783 this.legend = { 8784 textColor: null, 8785 fontFamily: null, 8786 fontSize: null, 8787 border: null, 8788 background: null 8789 }; 8790 this.title = { 8791 textColor: null, 8792 fontFamily: null, 8793 fontSize: null, 8794 textAlign: null 8795 }; 8796 this.seriesStyles = {}; 8797 this.series = []; 8798 this.grid = { 8799 drawGridlines: null, 8800 gridLineColor: null, 8801 gridLineWidth: null, 8802 backgroundColor: null, 8803 borderColor: null, 8804 borderWidth: null, 8805 shadow: null 8806 }; 8807 this.axesStyles = {label:{}, ticks:{}}; 8808 this.axes = {}; 8809 if (typeof(obj) == 'string') { 8810 this._name = obj; 8811 } 8812 else if(typeof(obj) == 'object') { 8813 $.jqplot.extend(true, this, obj); 8814 } 8815 }; 8816 8817 var AxisProperties = function() { 8818 this.borderColor = null; 8819 this.borderWidth = null; 8820 this.ticks = new AxisTicks(); 8821 this.label = new AxisLabel(); 8822 }; 8823 8824 var AxisTicks = function() { 8825 this.show = null; 8826 this.showGridline = null; 8827 this.showLabel = null; 8828 this.showMark = null; 8829 this.size = null; 8830 this.textColor = null; 8831 this.whiteSpace = null; 8832 this.fontSize = null; 8833 this.fontFamily = null; 8834 }; 8835 8836 var AxisLabel = function() { 8837 this.textColor = null; 8838 this.whiteSpace = null; 8839 this.fontSize = null; 8840 this.fontFamily = null; 8841 this.fontWeight = null; 8842 }; 8843 8844 var LineSeriesProperties = function() { 8845 this.color=null; 8846 this.lineWidth=null; 8847 this.linePattern=null; 8848 this.shadow=null; 8849 this.fillColor=null; 8850 this.showMarker=null; 8851 this.markerOptions = new MarkerOptions(); 8852 }; 8853 8854 var MarkerOptions = function() { 8855 this.show = null; 8856 this.style = null; 8857 this.lineWidth = null; 8858 this.size = null; 8859 this.color = null; 8860 this.shadow = null; 8861 }; 8862 8863 var BarSeriesProperties = function() { 8864 this.color=null; 8865 this.seriesColors=null; 8866 this.lineWidth=null; 8867 this.shadow=null; 8868 this.barPadding=null; 8869 this.barMargin=null; 8870 this.barWidth=null; 8871 this.highlightColors=null; 8872 }; 8873 8874 var PieSeriesProperties = function() { 8875 this.seriesColors=null; 8876 this.padding=null; 8877 this.sliceMargin=null; 8878 this.fill=null; 8879 this.shadow=null; 8880 this.startAngle=null; 8881 this.lineWidth=null; 8882 this.highlightColors=null; 8883 }; 8884 8885 var DonutSeriesProperties = function() { 8886 this.seriesColors=null; 8887 this.padding=null; 8888 this.sliceMargin=null; 8889 this.fill=null; 8890 this.shadow=null; 8891 this.startAngle=null; 8892 this.lineWidth=null; 8893 this.innerDiameter=null; 8894 this.thickness=null; 8895 this.ringMargin=null; 8896 this.highlightColors=null; 8897 }; 8898 8899 var FunnelSeriesProperties = function() { 8900 this.color=null; 8901 this.lineWidth=null; 8902 this.shadow=null; 8903 this.padding=null; 8904 this.sectionMargin=null; 8905 this.seriesColors=null; 8906 this.highlightColors=null; 8907 }; 8908 8909 var MeterSeriesProperties = function() { 8910 this.padding=null; 8911 this.backgroundColor=null; 8912 this.ringColor=null; 8913 this.tickColor=null; 8914 this.ringWidth=null; 8915 this.intervalColors=null; 8916 this.intervalInnerRadius=null; 8917 this.intervalOuterRadius=null; 8918 this.hubRadius=null; 8919 this.needleThickness=null; 8920 this.needlePad=null; 8921 }; 8922 8923 8924 8925 8926 $.fn.jqplotChildText = function() { 8927 return $(this).contents().filter(function() { 8928 return this.nodeType == 3; // Node.TEXT_NODE not defined in I7 8929 }).text(); 8930 }; 8931 8932 // Returns font style as abbreviation for "font" property. 8933 $.fn.jqplotGetComputedFontStyle = function() { 8934 var css = window.getComputedStyle ? window.getComputedStyle(this[0], "") : this[0].currentStyle; 8935 var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily']; 8936 var style = []; 8937 8938 for (var i=0 ; i < attrs.length; ++i) { 8939 var attr = String(css[attrs[i]]); 8940 8941 if (attr && attr != 'normal') { 8942 style.push(attr); 8943 } 8944 } 8945 return style.join(' '); 8946 }; 8947 8948 /** 8949 * Namespace: $.fn 8950 * jQuery namespace to attach functions to jQuery elements. 8951 * 8952 */ 8953 8954 $.fn.jqplotToImageCanvas = function(options) { 8955 8956 options = options || {}; 8957 var x_offset = (options.x_offset == null) ? 0 : options.x_offset; 8958 var y_offset = (options.y_offset == null) ? 0 : options.y_offset; 8959 var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor; 8960 8961 if ($(this).width() == 0 || $(this).height() == 0) { 8962 return null; 8963 } 8964 8965 // excanvas and hence IE < 9 do not support toDataURL and cannot export images. 8966 if ($.jqplot.use_excanvas) { 8967 return null; 8968 } 8969 8970 var newCanvas = document.createElement("canvas"); 8971 var h = $(this).outerHeight(true); 8972 var w = $(this).outerWidth(true); 8973 var offs = $(this).offset(); 8974 var plotleft = offs.left; 8975 var plottop = offs.top; 8976 var transx = 0, transy = 0; 8977 8978 // have to check if any elements are hanging outside of plot area before rendering, 8979 // since changing width of canvas will erase canvas. 8980 8981 var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick', 8982 'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick', 8983 'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label', 8984 'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ]; 8985 8986 var temptop, templeft, tempbottom, tempright; 8987 8988 for (var i = 0; i < clses.length; i++) { 8989 $(this).find('.'+clses[i]).each(function() { 8990 temptop = $(this).offset().top - plottop; 8991 templeft = $(this).offset().left - plotleft; 8992 tempright = templeft + $(this).outerWidth(true) + transx; 8993 tempbottom = temptop + $(this).outerHeight(true) + transy; 8994 if (templeft < -transx) { 8995 w = w - transx - templeft; 8996 transx = -templeft; 8997 } 8998 if (temptop < -transy) { 8999 h = h - transy - temptop; 9000 transy = - temptop; 9001 } 9002 if (tempright > w) { 9003 w = tempright; 9004 } 9005 if (tempbottom > h) { 9006 h = tempbottom; 9007 } 9008 }); 9009 } 9010 9011 newCanvas.width = w + Number(x_offset); 9012 newCanvas.height = h + Number(y_offset); 9013 9014 var newContext = newCanvas.getContext("2d"); 9015 9016 newContext.save(); 9017 newContext.fillStyle = backgroundColor; 9018 newContext.fillRect(0,0, newCanvas.width, newCanvas.height); 9019 newContext.restore(); 9020 9021 newContext.translate(transx, transy); 9022 newContext.textAlign = 'left'; 9023 newContext.textBaseline = 'top'; 9024 9025 function getLineheight(el) { 9026 var lineheight = parseInt($(el).css('line-height'), 10); 9027 9028 if (isNaN(lineheight)) { 9029 lineheight = parseInt($(el).css('font-size'), 10) * 1.2; 9030 } 9031 return lineheight; 9032 } 9033 9034 function writeWrappedText (el, context, text, left, top, canvasWidth) { 9035 var lineheight = getLineheight(el); 9036 var tagwidth = $(el).innerWidth(); 9037 var tagheight = $(el).innerHeight(); 9038 var words = text.split(/\s+/); 9039 var wl = words.length; 9040 var w = ''; 9041 var breaks = []; 9042 var temptop = top; 9043 var templeft = left; 9044 9045 for (var i=0; i<wl; i++) { 9046 w += words[i]; 9047 if (context.measureText(w).width > tagwidth) { 9048 breaks.push(i); 9049 w = ''; 9050 i--; 9051 } 9052 } 9053 if (breaks.length === 0) { 9054 // center text if necessary 9055 if ($(el).css('textAlign') === 'center') { 9056 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; 9057 } 9058 context.fillText(text, templeft, top); 9059 } 9060 else { 9061 w = words.slice(0, breaks[0]).join(' '); 9062 // center text if necessary 9063 if ($(el).css('textAlign') === 'center') { 9064 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; 9065 } 9066 context.fillText(w, templeft, temptop); 9067 temptop += lineheight; 9068 for (var i=1, l=breaks.length; i<l; i++) { 9069 w = words.slice(breaks[i-1], breaks[i]).join(' '); 9070 // center text if necessary 9071 if ($(el).css('textAlign') === 'center') { 9072 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; 9073 } 9074 context.fillText(w, templeft, temptop); 9075 temptop += lineheight; 9076 } 9077 w = words.slice(breaks[i-1], words.length).join(' '); 9078 // center text if necessary 9079 if ($(el).css('textAlign') === 'center') { 9080 templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; 9081 } 9082 context.fillText(w, templeft, temptop); 9083 } 9084 9085 } 9086 9087 function _jqpToImage(el, x_offset, y_offset) { 9088 var tagname = el.tagName.toLowerCase(); 9089 var p = $(el).position(); 9090 var css = window.getComputedStyle ? window.getComputedStyle(el, "") : el.currentStyle; // for IE < 9 9091 var left = x_offset + p.left + parseInt(css.marginLeft, 10) + parseInt(css.borderLeftWidth, 10) + parseInt(css.paddingLeft, 10); 9092 var top = y_offset + p.top + parseInt(css.marginTop, 10) + parseInt(css.borderTopWidth, 10)+ parseInt(css.paddingTop, 10); 9093 var w = newCanvas.width; 9094 // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth') 9095 9096 // somehow in here, for divs within divs, the width of the inner div should be used instead of the canvas. 9097 9098 if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) { 9099 $(el).children().each(function() { 9100 _jqpToImage(this, left, top); 9101 }); 9102 var text = $(el).jqplotChildText(); 9103 9104 if (text) { 9105 newContext.font = $(el).jqplotGetComputedFontStyle(); 9106 newContext.fillStyle = $(el).css('color'); 9107 9108 writeWrappedText(el, newContext, text, left, top, w); 9109 } 9110 } 9111 9112 // handle the standard table legend 9113 9114 else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) { 9115 newContext.strokeStyle = $(el).css('border-top-color'); 9116 newContext.fillStyle = $(el).css('background-color'); 9117 newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight()); 9118 if (parseInt($(el).css('border-top-width'), 10) > 0) { 9119 newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight()); 9120 } 9121 9122 // find all the swatches 9123 $(el).find('div.jqplot-table-legend-swatch-outline').each(function() { 9124 // get the first div and stroke it 9125 var elem = $(this); 9126 newContext.strokeStyle = elem.css('border-top-color'); 9127 var l = left + elem.position().left; 9128 var t = top + elem.position().top; 9129 newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight()); 9130 9131 // now fill the swatch 9132 9133 l += parseInt(elem.css('padding-left'), 10); 9134 t += parseInt(elem.css('padding-top'), 10); 9135 var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top'), 10); 9136 var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left'), 10); 9137 9138 var swatch = elem.children('div.jqplot-table-legend-swatch'); 9139 newContext.fillStyle = swatch.css('background-color'); 9140 newContext.fillRect(l, t, w, h); 9141 }); 9142 9143 // now add text 9144 9145 $(el).find('td.jqplot-table-legend-label').each(function(){ 9146 var elem = $(this); 9147 var l = left + elem.position().left; 9148 var t = top + elem.position().top + parseInt(elem.css('padding-top'), 10); 9149 newContext.font = elem.jqplotGetComputedFontStyle(); 9150 newContext.fillStyle = elem.css('color'); 9151 writeWrappedText(elem, newContext, elem.text(), l, t, w); 9152 }); 9153 9154 var elem = null; 9155 } 9156 9157 else if (tagname == 'canvas') { 9158 newContext.drawImage(el, left, top); 9159 } 9160 } 9161 $(this).children().each(function() { 9162 _jqpToImage(this, x_offset, y_offset); 9163 }); 9164 return newCanvas; 9165 }; 9166 9167 // return the raw image data string. 9168 // Should work on canvas supporting browsers. 9169 $.fn.jqplotToImageStr = function(options) { 9170 var imgCanvas = $(this).jqplotToImageCanvas(options); 9171 if (imgCanvas) { 9172 return imgCanvas.toDataURL("image/png"); 9173 } 9174 else { 9175 return null; 9176 } 9177 }; 9178 9179 // return a DOM <img> element and return it. 9180 // Should work on canvas supporting browsers. 9181 $.fn.jqplotToImageElem = function(options) { 9182 var elem = document.createElement("img"); 9183 var str = $(this).jqplotToImageStr(options); 9184 elem.src = str; 9185 return elem; 9186 }; 9187 9188 // return a string for an <img> element and return it. 9189 // Should work on canvas supporting browsers. 9190 $.fn.jqplotToImageElemStr = function(options) { 9191 var str = '<img src='+$(this).jqplotToImageStr(options)+' />'; 9192 return str; 9193 }; 9194 9195 // Not gauranteed to work, even on canvas supporting browsers due to 9196 // limitations with location.href and browser support. 9197 $.fn.jqplotSaveImage = function() { 9198 var imgData = $(this).jqplotToImageStr({}); 9199 if (imgData) { 9200 window.location.href = imgData.replace("image/png", "image/octet-stream"); 9201 } 9202 9203 }; 9204 9205 // Not gauranteed to work, even on canvas supporting browsers due to 9206 // limitations with window.open and arbitrary data. 9207 $.fn.jqplotViewImage = function() { 9208 var imgStr = $(this).jqplotToImageElemStr({}); 9209 var imgData = $(this).jqplotToImageStr({}); 9210 if (imgStr) { 9211 var w = window.open(''); 9212 w.document.open("image/png"); 9213 w.document.write(imgStr); 9214 w.document.close(); 9215 w = null; 9216 } 9217 }; 9218 9219 9220 9221 9222 /** 9223 * @description 9224 * <p>Object with extended date parsing and formatting capabilities. 9225 * This library borrows many concepts and ideas from the Date Instance 9226 * Methods by Ken Snyder along with some parts of Ken's actual code.</p> 9227 * 9228 * <p>jsDate takes a different approach by not extending the built-in 9229 * Date Object, improving date parsing, allowing for multiple formatting 9230 * syntaxes and multiple and more easily expandable localization.</p> 9231 * 9232 * @author Chris Leonello 9233 * @date #date# 9234 * @version #VERSION# 9235 * @copyright (c) 2010 Chris Leonello 9236 * jsDate is currently available for use in all personal or commercial projects 9237 * under both the MIT and GPL version 2.0 licenses. This means that you can 9238 * choose the license that best suits your project and use it accordingly. 9239 * 9240 * <p>Ken's origianl Date Instance Methods and copyright notice:</p> 9241 * <pre> 9242 * Ken Snyder (ken d snyder at gmail dot com) 9243 * 2008-09-10 9244 * version 2.0.2 (http://kendsnyder.com/sandbox/date/) 9245 * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/) 9246 * </pre> 9247 * 9248 * @class 9249 * @name jsDate 9250 * @param {String | Number | Array | Date Object | Options Object} arguments Optional arguments, either a parsable date/time string, 9251 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds], 9252 * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional. 9253 */ 9254 9255 var jsDate = function () { 9256 9257 this.syntax = jsDate.config.syntax; 9258 this._type = "jsDate"; 9259 this.proxy = new Date(); 9260 this.options = {}; 9261 this.locale = jsDate.regional.getLocale(); 9262 this.formatString = ''; 9263 this.defaultCentury = jsDate.config.defaultCentury; 9264 9265 switch ( arguments.length ) { 9266 case 0: 9267 break; 9268 case 1: 9269 // other objects either won't have a _type property or, 9270 // if they do, it shouldn't be set to "jsDate", so 9271 // assume it is an options argument. 9272 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") { 9273 var opts = this.options = arguments[0]; 9274 this.syntax = opts.syntax || this.syntax; 9275 this.defaultCentury = opts.defaultCentury || this.defaultCentury; 9276 this.proxy = jsDate.createDate(opts.date); 9277 } 9278 else { 9279 this.proxy = jsDate.createDate(arguments[0]); 9280 } 9281 break; 9282 default: 9283 var a = []; 9284 for ( var i=0; i<arguments.length; i++ ) { 9285 a.push(arguments[i]); 9286 } 9287 // this should be the current date/time? 9288 this.proxy = new Date(); 9289 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) ); 9290 if ( a.slice(3).length ) { 9291 this.proxy.setHours.apply( this.proxy, a.slice(3) ); 9292 } 9293 break; 9294 } 9295 }; 9296 9297 /** 9298 * @namespace Configuration options that will be used as defaults for all instances on the page. 9299 * @property {String} defaultLocale The default locale to use [en]. 9300 * @property {String} syntax The default syntax to use [perl]. 9301 * @property {Number} defaultCentury The default centry for 2 digit dates. 9302 */ 9303 jsDate.config = { 9304 defaultLocale: 'en', 9305 syntax: 'perl', 9306 defaultCentury: 1900 9307 }; 9308 9309 /** 9310 * Add an arbitrary amount to the currently stored date 9311 * 9312 * @param {Number} number 9313 * @param {String} unit 9314 * @returns {jsDate} 9315 */ 9316 9317 jsDate.prototype.add = function(number, unit) { 9318 var factor = multipliers[unit] || multipliers.day; 9319 if (typeof factor == 'number') { 9320 this.proxy.setTime(this.proxy.getTime() + (factor * number)); 9321 } else { 9322 factor.add(this, number); 9323 } 9324 return this; 9325 }; 9326 9327 /** 9328 * Create a new jqplot.date object with the same date 9329 * 9330 * @returns {jsDate} 9331 */ 9332 9333 jsDate.prototype.clone = function() { 9334 return new jsDate(this.proxy.getTime()); 9335 }; 9336 9337 /** 9338 * Get the UTC TimeZone Offset of this date in milliseconds. 9339 * 9340 * @returns {Number} 9341 */ 9342 9343 jsDate.prototype.getUtcOffset = function() { 9344 return this.proxy.getTimezoneOffset() * 60000; 9345 }; 9346 9347 /** 9348 * Find the difference between this jsDate and another date. 9349 * 9350 * @param {String| Number| Array| jsDate Object| Date Object} dateObj 9351 * @param {String} unit 9352 * @param {Boolean} allowDecimal 9353 * @returns {Number} Number of units difference between dates. 9354 */ 9355 9356 jsDate.prototype.diff = function(dateObj, unit, allowDecimal) { 9357 // ensure we have a Date object 9358 dateObj = new jsDate(dateObj); 9359 if (dateObj === null) { 9360 return null; 9361 } 9362 // get the multiplying factor integer or factor function 9363 var factor = multipliers[unit] || multipliers.day; 9364 if (typeof factor == 'number') { 9365 // multiply 9366 var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor; 9367 } else { 9368 // run function 9369 var unitDiff = factor.diff(this.proxy, dateObj.proxy); 9370 } 9371 // if decimals are not allowed, round toward zero 9372 return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff)); 9373 }; 9374 9375 /** 9376 * Get the abbreviated name of the current week day 9377 * 9378 * @returns {String} 9379 */ 9380 9381 jsDate.prototype.getAbbrDayName = function() { 9382 return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()]; 9383 }; 9384 9385 /** 9386 * Get the abbreviated name of the current month 9387 * 9388 * @returns {String} 9389 */ 9390 9391 jsDate.prototype.getAbbrMonthName = function() { 9392 return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()]; 9393 }; 9394 9395 /** 9396 * Get UPPER CASE AM or PM for the current time 9397 * 9398 * @returns {String} 9399 */ 9400 9401 jsDate.prototype.getAMPM = function() { 9402 return this.proxy.getHours() >= 12 ? 'PM' : 'AM'; 9403 }; 9404 9405 /** 9406 * Get lower case am or pm for the current time 9407 * 9408 * @returns {String} 9409 */ 9410 9411 jsDate.prototype.getAmPm = function() { 9412 return this.proxy.getHours() >= 12 ? 'pm' : 'am'; 9413 }; 9414 9415 /** 9416 * Get the century (19 for 20th Century) 9417 * 9418 * @returns {Integer} Century (19 for 20th century). 9419 */ 9420 jsDate.prototype.getCentury = function() { 9421 return parseInt(this.proxy.getFullYear()/100, 10); 9422 }; 9423 9424 /** 9425 * Implements Date functionality 9426 */ 9427 jsDate.prototype.getDate = function() { 9428 return this.proxy.getDate(); 9429 }; 9430 9431 /** 9432 * Implements Date functionality 9433 */ 9434 jsDate.prototype.getDay = function() { 9435 return this.proxy.getDay(); 9436 }; 9437 9438 /** 9439 * Get the Day of week 1 (Monday) thru 7 (Sunday) 9440 * 9441 * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday) 9442 */ 9443 jsDate.prototype.getDayOfWeek = function() { 9444 var dow = this.proxy.getDay(); 9445 return dow===0?7:dow; 9446 }; 9447 9448 /** 9449 * Get the day of the year 9450 * 9451 * @returns {Integer} 1 - 366, day of the year 9452 */ 9453 jsDate.prototype.getDayOfYear = function() { 9454 var d = this.proxy; 9455 var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT'); 9456 ms += d.getTimezoneOffset()*60000; 9457 d = null; 9458 return parseInt(ms/60000/60/24, 10)+1; 9459 }; 9460 9461 /** 9462 * Get the name of the current week day 9463 * 9464 * @returns {String} 9465 */ 9466 9467 jsDate.prototype.getDayName = function() { 9468 return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()]; 9469 }; 9470 9471 /** 9472 * Get the week number of the given year, starting with the first Sunday as the first week 9473 * @returns {Integer} Week number (13 for the 13th full week of the year). 9474 */ 9475 jsDate.prototype.getFullWeekOfYear = function() { 9476 var d = this.proxy; 9477 var doy = this.getDayOfYear(); 9478 var rdow = 6-d.getDay(); 9479 var woy = parseInt((doy+rdow)/7, 10); 9480 return woy; 9481 }; 9482 9483 /** 9484 * Implements Date functionality 9485 */ 9486 jsDate.prototype.getFullYear = function() { 9487 return this.proxy.getFullYear(); 9488 }; 9489 9490 /** 9491 * Get the GMT offset in hours and minutes (e.g. +06:30) 9492 * 9493 * @returns {String} 9494 */ 9495 9496 jsDate.prototype.getGmtOffset = function() { 9497 // divide the minutes offset by 60 9498 var hours = this.proxy.getTimezoneOffset() / 60; 9499 // decide if we are ahead of or behind GMT 9500 var prefix = hours < 0 ? '+' : '-'; 9501 // remove the negative sign if any 9502 hours = Math.abs(hours); 9503 // add the +/- to the padded number of hours to : to the padded minutes 9504 return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2); 9505 }; 9506 9507 /** 9508 * Implements Date functionality 9509 */ 9510 jsDate.prototype.getHours = function() { 9511 return this.proxy.getHours(); 9512 }; 9513 9514 /** 9515 * Get the current hour on a 12-hour scheme 9516 * 9517 * @returns {Integer} 9518 */ 9519 9520 jsDate.prototype.getHours12 = function() { 9521 var hours = this.proxy.getHours(); 9522 return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours); 9523 }; 9524 9525 9526 jsDate.prototype.getIsoWeek = function() { 9527 var d = this.proxy; 9528 var woy = d.getWeekOfYear(); 9529 var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay(); 9530 // First week is 01 and not 00 as in the case of %U and %W, 9531 // so we add 1 to the final result except if day 1 of the year 9532 // is a Monday (then %W returns 01). 9533 // We also need to subtract 1 if the day 1 of the year is 9534 // Friday-Sunday, so the resulting equation becomes: 9535 var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1); 9536 if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4) 9537 { 9538 idow = 1; 9539 } 9540 else if(idow === 0) 9541 { 9542 d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31')); 9543 idow = d.getIsoWeek(); 9544 } 9545 d = null; 9546 return idow; 9547 }; 9548 9549 /** 9550 * Implements Date functionality 9551 */ 9552 jsDate.prototype.getMilliseconds = function() { 9553 return this.proxy.getMilliseconds(); 9554 }; 9555 9556 /** 9557 * Implements Date functionality 9558 */ 9559 jsDate.prototype.getMinutes = function() { 9560 return this.proxy.getMinutes(); 9561 }; 9562 9563 /** 9564 * Implements Date functionality 9565 */ 9566 jsDate.prototype.getMonth = function() { 9567 return this.proxy.getMonth(); 9568 }; 9569 9570 /** 9571 * Get the name of the current month 9572 * 9573 * @returns {String} 9574 */ 9575 9576 jsDate.prototype.getMonthName = function() { 9577 return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()]; 9578 }; 9579 9580 /** 9581 * Get the number of the current month, 1-12 9582 * 9583 * @returns {Integer} 9584 */ 9585 9586 jsDate.prototype.getMonthNumber = function() { 9587 return this.proxy.getMonth() + 1; 9588 }; 9589 9590 /** 9591 * Implements Date functionality 9592 */ 9593 jsDate.prototype.getSeconds = function() { 9594 return this.proxy.getSeconds(); 9595 }; 9596 9597 /** 9598 * Return a proper two-digit year integer 9599 * 9600 * @returns {Integer} 9601 */ 9602 9603 jsDate.prototype.getShortYear = function() { 9604 return this.proxy.getYear() % 100; 9605 }; 9606 9607 /** 9608 * Implements Date functionality 9609 */ 9610 jsDate.prototype.getTime = function() { 9611 return this.proxy.getTime(); 9612 }; 9613 9614 /** 9615 * Get the timezone abbreviation 9616 * 9617 * @returns {String} Abbreviation for the timezone 9618 */ 9619 jsDate.prototype.getTimezoneAbbr = function() { 9620 return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1'); 9621 }; 9622 9623 /** 9624 * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time) 9625 * 9626 * @returns {String} 9627 */ 9628 jsDate.prototype.getTimezoneName = function() { 9629 var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString()); 9630 return match[1] || match[2] || 'GMT' + this.getGmtOffset(); 9631 }; 9632 9633 /** 9634 * Implements Date functionality 9635 */ 9636 jsDate.prototype.getTimezoneOffset = function() { 9637 return this.proxy.getTimezoneOffset(); 9638 }; 9639 9640 9641 /** 9642 * Get the week number of the given year, starting with the first Monday as the first week 9643 * @returns {Integer} Week number (13 for the 13th week of the year). 9644 */ 9645 jsDate.prototype.getWeekOfYear = function() { 9646 var doy = this.getDayOfYear(); 9647 var rdow = 7 - this.getDayOfWeek(); 9648 var woy = parseInt((doy+rdow)/7, 10); 9649 return woy; 9650 }; 9651 9652 /** 9653 * Get the current date as a Unix timestamp 9654 * 9655 * @returns {Integer} 9656 */ 9657 9658 jsDate.prototype.getUnix = function() { 9659 return Math.round(this.proxy.getTime() / 1000, 0); 9660 }; 9661 9662 /** 9663 * Implements Date functionality 9664 */ 9665 jsDate.prototype.getYear = function() { 9666 return this.proxy.getYear(); 9667 }; 9668 9669 /** 9670 * Return a date one day ahead (or any other unit) 9671 * 9672 * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond 9673 * @returns {jsDate} 9674 */ 9675 9676 jsDate.prototype.next = function(unit) { 9677 unit = unit || 'day'; 9678 return this.clone().add(1, unit); 9679 }; 9680 9681 /** 9682 * Set the jsDate instance to a new date. 9683 * 9684 * @param {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments, 9685 * either a parsable date/time string, 9686 * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds], 9687 * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional. 9688 */ 9689 jsDate.prototype.set = function() { 9690 switch ( arguments.length ) { 9691 case 0: 9692 this.proxy = new Date(); 9693 break; 9694 case 1: 9695 // other objects either won't have a _type property or, 9696 // if they do, it shouldn't be set to "jsDate", so 9697 // assume it is an options argument. 9698 if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") { 9699 var opts = this.options = arguments[0]; 9700 this.syntax = opts.syntax || this.syntax; 9701 this.defaultCentury = opts.defaultCentury || this.defaultCentury; 9702 this.proxy = jsDate.createDate(opts.date); 9703 } 9704 else { 9705 this.proxy = jsDate.createDate(arguments[0]); 9706 } 9707 break; 9708 default: 9709 var a = []; 9710 for ( var i=0; i<arguments.length; i++ ) { 9711 a.push(arguments[i]); 9712 } 9713 // this should be the current date/time 9714 this.proxy = new Date(); 9715 this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) ); 9716 if ( a.slice(3).length ) { 9717 this.proxy.setHours.apply( this.proxy, a.slice(3) ); 9718 } 9719 break; 9720 } 9721 return this; 9722 }; 9723 9724 /** 9725 * Sets the day of the month for a specified date according to local time. 9726 * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month. 9727 */ 9728 jsDate.prototype.setDate = function(n) { 9729 this.proxy.setDate(n); 9730 return this; 9731 }; 9732 9733 /** 9734 * Sets the full year for a specified date according to local time. 9735 * @param {Integer} yearValue The numeric value of the year, for example, 1995. 9736 * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December. 9737 * @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. 9738 */ 9739 jsDate.prototype.setFullYear = function() { 9740 this.proxy.setFullYear.apply(this.proxy, arguments); 9741 return this; 9742 }; 9743 9744 /** 9745 * Sets the hours for a specified date according to local time. 9746 * 9747 * @param {Integer} hoursValue An integer between 0 and 23, representing the hour. 9748 * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes. 9749 * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds. 9750 * If you specify the secondsValue parameter, you must also specify the minutesValue. 9751 * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds. 9752 * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue. 9753 */ 9754 jsDate.prototype.setHours = function() { 9755 this.proxy.setHours.apply(this.proxy, arguments); 9756 return this; 9757 }; 9758 9759 /** 9760 * Implements Date functionality 9761 */ 9762 jsDate.prototype.setMilliseconds = function(n) { 9763 this.proxy.setMilliseconds(n); 9764 return this; 9765 }; 9766 9767 /** 9768 * Implements Date functionality 9769 */ 9770 jsDate.prototype.setMinutes = function() { 9771 this.proxy.setMinutes.apply(this.proxy, arguments); 9772 return this; 9773 }; 9774 9775 /** 9776 * Implements Date functionality 9777 */ 9778 jsDate.prototype.setMonth = function() { 9779 this.proxy.setMonth.apply(this.proxy, arguments); 9780 return this; 9781 }; 9782 9783 /** 9784 * Implements Date functionality 9785 */ 9786 jsDate.prototype.setSeconds = function() { 9787 this.proxy.setSeconds.apply(this.proxy, arguments); 9788 return this; 9789 }; 9790 9791 /** 9792 * Implements Date functionality 9793 */ 9794 jsDate.prototype.setTime = function(n) { 9795 this.proxy.setTime(n); 9796 return this; 9797 }; 9798 9799 /** 9800 * Implements Date functionality 9801 */ 9802 jsDate.prototype.setYear = function() { 9803 this.proxy.setYear.apply(this.proxy, arguments); 9804 return this; 9805 }; 9806 9807 /** 9808 * Provide a formatted string representation of this date. 9809 * 9810 * @param {String} formatString A format string. 9811 * See: {@link jsDate.formats}. 9812 * @returns {String} Date String. 9813 */ 9814 9815 jsDate.prototype.strftime = function(formatString) { 9816 formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString']; 9817 return jsDate.strftime(this, formatString, this.syntax); 9818 }; 9819 9820 /** 9821 * Return a String representation of this jsDate object. 9822 * @returns {String} Date string. 9823 */ 9824 9825 jsDate.prototype.toString = function() { 9826 return this.proxy.toString(); 9827 }; 9828 9829 /** 9830 * Convert the current date to an 8-digit integer (%Y%m%d) 9831 * 9832 * @returns {Integer} 9833 */ 9834 9835 jsDate.prototype.toYmdInt = function() { 9836 return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate(); 9837 }; 9838 9839 /** 9840 * @namespace Holds localizations for month/day names. 9841 * <p>jsDate attempts to detect locale when loaded and defaults to 'en'. 9842 * If a localization is detected which is not available, jsDate defaults to 'en'. 9843 * Additional localizations can be added after jsDate loads. After adding a localization, 9844 * call the jsDate.regional.getLocale() method. Currently, en, fr and de are defined.</p> 9845 * 9846 * <p>Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p> 9847 * <pre class="code"> 9848 * jsDate.regional['en'] = { 9849 * monthNames : 'January February March April May June July August September October November December'.split(' '), 9850 * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '), 9851 * dayNames : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '), 9852 * dayNamesShort : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ') 9853 * }; 9854 * </pre> 9855 * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the 9856 * new localizations.</p> 9857 */ 9858 9859 jsDate.regional = { 9860 'en': { 9861 monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'], 9862 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 9863 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 9864 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 9865 formatString: '%Y-%m-%d %H:%M:%S' 9866 }, 9867 9868 'fr': { 9869 monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'], 9870 monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'], 9871 dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], 9872 dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'], 9873 formatString: '%Y-%m-%d %H:%M:%S' 9874 }, 9875 9876 'de': { 9877 monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'], 9878 monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'], 9879 dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], 9880 dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], 9881 formatString: '%Y-%m-%d %H:%M:%S' 9882 }, 9883 9884 'es': { 9885 monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'], 9886 monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'], 9887 dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'], 9888 dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'], 9889 formatString: '%Y-%m-%d %H:%M:%S' 9890 }, 9891 9892 'ru': { 9893 monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'], 9894 monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], 9895 dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'], 9896 dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'], 9897 formatString: '%Y-%m-%d %H:%M:%S' 9898 }, 9899 9900 'ar': { 9901 monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], 9902 monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'], 9903 dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'], 9904 dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'], 9905 formatString: '%Y-%m-%d %H:%M:%S' 9906 }, 9907 9908 'pt': { 9909 monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], 9910 monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'], 9911 dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], 9912 dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], 9913 formatString: '%Y-%m-%d %H:%M:%S' 9914 }, 9915 9916 'pt-BR': { 9917 monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], 9918 monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'], 9919 dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], 9920 dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], 9921 formatString: '%Y-%m-%d %H:%M:%S' 9922 } 9923 9924 9925 }; 9926 9927 // Set english variants to 'en' 9928 jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en']; 9929 9930 /** 9931 * Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en' 9932 * if it cannot figure out a locale of if the locale does not have a localization defined. 9933 * @returns {String} locale 9934 */ 9935 9936 jsDate.regional.getLocale = function () { 9937 var l = jsDate.config.defaultLocale; 9938 9939 if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) { 9940 l = document.getElementsByTagName('html')[0].lang; 9941 if (!jsDate.regional.hasOwnProperty(l)) { 9942 l = jsDate.config.defaultLocale; 9943 } 9944 } 9945 9946 return l; 9947 }; 9948 9949 // ms in day 9950 var day = 24 * 60 * 60 * 1000; 9951 9952 // padd a number with zeros 9953 var addZeros = function(num, digits) { 9954 num = String(num); 9955 var i = digits - num.length; 9956 var s = String(Math.pow(10, i)).slice(1); 9957 return s.concat(num); 9958 }; 9959 9960 // representations used for calculating differences between dates. 9961 // This borrows heavily from Ken Snyder's work. 9962 var multipliers = { 9963 millisecond: 1, 9964 second: 1000, 9965 minute: 60 * 1000, 9966 hour: 60 * 60 * 1000, 9967 day: day, 9968 week: 7 * day, 9969 month: { 9970 // add a number of months 9971 add: function(d, number) { 9972 // add any years needed (increments of 12) 9973 multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12)); 9974 // ensure that we properly wrap betwen December and January 9975 // 11 % 12 = 11 9976 // 12 % 12 = 0 9977 var prevMonth = d.getMonth() + (number % 12); 9978 if (prevMonth == 12) { 9979 prevMonth = 0; 9980 d.setYear(d.getFullYear() + 1); 9981 } else if (prevMonth == -1) { 9982 prevMonth = 11; 9983 d.setYear(d.getFullYear() - 1); 9984 } 9985 d.setMonth(prevMonth); 9986 }, 9987 // get the number of months between two Date objects (decimal to the nearest day) 9988 diff: function(d1, d2) { 9989 // get the number of years 9990 var diffYears = d1.getFullYear() - d2.getFullYear(); 9991 // get the number of remaining months 9992 var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12); 9993 // get the number of remaining days 9994 var diffDays = d1.getDate() - d2.getDate(); 9995 // return the month difference with the days difference as a decimal 9996 return diffMonths + (diffDays / 30); 9997 } 9998 }, 9999 year: { 10000 // add a number of years 10001 add: function(d, number) { 10002 d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number)); 10003 }, 10004 // get the number of years between two Date objects (decimal to the nearest day) 10005 diff: function(d1, d2) { 10006 return multipliers.month.diff(d1, d2) / 12; 10007 } 10008 } 10009 }; 10010 // 10011 // Alias each multiplier with an 's' to allow 'year' and 'years' for example. 10012 // This comes from Ken Snyders work. 10013 // 10014 for (var unit in multipliers) { 10015 if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :| 10016 multipliers[unit + 's'] = multipliers[unit]; 10017 } 10018 } 10019 10020 // 10021 // take a jsDate instance and a format code and return the formatted value. 10022 // This is a somewhat modified version of Ken Snyder's method. 10023 // 10024 var format = function(d, code, syntax) { 10025 // if shorcut codes are used, recursively expand those. 10026 if (jsDate.formats[syntax]["shortcuts"][code]) { 10027 return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax); 10028 } else { 10029 // get the format code function and addZeros() argument 10030 var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.'); 10031 var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : ''; 10032 if (getter[1]) { 10033 nbr = addZeros(nbr, getter[1]); 10034 } 10035 return nbr; 10036 } 10037 }; 10038 10039 /** 10040 * @static 10041 * Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes. 10042 * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p> 10043 * <pre class="code"> 10044 * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S'); 10045 * </pre> 10046 * @param {String | Number | Array | jsDate Object | Date Object} date A parsable date string, JavaScript time stamp, Array of form [year, month, day, hours, minutes, seconds, milliseconds], jsDate Object or Date object. 10047 * @param {String} formatString String with embedded date formatting codes. 10048 * See: {@link jsDate.formats}. 10049 * @param {String} syntax Optional syntax to use [default perl]. 10050 * @param {String} locale Optional locale to use. 10051 * @returns {String} Formatted representation of the date. 10052 */ 10053 // 10054 // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods. 10055 // 10056 jsDate.strftime = function(d, formatString, syntax, locale) { 10057 var syn = 'perl'; 10058 var loc = jsDate.regional.getLocale(); 10059 10060 // check if syntax and locale are available or reversed 10061 if (syntax && jsDate.formats.hasOwnProperty(syntax)) { 10062 syn = syntax; 10063 } 10064 else if (syntax && jsDate.regional.hasOwnProperty(syntax)) { 10065 loc = syntax; 10066 } 10067 10068 if (locale && jsDate.formats.hasOwnProperty(locale)) { 10069 syn = locale; 10070 } 10071 else if (locale && jsDate.regional.hasOwnProperty(locale)) { 10072 loc = locale; 10073 } 10074 10075 if (get_type(d) != "[object Object]" || d._type != "jsDate") { 10076 d = new jsDate(d); 10077 d.locale = loc; 10078 } 10079 if (!formatString) { 10080 formatString = d.formatString || jsDate.regional[loc]['formatString']; 10081 } 10082 // default the format string to year-month-day 10083 var source = formatString || '%Y-%m-%d', 10084 result = '', 10085 match; 10086 // replace each format code 10087 while (source.length > 0) { 10088 if (match = source.match(jsDate.formats[syn].codes.matcher)) { 10089 result += source.slice(0, match.index); 10090 result += (match[1] || '') + format(d, match[2], syn); 10091 source = source.slice(match.index + match[0].length); 10092 } else { 10093 result += source; 10094 source = ''; 10095 } 10096 } 10097 return result; 10098 }; 10099 10100 /** 10101 * @namespace 10102 * Namespace to hold format codes and format shortcuts. "perl" and "php" format codes 10103 * and shortcuts are defined by default. Additional codes and shortcuts can be 10104 * added like: 10105 * 10106 * <pre class="code"> 10107 * jsDate.formats["perl"] = { 10108 * "codes": { 10109 * matcher: /someregex/, 10110 * Y: "fullYear", // name of "get" method without the "get", 10111 * ..., // more codes 10112 * }, 10113 * "shortcuts": { 10114 * F: '%Y-%m-%d', 10115 * ..., // more shortcuts 10116 * } 10117 * }; 10118 * </pre> 10119 * 10120 * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via: 10121 * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code> 10122 */ 10123 10124 jsDate.formats = { 10125 ISO:'%Y-%m-%dT%H:%M:%S.%N%G', 10126 SQL:'%Y-%m-%d %H:%M:%S' 10127 }; 10128 10129 /** 10130 * Perl format codes and shortcuts for strftime. 10131 * 10132 * A hash (object) of codes where each code must be an array where the first member is 10133 * the name of a Date.prototype or jsDate.prototype function to call 10134 * and optionally a second member indicating the number to pass to addZeros() 10135 * 10136 * <p>The following format codes are defined:</p> 10137 * 10138 * <pre class="code"> 10139 * Code Result Description 10140 * == Years == 10141 * %Y 2008 Four-digit year 10142 * %y 08 Two-digit year 10143 * 10144 * == Months == 10145 * %m 09 Two-digit month 10146 * %#m 9 One or two-digit month 10147 * %B September Full month name 10148 * %b Sep Abbreviated month name 10149 * 10150 * == Days == 10151 * %d 05 Two-digit day of month 10152 * %#d 5 One or two-digit day of month 10153 * %e 5 One or two-digit day of month 10154 * %A Sunday Full name of the day of the week 10155 * %a Sun Abbreviated name of the day of the week 10156 * %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday) 10157 * 10158 * == Hours == 10159 * %H 23 Hours in 24-hour format (two digits) 10160 * %#H 3 Hours in 24-hour integer format (one or two digits) 10161 * %I 11 Hours in 12-hour format (two digits) 10162 * %#I 3 Hours in 12-hour integer format (one or two digits) 10163 * %p PM AM or PM 10164 * 10165 * == Minutes == 10166 * %M 09 Minutes (two digits) 10167 * %#M 9 Minutes (one or two digits) 10168 * 10169 * == Seconds == 10170 * %S 02 Seconds (two digits) 10171 * %#S 2 Seconds (one or two digits) 10172 * %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00) 10173 * 10174 * == Milliseconds == 10175 * %N 008 Milliseconds (three digits) 10176 * %#N 8 Milliseconds (one to three digits) 10177 * 10178 * == Timezone == 10179 * %O 360 difference in minutes between local time and GMT 10180 * %Z Mountain Standard Time Name of timezone as reported by browser 10181 * %G 06:00 Hours and minutes between GMT 10182 * 10183 * == Shortcuts == 10184 * %F 2008-03-26 %Y-%m-%d 10185 * %T 05:06:30 %H:%M:%S 10186 * %X 05:06:30 %H:%M:%S 10187 * %x 03/26/08 %m/%d/%y 10188 * %D 03/26/08 %m/%d/%y 10189 * %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y 10190 * %v 3-Sep-2008 %e-%b-%Y 10191 * %R 15:31 %H:%M 10192 * %r 03:31:00 PM %I:%M:%S %p 10193 * 10194 * == Characters == 10195 * %n \n Newline 10196 * %t \t Tab 10197 * %% % Percent Symbol 10198 * </pre> 10199 * 10200 * <p>Formatting shortcuts that will be translated into their longer version. 10201 * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p> 10202 * 10203 * <p>Format codes and format shortcuts can be redefined after the jsDate 10204 * module is imported.</p> 10205 * 10206 * <p>Note that if you redefine the whole hash (object), you must supply a "matcher" 10207 * regex for the parser. The default matcher is:</p> 10208 * 10209 * <code>/()%(#?(%|[a-z]))/i</code> 10210 * 10211 * <p>which corresponds to the Perl syntax used by default.</p> 10212 * 10213 * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p> 10214 */ 10215 10216 jsDate.formats.perl = { 10217 codes: { 10218 // 10219 // 2-part regex matcher for format codes 10220 // 10221 // first match must be the character before the code (to account for escaping) 10222 // second match must be the format code character(s) 10223 // 10224 matcher: /()%(#?(%|[a-z]))/i, 10225 // year 10226 Y: 'FullYear', 10227 y: 'ShortYear.2', 10228 // month 10229 m: 'MonthNumber.2', 10230 '#m': 'MonthNumber', 10231 B: 'MonthName', 10232 b: 'AbbrMonthName', 10233 // day 10234 d: 'Date.2', 10235 '#d': 'Date', 10236 e: 'Date', 10237 A: 'DayName', 10238 a: 'AbbrDayName', 10239 w: 'Day', 10240 // hours 10241 H: 'Hours.2', 10242 '#H': 'Hours', 10243 I: 'Hours12.2', 10244 '#I': 'Hours12', 10245 p: 'AMPM', 10246 // minutes 10247 M: 'Minutes.2', 10248 '#M': 'Minutes', 10249 // seconds 10250 S: 'Seconds.2', 10251 '#S': 'Seconds', 10252 s: 'Unix', 10253 // milliseconds 10254 N: 'Milliseconds.3', 10255 '#N': 'Milliseconds', 10256 // timezone 10257 O: 'TimezoneOffset', 10258 Z: 'TimezoneName', 10259 G: 'GmtOffset' 10260 }, 10261 10262 shortcuts: { 10263 // date 10264 F: '%Y-%m-%d', 10265 // time 10266 T: '%H:%M:%S', 10267 X: '%H:%M:%S', 10268 // local format date 10269 x: '%m/%d/%y', 10270 D: '%m/%d/%y', 10271 // local format extended 10272 '#c': '%a %b %e %H:%M:%S %Y', 10273 // local format short 10274 v: '%e-%b-%Y', 10275 R: '%H:%M', 10276 r: '%I:%M:%S %p', 10277 // tab and newline 10278 t: '\t', 10279 n: '\n', 10280 '%': '%' 10281 } 10282 }; 10283 10284 /** 10285 * PHP format codes and shortcuts for strftime. 10286 * 10287 * A hash (object) of codes where each code must be an array where the first member is 10288 * the name of a Date.prototype or jsDate.prototype function to call 10289 * and optionally a second member indicating the number to pass to addZeros() 10290 * 10291 * <p>The following format codes are defined:</p> 10292 * 10293 * <pre class="code"> 10294 * Code Result Description 10295 * === Days === 10296 * %a Sun through Sat An abbreviated textual representation of the day 10297 * %A Sunday - Saturday A full textual representation of the day 10298 * %d 01 to 31 Two-digit day of the month (with leading zeros) 10299 * %e 1 to 31 Day of the month, with a space preceding single digits. 10300 * %j 001 to 366 Day of the year, 3 digits with leading zeros 10301 * %u 1 - 7 (Mon - Sun) ISO-8601 numeric representation of the day of the week 10302 * %w 0 - 6 (Sun - Sat) Numeric representation of the day of the week 10303 * 10304 * === Week === 10305 * %U 13 Full Week number, starting with the first Sunday as the first week 10306 * %V 01 through 53 ISO-8601:1988 week number, starting with the first week of the year 10307 * with at least 4 weekdays, with Monday being the start of the week 10308 * %W 46 A numeric representation of the week of the year, 10309 * starting with the first Monday as the first week 10310 * === Month === 10311 * %b Jan through Dec Abbreviated month name, based on the locale 10312 * %B January - December Full month name, based on the locale 10313 * %h Jan through Dec Abbreviated month name, based on the locale (an alias of %b) 10314 * %m 01 - 12 (Jan - Dec) Two digit representation of the month 10315 * 10316 * === Year === 10317 * %C 19 Two digit century (year/100, truncated to an integer) 10318 * %y 09 for 2009 Two digit year 10319 * %Y 2038 Four digit year 10320 * 10321 * === Time === 10322 * %H 00 through 23 Two digit representation of the hour in 24-hour format 10323 * %I 01 through 12 Two digit representation of the hour in 12-hour format 10324 * %l 1 through 12 Hour in 12-hour format, with a space preceeding single digits 10325 * %M 00 through 59 Two digit representation of the minute 10326 * %p AM/PM UPPER-CASE 'AM' or 'PM' based on the given time 10327 * %P am/pm lower-case 'am' or 'pm' based on the given time 10328 * %r 09:34:17 PM Same as %I:%M:%S %p 10329 * %R 00:35 Same as %H:%M 10330 * %S 00 through 59 Two digit representation of the second 10331 * %T 21:34:17 Same as %H:%M:%S 10332 * %X 03:59:16 Preferred time representation based on locale, without the date 10333 * %z -0500 or EST Either the time zone offset from UTC or the abbreviation 10334 * %Z -0500 or EST The time zone offset/abbreviation option NOT given by %z 10335 * 10336 * === Time and Date === 10337 * %D 02/05/09 Same as %m/%d/%y 10338 * %F 2009-02-05 Same as %Y-%m-%d (commonly used in database datestamps) 10339 * %s 305815200 Unix Epoch Time timestamp (same as the time() function) 10340 * %x 02/05/09 Preferred date representation, without the time 10341 * 10342 * === Miscellaneous === 10343 * %n --- A newline character (\n) 10344 * %t --- A Tab character (\t) 10345 * %% --- A literal percentage character (%) 10346 * </pre> 10347 */ 10348 10349 jsDate.formats.php = { 10350 codes: { 10351 // 10352 // 2-part regex matcher for format codes 10353 // 10354 // first match must be the character before the code (to account for escaping) 10355 // second match must be the format code character(s) 10356 // 10357 matcher: /()%((%|[a-z]))/i, 10358 // day 10359 a: 'AbbrDayName', 10360 A: 'DayName', 10361 d: 'Date.2', 10362 e: 'Date', 10363 j: 'DayOfYear.3', 10364 u: 'DayOfWeek', 10365 w: 'Day', 10366 // week 10367 U: 'FullWeekOfYear.2', 10368 V: 'IsoWeek.2', 10369 W: 'WeekOfYear.2', 10370 // month 10371 b: 'AbbrMonthName', 10372 B: 'MonthName', 10373 m: 'MonthNumber.2', 10374 h: 'AbbrMonthName', 10375 // year 10376 C: 'Century.2', 10377 y: 'ShortYear.2', 10378 Y: 'FullYear', 10379 // time 10380 H: 'Hours.2', 10381 I: 'Hours12.2', 10382 l: 'Hours12', 10383 p: 'AMPM', 10384 P: 'AmPm', 10385 M: 'Minutes.2', 10386 S: 'Seconds.2', 10387 s: 'Unix', 10388 O: 'TimezoneOffset', 10389 z: 'GmtOffset', 10390 Z: 'TimezoneAbbr' 10391 }, 10392 10393 shortcuts: { 10394 D: '%m/%d/%y', 10395 F: '%Y-%m-%d', 10396 T: '%H:%M:%S', 10397 X: '%H:%M:%S', 10398 x: '%m/%d/%y', 10399 R: '%H:%M', 10400 r: '%I:%M:%S %p', 10401 t: '\t', 10402 n: '\n', 10403 '%': '%' 10404 } 10405 }; 10406 // 10407 // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods. 10408 // I use his idea of a set of parsers which can be regular expressions or functions, 10409 // iterating through those, and then seeing if Date.parse() will create a date. 10410 // The parser expressions and functions are a little different and some bugs have been 10411 // worked out. Also, a lot of "pre-parsing" is done to fix implementation 10412 // variations of Date.parse() between browsers. 10413 // 10414 jsDate.createDate = function(date) { 10415 // if passing in multiple arguments, try Date constructor 10416 if (date == null) { 10417 return new Date(); 10418 } 10419 // If the passed value is already a date object, return it 10420 if (date instanceof Date) { 10421 return date; 10422 } 10423 // if (typeof date == 'number') return new Date(date * 1000); 10424 // If the passed value is an integer, interpret it as a javascript timestamp 10425 if (typeof date == 'number') { 10426 return new Date(date); 10427 } 10428 10429 // Before passing strings into Date.parse(), have to normalize them for certain conditions. 10430 // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent. 10431 // 10432 // For example: 10433 // * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century. 10434 // * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse. 10435 // * Both FF, Chrome and Opera will parse '1984/1/25' into localtime. 10436 10437 // remove leading and trailing spaces 10438 var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1'); 10439 10440 // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn] 10441 parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3"); 10442 10443 ///////// 10444 // Need to check for '15-Dec-09' also. 10445 // FF will not parse, but Chrome will. 10446 // Chrome will set date to 2009 as well. 10447 ///////// 10448 10449 // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010' 10450 parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3"); 10451 10452 // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century. 10453 var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i); 10454 if (match && match.length > 3) { 10455 var m3 = parseFloat(match[3]); 10456 var ny = jsDate.config.defaultCentury + m3; 10457 ny = String(ny); 10458 10459 // now replace 2 digit year with 4 digit year 10460 parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny); 10461 10462 } 10463 10464 // Check for '1/19/70 8:14PM' 10465 // where starts with mm/dd/yy or yy/mm/dd and have something after 10466 // Check if 1st postiion is greater than 31, assume it is year. 10467 // Assme all 2 digit years are 1900's. 10468 // Finally, change them into US style mm/dd/yyyy representations. 10469 match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/); 10470 10471 function h1(parsable, match) { 10472 var m1 = parseFloat(match[1]); 10473 var m2 = parseFloat(match[2]); 10474 var m3 = parseFloat(match[3]); 10475 var cent = jsDate.config.defaultCentury; 10476 var ny, nd, nm, str; 10477 10478 if (m1 > 31) { // first number is a year 10479 nd = m3; 10480 nm = m2; 10481 ny = cent + m1; 10482 } 10483 10484 else { // last number is the year 10485 nd = m2; 10486 nm = m1; 10487 ny = cent + m3; 10488 } 10489 10490 str = nm+'/'+nd+'/'+ny; 10491 10492 // now replace 2 digit year with 4 digit year 10493 return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str); 10494 10495 } 10496 10497 if (match && match.length > 3) { 10498 parsable = h1(parsable, match); 10499 } 10500 10501 // Now check for '1/19/70' with nothing after and do as above 10502 var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/); 10503 10504 if (match && match.length > 3) { 10505 parsable = h1(parsable, match); 10506 } 10507 10508 10509 var i = 0; 10510 var length = jsDate.matchers.length; 10511 var pattern, 10512 ms, 10513 current = parsable, 10514 obj; 10515 while (i < length) { 10516 ms = Date.parse(current); 10517 if (!isNaN(ms)) { 10518 return new Date(ms); 10519 } 10520 pattern = jsDate.matchers[i]; 10521 if (typeof pattern == 'function') { 10522 obj = pattern.call(jsDate, current); 10523 if (obj instanceof Date) { 10524 return obj; 10525 } 10526 } else { 10527 current = parsable.replace(pattern[0], pattern[1]); 10528 } 10529 i++; 10530 } 10531 return NaN; 10532 }; 10533 10534 10535 /** 10536 * @static 10537 * Handy static utility function to return the number of days in a given month. 10538 * @param {Integer} year Year 10539 * @param {Integer} month Month (1-12) 10540 * @returns {Integer} Number of days in the month. 10541 */ 10542 // 10543 // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods. 10544 // 10545 jsDate.daysInMonth = function(year, month) { 10546 if (month == 2) { 10547 return new Date(year, 1, 29).getDate() == 29 ? 29 : 28; 10548 } 10549 return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month]; 10550 }; 10551 10552 10553 // 10554 // An Array of regular expressions or functions that will attempt to match the date string. 10555 // Functions are called with scope of a jsDate instance. 10556 // 10557 jsDate.matchers = [ 10558 // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date). 10559 [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'], 10560 // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date). 10561 [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'], 10562 // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part. 10563 function(str) { 10564 var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i); 10565 // opt. date hour opt. minute opt. second opt. msec opt. am or pm 10566 if (match) { 10567 if (match[1]) { 10568 var d = this.createDate(match[1]); 10569 if (isNaN(d)) { 10570 return; 10571 } 10572 } else { 10573 var d = new Date(); 10574 d.setMilliseconds(0); 10575 } 10576 var hour = parseFloat(match[2]); 10577 if (match[6]) { 10578 hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12); 10579 } 10580 d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000); 10581 return d; 10582 } 10583 else { 10584 return str; 10585 } 10586 }, 10587 // Handle ISO timestamp with time zone. 10588 function(str) { 10589 var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i); 10590 if (match) { 10591 if (match[1]) { 10592 var d = this.createDate(match[1]); 10593 if (isNaN(d)) { 10594 return; 10595 } 10596 } else { 10597 var d = new Date(); 10598 d.setMilliseconds(0); 10599 } 10600 var hour = parseFloat(match[2]); 10601 d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000); 10602 return d; 10603 } 10604 else { 10605 return str; 10606 } 10607 }, 10608 // Try to match ambiguous strings like 12/8/22. 10609 // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's). 10610 // This may be redundant with pre processing of date already performed. 10611 function(str) { 10612 var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/); 10613 if (match) { 10614 var d = new Date(); 10615 var cent = jsDate.config.defaultCentury; 10616 var m1 = parseFloat(match[1]); 10617 var m3 = parseFloat(match[3]); 10618 var ny, nd, nm; 10619 if (m1 > 31) { // first number is a year 10620 nd = m3; 10621 ny = cent + m1; 10622 } 10623 10624 else { // last number is the year 10625 nd = m1; 10626 ny = cent + m3; 10627 } 10628 10629 var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]); 10630 10631 if (nm == -1) { 10632 nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]); 10633 } 10634 10635 d.setFullYear(ny, nm, nd); 10636 d.setHours(0,0,0,0); 10637 return d; 10638 } 10639 10640 else { 10641 return str; 10642 } 10643 } 10644 ]; 10645 10646 // 10647 // I think John Reisig published this method on his blog, ejohn. 10648 // 10649 function inArray( elem, array ) { 10650 if ( array.indexOf ) { 10651 return array.indexOf( elem ); 10652 } 10653 10654 for ( var i = 0, length = array.length; i < length; i++ ) { 10655 if ( array[ i ] === elem ) { 10656 return i; 10657 } 10658 } 10659 10660 return -1; 10661 } 10662 10663 // 10664 // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method. 10665 // 10666 function get_type(thing){ 10667 if(thing===null) return "[object Null]"; // special case 10668 return Object.prototype.toString.call(thing); 10669 } 10670 10671 $.jsDate = jsDate; 10672 10673 10674 /** 10675 * JavaScript printf/sprintf functions. 10676 * 10677 * This code has been adapted from the publicly available sprintf methods 10678 * by Ash Searle. His original header follows: 10679 * 10680 * This code is unrestricted: you are free to use it however you like. 10681 * 10682 * The functions should work as expected, performing left or right alignment, 10683 * truncating strings, outputting numbers with a required precision etc. 10684 * 10685 * For complex cases, these functions follow the Perl implementations of 10686 * (s)printf, allowing arguments to be passed out-of-order, and to set the 10687 * precision or length of the output based on arguments instead of fixed 10688 * numbers. 10689 * 10690 * See http://perldoc.perl.org/functions/sprintf.html for more information. 10691 * 10692 * Implemented: 10693 * - zero and space-padding 10694 * - right and left-alignment, 10695 * - base X prefix (binary, octal and hex) 10696 * - positive number prefix 10697 * - (minimum) width 10698 * - precision / truncation / maximum width 10699 * - out of order arguments 10700 * 10701 * Not implemented (yet): 10702 * - vector flag 10703 * - size (bytes, words, long-words etc.) 10704 * 10705 * Will not implement: 10706 * - %n or %p (no pass-by-reference in JavaScript) 10707 * 10708 * @version 2007.04.27 10709 * @author Ash Searle 10710 * 10711 * You can see the original work and comments on his blog: 10712 * http://hexmen.com/blog/2007/03/printf-sprintf/ 10713 * http://hexmen.com/js/sprintf.js 10714 */ 10715 10716 /** 10717 * @Modifications 2009.05.26 10718 * @author Chris Leonello 10719 * 10720 * Added %p %P specifier 10721 * Acts like %g or %G but will not add more significant digits to the output than present in the input. 10722 * Example: 10723 * Format: '%.3p', Input: 0.012, Output: 0.012 10724 * Format: '%.3g', Input: 0.012, Output: 0.0120 10725 * Format: '%.4p', Input: 12.0, Output: 12.0 10726 * Format: '%.4g', Input: 12.0, Output: 12.00 10727 * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5 10728 * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5 10729 * 10730 * Example: 10731 * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23) 10732 * "23.35, 43" 10733 * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524) 10734 * "no value: , decimal with thousands separator: 433,524" 10735 */ 10736 $.jqplot.sprintf = function() { 10737 function pad(str, len, chr, leftJustify) { 10738 var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr); 10739 return leftJustify ? str + padding : padding + str; 10740 10741 } 10742 10743 function thousand_separate(value) { 10744 var value_str = new String(value); 10745 for (var i=10; i>0; i--) { 10746 if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break; 10747 } 10748 return value_str; 10749 } 10750 10751 function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) { 10752 var diff = minWidth - value.length; 10753 if (diff > 0) { 10754 var spchar = ' '; 10755 if (htmlSpace) { spchar = ' '; } 10756 if (leftJustify || !zeroPad) { 10757 value = pad(value, minWidth, spchar, leftJustify); 10758 } else { 10759 value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length); 10760 } 10761 } 10762 return value; 10763 } 10764 10765 function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) { 10766 // Note: casts negative numbers to positive ones 10767 var number = value >>> 0; 10768 prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || ''; 10769 value = prefix + pad(number.toString(base), precision || 0, '0', false); 10770 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); 10771 } 10772 10773 function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) { 10774 if (precision != null) { 10775 value = value.slice(0, precision); 10776 } 10777 return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace); 10778 } 10779 10780 var a = arguments, i = 0, format = a[i++]; 10781 10782 return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) { 10783 if (substring == '%%') { return '%'; } 10784 10785 // parse flags 10786 var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false; 10787 for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) { 10788 case ' ': positivePrefix = ' '; break; 10789 case '+': positivePrefix = '+'; break; 10790 case '-': leftJustify = true; break; 10791 case '0': zeroPad = true; break; 10792 case '#': prefixBaseX = true; break; 10793 case '&': htmlSpace = true; break; 10794 case '\'': thousandSeparation = true; break; 10795 } 10796 10797 // parameters may be null, undefined, empty-string or real valued 10798 // we want to ignore null, undefined and empty-string values 10799 10800 if (!minWidth) { 10801 minWidth = 0; 10802 } 10803 else if (minWidth == '*') { 10804 minWidth = +a[i++]; 10805 } 10806 else if (minWidth.charAt(0) == '*') { 10807 minWidth = +a[minWidth.slice(1, -1)]; 10808 } 10809 else { 10810 minWidth = +minWidth; 10811 } 10812 10813 // Note: undocumented perl feature: 10814 if (minWidth < 0) { 10815 minWidth = -minWidth; 10816 leftJustify = true; 10817 } 10818 10819 if (!isFinite(minWidth)) { 10820 throw new Error('$.jqplot.sprintf: (minimum-)width must be finite'); 10821 } 10822 10823 if (!precision) { 10824 precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0); 10825 } 10826 else if (precision == '*') { 10827 precision = +a[i++]; 10828 } 10829 else if (precision.charAt(0) == '*') { 10830 precision = +a[precision.slice(1, -1)]; 10831 } 10832 else { 10833 precision = +precision; 10834 } 10835 10836 // grab value using valueIndex if required? 10837 var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++]; 10838 10839 switch (type) { 10840 case 's': { 10841 if (value == null) { 10842 return ''; 10843 } 10844 return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace); 10845 } 10846 case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace); 10847 case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace); 10848 case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); 10849 case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); 10850 case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase(); 10851 case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); 10852 case 'i': { 10853 var number = parseInt(+value, 10); 10854 if (isNaN(number)) { 10855 return ''; 10856 } 10857 var prefix = number < 0 ? '-' : positivePrefix; 10858 var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number)); 10859 value = prefix + pad(number_str, precision, '0', false); 10860 //value = prefix + pad(String(Math.abs(number)), precision, '0', false); 10861 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); 10862 } 10863 case 'd': { 10864 var number = Math.round(+value); 10865 if (isNaN(number)) { 10866 return ''; 10867 } 10868 var prefix = number < 0 ? '-' : positivePrefix; 10869 var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number)); 10870 value = prefix + pad(number_str, precision, '0', false); 10871 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); 10872 } 10873 case 'e': 10874 case 'E': 10875 case 'f': 10876 case 'F': 10877 case 'g': 10878 case 'G': 10879 { 10880 var number = +value; 10881 if (isNaN(number)) { 10882 return ''; 10883 } 10884 var prefix = number < 0 ? '-' : positivePrefix; 10885 var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())]; 10886 var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2]; 10887 var number_str = Math.abs(number)[method](precision); 10888 number_str = thousandSeparation ? thousand_separate(number_str): number_str; 10889 value = prefix + number_str; 10890 var justified = justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform](); 10891 10892 if ($.jqplot.sprintf.decimalMark !== '.' && $.jqplot.sprintf.decimalMark !== $.jqplot.sprintf.thousandsSeparator) { 10893 return justified.replace(/\./, $.jqplot.sprintf.decimalMark); 10894 } else { 10895 return justified; 10896 } 10897 } 10898 case 'p': 10899 case 'P': 10900 { 10901 // make sure number is a number 10902 var number = +value; 10903 if (isNaN(number)) { 10904 return ''; 10905 } 10906 var prefix = number < 0 ? '-' : positivePrefix; 10907 10908 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/); 10909 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length; 10910 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0; 10911 10912 if (Math.abs(number) < 1) { 10913 if (sd + zeros <= precision) { 10914 value = prefix + Math.abs(number).toPrecision(sd); 10915 } 10916 else { 10917 if (sd <= precision - 1) { 10918 value = prefix + Math.abs(number).toExponential(sd-1); 10919 } 10920 else { 10921 value = prefix + Math.abs(number).toExponential(precision-1); 10922 } 10923 } 10924 } 10925 else { 10926 var prec = (sd <= precision) ? sd : precision; 10927 value = prefix + Math.abs(number).toPrecision(prec); 10928 } 10929 var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2]; 10930 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform](); 10931 } 10932 case 'n': return ''; 10933 default: return substring; 10934 } 10935 }); 10936 }; 10937 10938 $.jqplot.sprintf.thousandsSeparator = ','; 10939 // Specifies the decimal mark for floating point values. By default a period '.' 10940 // is used. If you change this value to for example a comma be sure to also 10941 // change the thousands separator or else this won't work since a simple String 10942 // replace is used (replacing all periods with the mark specified here). 10943 $.jqplot.sprintf.decimalMark = '.'; 10944 10945 $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g; 10946 10947 $.jqplot.getSignificantFigures = function(number) { 10948 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/); 10949 // total significant digits 10950 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length; 10951 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0; 10952 // exponent 10953 var expn = parseInt(parts[1], 10); 10954 // digits to the left of the decimal place 10955 var dleft = (expn + 1 > 0) ? expn + 1 : 0; 10956 // digits to the right of the decimal place 10957 var dright = (sd <= dleft) ? 0 : sd - expn - 1; 10958 return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ; 10959 }; 10960 10961 $.jqplot.getPrecision = function(number) { 10962 return $.jqplot.getSignificantFigures(number).digitsRight; 10963 }; 10964 10965 })(jQuery); 10966 10967 10968 var backCompat = $.uiBackCompat !== false; 10969 10970 $.jqplot.effects = { 10971 effect: {} 10972 }; 10973 10974 // prefix used for storing data on .data() 10975 var dataSpace = "jqplot.storage."; 10976 10977 /******************************************************************************/ 10978 /*********************************** EFFECTS **********************************/ 10979 /******************************************************************************/ 10980 10981 $.extend( $.jqplot.effects, { 10982 version: "1.9pre", 10983 10984 // Saves a set of properties in a data storage 10985 save: function( element, set ) { 10986 for( var i=0; i < set.length; i++ ) { 10987 if ( set[ i ] !== null ) { 10988 element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] ); 10989 } 10990 } 10991 }, 10992 10993 // Restores a set of previously saved properties from a data storage 10994 restore: function( element, set ) { 10995 for( var i=0; i < set.length; i++ ) { 10996 if ( set[ i ] !== null ) { 10997 element.css( set[ i ], element.data( dataSpace + set[ i ] ) ); 10998 } 10999 } 11000 }, 11001 11002 setMode: function( el, mode ) { 11003 if (mode === "toggle") { 11004 mode = el.is( ":hidden" ) ? "show" : "hide"; 11005 } 11006 return mode; 11007 }, 11008 11009 // Wraps the element around a wrapper that copies position properties 11010 createWrapper: function( element ) { 11011 11012 // if the element is already wrapped, return it 11013 if ( element.parent().is( ".ui-effects-wrapper" )) { 11014 return element.parent(); 11015 } 11016 11017 // wrap the element 11018 var props = { 11019 width: element.outerWidth(true), 11020 height: element.outerHeight(true), 11021 "float": element.css( "float" ) 11022 }, 11023 wrapper = $( "<div></div>" ) 11024 .addClass( "ui-effects-wrapper" ) 11025 .css({ 11026 fontSize: "100%", 11027 background: "transparent", 11028 border: "none", 11029 margin: 0, 11030 padding: 0 11031 }), 11032 // Store the size in case width/height are defined in % - Fixes #5245 11033 size = { 11034 width: element.width(), 11035 height: element.height() 11036 }, 11037 active = document.activeElement; 11038 11039 element.wrap( wrapper ); 11040 11041 // Fixes #7595 - Elements lose focus when wrapped. 11042 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { 11043 $( active ).focus(); 11044 } 11045 11046 wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually loose the reference to the wrapped element 11047 11048 // transfer positioning properties to the wrapper 11049 if ( element.css( "position" ) === "static" ) { 11050 wrapper.css({ position: "relative" }); 11051 element.css({ position: "relative" }); 11052 } else { 11053 $.extend( props, { 11054 position: element.css( "position" ), 11055 zIndex: element.css( "z-index" ) 11056 }); 11057 $.each([ "top", "left", "bottom", "right" ], function(i, pos) { 11058 props[ pos ] = element.css( pos ); 11059 if ( isNaN( parseInt( props[ pos ], 10 ) ) ) { 11060 props[ pos ] = "auto"; 11061 } 11062 }); 11063 element.css({ 11064 position: "relative", 11065 top: 0, 11066 left: 0, 11067 right: "auto", 11068 bottom: "auto" 11069 }); 11070 } 11071 element.css(size); 11072 11073 return wrapper.css( props ).show(); 11074 }, 11075 11076 removeWrapper: function( element ) { 11077 var active = document.activeElement; 11078 11079 if ( element.parent().is( ".ui-effects-wrapper" ) ) { 11080 element.parent().replaceWith( element ); 11081 11082 // Fixes #7595 - Elements lose focus when wrapped. 11083 if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) { 11084 $( active ).focus(); 11085 } 11086 } 11087 11088 11089 return element; 11090 } 11091 }); 11092 11093 // return an effect options object for the given parameters: 11094 function _normalizeArguments( effect, options, speed, callback ) { 11095 11096 // short path for passing an effect options object: 11097 if ( $.isPlainObject( effect ) ) { 11098 return effect; 11099 } 11100 11101 // convert to an object 11102 effect = { effect: effect }; 11103 11104 // catch (effect) 11105 if ( options === undefined ) { 11106 options = {}; 11107 } 11108 11109 // catch (effect, callback) 11110 if ( $.isFunction( options ) ) { 11111 callback = options; 11112 speed = null; 11113 options = {}; 11114 } 11115 11116 // catch (effect, speed, ?) 11117 if ( $.type( options ) === "number" || $.fx.speeds[ options ]) { 11118 callback = speed; 11119 speed = options; 11120 options = {}; 11121 } 11122 11123 // catch (effect, options, callback) 11124 if ( $.isFunction( speed ) ) { 11125 callback = speed; 11126 speed = null; 11127 } 11128 11129 // add options to effect 11130 if ( options ) { 11131 $.extend( effect, options ); 11132 } 11133 11134 speed = speed || options.duration; 11135 effect.duration = $.fx.off ? 0 : typeof speed === "number" 11136 ? speed : speed in $.fx.speeds ? $.fx.speeds[ speed ] : $.fx.speeds._default; 11137 11138 effect.complete = callback || options.complete; 11139 11140 return effect; 11141 } 11142 11143 function standardSpeed( speed ) { 11144 // valid standard speeds 11145 if ( !speed || typeof speed === "number" || $.fx.speeds[ speed ] ) { 11146 return true; 11147 } 11148 11149 // invalid strings - treat as "normal" speed 11150 if ( typeof speed === "string" && !$.jqplot.effects.effect[ speed ] ) { 11151 // TODO: remove in 2.0 (#7115) 11152 if ( backCompat && $.jqplot.effects[ speed ] ) { 11153 return false; 11154 } 11155 return true; 11156 } 11157 11158 return false; 11159 } 11160 11161 $.fn.extend({ 11162 jqplotEffect: function( effect, options, speed, callback ) { 11163 var args = _normalizeArguments.apply( this, arguments ), 11164 mode = args.mode, 11165 queue = args.queue, 11166 effectMethod = $.jqplot.effects.effect[ args.effect ], 11167 11168 // DEPRECATED: remove in 2.0 (#7115) 11169 oldEffectMethod = !effectMethod && backCompat && $.jqplot.effects[ args.effect ]; 11170 11171 if ( $.fx.off || !( effectMethod || oldEffectMethod ) ) { 11172 // delegate to the original method (e.g., .show()) if possible 11173 if ( mode ) { 11174 return this[ mode ]( args.duration, args.complete ); 11175 } else { 11176 return this.each( function() { 11177 if ( args.complete ) { 11178 args.complete.call( this ); 11179 } 11180 }); 11181 } 11182 } 11183 11184 function run( next ) { 11185 var elem = $( this ), 11186 complete = args.complete, 11187 mode = args.mode; 11188 11189 function done() { 11190 if ( $.isFunction( complete ) ) { 11191 complete.call( elem[0] ); 11192 } 11193 if ( $.isFunction( next ) ) { 11194 next(); 11195 } 11196 } 11197 11198 // if the element is hiddden and mode is hide, 11199 // or element is visible and mode is show 11200 if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) { 11201 done(); 11202 } else { 11203 effectMethod.call( elem[0], args, done ); 11204 } 11205 } 11206 11207 // TODO: remove this check in 2.0, effectMethod will always be true 11208 if ( effectMethod ) { 11209 return queue === false ? this.each( run ) : this.queue( queue || "fx", run ); 11210 } else { 11211 // DEPRECATED: remove in 2.0 (#7115) 11212 return oldEffectMethod.call(this, { 11213 options: args, 11214 duration: args.duration, 11215 callback: args.complete, 11216 mode: args.mode 11217 }); 11218 } 11219 } 11220 }); 11221 11222 11223 11224 11225 var rvertical = /up|down|vertical/, 11226 rpositivemotion = /up|left|vertical|horizontal/; 11227 11228 $.jqplot.effects.effect.blind = function( o, done ) { 11229 // Create element 11230 var el = $( this ), 11231 props = [ "position", "top", "bottom", "left", "right", "height", "width" ], 11232 mode = $.jqplot.effects.setMode( el, o.mode || "hide" ), 11233 direction = o.direction || "up", 11234 vertical = rvertical.test( direction ), 11235 ref = vertical ? "height" : "width", 11236 ref2 = vertical ? "top" : "left", 11237 motion = rpositivemotion.test( direction ), 11238 animation = {}, 11239 show = mode === "show", 11240 wrapper, distance, top; 11241 11242 // // if already wrapped, the wrapper's properties are my property. #6245 11243 if ( el.parent().is( ".ui-effects-wrapper" ) ) { 11244 $.jqplot.effects.save( el.parent(), props ); 11245 } else { 11246 $.jqplot.effects.save( el, props ); 11247 } 11248 el.show(); 11249 top = parseInt(el.css('top'), 10); 11250 wrapper = $.jqplot.effects.createWrapper( el ).css({ 11251 overflow: "hidden" 11252 }); 11253 11254 distance = vertical ? wrapper[ ref ]() + top : wrapper[ ref ](); 11255 11256 animation[ ref ] = show ? String(distance) : '0'; 11257 if ( !motion ) { 11258 el 11259 .css( vertical ? "bottom" : "right", 0 ) 11260 .css( vertical ? "top" : "left", "" ) 11261 .css({ position: "absolute" }); 11262 animation[ ref2 ] = show ? '0' : String(distance); 11263 } 11264 11265 // // start at 0 if we are showing 11266 if ( show ) { 11267 wrapper.css( ref, 0 ); 11268 if ( ! motion ) { 11269 wrapper.css( ref2, distance ); 11270 } 11271 } 11272 11273 // // Animate 11274 wrapper.animate( animation, { 11275 duration: o.duration, 11276 easing: o.easing, 11277 queue: false, 11278 complete: function() { 11279 if ( mode === "hide" ) { 11280 el.hide(); 11281 } 11282 $.jqplot.effects.restore( el, props ); 11283 $.jqplot.effects.removeWrapper( el ); 11284 done(); 11285 } 11286 }); 11287 11288 }; 11289 11290
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:08:37 2014 | Cross-referenced by PHPXref 0.7.1 |