vinayakb | 60c6a8e | 2011-11-08 18:12:15 +0000 | [diff] [blame] | 1 | /** |
| 2 | * Title: jqPlot Charts |
| 3 | * |
| 4 | * Pure JavaScript plotting plugin for jQuery. |
| 5 | * |
| 6 | * About: Version |
| 7 | * |
| 8 | * 1.0.0b2_r947 |
| 9 | * |
| 10 | * About: Copyright & License |
| 11 | * |
| 12 | * Copyright (c) 2009-2011 Chris Leonello |
| 13 | * jqPlot is currently available for use in all personal or commercial projects |
| 14 | * under both the MIT and GPL version 2.0 licenses. This means that you can |
| 15 | * choose the license that best suits your project and use it accordingly. |
| 16 | * |
| 17 | * See <GPL Version 2> and <MIT License> contained within this distribution for further information. |
| 18 | * |
| 19 | * The author would appreciate an email letting him know of any substantial |
| 20 | * use of jqPlot. You can reach the author at: chris at jqplot dot com |
| 21 | * or see http://www.jqplot.com/info.php. This is, of course, not required. |
| 22 | * |
| 23 | * If you are feeling kind and generous, consider supporting the project by |
| 24 | * making a donation at: http://www.jqplot.com/donate.php. |
| 25 | * |
| 26 | * sprintf functions contained in jqplot.sprintf.js by Ash Searle: |
| 27 | * |
| 28 | * version 2007.04.27 |
| 29 | * author Ash Searle |
| 30 | * http://hexmen.com/blog/2007/03/printf-sprintf/ |
| 31 | * http://hexmen.com/js/sprintf.js |
| 32 | * The author (Ash Searle) has placed this code in the public domain: |
| 33 | * "This code is unrestricted: you are free to use it however you like." |
| 34 | * |
| 35 | * |
| 36 | * About: Introduction |
| 37 | * |
| 38 | * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.2 is included in the distribution. |
| 39 | * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally |
| 40 | * the excanvas script for IE support in your web page: |
| 41 | * |
| 42 | * > <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]--> |
| 43 | * > <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script> |
| 44 | * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script> |
| 45 | * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" /> |
| 46 | * |
| 47 | * jqPlot can be customized by overriding the defaults of any of the objects which make |
| 48 | * up the plot. The general usage of jqplot is: |
| 49 | * |
| 50 | * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject}); |
| 51 | * |
| 52 | * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file. |
| 53 | * |
| 54 | * An actual call to $.jqplot() may look like the |
| 55 | * examples below: |
| 56 | * |
| 57 | * > chart = $.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]); |
| 58 | * |
| 59 | * or |
| 60 | * |
| 61 | * > dataArray = [34,12,43,55,77]; |
| 62 | * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}}); |
| 63 | * |
| 64 | * For more inforrmation, see <jqPlot Usage>. |
| 65 | * |
| 66 | * About: Usage |
| 67 | * |
| 68 | * See <jqPlot Usage> |
| 69 | * |
| 70 | * About: Available Options |
| 71 | * |
| 72 | * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!) |
| 73 | * |
| 74 | * About: Options Usage |
| 75 | * |
| 76 | * See <Options Tutorial> |
| 77 | * |
| 78 | * About: Changes |
| 79 | * |
| 80 | * See <Change Log> |
| 81 | * |
| 82 | */ |
| 83 | |
| 84 | (function($) { |
| 85 | // make sure undefined is undefined |
| 86 | var undefined; |
| 87 | |
| 88 | $.fn.emptyForce = function() { |
| 89 | for ( var i = 0, elem; (elem = $(this)[i]) != null; i++ ) { |
| 90 | // Remove element nodes and prevent memory leaks |
| 91 | if ( elem.nodeType === 1 ) { |
| 92 | jQuery.cleanData( elem.getElementsByTagName("*") ); |
| 93 | } |
| 94 | |
| 95 | // Remove any remaining nodes |
| 96 | if ($.jqplot_use_excanvas) { |
| 97 | elem.outerHTML = ""; |
| 98 | } |
| 99 | else { |
| 100 | while ( elem.firstChild ) { |
| 101 | elem.removeChild( elem.firstChild ); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | elem = null; |
| 106 | } |
| 107 | |
| 108 | return $(this); |
| 109 | }; |
| 110 | |
| 111 | $.fn.removeChildForce = function(parent) { |
| 112 | while ( parent.firstChild ) { |
| 113 | this.removeChildForce( parent.firstChild ); |
| 114 | parent.removeChild( parent.firstChild ); |
| 115 | } |
| 116 | }; |
| 117 | |
| 118 | |
| 119 | /** |
| 120 | * Namespace: $.jqplot |
| 121 | * jQuery function called by the user to create a plot. |
| 122 | * |
| 123 | * Parameters: |
| 124 | * target - ID of target element to render the plot into. |
| 125 | * data - an array of data series. |
| 126 | * options - user defined options object. See the individual classes for available options. |
| 127 | * |
| 128 | * Properties: |
| 129 | * config - object to hold configuration information for jqPlot plot object. |
| 130 | * |
| 131 | * attributes: |
| 132 | * enablePlugins - False to disable plugins by default. Plugins must then be explicitly |
| 133 | * enabled in the individual plot options. Default: false. |
| 134 | * This property sets the "show" property of certain plugins to true or false. |
| 135 | * Only plugins that can be immediately active upon loading are affected. This includes |
| 136 | * non-renderer plugins like cursor, dragable, highlighter, and trendline. |
| 137 | * defaultHeight - Default height for plots where no css height specification exists. This |
| 138 | * is a jqplot wide default. |
| 139 | * defaultWidth - Default height for plots where no css height specification exists. This |
| 140 | * is a jqplot wide default. |
| 141 | */ |
| 142 | |
| 143 | $.jqplot = function(target, data, options) { |
| 144 | var _data, _options; |
| 145 | |
| 146 | if (options == null) { |
| 147 | if (jQuery.isArray(data)) { |
| 148 | _data = data; |
| 149 | _options = null; |
| 150 | } |
| 151 | |
| 152 | else if (typeof(data) === 'object') { |
| 153 | _data = null; |
| 154 | _options = data; |
| 155 | } |
| 156 | } |
| 157 | else { |
| 158 | _data = data; |
| 159 | _options = options; |
| 160 | } |
| 161 | var plot = new jqPlot(); |
| 162 | // remove any error class that may be stuck on target. |
| 163 | $('#'+target).removeClass('jqplot-error'); |
| 164 | |
| 165 | if ($.jqplot.config.catchErrors) { |
| 166 | try { |
| 167 | plot.init(target, _data, _options); |
| 168 | plot.draw(); |
| 169 | plot.themeEngine.init.call(plot); |
| 170 | return plot; |
| 171 | } |
| 172 | catch(e) { |
| 173 | var msg = $.jqplot.config.errorMessage || e.message; |
| 174 | $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>'); |
| 175 | $('#'+target).addClass('jqplot-error'); |
| 176 | document.getElementById(target).style.background = $.jqplot.config.errorBackground; |
| 177 | document.getElementById(target).style.border = $.jqplot.config.errorBorder; |
| 178 | document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily; |
| 179 | document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize; |
| 180 | document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle; |
| 181 | document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight; |
| 182 | } |
| 183 | } |
| 184 | else { |
| 185 | plot.init(target, _data, _options); |
| 186 | plot.draw(); |
| 187 | plot.themeEngine.init.call(plot); |
| 188 | return plot; |
| 189 | } |
| 190 | }; |
| 191 | |
| 192 | $.jqplot.version = "1.0.0b2_r947"; |
| 193 | |
| 194 | // canvas manager to reuse canvases on the plot. |
| 195 | // Should help solve problem of canvases not being freed and |
| 196 | // problem of waiting forever for firefox to decide to free memory. |
| 197 | $.jqplot.CanvasManager = function() { |
| 198 | // canvases are managed globally so that they can be reused |
| 199 | // across plots after they have been freed |
| 200 | if (typeof $.jqplot.CanvasManager.canvases == 'undefined') { |
| 201 | $.jqplot.CanvasManager.canvases = []; |
| 202 | $.jqplot.CanvasManager.free = []; |
| 203 | } |
| 204 | |
| 205 | var myCanvases = []; |
| 206 | |
| 207 | this.getCanvas = function() { |
| 208 | var canvas; |
| 209 | var makeNew = true; |
| 210 | |
| 211 | if (!$.jqplot.use_excanvas) { |
| 212 | for (var i = 0, l = $.jqplot.CanvasManager.canvases.length; i < l; i++) { |
| 213 | if ($.jqplot.CanvasManager.free[i] === true) { |
| 214 | makeNew = false; |
| 215 | canvas = $.jqplot.CanvasManager.canvases[i]; |
| 216 | // $(canvas).removeClass('jqplot-canvasManager-free').addClass('jqplot-canvasManager-inuse'); |
| 217 | $.jqplot.CanvasManager.free[i] = false; |
| 218 | myCanvases.push(i); |
| 219 | break; |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | if (makeNew) { |
| 225 | canvas = document.createElement('canvas'); |
| 226 | myCanvases.push($.jqplot.CanvasManager.canvases.length); |
| 227 | $.jqplot.CanvasManager.canvases.push(canvas); |
| 228 | $.jqplot.CanvasManager.free.push(false); |
| 229 | } |
| 230 | |
| 231 | return canvas; |
| 232 | }; |
| 233 | |
| 234 | // this method has to be used after settings the dimesions |
| 235 | // on the element returned by getCanvas() |
| 236 | this.initCanvas = function(canvas) { |
| 237 | if ($.jqplot.use_excanvas) { |
| 238 | return window.G_vmlCanvasManager.initElement(canvas); |
| 239 | } |
| 240 | return canvas; |
| 241 | }; |
| 242 | |
| 243 | this.freeAllCanvases = function() { |
| 244 | for (var i = 0, l=myCanvases.length; i < l; i++) { |
| 245 | this.freeCanvas(myCanvases[i]); |
| 246 | } |
| 247 | myCanvases = []; |
| 248 | }; |
| 249 | |
| 250 | this.freeCanvas = function(idx) { |
| 251 | if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { |
| 252 | // excanvas can't be reused, but properly unset |
| 253 | window.G_vmlCanvasManager.uninitElement($.jqplot.CanvasManager.canvases[idx]); |
| 254 | $.jqplot.CanvasManager.canvases[idx] = null; |
| 255 | } |
| 256 | else { |
| 257 | var canvas = $.jqplot.CanvasManager.canvases[idx]; |
| 258 | canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); |
| 259 | $(canvas).unbind().removeAttr('class').removeAttr('style'); |
| 260 | // Style attributes seemed to be still hanging around. wierd. Some ticks |
| 261 | // still retained a left: 0px attribute after reusing a canvas. |
| 262 | $(canvas).css({left: '', top: '', position: ''}); |
| 263 | // setting size to 0 may save memory of unused canvases? |
| 264 | canvas.width = 0; |
| 265 | canvas.height = 0; |
| 266 | $.jqplot.CanvasManager.free[idx] = true; |
| 267 | } |
| 268 | }; |
| 269 | |
| 270 | }; |
| 271 | |
| 272 | |
| 273 | // Convienence function that won't hang IE or FF without FireBug. |
| 274 | $.jqplot.log = function() { |
| 275 | if (window.console) { |
| 276 | window.console.log.apply(window.console, arguments); |
| 277 | } |
| 278 | }; |
| 279 | |
| 280 | $.jqplot.config = { |
| 281 | enablePlugins:false, |
| 282 | defaultHeight:300, |
| 283 | defaultWidth:400, |
| 284 | UTCAdjust:false, |
| 285 | timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000), |
| 286 | errorMessage: '', |
| 287 | errorBackground: '', |
| 288 | errorBorder: '', |
| 289 | errorFontFamily: '', |
| 290 | errorFontSize: '', |
| 291 | errorFontStyle: '', |
| 292 | errorFontWeight: '', |
| 293 | catchErrors: false, |
| 294 | defaultTickFormatString: "%.1f", |
| 295 | defaultColors: [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], |
| 296 | defaultNegativeColors: [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"], |
| 297 | dashLength: 4, |
| 298 | gapLength: 4, |
| 299 | dotGapLength: 2.5, |
| 300 | srcLocation: 'jqplot/src/', |
| 301 | pluginLocation: 'jqplot/src/plugins/' |
| 302 | }; |
| 303 | |
| 304 | |
| 305 | $.jqplot.arrayMax = function( array ){ |
| 306 | return Math.max.apply( Math, array ); |
| 307 | }; |
| 308 | |
| 309 | $.jqplot.arrayMin = function( array ){ |
| 310 | return Math.min.apply( Math, array ); |
| 311 | }; |
| 312 | |
| 313 | $.jqplot.enablePlugins = $.jqplot.config.enablePlugins; |
| 314 | |
| 315 | // canvas related tests taken from modernizer: |
| 316 | // Copyright (c) 2009 - 2010 Faruk Ates. |
| 317 | // http://www.modernizr.com |
| 318 | |
| 319 | $.jqplot.support_canvas = function() { |
| 320 | if (typeof $.jqplot.support_canvas.result == 'undefined') { |
| 321 | $.jqplot.support_canvas.result = !!document.createElement('canvas').getContext; |
| 322 | } |
| 323 | return $.jqplot.support_canvas.result; |
| 324 | }; |
| 325 | |
| 326 | $.jqplot.support_canvas_text = function() { |
| 327 | if (typeof $.jqplot.support_canvas_text.result == 'undefined') { |
| 328 | if (window.G_vmlCanvasManager !== undefined && window.G_vmlCanvasManager._version > 887) { |
| 329 | $.jqplot.support_canvas_text.result = true; |
| 330 | } |
| 331 | else { |
| 332 | $.jqplot.support_canvas_text.result = !!(document.createElement('canvas').getContext && typeof document.createElement('canvas').getContext('2d').fillText == 'function'); |
| 333 | } |
| 334 | |
| 335 | } |
| 336 | return $.jqplot.support_canvas_text.result; |
| 337 | }; |
| 338 | |
| 339 | $.jqplot.use_excanvas = ($.browser.msie && !$.jqplot.support_canvas()) ? true : false; |
| 340 | |
| 341 | /** |
| 342 | * |
| 343 | * Hooks: jqPlot Pugin Hooks |
| 344 | * |
| 345 | * $.jqplot.preInitHooks - called before initialization. |
| 346 | * $.jqplot.postInitHooks - called after initialization. |
| 347 | * $.jqplot.preParseOptionsHooks - called before user options are parsed. |
| 348 | * $.jqplot.postParseOptionsHooks - called after user options are parsed. |
| 349 | * $.jqplot.preDrawHooks - called before plot draw. |
| 350 | * $.jqplot.postDrawHooks - called after plot draw. |
| 351 | * $.jqplot.preDrawSeriesHooks - called before each series is drawn. |
| 352 | * $.jqplot.postDrawSeriesHooks - called after each series is drawn. |
| 353 | * $.jqplot.preDrawLegendHooks - called before the legend is drawn. |
| 354 | * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins |
| 355 | * can add rows to the legend table. |
| 356 | * $.jqplot.preSeriesInitHooks - called before series is initialized. |
| 357 | * $.jqplot.postSeriesInitHooks - called after series is initialized. |
| 358 | * $.jqplot.preParseSeriesOptionsHooks - called before series related options |
| 359 | * are parsed. |
| 360 | * $.jqplot.postParseSeriesOptionsHooks - called after series related options |
| 361 | * are parsed. |
| 362 | * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds |
| 363 | * listeners to the event canvas which lays on top of the grid area. |
| 364 | * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn. |
| 365 | * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn. |
| 366 | * |
| 367 | */ |
| 368 | |
| 369 | $.jqplot.preInitHooks = []; |
| 370 | $.jqplot.postInitHooks = []; |
| 371 | $.jqplot.preParseOptionsHooks = []; |
| 372 | $.jqplot.postParseOptionsHooks = []; |
| 373 | $.jqplot.preDrawHooks = []; |
| 374 | $.jqplot.postDrawHooks = []; |
| 375 | $.jqplot.preDrawSeriesHooks = []; |
| 376 | $.jqplot.postDrawSeriesHooks = []; |
| 377 | $.jqplot.preDrawLegendHooks = []; |
| 378 | $.jqplot.addLegendRowHooks = []; |
| 379 | $.jqplot.preSeriesInitHooks = []; |
| 380 | $.jqplot.postSeriesInitHooks = []; |
| 381 | $.jqplot.preParseSeriesOptionsHooks = []; |
| 382 | $.jqplot.postParseSeriesOptionsHooks = []; |
| 383 | $.jqplot.eventListenerHooks = []; |
| 384 | $.jqplot.preDrawSeriesShadowHooks = []; |
| 385 | $.jqplot.postDrawSeriesShadowHooks = []; |
| 386 | |
| 387 | // A superclass holding some common properties and methods. |
| 388 | $.jqplot.ElemContainer = function() { |
| 389 | this._elem; |
| 390 | this._plotWidth; |
| 391 | this._plotHeight; |
| 392 | this._plotDimensions = {height:null, width:null}; |
| 393 | }; |
| 394 | |
| 395 | $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) { |
| 396 | this._offsets = offsets; |
| 397 | var klass = clss || 'jqplot'; |
| 398 | var elem = document.createElement(el); |
| 399 | this._elem = $(elem); |
| 400 | this._elem.addClass(klass); |
| 401 | this._elem.css(cssopts); |
| 402 | this._elem.attr(attrib); |
| 403 | // avoid memory leak; |
| 404 | elem = null; |
| 405 | return this._elem; |
| 406 | }; |
| 407 | |
| 408 | $.jqplot.ElemContainer.prototype.getWidth = function() { |
| 409 | if (this._elem) { |
| 410 | return this._elem.outerWidth(true); |
| 411 | } |
| 412 | else { |
| 413 | return null; |
| 414 | } |
| 415 | }; |
| 416 | |
| 417 | $.jqplot.ElemContainer.prototype.getHeight = function() { |
| 418 | if (this._elem) { |
| 419 | return this._elem.outerHeight(true); |
| 420 | } |
| 421 | else { |
| 422 | return null; |
| 423 | } |
| 424 | }; |
| 425 | |
| 426 | $.jqplot.ElemContainer.prototype.getPosition = function() { |
| 427 | if (this._elem) { |
| 428 | return this._elem.position(); |
| 429 | } |
| 430 | else { |
| 431 | return {top:null, left:null, bottom:null, right:null}; |
| 432 | } |
| 433 | }; |
| 434 | |
| 435 | $.jqplot.ElemContainer.prototype.getTop = function() { |
| 436 | return this.getPosition().top; |
| 437 | }; |
| 438 | |
| 439 | $.jqplot.ElemContainer.prototype.getLeft = function() { |
| 440 | return this.getPosition().left; |
| 441 | }; |
| 442 | |
| 443 | $.jqplot.ElemContainer.prototype.getBottom = function() { |
| 444 | return this._elem.css('bottom'); |
| 445 | }; |
| 446 | |
| 447 | $.jqplot.ElemContainer.prototype.getRight = function() { |
| 448 | return this._elem.css('right'); |
| 449 | }; |
| 450 | |
| 451 | |
| 452 | /** |
| 453 | * Class: Axis |
| 454 | * An individual axis object. Cannot be instantiated directly, but created |
| 455 | * by the Plot oject. Axis properties can be set or overriden by the |
| 456 | * options passed in from the user. |
| 457 | * |
| 458 | */ |
| 459 | function Axis(name) { |
| 460 | $.jqplot.ElemContainer.call(this); |
| 461 | // Group: Properties |
| 462 | // |
| 463 | // Axes options are specified within an axes object at the top level of the |
| 464 | // plot options like so: |
| 465 | // > { |
| 466 | // > axes: { |
| 467 | // > xaxis: {min: 5}, |
| 468 | // > yaxis: {min: 2, max: 8, numberTicks:4}, |
| 469 | // > x2axis: {pad: 1.5}, |
| 470 | // > y2axis: {ticks:[22, 44, 66, 88]} |
| 471 | // > } |
| 472 | // > } |
| 473 | // There are 2 x axes, 'xaxis' and 'x2axis', and |
| 474 | // 9 yaxes, 'yaxis', 'y2axis'. 'y3axis', ... Any or all of which may be specified. |
| 475 | this.name = name; |
| 476 | this._series = []; |
| 477 | // prop: show |
| 478 | // Wether to display the axis on the graph. |
| 479 | this.show = false; |
| 480 | // prop: tickRenderer |
| 481 | // A class of a rendering engine for creating the ticks labels displayed on the plot, |
| 482 | // See <$.jqplot.AxisTickRenderer>. |
| 483 | this.tickRenderer = $.jqplot.AxisTickRenderer; |
| 484 | // prop: tickOptions |
| 485 | // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options. |
| 486 | this.tickOptions = {}; |
| 487 | // prop: labelRenderer |
| 488 | // A class of a rendering engine for creating an axis label. |
| 489 | this.labelRenderer = $.jqplot.AxisLabelRenderer; |
| 490 | // prop: labelOptions |
| 491 | // Options passed to the label renderer. |
| 492 | this.labelOptions = {}; |
| 493 | // prop: label |
| 494 | // Label for the axis |
| 495 | this.label = null; |
| 496 | // prop: showLabel |
| 497 | // true to show the axis label. |
| 498 | this.showLabel = true; |
| 499 | // prop: min |
| 500 | // minimum value of the axis (in data units, not pixels). |
| 501 | this.min = null; |
| 502 | // prop: max |
| 503 | // maximum value of the axis (in data units, not pixels). |
| 504 | this.max = null; |
| 505 | // prop: autoscale |
| 506 | // DEPRECATED |
| 507 | // the default scaling algorithm produces superior results. |
| 508 | this.autoscale = false; |
| 509 | // prop: pad |
| 510 | // Padding to extend the range above and below the data bounds. |
| 511 | // The data range is multiplied by this factor to determine minimum and maximum axis bounds. |
| 512 | // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0. |
| 513 | this.pad = 1.2; |
| 514 | // prop: padMax |
| 515 | // Padding to extend the range above data bounds. |
| 516 | // The top of the data range is multiplied by this factor to determine maximum axis bounds. |
| 517 | // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0. |
| 518 | this.padMax = null; |
| 519 | // prop: padMin |
| 520 | // Padding to extend the range below data bounds. |
| 521 | // The bottom of the data range is multiplied by this factor to determine minimum axis bounds. |
| 522 | // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0. |
| 523 | this.padMin = null; |
| 524 | // prop: ticks |
| 525 | // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis. |
| 526 | // If no label is specified, the value is formatted into an appropriate label. |
| 527 | this.ticks = []; |
| 528 | // prop: numberTicks |
| 529 | // Desired number of ticks. Default is to compute automatically. |
| 530 | this.numberTicks; |
| 531 | // prop: tickInterval |
| 532 | // number of units between ticks. Mutually exclusive with numberTicks. |
| 533 | this.tickInterval; |
| 534 | // prop: renderer |
| 535 | // A class of a rendering engine that handles tick generation, |
| 536 | // scaling input data to pixel grid units and drawing the axis element. |
| 537 | this.renderer = $.jqplot.LinearAxisRenderer; |
| 538 | // prop: rendererOptions |
| 539 | // renderer specific options. See <$.jqplot.LinearAxisRenderer> for options. |
| 540 | this.rendererOptions = {}; |
| 541 | // prop: showTicks |
| 542 | // Wether to show the ticks (both marks and labels) or not. |
| 543 | // Will not override showMark and showLabel options if specified on the ticks themselves. |
| 544 | this.showTicks = true; |
| 545 | // prop: showTickMarks |
| 546 | // Wether to show the tick marks (line crossing grid) or not. |
| 547 | // Overridden by showTicks and showMark option of tick itself. |
| 548 | this.showTickMarks = true; |
| 549 | // prop: showMinorTicks |
| 550 | // Wether or not to show minor ticks. This is renderer dependent. |
| 551 | this.showMinorTicks = true; |
| 552 | // prop: drawMajorGridlines |
| 553 | // True to draw gridlines for major axis ticks. |
| 554 | this.drawMajorGridlines = true; |
| 555 | // prop: drawMinorGridlines |
| 556 | // True to draw gridlines for minor ticks. |
| 557 | this.drawMinorGridlines = false; |
| 558 | // prop: drawMajorTickMarks |
| 559 | // True to draw tick marks for major axis ticks. |
| 560 | this.drawMajorTickMarks = true; |
| 561 | // prop: drawMinorTickMarks |
| 562 | // True to draw tick marks for minor ticks. This is renderer dependent. |
| 563 | this.drawMinorTickMarks = true; |
| 564 | // prop: useSeriesColor |
| 565 | // Use the color of the first series associated with this axis for the |
| 566 | // tick marks and line bordering this axis. |
| 567 | this.useSeriesColor = false; |
| 568 | // prop: borderWidth |
| 569 | // width of line stroked at the border of the axis. Defaults |
| 570 | // to the width of the grid boarder. |
| 571 | this.borderWidth = null; |
| 572 | // prop: borderColor |
| 573 | // color of the border adjacent to the axis. Defaults to grid border color. |
| 574 | this.borderColor = null; |
| 575 | // minimum and maximum values on the axis. |
| 576 | this._dataBounds = {min:null, max:null}; |
| 577 | // statistics (min, max, mean) as well as actual data intervals for each series attached to axis. |
| 578 | // holds collection of {intervals:[], min:, max:, mean: } objects for each series on axis. |
| 579 | this._intervalStats = []; |
| 580 | // pixel position from the top left of the min value and max value on the axis. |
| 581 | this._offsets = {min:null, max:null}; |
| 582 | this._ticks=[]; |
| 583 | this._label = null; |
| 584 | // prop: syncTicks |
| 585 | // true to try and synchronize tick spacing across multiple axes so that ticks and |
| 586 | // grid lines line up. This has an impact on autoscaling algorithm, however. |
| 587 | // In general, autoscaling an individual axis will work better if it does not |
| 588 | // have to sync ticks. |
| 589 | this.syncTicks = null; |
| 590 | // prop: tickSpacing |
| 591 | // Approximate pixel spacing between ticks on graph. Used during autoscaling. |
| 592 | // This number will be an upper bound, actual spacing will be less. |
| 593 | this.tickSpacing = 75; |
| 594 | // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks |
| 595 | // so they can be restored if altered by plugins. |
| 596 | this._min = null; |
| 597 | this._max = null; |
| 598 | this._tickInterval = null; |
| 599 | this._numberTicks = null; |
| 600 | this.__ticks = null; |
| 601 | // hold original user options. |
| 602 | this._options = {}; |
| 603 | } |
| 604 | |
| 605 | Axis.prototype = new $.jqplot.ElemContainer(); |
| 606 | Axis.prototype.constructor = Axis; |
| 607 | |
| 608 | Axis.prototype.init = function() { |
| 609 | this.renderer = new this.renderer(); |
| 610 | // set the axis name |
| 611 | this.tickOptions.axis = this.name; |
| 612 | // if showMark or showLabel tick options not specified, use value of axis option. |
| 613 | // showTicks overrides showTickMarks. |
| 614 | if (this.tickOptions.showMark == null) { |
| 615 | this.tickOptions.showMark = this.showTicks; |
| 616 | } |
| 617 | if (this.tickOptions.showMark == null) { |
| 618 | this.tickOptions.showMark = this.showTickMarks; |
| 619 | } |
| 620 | if (this.tickOptions.showLabel == null) { |
| 621 | this.tickOptions.showLabel = this.showTicks; |
| 622 | } |
| 623 | |
| 624 | if (this.label == null || this.label == '') { |
| 625 | this.showLabel = false; |
| 626 | } |
| 627 | else { |
| 628 | this.labelOptions.label = this.label; |
| 629 | } |
| 630 | if (this.showLabel == false) { |
| 631 | this.labelOptions.show = false; |
| 632 | } |
| 633 | // set the default padMax, padMin if not specified |
| 634 | // special check, if no padding desired, padding |
| 635 | // should be set to 1.0 |
| 636 | if (this.pad == 0) { |
| 637 | this.pad = 1.0; |
| 638 | } |
| 639 | if (this.padMax == 0) { |
| 640 | this.padMax = 1.0; |
| 641 | } |
| 642 | if (this.padMin == 0) { |
| 643 | this.padMin = 1.0; |
| 644 | } |
| 645 | if (this.padMax == null) { |
| 646 | this.padMax = (this.pad-1)/2 + 1; |
| 647 | } |
| 648 | if (this.padMin == null) { |
| 649 | this.padMin = (this.pad-1)/2 + 1; |
| 650 | } |
| 651 | // now that padMin and padMax are correctly set, reset pad in case user has supplied |
| 652 | // padMin and/or padMax |
| 653 | this.pad = this.padMax + this.padMin - 1; |
| 654 | if (this.min != null || this.max != null) { |
| 655 | this.autoscale = false; |
| 656 | } |
| 657 | // if not set, sync ticks for y axes but not x by default. |
| 658 | if (this.syncTicks == null && this.name.indexOf('y') > -1) { |
| 659 | this.syncTicks = true; |
| 660 | } |
| 661 | else if (this.syncTicks == null){ |
| 662 | this.syncTicks = false; |
| 663 | } |
| 664 | this.renderer.init.call(this, this.rendererOptions); |
| 665 | |
| 666 | }; |
| 667 | |
| 668 | Axis.prototype.draw = function(ctx, plot) { |
| 669 | // Memory Leaks patch |
| 670 | if (this.__ticks) { |
| 671 | this.__ticks = null; |
| 672 | } |
| 673 | |
| 674 | return this.renderer.draw.call(this, ctx, plot); |
| 675 | |
| 676 | }; |
| 677 | |
| 678 | Axis.prototype.set = function() { |
| 679 | this.renderer.set.call(this); |
| 680 | }; |
| 681 | |
| 682 | Axis.prototype.pack = function(pos, offsets) { |
| 683 | if (this.show) { |
| 684 | this.renderer.pack.call(this, pos, offsets); |
| 685 | } |
| 686 | // these properties should all be available now. |
| 687 | if (this._min == null) { |
| 688 | this._min = this.min; |
| 689 | this._max = this.max; |
| 690 | this._tickInterval = this.tickInterval; |
| 691 | this._numberTicks = this.numberTicks; |
| 692 | this.__ticks = this._ticks; |
| 693 | } |
| 694 | }; |
| 695 | |
| 696 | // reset the axis back to original values if it has been scaled, zoomed, etc. |
| 697 | Axis.prototype.reset = function() { |
| 698 | this.renderer.reset.call(this); |
| 699 | }; |
| 700 | |
| 701 | Axis.prototype.resetScale = function(opts) { |
| 702 | $.extend(true, this, {min: null, max: null, numberTicks: null, tickInterval: null, _ticks: [], ticks: []}, opts); |
| 703 | this.resetDataBounds(); |
| 704 | }; |
| 705 | |
| 706 | Axis.prototype.resetDataBounds = function() { |
| 707 | // Go through all the series attached to this axis and find |
| 708 | // the min/max bounds for this axis. |
| 709 | var db = this._dataBounds; |
| 710 | db.min = null; |
| 711 | db.max = null; |
| 712 | var l, s, d; |
| 713 | // check for when to force min 0 on bar series plots. |
| 714 | var doforce = (this.show) ? true : false; |
| 715 | for (var i=0; i<this._series.length; i++) { |
| 716 | s = this._series[i]; |
| 717 | d = s._plotData; |
| 718 | if (s._type === 'line' && s.renderer.bands.show && this.name.charAt(0) !== 'x') { |
| 719 | d = [[0, s.renderer.bands._min], [1, s.renderer.bands._max]]; |
| 720 | } |
| 721 | |
| 722 | var minyidx = 1, maxyidx = 1; |
| 723 | |
| 724 | if (s._type != null && s._type == 'ohlc') { |
| 725 | minyidx = 3; |
| 726 | maxyidx = 2; |
| 727 | } |
| 728 | |
| 729 | for (var j=0, l=d.length; j<l; j++) { |
| 730 | if (this.name == 'xaxis' || this.name == 'x2axis') { |
| 731 | if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) { |
| 732 | db.min = d[j][0]; |
| 733 | } |
| 734 | if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) { |
| 735 | db.max = d[j][0]; |
| 736 | } |
| 737 | } |
| 738 | else { |
| 739 | if ((d[j][minyidx] != null && d[j][minyidx] < db.min) || db.min == null) { |
| 740 | db.min = d[j][minyidx]; |
| 741 | } |
| 742 | if ((d[j][maxyidx] != null && d[j][maxyidx] > db.max) || db.max == null) { |
| 743 | db.max = d[j][maxyidx]; |
| 744 | } |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | // Hack to not pad out bottom of bar plots unless user has specified a padding. |
| 749 | // every series will have a chance to set doforce to false. once it is set to |
| 750 | // false, it cannot be reset to true. |
| 751 | // If any series attached to axis is not a bar, wont force 0. |
| 752 | if (doforce && s.renderer.constructor !== $.jqplot.BarRenderer) { |
| 753 | doforce = false; |
| 754 | } |
| 755 | |
| 756 | else if (doforce && this._options.hasOwnProperty('forceTickAt0') && this._options.forceTickAt0 == false) { |
| 757 | doforce = false; |
| 758 | } |
| 759 | |
| 760 | else if (doforce && s.renderer.constructor === $.jqplot.BarRenderer) { |
| 761 | if (s.barDirection == 'vertical' && this.name != 'xaxis' && this.name != 'x2axis') { |
| 762 | if (this._options.pad != null || this._options.padMin != null) { |
| 763 | doforce = false; |
| 764 | } |
| 765 | } |
| 766 | |
| 767 | else if (s.barDirection == 'horizontal' && (this.name == 'xaxis' || this.name == 'x2axis')) { |
| 768 | if (this._options.pad != null || this._options.padMin != null) { |
| 769 | doforce = false; |
| 770 | } |
| 771 | } |
| 772 | |
| 773 | } |
| 774 | } |
| 775 | |
| 776 | if (doforce && this.renderer.constructor === $.jqplot.LinearAxisRenderer && db.min >= 0) { |
| 777 | this.padMin = 1.0; |
| 778 | this.forceTickAt0 = true; |
| 779 | } |
| 780 | }; |
| 781 | |
| 782 | /** |
| 783 | * Class: Legend |
| 784 | * Legend object. Cannot be instantiated directly, but created |
| 785 | * by the Plot oject. Legend properties can be set or overriden by the |
| 786 | * options passed in from the user. |
| 787 | */ |
| 788 | function Legend(options) { |
| 789 | $.jqplot.ElemContainer.call(this); |
| 790 | // Group: Properties |
| 791 | |
| 792 | // prop: show |
| 793 | // Wether to display the legend on the graph. |
| 794 | this.show = false; |
| 795 | // prop: location |
| 796 | // Placement of the legend. one of the compass directions: nw, n, ne, e, se, s, sw, w |
| 797 | this.location = 'ne'; |
| 798 | // prop: labels |
| 799 | // Array of labels to use. By default the renderer will look for labels on the series. |
| 800 | // Labels specified in this array will override labels specified on the series. |
| 801 | this.labels = []; |
| 802 | // prop: showLabels |
| 803 | // true to show the label text on the legend. |
| 804 | this.showLabels = true; |
| 805 | // prop: showSwatch |
| 806 | // true to show the color swatches on the legend. |
| 807 | this.showSwatches = true; |
| 808 | // prop: placement |
| 809 | // "insideGrid" places legend inside the grid area of the plot. |
| 810 | // "outsideGrid" places the legend outside the grid but inside the plot container, |
| 811 | // shrinking the grid to accomodate the legend. |
| 812 | // "inside" synonym for "insideGrid", |
| 813 | // "outside" places the legend ouside the grid area, but does not shrink the grid which |
| 814 | // can cause the legend to overflow the plot container. |
| 815 | this.placement = "insideGrid"; |
| 816 | // prop: xoffset |
| 817 | // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc. |
| 818 | // properties or via CSS margin styling of the .jqplot-table-legend class. |
| 819 | this.xoffset = 0; |
| 820 | // prop: yoffset |
| 821 | // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc. |
| 822 | // properties or via CSS margin styling of the .jqplot-table-legend class. |
| 823 | this.yoffset = 0; |
| 824 | // prop: border |
| 825 | // css spec for the border around the legend box. |
| 826 | this.border; |
| 827 | // prop: background |
| 828 | // css spec for the background of the legend box. |
| 829 | this.background; |
| 830 | // prop: textColor |
| 831 | // css color spec for the legend text. |
| 832 | this.textColor; |
| 833 | // prop: fontFamily |
| 834 | // css font-family spec for the legend text. |
| 835 | this.fontFamily; |
| 836 | // prop: fontSize |
| 837 | // css font-size spec for the legend text. |
| 838 | this.fontSize ; |
| 839 | // prop: rowSpacing |
| 840 | // css padding-top spec for the rows in the legend. |
| 841 | this.rowSpacing = '0.5em'; |
| 842 | // renderer |
| 843 | // A class that will create a DOM object for the legend, |
| 844 | // see <$.jqplot.TableLegendRenderer>. |
| 845 | this.renderer = $.jqplot.TableLegendRenderer; |
| 846 | // prop: rendererOptions |
| 847 | // renderer specific options passed to the renderer. |
| 848 | this.rendererOptions = {}; |
| 849 | // prop: predraw |
| 850 | // Wether to draw the legend before the series or not. |
| 851 | // Used with series specific legend renderers for pie, donut, mekko charts, etc. |
| 852 | this.preDraw = false; |
| 853 | // prop: marginTop |
| 854 | // CSS margin for the legend DOM element. This will set an element |
| 855 | // CSS style for the margin which will override any style sheet setting. |
| 856 | // The default will be taken from the stylesheet. |
| 857 | this.marginTop = null; |
| 858 | // prop: marginRight |
| 859 | // CSS margin for the legend DOM element. This will set an element |
| 860 | // CSS style for the margin which will override any style sheet setting. |
| 861 | // The default will be taken from the stylesheet. |
| 862 | this.marginRight = null; |
| 863 | // prop: marginBottom |
| 864 | // CSS margin for the legend DOM element. This will set an element |
| 865 | // CSS style for the margin which will override any style sheet setting. |
| 866 | // The default will be taken from the stylesheet. |
| 867 | this.marginBottom = null; |
| 868 | // prop: marginLeft |
| 869 | // CSS margin for the legend DOM element. This will set an element |
| 870 | // CSS style for the margin which will override any style sheet setting. |
| 871 | // The default will be taken from the stylesheet. |
| 872 | this.marginLeft = null; |
| 873 | // prop: escapeHtml |
| 874 | // True to escape special characters with their html entity equivalents |
| 875 | // in legend text. "<" becomes < and so on, so html tags are not rendered. |
| 876 | this.escapeHtml = false; |
| 877 | this._series = []; |
| 878 | |
| 879 | $.extend(true, this, options); |
| 880 | } |
| 881 | |
| 882 | Legend.prototype = new $.jqplot.ElemContainer(); |
| 883 | Legend.prototype.constructor = Legend; |
| 884 | |
| 885 | Legend.prototype.setOptions = function(options) { |
| 886 | $.extend(true, this, options); |
| 887 | |
| 888 | // Try to emulate deprecated behaviour |
| 889 | // if user has specified xoffset or yoffset, copy these to |
| 890 | // the margin properties. |
| 891 | |
| 892 | if (this.placement == 'inside') { |
| 893 | this.placement = 'insideGrid'; |
| 894 | } |
| 895 | |
| 896 | if (this.xoffset >0) { |
| 897 | if (this.placement == 'insideGrid') { |
| 898 | switch (this.location) { |
| 899 | case 'nw': |
| 900 | case 'w': |
| 901 | case 'sw': |
| 902 | if (this.marginLeft == null) { |
| 903 | this.marginLeft = this.xoffset + 'px'; |
| 904 | } |
| 905 | this.marginRight = '0px'; |
| 906 | break; |
| 907 | case 'ne': |
| 908 | case 'e': |
| 909 | case 'se': |
| 910 | default: |
| 911 | if (this.marginRight == null) { |
| 912 | this.marginRight = this.xoffset + 'px'; |
| 913 | } |
| 914 | this.marginLeft = '0px'; |
| 915 | break; |
| 916 | } |
| 917 | } |
| 918 | else if (this.placement == 'outside') { |
| 919 | switch (this.location) { |
| 920 | case 'nw': |
| 921 | case 'w': |
| 922 | case 'sw': |
| 923 | if (this.marginRight == null) { |
| 924 | this.marginRight = this.xoffset + 'px'; |
| 925 | } |
| 926 | this.marginLeft = '0px'; |
| 927 | break; |
| 928 | case 'ne': |
| 929 | case 'e': |
| 930 | case 'se': |
| 931 | default: |
| 932 | if (this.marginLeft == null) { |
| 933 | this.marginLeft = this.xoffset + 'px'; |
| 934 | } |
| 935 | this.marginRight = '0px'; |
| 936 | break; |
| 937 | } |
| 938 | } |
| 939 | this.xoffset = 0; |
| 940 | } |
| 941 | |
| 942 | if (this.yoffset >0) { |
| 943 | if (this.placement == 'outside') { |
| 944 | switch (this.location) { |
| 945 | case 'sw': |
| 946 | case 's': |
| 947 | case 'se': |
| 948 | if (this.marginTop == null) { |
| 949 | this.marginTop = this.yoffset + 'px'; |
| 950 | } |
| 951 | this.marginBottom = '0px'; |
| 952 | break; |
| 953 | case 'ne': |
| 954 | case 'n': |
| 955 | case 'nw': |
| 956 | default: |
| 957 | if (this.marginBottom == null) { |
| 958 | this.marginBottom = this.yoffset + 'px'; |
| 959 | } |
| 960 | this.marginTop = '0px'; |
| 961 | break; |
| 962 | } |
| 963 | } |
| 964 | else if (this.placement == 'insideGrid') { |
| 965 | switch (this.location) { |
| 966 | case 'sw': |
| 967 | case 's': |
| 968 | case 'se': |
| 969 | if (this.marginBottom == null) { |
| 970 | this.marginBottom = this.yoffset + 'px'; |
| 971 | } |
| 972 | this.marginTop = '0px'; |
| 973 | break; |
| 974 | case 'ne': |
| 975 | case 'n': |
| 976 | case 'nw': |
| 977 | default: |
| 978 | if (this.marginTop == null) { |
| 979 | this.marginTop = this.yoffset + 'px'; |
| 980 | } |
| 981 | this.marginBottom = '0px'; |
| 982 | break; |
| 983 | } |
| 984 | } |
| 985 | this.yoffset = 0; |
| 986 | } |
| 987 | |
| 988 | // TO-DO: |
| 989 | // Handle case where offsets are < 0. |
| 990 | // |
| 991 | }; |
| 992 | |
| 993 | Legend.prototype.init = function() { |
| 994 | this.renderer = new this.renderer(); |
| 995 | this.renderer.init.call(this, this.rendererOptions); |
| 996 | }; |
| 997 | |
| 998 | Legend.prototype.draw = function(offsets) { |
| 999 | for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){ |
| 1000 | $.jqplot.preDrawLegendHooks[i].call(this, offsets); |
| 1001 | } |
| 1002 | return this.renderer.draw.call(this, offsets); |
| 1003 | }; |
| 1004 | |
| 1005 | Legend.prototype.pack = function(offsets) { |
| 1006 | this.renderer.pack.call(this, offsets); |
| 1007 | }; |
| 1008 | |
| 1009 | /** |
| 1010 | * Class: Title |
| 1011 | * Plot Title object. Cannot be instantiated directly, but created |
| 1012 | * by the Plot oject. Title properties can be set or overriden by the |
| 1013 | * options passed in from the user. |
| 1014 | * |
| 1015 | * Parameters: |
| 1016 | * text - text of the title. |
| 1017 | */ |
| 1018 | function Title(text) { |
| 1019 | $.jqplot.ElemContainer.call(this); |
| 1020 | // Group: Properties |
| 1021 | |
| 1022 | // prop: text |
| 1023 | // text of the title; |
| 1024 | this.text = text; |
| 1025 | // prop: show |
| 1026 | // wether or not to show the title |
| 1027 | this.show = true; |
| 1028 | // prop: fontFamily |
| 1029 | // css font-family spec for the text. |
| 1030 | this.fontFamily; |
| 1031 | // prop: fontSize |
| 1032 | // css font-size spec for the text. |
| 1033 | this.fontSize ; |
| 1034 | // prop: textAlign |
| 1035 | // css text-align spec for the text. |
| 1036 | this.textAlign; |
| 1037 | // prop: textColor |
| 1038 | // css color spec for the text. |
| 1039 | this.textColor; |
| 1040 | // prop: renderer |
| 1041 | // A class for creating a DOM element for the title, |
| 1042 | // see <$.jqplot.DivTitleRenderer>. |
| 1043 | this.renderer = $.jqplot.DivTitleRenderer; |
| 1044 | // prop: rendererOptions |
| 1045 | // renderer specific options passed to the renderer. |
| 1046 | this.rendererOptions = {}; |
| 1047 | // prop: escapeHtml |
| 1048 | // True to escape special characters with their html entity equivalents |
| 1049 | // in title text. "<" becomes < and so on, so html tags are not rendered. |
| 1050 | this.escapeHtml = false; |
| 1051 | } |
| 1052 | |
| 1053 | Title.prototype = new $.jqplot.ElemContainer(); |
| 1054 | Title.prototype.constructor = Title; |
| 1055 | |
| 1056 | Title.prototype.init = function() { |
| 1057 | this.renderer = new this.renderer(); |
| 1058 | this.renderer.init.call(this, this.rendererOptions); |
| 1059 | }; |
| 1060 | |
| 1061 | Title.prototype.draw = function(width) { |
| 1062 | return this.renderer.draw.call(this, width); |
| 1063 | }; |
| 1064 | |
| 1065 | Title.prototype.pack = function() { |
| 1066 | this.renderer.pack.call(this); |
| 1067 | }; |
| 1068 | |
| 1069 | |
| 1070 | /** |
| 1071 | * Class: Series |
| 1072 | * An individual data series object. Cannot be instantiated directly, but created |
| 1073 | * by the Plot oject. Series properties can be set or overriden by the |
| 1074 | * options passed in from the user. |
| 1075 | */ |
| 1076 | function Series() { |
| 1077 | $.jqplot.ElemContainer.call(this); |
| 1078 | // Group: Properties |
| 1079 | // Properties will be assigned from a series array at the top level of the |
| 1080 | // options. If you had two series and wanted to change the color and line |
| 1081 | // width of the first and set the second to use the secondary y axis with |
| 1082 | // no shadow and supply custom labels for each: |
| 1083 | // > { |
| 1084 | // > series:[ |
| 1085 | // > {color: '#ff4466', lineWidth: 5, label:'good line'}, |
| 1086 | // > {yaxis: 'y2axis', shadow: false, label:'bad line'} |
| 1087 | // > ] |
| 1088 | // > } |
| 1089 | |
| 1090 | // prop: show |
| 1091 | // wether or not to draw the series. |
| 1092 | this.show = true; |
| 1093 | // prop: xaxis |
| 1094 | // which x axis to use with this series, either 'xaxis' or 'x2axis'. |
| 1095 | this.xaxis = 'xaxis'; |
| 1096 | this._xaxis; |
| 1097 | // prop: yaxis |
| 1098 | // which y axis to use with this series, either 'yaxis' or 'y2axis'. |
| 1099 | this.yaxis = 'yaxis'; |
| 1100 | this._yaxis; |
| 1101 | this.gridBorderWidth = 2.0; |
| 1102 | // prop: renderer |
| 1103 | // A class of a renderer which will draw the series, |
| 1104 | // see <$.jqplot.LineRenderer>. |
| 1105 | this.renderer = $.jqplot.LineRenderer; |
| 1106 | // prop: rendererOptions |
| 1107 | // Options to pass on to the renderer. |
| 1108 | this.rendererOptions = {}; |
| 1109 | this.data = []; |
| 1110 | this.gridData = []; |
| 1111 | // prop: label |
| 1112 | // Line label to use in the legend. |
| 1113 | this.label = ''; |
| 1114 | // prop: showLabel |
| 1115 | // true to show label for this series in the legend. |
| 1116 | this.showLabel = true; |
| 1117 | // prop: color |
| 1118 | // css color spec for the series |
| 1119 | this.color; |
| 1120 | // prop: negativeColor |
| 1121 | // css color spec used for filled (area) plots that are filled to zero and |
| 1122 | // the "useNegativeColors" option is true. |
| 1123 | this.negativeColor; |
| 1124 | // prop: lineWidth |
| 1125 | // width of the line in pixels. May have different meanings depending on renderer. |
| 1126 | this.lineWidth = 2.5; |
| 1127 | // prop: lineJoin |
| 1128 | // Canvas lineJoin style between segments of series. |
| 1129 | this.lineJoin = 'round'; |
| 1130 | // prop: lineCap |
| 1131 | // Canvas lineCap style at ends of line. |
| 1132 | this.lineCap = 'round'; |
| 1133 | // prop: linePattern |
| 1134 | // line pattern 'dashed', 'dotted', 'solid', some combination |
| 1135 | // of '-' and '.' characters such as '.-.' or a numerical array like |
| 1136 | // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, |
| 1137 | // [1, 10, 20, 10] to draw a dot-dash line, and so on. |
| 1138 | this.linePattern = 'solid'; |
| 1139 | this.shadow = true; |
| 1140 | // prop: shadowAngle |
| 1141 | // Shadow angle in degrees |
| 1142 | this.shadowAngle = 45; |
| 1143 | // prop: shadowOffset |
| 1144 | // Shadow offset from line in pixels |
| 1145 | this.shadowOffset = 1.25; |
| 1146 | // prop: shadowDepth |
| 1147 | // Number of times shadow is stroked, each stroke offset shadowOffset from the last. |
| 1148 | this.shadowDepth = 3; |
| 1149 | // prop: shadowAlpha |
| 1150 | // Alpha channel transparency of shadow. 0 = transparent. |
| 1151 | this.shadowAlpha = '0.1'; |
| 1152 | // prop: breakOnNull |
| 1153 | // Wether line segments should be be broken at null value. |
| 1154 | // False will join point on either side of line. |
| 1155 | this.breakOnNull = false; |
| 1156 | // prop: markerRenderer |
| 1157 | // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points, |
| 1158 | // see <$.jqplot.MarkerRenderer>. |
| 1159 | this.markerRenderer = $.jqplot.MarkerRenderer; |
| 1160 | // prop: markerOptions |
| 1161 | // renderer specific options to pass to the markerRenderer, |
| 1162 | // see <$.jqplot.MarkerRenderer>. |
| 1163 | this.markerOptions = {}; |
| 1164 | // prop: showLine |
| 1165 | // wether to actually draw the line or not. Series will still be renderered, even if no line is drawn. |
| 1166 | this.showLine = true; |
| 1167 | // prop: showMarker |
| 1168 | // wether or not to show the markers at the data points. |
| 1169 | this.showMarker = true; |
| 1170 | // prop: index |
| 1171 | // 0 based index of this series in the plot series array. |
| 1172 | this.index; |
| 1173 | // prop: fill |
| 1174 | // true or false, wether to fill under lines or in bars. |
| 1175 | // May not be implemented in all renderers. |
| 1176 | this.fill = false; |
| 1177 | // prop: fillColor |
| 1178 | // CSS color spec to use for fill under line. Defaults to line color. |
| 1179 | this.fillColor; |
| 1180 | // prop: fillAlpha |
| 1181 | // Alpha transparency to apply to the fill under the line. |
| 1182 | // Use this to adjust alpha separate from fill color. |
| 1183 | this.fillAlpha; |
| 1184 | // prop: fillAndStroke |
| 1185 | // If true will stroke the line (with color this.color) as well as fill under it. |
| 1186 | // Applies only when fill is true. |
| 1187 | this.fillAndStroke = false; |
| 1188 | // prop: disableStack |
| 1189 | // true to not stack this series with other series in the plot. |
| 1190 | // To render properly, non-stacked series must come after any stacked series |
| 1191 | // in the plot's data series array. So, the plot's data series array would look like: |
| 1192 | // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...] |
| 1193 | // disableStack will put a gap in the stacking order of series, and subsequent |
| 1194 | // stacked series will not fill down through the non-stacked series and will |
| 1195 | // most likely not stack properly on top of the non-stacked series. |
| 1196 | this.disableStack = false; |
| 1197 | // _stack is set by the Plot if the plot is a stacked chart. |
| 1198 | // will stack lines or bars on top of one another to build a "mountain" style chart. |
| 1199 | // May not be implemented in all renderers. |
| 1200 | this._stack = false; |
| 1201 | // prop: neighborThreshold |
| 1202 | // how close or far (in pixels) the cursor must be from a point marker to detect the point. |
| 1203 | this.neighborThreshold = 4; |
| 1204 | // prop: fillToZero |
| 1205 | // true will force bar and filled series to fill toward zero on the fill Axis. |
| 1206 | this.fillToZero = false; |
| 1207 | // prop: fillToValue |
| 1208 | // fill a filled series to this value on the fill axis. |
| 1209 | // Works in conjunction with fillToZero, so that must be true. |
| 1210 | this.fillToValue = 0; |
| 1211 | // prop: fillAxis |
| 1212 | // Either 'x' or 'y'. Which axis to fill the line toward if fillToZero is true. |
| 1213 | // 'y' means fill up/down to 0 on the y axis for this series. |
| 1214 | this.fillAxis = 'y'; |
| 1215 | // prop: useNegativeColors |
| 1216 | // true to color negative values differently in filled and bar charts. |
| 1217 | this.useNegativeColors = true; |
| 1218 | this._stackData = []; |
| 1219 | // _plotData accounts for stacking. If plots not stacked, _plotData and data are same. If |
| 1220 | // stacked, _plotData is accumulation of stacking data. |
| 1221 | this._plotData = []; |
| 1222 | // _plotValues hold the individual x and y values that will be plotted for this series. |
| 1223 | this._plotValues = {x:[], y:[]}; |
| 1224 | // statistics about the intervals between data points. Used for auto scaling. |
| 1225 | this._intervals = {x:{}, y:{}}; |
| 1226 | // data from the previous series, for stacked charts. |
| 1227 | this._prevPlotData = []; |
| 1228 | this._prevGridData = []; |
| 1229 | this._stackAxis = 'y'; |
| 1230 | this._primaryAxis = '_xaxis'; |
| 1231 | // give each series a canvas to draw on. This should allow for redrawing speedups. |
| 1232 | this.canvas = new $.jqplot.GenericCanvas(); |
| 1233 | this.shadowCanvas = new $.jqplot.GenericCanvas(); |
| 1234 | this.plugins = {}; |
| 1235 | // sum of y values in this series. |
| 1236 | this._sumy = 0; |
| 1237 | this._sumx = 0; |
| 1238 | this._type = ''; |
| 1239 | } |
| 1240 | |
| 1241 | Series.prototype = new $.jqplot.ElemContainer(); |
| 1242 | Series.prototype.constructor = Series; |
| 1243 | |
| 1244 | Series.prototype.init = function(index, gridbw, plot) { |
| 1245 | // weed out any null values in the data. |
| 1246 | this.index = index; |
| 1247 | this.gridBorderWidth = gridbw; |
| 1248 | var d = this.data; |
| 1249 | var temp = [], i; |
| 1250 | for (i=0; i<d.length; i++) { |
| 1251 | if (! this.breakOnNull) { |
| 1252 | if (d[i] == null || d[i][0] == null || d[i][1] == null) { |
| 1253 | continue; |
| 1254 | } |
| 1255 | else { |
| 1256 | temp.push(d[i]); |
| 1257 | } |
| 1258 | } |
| 1259 | else { |
| 1260 | // TODO: figure out what to do with null values |
| 1261 | // probably involve keeping nulls in data array |
| 1262 | // and then updating renderers to break line |
| 1263 | // when it hits null value. |
| 1264 | // For now, just keep value. |
| 1265 | temp.push(d[i]); |
| 1266 | } |
| 1267 | } |
| 1268 | this.data = temp; |
| 1269 | |
| 1270 | // parse the renderer options and apply default colors if not provided |
| 1271 | if (!this.color && this.show) { |
| 1272 | this.color = plot.colorGenerator.get(this.index); |
| 1273 | } |
| 1274 | if (!this.negativeColor && this.show) { |
| 1275 | this.negativeColor = plot.negativeColorGenerator.get(this.index); |
| 1276 | } |
| 1277 | |
| 1278 | |
| 1279 | if (!this.fillColor) { |
| 1280 | this.fillColor = this.color; |
| 1281 | } |
| 1282 | if (this.fillAlpha) { |
| 1283 | var comp = $.jqplot.normalize2rgb(this.fillColor); |
| 1284 | var comp = $.jqplot.getColorComponents(comp); |
| 1285 | this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')'; |
| 1286 | } |
| 1287 | this.renderer = new this.renderer(); |
| 1288 | this.renderer.init.call(this, this.rendererOptions, plot); |
| 1289 | this.markerRenderer = new this.markerRenderer(); |
| 1290 | if (!this.markerOptions.color) { |
| 1291 | this.markerOptions.color = this.color; |
| 1292 | } |
| 1293 | if (this.markerOptions.show == null) { |
| 1294 | this.markerOptions.show = this.showMarker; |
| 1295 | } |
| 1296 | this.showMarker = this.markerOptions.show; |
| 1297 | // the markerRenderer is called within it's own scaope, don't want to overwrite series options!! |
| 1298 | this.markerRenderer.init(this.markerOptions); |
| 1299 | }; |
| 1300 | |
| 1301 | // data - optional data point array to draw using this series renderer |
| 1302 | // gridData - optional grid data point array to draw using this series renderer |
| 1303 | // stackData - array of cumulative data for stacked plots. |
| 1304 | Series.prototype.draw = function(sctx, opts, plot) { |
| 1305 | var options = (opts == undefined) ? {} : opts; |
| 1306 | sctx = (sctx == undefined) ? this.canvas._ctx : sctx; |
| 1307 | |
| 1308 | var j, data, gridData; |
| 1309 | |
| 1310 | // hooks get called even if series not shown |
| 1311 | // we don't clear canvas here, it would wipe out all other series as well. |
| 1312 | for (j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) { |
| 1313 | $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options); |
| 1314 | } |
| 1315 | if (this.show) { |
| 1316 | this.renderer.setGridData.call(this, plot); |
| 1317 | if (!options.preventJqPlotSeriesDrawTrigger) { |
| 1318 | $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]); |
| 1319 | } |
| 1320 | data = []; |
| 1321 | if (options.data) { |
| 1322 | data = options.data; |
| 1323 | } |
| 1324 | else if (!this._stack) { |
| 1325 | data = this.data; |
| 1326 | } |
| 1327 | else { |
| 1328 | data = this._plotData; |
| 1329 | } |
| 1330 | gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot); |
| 1331 | |
| 1332 | if (this._type === 'line' && this.renderer.smooth && this.renderer._smoothedData.length) { |
| 1333 | gridData = this.renderer._smoothedData; |
| 1334 | } |
| 1335 | |
| 1336 | this.renderer.draw.call(this, sctx, gridData, options, plot); |
| 1337 | } |
| 1338 | |
| 1339 | for (j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) { |
| 1340 | $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options); |
| 1341 | } |
| 1342 | |
| 1343 | sctx = opts = plot = j = data = gridData = null; |
| 1344 | }; |
| 1345 | |
| 1346 | Series.prototype.drawShadow = function(sctx, opts, plot) { |
| 1347 | var options = (opts == undefined) ? {} : opts; |
| 1348 | sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx; |
| 1349 | |
| 1350 | var j, data, gridData; |
| 1351 | |
| 1352 | // hooks get called even if series not shown |
| 1353 | // we don't clear canvas here, it would wipe out all other series as well. |
| 1354 | for (j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) { |
| 1355 | $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options); |
| 1356 | } |
| 1357 | if (this.shadow) { |
| 1358 | this.renderer.setGridData.call(this, plot); |
| 1359 | |
| 1360 | data = []; |
| 1361 | if (options.data) { |
| 1362 | data = options.data; |
| 1363 | } |
| 1364 | else if (!this._stack) { |
| 1365 | data = this.data; |
| 1366 | } |
| 1367 | else { |
| 1368 | data = this._plotData; |
| 1369 | } |
| 1370 | gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot); |
| 1371 | |
| 1372 | this.renderer.drawShadow.call(this, sctx, gridData, options); |
| 1373 | } |
| 1374 | |
| 1375 | for (j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) { |
| 1376 | $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options); |
| 1377 | } |
| 1378 | |
| 1379 | sctx = opts = plot = j = data = gridData = null; |
| 1380 | |
| 1381 | }; |
| 1382 | |
| 1383 | // toggles series display on plot, e.g. show/hide series |
| 1384 | Series.prototype.toggleDisplay = function(ev) { |
| 1385 | var s, speed; |
| 1386 | if (ev.data.series) { |
| 1387 | s = ev.data.series; |
| 1388 | } |
| 1389 | else { |
| 1390 | s = this; |
| 1391 | } |
| 1392 | if (ev.data.speed) { |
| 1393 | speed = ev.data.speed; |
| 1394 | } |
| 1395 | if (speed) { |
| 1396 | if (s.canvas._elem.is(':hidden')) { |
| 1397 | s.canvas._elem.removeClass('jqplot-series-hidden'); |
| 1398 | if (s.shadowCanvas._elem) { |
| 1399 | s.shadowCanvas._elem.fadeIn(speed); |
| 1400 | } |
| 1401 | s.canvas._elem.fadeIn(speed); |
| 1402 | s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed); |
| 1403 | } |
| 1404 | else { |
| 1405 | s.canvas._elem.addClass('jqplot-series-hidden'); |
| 1406 | if (s.shadowCanvas._elem) { |
| 1407 | s.shadowCanvas._elem.fadeOut(speed); |
| 1408 | } |
| 1409 | s.canvas._elem.fadeOut(speed); |
| 1410 | s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed); |
| 1411 | } |
| 1412 | } |
| 1413 | else { |
| 1414 | if (s.canvas._elem.is(':hidden')) { |
| 1415 | s.canvas._elem.removeClass('jqplot-series-hidden'); |
| 1416 | if (s.shadowCanvas._elem) { |
| 1417 | s.shadowCanvas._elem.show(); |
| 1418 | } |
| 1419 | s.canvas._elem.show(); |
| 1420 | s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show(); |
| 1421 | } |
| 1422 | else { |
| 1423 | s.canvas._elem.addClass('jqplot-series-hidden'); |
| 1424 | if (s.shadowCanvas._elem) { |
| 1425 | s.shadowCanvas._elem.hide(); |
| 1426 | } |
| 1427 | s.canvas._elem.hide(); |
| 1428 | s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide(); |
| 1429 | } |
| 1430 | } |
| 1431 | }; |
| 1432 | |
| 1433 | |
| 1434 | |
| 1435 | /** |
| 1436 | * Class: Grid |
| 1437 | * |
| 1438 | * Object representing the grid on which the plot is drawn. The grid in this |
| 1439 | * context is the area bounded by the axes, the area which will contain the series. |
| 1440 | * Note, the series are drawn on their own canvas. |
| 1441 | * The Grid object cannot be instantiated directly, but is created by the Plot oject. |
| 1442 | * Grid properties can be set or overriden by the options passed in from the user. |
| 1443 | */ |
| 1444 | function Grid() { |
| 1445 | $.jqplot.ElemContainer.call(this); |
| 1446 | // Group: Properties |
| 1447 | |
| 1448 | // prop: drawGridlines |
| 1449 | // wether to draw the gridlines on the plot. |
| 1450 | this.drawGridlines = true; |
| 1451 | // prop: gridLineColor |
| 1452 | // color of the grid lines. |
| 1453 | this.gridLineColor = '#cccccc'; |
| 1454 | // prop: gridLineWidth |
| 1455 | // width of the grid lines. |
| 1456 | this.gridLineWidth = 1.0; |
| 1457 | // prop: background |
| 1458 | // css spec for the background color. |
| 1459 | this.background = '#fffdf6'; |
| 1460 | // prop: borderColor |
| 1461 | // css spec for the color of the grid border. |
| 1462 | this.borderColor = '#999999'; |
| 1463 | // prop: borderWidth |
| 1464 | // width of the border in pixels. |
| 1465 | this.borderWidth = 2.0; |
| 1466 | // prop: drawBorder |
| 1467 | // True to draw border around grid. |
| 1468 | this.drawBorder = true; |
| 1469 | // prop: shadow |
| 1470 | // wether to show a shadow behind the grid. |
| 1471 | this.shadow = true; |
| 1472 | // prop: shadowAngle |
| 1473 | // shadow angle in degrees |
| 1474 | this.shadowAngle = 45; |
| 1475 | // prop: shadowOffset |
| 1476 | // Offset of each shadow stroke from the border in pixels |
| 1477 | this.shadowOffset = 1.5; |
| 1478 | // prop: shadowWidth |
| 1479 | // width of the stoke for the shadow |
| 1480 | this.shadowWidth = 3; |
| 1481 | // prop: shadowDepth |
| 1482 | // Number of times shadow is stroked, each stroke offset shadowOffset from the last. |
| 1483 | this.shadowDepth = 3; |
| 1484 | // prop: shadowColor |
| 1485 | // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form |
| 1486 | this.shadowColor = null; |
| 1487 | // prop: shadowAlpha |
| 1488 | // Alpha channel transparency of shadow. 0 = transparent. |
| 1489 | this.shadowAlpha = '0.07'; |
| 1490 | this._left; |
| 1491 | this._top; |
| 1492 | this._right; |
| 1493 | this._bottom; |
| 1494 | this._width; |
| 1495 | this._height; |
| 1496 | this._axes = []; |
| 1497 | // prop: renderer |
| 1498 | // Instance of a renderer which will actually render the grid, |
| 1499 | // see <$.jqplot.CanvasGridRenderer>. |
| 1500 | this.renderer = $.jqplot.CanvasGridRenderer; |
| 1501 | // prop: rendererOptions |
| 1502 | // Options to pass on to the renderer, |
| 1503 | // see <$.jqplot.CanvasGridRenderer>. |
| 1504 | this.rendererOptions = {}; |
| 1505 | this._offsets = {top:null, bottom:null, left:null, right:null}; |
| 1506 | } |
| 1507 | |
| 1508 | Grid.prototype = new $.jqplot.ElemContainer(); |
| 1509 | Grid.prototype.constructor = Grid; |
| 1510 | |
| 1511 | Grid.prototype.init = function() { |
| 1512 | this.renderer = new this.renderer(); |
| 1513 | this.renderer.init.call(this, this.rendererOptions); |
| 1514 | }; |
| 1515 | |
| 1516 | Grid.prototype.createElement = function(offsets,plot) { |
| 1517 | this._offsets = offsets; |
| 1518 | return this.renderer.createElement.call(this, plot); |
| 1519 | }; |
| 1520 | |
| 1521 | Grid.prototype.draw = function() { |
| 1522 | this.renderer.draw.call(this); |
| 1523 | }; |
| 1524 | |
| 1525 | $.jqplot.GenericCanvas = function() { |
| 1526 | $.jqplot.ElemContainer.call(this); |
| 1527 | this._ctx; |
| 1528 | }; |
| 1529 | |
| 1530 | $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer(); |
| 1531 | $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas; |
| 1532 | |
| 1533 | $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions, plot) { |
| 1534 | this._offsets = offsets; |
| 1535 | var klass = 'jqplot'; |
| 1536 | if (clss != undefined) { |
| 1537 | klass = clss; |
| 1538 | } |
| 1539 | var elem; |
| 1540 | |
| 1541 | elem = plot.canvasManager.getCanvas(); |
| 1542 | |
| 1543 | // if new plotDimensions supplied, use them. |
| 1544 | if (plotDimensions != null) { |
| 1545 | this._plotDimensions = plotDimensions; |
| 1546 | } |
| 1547 | |
| 1548 | elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right; |
| 1549 | elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom; |
| 1550 | this._elem = $(elem); |
| 1551 | this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top }); |
| 1552 | |
| 1553 | this._elem.addClass(klass); |
| 1554 | |
| 1555 | elem = plot.canvasManager.initCanvas(elem); |
| 1556 | |
| 1557 | elem = null; |
| 1558 | return this._elem; |
| 1559 | }; |
| 1560 | |
| 1561 | $.jqplot.GenericCanvas.prototype.setContext = function() { |
| 1562 | this._ctx = this._elem.get(0).getContext("2d"); |
| 1563 | return this._ctx; |
| 1564 | }; |
| 1565 | |
| 1566 | // Memory Leaks patch |
| 1567 | $.jqplot.GenericCanvas.prototype.resetCanvas = function() { |
| 1568 | if (this._elem) { |
| 1569 | if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { |
| 1570 | window.G_vmlCanvasManager.uninitElement(this._elem.get(0)); |
| 1571 | } |
| 1572 | |
| 1573 | //this._elem.remove(); |
| 1574 | this._elem.emptyForce(); |
| 1575 | } |
| 1576 | |
| 1577 | this._ctx = null; |
| 1578 | }; |
| 1579 | |
| 1580 | $.jqplot.HooksManager = function () { |
| 1581 | this.hooks =[]; |
| 1582 | }; |
| 1583 | |
| 1584 | $.jqplot.HooksManager.prototype.addOnce = function(fn) { |
| 1585 | var havehook = false, i; |
| 1586 | for (i=0; i<this.hooks.length; i++) { |
| 1587 | if (this.hooks[i][0] == fn) { |
| 1588 | havehook = true; |
| 1589 | } |
| 1590 | } |
| 1591 | if (!havehook) { |
| 1592 | this.hooks.push(fn); |
| 1593 | } |
| 1594 | }; |
| 1595 | |
| 1596 | $.jqplot.HooksManager.prototype.add = function(fn) { |
| 1597 | this.hooks.push(fn); |
| 1598 | }; |
| 1599 | |
| 1600 | $.jqplot.EventListenerManager = function () { |
| 1601 | this.hooks =[]; |
| 1602 | }; |
| 1603 | |
| 1604 | $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) { |
| 1605 | var havehook = false, h, i; |
| 1606 | for (i=0; i<this.hooks.length; i++) { |
| 1607 | h = this.hooks[i]; |
| 1608 | if (h[0] == ev && h[1] == fn) { |
| 1609 | havehook = true; |
| 1610 | } |
| 1611 | } |
| 1612 | if (!havehook) { |
| 1613 | this.hooks.push([ev, fn]); |
| 1614 | } |
| 1615 | }; |
| 1616 | |
| 1617 | $.jqplot.EventListenerManager.prototype.add = function(ev, fn) { |
| 1618 | this.hooks.push([ev, fn]); |
| 1619 | }; |
| 1620 | |
| 1621 | |
| 1622 | var _axisNames = ['yMidAxis', 'xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; |
| 1623 | |
| 1624 | /** |
| 1625 | * Class: jqPlot |
| 1626 | * Plot object returned by call to $.jqplot. Handles parsing user options, |
| 1627 | * creating sub objects (Axes, legend, title, series) and rendering the plot. |
| 1628 | */ |
| 1629 | function jqPlot() { |
| 1630 | // Group: Properties |
| 1631 | // These properties are specified at the top of the options object |
| 1632 | // like so: |
| 1633 | // > { |
| 1634 | // > axesDefaults:{min:0}, |
| 1635 | // > series:[{color:'#6633dd'}], |
| 1636 | // > title: 'A Plot' |
| 1637 | // > } |
| 1638 | // |
| 1639 | // prop: data |
| 1640 | // user's data. Data should *NOT* be specified in the options object, |
| 1641 | // but be passed in as the second argument to the $.jqplot() function. |
| 1642 | // The data property is described here soley for reference. |
| 1643 | // The data should be in the form of an array of 2D or 1D arrays like |
| 1644 | // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ]. |
| 1645 | this.data = []; |
| 1646 | // prop: dataRenderer |
| 1647 | // A callable which can be used to preprocess data passed into the plot. |
| 1648 | // Will be called with 2 arguments, the plot data and a reference to the plot. |
| 1649 | this.dataRenderer; |
| 1650 | // prop: dataRendererOptions |
| 1651 | // Options that will be passed to the dataRenderer. |
| 1652 | // Can be of any type. |
| 1653 | this.dataRendererOptions; |
| 1654 | // prop: noDataIndicator |
| 1655 | // Options to set up a mock plot with a data loading indicator if no data is specified. |
| 1656 | this.noDataIndicator = { |
| 1657 | show: false, |
| 1658 | indicator: 'Loading Data...', |
| 1659 | axes: { |
| 1660 | xaxis: { |
| 1661 | min: 0, |
| 1662 | max: 10, |
| 1663 | tickInterval: 2, |
| 1664 | show: true |
| 1665 | }, |
| 1666 | yaxis: { |
| 1667 | min: 0, |
| 1668 | max: 12, |
| 1669 | tickInterval: 3, |
| 1670 | show: true |
| 1671 | } |
| 1672 | } |
| 1673 | }; |
| 1674 | // The id of the dom element to render the plot into |
| 1675 | this.targetId = null; |
| 1676 | // the jquery object for the dom target. |
| 1677 | this.target = null; |
| 1678 | this.defaults = { |
| 1679 | // prop: axesDefaults |
| 1680 | // default options that will be applied to all axes. |
| 1681 | // see <Axis> for axes options. |
| 1682 | axesDefaults: {}, |
| 1683 | axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}, yMidAxis:{}}, |
| 1684 | // prop: seriesDefaults |
| 1685 | // default options that will be applied to all series. |
| 1686 | // see <Series> for series options. |
| 1687 | seriesDefaults: {}, |
| 1688 | series:[] |
| 1689 | }; |
| 1690 | // prop: series |
| 1691 | // Array of series object options. |
| 1692 | // see <Series> for series specific options. |
| 1693 | this.series = []; |
| 1694 | // prop: axes |
| 1695 | // up to 4 axes are supported, each with it's own options, |
| 1696 | // See <Axis> for axis specific options. |
| 1697 | this.axes = {xaxis: new Axis('xaxis'), yaxis: new Axis('yaxis'), x2axis: new Axis('x2axis'), y2axis: new Axis('y2axis'), y3axis: new Axis('y3axis'), y4axis: new Axis('y4axis'), y5axis: new Axis('y5axis'), y6axis: new Axis('y6axis'), y7axis: new Axis('y7axis'), y8axis: new Axis('y8axis'), y9axis: new Axis('y9axis'), yMidAxis: new Axis('yMidAxis')}; |
| 1698 | // prop: grid |
| 1699 | // See <Grid> for grid specific options. |
| 1700 | this.grid = new Grid(); |
| 1701 | // prop: legend |
| 1702 | // see <$.jqplot.TableLegendRenderer> |
| 1703 | this.legend = new Legend(); |
| 1704 | this.baseCanvas = new $.jqplot.GenericCanvas(); |
| 1705 | // array of series indicies. Keep track of order |
| 1706 | // which series canvases are displayed, lowest |
| 1707 | // to highest, back to front. |
| 1708 | this.seriesStack = []; |
| 1709 | this.previousSeriesStack = []; |
| 1710 | this.eventCanvas = new $.jqplot.GenericCanvas(); |
| 1711 | this._width = null; |
| 1712 | this._height = null; |
| 1713 | this._plotDimensions = {height:null, width:null}; |
| 1714 | this._gridPadding = {top:null, right:null, bottom:null, left:null}; |
| 1715 | this._defaultGridPadding = {top:10, right:10, bottom:23, left:10}; |
| 1716 | // a shortcut for axis syncTicks options. Not implemented yet. |
| 1717 | this.syncXTicks = true; |
| 1718 | // a shortcut for axis syncTicks options. Not implemented yet. |
| 1719 | this.syncYTicks = true; |
| 1720 | // prop: seriesColors |
| 1721 | // Ann array of CSS color specifications that will be applied, in order, |
| 1722 | // to the series in the plot. Colors will wrap around so, if their |
| 1723 | // are more series than colors, colors will be reused starting at the |
| 1724 | // beginning. For pie charts, this specifies the colors of the slices. |
| 1725 | this.seriesColors = $.jqplot.config.defaultColors; |
| 1726 | this.negativeSeriesColors = $.jqplot.config.defaultNegativeColors; |
| 1727 | // prop: sortData |
| 1728 | // false to not sort the data passed in by the user. |
| 1729 | // Many bar, stakced and other graphs as well as many plugins depend on |
| 1730 | // having sorted data. |
| 1731 | this.sortData = true; |
| 1732 | var seriesColorsIndex = 0; |
| 1733 | // prop textColor |
| 1734 | // css spec for the css color attribute. Default for the entire plot. |
| 1735 | this.textColor; |
| 1736 | // prop; fontFamily |
| 1737 | // css spec for the font-family attribute. Default for the entire plot. |
| 1738 | this.fontFamily; |
| 1739 | // prop: fontSize |
| 1740 | // css spec for the font-size attribute. Default for the entire plot. |
| 1741 | this.fontSize; |
| 1742 | // prop: title |
| 1743 | // Title object. See <Title> for specific options. As a shortcut, you |
| 1744 | // can specify the title option as just a string like: title: 'My Plot' |
| 1745 | // and this will create a new title object with the specified text. |
| 1746 | this.title = new Title(); |
| 1747 | // container to hold all of the merged options. Convienence for plugins. |
| 1748 | this.options = {}; |
| 1749 | // prop: stackSeries |
| 1750 | // true or false, creates a stack or "mountain" plot. |
| 1751 | // Not all series renderers may implement this option. |
| 1752 | this.stackSeries = false; |
| 1753 | // prop: defaultAxisStart |
| 1754 | // 1-D data series are internally converted into 2-D [x,y] data point arrays |
| 1755 | // by jqPlot. This is the default starting value for the missing x or y value. |
| 1756 | // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...]) |
| 1757 | // starting at this value. |
| 1758 | this.defaultAxisStart = 1; |
| 1759 | // array to hold the cumulative stacked series data. |
| 1760 | // used to ajust the individual series data, which won't have access to other |
| 1761 | // series data. |
| 1762 | this._stackData = []; |
| 1763 | // array that holds the data to be plotted. This will be the series data |
| 1764 | // merged with the the appropriate data from _stackData according to the stackAxis. |
| 1765 | this._plotData = []; |
| 1766 | // Namespece to hold plugins. Generally non-renderer plugins add themselves to here. |
| 1767 | this.plugins = {}; |
| 1768 | // Count how many times the draw method has been called while the plot is visible. |
| 1769 | // Mostly used to test if plot has never been dran (=0), has been successfully drawn |
| 1770 | // into a visible container once (=1) or draw more than once into a visible container. |
| 1771 | // Can use this in tests to see if plot has been visibly drawn at least one time. |
| 1772 | // After plot has been visibly drawn once, it generally doesn't need redrawn if its |
| 1773 | // container is hidden and shown. |
| 1774 | this._drawCount = 0; |
| 1775 | // this.doCustomEventBinding = true; |
| 1776 | // prop: drawIfHidden |
| 1777 | // True to execute the draw method even if the plot target is hidden. |
| 1778 | // Generally, this should be false. Most plot elements will not be sized/ |
| 1779 | // positioned correclty if renderered into a hidden container. To render into |
| 1780 | // a hidden container, call the replot method when the container is shown. |
| 1781 | this.drawIfHidden = false; |
| 1782 | // true to intercept right click events and fire a 'jqplotRightClick' event. |
| 1783 | // this will also block the context menu. |
| 1784 | this.captureRightClick = false; |
| 1785 | this.themeEngine = new $.jqplot.ThemeEngine(); |
| 1786 | // sum of y values for all series in plot. |
| 1787 | // used in mekko chart. |
| 1788 | this._sumy = 0; |
| 1789 | this._sumx = 0; |
| 1790 | this.preInitHooks = new $.jqplot.HooksManager(); |
| 1791 | this.postInitHooks = new $.jqplot.HooksManager(); |
| 1792 | this.preParseOptionsHooks = new $.jqplot.HooksManager(); |
| 1793 | this.postParseOptionsHooks = new $.jqplot.HooksManager(); |
| 1794 | this.preDrawHooks = new $.jqplot.HooksManager(); |
| 1795 | this.postDrawHooks = new $.jqplot.HooksManager(); |
| 1796 | this.preDrawSeriesHooks = new $.jqplot.HooksManager(); |
| 1797 | this.postDrawSeriesHooks = new $.jqplot.HooksManager(); |
| 1798 | this.preDrawLegendHooks = new $.jqplot.HooksManager(); |
| 1799 | this.addLegendRowHooks = new $.jqplot.HooksManager(); |
| 1800 | this.preSeriesInitHooks = new $.jqplot.HooksManager(); |
| 1801 | this.postSeriesInitHooks = new $.jqplot.HooksManager(); |
| 1802 | this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager(); |
| 1803 | this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager(); |
| 1804 | this.eventListenerHooks = new $.jqplot.EventListenerManager(); |
| 1805 | this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager(); |
| 1806 | this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager(); |
| 1807 | |
| 1808 | this.colorGenerator = new $.jqplot.ColorGenerator(); |
| 1809 | this.negativeColorGenerator = new $.jqplot.ColorGenerator(); |
| 1810 | |
| 1811 | this.canvasManager = new $.jqplot.CanvasManager(); |
| 1812 | |
| 1813 | // Group: methods |
| 1814 | // |
| 1815 | // method: init |
| 1816 | // sets the plot target, checks data and applies user |
| 1817 | // options to plot. |
| 1818 | this.init = function(target, data, options) { |
| 1819 | options = options || {}; |
| 1820 | for (var i=0; i<$.jqplot.preInitHooks.length; i++) { |
| 1821 | $.jqplot.preInitHooks[i].call(this, target, data, options); |
| 1822 | } |
| 1823 | |
| 1824 | for (var i=0; i<this.preInitHooks.hooks.length; i++) { |
| 1825 | this.preInitHooks.hooks[i].call(this, target, data, options); |
| 1826 | } |
| 1827 | |
| 1828 | this.targetId = '#'+target; |
| 1829 | this.target = $('#'+target); |
| 1830 | // remove any error class that may be stuck on target. |
| 1831 | this.target.removeClass('jqplot-error'); |
| 1832 | if (!this.target.get(0)) { |
| 1833 | throw "No plot target specified"; |
| 1834 | } |
| 1835 | |
| 1836 | // make sure the target is positioned by some means and set css |
| 1837 | if (this.target.css('position') == 'static') { |
| 1838 | this.target.css('position', 'relative'); |
| 1839 | } |
| 1840 | if (!this.target.hasClass('jqplot-target')) { |
| 1841 | this.target.addClass('jqplot-target'); |
| 1842 | } |
| 1843 | |
| 1844 | // if no height or width specified, use a default. |
| 1845 | if (!this.target.height()) { |
| 1846 | var h; |
| 1847 | if (options && options.height) { |
| 1848 | h = parseInt(options.height, 10); |
| 1849 | } |
| 1850 | else if (this.target.attr('data-height')) { |
| 1851 | h = parseInt(this.target.attr('data-height'), 10); |
| 1852 | } |
| 1853 | else { |
| 1854 | h = parseInt($.jqplot.config.defaultHeight, 10); |
| 1855 | } |
| 1856 | this._height = h; |
| 1857 | this.target.css('height', h+'px'); |
| 1858 | } |
| 1859 | else { |
| 1860 | this._height = h = this.target.height(); |
| 1861 | } |
| 1862 | if (!this.target.width()) { |
| 1863 | var w; |
| 1864 | if (options && options.width) { |
| 1865 | w = parseInt(options.width, 10); |
| 1866 | } |
| 1867 | else if (this.target.attr('data-width')) { |
| 1868 | w = parseInt(this.target.attr('data-width'), 10); |
| 1869 | } |
| 1870 | else { |
| 1871 | w = parseInt($.jqplot.config.defaultWidth, 10); |
| 1872 | } |
| 1873 | this._width = w; |
| 1874 | this.target.css('width', w+'px'); |
| 1875 | } |
| 1876 | else { |
| 1877 | this._width = w = this.target.width(); |
| 1878 | } |
| 1879 | |
| 1880 | this._plotDimensions.height = this._height; |
| 1881 | this._plotDimensions.width = this._width; |
| 1882 | this.grid._plotDimensions = this._plotDimensions; |
| 1883 | this.title._plotDimensions = this._plotDimensions; |
| 1884 | this.baseCanvas._plotDimensions = this._plotDimensions; |
| 1885 | this.eventCanvas._plotDimensions = this._plotDimensions; |
| 1886 | this.legend._plotDimensions = this._plotDimensions; |
| 1887 | if (this._height <=0 || this._width <=0 || !this._height || !this._width) { |
| 1888 | throw "Canvas dimension not set"; |
| 1889 | } |
| 1890 | |
| 1891 | if (options.dataRenderer && jQuery.isFunction(options.dataRenderer)) { |
| 1892 | if (options.dataRendererOptions) { |
| 1893 | this.dataRendererOptions = options.dataRendererOptions; |
| 1894 | } |
| 1895 | this.dataRenderer = options.dataRenderer; |
| 1896 | data = this.dataRenderer(data, this, this.dataRendererOptions); |
| 1897 | } |
| 1898 | |
| 1899 | if (options.noDataIndicator && jQuery.isPlainObject(options.noDataIndicator)) { |
| 1900 | $.extend(true, this.noDataIndicator, options.noDataIndicator); |
| 1901 | } |
| 1902 | |
| 1903 | if (data == null || jQuery.isArray(data) == false || data.length == 0 || jQuery.isArray(data[0]) == false || data[0].length == 0) { |
| 1904 | |
| 1905 | if (this.noDataIndicator.show == false) { |
| 1906 | throw{ |
| 1907 | name: "DataError", |
| 1908 | message: "No data to plot." |
| 1909 | }; |
| 1910 | } |
| 1911 | |
| 1912 | else { |
| 1913 | // have to be descructive here in order for plot to not try and render series. |
| 1914 | // This means that $.jqplot() will have to be called again when there is data. |
| 1915 | //delete options.series; |
| 1916 | |
| 1917 | for (var ax in this.noDataIndicator.axes) { |
| 1918 | for (var prop in this.noDataIndicator.axes[ax]) { |
| 1919 | this.axes[ax][prop] = this.noDataIndicator.axes[ax][prop]; |
| 1920 | } |
| 1921 | } |
| 1922 | |
| 1923 | this.postDrawHooks.add(function() { |
| 1924 | var eh = this.eventCanvas.getHeight(); |
| 1925 | var ew = this.eventCanvas.getWidth(); |
| 1926 | var temp = $('<div class="jqplot-noData-container" style="position:absolute;"></div>'); |
| 1927 | this.target.append(temp); |
| 1928 | temp.height(eh); |
| 1929 | temp.width(ew); |
| 1930 | temp.css('top', this.eventCanvas._offsets.top); |
| 1931 | temp.css('left', this.eventCanvas._offsets.left); |
| 1932 | |
| 1933 | var temp2 = $('<div class="jqplot-noData-contents" style="text-align:center; position:relative; margin-left:auto; margin-right:auto;"></div>'); |
| 1934 | temp.append(temp2); |
| 1935 | temp2.html(this.noDataIndicator.indicator); |
| 1936 | var th = temp2.height(); |
| 1937 | var tw = temp2.width(); |
| 1938 | temp2.height(th); |
| 1939 | temp2.width(tw); |
| 1940 | temp2.css('top', (eh - th)/2 + 'px'); |
| 1941 | }); |
| 1942 | |
| 1943 | } |
| 1944 | } |
| 1945 | |
| 1946 | this.data = data; |
| 1947 | |
| 1948 | this.parseOptions(options); |
| 1949 | |
| 1950 | if (this.textColor) { |
| 1951 | this.target.css('color', this.textColor); |
| 1952 | } |
| 1953 | if (this.fontFamily) { |
| 1954 | this.target.css('font-family', this.fontFamily); |
| 1955 | } |
| 1956 | if (this.fontSize) { |
| 1957 | this.target.css('font-size', this.fontSize); |
| 1958 | } |
| 1959 | |
| 1960 | this.title.init(); |
| 1961 | this.legend.init(); |
| 1962 | this._sumy = 0; |
| 1963 | this._sumx = 0; |
| 1964 | for (var i=0; i<this.series.length; i++) { |
| 1965 | // set default stacking order for series canvases |
| 1966 | this.seriesStack.push(i); |
| 1967 | this.previousSeriesStack.push(i); |
| 1968 | this.series[i].shadowCanvas._plotDimensions = this._plotDimensions; |
| 1969 | this.series[i].canvas._plotDimensions = this._plotDimensions; |
| 1970 | for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) { |
| 1971 | $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); |
| 1972 | } |
| 1973 | for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) { |
| 1974 | this.preSeriesInitHooks.hooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); |
| 1975 | } |
| 1976 | this.populatePlotData(this.series[i], i); |
| 1977 | this.series[i]._plotDimensions = this._plotDimensions; |
| 1978 | this.series[i].init(i, this.grid.borderWidth, this); |
| 1979 | for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) { |
| 1980 | $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); |
| 1981 | } |
| 1982 | for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) { |
| 1983 | this.postSeriesInitHooks.hooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); |
| 1984 | } |
| 1985 | this._sumy += this.series[i]._sumy; |
| 1986 | this._sumx += this.series[i]._sumx; |
| 1987 | } |
| 1988 | |
| 1989 | for (var i=0; i<12; i++) { |
| 1990 | name = _axisNames[i]; |
| 1991 | this.axes[name]._plotDimensions = this._plotDimensions; |
| 1992 | this.axes[name].init(); |
| 1993 | if (this.axes[name].borderColor == null) { |
| 1994 | if (name.charAt(0) !== 'x' && this.axes[name].useSeriesColor === true && this.axes[name].show) { |
| 1995 | this.axes[name].borderColor = this.axes[name]._series[0].color; |
| 1996 | } |
| 1997 | else { |
| 1998 | this.axes[name].borderColor = this.grid.borderColor; |
| 1999 | } |
| 2000 | } |
| 2001 | } |
| 2002 | |
| 2003 | if (this.sortData) { |
| 2004 | sortData(this.series); |
| 2005 | } |
| 2006 | this.grid.init(); |
| 2007 | this.grid._axes = this.axes; |
| 2008 | |
| 2009 | this.legend._series = this.series; |
| 2010 | |
| 2011 | for (var i=0; i<$.jqplot.postInitHooks.length; i++) { |
| 2012 | $.jqplot.postInitHooks[i].call(this, target, data, options); |
| 2013 | } |
| 2014 | |
| 2015 | for (var i=0; i<this.postInitHooks.hooks.length; i++) { |
| 2016 | this.postInitHooks.hooks[i].call(this, target, data, options); |
| 2017 | } |
| 2018 | }; |
| 2019 | |
| 2020 | // method: resetAxesScale |
| 2021 | // Reset the specified axes min, max, numberTicks and tickInterval properties to null |
| 2022 | // or reset these properties on all axes if no list of axes is provided. |
| 2023 | // |
| 2024 | // Parameters: |
| 2025 | // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset. |
| 2026 | this.resetAxesScale = function(axes, options) { |
| 2027 | var opts = options || {}; |
| 2028 | var ax = axes || this.axes; |
| 2029 | if (ax === true) { |
| 2030 | ax = this.axes; |
| 2031 | } |
| 2032 | if (jQuery.isArray(ax)) { |
| 2033 | for (var i = 0; i < ax.length; i++) { |
| 2034 | this.axes[ax[i]].resetScale(opts[ax[i]]); |
| 2035 | } |
| 2036 | } |
| 2037 | else if (typeof(ax) === 'object') { |
| 2038 | for (var name in ax) { |
| 2039 | this.axes[name].resetScale(opts[name]); |
| 2040 | } |
| 2041 | } |
| 2042 | }; |
| 2043 | // method: reInitialize |
| 2044 | // reinitialize plot for replotting. |
| 2045 | // not called directly. |
| 2046 | this.reInitialize = function () { |
| 2047 | // Plot should be visible and have a height and width. |
| 2048 | // If plot doesn't have height and width for some |
| 2049 | // reason, set it by other means. Plot must not have |
| 2050 | // a display:none attribute, however. |
| 2051 | |
| 2052 | // |
| 2053 | // Wont have options here |
| 2054 | /* |
| 2055 | if (!this.target.height()) { |
| 2056 | var h; |
| 2057 | if (options && options.height) { |
| 2058 | h = parseInt(options.height, 10); |
| 2059 | } |
| 2060 | else if (this.target.attr('data-height')) { |
| 2061 | h = parseInt(this.target.attr('data-height'), 10); |
| 2062 | } |
| 2063 | else { |
| 2064 | h = parseInt($.jqplot.config.defaultHeight, 10); |
| 2065 | } |
| 2066 | this._height = h; |
| 2067 | this.target.css('height', h+'px'); |
| 2068 | } |
| 2069 | else { |
| 2070 | this._height = this.target.height(); |
| 2071 | } |
| 2072 | if (!this.target.width()) { |
| 2073 | var w; |
| 2074 | if (options && options.width) { |
| 2075 | w = parseInt(options.width, 10); |
| 2076 | } |
| 2077 | else if (this.target.attr('data-width')) { |
| 2078 | w = parseInt(this.target.attr('data-width'), 10); |
| 2079 | } |
| 2080 | else { |
| 2081 | w = parseInt($.jqplot.config.defaultWidth, 10); |
| 2082 | } |
| 2083 | this._width = w; |
| 2084 | this.target.css('width', w+'px'); |
| 2085 | } |
| 2086 | else { |
| 2087 | this._width = this.target.width(); |
| 2088 | } |
| 2089 | */ |
| 2090 | |
| 2091 | this._height = this.target.height(); |
| 2092 | this._width = this.target.width(); |
| 2093 | |
| 2094 | if (this._height <=0 || this._width <=0 || !this._height || !this._width) { |
| 2095 | throw "Target dimension not set"; |
| 2096 | } |
| 2097 | |
| 2098 | this._plotDimensions.height = this._height; |
| 2099 | this._plotDimensions.width = this._width; |
| 2100 | this.grid._plotDimensions = this._plotDimensions; |
| 2101 | this.title._plotDimensions = this._plotDimensions; |
| 2102 | this.baseCanvas._plotDimensions = this._plotDimensions; |
| 2103 | this.eventCanvas._plotDimensions = this._plotDimensions; |
| 2104 | this.legend._plotDimensions = this._plotDimensions; |
| 2105 | |
| 2106 | for (var n in this.axes) { |
| 2107 | this.axes[n]._plotWidth = this._width; |
| 2108 | this.axes[n]._plotHeight = this._height; |
| 2109 | } |
| 2110 | |
| 2111 | this.title._plotWidth = this._width; |
| 2112 | |
| 2113 | if (this.textColor) { |
| 2114 | this.target.css('color', this.textColor); |
| 2115 | } |
| 2116 | if (this.fontFamily) { |
| 2117 | this.target.css('font-family', this.fontFamily); |
| 2118 | } |
| 2119 | if (this.fontSize) { |
| 2120 | this.target.css('font-size', this.fontSize); |
| 2121 | } |
| 2122 | |
| 2123 | this._sumy = 0; |
| 2124 | this._sumx = 0; |
| 2125 | for (var i=0; i<this.series.length; i++) { |
| 2126 | this.populatePlotData(this.series[i], i); |
| 2127 | if (this.series[i]._type === 'line' && this.series[i].renderer.bands.show) { |
| 2128 | this.series[i].renderer.initBands.call(this.series[i], this.series[i].renderer.options, this); |
| 2129 | } |
| 2130 | this.series[i]._plotDimensions = this._plotDimensions; |
| 2131 | this.series[i].canvas._plotDimensions = this._plotDimensions; |
| 2132 | //this.series[i].init(i, this.grid.borderWidth); |
| 2133 | this._sumy += this.series[i]._sumy; |
| 2134 | this._sumx += this.series[i]._sumx; |
| 2135 | } |
| 2136 | |
| 2137 | for (var j=0; j<12; j++) { |
| 2138 | name = _axisNames[j]; |
| 2139 | // Memory Leaks patch : clear ticks elements |
| 2140 | var t = this.axes[name]._ticks; |
| 2141 | for (var i = 0; i < t.length; i++) { |
| 2142 | var el = t[i]._elem; |
| 2143 | if (el) { |
| 2144 | // if canvas renderer |
| 2145 | if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { |
| 2146 | window.G_vmlCanvasManager.uninitElement(el.get(0)); |
| 2147 | } |
| 2148 | el.emptyForce(); |
| 2149 | el = null; |
| 2150 | t._elem = null; |
| 2151 | } |
| 2152 | } |
| 2153 | t = null; |
| 2154 | |
| 2155 | this.axes[name]._plotDimensions = this._plotDimensions; |
| 2156 | this.axes[name]._ticks = []; |
| 2157 | // this.axes[name].renderer.init.call(this.axes[name], {}); |
| 2158 | } |
| 2159 | |
| 2160 | if (this.sortData) { |
| 2161 | sortData(this.series); |
| 2162 | } |
| 2163 | |
| 2164 | this.grid._axes = this.axes; |
| 2165 | |
| 2166 | this.legend._series = this.series; |
| 2167 | }; |
| 2168 | |
| 2169 | // sort the series data in increasing order. |
| 2170 | function sortData(series) { |
| 2171 | var d, sd, pd, ppd, ret; |
| 2172 | for (var i=0; i<series.length; i++) { |
| 2173 | // d = series[i].data; |
| 2174 | // sd = series[i]._stackData; |
| 2175 | // pd = series[i]._plotData; |
| 2176 | // ppd = series[i]._prevPlotData; |
| 2177 | var check; |
| 2178 | var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData]; |
| 2179 | for (var n=0; n<4; n++) { |
| 2180 | check = true; |
| 2181 | d = bat[n]; |
| 2182 | if (series[i]._stackAxis == 'x') { |
| 2183 | for (var j = 0; j < d.length; j++) { |
| 2184 | if (typeof(d[j][1]) != "number") { |
| 2185 | check = false; |
| 2186 | break; |
| 2187 | } |
| 2188 | } |
| 2189 | if (check) { |
| 2190 | d.sort(function(a,b) { return a[1] - b[1]; }); |
| 2191 | // sd.sort(function(a,b) { return a[1] - b[1]; }); |
| 2192 | // pd.sort(function(a,b) { return a[1] - b[1]; }); |
| 2193 | // ppd.sort(function(a,b) { return a[1] - b[1]; }); |
| 2194 | } |
| 2195 | } |
| 2196 | else { |
| 2197 | for (var j = 0; j < d.length; j++) { |
| 2198 | if (typeof(d[j][0]) != "number") { |
| 2199 | check = false; |
| 2200 | break; |
| 2201 | } |
| 2202 | } |
| 2203 | if (check) { |
| 2204 | d.sort(function(a,b) { return a[0] - b[0]; }); |
| 2205 | // sd.sort(function(a,b) { return a[0] - b[0]; }); |
| 2206 | // pd.sort(function(a,b) { return a[0] - b[0]; }); |
| 2207 | // ppd.sort(function(a,b) { return a[0] - b[0]; }); |
| 2208 | } |
| 2209 | } |
| 2210 | } |
| 2211 | |
| 2212 | } |
| 2213 | } |
| 2214 | |
| 2215 | // populate the _stackData and _plotData arrays for the plot and the series. |
| 2216 | this.populatePlotData = function(series, index) { |
| 2217 | // if a stacked chart, compute the stacked data |
| 2218 | this._plotData = []; |
| 2219 | this._stackData = []; |
| 2220 | series._stackData = []; |
| 2221 | series._plotData = []; |
| 2222 | var plotValues = {x:[], y:[]}; |
| 2223 | if (this.stackSeries && !series.disableStack) { |
| 2224 | series._stack = true; |
| 2225 | var sidx = series._stackAxis == 'x' ? 0 : 1; |
| 2226 | var idx = sidx ? 0 : 1; |
| 2227 | // push the current data into stackData |
| 2228 | //this._stackData.push(this.series[i].data); |
| 2229 | var temp = $.extend(true, [], series.data); |
| 2230 | // create the data that will be plotted for this series |
| 2231 | var plotdata = $.extend(true, [], series.data); |
| 2232 | // for first series, nothing to add to stackData. |
| 2233 | for (var j=0; j<index; j++) { |
| 2234 | var cd = this.series[j].data; |
| 2235 | for (var k=0; k<cd.length; k++) { |
| 2236 | temp[k][0] += cd[k][0]; |
| 2237 | temp[k][1] += cd[k][1]; |
| 2238 | // only need to sum up the stack axis column of data |
| 2239 | plotdata[k][sidx] += cd[k][sidx]; |
| 2240 | } |
| 2241 | } |
| 2242 | for (var i=0; i<plotdata.length; i++) { |
| 2243 | plotValues.x.push(plotdata[i][0]); |
| 2244 | plotValues.y.push(plotdata[i][1]); |
| 2245 | } |
| 2246 | this._plotData.push(plotdata); |
| 2247 | this._stackData.push(temp); |
| 2248 | series._stackData = temp; |
| 2249 | series._plotData = plotdata; |
| 2250 | series._plotValues = plotValues; |
| 2251 | } |
| 2252 | else { |
| 2253 | for (var i=0; i<series.data.length; i++) { |
| 2254 | plotValues.x.push(series.data[i][0]); |
| 2255 | plotValues.y.push(series.data[i][1]); |
| 2256 | } |
| 2257 | this._stackData.push(series.data); |
| 2258 | this.series[index]._stackData = series.data; |
| 2259 | this._plotData.push(series.data); |
| 2260 | series._plotData = series.data; |
| 2261 | series._plotValues = plotValues; |
| 2262 | } |
| 2263 | if (index>0) { |
| 2264 | series._prevPlotData = this.series[index-1]._plotData; |
| 2265 | } |
| 2266 | series._sumy = 0; |
| 2267 | series._sumx = 0; |
| 2268 | for (i=series.data.length-1; i>-1; i--) { |
| 2269 | series._sumy += series.data[i][1]; |
| 2270 | series._sumx += series.data[i][0]; |
| 2271 | } |
| 2272 | }; |
| 2273 | |
| 2274 | // this.setData = function(seriesIndex, newdata) { |
| 2275 | // // if newdata is null, assume all data is passed in as first argument |
| 2276 | // if (newdata == null) { |
| 2277 | |
| 2278 | // } |
| 2279 | // }; |
| 2280 | |
| 2281 | // function to safely return colors from the color array and wrap around at the end. |
| 2282 | this.getNextSeriesColor = (function(t) { |
| 2283 | var idx = 0; |
| 2284 | var sc = t.seriesColors; |
| 2285 | |
| 2286 | return function () { |
| 2287 | if (idx < sc.length) { |
| 2288 | return sc[idx++]; |
| 2289 | } |
| 2290 | else { |
| 2291 | idx = 0; |
| 2292 | return sc[idx++]; |
| 2293 | } |
| 2294 | }; |
| 2295 | })(this); |
| 2296 | |
| 2297 | this.parseOptions = function(options){ |
| 2298 | for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) { |
| 2299 | this.preParseOptionsHooks.hooks[i].call(this, options); |
| 2300 | } |
| 2301 | for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) { |
| 2302 | $.jqplot.preParseOptionsHooks[i].call(this, options); |
| 2303 | } |
| 2304 | this.options = $.extend(true, {}, this.defaults, options); |
| 2305 | this.stackSeries = this.options.stackSeries; |
| 2306 | if (this.options.seriesColors) { |
| 2307 | this.seriesColors = this.options.seriesColors; |
| 2308 | } |
| 2309 | if (this.options.negativeSeriesColors) { |
| 2310 | this.negativeSeriesColors = this.options.negativeSeriesColors; |
| 2311 | } |
| 2312 | if (this.options.captureRightClick) { |
| 2313 | this.captureRightClick = this.options.captureRightClick; |
| 2314 | } |
| 2315 | this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart; |
| 2316 | this.colorGenerator.setColors(this.seriesColors); |
| 2317 | this.negativeColorGenerator.setColors(this.negativeSeriesColors); |
| 2318 | // var cg = new this.colorGenerator(this.seriesColors); |
| 2319 | // var ncg = new this.colorGenerator(this.negativeSeriesColors); |
| 2320 | // this._gridPadding = this.options.gridPadding; |
| 2321 | $.extend(true, this._gridPadding, this.options.gridPadding); |
| 2322 | this.sortData = (this.options.sortData != null) ? this.options.sortData : this.sortData; |
| 2323 | for (var i=0; i<12; i++) { |
| 2324 | var n = _axisNames[i]; |
| 2325 | var axis = this.axes[n]; |
| 2326 | axis._options = $.extend(true, {}, this.options.axesDefaults, this.options.axes[n]); |
| 2327 | $.extend(true, axis, this.options.axesDefaults, this.options.axes[n]); |
| 2328 | axis._plotWidth = this._width; |
| 2329 | axis._plotHeight = this._height; |
| 2330 | } |
| 2331 | // if (this.data.length == 0) { |
| 2332 | // this.data = []; |
| 2333 | // for (var i=0; i<this.options.series.length; i++) { |
| 2334 | // this.data.push(this.options.series.data); |
| 2335 | // } |
| 2336 | // } |
| 2337 | |
| 2338 | var normalizeData = function(data, dir, start) { |
| 2339 | // return data as an array of point arrays, |
| 2340 | // in form [[x1,y1...], [x2,y2...], ...] |
| 2341 | var temp = []; |
| 2342 | var i; |
| 2343 | dir = dir || 'vertical'; |
| 2344 | if (!jQuery.isArray(data[0])) { |
| 2345 | // we have a series of scalars. One line with just y values. |
| 2346 | // turn the scalar list of data into a data array of form: |
| 2347 | // [[1, data[0]], [2, data[1]], ...] |
| 2348 | for (i=0; i<data.length; i++) { |
| 2349 | if (dir == 'vertical') { |
| 2350 | temp.push([start + i, data[i]]); |
| 2351 | } |
| 2352 | else { |
| 2353 | temp.push([data[i], start+i]); |
| 2354 | } |
| 2355 | } |
| 2356 | } |
| 2357 | else { |
| 2358 | // we have a properly formatted data series, copy it. |
| 2359 | $.extend(true, temp, data); |
| 2360 | } |
| 2361 | return temp; |
| 2362 | }; |
| 2363 | |
| 2364 | var colorIndex = 0; |
| 2365 | for (var i=0; i<this.data.length; i++) { |
| 2366 | var temp = new Series(); |
| 2367 | for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) { |
| 2368 | $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]); |
| 2369 | } |
| 2370 | for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) { |
| 2371 | this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]); |
| 2372 | } |
| 2373 | $.extend(true, temp, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i]); |
| 2374 | var dir = 'vertical'; |
| 2375 | if (temp.renderer === $.jqplot.BarRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal' && temp.transposeData === true) { |
| 2376 | dir = 'horizontal'; |
| 2377 | } |
| 2378 | temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart); |
| 2379 | switch (temp.xaxis) { |
| 2380 | case 'xaxis': |
| 2381 | temp._xaxis = this.axes.xaxis; |
| 2382 | break; |
| 2383 | case 'x2axis': |
| 2384 | temp._xaxis = this.axes.x2axis; |
| 2385 | break; |
| 2386 | default: |
| 2387 | break; |
| 2388 | } |
| 2389 | temp._yaxis = this.axes[temp.yaxis]; |
| 2390 | temp._xaxis._series.push(temp); |
| 2391 | temp._yaxis._series.push(temp); |
| 2392 | if (temp.show) { |
| 2393 | temp._xaxis.show = true; |
| 2394 | temp._yaxis.show = true; |
| 2395 | } |
| 2396 | |
| 2397 | // // parse the renderer options and apply default colors if not provided |
| 2398 | // if (!temp.color && temp.show != false) { |
| 2399 | // temp.color = cg.next(); |
| 2400 | // colorIndex = cg.getIndex() - 1;; |
| 2401 | // } |
| 2402 | // if (!temp.negativeColor && temp.show != false) { |
| 2403 | // temp.negativeColor = ncg.get(colorIndex); |
| 2404 | // ncg.setIndex(colorIndex); |
| 2405 | // } |
| 2406 | if (!temp.label) { |
| 2407 | temp.label = 'Series '+ (i+1).toString(); |
| 2408 | } |
| 2409 | // temp.rendererOptions.show = temp.show; |
| 2410 | // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions); |
| 2411 | this.series.push(temp); |
| 2412 | for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) { |
| 2413 | $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]); |
| 2414 | } |
| 2415 | for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) { |
| 2416 | this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]); |
| 2417 | } |
| 2418 | } |
| 2419 | |
| 2420 | // copy the grid and title options into this object. |
| 2421 | $.extend(true, this.grid, this.options.grid); |
| 2422 | // if axis border properties aren't set, set default. |
| 2423 | for (var i=0; i<12; i++) { |
| 2424 | var n = _axisNames[i]; |
| 2425 | var axis = this.axes[n]; |
| 2426 | if (axis.borderWidth == null) { |
| 2427 | axis.borderWidth =this.grid.borderWidth; |
| 2428 | } |
| 2429 | } |
| 2430 | |
| 2431 | if (typeof this.options.title == 'string') { |
| 2432 | this.title.text = this.options.title; |
| 2433 | } |
| 2434 | else if (typeof this.options.title == 'object') { |
| 2435 | $.extend(true, this.title, this.options.title); |
| 2436 | } |
| 2437 | this.title._plotWidth = this._width; |
| 2438 | this.legend.setOptions(this.options.legend); |
| 2439 | |
| 2440 | for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) { |
| 2441 | $.jqplot.postParseOptionsHooks[i].call(this, options); |
| 2442 | } |
| 2443 | for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) { |
| 2444 | this.postParseOptionsHooks.hooks[i].call(this, options); |
| 2445 | } |
| 2446 | }; |
| 2447 | |
| 2448 | // method: destroy |
| 2449 | // Releases all resources occupied by the plot |
| 2450 | this.destroy = function() { |
| 2451 | this.canvasManager.freeAllCanvases(); |
| 2452 | if (this.eventCanvas && this.eventCanvas._elem) { |
| 2453 | this.eventCanvas._elem.unbind(); |
| 2454 | } |
| 2455 | // Couple of posts on Stack Overflow indicate that empty() doesn't |
| 2456 | // always cear up the dom and release memory. Sometimes setting |
| 2457 | // innerHTML property to null is needed. Particularly on IE, may |
| 2458 | // have to directly set it to null, bypassing jQuery. |
| 2459 | this.target.empty(); |
| 2460 | |
| 2461 | this.target[0].innerHTML = ''; |
| 2462 | }; |
| 2463 | |
| 2464 | // method: replot |
| 2465 | // Does a reinitialization of the plot followed by |
| 2466 | // a redraw. Method could be used to interactively |
| 2467 | // change plot characteristics and then replot. |
| 2468 | // |
| 2469 | // Parameters: |
| 2470 | // options - Options used for replotting. |
| 2471 | // |
| 2472 | // Properties: |
| 2473 | // clear - false to not clear (empty) the plot container before replotting (default: true). |
| 2474 | // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves. |
| 2475 | // optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false). |
| 2476 | this.replot = function(options) { |
| 2477 | var opts = options || {}; |
| 2478 | var clear = opts.clear || true; |
| 2479 | var resetAxes = opts.resetAxes || false; |
| 2480 | this.target.trigger('jqplotPreReplot'); |
| 2481 | |
| 2482 | if (clear) { |
| 2483 | this.destroy(); |
| 2484 | } |
| 2485 | this.reInitialize(); |
| 2486 | if (resetAxes) { |
| 2487 | this.resetAxesScale(resetAxes, opts.axes); |
| 2488 | } |
| 2489 | this.draw(); |
| 2490 | this.target.trigger('jqplotPostReplot'); |
| 2491 | }; |
| 2492 | |
| 2493 | // method: redraw |
| 2494 | // Empties the plot target div and redraws the plot. |
| 2495 | // This enables plot data and properties to be changed |
| 2496 | // and then to comletely clear the plot and redraw. |
| 2497 | // redraw *will not* reinitialize any plot elements. |
| 2498 | // That is, axes will not be autoscaled and defaults |
| 2499 | // will not be reapplied to any plot elements. redraw |
| 2500 | // is used primarily with zooming. |
| 2501 | // |
| 2502 | // Parameters: |
| 2503 | // clear - false to not clear (empty) the plot container before redrawing (default: true). |
| 2504 | this.redraw = function(clear) { |
| 2505 | clear = (clear != null) ? clear : true; |
| 2506 | this.target.trigger('jqplotPreRedraw'); |
| 2507 | if (clear) { |
| 2508 | this.canvasManager.freeAllCanvases(); |
| 2509 | this.eventCanvas._elem.unbind(); |
| 2510 | // Dont think I bind any events to the target, this shouldn't be necessary. |
| 2511 | // It will remove user's events. |
| 2512 | // this.target.unbind(); |
| 2513 | this.target.empty(); |
| 2514 | } |
| 2515 | for (var ax in this.axes) { |
| 2516 | this.axes[ax]._ticks = []; |
| 2517 | } |
| 2518 | for (var i=0; i<this.series.length; i++) { |
| 2519 | this.populatePlotData(this.series[i], i); |
| 2520 | } |
| 2521 | this._sumy = 0; |
| 2522 | this._sumx = 0; |
| 2523 | for (i=0; i<this.series.length; i++) { |
| 2524 | this._sumy += this.series[i]._sumy; |
| 2525 | this._sumx += this.series[i]._sumx; |
| 2526 | } |
| 2527 | this.draw(); |
| 2528 | this.target.trigger('jqplotPostRedraw'); |
| 2529 | }; |
| 2530 | |
| 2531 | // method: draw |
| 2532 | // Draws all elements of the plot into the container. |
| 2533 | // Does not clear the container before drawing. |
| 2534 | this.draw = function(){ |
| 2535 | if (this.drawIfHidden || this.target.is(':visible')) { |
| 2536 | this.target.trigger('jqplotPreDraw'); |
| 2537 | var i, j; |
| 2538 | for (i=0; i<$.jqplot.preDrawHooks.length; i++) { |
| 2539 | $.jqplot.preDrawHooks[i].call(this); |
| 2540 | } |
| 2541 | for (i=0; i<this.preDrawHooks.hooks.length; i++) { |
| 2542 | this.preDrawHooks.hooks[i].call(this); |
| 2543 | } |
| 2544 | // create an underlying canvas to be used for special features. |
| 2545 | this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas', null, this)); |
| 2546 | this.baseCanvas.setContext(); |
| 2547 | this.target.append(this.title.draw()); |
| 2548 | this.title.pack({top:0, left:0}); |
| 2549 | |
| 2550 | // make room for the legend between the grid and the edge. |
| 2551 | var legendElem = this.legend.draw(); |
| 2552 | |
| 2553 | var gridPadding = {top:0, left:0, bottom:0, right:0}; |
| 2554 | |
| 2555 | if (this.legend.placement == "outsideGrid") { |
| 2556 | // temporarily append the legend to get dimensions |
| 2557 | this.target.append(legendElem); |
| 2558 | switch (this.legend.location) { |
| 2559 | case 'n': |
| 2560 | gridPadding.top += this.legend.getHeight(); |
| 2561 | break; |
| 2562 | case 's': |
| 2563 | gridPadding.bottom += this.legend.getHeight(); |
| 2564 | break; |
| 2565 | case 'ne': |
| 2566 | case 'e': |
| 2567 | case 'se': |
| 2568 | gridPadding.right += this.legend.getWidth(); |
| 2569 | break; |
| 2570 | case 'nw': |
| 2571 | case 'w': |
| 2572 | case 'sw': |
| 2573 | gridPadding.left += this.legend.getWidth(); |
| 2574 | break; |
| 2575 | default: // same as 'ne' |
| 2576 | gridPadding.right += this.legend.getWidth(); |
| 2577 | break; |
| 2578 | } |
| 2579 | legendElem = legendElem.detach(); |
| 2580 | } |
| 2581 | |
| 2582 | var ax = this.axes; |
| 2583 | // draw the yMidAxis first, so xaxis of pyramid chart can adjust itself if needed. |
| 2584 | for (i=0; i<12; i++) { |
| 2585 | name = _axisNames[i]; |
| 2586 | this.target.append(ax[name].draw(this.baseCanvas._ctx, this)); |
| 2587 | ax[name].set(); |
| 2588 | } |
| 2589 | if (ax.yaxis.show) { |
| 2590 | gridPadding.left += ax.yaxis.getWidth(); |
| 2591 | } |
| 2592 | var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; |
| 2593 | var rapad = [0, 0, 0, 0, 0, 0, 0, 0]; |
| 2594 | var gpr = 0; |
| 2595 | var n; |
| 2596 | for (n=0; n<8; n++) { |
| 2597 | if (ax[ra[n]].show) { |
| 2598 | gpr += ax[ra[n]].getWidth(); |
| 2599 | rapad[n] = gpr; |
| 2600 | } |
| 2601 | } |
| 2602 | gridPadding.right += gpr; |
| 2603 | if (ax.x2axis.show) { |
| 2604 | gridPadding.top += ax.x2axis.getHeight(); |
| 2605 | } |
| 2606 | if (this.title.show) { |
| 2607 | gridPadding.top += this.title.getHeight(); |
| 2608 | } |
| 2609 | if (ax.xaxis.show) { |
| 2610 | gridPadding.bottom += ax.xaxis.getHeight(); |
| 2611 | } |
| 2612 | |
| 2613 | // end of gridPadding adjustments. |
| 2614 | |
| 2615 | // if user passed in gridDimensions option, check against calculated gridPadding |
| 2616 | if (this.options.gridDimensions && $.isPlainObject(this.options.gridDimensions)) { |
| 2617 | var gdw = parseInt(this.options.gridDimensions.width, 10) || 0; |
| 2618 | var gdh = parseInt(this.options.gridDimensions.height, 10) || 0; |
| 2619 | var widthAdj = (this._width - gridPadding.left - gridPadding.right - gdw)/2; |
| 2620 | var heightAdj = (this._height - gridPadding.top - gridPadding.bottom - gdh)/2; |
| 2621 | |
| 2622 | if (heightAdj >= 0 && widthAdj >= 0) { |
| 2623 | gridPadding.top += heightAdj; |
| 2624 | gridPadding.bottom += heightAdj; |
| 2625 | gridPadding.left += widthAdj; |
| 2626 | gridPadding.right += widthAdj; |
| 2627 | } |
| 2628 | } |
| 2629 | var arr = ['top', 'bottom', 'left', 'right']; |
| 2630 | for (var n in arr) { |
| 2631 | if (this._gridPadding[arr[n]] == null && gridPadding[arr[n]] > 0) { |
| 2632 | this._gridPadding[arr[n]] = gridPadding[arr[n]]; |
| 2633 | } |
| 2634 | else if (this._gridPadding[arr[n]] == null) { |
| 2635 | this._gridPadding[arr[n]] = this._defaultGridPadding[arr[n]]; |
| 2636 | } |
| 2637 | } |
| 2638 | |
| 2639 | var legendPadding = (this.legend.placement == 'outsideGrid') ? {top:this.title.getHeight(), left: 0, right: 0, bottom: 0} : this._gridPadding; |
| 2640 | |
| 2641 | ax.xaxis.pack({position:'absolute', bottom:this._gridPadding.bottom - ax.xaxis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right}); |
| 2642 | ax.yaxis.pack({position:'absolute', top:0, left:this._gridPadding.left - ax.yaxis.getWidth(), height:this._height}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top}); |
| 2643 | ax.x2axis.pack({position:'absolute', top:this._gridPadding.top - ax.x2axis.getHeight(), left:0, width:this._width}, {min:this._gridPadding.left, max:this._width - this._gridPadding.right}); |
| 2644 | for (i=8; i>0; i--) { |
| 2645 | ax[ra[i-1]].pack({position:'absolute', top:0, right:this._gridPadding.right - rapad[i-1]}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top}); |
| 2646 | } |
| 2647 | var ltemp = (this._width - this._gridPadding.left - this._gridPadding.right)/2.0 + this._gridPadding.left - ax.yMidAxis.getWidth()/2.0; |
| 2648 | ax.yMidAxis.pack({position:'absolute', top:0, left:ltemp, zIndex:9, textAlign: 'center'}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top}); |
| 2649 | |
| 2650 | this.target.append(this.grid.createElement(this._gridPadding, this)); |
| 2651 | this.grid.draw(); |
| 2652 | |
| 2653 | // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars. |
| 2654 | for (i=0; i<this.series.length; i++) { |
| 2655 | // draw series in order of stacking. This affects only |
| 2656 | // order in which canvases are added to dom. |
| 2657 | j = this.seriesStack[i]; |
| 2658 | this.target.append(this.series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas', null, this)); |
| 2659 | this.series[j].shadowCanvas.setContext(); |
| 2660 | this.series[j].shadowCanvas._elem.data('seriesIndex', j); |
| 2661 | } |
| 2662 | |
| 2663 | for (i=0; i<this.series.length; i++) { |
| 2664 | // draw series in order of stacking. This affects only |
| 2665 | // order in which canvases are added to dom. |
| 2666 | j = this.seriesStack[i]; |
| 2667 | this.target.append(this.series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas', null, this)); |
| 2668 | this.series[j].canvas.setContext(); |
| 2669 | this.series[j].canvas._elem.data('seriesIndex', j); |
| 2670 | } |
| 2671 | // Need to use filled canvas to capture events in IE. |
| 2672 | // Also, canvas seems to block selection of other elements in document on FF. |
| 2673 | this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas', null, this)); |
| 2674 | this.eventCanvas.setContext(); |
| 2675 | this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)'; |
| 2676 | this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height); |
| 2677 | |
| 2678 | // bind custom event handlers to regular events. |
| 2679 | this.bindCustomEvents(); |
| 2680 | |
| 2681 | // draw legend before series if the series needs to know the legend dimensions. |
| 2682 | if (this.legend.preDraw) { |
| 2683 | this.eventCanvas._elem.before(legendElem); |
| 2684 | this.legend.pack(legendPadding); |
| 2685 | if (this.legend._elem) { |
| 2686 | this.drawSeries({legendInfo:{location:this.legend.location, placement:this.legend.placement, width:this.legend.getWidth(), height:this.legend.getHeight(), xoffset:this.legend.xoffset, yoffset:this.legend.yoffset}}); |
| 2687 | } |
| 2688 | else { |
| 2689 | this.drawSeries(); |
| 2690 | } |
| 2691 | } |
| 2692 | else { // draw series before legend |
| 2693 | this.drawSeries(); |
| 2694 | if (this.series.length) { |
| 2695 | $(this.series[this.series.length-1].canvas._elem).after(legendElem); |
| 2696 | } |
| 2697 | this.legend.pack(legendPadding); |
| 2698 | } |
| 2699 | |
| 2700 | // register event listeners on the overlay canvas |
| 2701 | for (var i=0; i<$.jqplot.eventListenerHooks.length; i++) { |
| 2702 | // in the handler, this will refer to the eventCanvas dom element. |
| 2703 | // make sure there are references back into plot objects. |
| 2704 | this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]); |
| 2705 | } |
| 2706 | |
| 2707 | // register event listeners on the overlay canvas |
| 2708 | for (var i=0; i<this.eventListenerHooks.hooks.length; i++) { |
| 2709 | // in the handler, this will refer to the eventCanvas dom element. |
| 2710 | // make sure there are references back into plot objects. |
| 2711 | this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]); |
| 2712 | } |
| 2713 | |
| 2714 | for (var i=0; i<$.jqplot.postDrawHooks.length; i++) { |
| 2715 | $.jqplot.postDrawHooks[i].call(this); |
| 2716 | } |
| 2717 | |
| 2718 | for (var i=0; i<this.postDrawHooks.hooks.length; i++) { |
| 2719 | this.postDrawHooks.hooks[i].call(this); |
| 2720 | } |
| 2721 | |
| 2722 | if (this.target.is(':visible')) { |
| 2723 | this._drawCount += 1; |
| 2724 | } |
| 2725 | |
| 2726 | this.target.trigger('jqplotPostDraw', [this]); |
| 2727 | } |
| 2728 | }; |
| 2729 | |
| 2730 | this.bindCustomEvents = function() { |
| 2731 | this.eventCanvas._elem.bind('click', {plot:this}, this.onClick); |
| 2732 | this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick); |
| 2733 | this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown); |
| 2734 | this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove); |
| 2735 | this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter); |
| 2736 | this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave); |
| 2737 | if (this.captureRightClick) { |
| 2738 | this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick); |
| 2739 | this.eventCanvas._elem.get(0).oncontextmenu = function() { |
| 2740 | return false; |
| 2741 | }; |
| 2742 | } |
| 2743 | else { |
| 2744 | this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp); |
| 2745 | } |
| 2746 | }; |
| 2747 | |
| 2748 | function getEventPosition(ev) { |
| 2749 | var plot = ev.data.plot; |
| 2750 | var go = plot.eventCanvas._elem.offset(); |
| 2751 | var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top}; |
| 2752 | var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null}; |
| 2753 | var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis']; |
| 2754 | var ax = plot.axes; |
| 2755 | var n, axis; |
| 2756 | for (n=11; n>0; n--) { |
| 2757 | axis = an[n-1]; |
| 2758 | if (ax[axis].show) { |
| 2759 | dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]); |
| 2760 | } |
| 2761 | } |
| 2762 | |
| 2763 | return {offsets:go, gridPos:gridPos, dataPos:dataPos}; |
| 2764 | } |
| 2765 | |
| 2766 | |
| 2767 | // function to check if event location is over a area area |
| 2768 | function checkIntersection(gridpos, plot) { |
| 2769 | var series = plot.series; |
| 2770 | var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang; |
| 2771 | var d0, d, p, pp, points, bw; |
| 2772 | var threshold, t; |
| 2773 | for (k=plot.seriesStack.length-1; k>=0; k--) { |
| 2774 | i = plot.seriesStack[k]; |
| 2775 | s = series[i]; |
| 2776 | switch (s.renderer.constructor) { |
| 2777 | case $.jqplot.BarRenderer: |
| 2778 | case $.jqplot.PyramidRenderer: |
| 2779 | x = gridpos.x; |
| 2780 | y = gridpos.y; |
| 2781 | for (j=0; j<s._barPoints.length; j++) { |
| 2782 | points = s._barPoints[j]; |
| 2783 | p = s.gridData[j]; |
| 2784 | if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) { |
| 2785 | return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]}; |
| 2786 | } |
| 2787 | } |
| 2788 | break; |
| 2789 | |
| 2790 | case $.jqplot.DonutRenderer: |
| 2791 | sa = s.startAngle/180*Math.PI; |
| 2792 | x = gridpos.x - s._center[0]; |
| 2793 | y = gridpos.y - s._center[1]; |
| 2794 | r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); |
| 2795 | if (x > 0 && -y >= 0) { |
| 2796 | theta = 2*Math.PI - Math.atan(-y/x); |
| 2797 | } |
| 2798 | else if (x > 0 && -y < 0) { |
| 2799 | theta = -Math.atan(-y/x); |
| 2800 | } |
| 2801 | else if (x < 0) { |
| 2802 | theta = Math.PI - Math.atan(-y/x); |
| 2803 | } |
| 2804 | else if (x == 0 && -y > 0) { |
| 2805 | theta = 3*Math.PI/2; |
| 2806 | } |
| 2807 | else if (x == 0 && -y < 0) { |
| 2808 | theta = Math.PI/2; |
| 2809 | } |
| 2810 | else if (x == 0 && y == 0) { |
| 2811 | theta = 0; |
| 2812 | } |
| 2813 | if (sa) { |
| 2814 | theta -= sa; |
| 2815 | if (theta < 0) { |
| 2816 | theta += 2*Math.PI; |
| 2817 | } |
| 2818 | else if (theta > 2*Math.PI) { |
| 2819 | theta -= 2*Math.PI; |
| 2820 | } |
| 2821 | } |
| 2822 | |
| 2823 | sm = s.sliceMargin/180*Math.PI; |
| 2824 | if (r < s._radius && r > s._innerRadius) { |
| 2825 | for (j=0; j<s.gridData.length; j++) { |
| 2826 | minang = (j>0) ? s.gridData[j-1][1]+sm : sm; |
| 2827 | maxang = s.gridData[j][1]; |
| 2828 | if (theta > minang && theta < maxang) { |
| 2829 | return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]}; |
| 2830 | } |
| 2831 | } |
| 2832 | } |
| 2833 | break; |
| 2834 | |
| 2835 | case $.jqplot.PieRenderer: |
| 2836 | sa = s.startAngle/180*Math.PI; |
| 2837 | x = gridpos.x - s._center[0]; |
| 2838 | y = gridpos.y - s._center[1]; |
| 2839 | r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); |
| 2840 | if (x > 0 && -y >= 0) { |
| 2841 | theta = 2*Math.PI - Math.atan(-y/x); |
| 2842 | } |
| 2843 | else if (x > 0 && -y < 0) { |
| 2844 | theta = -Math.atan(-y/x); |
| 2845 | } |
| 2846 | else if (x < 0) { |
| 2847 | theta = Math.PI - Math.atan(-y/x); |
| 2848 | } |
| 2849 | else if (x == 0 && -y > 0) { |
| 2850 | theta = 3*Math.PI/2; |
| 2851 | } |
| 2852 | else if (x == 0 && -y < 0) { |
| 2853 | theta = Math.PI/2; |
| 2854 | } |
| 2855 | else if (x == 0 && y == 0) { |
| 2856 | theta = 0; |
| 2857 | } |
| 2858 | if (sa) { |
| 2859 | theta -= sa; |
| 2860 | if (theta < 0) { |
| 2861 | theta += 2*Math.PI; |
| 2862 | } |
| 2863 | else if (theta > 2*Math.PI) { |
| 2864 | theta -= 2*Math.PI; |
| 2865 | } |
| 2866 | } |
| 2867 | |
| 2868 | sm = s.sliceMargin/180*Math.PI; |
| 2869 | if (r < s._radius) { |
| 2870 | for (j=0; j<s.gridData.length; j++) { |
| 2871 | minang = (j>0) ? s.gridData[j-1][1]+sm : sm; |
| 2872 | maxang = s.gridData[j][1]; |
| 2873 | if (theta > minang && theta < maxang) { |
| 2874 | return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]}; |
| 2875 | } |
| 2876 | } |
| 2877 | } |
| 2878 | break; |
| 2879 | |
| 2880 | case $.jqplot.BubbleRenderer: |
| 2881 | x = gridpos.x; |
| 2882 | y = gridpos.y; |
| 2883 | var ret = null; |
| 2884 | |
| 2885 | if (s.show) { |
| 2886 | for (var j=0; j<s.gridData.length; j++) { |
| 2887 | p = s.gridData[j]; |
| 2888 | d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); |
| 2889 | if (d <= p[2] && (d <= d0 || d0 == null)) { |
| 2890 | d0 = d; |
| 2891 | ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 2892 | } |
| 2893 | } |
| 2894 | if (ret != null) { |
| 2895 | return ret; |
| 2896 | } |
| 2897 | } |
| 2898 | break; |
| 2899 | |
| 2900 | case $.jqplot.FunnelRenderer: |
| 2901 | x = gridpos.x; |
| 2902 | y = gridpos.y; |
| 2903 | var v = s._vertices, |
| 2904 | vfirst = v[0], |
| 2905 | vlast = v[v.length-1], |
| 2906 | lex, |
| 2907 | rex, |
| 2908 | cv; |
| 2909 | |
| 2910 | // equations of right and left sides, returns x, y values given height of section (y value and 2 points) |
| 2911 | |
| 2912 | function findedge (l, p1 , p2) { |
| 2913 | var m = (p1[1] - p2[1])/(p1[0] - p2[0]); |
| 2914 | var b = p1[1] - m*p1[0]; |
| 2915 | var y = l + p1[1]; |
| 2916 | |
| 2917 | return [(y - b)/m, y]; |
| 2918 | } |
| 2919 | |
| 2920 | // check each section |
| 2921 | lex = findedge(y, vfirst[0], vlast[3]); |
| 2922 | rex = findedge(y, vfirst[1], vlast[2]); |
| 2923 | for (j=0; j<v.length; j++) { |
| 2924 | cv = v[j]; |
| 2925 | if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) { |
| 2926 | return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]}; |
| 2927 | } |
| 2928 | } |
| 2929 | break; |
| 2930 | |
| 2931 | case $.jqplot.LineRenderer: |
| 2932 | x = gridpos.x; |
| 2933 | y = gridpos.y; |
| 2934 | r = s.renderer; |
| 2935 | if (s.show) { |
| 2936 | if ((s.fill || (s.renderer.bands.show && s.renderer.bands.fill)) && (!plot.plugins.highlighter || !plot.plugins.highlighter.show)) { |
| 2937 | // first check if it is in bounding box |
| 2938 | var inside = false; |
| 2939 | if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) { |
| 2940 | // now check the crossing number |
| 2941 | |
| 2942 | var numPoints = s._areaPoints.length; |
| 2943 | var ii; |
| 2944 | var j = numPoints-1; |
| 2945 | |
| 2946 | for(var ii=0; ii < numPoints; ii++) { |
| 2947 | var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]]; |
| 2948 | var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]]; |
| 2949 | |
| 2950 | if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y) { |
| 2951 | if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) { |
| 2952 | inside = !inside; |
| 2953 | } |
| 2954 | } |
| 2955 | |
| 2956 | j = ii; |
| 2957 | } |
| 2958 | } |
| 2959 | if (inside) { |
| 2960 | return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints}; |
| 2961 | } |
| 2962 | break; |
| 2963 | |
| 2964 | } |
| 2965 | |
| 2966 | else { |
| 2967 | t = s.markerRenderer.size/2+s.neighborThreshold; |
| 2968 | threshold = (t > 0) ? t : 0; |
| 2969 | for (var j=0; j<s.gridData.length; j++) { |
| 2970 | p = s.gridData[j]; |
| 2971 | // neighbor looks different to OHLC chart. |
| 2972 | if (r.constructor == $.jqplot.OHLCRenderer) { |
| 2973 | if (r.candleStick) { |
| 2974 | var yp = s._yaxis.series_u2p; |
| 2975 | if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { |
| 2976 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 2977 | } |
| 2978 | } |
| 2979 | // if an open hi low close chart |
| 2980 | else if (!r.hlc){ |
| 2981 | var yp = s._yaxis.series_u2p; |
| 2982 | if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { |
| 2983 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 2984 | } |
| 2985 | } |
| 2986 | // a hi low close chart |
| 2987 | else { |
| 2988 | var yp = s._yaxis.series_u2p; |
| 2989 | if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { |
| 2990 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 2991 | } |
| 2992 | } |
| 2993 | |
| 2994 | } |
| 2995 | else if (p[0] != null && p[1] != null){ |
| 2996 | d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); |
| 2997 | if (d <= threshold && (d <= d0 || d0 == null)) { |
| 2998 | d0 = d; |
| 2999 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 3000 | } |
| 3001 | } |
| 3002 | } |
| 3003 | } |
| 3004 | } |
| 3005 | break; |
| 3006 | |
| 3007 | default: |
| 3008 | x = gridpos.x; |
| 3009 | y = gridpos.y; |
| 3010 | r = s.renderer; |
| 3011 | if (s.show) { |
| 3012 | t = s.markerRenderer.size/2+s.neighborThreshold; |
| 3013 | threshold = (t > 0) ? t : 0; |
| 3014 | for (var j=0; j<s.gridData.length; j++) { |
| 3015 | p = s.gridData[j]; |
| 3016 | // neighbor looks different to OHLC chart. |
| 3017 | if (r.constructor == $.jqplot.OHLCRenderer) { |
| 3018 | if (r.candleStick) { |
| 3019 | var yp = s._yaxis.series_u2p; |
| 3020 | if (x >= p[0]-r._bodyWidth/2 && x <= p[0]+r._bodyWidth/2 && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { |
| 3021 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 3022 | } |
| 3023 | } |
| 3024 | // if an open hi low close chart |
| 3025 | else if (!r.hlc){ |
| 3026 | var yp = s._yaxis.series_u2p; |
| 3027 | if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { |
| 3028 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 3029 | } |
| 3030 | } |
| 3031 | // a hi low close chart |
| 3032 | else { |
| 3033 | var yp = s._yaxis.series_u2p; |
| 3034 | if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { |
| 3035 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 3036 | } |
| 3037 | } |
| 3038 | |
| 3039 | } |
| 3040 | else { |
| 3041 | d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); |
| 3042 | if (d <= threshold && (d <= d0 || d0 == null)) { |
| 3043 | d0 = d; |
| 3044 | return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; |
| 3045 | } |
| 3046 | } |
| 3047 | } |
| 3048 | } |
| 3049 | break; |
| 3050 | } |
| 3051 | } |
| 3052 | |
| 3053 | return null; |
| 3054 | } |
| 3055 | |
| 3056 | |
| 3057 | |
| 3058 | this.onClick = function(ev) { |
| 3059 | // Event passed in is normalized and will have data attribute. |
| 3060 | // Event passed out is unnormalized. |
| 3061 | var positions = getEventPosition(ev); |
| 3062 | var p = ev.data.plot; |
| 3063 | var neighbor = checkIntersection(positions.gridPos, p); |
| 3064 | var evt = jQuery.Event('jqplotClick'); |
| 3065 | evt.pageX = ev.pageX; |
| 3066 | evt.pageY = ev.pageY; |
| 3067 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); |
| 3068 | }; |
| 3069 | |
| 3070 | this.onDblClick = function(ev) { |
| 3071 | // Event passed in is normalized and will have data attribute. |
| 3072 | // Event passed out is unnormalized. |
| 3073 | var positions = getEventPosition(ev); |
| 3074 | var p = ev.data.plot; |
| 3075 | var neighbor = checkIntersection(positions.gridPos, p); |
| 3076 | var evt = jQuery.Event('jqplotDblClick'); |
| 3077 | evt.pageX = ev.pageX; |
| 3078 | evt.pageY = ev.pageY; |
| 3079 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); |
| 3080 | }; |
| 3081 | |
| 3082 | this.onMouseDown = function(ev) { |
| 3083 | var positions = getEventPosition(ev); |
| 3084 | var p = ev.data.plot; |
| 3085 | var neighbor = checkIntersection(positions.gridPos, p); |
| 3086 | var evt = jQuery.Event('jqplotMouseDown'); |
| 3087 | evt.pageX = ev.pageX; |
| 3088 | evt.pageY = ev.pageY; |
| 3089 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); |
| 3090 | }; |
| 3091 | |
| 3092 | this.onMouseUp = function(ev) { |
| 3093 | var positions = getEventPosition(ev); |
| 3094 | var evt = jQuery.Event('jqplotMouseUp'); |
| 3095 | evt.pageX = ev.pageX; |
| 3096 | evt.pageY = ev.pageY; |
| 3097 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]); |
| 3098 | }; |
| 3099 | |
| 3100 | this.onRightClick = function(ev) { |
| 3101 | var positions = getEventPosition(ev); |
| 3102 | var p = ev.data.plot; |
| 3103 | var neighbor = checkIntersection(positions.gridPos, p); |
| 3104 | if (p.captureRightClick) { |
| 3105 | if (ev.which == 3) { |
| 3106 | var evt = jQuery.Event('jqplotRightClick'); |
| 3107 | evt.pageX = ev.pageX; |
| 3108 | evt.pageY = ev.pageY; |
| 3109 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); |
| 3110 | } |
| 3111 | else { |
| 3112 | var evt = jQuery.Event('jqplotMouseUp'); |
| 3113 | evt.pageX = ev.pageX; |
| 3114 | evt.pageY = ev.pageY; |
| 3115 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); |
| 3116 | } |
| 3117 | } |
| 3118 | }; |
| 3119 | |
| 3120 | this.onMouseMove = function(ev) { |
| 3121 | var positions = getEventPosition(ev); |
| 3122 | var p = ev.data.plot; |
| 3123 | var neighbor = checkIntersection(positions.gridPos, p); |
| 3124 | var evt = jQuery.Event('jqplotMouseMove'); |
| 3125 | evt.pageX = ev.pageX; |
| 3126 | evt.pageY = ev.pageY; |
| 3127 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); |
| 3128 | }; |
| 3129 | |
| 3130 | this.onMouseEnter = function(ev) { |
| 3131 | var positions = getEventPosition(ev); |
| 3132 | var p = ev.data.plot; |
| 3133 | var evt = jQuery.Event('jqplotMouseEnter'); |
| 3134 | evt.pageX = ev.pageX; |
| 3135 | evt.pageY = ev.pageY; |
| 3136 | evt.relatedTarget = ev.relatedTarget; |
| 3137 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]); |
| 3138 | }; |
| 3139 | |
| 3140 | this.onMouseLeave = function(ev) { |
| 3141 | var positions = getEventPosition(ev); |
| 3142 | var p = ev.data.plot; |
| 3143 | var evt = jQuery.Event('jqplotMouseLeave'); |
| 3144 | evt.pageX = ev.pageX; |
| 3145 | evt.pageY = ev.pageY; |
| 3146 | evt.relatedTarget = ev.relatedTarget; |
| 3147 | $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]); |
| 3148 | }; |
| 3149 | |
| 3150 | // method: drawSeries |
| 3151 | // Redraws all or just one series on the plot. No axis scaling |
| 3152 | // is performed and no other elements on the plot are redrawn. |
| 3153 | // options is an options object to pass on to the series renderers. |
| 3154 | // It can be an empty object {}. idx is the series index |
| 3155 | // to redraw if only one series is to be redrawn. |
| 3156 | this.drawSeries = function(options, idx){ |
| 3157 | var i, series, ctx; |
| 3158 | // if only one argument passed in and it is a number, use it ad idx. |
| 3159 | idx = (typeof(options) === "number" && idx == null) ? options : idx; |
| 3160 | options = (typeof(options) === "object") ? options : {}; |
| 3161 | // draw specified series |
| 3162 | if (idx != undefined) { |
| 3163 | series = this.series[idx]; |
| 3164 | ctx = series.shadowCanvas._ctx; |
| 3165 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 3166 | series.drawShadow(ctx, options, this); |
| 3167 | ctx = series.canvas._ctx; |
| 3168 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 3169 | series.draw(ctx, options, this); |
| 3170 | if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) { |
| 3171 | if (idx < this.series.length - 1) { |
| 3172 | this.drawSeries(idx+1); |
| 3173 | } |
| 3174 | } |
| 3175 | } |
| 3176 | |
| 3177 | else { |
| 3178 | // if call series drawShadow method first, in case all series shadows |
| 3179 | // should be drawn before any series. This will ensure, like for |
| 3180 | // stacked bar plots, that shadows don't overlap series. |
| 3181 | for (i=0; i<this.series.length; i++) { |
| 3182 | // first clear the canvas |
| 3183 | series = this.series[i]; |
| 3184 | ctx = series.shadowCanvas._ctx; |
| 3185 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 3186 | series.drawShadow(ctx, options, this); |
| 3187 | ctx = series.canvas._ctx; |
| 3188 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
| 3189 | series.draw(ctx, options, this); |
| 3190 | } |
| 3191 | } |
| 3192 | options = idx = i = series = ctx = null; |
| 3193 | }; |
| 3194 | |
| 3195 | // method: moveSeriesToFront |
| 3196 | // This method requires jQuery 1.4+ |
| 3197 | // Moves the specified series canvas in front of all other series canvases. |
| 3198 | // This effectively "draws" the specified series on top of all other series, |
| 3199 | // although it is performed through DOM manipulation, no redrawing is performed. |
| 3200 | // |
| 3201 | // Parameters: |
| 3202 | // idx - 0 based index of the series to move. This will be the index of the series |
| 3203 | // as it was first passed into the jqplot function. |
| 3204 | this.moveSeriesToFront = function (idx) { |
| 3205 | idx = parseInt(idx, 10); |
| 3206 | var stackIndex = $.inArray(idx, this.seriesStack); |
| 3207 | // if already in front, return |
| 3208 | if (stackIndex == -1) { |
| 3209 | return; |
| 3210 | } |
| 3211 | if (stackIndex == this.seriesStack.length -1) { |
| 3212 | this.previousSeriesStack = this.seriesStack.slice(0); |
| 3213 | return; |
| 3214 | } |
| 3215 | var opidx = this.seriesStack[this.seriesStack.length -1]; |
| 3216 | var serelem = this.series[idx].canvas._elem.detach(); |
| 3217 | var shadelem = this.series[idx].shadowCanvas._elem.detach(); |
| 3218 | this.series[opidx].shadowCanvas._elem.after(shadelem); |
| 3219 | this.series[opidx].canvas._elem.after(serelem); |
| 3220 | this.previousSeriesStack = this.seriesStack.slice(0); |
| 3221 | this.seriesStack.splice(stackIndex, 1); |
| 3222 | this.seriesStack.push(idx); |
| 3223 | }; |
| 3224 | |
| 3225 | // method: moveSeriesToBack |
| 3226 | // This method requires jQuery 1.4+ |
| 3227 | // Moves the specified series canvas behind all other series canvases. |
| 3228 | // |
| 3229 | // Parameters: |
| 3230 | // idx - 0 based index of the series to move. This will be the index of the series |
| 3231 | // as it was first passed into the jqplot function. |
| 3232 | this.moveSeriesToBack = function (idx) { |
| 3233 | idx = parseInt(idx, 10); |
| 3234 | var stackIndex = $.inArray(idx, this.seriesStack); |
| 3235 | // if already in back, return |
| 3236 | if (stackIndex == 0 || stackIndex == -1) { |
| 3237 | return; |
| 3238 | } |
| 3239 | var opidx = this.seriesStack[0]; |
| 3240 | var serelem = this.series[idx].canvas._elem.detach(); |
| 3241 | var shadelem = this.series[idx].shadowCanvas._elem.detach(); |
| 3242 | this.series[opidx].shadowCanvas._elem.before(shadelem); |
| 3243 | this.series[opidx].canvas._elem.before(serelem); |
| 3244 | this.previousSeriesStack = this.seriesStack.slice(0); |
| 3245 | this.seriesStack.splice(stackIndex, 1); |
| 3246 | this.seriesStack.unshift(idx); |
| 3247 | }; |
| 3248 | |
| 3249 | // method: restorePreviousSeriesOrder |
| 3250 | // This method requires jQuery 1.4+ |
| 3251 | // Restore the series canvas order to its previous state. |
| 3252 | // Useful to put a series back where it belongs after moving |
| 3253 | // it to the front. |
| 3254 | this.restorePreviousSeriesOrder = function () { |
| 3255 | var i, j, serelem, shadelem, temp, move, keep; |
| 3256 | // if no change, return. |
| 3257 | if (this.seriesStack == this.previousSeriesStack) { |
| 3258 | return; |
| 3259 | } |
| 3260 | for (i=1; i<this.previousSeriesStack.length; i++) { |
| 3261 | move = this.previousSeriesStack[i]; |
| 3262 | keep = this.previousSeriesStack[i-1]; |
| 3263 | serelem = this.series[move].canvas._elem.detach(); |
| 3264 | shadelem = this.series[move].shadowCanvas._elem.detach(); |
| 3265 | this.series[keep].shadowCanvas._elem.after(shadelem); |
| 3266 | this.series[keep].canvas._elem.after(serelem); |
| 3267 | } |
| 3268 | temp = this.seriesStack.slice(0); |
| 3269 | this.seriesStack = this.previousSeriesStack.slice(0); |
| 3270 | this.previousSeriesStack = temp; |
| 3271 | }; |
| 3272 | |
| 3273 | // method: restoreOriginalSeriesOrder |
| 3274 | // This method requires jQuery 1.4+ |
| 3275 | // Restore the series canvas order to its original order |
| 3276 | // when the plot was created. |
| 3277 | this.restoreOriginalSeriesOrder = function () { |
| 3278 | var i, j, arr=[], serelem, shadelem; |
| 3279 | for (i=0; i<this.series.length; i++) { |
| 3280 | arr.push(i); |
| 3281 | } |
| 3282 | if (this.seriesStack == arr) { |
| 3283 | return; |
| 3284 | } |
| 3285 | this.previousSeriesStack = this.seriesStack.slice(0); |
| 3286 | this.seriesStack = arr; |
| 3287 | for (i=1; i<this.seriesStack.length; i++) { |
| 3288 | serelem = this.series[i].canvas._elem.detach(); |
| 3289 | shadelem = this.series[i].shadowCanvas._elem.detach(); |
| 3290 | this.series[i-1].shadowCanvas._elem.after(shadelem); |
| 3291 | this.series[i-1].canvas._elem.after(serelem); |
| 3292 | } |
| 3293 | }; |
| 3294 | |
| 3295 | this.activateTheme = function (name) { |
| 3296 | this.themeEngine.activate(this, name); |
| 3297 | }; |
| 3298 | } |
| 3299 | |
| 3300 | |
| 3301 | // conpute a highlight color or array of highlight colors from given colors. |
| 3302 | $.jqplot.computeHighlightColors = function(colors) { |
| 3303 | var ret; |
| 3304 | if (jQuery.isArray(colors)) { |
| 3305 | ret = []; |
| 3306 | for (var i=0; i<colors.length; i++){ |
| 3307 | var rgba = $.jqplot.getColorComponents(colors[i]); |
| 3308 | var newrgb = [rgba[0], rgba[1], rgba[2]]; |
| 3309 | var sum = newrgb[0] + newrgb[1] + newrgb[2]; |
| 3310 | for (var j=0; j<3; j++) { |
| 3311 | // when darkening, lowest color component can be is 60. |
| 3312 | newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90; |
| 3313 | newrgb[j] = parseInt(newrgb[j], 10); |
| 3314 | (newrgb[j] > 255) ? 255 : newrgb[j]; |
| 3315 | } |
| 3316 | // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5; |
| 3317 | // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2; |
| 3318 | newrgb[3] = 0.3 + 0.35 * rgba[3]; |
| 3319 | ret.push('rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')'); |
| 3320 | } |
| 3321 | } |
| 3322 | else { |
| 3323 | var rgba = $.jqplot.getColorComponents(colors); |
| 3324 | var newrgb = [rgba[0], rgba[1], rgba[2]]; |
| 3325 | var sum = newrgb[0] + newrgb[1] + newrgb[2]; |
| 3326 | for (var j=0; j<3; j++) { |
| 3327 | // when darkening, lowest color component can be is 60. |
| 3328 | // newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]); |
| 3329 | // newrgb[j] = parseInt(newrgb[j], 10); |
| 3330 | newrgb[j] = (sum > 660) ? newrgb[j] * 0.85 : 0.73 * newrgb[j] + 90; |
| 3331 | newrgb[j] = parseInt(newrgb[j], 10); |
| 3332 | (newrgb[j] > 255) ? 255 : newrgb[j]; |
| 3333 | } |
| 3334 | // newrgb[3] = (rgba[3] > 0.4) ? rgba[3] * 0.4 : rgba[3] * 1.5; |
| 3335 | // newrgb[3] = (rgba[3] > 0.5) ? 0.8 * rgba[3] - .1 : rgba[3] + 0.2; |
| 3336 | newrgb[3] = 0.3 + 0.35 * rgba[3]; |
| 3337 | ret = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+newrgb[3]+')'; |
| 3338 | } |
| 3339 | return ret; |
| 3340 | }; |
| 3341 | |
| 3342 | $.jqplot.ColorGenerator = function(colors) { |
| 3343 | colors = colors || $.jqplot.config.defaultColors; |
| 3344 | var idx = 0; |
| 3345 | |
| 3346 | this.next = function () { |
| 3347 | if (idx < colors.length) { |
| 3348 | return colors[idx++]; |
| 3349 | } |
| 3350 | else { |
| 3351 | idx = 0; |
| 3352 | return colors[idx++]; |
| 3353 | } |
| 3354 | }; |
| 3355 | |
| 3356 | this.previous = function () { |
| 3357 | if (idx > 0) { |
| 3358 | return colors[idx--]; |
| 3359 | } |
| 3360 | else { |
| 3361 | idx = colors.length-1; |
| 3362 | return colors[idx]; |
| 3363 | } |
| 3364 | }; |
| 3365 | |
| 3366 | // get a color by index without advancing pointer. |
| 3367 | this.get = function(i) { |
| 3368 | var idx = i - colors.length * Math.floor(i/colors.length); |
| 3369 | return colors[idx]; |
| 3370 | }; |
| 3371 | |
| 3372 | this.setColors = function(c) { |
| 3373 | colors = c; |
| 3374 | }; |
| 3375 | |
| 3376 | this.reset = function() { |
| 3377 | idx = 0; |
| 3378 | }; |
| 3379 | |
| 3380 | this.getIndex = function() { |
| 3381 | return idx; |
| 3382 | }; |
| 3383 | |
| 3384 | this.setIndex = function(index) { |
| 3385 | idx = index; |
| 3386 | }; |
| 3387 | }; |
| 3388 | |
| 3389 | // convert a hex color string to rgb string. |
| 3390 | // h - 3 or 6 character hex string, with or without leading # |
| 3391 | // a - optional alpha |
| 3392 | $.jqplot.hex2rgb = function(h, a) { |
| 3393 | h = h.replace('#', ''); |
| 3394 | if (h.length == 3) { |
| 3395 | h = h.charAt(0)+h.charAt(0)+h.charAt(1)+h.charAt(1)+h.charAt(2)+h.charAt(2); |
| 3396 | } |
| 3397 | var rgb; |
| 3398 | rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16); |
| 3399 | if (a) { |
| 3400 | rgb += ', '+a; |
| 3401 | } |
| 3402 | rgb += ')'; |
| 3403 | return rgb; |
| 3404 | }; |
| 3405 | |
| 3406 | // convert an rgb color spec to a hex spec. ignore any alpha specification. |
| 3407 | $.jqplot.rgb2hex = function(s) { |
| 3408 | var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/; |
| 3409 | var m = s.match(pat); |
| 3410 | var h = '#'; |
| 3411 | for (var i=1; i<4; i++) { |
| 3412 | var temp; |
| 3413 | if (m[i].search(/%/) != -1) { |
| 3414 | temp = parseInt(255*m[i]/100, 10).toString(16); |
| 3415 | if (temp.length == 1) { |
| 3416 | temp = '0'+temp; |
| 3417 | } |
| 3418 | } |
| 3419 | else { |
| 3420 | temp = parseInt(m[i], 10).toString(16); |
| 3421 | if (temp.length == 1) { |
| 3422 | temp = '0'+temp; |
| 3423 | } |
| 3424 | } |
| 3425 | h += temp; |
| 3426 | } |
| 3427 | return h; |
| 3428 | }; |
| 3429 | |
| 3430 | // given a css color spec, return an rgb css color spec |
| 3431 | $.jqplot.normalize2rgb = function(s, a) { |
| 3432 | if (s.search(/^ *rgba?\(/) != -1) { |
| 3433 | return s; |
| 3434 | } |
| 3435 | else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) { |
| 3436 | return $.jqplot.hex2rgb(s, a); |
| 3437 | } |
| 3438 | else { |
| 3439 | throw 'invalid color spec'; |
| 3440 | } |
| 3441 | }; |
| 3442 | |
| 3443 | // extract the r, g, b, a color components out of a css color spec. |
| 3444 | $.jqplot.getColorComponents = function(s) { |
| 3445 | // check to see if a color keyword. |
| 3446 | s = $.jqplot.colorKeywordMap[s] || s; |
| 3447 | var rgb = $.jqplot.normalize2rgb(s); |
| 3448 | var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/; |
| 3449 | var m = rgb.match(pat); |
| 3450 | var ret = []; |
| 3451 | for (var i=1; i<4; i++) { |
| 3452 | if (m[i].search(/%/) != -1) { |
| 3453 | ret[i-1] = parseInt(255*m[i]/100, 10); |
| 3454 | } |
| 3455 | else { |
| 3456 | ret[i-1] = parseInt(m[i], 10); |
| 3457 | } |
| 3458 | } |
| 3459 | ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0; |
| 3460 | return ret; |
| 3461 | }; |
| 3462 | |
| 3463 | $.jqplot.colorKeywordMap = { |
| 3464 | aliceblue: 'rgb(240, 248, 255)', |
| 3465 | antiquewhite: 'rgb(250, 235, 215)', |
| 3466 | aqua: 'rgb( 0, 255, 255)', |
| 3467 | aquamarine: 'rgb(127, 255, 212)', |
| 3468 | azure: 'rgb(240, 255, 255)', |
| 3469 | beige: 'rgb(245, 245, 220)', |
| 3470 | bisque: 'rgb(255, 228, 196)', |
| 3471 | black: 'rgb( 0, 0, 0)', |
| 3472 | blanchedalmond: 'rgb(255, 235, 205)', |
| 3473 | blue: 'rgb( 0, 0, 255)', |
| 3474 | blueviolet: 'rgb(138, 43, 226)', |
| 3475 | brown: 'rgb(165, 42, 42)', |
| 3476 | burlywood: 'rgb(222, 184, 135)', |
| 3477 | cadetblue: 'rgb( 95, 158, 160)', |
| 3478 | chartreuse: 'rgb(127, 255, 0)', |
| 3479 | chocolate: 'rgb(210, 105, 30)', |
| 3480 | coral: 'rgb(255, 127, 80)', |
| 3481 | cornflowerblue: 'rgb(100, 149, 237)', |
| 3482 | cornsilk: 'rgb(255, 248, 220)', |
| 3483 | crimson: 'rgb(220, 20, 60)', |
| 3484 | cyan: 'rgb( 0, 255, 255)', |
| 3485 | darkblue: 'rgb( 0, 0, 139)', |
| 3486 | darkcyan: 'rgb( 0, 139, 139)', |
| 3487 | darkgoldenrod: 'rgb(184, 134, 11)', |
| 3488 | darkgray: 'rgb(169, 169, 169)', |
| 3489 | darkgreen: 'rgb( 0, 100, 0)', |
| 3490 | darkgrey: 'rgb(169, 169, 169)', |
| 3491 | darkkhaki: 'rgb(189, 183, 107)', |
| 3492 | darkmagenta: 'rgb(139, 0, 139)', |
| 3493 | darkolivegreen: 'rgb( 85, 107, 47)', |
| 3494 | darkorange: 'rgb(255, 140, 0)', |
| 3495 | darkorchid: 'rgb(153, 50, 204)', |
| 3496 | darkred: 'rgb(139, 0, 0)', |
| 3497 | darksalmon: 'rgb(233, 150, 122)', |
| 3498 | darkseagreen: 'rgb(143, 188, 143)', |
| 3499 | darkslateblue: 'rgb( 72, 61, 139)', |
| 3500 | darkslategray: 'rgb( 47, 79, 79)', |
| 3501 | darkslategrey: 'rgb( 47, 79, 79)', |
| 3502 | darkturquoise: 'rgb( 0, 206, 209)', |
| 3503 | darkviolet: 'rgb(148, 0, 211)', |
| 3504 | deeppink: 'rgb(255, 20, 147)', |
| 3505 | deepskyblue: 'rgb( 0, 191, 255)', |
| 3506 | dimgray: 'rgb(105, 105, 105)', |
| 3507 | dimgrey: 'rgb(105, 105, 105)', |
| 3508 | dodgerblue: 'rgb( 30, 144, 255)', |
| 3509 | firebrick: 'rgb(178, 34, 34)', |
| 3510 | floralwhite: 'rgb(255, 250, 240)', |
| 3511 | forestgreen: 'rgb( 34, 139, 34)', |
| 3512 | fuchsia: 'rgb(255, 0, 255)', |
| 3513 | gainsboro: 'rgb(220, 220, 220)', |
| 3514 | ghostwhite: 'rgb(248, 248, 255)', |
| 3515 | gold: 'rgb(255, 215, 0)', |
| 3516 | goldenrod: 'rgb(218, 165, 32)', |
| 3517 | gray: 'rgb(128, 128, 128)', |
| 3518 | grey: 'rgb(128, 128, 128)', |
| 3519 | green: 'rgb( 0, 128, 0)', |
| 3520 | greenyellow: 'rgb(173, 255, 47)', |
| 3521 | honeydew: 'rgb(240, 255, 240)', |
| 3522 | hotpink: 'rgb(255, 105, 180)', |
| 3523 | indianred: 'rgb(205, 92, 92)', |
| 3524 | indigo: 'rgb( 75, 0, 130)', |
| 3525 | ivory: 'rgb(255, 255, 240)', |
| 3526 | khaki: 'rgb(240, 230, 140)', |
| 3527 | lavender: 'rgb(230, 230, 250)', |
| 3528 | lavenderblush: 'rgb(255, 240, 245)', |
| 3529 | lawngreen: 'rgb(124, 252, 0)', |
| 3530 | lemonchiffon: 'rgb(255, 250, 205)', |
| 3531 | lightblue: 'rgb(173, 216, 230)', |
| 3532 | lightcoral: 'rgb(240, 128, 128)', |
| 3533 | lightcyan: 'rgb(224, 255, 255)', |
| 3534 | lightgoldenrodyellow: 'rgb(250, 250, 210)', |
| 3535 | lightgray: 'rgb(211, 211, 211)', |
| 3536 | lightgreen: 'rgb(144, 238, 144)', |
| 3537 | lightgrey: 'rgb(211, 211, 211)', |
| 3538 | lightpink: 'rgb(255, 182, 193)', |
| 3539 | lightsalmon: 'rgb(255, 160, 122)', |
| 3540 | lightseagreen: 'rgb( 32, 178, 170)', |
| 3541 | lightskyblue: 'rgb(135, 206, 250)', |
| 3542 | lightslategray: 'rgb(119, 136, 153)', |
| 3543 | lightslategrey: 'rgb(119, 136, 153)', |
| 3544 | lightsteelblue: 'rgb(176, 196, 222)', |
| 3545 | lightyellow: 'rgb(255, 255, 224)', |
| 3546 | lime: 'rgb( 0, 255, 0)', |
| 3547 | limegreen: 'rgb( 50, 205, 50)', |
| 3548 | linen: 'rgb(250, 240, 230)', |
| 3549 | magenta: 'rgb(255, 0, 255)', |
| 3550 | maroon: 'rgb(128, 0, 0)', |
| 3551 | mediumaquamarine: 'rgb(102, 205, 170)', |
| 3552 | mediumblue: 'rgb( 0, 0, 205)', |
| 3553 | mediumorchid: 'rgb(186, 85, 211)', |
| 3554 | mediumpurple: 'rgb(147, 112, 219)', |
| 3555 | mediumseagreen: 'rgb( 60, 179, 113)', |
| 3556 | mediumslateblue: 'rgb(123, 104, 238)', |
| 3557 | mediumspringgreen: 'rgb( 0, 250, 154)', |
| 3558 | mediumturquoise: 'rgb( 72, 209, 204)', |
| 3559 | mediumvioletred: 'rgb(199, 21, 133)', |
| 3560 | midnightblue: 'rgb( 25, 25, 112)', |
| 3561 | mintcream: 'rgb(245, 255, 250)', |
| 3562 | mistyrose: 'rgb(255, 228, 225)', |
| 3563 | moccasin: 'rgb(255, 228, 181)', |
| 3564 | navajowhite: 'rgb(255, 222, 173)', |
| 3565 | navy: 'rgb( 0, 0, 128)', |
| 3566 | oldlace: 'rgb(253, 245, 230)', |
| 3567 | olive: 'rgb(128, 128, 0)', |
| 3568 | olivedrab: 'rgb(107, 142, 35)', |
| 3569 | orange: 'rgb(255, 165, 0)', |
| 3570 | orangered: 'rgb(255, 69, 0)', |
| 3571 | orchid: 'rgb(218, 112, 214)', |
| 3572 | palegoldenrod: 'rgb(238, 232, 170)', |
| 3573 | palegreen: 'rgb(152, 251, 152)', |
| 3574 | paleturquoise: 'rgb(175, 238, 238)', |
| 3575 | palevioletred: 'rgb(219, 112, 147)', |
| 3576 | papayawhip: 'rgb(255, 239, 213)', |
| 3577 | peachpuff: 'rgb(255, 218, 185)', |
| 3578 | peru: 'rgb(205, 133, 63)', |
| 3579 | pink: 'rgb(255, 192, 203)', |
| 3580 | plum: 'rgb(221, 160, 221)', |
| 3581 | powderblue: 'rgb(176, 224, 230)', |
| 3582 | purple: 'rgb(128, 0, 128)', |
| 3583 | red: 'rgb(255, 0, 0)', |
| 3584 | rosybrown: 'rgb(188, 143, 143)', |
| 3585 | royalblue: 'rgb( 65, 105, 225)', |
| 3586 | saddlebrown: 'rgb(139, 69, 19)', |
| 3587 | salmon: 'rgb(250, 128, 114)', |
| 3588 | sandybrown: 'rgb(244, 164, 96)', |
| 3589 | seagreen: 'rgb( 46, 139, 87)', |
| 3590 | seashell: 'rgb(255, 245, 238)', |
| 3591 | sienna: 'rgb(160, 82, 45)', |
| 3592 | silver: 'rgb(192, 192, 192)', |
| 3593 | skyblue: 'rgb(135, 206, 235)', |
| 3594 | slateblue: 'rgb(106, 90, 205)', |
| 3595 | slategray: 'rgb(112, 128, 144)', |
| 3596 | slategrey: 'rgb(112, 128, 144)', |
| 3597 | snow: 'rgb(255, 250, 250)', |
| 3598 | springgreen: 'rgb( 0, 255, 127)', |
| 3599 | steelblue: 'rgb( 70, 130, 180)', |
| 3600 | tan: 'rgb(210, 180, 140)', |
| 3601 | teal: 'rgb( 0, 128, 128)', |
| 3602 | thistle: 'rgb(216, 191, 216)', |
| 3603 | tomato: 'rgb(255, 99, 71)', |
| 3604 | turquoise: 'rgb( 64, 224, 208)', |
| 3605 | violet: 'rgb(238, 130, 238)', |
| 3606 | wheat: 'rgb(245, 222, 179)', |
| 3607 | white: 'rgb(255, 255, 255)', |
| 3608 | whitesmoke: 'rgb(245, 245, 245)', |
| 3609 | yellow: 'rgb(255, 255, 0)', |
| 3610 | yellowgreen: 'rgb(154, 205, 50)' |
| 3611 | }; |
| 3612 | |
| 3613 | |
| 3614 | |
| 3615 | // class: $.jqplot.AxisLabelRenderer |
| 3616 | // Renderer to place labels on the axes. |
| 3617 | $.jqplot.AxisLabelRenderer = function(options) { |
| 3618 | // Group: Properties |
| 3619 | $.jqplot.ElemContainer.call(this); |
| 3620 | // name of the axis associated with this tick |
| 3621 | this.axis; |
| 3622 | // prop: show |
| 3623 | // wether or not to show the tick (mark and label). |
| 3624 | this.show = true; |
| 3625 | // prop: label |
| 3626 | // The text or html for the label. |
| 3627 | this.label = ''; |
| 3628 | this.fontFamily = null; |
| 3629 | this.fontSize = null; |
| 3630 | this.textColor = null; |
| 3631 | this._elem; |
| 3632 | // prop: escapeHTML |
| 3633 | // true to escape HTML entities in the label. |
| 3634 | this.escapeHTML = false; |
| 3635 | |
| 3636 | $.extend(true, this, options); |
| 3637 | }; |
| 3638 | |
| 3639 | $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer(); |
| 3640 | $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer; |
| 3641 | |
| 3642 | $.jqplot.AxisLabelRenderer.prototype.init = function(options) { |
| 3643 | $.extend(true, this, options); |
| 3644 | }; |
| 3645 | |
| 3646 | $.jqplot.AxisLabelRenderer.prototype.draw = function(ctx, plot) { |
| 3647 | // Memory Leaks patch |
| 3648 | if (this._elem) { |
| 3649 | this._elem.emptyForce(); |
| 3650 | this._elem = null; |
| 3651 | } |
| 3652 | |
| 3653 | this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>'); |
| 3654 | |
| 3655 | if (Number(this.label)) { |
| 3656 | this._elem.css('white-space', 'nowrap'); |
| 3657 | } |
| 3658 | |
| 3659 | if (!this.escapeHTML) { |
| 3660 | this._elem.html(this.label); |
| 3661 | } |
| 3662 | else { |
| 3663 | this._elem.text(this.label); |
| 3664 | } |
| 3665 | if (this.fontFamily) { |
| 3666 | this._elem.css('font-family', this.fontFamily); |
| 3667 | } |
| 3668 | if (this.fontSize) { |
| 3669 | this._elem.css('font-size', this.fontSize); |
| 3670 | } |
| 3671 | if (this.textColor) { |
| 3672 | this._elem.css('color', this.textColor); |
| 3673 | } |
| 3674 | |
| 3675 | return this._elem; |
| 3676 | }; |
| 3677 | |
| 3678 | $.jqplot.AxisLabelRenderer.prototype.pack = function() { |
| 3679 | }; |
| 3680 | |
| 3681 | // class: $.jqplot.AxisTickRenderer |
| 3682 | // A "tick" object showing the value of a tick/gridline on the plot. |
| 3683 | $.jqplot.AxisTickRenderer = function(options) { |
| 3684 | // Group: Properties |
| 3685 | $.jqplot.ElemContainer.call(this); |
| 3686 | // prop: mark |
| 3687 | // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null. |
| 3688 | this.mark = 'outside'; |
| 3689 | // name of the axis associated with this tick |
| 3690 | this.axis; |
| 3691 | // prop: showMark |
| 3692 | // wether or not to show the mark on the axis. |
| 3693 | this.showMark = true; |
| 3694 | // prop: showGridline |
| 3695 | // wether or not to draw the gridline on the grid at this tick. |
| 3696 | this.showGridline = true; |
| 3697 | // prop: isMinorTick |
| 3698 | // if this is a minor tick. |
| 3699 | this.isMinorTick = false; |
| 3700 | // prop: size |
| 3701 | // Length of the tick beyond the grid in pixels. |
| 3702 | // DEPRECATED: This has been superceeded by markSize |
| 3703 | this.size = 4; |
| 3704 | // prop: markSize |
| 3705 | // Length of the tick marks in pixels. For 'cross' style, length |
| 3706 | // will be stoked above and below axis, so total length will be twice this. |
| 3707 | this.markSize = 6; |
| 3708 | // prop: show |
| 3709 | // wether or not to show the tick (mark and label). |
| 3710 | // Setting this to false requires more testing. It is recommended |
| 3711 | // to set showLabel and showMark to false instead. |
| 3712 | this.show = true; |
| 3713 | // prop: showLabel |
| 3714 | // wether or not to show the label. |
| 3715 | this.showLabel = true; |
| 3716 | this.label = null; |
| 3717 | this.value = null; |
| 3718 | this._styles = {}; |
| 3719 | // prop: formatter |
| 3720 | // A class of a formatter for the tick text. sprintf by default. |
| 3721 | this.formatter = $.jqplot.DefaultTickFormatter; |
| 3722 | // prop: prefix |
| 3723 | // String to prepend to the tick label. |
| 3724 | // Prefix is prepended to the formatted tick label. |
| 3725 | this.prefix = ''; |
| 3726 | // prop: formatString |
| 3727 | // string passed to the formatter. |
| 3728 | this.formatString = ''; |
| 3729 | // prop: fontFamily |
| 3730 | // css spec for the font-family css attribute. |
| 3731 | this.fontFamily; |
| 3732 | // prop: fontSize |
| 3733 | // css spec for the font-size css attribute. |
| 3734 | this.fontSize; |
| 3735 | // prop: textColor |
| 3736 | // css spec for the color attribute. |
| 3737 | this.textColor; |
| 3738 | // prop: escapeHTML |
| 3739 | // true to escape HTML entities in the label. |
| 3740 | this.escapeHTML = false; |
| 3741 | this._elem; |
| 3742 | this._breakTick = false; |
| 3743 | |
| 3744 | $.extend(true, this, options); |
| 3745 | }; |
| 3746 | |
| 3747 | $.jqplot.AxisTickRenderer.prototype.init = function(options) { |
| 3748 | $.extend(true, this, options); |
| 3749 | }; |
| 3750 | |
| 3751 | $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer(); |
| 3752 | $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer; |
| 3753 | |
| 3754 | $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) { |
| 3755 | this.value = value; |
| 3756 | this.axis = axisName; |
| 3757 | if (isMinor) { |
| 3758 | this.isMinorTick = true; |
| 3759 | } |
| 3760 | return this; |
| 3761 | }; |
| 3762 | |
| 3763 | $.jqplot.AxisTickRenderer.prototype.draw = function() { |
| 3764 | if (this.label === null) { |
| 3765 | this.label = this.prefix + this.formatter(this.formatString, this.value); |
| 3766 | } |
| 3767 | var style = {position: 'absolute'}; |
| 3768 | if (Number(this.label)) { |
| 3769 | style['whitSpace'] = 'nowrap'; |
| 3770 | } |
| 3771 | |
| 3772 | // Memory Leaks patch |
| 3773 | if (this._elem) { |
| 3774 | this._elem.emptyForce(); |
| 3775 | this._elem = null; |
| 3776 | } |
| 3777 | |
| 3778 | this._elem = $(document.createElement('div')); |
| 3779 | this._elem.addClass("jqplot-"+this.axis+"-tick"); |
| 3780 | |
| 3781 | if (!this.escapeHTML) { |
| 3782 | this._elem.html(this.label); |
| 3783 | } |
| 3784 | else { |
| 3785 | this._elem.text(this.label); |
| 3786 | } |
| 3787 | |
| 3788 | this._elem.css(style); |
| 3789 | |
| 3790 | for (var s in this._styles) { |
| 3791 | this._elem.css(s, this._styles[s]); |
| 3792 | } |
| 3793 | if (this.fontFamily) { |
| 3794 | this._elem.css('font-family', this.fontFamily); |
| 3795 | } |
| 3796 | if (this.fontSize) { |
| 3797 | this._elem.css('font-size', this.fontSize); |
| 3798 | } |
| 3799 | if (this.textColor) { |
| 3800 | this._elem.css('color', this.textColor); |
| 3801 | } |
| 3802 | if (this._breakTick) { |
| 3803 | this._elem.addClass('jqplot-breakTick'); |
| 3804 | } |
| 3805 | |
| 3806 | return this._elem; |
| 3807 | }; |
| 3808 | |
| 3809 | $.jqplot.DefaultTickFormatter = function (format, val) { |
| 3810 | if (typeof val == 'number') { |
| 3811 | if (!format) { |
| 3812 | format = $.jqplot.config.defaultTickFormatString; |
| 3813 | } |
| 3814 | return $.jqplot.sprintf(format, val); |
| 3815 | } |
| 3816 | else { |
| 3817 | return String(val); |
| 3818 | } |
| 3819 | }; |
| 3820 | |
| 3821 | $.jqplot.AxisTickRenderer.prototype.pack = function() { |
| 3822 | }; |
| 3823 | |
| 3824 | // Class: $.jqplot.CanvasGridRenderer |
| 3825 | // The default jqPlot grid renderer, creating a grid on a canvas element. |
| 3826 | // The renderer has no additional options beyond the <Grid> class. |
| 3827 | $.jqplot.CanvasGridRenderer = function(){ |
| 3828 | this.shadowRenderer = new $.jqplot.ShadowRenderer(); |
| 3829 | }; |
| 3830 | |
| 3831 | // called with context of Grid object |
| 3832 | $.jqplot.CanvasGridRenderer.prototype.init = function(options) { |
| 3833 | this._ctx; |
| 3834 | $.extend(true, this, options); |
| 3835 | // set the shadow renderer options |
| 3836 | var sopts = {lineJoin:'miter', lineCap:'round', fill:false, isarc:false, angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.shadowWidth, closePath:false, strokeStyle:this.shadowColor}; |
| 3837 | this.renderer.shadowRenderer.init(sopts); |
| 3838 | }; |
| 3839 | |
| 3840 | // called with context of Grid. |
| 3841 | $.jqplot.CanvasGridRenderer.prototype.createElement = function(plot) { |
| 3842 | var elem; |
| 3843 | // Memory Leaks patch |
| 3844 | if (this._elem) { |
| 3845 | if ($.jqplot.use_excanvas && window.G_vmlCanvasManager.uninitElement !== undefined) { |
| 3846 | elem = this._elem.get(0); |
| 3847 | window.G_vmlCanvasManager.uninitElement(elem); |
| 3848 | elem = null; |
| 3849 | } |
| 3850 | |
| 3851 | this._elem.emptyForce(); |
| 3852 | this._elem = null; |
| 3853 | } |
| 3854 | |
| 3855 | elem = plot.canvasManager.getCanvas(); |
| 3856 | |
| 3857 | var w = this._plotDimensions.width; |
| 3858 | var h = this._plotDimensions.height; |
| 3859 | elem.width = w; |
| 3860 | elem.height = h; |
| 3861 | this._elem = $(elem); |
| 3862 | this._elem.addClass('jqplot-grid-canvas'); |
| 3863 | this._elem.css({ position: 'absolute', left: 0, top: 0 }); |
| 3864 | |
| 3865 | elem = plot.canvasManager.initCanvas(elem); |
| 3866 | |
| 3867 | this._top = this._offsets.top; |
| 3868 | this._bottom = h - this._offsets.bottom; |
| 3869 | this._left = this._offsets.left; |
| 3870 | this._right = w - this._offsets.right; |
| 3871 | this._width = this._right - this._left; |
| 3872 | this._height = this._bottom - this._top; |
| 3873 | // avoid memory leak |
| 3874 | elem = null; |
| 3875 | return this._elem; |
| 3876 | }; |
| 3877 | |
| 3878 | $.jqplot.CanvasGridRenderer.prototype.draw = function() { |
| 3879 | this._ctx = this._elem.get(0).getContext("2d"); |
| 3880 | var ctx = this._ctx; |
| 3881 | var axes = this._axes; |
| 3882 | // Add the grid onto the grid canvas. This is the bottom most layer. |
| 3883 | ctx.save(); |
| 3884 | ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height); |
| 3885 | ctx.fillStyle = this.backgroundColor || this.background; |
| 3886 | ctx.fillRect(this._left, this._top, this._width, this._height); |
| 3887 | |
| 3888 | ctx.save(); |
| 3889 | ctx.lineJoin = 'miter'; |
| 3890 | ctx.lineCap = 'butt'; |
| 3891 | ctx.lineWidth = this.gridLineWidth; |
| 3892 | ctx.strokeStyle = this.gridLineColor; |
| 3893 | var b, e, s, m; |
| 3894 | var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis']; |
| 3895 | for (var i=4; i>0; i--) { |
| 3896 | var name = ax[i-1]; |
| 3897 | var axis = axes[name]; |
| 3898 | var ticks = axis._ticks; |
| 3899 | var numticks = ticks.length; |
| 3900 | if (axis.show) { |
| 3901 | if (axis.drawBaseline) { |
| 3902 | var bopts = {}; |
| 3903 | if (axis.baselineWidth !== null) { |
| 3904 | bopts.lineWidth = axis.baselineWidth; |
| 3905 | } |
| 3906 | if (axis.baselineColor !== null) { |
| 3907 | bopts.strokeStyle = axis.baselineColor; |
| 3908 | } |
| 3909 | switch (name) { |
| 3910 | case 'xaxis': |
| 3911 | drawLine (this._left, this._bottom, this._right, this._bottom, bopts); |
| 3912 | break; |
| 3913 | case 'yaxis': |
| 3914 | drawLine (this._left, this._bottom, this._left, this._top, bopts); |
| 3915 | break; |
| 3916 | case 'x2axis': |
| 3917 | drawLine (this._left, this._bottom, this._right, this._bottom, bopts); |
| 3918 | break; |
| 3919 | case 'y2axis': |
| 3920 | drawLine (this._right, this._bottom, this._right, this._top, bopts); |
| 3921 | break; |
| 3922 | } |
| 3923 | } |
| 3924 | for (var j=numticks; j>0; j--) { |
| 3925 | var t = ticks[j-1]; |
| 3926 | if (t.show) { |
| 3927 | var pos = Math.round(axis.u2p(t.value)) + 0.5; |
| 3928 | switch (name) { |
| 3929 | case 'xaxis': |
| 3930 | // draw the grid line if we should |
| 3931 | if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { |
| 3932 | drawLine(pos, this._top, pos, this._bottom); |
| 3933 | } |
| 3934 | // draw the mark |
| 3935 | if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { |
| 3936 | s = t.markSize; |
| 3937 | m = t.mark; |
| 3938 | var pos = Math.round(axis.u2p(t.value)) + 0.5; |
| 3939 | switch (m) { |
| 3940 | case 'outside': |
| 3941 | b = this._bottom; |
| 3942 | e = this._bottom+s; |
| 3943 | break; |
| 3944 | case 'inside': |
| 3945 | b = this._bottom-s; |
| 3946 | e = this._bottom; |
| 3947 | break; |
| 3948 | case 'cross': |
| 3949 | b = this._bottom-s; |
| 3950 | e = this._bottom+s; |
| 3951 | break; |
| 3952 | default: |
| 3953 | b = this._bottom; |
| 3954 | e = this._bottom+s; |
| 3955 | break; |
| 3956 | } |
| 3957 | // draw the shadow |
| 3958 | if (this.shadow) { |
| 3959 | this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false}); |
| 3960 | } |
| 3961 | // draw the line |
| 3962 | drawLine(pos, b, pos, e); |
| 3963 | } |
| 3964 | break; |
| 3965 | case 'yaxis': |
| 3966 | // draw the grid line |
| 3967 | if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { |
| 3968 | drawLine(this._right, pos, this._left, pos); |
| 3969 | } |
| 3970 | // draw the mark |
| 3971 | if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { |
| 3972 | s = t.markSize; |
| 3973 | m = t.mark; |
| 3974 | var pos = Math.round(axis.u2p(t.value)) + 0.5; |
| 3975 | switch (m) { |
| 3976 | case 'outside': |
| 3977 | b = this._left-s; |
| 3978 | e = this._left; |
| 3979 | break; |
| 3980 | case 'inside': |
| 3981 | b = this._left; |
| 3982 | e = this._left+s; |
| 3983 | break; |
| 3984 | case 'cross': |
| 3985 | b = this._left-s; |
| 3986 | e = this._left+s; |
| 3987 | break; |
| 3988 | default: |
| 3989 | b = this._left-s; |
| 3990 | e = this._left; |
| 3991 | break; |
| 3992 | } |
| 3993 | // draw the shadow |
| 3994 | if (this.shadow) { |
| 3995 | this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false}); |
| 3996 | } |
| 3997 | drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); |
| 3998 | } |
| 3999 | break; |
| 4000 | case 'x2axis': |
| 4001 | // draw the grid line |
| 4002 | if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { |
| 4003 | drawLine(pos, this._bottom, pos, this._top); |
| 4004 | } |
| 4005 | // draw the mark |
| 4006 | if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { |
| 4007 | s = t.markSize; |
| 4008 | m = t.mark; |
| 4009 | var pos = Math.round(axis.u2p(t.value)) + 0.5; |
| 4010 | switch (m) { |
| 4011 | case 'outside': |
| 4012 | b = this._top-s; |
| 4013 | e = this._top; |
| 4014 | break; |
| 4015 | case 'inside': |
| 4016 | b = this._top; |
| 4017 | e = this._top+s; |
| 4018 | break; |
| 4019 | case 'cross': |
| 4020 | b = this._top-s; |
| 4021 | e = this._top+s; |
| 4022 | break; |
| 4023 | default: |
| 4024 | b = this._top-s; |
| 4025 | e = this._top; |
| 4026 | break; |
| 4027 | } |
| 4028 | // draw the shadow |
| 4029 | if (this.shadow) { |
| 4030 | this.renderer.shadowRenderer.draw(ctx, [[pos,b],[pos,e]], {lineCap:'butt', lineWidth:this.gridLineWidth, offset:this.gridLineWidth*0.75, depth:2, fill:false, closePath:false}); |
| 4031 | } |
| 4032 | drawLine(pos, b, pos, e); |
| 4033 | } |
| 4034 | break; |
| 4035 | case 'y2axis': |
| 4036 | // draw the grid line |
| 4037 | if (t.showGridline && this.drawGridlines && ((!t.isMinorTick && axis.drawMajorGridlines) || (t.isMinorTick && axis.drawMinorGridlines)) ) { |
| 4038 | drawLine(this._left, pos, this._right, pos); |
| 4039 | } |
| 4040 | // draw the mark |
| 4041 | if (t.showMark && t.mark && ((!t.isMinorTick && axis.drawMajorTickMarks) || (t.isMinorTick && axis.drawMinorTickMarks)) ) { |
| 4042 | s = t.markSize; |
| 4043 | m = t.mark; |
| 4044 | var pos = Math.round(axis.u2p(t.value)) + 0.5; |
| 4045 | switch (m) { |
| 4046 | case 'outside': |
| 4047 | b = this._right; |
| 4048 | e = this._right+s; |
| 4049 | break; |
| 4050 | case 'inside': |
| 4051 | b = this._right-s; |
| 4052 | e = this._right; |
| 4053 | break; |
| 4054 | case 'cross': |
| 4055 | b = this._right-s; |
| 4056 | e = this._right+s; |
| 4057 | break; |
| 4058 | default: |
| 4059 | b = this._right; |
| 4060 | e = this._right+s; |
| 4061 | break; |
| 4062 | } |
| 4063 | // draw the shadow |
| 4064 | if (this.shadow) { |
| 4065 | this.renderer.shadowRenderer.draw(ctx, [[b, pos], [e, pos]], {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false}); |
| 4066 | } |
| 4067 | drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); |
| 4068 | } |
| 4069 | break; |
| 4070 | default: |
| 4071 | break; |
| 4072 | } |
| 4073 | } |
| 4074 | } |
| 4075 | t = null; |
| 4076 | } |
| 4077 | axis = null; |
| 4078 | ticks = null; |
| 4079 | } |
| 4080 | // Now draw grid lines for additional y axes |
| 4081 | ////// |
| 4082 | // TO DO: handle yMidAxis |
| 4083 | ////// |
| 4084 | ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis']; |
| 4085 | for (var i=7; i>0; i--) { |
| 4086 | var axis = axes[ax[i-1]]; |
| 4087 | var ticks = axis._ticks; |
| 4088 | if (axis.show) { |
| 4089 | var tn = ticks[axis.numberTicks-1]; |
| 4090 | var t0 = ticks[0]; |
| 4091 | var left = axis.getLeft(); |
| 4092 | var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]]; |
| 4093 | // draw the shadow |
| 4094 | if (this.shadow) { |
| 4095 | this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false}); |
| 4096 | } |
| 4097 | // draw the line |
| 4098 | drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth}); |
| 4099 | // draw the tick marks |
| 4100 | for (var j=ticks.length; j>0; j--) { |
| 4101 | var t = ticks[j-1]; |
| 4102 | s = t.markSize; |
| 4103 | m = t.mark; |
| 4104 | var pos = Math.round(axis.u2p(t.value)) + 0.5; |
| 4105 | if (t.showMark && t.mark) { |
| 4106 | switch (m) { |
| 4107 | case 'outside': |
| 4108 | b = left; |
| 4109 | e = left+s; |
| 4110 | break; |
| 4111 | case 'inside': |
| 4112 | b = left-s; |
| 4113 | e = left; |
| 4114 | break; |
| 4115 | case 'cross': |
| 4116 | b = left-s; |
| 4117 | e = left+s; |
| 4118 | break; |
| 4119 | default: |
| 4120 | b = left; |
| 4121 | e = left+s; |
| 4122 | break; |
| 4123 | } |
| 4124 | points = [[b,pos], [e,pos]]; |
| 4125 | // draw the shadow |
| 4126 | if (this.shadow) { |
| 4127 | this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false}); |
| 4128 | } |
| 4129 | // draw the line |
| 4130 | drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); |
| 4131 | } |
| 4132 | t = null; |
| 4133 | } |
| 4134 | t0 = null; |
| 4135 | } |
| 4136 | axis = null; |
| 4137 | ticks = null; |
| 4138 | } |
| 4139 | |
| 4140 | ctx.restore(); |
| 4141 | |
| 4142 | function drawLine(bx, by, ex, ey, opts) { |
| 4143 | ctx.save(); |
| 4144 | opts = opts || {}; |
| 4145 | if (opts.lineWidth == null || opts.lineWidth != 0){ |
| 4146 | $.extend(true, ctx, opts); |
| 4147 | ctx.beginPath(); |
| 4148 | ctx.moveTo(bx, by); |
| 4149 | ctx.lineTo(ex, ey); |
| 4150 | ctx.stroke(); |
| 4151 | ctx.restore(); |
| 4152 | } |
| 4153 | } |
| 4154 | |
| 4155 | if (this.shadow) { |
| 4156 | var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]]; |
| 4157 | this.renderer.shadowRenderer.draw(ctx, points); |
| 4158 | } |
| 4159 | // Now draw border around grid. Use axis border definitions. start at |
| 4160 | // upper left and go clockwise. |
| 4161 | if (this.borderWidth != 0 && this.drawBorder) { |
| 4162 | drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth}); |
| 4163 | drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth}); |
| 4164 | drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth}); |
| 4165 | drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth}); |
| 4166 | } |
| 4167 | // ctx.lineWidth = this.borderWidth; |
| 4168 | // ctx.strokeStyle = this.borderColor; |
| 4169 | // ctx.strokeRect(this._left, this._top, this._width, this._height); |
| 4170 | |
| 4171 | ctx.restore(); |
| 4172 | ctx = null; |
| 4173 | axes = null; |
| 4174 | }; |
| 4175 | |
| 4176 | // Class: $.jqplot.DivTitleRenderer |
| 4177 | // The default title renderer for jqPlot. This class has no options beyond the <Title> class. |
| 4178 | $.jqplot.DivTitleRenderer = function() { |
| 4179 | }; |
| 4180 | |
| 4181 | $.jqplot.DivTitleRenderer.prototype.init = function(options) { |
| 4182 | $.extend(true, this, options); |
| 4183 | }; |
| 4184 | |
| 4185 | $.jqplot.DivTitleRenderer.prototype.draw = function() { |
| 4186 | // Memory Leaks patch |
| 4187 | if (this._elem) { |
| 4188 | this._elem.emptyForce(); |
| 4189 | this._elem = null; |
| 4190 | } |
| 4191 | |
| 4192 | var r = this.renderer; |
| 4193 | var elem = document.createElement('div'); |
| 4194 | this._elem = $(elem); |
| 4195 | this._elem.addClass('jqplot-title'); |
| 4196 | |
| 4197 | if (!this.text) { |
| 4198 | this.show = false; |
| 4199 | this._elem.height(0); |
| 4200 | this._elem.width(0); |
| 4201 | } |
| 4202 | else if (this.text) { |
| 4203 | var color; |
| 4204 | if (this.color) { |
| 4205 | color = this.color; |
| 4206 | } |
| 4207 | else if (this.textColor) { |
| 4208 | color = this.textColor; |
| 4209 | } |
| 4210 | |
| 4211 | // don't trust that a stylesheet is present, set the position. |
| 4212 | var styles = {position:'absolute', top:'0px', left:'0px'}; |
| 4213 | |
| 4214 | if (this._plotWidth) { |
| 4215 | styles['width'] = this._plotWidth+'px'; |
| 4216 | } |
| 4217 | if (this.fontSize) { |
| 4218 | styles['fontSize'] = this.fontSize; |
| 4219 | } |
| 4220 | if (typeof this.textAlign === 'string') { |
| 4221 | styles['textAlign'] = this.textAlign; |
| 4222 | } |
| 4223 | else { |
| 4224 | styles['textAlign'] = 'center'; |
| 4225 | } |
| 4226 | if (color) { |
| 4227 | styles['color'] = color; |
| 4228 | } |
| 4229 | if (this.paddingBottom) { |
| 4230 | styles['paddingBottom'] = this.paddingBottom; |
| 4231 | } |
| 4232 | if (this.fontFamily) { |
| 4233 | styles['fontFamily'] = this.fontFamily; |
| 4234 | } |
| 4235 | |
| 4236 | this._elem.css(styles); |
| 4237 | if (this.escapeHtml) { |
| 4238 | this._elem.text(this.text); |
| 4239 | } |
| 4240 | else { |
| 4241 | this._elem.html(this.text); |
| 4242 | } |
| 4243 | |
| 4244 | |
| 4245 | // styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : ''; |
| 4246 | // styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : ''; |
| 4247 | // styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;'; |
| 4248 | // styletext += (color) ? 'color:'+color+';' : ''; |
| 4249 | // styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : ''; |
| 4250 | // this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>'); |
| 4251 | // if (this.fontFamily) { |
| 4252 | // this._elem.css('font-family', this.fontFamily); |
| 4253 | // } |
| 4254 | } |
| 4255 | |
| 4256 | elem = null; |
| 4257 | |
| 4258 | return this._elem; |
| 4259 | }; |
| 4260 | |
| 4261 | $.jqplot.DivTitleRenderer.prototype.pack = function() { |
| 4262 | // nothing to do here |
| 4263 | }; |
| 4264 | |
| 4265 | |
| 4266 | var dotlen = 0.1; |
| 4267 | |
| 4268 | $.jqplot.LinePattern = function (ctx, pattern) { |
| 4269 | |
| 4270 | var defaultLinePatterns = { |
| 4271 | dotted: [ dotlen, $.jqplot.config.dotGapLength ], |
| 4272 | dashed: [ $.jqplot.config.dashLength, $.jqplot.config.gapLength ], |
| 4273 | solid: null |
| 4274 | }; |
| 4275 | |
| 4276 | if (typeof pattern === 'string') { |
| 4277 | if (pattern[0] === '.' || pattern[0] === '-') { |
| 4278 | var s = pattern; |
| 4279 | pattern = []; |
| 4280 | for (var i=0, imax=s.length; i<imax; i++) { |
| 4281 | if (s[i] === '.') { |
| 4282 | pattern.push( dotlen ); |
| 4283 | } |
| 4284 | else if (s[i] === '-') { |
| 4285 | pattern.push( $.jqplot.config.dashLength ); |
| 4286 | } |
| 4287 | else { |
| 4288 | continue; |
| 4289 | } |
| 4290 | pattern.push( $.jqplot.config.gapLength ); |
| 4291 | } |
| 4292 | } |
| 4293 | else { |
| 4294 | pattern = defaultLinePatterns[pattern]; |
| 4295 | } |
| 4296 | } |
| 4297 | |
| 4298 | if (!(pattern && pattern.length)) { |
| 4299 | return ctx; |
| 4300 | } |
| 4301 | |
| 4302 | var patternIndex = 0; |
| 4303 | var patternDistance = pattern[0]; |
| 4304 | var px = 0; |
| 4305 | var py = 0; |
| 4306 | var pathx0 = 0; |
| 4307 | var pathy0 = 0; |
| 4308 | |
| 4309 | var moveTo = function (x, y) { |
| 4310 | ctx.moveTo( x, y ); |
| 4311 | px = x; |
| 4312 | py = y; |
| 4313 | pathx0 = x; |
| 4314 | pathy0 = y; |
| 4315 | }; |
| 4316 | |
| 4317 | var lineTo = function (x, y) { |
| 4318 | var scale = ctx.lineWidth; |
| 4319 | var dx = x - px; |
| 4320 | var dy = y - py; |
| 4321 | var dist = Math.sqrt(dx*dx+dy*dy); |
| 4322 | if ((dist > 0) && (scale > 0)) { |
| 4323 | dx /= dist; |
| 4324 | dy /= dist; |
| 4325 | while (true) { |
| 4326 | var dp = scale * patternDistance; |
| 4327 | if (dp < dist) { |
| 4328 | px += dp * dx; |
| 4329 | py += dp * dy; |
| 4330 | if ((patternIndex & 1) == 0) { |
| 4331 | ctx.lineTo( px, py ); |
| 4332 | } |
| 4333 | else { |
| 4334 | ctx.moveTo( px, py ); |
| 4335 | } |
| 4336 | dist -= dp; |
| 4337 | patternIndex++; |
| 4338 | if (patternIndex >= pattern.length) { |
| 4339 | patternIndex = 0; |
| 4340 | } |
| 4341 | patternDistance = pattern[patternIndex]; |
| 4342 | } |
| 4343 | else { |
| 4344 | px = x; |
| 4345 | py = y; |
| 4346 | if ((patternIndex & 1) == 0) { |
| 4347 | ctx.lineTo( px, py ); |
| 4348 | } |
| 4349 | else { |
| 4350 | ctx.moveTo( px, py ); |
| 4351 | } |
| 4352 | patternDistance -= dist / scale; |
| 4353 | break; |
| 4354 | } |
| 4355 | } |
| 4356 | } |
| 4357 | }; |
| 4358 | |
| 4359 | var beginPath = function () { |
| 4360 | ctx.beginPath(); |
| 4361 | }; |
| 4362 | |
| 4363 | var closePath = function () { |
| 4364 | lineTo( pathx0, pathy0 ); |
| 4365 | }; |
| 4366 | |
| 4367 | return { |
| 4368 | moveTo: moveTo, |
| 4369 | lineTo: lineTo, |
| 4370 | beginPath: beginPath, |
| 4371 | closePath: closePath |
| 4372 | }; |
| 4373 | }; |
| 4374 | |
| 4375 | // Class: $.jqplot.LineRenderer |
| 4376 | // The default line renderer for jqPlot, this class has no options beyond the <Series> class. |
| 4377 | // Draws series as a line. |
| 4378 | $.jqplot.LineRenderer = function(){ |
| 4379 | this.shapeRenderer = new $.jqplot.ShapeRenderer(); |
| 4380 | this.shadowRenderer = new $.jqplot.ShadowRenderer(); |
| 4381 | }; |
| 4382 | |
| 4383 | // called with scope of series. |
| 4384 | $.jqplot.LineRenderer.prototype.init = function(options, plot) { |
| 4385 | // Group: Properties |
| 4386 | // |
| 4387 | options = options || {}; |
| 4388 | this._type='line'; |
| 4389 | // prop: smooth |
| 4390 | // True to draw a smoothed (interpolated) line through the data points |
| 4391 | // with automatically computed number of smoothing points. |
| 4392 | // Set to an integer number > 2 to specify number of smoothing points |
| 4393 | // to use between each data point. |
| 4394 | this.renderer.smooth = false; // true or a number > 2 for smoothing. |
| 4395 | this.renderer.tension = null; // null to auto compute or a number typically > 6. Fewer points requires higher tension. |
| 4396 | // prop: constrainSmoothing |
| 4397 | // True to use a more accurate smoothing algorithm that will |
| 4398 | // not overshoot any data points. False to allow overshoot but |
| 4399 | // produce a smoother looking line. |
| 4400 | this.renderer.constrainSmoothing = true; |
| 4401 | // this is smoothed data in grid coordinates, like gridData |
| 4402 | this.renderer._smoothedData = []; |
| 4403 | // this is smoothed data in plot units (plot coordinates), like plotData. |
| 4404 | this.renderer._smoothedPlotData = []; |
| 4405 | this.renderer._hiBandGridData = []; |
| 4406 | this.renderer._lowBandGridData = []; |
| 4407 | this.renderer._hiBandSmoothedData = []; |
| 4408 | this.renderer._lowBandSmoothedData = []; |
| 4409 | |
| 4410 | // prop: bandData |
| 4411 | // Data used to draw error bands or confidence intervals above/below a line. |
| 4412 | // |
| 4413 | // bandData can be input in 3 forms. jqPlot will figure out which is the |
| 4414 | // low band line and which is the high band line for all forms: |
| 4415 | // |
| 4416 | // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where |
| 4417 | // [yl1, yl2, ...] are y values of the lower line and |
| 4418 | // [yu1, yu2, ...] are y values of the upper line. |
| 4419 | // In this case there must be the same number of y data points as data points |
| 4420 | // in the series and the bands will inherit the x values of the series. |
| 4421 | // |
| 4422 | // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]] |
| 4423 | // where [xl1, yl1] are x,y data points for the lower line and |
| 4424 | // [xh1, yh1] are x,y data points for the high line. |
| 4425 | // x values do not have to correspond to the x values of the series and can |
| 4426 | // be of any arbitrary length. |
| 4427 | // |
| 4428 | // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where |
| 4429 | // there must be 3 or more arrays and there must be the same number of arrays |
| 4430 | // as there are data points in the series. In this case, |
| 4431 | // [yl1, yu1] specifies the lower and upper y values for the 1st |
| 4432 | // data point and so on. The bands will inherit the x |
| 4433 | // values from the series. |
| 4434 | this.renderer.bandData = []; |
| 4435 | |
| 4436 | // Group: bands |
| 4437 | // Banding around line, e.g error bands or confidence intervals. |
| 4438 | this.renderer.bands = { |
| 4439 | // prop: show |
| 4440 | // true to show the bands. If bandData or interval is |
| 4441 | // supplied, show will be set to true by default. |
| 4442 | show: false, |
| 4443 | hiData: [], |
| 4444 | lowData: [], |
| 4445 | // prop: color |
| 4446 | // color of lines at top and bottom of bands [default: series color]. |
| 4447 | color: this.color, |
| 4448 | // prop: showLines |
| 4449 | // True to show lines at top and bottom of bands [default: false]. |
| 4450 | showLines: false, |
| 4451 | // prop: fill |
| 4452 | // True to fill area between bands [default: true]. |
| 4453 | fill: true, |
| 4454 | // prop: fillColor |
| 4455 | // css color spec for filled area. [default: series color]. |
| 4456 | fillColor: null, |
| 4457 | _min: null, |
| 4458 | _max: null, |
| 4459 | // prop: interval |
| 4460 | // User specified interval above and below line for bands [default: '3%'']. |
| 4461 | // Can be a value like 3 or a string like '3%' |
| 4462 | // or an upper/lower array like [1, -2] or ['2%', '-1.5%'] |
| 4463 | interval: '3%' |
| 4464 | }; |
| 4465 | |
| 4466 | |
| 4467 | var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor}; |
| 4468 | |
| 4469 | delete (options.highlightMouseOver); |
| 4470 | delete (options.highlightMouseDown); |
| 4471 | delete (options.highlightColor); |
| 4472 | |
| 4473 | $.extend(true, this.renderer, options); |
| 4474 | |
| 4475 | this.renderer.options = options; |
| 4476 | |
| 4477 | // if we are given some band data, and bands aren't explicity set to false in options, turn them on. |
| 4478 | if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) { |
| 4479 | this.renderer.bands.show = true; |
| 4480 | } |
| 4481 | |
| 4482 | // if we are given an interval, and bands aren't explicity set to false in options, turn them on. |
| 4483 | else if (options.bands && options.bands.show == null && options.bands.interval != null) { |
| 4484 | this.renderer.bands.show = true; |
| 4485 | } |
| 4486 | |
| 4487 | // if plot is filled, turn off bands. |
| 4488 | if (this.fill) { |
| 4489 | this.renderer.bands.show = false; |
| 4490 | } |
| 4491 | |
| 4492 | if (this.renderer.bands.show) { |
| 4493 | this.renderer.initBands.call(this, this.renderer.options, plot); |
| 4494 | } |
| 4495 | |
| 4496 | |
| 4497 | // smoothing is not compatible with stacked lines, disable |
| 4498 | if (this._stack) { |
| 4499 | this.renderer.smooth = false; |
| 4500 | } |
| 4501 | |
| 4502 | // set the shape renderer options |
| 4503 | var opts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill}; |
| 4504 | this.renderer.shapeRenderer.init(opts); |
| 4505 | |
| 4506 | var shadow_offset = options.shadowOffset; |
| 4507 | // set the shadow renderer options |
| 4508 | if (shadow_offset == null) { |
| 4509 | // scale the shadowOffset to the width of the line. |
| 4510 | if (this.lineWidth > 2.5) { |
| 4511 | shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6); |
| 4512 | // var shadow_offset = this.shadowOffset; |
| 4513 | } |
| 4514 | // for skinny lines, don't make such a big shadow. |
| 4515 | else { |
| 4516 | shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163; |
| 4517 | } |
| 4518 | } |
| 4519 | |
| 4520 | var sopts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill}; |
| 4521 | this.renderer.shadowRenderer.init(sopts); |
| 4522 | this._areaPoints = []; |
| 4523 | this._boundingBox = [[],[]]; |
| 4524 | |
| 4525 | if (!this.isTrendline && this.fill || this.renderer.bands.show) { |
| 4526 | // Group: Properties |
| 4527 | // |
| 4528 | // prop: highlightMouseOver |
| 4529 | // True to highlight area on a filled plot when moused over. |
| 4530 | // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot. |
| 4531 | this.highlightMouseOver = true; |
| 4532 | // prop: highlightMouseDown |
| 4533 | // True to highlight when a mouse button is pressed over an area on a filled plot. |
| 4534 | // This will be disabled if highlightMouseOver is true. |
| 4535 | this.highlightMouseDown = false; |
| 4536 | // prop: highlightColor |
| 4537 | // color to use when highlighting an area on a filled plot. |
| 4538 | this.highlightColor = null; |
| 4539 | // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver |
| 4540 | if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) { |
| 4541 | lopts.highlightMouseOver = false; |
| 4542 | } |
| 4543 | |
| 4544 | $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor}); |
| 4545 | |
| 4546 | if (!this.highlightColor) { |
| 4547 | var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor; |
| 4548 | this.highlightColor = $.jqplot.computeHighlightColors(fc); |
| 4549 | } |
| 4550 | // turn off (disable) the highlighter plugin |
| 4551 | if (this.highlighter) { |
| 4552 | this.highlighter.show = false; |
| 4553 | } |
| 4554 | } |
| 4555 | |
| 4556 | if (!this.isTrendline && plot) { |
| 4557 | plot.plugins.lineRenderer = {}; |
| 4558 | plot.postInitHooks.addOnce(postInit); |
| 4559 | plot.postDrawHooks.addOnce(postPlotDraw); |
| 4560 | plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove); |
| 4561 | plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown); |
| 4562 | plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp); |
| 4563 | plot.eventListenerHooks.addOnce('jqplotClick', handleClick); |
| 4564 | plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick); |
| 4565 | } |
| 4566 | |
| 4567 | }; |
| 4568 | |
| 4569 | $.jqplot.LineRenderer.prototype.initBands = function(options, plot) { |
| 4570 | // use bandData if no data specified in bands option |
| 4571 | //var bd = this.renderer.bandData; |
| 4572 | var bd = options.bandData || []; |
| 4573 | var bands = this.renderer.bands; |
| 4574 | bands.hiData = []; |
| 4575 | bands.lowData = []; |
| 4576 | var data = this.data; |
| 4577 | bands._max = null; |
| 4578 | bands._min = null; |
| 4579 | // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values. |
| 4580 | if (bd.length == 2) { |
| 4581 | // Do we have an array of x,y values? |
| 4582 | // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]] |
| 4583 | if ($.isArray(bd[0][0])) { |
| 4584 | // since an arbitrary array of points, spin through all of them to determine max and min lines. |
| 4585 | |
| 4586 | var p; |
| 4587 | var bdminidx = 0, bdmaxidx = 0; |
| 4588 | for (var i = 0, l = bd[0].length; i<l; i++) { |
| 4589 | p = bd[0][i]; |
| 4590 | if ((p[1] != null && p[1] > bands._max) || bands._max == null) { |
| 4591 | bands._max = p[1]; |
| 4592 | } |
| 4593 | if ((p[1] != null && p[1] < bands._min) || bands._min == null) { |
| 4594 | bands._min = p[1]; |
| 4595 | } |
| 4596 | } |
| 4597 | for (var i = 0, l = bd[1].length; i<l; i++) { |
| 4598 | p = bd[1][i]; |
| 4599 | if ((p[1] != null && p[1] > bands._max) || bands._max == null) { |
| 4600 | bands._max = p[1]; |
| 4601 | bdmaxidx = 1; |
| 4602 | } |
| 4603 | if ((p[1] != null && p[1] < bands._min) || bands._min == null) { |
| 4604 | bands._min = p[1]; |
| 4605 | bdminidx = 1; |
| 4606 | } |
| 4607 | } |
| 4608 | |
| 4609 | if (bdmaxidx === bdminidx) { |
| 4610 | bands.show = false; |
| 4611 | } |
| 4612 | |
| 4613 | bands.hiData = bd[bdmaxidx]; |
| 4614 | bands.lowData = bd[bdminidx]; |
| 4615 | } |
| 4616 | // else data is arrays of y values |
| 4617 | // like [[1,4,3], [3,6,5]] |
| 4618 | // must have same number of band data points as points in series |
| 4619 | else if (bd[0].length === data.length && bd[1].length === data.length) { |
| 4620 | var hi = (bd[0][0] > bd[1][0]) ? 0 : 1; |
| 4621 | var low = (hi) ? 0 : 1; |
| 4622 | for (var i=0, l=data.length; i < l; i++) { |
| 4623 | bands.hiData.push([data[i][0], bd[hi][i]]); |
| 4624 | bands.lowData.push([data[i][0], bd[low][i]]); |
| 4625 | } |
| 4626 | } |
| 4627 | |
| 4628 | // we don't have proper data array, don't show bands. |
| 4629 | else { |
| 4630 | bands.show = false; |
| 4631 | } |
| 4632 | } |
| 4633 | |
| 4634 | // if more than 2 arrays, have arrays of [ylow, yhi] values. |
| 4635 | // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]] |
| 4636 | // this is assumed to be of the latter form. |
| 4637 | else if (bd.length > 2 && !$.isArray(bd[0][0])) { |
| 4638 | var hi = (bd[0][0] > bd[0][1]) ? 0 : 1; |
| 4639 | var low = (hi) ? 0 : 1; |
| 4640 | for (var i=0, l=bd.length; i<l; i++) { |
| 4641 | bands.hiData.push([data[i][0], bd[i][hi]]); |
| 4642 | bands.lowData.push([data[i][0], bd[i][low]]); |
| 4643 | } |
| 4644 | } |
| 4645 | |
| 4646 | // don't have proper data, auto calculate |
| 4647 | else { |
| 4648 | var intrv = bands.interval; |
| 4649 | var a = null; |
| 4650 | var b = null; |
| 4651 | var afunc = null; |
| 4652 | var bfunc = null; |
| 4653 | |
| 4654 | if ($.isArray(intrv)) { |
| 4655 | a = intrv[0]; |
| 4656 | b = intrv[1]; |
| 4657 | } |
| 4658 | else { |
| 4659 | a = intrv; |
| 4660 | } |
| 4661 | |
| 4662 | if (isNaN(a)) { |
| 4663 | // we have a string |
| 4664 | if (a.charAt(a.length - 1) === '%') { |
| 4665 | afunc = 'multiply'; |
| 4666 | a = parseFloat(a)/100 + 1; |
| 4667 | } |
| 4668 | } |
| 4669 | |
| 4670 | else { |
| 4671 | a = parseFloat(a); |
| 4672 | afunc = 'add'; |
| 4673 | } |
| 4674 | |
| 4675 | if (b !== null && isNaN(b)) { |
| 4676 | // we have a string |
| 4677 | if (b.charAt(b.length - 1) === '%') { |
| 4678 | bfunc = 'multiply'; |
| 4679 | b = parseFloat(b)/100 + 1; |
| 4680 | } |
| 4681 | } |
| 4682 | |
| 4683 | else if (b !== null) { |
| 4684 | b = parseFloat(b); |
| 4685 | bfunc = 'add'; |
| 4686 | } |
| 4687 | |
| 4688 | if (a !== null) { |
| 4689 | if (b === null) { |
| 4690 | b = -a; |
| 4691 | bfunc = afunc; |
| 4692 | if (bfunc === 'multiply') { |
| 4693 | b += 2; |
| 4694 | } |
| 4695 | } |
| 4696 | |
| 4697 | // make sure a always applies to hi band. |
| 4698 | if (a < b) { |
| 4699 | var temp = a; |
| 4700 | a = b; |
| 4701 | b = temp; |
| 4702 | temp = afunc; |
| 4703 | afunc = bfunc; |
| 4704 | bfunc = temp; |
| 4705 | } |
| 4706 | |
| 4707 | for (var i=0, l = data.length; i < l; i++) { |
| 4708 | switch (afunc) { |
| 4709 | case 'add': |
| 4710 | bands.hiData.push([data[i][0], data[i][1] + a]); |
| 4711 | break; |
| 4712 | case 'multiply': |
| 4713 | bands.hiData.push([data[i][0], data[i][1] * a]); |
| 4714 | break; |
| 4715 | } |
| 4716 | switch (bfunc) { |
| 4717 | case 'add': |
| 4718 | bands.lowData.push([data[i][0], data[i][1] + b]); |
| 4719 | break; |
| 4720 | case 'multiply': |
| 4721 | bands.lowData.push([data[i][0], data[i][1] * b]); |
| 4722 | break; |
| 4723 | } |
| 4724 | } |
| 4725 | } |
| 4726 | |
| 4727 | else { |
| 4728 | bands.show = false; |
| 4729 | } |
| 4730 | } |
| 4731 | |
| 4732 | var hd = bands.hiData; |
| 4733 | var ld = bands.lowData; |
| 4734 | for (var i = 0, l = hd.length; i<l; i++) { |
| 4735 | if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) { |
| 4736 | bands._max = hd[i][1]; |
| 4737 | } |
| 4738 | } |
| 4739 | for (var i = 0, l = ld.length; i<l; i++) { |
| 4740 | if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) { |
| 4741 | bands._min = ld[i][1]; |
| 4742 | } |
| 4743 | } |
| 4744 | |
| 4745 | // one last check for proper data |
| 4746 | // these don't apply any more since allowing arbitrary x,y values |
| 4747 | // if (bands.hiData.length != bands.lowData.length) { |
| 4748 | // bands.show = false; |
| 4749 | // } |
| 4750 | |
| 4751 | // if (bands.hiData.length != this.data.length) { |
| 4752 | // bands.show = false; |
| 4753 | // } |
| 4754 | |
| 4755 | if (bands.fillColor === null) { |
| 4756 | var c = $.jqplot.getColorComponents(bands.color); |
| 4757 | // now adjust alpha to differentiate fill |
| 4758 | c[3] = c[3] * 0.5; |
| 4759 | bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')'; |
| 4760 | } |
| 4761 | }; |
| 4762 | |
| 4763 | function getSteps (d, f) { |
| 4764 | return (3.4182054+f) * Math.pow(d, -0.3534992); |
| 4765 | } |
| 4766 | |
| 4767 | function computeSteps (d1, d2) { |
| 4768 | var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2)); |
| 4769 | return 5.7648 * Math.log(s) + 7.4456; |
| 4770 | } |
| 4771 | |
| 4772 | function tanh (x) { |
| 4773 | var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1); |
| 4774 | return a; |
| 4775 | } |
| 4776 | |
| 4777 | ////////// |
| 4778 | // computeConstrainedSmoothedData |
| 4779 | // An implementation of the constrained cubic spline interpolation |
| 4780 | // method as presented in: |
| 4781 | // |
| 4782 | // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications |
| 4783 | // http://www.korf.co.uk/spline.pdf |
| 4784 | // |
| 4785 | // The implementation below borrows heavily from the sample Visual Basic |
| 4786 | // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls |
| 4787 | // |
| 4788 | ///////// |
| 4789 | |
| 4790 | // called with scope of series |
| 4791 | function computeConstrainedSmoothedData (gd) { |
| 4792 | var smooth = this.renderer.smooth; |
| 4793 | var dim = this.canvas.getWidth(); |
| 4794 | var xp = this._xaxis.series_p2u; |
| 4795 | var yp = this._yaxis.series_p2u; |
| 4796 | var steps =null; |
| 4797 | var _steps = null; |
| 4798 | var dist = gd.length/dim; |
| 4799 | var _smoothedData = []; |
| 4800 | var _smoothedPlotData = []; |
| 4801 | |
| 4802 | if (!isNaN(parseFloat(smooth))) { |
| 4803 | steps = parseFloat(smooth); |
| 4804 | } |
| 4805 | else { |
| 4806 | steps = getSteps(dist, 0.5); |
| 4807 | } |
| 4808 | |
| 4809 | var yy = []; |
| 4810 | var xx = []; |
| 4811 | |
| 4812 | for (var i=0, l = gd.length; i<l; i++) { |
| 4813 | yy.push(gd[i][1]); |
| 4814 | xx.push(gd[i][0]); |
| 4815 | } |
| 4816 | |
| 4817 | function dxx(x1, x0) { |
| 4818 | if (x1 - x0 == 0) { |
| 4819 | return Math.pow(10,10); |
| 4820 | } |
| 4821 | else { |
| 4822 | return x1 - x0; |
| 4823 | } |
| 4824 | } |
| 4825 | |
| 4826 | var A, B, C, D; |
| 4827 | // loop through each line segment. Have # points - 1 line segments. Nmber segments starting at 1. |
| 4828 | var nmax = gd.length - 1; |
| 4829 | for (var num = 1, gdl = gd.length; num<gdl; num++) { |
| 4830 | var gxx = []; |
| 4831 | var ggxx = []; |
| 4832 | // point at each end of segment. |
| 4833 | for (var j = 0; j < 2; j++) { |
| 4834 | var i = num - 1 + j; // point number, 0 to # points. |
| 4835 | |
| 4836 | if (i == 0 || i == nmax) { |
| 4837 | gxx[j] = Math.pow(10, 10); |
| 4838 | } |
| 4839 | else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) { |
| 4840 | gxx[j] = 0; |
| 4841 | } |
| 4842 | else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) { |
| 4843 | gxx[j] = 0; |
| 4844 | } |
| 4845 | else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) { |
| 4846 | gxx[j] = 0; |
| 4847 | } |
| 4848 | |
| 4849 | else { |
| 4850 | gxx[j] = 2 / (dxx(xx[i + 1], xx[i]) / (yy[i + 1] - yy[i]) + dxx(xx[i], xx[i - 1]) / (yy[i] - yy[i - 1])); |
| 4851 | } |
| 4852 | } |
| 4853 | |
| 4854 | // Reset first derivative (slope) at first and last point |
| 4855 | if (num == 1) { |
| 4856 | // First point has 0 2nd derivative |
| 4857 | gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2; |
| 4858 | } |
| 4859 | else if (num == nmax) { |
| 4860 | // Last point has 0 2nd derivative |
| 4861 | gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2; |
| 4862 | } |
| 4863 | |
| 4864 | // Calc second derivative at points |
| 4865 | ggxx[0] = -2 * (gxx[1] + 2 * gxx[0]) / dxx(xx[num], xx[num - 1]) + 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2); |
| 4866 | ggxx[1] = 2 * (2 * gxx[1] + gxx[0]) / dxx(xx[num], xx[num - 1]) - 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2); |
| 4867 | |
| 4868 | // Calc constants for cubic interpolation |
| 4869 | D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]); |
| 4870 | C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]); |
| 4871 | B = (yy[num] - yy[num - 1] - C * (Math.pow(xx[num], 2) - Math.pow(xx[num - 1], 2)) - D * (Math.pow(xx[num], 3) - Math.pow(xx[num - 1], 3))) / dxx(xx[num], xx[num - 1]); |
| 4872 | A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3); |
| 4873 | |
| 4874 | var increment = (xx[num] - xx[num - 1]) / steps; |
| 4875 | var temp, tempx; |
| 4876 | |
| 4877 | for (var j = 0, l = steps; j < l; j++) { |
| 4878 | temp = []; |
| 4879 | tempx = xx[num - 1] + j * increment; |
| 4880 | temp.push(tempx); |
| 4881 | temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3)); |
| 4882 | _smoothedData.push(temp); |
| 4883 | _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]); |
| 4884 | } |
| 4885 | } |
| 4886 | |
| 4887 | _smoothedData.push(gd[i]); |
| 4888 | _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]); |
| 4889 | |
| 4890 | return [_smoothedData, _smoothedPlotData]; |
| 4891 | } |
| 4892 | |
| 4893 | /////// |
| 4894 | // computeHermiteSmoothedData |
| 4895 | // A hermite spline smoothing of the plot data. |
| 4896 | // This implementation is derived from the one posted |
| 4897 | // by krypin on the jqplot-users mailing list: |
| 4898 | // |
| 4899 | // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1 |
| 4900 | // |
| 4901 | // with a blog post: |
| 4902 | // |
| 4903 | // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/ |
| 4904 | // |
| 4905 | // and download of the original plugin: |
| 4906 | // |
| 4907 | // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js |
| 4908 | ////////// |
| 4909 | |
| 4910 | // called with scope of series |
| 4911 | function computeHermiteSmoothedData (gd) { |
| 4912 | var smooth = this.renderer.smooth; |
| 4913 | var tension = this.renderer.tension; |
| 4914 | var dim = this.canvas.getWidth(); |
| 4915 | var xp = this._xaxis.series_p2u; |
| 4916 | var yp = this._yaxis.series_p2u; |
| 4917 | var steps =null; |
| 4918 | var _steps = null; |
| 4919 | var a = null; |
| 4920 | var a1 = null; |
| 4921 | var a2 = null; |
| 4922 | var slope = null; |
| 4923 | var slope2 = null; |
| 4924 | var temp = null; |
| 4925 | var t, s, h1, h2, h3, h4; |
| 4926 | var TiX, TiY, Ti1X, Ti1Y; |
| 4927 | var pX, pY, p; |
| 4928 | var sd = []; |
| 4929 | var spd = []; |
| 4930 | var dist = gd.length/dim; |
| 4931 | var min, max, stretch, scale, shift; |
| 4932 | var _smoothedData = []; |
| 4933 | var _smoothedPlotData = []; |
| 4934 | if (!isNaN(parseFloat(smooth))) { |
| 4935 | steps = parseFloat(smooth); |
| 4936 | } |
| 4937 | else { |
| 4938 | steps = getSteps(dist, 0.5); |
| 4939 | } |
| 4940 | if (!isNaN(parseFloat(tension))) { |
| 4941 | tension = parseFloat(tension); |
| 4942 | } |
| 4943 | |
| 4944 | for (var i=0, l = gd.length-1; i < l; i++) { |
| 4945 | |
| 4946 | if (tension === null) { |
| 4947 | slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0])); |
| 4948 | |
| 4949 | min = 0.3; |
| 4950 | max = 0.6; |
| 4951 | stretch = (max - min)/2.0; |
| 4952 | scale = 2.5; |
| 4953 | shift = -1.4; |
| 4954 | |
| 4955 | temp = slope/scale + shift; |
| 4956 | |
| 4957 | a1 = stretch * tanh(temp) - stretch * tanh(shift) + min; |
| 4958 | |
| 4959 | // if have both left and right line segments, will use minimum tension. |
| 4960 | if (i > 0) { |
| 4961 | slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0])); |
| 4962 | } |
| 4963 | temp = slope2/scale + shift; |
| 4964 | |
| 4965 | a2 = stretch * tanh(temp) - stretch * tanh(shift) + min; |
| 4966 | |
| 4967 | a = (a1 + a2)/2.0; |
| 4968 | |
| 4969 | } |
| 4970 | else { |
| 4971 | a = tension; |
| 4972 | } |
| 4973 | for (t=0; t < steps; t++) { |
| 4974 | s = t / steps; |
| 4975 | h1 = (1 + 2*s)*Math.pow((1-s),2); |
| 4976 | h2 = s*Math.pow((1-s),2); |
| 4977 | h3 = Math.pow(s,2)*(3-2*s); |
| 4978 | h4 = Math.pow(s,2)*(s-1); |
| 4979 | |
| 4980 | if (gd[i-1]) { |
| 4981 | TiX = a * (gd[i+1][0] - gd[i-1][0]); |
| 4982 | TiY = a * (gd[i+1][1] - gd[i-1][1]); |
| 4983 | } else { |
| 4984 | TiX = a * (gd[i+1][0] - gd[i][0]); |
| 4985 | TiY = a * (gd[i+1][1] - gd[i][1]); |
| 4986 | } |
| 4987 | if (gd[i+2]) { |
| 4988 | Ti1X = a * (gd[i+2][0] - gd[i][0]); |
| 4989 | Ti1Y = a * (gd[i+2][1] - gd[i][1]); |
| 4990 | } else { |
| 4991 | Ti1X = a * (gd[i+1][0] - gd[i][0]); |
| 4992 | Ti1Y = a * (gd[i+1][1] - gd[i][1]); |
| 4993 | } |
| 4994 | |
| 4995 | pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X; |
| 4996 | pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y; |
| 4997 | p = [pX, pY]; |
| 4998 | |
| 4999 | _smoothedData.push(p); |
| 5000 | _smoothedPlotData.push([xp(pX), yp(pY)]); |
| 5001 | } |
| 5002 | } |
| 5003 | _smoothedData.push(gd[l]); |
| 5004 | _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]); |
| 5005 | |
| 5006 | return [_smoothedData, _smoothedPlotData]; |
| 5007 | } |
| 5008 | |
| 5009 | // setGridData |
| 5010 | // converts the user data values to grid coordinates and stores them |
| 5011 | // in the gridData array. |
| 5012 | // Called with scope of a series. |
| 5013 | $.jqplot.LineRenderer.prototype.setGridData = function(plot) { |
| 5014 | // recalculate the grid data |
| 5015 | var xp = this._xaxis.series_u2p; |
| 5016 | var yp = this._yaxis.series_u2p; |
| 5017 | var data = this._plotData; |
| 5018 | var pdata = this._prevPlotData; |
| 5019 | this.gridData = []; |
| 5020 | this._prevGridData = []; |
| 5021 | this.renderer._smoothedData = []; |
| 5022 | this.renderer._smoothedPlotData = []; |
| 5023 | this.renderer._hiBandGridData = []; |
| 5024 | this.renderer._lowBandGridData = []; |
| 5025 | this.renderer._hiBandSmoothedData = []; |
| 5026 | this.renderer._lowBandSmoothedData = []; |
| 5027 | var bands = this.renderer.bands; |
| 5028 | var hasNull = false; |
| 5029 | for (var i=0, l=this.data.length; i < l; i++) { |
| 5030 | // if not a line series or if no nulls in data, push the converted point onto the array. |
| 5031 | if (data[i][0] != null && data[i][1] != null) { |
| 5032 | this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]); |
| 5033 | } |
| 5034 | // else if there is a null, preserve it. |
| 5035 | else if (data[i][0] == null) { |
| 5036 | hasNull = true; |
| 5037 | this.gridData.push([null, yp.call(this._yaxis, data[i][1])]); |
| 5038 | } |
| 5039 | else if (data[i][1] == null) { |
| 5040 | hasNull = true; |
| 5041 | this.gridData.push([xp.call(this._xaxis, data[i][0]), null]); |
| 5042 | } |
| 5043 | // if not a line series or if no nulls in data, push the converted point onto the array. |
| 5044 | if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) { |
| 5045 | this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]); |
| 5046 | } |
| 5047 | // else if there is a null, preserve it. |
| 5048 | else if (pdata[i] != null && pdata[i][0] == null) { |
| 5049 | this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]); |
| 5050 | } |
| 5051 | else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) { |
| 5052 | this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]); |
| 5053 | } |
| 5054 | } |
| 5055 | |
| 5056 | // don't do smoothing or bands on broken lines. |
| 5057 | if (hasNull) { |
| 5058 | this.renderer.smooth = false; |
| 5059 | if (this._type === 'line') { |
| 5060 | bands.show = false; |
| 5061 | } |
| 5062 | } |
| 5063 | |
| 5064 | if (this._type === 'line' && bands.show) { |
| 5065 | for (var i=0, l=bands.hiData.length; i<l; i++) { |
| 5066 | this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]); |
| 5067 | } |
| 5068 | for (var i=0, l=bands.lowData.length; i<l; i++) { |
| 5069 | this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]); |
| 5070 | } |
| 5071 | } |
| 5072 | |
| 5073 | // calculate smoothed data if enough points and no nulls |
| 5074 | if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) { |
| 5075 | var ret; |
| 5076 | if (this.renderer.constrainSmoothing) { |
| 5077 | ret = computeConstrainedSmoothedData.call(this, this.gridData); |
| 5078 | this.renderer._smoothedData = ret[0]; |
| 5079 | this.renderer._smoothedPlotData = ret[1]; |
| 5080 | |
| 5081 | if (bands.show) { |
| 5082 | ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData); |
| 5083 | this.renderer._hiBandSmoothedData = ret[0]; |
| 5084 | ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData); |
| 5085 | this.renderer._lowBandSmoothedData = ret[0]; |
| 5086 | } |
| 5087 | |
| 5088 | ret = null; |
| 5089 | } |
| 5090 | else { |
| 5091 | ret = computeHermiteSmoothedData.call(this, this.gridData); |
| 5092 | this.renderer._smoothedData = ret[0]; |
| 5093 | this.renderer._smoothedPlotData = ret[1]; |
| 5094 | |
| 5095 | if (bands.show) { |
| 5096 | ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData); |
| 5097 | this.renderer._hiBandSmoothedData = ret[0]; |
| 5098 | ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData); |
| 5099 | this.renderer._lowBandSmoothedData = ret[0]; |
| 5100 | } |
| 5101 | |
| 5102 | ret = null; |
| 5103 | } |
| 5104 | } |
| 5105 | }; |
| 5106 | |
| 5107 | // makeGridData |
| 5108 | // converts any arbitrary data values to grid coordinates and |
| 5109 | // returns them. This method exists so that plugins can use a series' |
| 5110 | // linerenderer to generate grid data points without overwriting the |
| 5111 | // grid data associated with that series. |
| 5112 | // Called with scope of a series. |
| 5113 | $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) { |
| 5114 | // recalculate the grid data |
| 5115 | var xp = this._xaxis.series_u2p; |
| 5116 | var yp = this._yaxis.series_u2p; |
| 5117 | var gd = []; |
| 5118 | var pgd = []; |
| 5119 | this.renderer._smoothedData = []; |
| 5120 | this.renderer._smoothedPlotData = []; |
| 5121 | this.renderer._hiBandGridData = []; |
| 5122 | this.renderer._lowBandGridData = []; |
| 5123 | this.renderer._hiBandSmoothedData = []; |
| 5124 | this.renderer._lowBandSmoothedData = []; |
| 5125 | var bands = this.renderer.bands; |
| 5126 | var hasNull = false; |
| 5127 | for (var i=0; i<data.length; i++) { |
| 5128 | // if not a line series or if no nulls in data, push the converted point onto the array. |
| 5129 | if (data[i][0] != null && data[i][1] != null) { |
| 5130 | gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]); |
| 5131 | } |
| 5132 | // else if there is a null, preserve it. |
| 5133 | else if (data[i][0] == null) { |
| 5134 | hasNull = true; |
| 5135 | gd.push([null, yp.call(this._yaxis, data[i][1])]); |
| 5136 | } |
| 5137 | else if (data[i][1] == null) { |
| 5138 | hasNull = true; |
| 5139 | gd.push([xp.call(this._xaxis, data[i][0]), null]); |
| 5140 | } |
| 5141 | } |
| 5142 | |
| 5143 | // don't do smoothing or bands on broken lines. |
| 5144 | if (hasNull) { |
| 5145 | this.renderer.smooth = false; |
| 5146 | if (this._type === 'line') { |
| 5147 | bands.show = false; |
| 5148 | } |
| 5149 | } |
| 5150 | |
| 5151 | if (this._type === 'line' && bands.show) { |
| 5152 | for (var i=0, l=bands.hiData.length; i<l; i++) { |
| 5153 | this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]); |
| 5154 | } |
| 5155 | for (var i=0, l=bands.lowData.length; i<l; i++) { |
| 5156 | this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]); |
| 5157 | } |
| 5158 | } |
| 5159 | |
| 5160 | if (this._type === 'line' && this.renderer.smooth && gd.length > 2) { |
| 5161 | var ret; |
| 5162 | if (this.renderer.constrainSmoothing) { |
| 5163 | ret = computeConstrainedSmoothedData.call(this, gd); |
| 5164 | this.renderer._smoothedData = ret[0]; |
| 5165 | this.renderer._smoothedPlotData = ret[1]; |
| 5166 | |
| 5167 | if (bands.show) { |
| 5168 | ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData); |
| 5169 | this.renderer._hiBandSmoothedData = ret[0]; |
| 5170 | ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData); |
| 5171 | this.renderer._lowBandSmoothedData = ret[0]; |
| 5172 | } |
| 5173 | |
| 5174 | ret = null; |
| 5175 | } |
| 5176 | else { |
| 5177 | ret = computeHermiteSmoothedData.call(this, gd); |
| 5178 | this.renderer._smoothedData = ret[0]; |
| 5179 | this.renderer._smoothedPlotData = ret[1]; |
| 5180 | |
| 5181 | if (bands.show) { |
| 5182 | ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData); |
| 5183 | this.renderer._hiBandSmoothedData = ret[0]; |
| 5184 | ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData); |
| 5185 | this.renderer._lowBandSmoothedData = ret[0]; |
| 5186 | } |
| 5187 | |
| 5188 | ret = null; |
| 5189 | } |
| 5190 | } |
| 5191 | return gd; |
| 5192 | }; |
| 5193 | |
| 5194 | |
| 5195 | // called within scope of series. |
| 5196 | $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) { |
| 5197 | var i; |
| 5198 | // get a copy of the options, so we don't modify the original object. |
| 5199 | var opts = $.extend(true, {}, options); |
| 5200 | var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow; |
| 5201 | var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine; |
| 5202 | var fill = (opts.fill != undefined) ? opts.fill : this.fill; |
| 5203 | var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke; |
| 5204 | var xmin, ymin, xmax, ymax; |
| 5205 | ctx.save(); |
| 5206 | if (gd.length) { |
| 5207 | if (showLine) { |
| 5208 | // if we fill, we'll have to add points to close the curve. |
| 5209 | if (fill) { |
| 5210 | if (this.fillToZero) { |
| 5211 | // have to break line up into shapes at axis crossings |
| 5212 | var negativeColor = this.negativeColor; |
| 5213 | if (! this.useNegativeColors) { |
| 5214 | negativeColor = opts.fillStyle; |
| 5215 | } |
| 5216 | var isnegative = false; |
| 5217 | var posfs = opts.fillStyle; |
| 5218 | |
| 5219 | // if stoking line as well as filling, get a copy of line data. |
| 5220 | if (fillAndStroke) { |
| 5221 | var fasgd = gd.slice(0); |
| 5222 | } |
| 5223 | // if not stacked, fill down to axis |
| 5224 | if (this.index == 0 || !this._stack) { |
| 5225 | |
| 5226 | var tempgd = []; |
| 5227 | var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData; |
| 5228 | this._areaPoints = []; |
| 5229 | var pyzero = this._yaxis.series_u2p(this.fillToValue); |
| 5230 | var pxzero = this._xaxis.series_u2p(this.fillToValue); |
| 5231 | |
| 5232 | opts.closePath = true; |
| 5233 | |
| 5234 | if (this.fillAxis == 'y') { |
| 5235 | tempgd.push([gd[0][0], pyzero]); |
| 5236 | this._areaPoints.push([gd[0][0], pyzero]); |
| 5237 | |
| 5238 | for (var i=0; i<gd.length-1; i++) { |
| 5239 | tempgd.push(gd[i]); |
| 5240 | this._areaPoints.push(gd[i]); |
| 5241 | // do we have an axis crossing? |
| 5242 | if (pd[i][1] * pd[i+1][1] < 0) { |
| 5243 | if (pd[i][1] < 0) { |
| 5244 | isnegative = true; |
| 5245 | opts.fillStyle = negativeColor; |
| 5246 | } |
| 5247 | else { |
| 5248 | isnegative = false; |
| 5249 | opts.fillStyle = posfs; |
| 5250 | } |
| 5251 | |
| 5252 | var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]); |
| 5253 | tempgd.push([xintercept, pyzero]); |
| 5254 | this._areaPoints.push([xintercept, pyzero]); |
| 5255 | // now draw this shape and shadow. |
| 5256 | if (shadow) { |
| 5257 | this.renderer.shadowRenderer.draw(ctx, tempgd, opts); |
| 5258 | } |
| 5259 | this.renderer.shapeRenderer.draw(ctx, tempgd, opts); |
| 5260 | // now empty temp array and continue |
| 5261 | tempgd = [[xintercept, pyzero]]; |
| 5262 | // this._areaPoints = [[xintercept, pyzero]]; |
| 5263 | } |
| 5264 | } |
| 5265 | if (pd[gd.length-1][1] < 0) { |
| 5266 | isnegative = true; |
| 5267 | opts.fillStyle = negativeColor; |
| 5268 | } |
| 5269 | else { |
| 5270 | isnegative = false; |
| 5271 | opts.fillStyle = posfs; |
| 5272 | } |
| 5273 | tempgd.push(gd[gd.length-1]); |
| 5274 | this._areaPoints.push(gd[gd.length-1]); |
| 5275 | tempgd.push([gd[gd.length-1][0], pyzero]); |
| 5276 | this._areaPoints.push([gd[gd.length-1][0], pyzero]); |
| 5277 | } |
| 5278 | // now draw the last area. |
| 5279 | if (shadow) { |
| 5280 | this.renderer.shadowRenderer.draw(ctx, tempgd, opts); |
| 5281 | } |
| 5282 | this.renderer.shapeRenderer.draw(ctx, tempgd, opts); |
| 5283 | |
| 5284 | |
| 5285 | // var gridymin = this._yaxis.series_u2p(0); |
| 5286 | // // IE doesn't return new length on unshift |
| 5287 | // gd.unshift([gd[0][0], gridymin]); |
| 5288 | // len = gd.length; |
| 5289 | // gd.push([gd[len - 1][0], gridymin]); |
| 5290 | } |
| 5291 | // if stacked, fill to line below |
| 5292 | else { |
| 5293 | var prev = this._prevGridData; |
| 5294 | for (var i=prev.length; i>0; i--) { |
| 5295 | gd.push(prev[i-1]); |
| 5296 | // this._areaPoints.push(prev[i-1]); |
| 5297 | } |
| 5298 | if (shadow) { |
| 5299 | this.renderer.shadowRenderer.draw(ctx, gd, opts); |
| 5300 | } |
| 5301 | this._areaPoints = gd; |
| 5302 | this.renderer.shapeRenderer.draw(ctx, gd, opts); |
| 5303 | } |
| 5304 | } |
| 5305 | ///////////////////////// |
| 5306 | // Not filled to zero |
| 5307 | //////////////////////// |
| 5308 | else { |
| 5309 | // if stoking line as well as filling, get a copy of line data. |
| 5310 | if (fillAndStroke) { |
| 5311 | var fasgd = gd.slice(0); |
| 5312 | } |
| 5313 | // if not stacked, fill down to axis |
| 5314 | if (this.index == 0 || !this._stack) { |
| 5315 | // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2; |
| 5316 | var gridymin = ctx.canvas.height; |
| 5317 | // IE doesn't return new length on unshift |
| 5318 | gd.unshift([gd[0][0], gridymin]); |
| 5319 | var len = gd.length; |
| 5320 | gd.push([gd[len - 1][0], gridymin]); |
| 5321 | } |
| 5322 | // if stacked, fill to line below |
| 5323 | else { |
| 5324 | var prev = this._prevGridData; |
| 5325 | for (var i=prev.length; i>0; i--) { |
| 5326 | gd.push(prev[i-1]); |
| 5327 | } |
| 5328 | } |
| 5329 | this._areaPoints = gd; |
| 5330 | |
| 5331 | if (shadow) { |
| 5332 | this.renderer.shadowRenderer.draw(ctx, gd, opts); |
| 5333 | } |
| 5334 | |
| 5335 | this.renderer.shapeRenderer.draw(ctx, gd, opts); |
| 5336 | } |
| 5337 | if (fillAndStroke) { |
| 5338 | var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false}); |
| 5339 | this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts); |
| 5340 | ////////// |
| 5341 | // TODO: figure out some way to do shadows nicely |
| 5342 | // if (shadow) { |
| 5343 | // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts); |
| 5344 | // } |
| 5345 | // now draw the markers |
| 5346 | if (this.markerRenderer.show) { |
| 5347 | if (this.renderer.smooth) { |
| 5348 | fasgd = this.gridData; |
| 5349 | } |
| 5350 | for (i=0; i<fasgd.length; i++) { |
| 5351 | this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions); |
| 5352 | } |
| 5353 | } |
| 5354 | } |
| 5355 | } |
| 5356 | else { |
| 5357 | |
| 5358 | if (this.renderer.bands.show) { |
| 5359 | var bdat; |
| 5360 | var bopts = $.extend(true, {}, opts); |
| 5361 | if (this.renderer.bands.showLines) { |
| 5362 | bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData; |
| 5363 | this.renderer.shapeRenderer.draw(ctx, bdat, opts); |
| 5364 | bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData; |
| 5365 | this.renderer.shapeRenderer.draw(ctx, bdat, bopts); |
| 5366 | } |
| 5367 | |
| 5368 | if (this.renderer.bands.fill) { |
| 5369 | if (this.renderer.smooth) { |
| 5370 | bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse()); |
| 5371 | } |
| 5372 | else { |
| 5373 | bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse()); |
| 5374 | } |
| 5375 | this._areaPoints = bdat; |
| 5376 | bopts.closePath = true; |
| 5377 | bopts.fill = true; |
| 5378 | bopts.fillStyle = this.renderer.bands.fillColor; |
| 5379 | this.renderer.shapeRenderer.draw(ctx, bdat, bopts); |
| 5380 | } |
| 5381 | } |
| 5382 | |
| 5383 | if (shadow) { |
| 5384 | this.renderer.shadowRenderer.draw(ctx, gd, opts); |
| 5385 | } |
| 5386 | |
| 5387 | this.renderer.shapeRenderer.draw(ctx, gd, opts); |
| 5388 | } |
| 5389 | } |
| 5390 | // calculate the bounding box |
| 5391 | var xmin = xmax = ymin = ymax = null; |
| 5392 | for (i=0; i<this._areaPoints.length; i++) { |
| 5393 | var p = this._areaPoints[i]; |
| 5394 | if (xmin > p[0] || xmin == null) { |
| 5395 | xmin = p[0]; |
| 5396 | } |
| 5397 | if (ymax < p[1] || ymax == null) { |
| 5398 | ymax = p[1]; |
| 5399 | } |
| 5400 | if (xmax < p[0] || xmax == null) { |
| 5401 | xmax = p[0]; |
| 5402 | } |
| 5403 | if (ymin > p[1] || ymin == null) { |
| 5404 | ymin = p[1]; |
| 5405 | } |
| 5406 | } |
| 5407 | |
| 5408 | if (this.type === 'line' && this.renderer.bands.show) { |
| 5409 | ymax = this._yaxis.series_u2p(this.renderer.bands._min); |
| 5410 | ymin = this._yaxis.series_u2p(this.renderer.bands._max); |
| 5411 | } |
| 5412 | |
| 5413 | this._boundingBox = [[xmin, ymax], [xmax, ymin]]; |
| 5414 | |
| 5415 | // now draw the markers |
| 5416 | if (this.markerRenderer.show && !fill) { |
| 5417 | if (this.renderer.smooth) { |
| 5418 | gd = this.gridData; |
| 5419 | } |
| 5420 | for (i=0; i<gd.length; i++) { |
| 5421 | if (gd[i][0] != null && gd[i][1] != null) { |
| 5422 | this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions); |
| 5423 | } |
| 5424 | } |
| 5425 | } |
| 5426 | } |
| 5427 | |
| 5428 | ctx.restore(); |
| 5429 | }; |
| 5430 | |
| 5431 | $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) { |
| 5432 | // This is a no-op, shadows drawn with lines. |
| 5433 | }; |
| 5434 | |
| 5435 | // called with scope of plot. |
| 5436 | // make sure to not leave anything highlighted. |
| 5437 | function postInit(target, data, options) { |
| 5438 | for (var i=0; i<this.series.length; i++) { |
| 5439 | if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) { |
| 5440 | // don't allow mouseover and mousedown at same time. |
| 5441 | if (this.series[i].highlightMouseOver) { |
| 5442 | this.series[i].highlightMouseDown = false; |
| 5443 | } |
| 5444 | } |
| 5445 | } |
| 5446 | } |
| 5447 | |
| 5448 | // called within context of plot |
| 5449 | // create a canvas which we can draw on. |
| 5450 | // insert it before the eventCanvas, so eventCanvas will still capture events. |
| 5451 | function postPlotDraw() { |
| 5452 | // Memory Leaks patch |
| 5453 | if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) { |
| 5454 | this.plugins.lineRenderer.highlightCanvas.resetCanvas(); |
| 5455 | this.plugins.lineRenderer.highlightCanvas = null; |
| 5456 | } |
| 5457 | |
| 5458 | this.plugins.lineRenderer.highlightedSeriesIndex = null; |
| 5459 | this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas(); |
| 5460 | |
| 5461 | this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this)); |
| 5462 | this.plugins.lineRenderer.highlightCanvas.setContext(); |
| 5463 | this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); }); |
| 5464 | } |
| 5465 | |
| 5466 | function highlight (plot, sidx, pidx, points) { |
| 5467 | var s = plot.series[sidx]; |
| 5468 | var canvas = plot.plugins.lineRenderer.highlightCanvas; |
| 5469 | canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height); |
| 5470 | s._highlightedPoint = pidx; |
| 5471 | plot.plugins.lineRenderer.highlightedSeriesIndex = sidx; |
| 5472 | var opts = {fillStyle: s.highlightColor}; |
| 5473 | if (s.type === 'line' && s.renderer.bands.show) { |
| 5474 | opts.fill = true; |
| 5475 | opts.closePath = true; |
| 5476 | } |
| 5477 | s.renderer.shapeRenderer.draw(canvas._ctx, points, opts); |
| 5478 | canvas = null; |
| 5479 | } |
| 5480 | |
| 5481 | function unhighlight (plot) { |
| 5482 | var canvas = plot.plugins.lineRenderer.highlightCanvas; |
| 5483 | canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height); |
| 5484 | for (var i=0; i<plot.series.length; i++) { |
| 5485 | plot.series[i]._highlightedPoint = null; |
| 5486 | } |
| 5487 | plot.plugins.lineRenderer.highlightedSeriesIndex = null; |
| 5488 | plot.target.trigger('jqplotDataUnhighlight'); |
| 5489 | canvas = null; |
| 5490 | } |
| 5491 | |
| 5492 | |
| 5493 | function handleMove(ev, gridpos, datapos, neighbor, plot) { |
| 5494 | if (neighbor) { |
| 5495 | var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 5496 | var evt1 = jQuery.Event('jqplotDataMouseOver'); |
| 5497 | evt1.pageX = ev.pageX; |
| 5498 | evt1.pageY = ev.pageY; |
| 5499 | plot.target.trigger(evt1, ins); |
| 5500 | if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) { |
| 5501 | var evt = jQuery.Event('jqplotDataHighlight'); |
| 5502 | evt.pageX = ev.pageX; |
| 5503 | evt.pageY = ev.pageY; |
| 5504 | plot.target.trigger(evt, ins); |
| 5505 | highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); |
| 5506 | } |
| 5507 | } |
| 5508 | else if (neighbor == null) { |
| 5509 | unhighlight (plot); |
| 5510 | } |
| 5511 | } |
| 5512 | |
| 5513 | function handleMouseDown(ev, gridpos, datapos, neighbor, plot) { |
| 5514 | if (neighbor) { |
| 5515 | var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 5516 | if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) { |
| 5517 | var evt = jQuery.Event('jqplotDataHighlight'); |
| 5518 | evt.pageX = ev.pageX; |
| 5519 | evt.pageY = ev.pageY; |
| 5520 | plot.target.trigger(evt, ins); |
| 5521 | highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); |
| 5522 | } |
| 5523 | } |
| 5524 | else if (neighbor == null) { |
| 5525 | unhighlight (plot); |
| 5526 | } |
| 5527 | } |
| 5528 | |
| 5529 | function handleMouseUp(ev, gridpos, datapos, neighbor, plot) { |
| 5530 | var idx = plot.plugins.lineRenderer.highlightedSeriesIndex; |
| 5531 | if (idx != null && plot.series[idx].highlightMouseDown) { |
| 5532 | unhighlight(plot); |
| 5533 | } |
| 5534 | } |
| 5535 | |
| 5536 | function handleClick(ev, gridpos, datapos, neighbor, plot) { |
| 5537 | if (neighbor) { |
| 5538 | var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 5539 | var evt = jQuery.Event('jqplotDataClick'); |
| 5540 | evt.pageX = ev.pageX; |
| 5541 | evt.pageY = ev.pageY; |
| 5542 | plot.target.trigger(evt, ins); |
| 5543 | } |
| 5544 | } |
| 5545 | |
| 5546 | function handleRightClick(ev, gridpos, datapos, neighbor, plot) { |
| 5547 | if (neighbor) { |
| 5548 | var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; |
| 5549 | var idx = plot.plugins.lineRenderer.highlightedSeriesIndex; |
| 5550 | if (idx != null && plot.series[idx].highlightMouseDown) { |
| 5551 | unhighlight(plot); |
| 5552 | } |
| 5553 | var evt = jQuery.Event('jqplotDataRightClick'); |
| 5554 | evt.pageX = ev.pageX; |
| 5555 | evt.pageY = ev.pageY; |
| 5556 | plot.target.trigger(evt, ins); |
| 5557 | } |
| 5558 | } |
| 5559 | |
| 5560 | |
| 5561 | // class: $.jqplot.LinearAxisRenderer |
| 5562 | // The default jqPlot axis renderer, creating a numeric axis. |
| 5563 | $.jqplot.LinearAxisRenderer = function() { |
| 5564 | }; |
| 5565 | |
| 5566 | // called with scope of axis object. |
| 5567 | $.jqplot.LinearAxisRenderer.prototype.init = function(options){ |
| 5568 | // prop: breakPoints |
| 5569 | // EXPERIMENTAL!! Use at your own risk! |
| 5570 | // Works only with linear axes and the default tick renderer. |
| 5571 | // Array of [start, stop] points to create a broken axis. |
| 5572 | // Broken axes have a "jump" in them, which is an immediate |
| 5573 | // transition from a smaller value to a larger value. |
| 5574 | // Currently, axis ticks MUST be manually assigned if using breakPoints |
| 5575 | // by using the axis ticks array option. |
| 5576 | this.breakPoints = null; |
| 5577 | // prop: breakTickLabel |
| 5578 | // Label to use at the axis break if breakPoints are specified. |
| 5579 | this.breakTickLabel = "≈"; |
| 5580 | // prop: drawBaseline |
| 5581 | // True to draw the axis baseline. |
| 5582 | this.drawBaseline = true; |
| 5583 | // prop: baselineWidth |
| 5584 | // width of the baseline in pixels. |
| 5585 | this.baselineWidth = null; |
| 5586 | // prop: baselineColor |
| 5587 | // CSS color spec for the baseline. |
| 5588 | this.baselineColor = null; |
| 5589 | // prop: forceTickAt0 |
| 5590 | // This will ensure that there is always a tick mark at 0. |
| 5591 | // If data range is strictly positive or negative, |
| 5592 | // this will force 0 to be inside the axis bounds unless |
| 5593 | // the appropriate axis pad (pad, padMin or padMax) is set |
| 5594 | // to 0, then this will force an axis min or max value at 0. |
| 5595 | // This has know effect when any of the following options |
| 5596 | // are set: autoscale, min, max, numberTicks or tickInterval. |
| 5597 | this.forceTickAt0 = false; |
| 5598 | // prop: forceTickAt100 |
| 5599 | // This will ensure that there is always a tick mark at 100. |
| 5600 | // If data range is strictly above or below 100, |
| 5601 | // this will force 100 to be inside the axis bounds unless |
| 5602 | // the appropriate axis pad (pad, padMin or padMax) is set |
| 5603 | // to 0, then this will force an axis min or max value at 100. |
| 5604 | // This has know effect when any of the following options |
| 5605 | // are set: autoscale, min, max, numberTicks or tickInterval. |
| 5606 | this.forceTickAt100 = false; |
| 5607 | // prop: tickInset |
| 5608 | // Controls the amount to inset the first and last ticks from |
| 5609 | // the edges of the grid, in multiples of the tick interval. |
| 5610 | // 0 is no inset, 0.5 is one half a tick interval, 1 is a full |
| 5611 | // tick interval, etc. |
| 5612 | this.tickInset = 0; |
| 5613 | // prop: minorTicks |
| 5614 | // Number of ticks to add between "major" ticks. |
| 5615 | // Major ticks are ticks supplied by user or auto computed. |
| 5616 | // Minor ticks cannot be created by user. |
| 5617 | this.minorTicks = 0; |
| 5618 | this.alignTicks = false; |
| 5619 | this._autoFormatString = ''; |
| 5620 | this._overrideFormatString = false; |
| 5621 | this._scalefact = 1.0; |
| 5622 | $.extend(true, this, options); |
| 5623 | if (this.breakPoints) { |
| 5624 | if (!$.isArray(this.breakPoints)) { |
| 5625 | this.breakPoints = null; |
| 5626 | } |
| 5627 | else if (this.breakPoints.length < 2 || this.breakPoints[1] <= this.breakPoints[0]) { |
| 5628 | this.breakPoints = null; |
| 5629 | } |
| 5630 | } |
| 5631 | if (this.numberTicks != null && this.numberTicks < 2) { |
| 5632 | this.numberTicks = 2; |
| 5633 | } |
| 5634 | this.resetDataBounds(); |
| 5635 | }; |
| 5636 | |
| 5637 | // called with scope of axis |
| 5638 | $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx, plot) { |
| 5639 | if (this.show) { |
| 5640 | // populate the axis label and value properties. |
| 5641 | // createTicks is a method on the renderer, but |
| 5642 | // call it within the scope of the axis. |
| 5643 | this.renderer.createTicks.call(this, plot); |
| 5644 | // fill a div with axes labels in the right direction. |
| 5645 | // Need to pregenerate each axis to get it's bounds and |
| 5646 | // position it and the labels correctly on the plot. |
| 5647 | var dim=0; |
| 5648 | var temp; |
| 5649 | // Added for theming. |
| 5650 | if (this._elem) { |
| 5651 | // Memory Leaks patch |
| 5652 | //this._elem.empty(); |
| 5653 | this._elem.emptyForce(); |
| 5654 | this._elem = null; |
| 5655 | } |
| 5656 | |
| 5657 | this._elem = $(document.createElement('div')); |
| 5658 | this._elem.addClass('jqplot-axis jqplot-'+this.name); |
| 5659 | this._elem.css('position', 'absolute'); |
| 5660 | |
| 5661 | |
| 5662 | if (this.name == 'xaxis' || this.name == 'x2axis') { |
| 5663 | this._elem.width(this._plotDimensions.width); |
| 5664 | } |
| 5665 | else { |
| 5666 | this._elem.height(this._plotDimensions.height); |
| 5667 | } |
| 5668 | |
| 5669 | // create a _label object. |
| 5670 | this.labelOptions.axis = this.name; |
| 5671 | this._label = new this.labelRenderer(this.labelOptions); |
| 5672 | if (this._label.show) { |
| 5673 | var elem = this._label.draw(ctx, plot); |
| 5674 | elem.appendTo(this._elem); |
| 5675 | elem = null; |
| 5676 | } |
| 5677 | |
| 5678 | var t = this._ticks; |
| 5679 | var tick; |
| 5680 | for (var i=0; i<t.length; i++) { |
| 5681 | tick = t[i]; |
| 5682 | if (tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) { |
| 5683 | this._elem.append(tick.draw(ctx, plot)); |
| 5684 | } |
| 5685 | } |
| 5686 | tick = null; |
| 5687 | t = null; |
| 5688 | } |
| 5689 | return this._elem; |
| 5690 | }; |
| 5691 | |
| 5692 | // called with scope of an axis |
| 5693 | $.jqplot.LinearAxisRenderer.prototype.reset = function() { |
| 5694 | this.min = this._options.min; |
| 5695 | this.max = this._options.max; |
| 5696 | this.tickInterval = this._options.tickInterval; |
| 5697 | this.numberTicks = this._options.numberTicks; |
| 5698 | this._autoFormatString = ''; |
| 5699 | if (this._overrideFormatString && this.tickOptions && this.tickOptions.formatString) { |
| 5700 | this.tickOptions.formatString = ''; |
| 5701 | } |
| 5702 | |
| 5703 | // this._ticks = this.__ticks; |
| 5704 | }; |
| 5705 | |
| 5706 | // called with scope of axis |
| 5707 | $.jqplot.LinearAxisRenderer.prototype.set = function() { |
| 5708 | var dim = 0; |
| 5709 | var temp; |
| 5710 | var w = 0; |
| 5711 | var h = 0; |
| 5712 | var lshow = (this._label == null) ? false : this._label.show; |
| 5713 | if (this.show) { |
| 5714 | var t = this._ticks; |
| 5715 | var tick; |
| 5716 | for (var i=0; i<t.length; i++) { |
| 5717 | tick = t[i]; |
| 5718 | if (!tick._breakTick && tick.show && tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) { |
| 5719 | if (this.name == 'xaxis' || this.name == 'x2axis') { |
| 5720 | temp = tick._elem.outerHeight(true); |
| 5721 | } |
| 5722 | else { |
| 5723 | temp = tick._elem.outerWidth(true); |
| 5724 | } |
| 5725 | if (temp > dim) { |
| 5726 | dim = temp; |
| 5727 | } |
| 5728 | } |
| 5729 | } |
| 5730 | tick = null; |
| 5731 | t = null; |
| 5732 | |
| 5733 | if (lshow) { |
| 5734 | w = this._label._elem.outerWidth(true); |
| 5735 | h = this._label._elem.outerHeight(true); |
| 5736 | } |
| 5737 | if (this.name == 'xaxis') { |
| 5738 | dim = dim + h; |
| 5739 | this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'}); |
| 5740 | } |
| 5741 | else if (this.name == 'x2axis') { |
| 5742 | dim = dim + h; |
| 5743 | this._elem.css({'height':dim+'px', left:'0px', top:'0px'}); |
| 5744 | } |
| 5745 | else if (this.name == 'yaxis') { |
| 5746 | dim = dim + w; |
| 5747 | this._elem.css({'width':dim+'px', left:'0px', top:'0px'}); |
| 5748 | if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { |
| 5749 | this._label._elem.css('width', w+'px'); |
| 5750 | } |
| 5751 | } |
| 5752 | else { |
| 5753 | dim = dim + w; |
| 5754 | this._elem.css({'width':dim+'px', right:'0px', top:'0px'}); |
| 5755 | if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { |
| 5756 | this._label._elem.css('width', w+'px'); |
| 5757 | } |
| 5758 | } |
| 5759 | } |
| 5760 | }; |
| 5761 | |
| 5762 | // called with scope of axis |
| 5763 | $.jqplot.LinearAxisRenderer.prototype.createTicks = function(plot) { |
| 5764 | // we're are operating on an axis here |
| 5765 | var ticks = this._ticks; |
| 5766 | var userTicks = this.ticks; |
| 5767 | var name = this.name; |
| 5768 | // databounds were set on axis initialization. |
| 5769 | var db = this._dataBounds; |
| 5770 | var dim, interval; |
| 5771 | var min, max; |
| 5772 | var pos1, pos2; |
| 5773 | var tt, i; |
| 5774 | // get a copy of user's settings for min/max. |
| 5775 | var userMin = this.min; |
| 5776 | var userMax = this.max; |
| 5777 | var userNT = this.numberTicks; |
| 5778 | var userTI = this.tickInterval; |
| 5779 | |
| 5780 | // if we already have ticks, use them. |
| 5781 | // ticks must be in order of increasing value. |
| 5782 | |
| 5783 | if (userTicks.length) { |
| 5784 | // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed |
| 5785 | for (i=0; i<userTicks.length; i++){ |
| 5786 | var ut = userTicks[i]; |
| 5787 | var t = new this.tickRenderer(this.tickOptions); |
| 5788 | if ($.isArray(ut)) { |
| 5789 | t.value = ut[0]; |
| 5790 | if (this.breakPoints) { |
| 5791 | if (ut[0] == this.breakPoints[0]) { |
| 5792 | t.label = this.breakTickLabel; |
| 5793 | t._breakTick = true; |
| 5794 | t.showGridline = false; |
| 5795 | t.showMark = false; |
| 5796 | } |
| 5797 | else if (ut[0] > this.breakPoints[0] && ut[0] <= this.breakPoints[1]) { |
| 5798 | t.show = false; |
| 5799 | t.showGridline = false; |
| 5800 | t.label = ut[1]; |
| 5801 | } |
| 5802 | else { |
| 5803 | t.label = ut[1]; |
| 5804 | } |
| 5805 | } |
| 5806 | else { |
| 5807 | t.label = ut[1]; |
| 5808 | } |
| 5809 | t.setTick(ut[0], this.name); |
| 5810 | this._ticks.push(t); |
| 5811 | } |
| 5812 | |
| 5813 | else if ($.isPlainObject(ut)) { |
| 5814 | $.extend(true, t, ut); |
| 5815 | t.axis = this.name; |
| 5816 | this._ticks.push(t); |
| 5817 | } |
| 5818 | |
| 5819 | else { |
| 5820 | t.value = ut; |
| 5821 | if (this.breakPoints) { |
| 5822 | if (ut == this.breakPoints[0]) { |
| 5823 | t.label = this.breakTickLabel; |
| 5824 | t._breakTick = true; |
| 5825 | t.showGridline = false; |
| 5826 | t.showMark = false; |
| 5827 | } |
| 5828 | else if (ut > this.breakPoints[0] && ut <= this.breakPoints[1]) { |
| 5829 | t.show = false; |
| 5830 | t.showGridline = false; |
| 5831 | } |
| 5832 | } |
| 5833 | t.setTick(ut, this.name); |
| 5834 | this._ticks.push(t); |
| 5835 | } |
| 5836 | } |
| 5837 | this.numberTicks = userTicks.length; |
| 5838 | this.min = this._ticks[0].value; |
| 5839 | this.max = this._ticks[this.numberTicks-1].value; |
| 5840 | this.tickInterval = (this.max - this.min) / (this.numberTicks - 1); |
| 5841 | } |
| 5842 | |
| 5843 | // we don't have any ticks yet, let's make some! |
| 5844 | else { |
| 5845 | if (name == 'xaxis' || name == 'x2axis') { |
| 5846 | dim = this._plotDimensions.width; |
| 5847 | } |
| 5848 | else { |
| 5849 | dim = this._plotDimensions.height; |
| 5850 | } |
| 5851 | |
| 5852 | var _numberTicks = this.numberTicks; |
| 5853 | |
| 5854 | // if aligning this axis, use number of ticks from previous axis. |
| 5855 | // Do I need to reset somehow if alignTicks is changed and then graph is replotted?? |
| 5856 | if (this.alignTicks) { |
| 5857 | if (this.name === 'x2axis' && plot.axes.xaxis.show) { |
| 5858 | _numberTicks = plot.axes.xaxis.numberTicks; |
| 5859 | } |
| 5860 | else if (this.name.charAt(0) === 'y' && this.name !== 'yaxis' && this.name !== 'yMidAxis' && plot.axes.yaxis.show) { |
| 5861 | _numberTicks = plot.axes.yaxis.numberTicks; |
| 5862 | } |
| 5863 | } |
| 5864 | |
| 5865 | // // if min, max and number of ticks specified, user can't specify interval. |
| 5866 | // if (!this.autoscale && this.min != null && this.max != null && this.numberTicks != null) { |
| 5867 | // console.log('doing this'); |
| 5868 | // this.tickInterval = null; |
| 5869 | // } |
| 5870 | |
| 5871 | // if max, min, and interval specified and interval won't fit, ignore interval. |
| 5872 | // if (this.min != null && this.max != null && this.tickInterval != null) { |
| 5873 | // if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) { |
| 5874 | // this.tickInterval = null; |
| 5875 | // } |
| 5876 | // } |
| 5877 | |
| 5878 | min = ((this.min != null) ? this.min : db.min); |
| 5879 | max = ((this.max != null) ? this.max : db.max); |
| 5880 | |
| 5881 | var range = max - min; |
| 5882 | var rmin, rmax; |
| 5883 | var temp; |
| 5884 | |
| 5885 | if (this.tickOptions == null || !this.tickOptions.formatString) { |
| 5886 | this._overrideFormatString = true; |
| 5887 | } |
| 5888 | |
| 5889 | // Doing complete autoscaling |
| 5890 | if (this.min == null && this.max == null && this.tickInterval == null && !this.autoscale) { |
| 5891 | // Check if user must have tick at 0 or 100 and ensure they are in range. |
| 5892 | // The autoscaling algorithm will always place ticks at 0 and 100 if they are in range. |
| 5893 | if (this.forceTickAt0) { |
| 5894 | if (min > 0) { |
| 5895 | min = 0; |
| 5896 | } |
| 5897 | if (max < 0) { |
| 5898 | max = 0; |
| 5899 | } |
| 5900 | } |
| 5901 | |
| 5902 | if (this.forceTickAt100) { |
| 5903 | if (min > 100) { |
| 5904 | min = 100; |
| 5905 | } |
| 5906 | if (max < 100) { |
| 5907 | max = 100; |
| 5908 | } |
| 5909 | } |
| 5910 | |
| 5911 | var threshold = 30; |
| 5912 | var tdim = Math.max(dim, threshold+1); |
| 5913 | this._scalefact = (tdim-threshold)/300.0; |
| 5914 | var ret = $.jqplot.LinearTickGenerator(min, max, this._scalefact, _numberTicks); |
| 5915 | // calculate a padded max and min, points should be less than these |
| 5916 | // so that they aren't too close to the edges of the plot. |
| 5917 | // User can adjust how much padding is allowed with pad, padMin and PadMax options. |
| 5918 | var tumin = min + range*(this.padMin - 1); |
| 5919 | var tumax = max - range*(this.padMax - 1); |
| 5920 | |
| 5921 | // if they're equal, we shouldn't have to do anything, right? |
| 5922 | // if (min <=tumin || max >= tumax) { |
| 5923 | if (min <tumin || max > tumax) { |
| 5924 | tumin = min - range*(this.padMin - 1); |
| 5925 | tumax = max + range*(this.padMax - 1); |
| 5926 | ret = $.jqplot.LinearTickGenerator(tumin, tumax, this._scalefact, _numberTicks); |
| 5927 | } |
| 5928 | |
| 5929 | this.min = ret[0]; |
| 5930 | this.max = ret[1]; |
| 5931 | // if numberTicks specified, it should return the same. |
| 5932 | this.numberTicks = ret[2]; |
| 5933 | this._autoFormatString = ret[3]; |
| 5934 | this.tickInterval = ret[4]; |
| 5935 | } |
| 5936 | |
| 5937 | // User has specified some axis scale related option, can use auto algorithm |
| 5938 | else { |
| 5939 | |
| 5940 | // if min and max are same, space them out a bit |
| 5941 | if (min == max) { |
| 5942 | var adj = 0.05; |
| 5943 | if (min > 0) { |
| 5944 | adj = Math.max(Math.log(min)/Math.LN10, 0.05); |
| 5945 | } |
| 5946 | min -= adj; |
| 5947 | max += adj; |
| 5948 | } |
| 5949 | |
| 5950 | // autoscale. Can't autoscale if min or max is supplied. |
| 5951 | // Will use numberTicks and tickInterval if supplied. Ticks |
| 5952 | // across multiple axes may not line up depending on how |
| 5953 | // bars are to be plotted. |
| 5954 | if (this.autoscale && this.min == null && this.max == null) { |
| 5955 | var rrange, ti, margin; |
| 5956 | var forceMinZero = false; |
| 5957 | var forceZeroLine = false; |
| 5958 | var intervals = {min:null, max:null, average:null, stddev:null}; |
| 5959 | // if any series are bars, or if any are fill to zero, and if this |
| 5960 | // is the axis to fill toward, check to see if we can start axis at zero. |
| 5961 | for (var i=0; i<this._series.length; i++) { |
| 5962 | var s = this._series[i]; |
| 5963 | var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name; |
| 5964 | // check to see if this is the fill axis |
| 5965 | if (this.name == faname) { |
| 5966 | var vals = s._plotValues[s.fillAxis]; |
| 5967 | var vmin = vals[0]; |
| 5968 | var vmax = vals[0]; |
| 5969 | for (var j=1; j<vals.length; j++) { |
| 5970 | if (vals[j] < vmin) { |
| 5971 | vmin = vals[j]; |
| 5972 | } |
| 5973 | else if (vals[j] > vmax) { |
| 5974 | vmax = vals[j]; |
| 5975 | } |
| 5976 | } |
| 5977 | var dp = (vmax - vmin) / vmax; |
| 5978 | // is this sries a bar? |
| 5979 | if (s.renderer.constructor == $.jqplot.BarRenderer) { |
| 5980 | // if no negative values and could also check range. |
| 5981 | if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { |
| 5982 | forceMinZero = true; |
| 5983 | } |
| 5984 | else { |
| 5985 | forceMinZero = false; |
| 5986 | if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) { |
| 5987 | forceZeroLine = true; |
| 5988 | } |
| 5989 | else { |
| 5990 | forceZeroLine = false; |
| 5991 | } |
| 5992 | } |
| 5993 | } |
| 5994 | |
| 5995 | // if not a bar and filling, use appropriate method. |
| 5996 | else if (s.fill) { |
| 5997 | if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { |
| 5998 | forceMinZero = true; |
| 5999 | } |
| 6000 | else if (vmin < 0 && vmax > 0 && s.fillToZero) { |
| 6001 | forceMinZero = false; |
| 6002 | forceZeroLine = true; |
| 6003 | } |
| 6004 | else { |
| 6005 | forceMinZero = false; |
| 6006 | forceZeroLine = false; |
| 6007 | } |
| 6008 | } |
| 6009 | |
| 6010 | // if not a bar and not filling, only change existing state |
| 6011 | // if it doesn't make sense |
| 6012 | else if (vmin < 0) { |
| 6013 | forceMinZero = false; |
| 6014 | } |
| 6015 | } |
| 6016 | } |
| 6017 | |
| 6018 | // check if we need make axis min at 0. |
| 6019 | if (forceMinZero) { |
| 6020 | // compute number of ticks |
| 6021 | this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); |
| 6022 | this.min = 0; |
| 6023 | userMin = 0; |
| 6024 | // what order is this range? |
| 6025 | // what tick interval does that give us? |
| 6026 | ti = max/(this.numberTicks-1); |
| 6027 | temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); |
| 6028 | if (ti/temp == parseInt(ti/temp, 10)) { |
| 6029 | ti += temp; |
| 6030 | } |
| 6031 | this.tickInterval = Math.ceil(ti/temp) * temp; |
| 6032 | this.max = this.tickInterval * (this.numberTicks - 1); |
| 6033 | } |
| 6034 | |
| 6035 | // check if we need to make sure there is a tick at 0. |
| 6036 | else if (forceZeroLine) { |
| 6037 | // compute number of ticks |
| 6038 | this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); |
| 6039 | var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1)); |
| 6040 | var ntmax = this.numberTicks - 1 - ntmin; |
| 6041 | ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax)); |
| 6042 | temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); |
| 6043 | this.tickInterval = Math.ceil(ti/temp) * temp; |
| 6044 | this.max = this.tickInterval * ntmax; |
| 6045 | this.min = -this.tickInterval * ntmin; |
| 6046 | } |
| 6047 | |
| 6048 | // if nothing else, do autoscaling which will try to line up ticks across axes. |
| 6049 | else { |
| 6050 | if (this.numberTicks == null){ |
| 6051 | if (this.tickInterval) { |
| 6052 | this.numberTicks = 3 + Math.ceil(range / this.tickInterval); |
| 6053 | } |
| 6054 | else { |
| 6055 | this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); |
| 6056 | } |
| 6057 | } |
| 6058 | |
| 6059 | if (this.tickInterval == null) { |
| 6060 | // get a tick interval |
| 6061 | ti = range/(this.numberTicks - 1); |
| 6062 | |
| 6063 | if (ti < 1) { |
| 6064 | temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); |
| 6065 | } |
| 6066 | else { |
| 6067 | temp = 1; |
| 6068 | } |
| 6069 | this.tickInterval = Math.ceil(ti*temp*this.pad)/temp; |
| 6070 | } |
| 6071 | else { |
| 6072 | temp = 1 / this.tickInterval; |
| 6073 | } |
| 6074 | |
| 6075 | // try to compute a nicer, more even tick interval |
| 6076 | // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10)); |
| 6077 | // this.tickInterval = Math.ceil(ti/temp) * temp; |
| 6078 | rrange = this.tickInterval * (this.numberTicks - 1); |
| 6079 | margin = (rrange - range)/2; |
| 6080 | |
| 6081 | if (this.min == null) { |
| 6082 | this.min = Math.floor(temp*(min-margin))/temp; |
| 6083 | } |
| 6084 | if (this.max == null) { |
| 6085 | this.max = this.min + rrange; |
| 6086 | } |
| 6087 | } |
| 6088 | |
| 6089 | // Compute a somewhat decent format string if it is needed. |
| 6090 | // get precision of interval and determine a format string. |
| 6091 | var sf = $.jqplot.getSignificantFigures(this.tickInterval); |
| 6092 | |
| 6093 | var fstr; |
| 6094 | |
| 6095 | // if we have only a whole number, use integer formatting |
| 6096 | if (sf.digitsLeft >= sf.significantDigits) { |
| 6097 | fstr = '%d'; |
| 6098 | } |
| 6099 | |
| 6100 | else { |
| 6101 | var temp = Math.max(0, 5 - sf.digitsLeft); |
| 6102 | temp = Math.min(temp, sf.digitsRight); |
| 6103 | fstr = '%.'+ temp + 'f'; |
| 6104 | } |
| 6105 | |
| 6106 | this._autoFormatString = fstr; |
| 6107 | } |
| 6108 | |
| 6109 | // Use the default algorithm which pads each axis to make the chart |
| 6110 | // centered nicely on the grid. |
| 6111 | else { |
| 6112 | |
| 6113 | rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1); |
| 6114 | rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1); |
| 6115 | range = rmax - rmin; |
| 6116 | |
| 6117 | if (this.numberTicks == null){ |
| 6118 | // if tickInterval is specified by user, we will ignore computed maximum. |
| 6119 | // max will be equal or greater to fit even # of ticks. |
| 6120 | if (this.tickInterval != null) { |
| 6121 | this.numberTicks = Math.ceil((rmax - rmin)/this.tickInterval)+1; |
| 6122 | } |
| 6123 | else if (dim > 100) { |
| 6124 | this.numberTicks = parseInt(3+(dim-100)/75, 10); |
| 6125 | } |
| 6126 | else { |
| 6127 | this.numberTicks = 2; |
| 6128 | } |
| 6129 | } |
| 6130 | |
| 6131 | if (this.tickInterval == null) { |
| 6132 | this.tickInterval = range / (this.numberTicks-1); |
| 6133 | } |
| 6134 | |
| 6135 | if (this.max == null) { |
| 6136 | rmax = rmin + this.tickInterval*(this.numberTicks - 1); |
| 6137 | } |
| 6138 | if (this.min == null) { |
| 6139 | rmin = rmax - this.tickInterval*(this.numberTicks - 1); |
| 6140 | } |
| 6141 | |
| 6142 | // get precision of interval and determine a format string. |
| 6143 | var sf = $.jqplot.getSignificantFigures(this.tickInterval); |
| 6144 | |
| 6145 | var fstr; |
| 6146 | |
| 6147 | // if we have only a whole number, use integer formatting |
| 6148 | if (sf.digitsLeft >= sf.significantDigits) { |
| 6149 | fstr = '%d'; |
| 6150 | } |
| 6151 | |
| 6152 | else { |
| 6153 | var temp = Math.max(0, 5 - sf.digitsLeft); |
| 6154 | temp = Math.min(temp, sf.digitsRight); |
| 6155 | fstr = '%.'+ temp + 'f'; |
| 6156 | } |
| 6157 | |
| 6158 | |
| 6159 | this._autoFormatString = fstr; |
| 6160 | |
| 6161 | this.min = rmin; |
| 6162 | this.max = rmax; |
| 6163 | } |
| 6164 | |
| 6165 | if (this.renderer.constructor == $.jqplot.LinearAxisRenderer && this._autoFormatString == '') { |
| 6166 | // fix for misleading tick display with small range and low precision. |
| 6167 | range = this.max - this.min; |
| 6168 | // figure out precision |
| 6169 | var temptick = new this.tickRenderer(this.tickOptions); |
| 6170 | // use the tick formatString or, the default. |
| 6171 | var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString; |
| 6172 | var fs = fs.match($.jqplot.sprintf.regex)[0]; |
| 6173 | var precision = 0; |
| 6174 | if (fs) { |
| 6175 | if (fs.search(/[fFeEgGpP]/) > -1) { |
| 6176 | var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/); |
| 6177 | if (m) { |
| 6178 | precision = parseInt(m[1], 10); |
| 6179 | } |
| 6180 | else { |
| 6181 | precision = 6; |
| 6182 | } |
| 6183 | } |
| 6184 | else if (fs.search(/[di]/) > -1) { |
| 6185 | precision = 0; |
| 6186 | } |
| 6187 | // fact will be <= 1; |
| 6188 | var fact = Math.pow(10, -precision); |
| 6189 | if (this.tickInterval < fact) { |
| 6190 | // need to correct underrange |
| 6191 | if (userNT == null && userTI == null) { |
| 6192 | this.tickInterval = fact; |
| 6193 | if (userMax == null && userMin == null) { |
| 6194 | // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact; |
| 6195 | this.min = Math.floor(this._dataBounds.min/fact) * fact; |
| 6196 | if (this.min == this._dataBounds.min) { |
| 6197 | this.min = this._dataBounds.min - this.tickInterval; |
| 6198 | } |
| 6199 | // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact; |
| 6200 | this.max = Math.ceil(this._dataBounds.max/fact) * fact; |
| 6201 | if (this.max == this._dataBounds.max) { |
| 6202 | this.max = this._dataBounds.max + this.tickInterval; |
| 6203 | } |
| 6204 | var n = (this.max - this.min)/this.tickInterval; |
| 6205 | n = n.toFixed(11); |
| 6206 | n = Math.ceil(n); |
| 6207 | this.numberTicks = n + 1; |
| 6208 | } |
| 6209 | else if (userMax == null) { |
| 6210 | // add one tick for top of range. |
| 6211 | var n = (this._dataBounds.max - this.min) / this.tickInterval; |
| 6212 | n = n.toFixed(11); |
| 6213 | this.numberTicks = Math.ceil(n) + 2; |
| 6214 | this.max = this.min + this.tickInterval * (this.numberTicks-1); |
| 6215 | } |
| 6216 | else if (userMin == null) { |
| 6217 | // add one tick for bottom of range. |
| 6218 | var n = (this.max - this._dataBounds.min) / this.tickInterval; |
| 6219 | n = n.toFixed(11); |
| 6220 | this.numberTicks = Math.ceil(n) + 2; |
| 6221 | this.min = this.max - this.tickInterval * (this.numberTicks-1); |
| 6222 | } |
| 6223 | else { |
| 6224 | // calculate a number of ticks so max is within axis scale |
| 6225 | this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1; |
| 6226 | // if user's min and max don't fit evenly in ticks, adjust. |
| 6227 | // This takes care of cases such as user min set to 0, max set to 3.5 but tick |
| 6228 | // format string set to %d (integer ticks) |
| 6229 | this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision); |
| 6230 | this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision); |
| 6231 | // this.max = this.min + this.tickInterval*(this.numberTicks-1); |
| 6232 | this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1; |
| 6233 | } |
| 6234 | } |
| 6235 | } |
| 6236 | } |
| 6237 | } |
| 6238 | |
| 6239 | } |
| 6240 | |
| 6241 | if (this._overrideFormatString && this._autoFormatString != '') { |
| 6242 | this.tickOptions = this.tickOptions || {}; |
| 6243 | this.tickOptions.formatString = this._autoFormatString; |
| 6244 | } |
| 6245 | |
| 6246 | var t, to; |
| 6247 | for (var i=0; i<this.numberTicks; i++){ |
| 6248 | tt = this.min + i * this.tickInterval; |
| 6249 | t = new this.tickRenderer(this.tickOptions); |
| 6250 | // var t = new $.jqplot.AxisTickRenderer(this.tickOptions); |
| 6251 | |
| 6252 | t.setTick(tt, this.name); |
| 6253 | this._ticks.push(t); |
| 6254 | |
| 6255 | if (i < this.numberTicks - 1) { |
| 6256 | for (var j=0; j<this.minorTicks; j++) { |
| 6257 | tt += this.tickInterval/(this.minorTicks+1); |
| 6258 | to = $.extend(true, {}, this.tickOptions, {name:this.name, value:tt, label:'', isMinorTick:true}); |
| 6259 | t = new this.tickRenderer(to); |
| 6260 | this._ticks.push(t); |
| 6261 | } |
| 6262 | } |
| 6263 | t = null; |
| 6264 | } |
| 6265 | } |
| 6266 | |
| 6267 | if (this.tickInset) { |
| 6268 | this.min = this.min - this.tickInset * this.tickInterval; |
| 6269 | this.max = this.max + this.tickInset * this.tickInterval; |
| 6270 | } |
| 6271 | |
| 6272 | ticks = null; |
| 6273 | }; |
| 6274 | |
| 6275 | // Used to reset just the values of the ticks and then repack, which will |
| 6276 | // recalculate the positioning functions. It is assuemd that the |
| 6277 | // number of ticks is the same and the values of the new array are at the |
| 6278 | // proper interval. |
| 6279 | // This method needs to be called with the scope of an axis object, like: |
| 6280 | // |
| 6281 | // > plot.axes.yaxis.renderer.resetTickValues.call(plot.axes.yaxis, yarr); |
| 6282 | // |
| 6283 | $.jqplot.LinearAxisRenderer.prototype.resetTickValues = function(opts) { |
| 6284 | if ($.isArray(opts) && opts.length == this._ticks.length) { |
| 6285 | var t; |
| 6286 | for (var i=0; i<opts.length; i++) { |
| 6287 | t = this._ticks[i]; |
| 6288 | t.value = opts[i]; |
| 6289 | t.label = t.formatter(t.formatString, opts[i]); |
| 6290 | t.label = t.prefix + t.label; |
| 6291 | t._elem.html(t.label); |
| 6292 | } |
| 6293 | t = null; |
| 6294 | this.min = $.jqplot.arrayMin(opts); |
| 6295 | this.max = $.jqplot.arrayMax(opts); |
| 6296 | this.pack(); |
| 6297 | } |
| 6298 | // Not implemented yet. |
| 6299 | // else if ($.isPlainObject(opts)) { |
| 6300 | // |
| 6301 | // } |
| 6302 | }; |
| 6303 | |
| 6304 | // called with scope of axis |
| 6305 | $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) { |
| 6306 | // Add defaults for repacking from resetTickValues function. |
| 6307 | pos = pos || {}; |
| 6308 | offsets = offsets || this._offsets; |
| 6309 | |
| 6310 | var ticks = this._ticks; |
| 6311 | var max = this.max; |
| 6312 | var min = this.min; |
| 6313 | var offmax = offsets.max; |
| 6314 | var offmin = offsets.min; |
| 6315 | var lshow = (this._label == null) ? false : this._label.show; |
| 6316 | |
| 6317 | for (var p in pos) { |
| 6318 | this._elem.css(p, pos[p]); |
| 6319 | } |
| 6320 | |
| 6321 | this._offsets = offsets; |
| 6322 | // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left. |
| 6323 | var pixellength = offmax - offmin; |
| 6324 | var unitlength = max - min; |
| 6325 | |
| 6326 | // point to unit and unit to point conversions references to Plot DOM element top left corner. |
| 6327 | if (this.breakPoints) { |
| 6328 | unitlength = unitlength - this.breakPoints[1] + this.breakPoints[0]; |
| 6329 | |
| 6330 | this.p2u = function(p){ |
| 6331 | return (p - offmin) * unitlength / pixellength + min; |
| 6332 | }; |
| 6333 | |
| 6334 | this.u2p = function(u){ |
| 6335 | if (u > this.breakPoints[0] && u < this.breakPoints[1]){ |
| 6336 | u = this.breakPoints[0]; |
| 6337 | } |
| 6338 | if (u <= this.breakPoints[0]) { |
| 6339 | return (u - min) * pixellength / unitlength + offmin; |
| 6340 | } |
| 6341 | else { |
| 6342 | return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength + offmin; |
| 6343 | } |
| 6344 | }; |
| 6345 | |
| 6346 | if (this.name.charAt(0) == 'x'){ |
| 6347 | this.series_u2p = function(u){ |
| 6348 | if (u > this.breakPoints[0] && u < this.breakPoints[1]){ |
| 6349 | u = this.breakPoints[0]; |
| 6350 | } |
| 6351 | if (u <= this.breakPoints[0]) { |
| 6352 | return (u - min) * pixellength / unitlength; |
| 6353 | } |
| 6354 | else { |
| 6355 | return (u - this.breakPoints[1] + this.breakPoints[0] - min) * pixellength / unitlength; |
| 6356 | } |
| 6357 | }; |
| 6358 | this.series_p2u = function(p){ |
| 6359 | return p * unitlength / pixellength + min; |
| 6360 | }; |
| 6361 | } |
| 6362 | |
| 6363 | else { |
| 6364 | this.series_u2p = function(u){ |
| 6365 | if (u > this.breakPoints[0] && u < this.breakPoints[1]){ |
| 6366 | u = this.breakPoints[0]; |
| 6367 | } |
| 6368 | if (u >= this.breakPoints[1]) { |
| 6369 | return (u - max) * pixellength / unitlength; |
| 6370 | } |
| 6371 | else { |
| 6372 | return (u + this.breakPoints[1] - this.breakPoints[0] - max) * pixellength / unitlength; |
| 6373 | } |
| 6374 | }; |
| 6375 | this.series_p2u = function(p){ |
| 6376 | return p * unitlength / pixellength + max; |
| 6377 | }; |
| 6378 | } |
| 6379 | } |
| 6380 | else { |
| 6381 | this.p2u = function(p){ |
| 6382 | return (p - offmin) * unitlength / pixellength + min; |
| 6383 | }; |
| 6384 | |
| 6385 | this.u2p = function(u){ |
| 6386 | return (u - min) * pixellength / unitlength + offmin; |
| 6387 | }; |
| 6388 | |
| 6389 | if (this.name == 'xaxis' || this.name == 'x2axis'){ |
| 6390 | this.series_u2p = function(u){ |
| 6391 | return (u - min) * pixellength / unitlength; |
| 6392 | }; |
| 6393 | this.series_p2u = function(p){ |
| 6394 | return p * unitlength / pixellength + min; |
| 6395 | }; |
| 6396 | } |
| 6397 | |
| 6398 | else { |
| 6399 | this.series_u2p = function(u){ |
| 6400 | return (u - max) * pixellength / unitlength; |
| 6401 | }; |
| 6402 | this.series_p2u = function(p){ |
| 6403 | return p * unitlength / pixellength + max; |
| 6404 | }; |
| 6405 | } |
| 6406 | } |
| 6407 | |
| 6408 | if (this.show) { |
| 6409 | if (this.name == 'xaxis' || this.name == 'x2axis') { |
| 6410 | for (var i=0; i<ticks.length; i++) { |
| 6411 | var t = ticks[i]; |
| 6412 | if (t.show && t.showLabel) { |
| 6413 | var shim; |
| 6414 | |
| 6415 | if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) { |
| 6416 | // will need to adjust auto positioning based on which axis this is. |
| 6417 | var temp = (this.name == 'xaxis') ? 1 : -1; |
| 6418 | switch (t.labelPosition) { |
| 6419 | case 'auto': |
| 6420 | // position at end |
| 6421 | if (temp * t.angle < 0) { |
| 6422 | shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; |
| 6423 | } |
| 6424 | // position at start |
| 6425 | else { |
| 6426 | shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; |
| 6427 | } |
| 6428 | break; |
| 6429 | case 'end': |
| 6430 | shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; |
| 6431 | break; |
| 6432 | case 'start': |
| 6433 | shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; |
| 6434 | break; |
| 6435 | case 'middle': |
| 6436 | shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; |
| 6437 | break; |
| 6438 | default: |
| 6439 | shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; |
| 6440 | break; |
| 6441 | } |
| 6442 | } |
| 6443 | else { |
| 6444 | shim = -t.getWidth()/2; |
| 6445 | } |
| 6446 | var val = this.u2p(t.value) + shim + 'px'; |
| 6447 | t._elem.css('left', val); |
| 6448 | t.pack(); |
| 6449 | } |
| 6450 | } |
| 6451 | if (lshow) { |
| 6452 | var w = this._label._elem.outerWidth(true); |
| 6453 | this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px'); |
| 6454 | if (this.name == 'xaxis') { |
| 6455 | this._label._elem.css('bottom', '0px'); |
| 6456 | } |
| 6457 | else { |
| 6458 | this._label._elem.css('top', '0px'); |
| 6459 | } |
| 6460 | this._label.pack(); |
| 6461 | } |
| 6462 | } |
| 6463 | else { |
| 6464 | for (var i=0; i<ticks.length; i++) { |
| 6465 | var t = ticks[i]; |
| 6466 | if (t.show && t.showLabel) { |
| 6467 | var shim; |
| 6468 | if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) { |
| 6469 | // will need to adjust auto positioning based on which axis this is. |
| 6470 | var temp = (this.name == 'yaxis') ? 1 : -1; |
| 6471 | switch (t.labelPosition) { |
| 6472 | case 'auto': |
| 6473 | // position at end |
| 6474 | case 'end': |
| 6475 | if (temp * t.angle < 0) { |
| 6476 | shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; |
| 6477 | } |
| 6478 | else { |
| 6479 | shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; |
| 6480 | } |
| 6481 | break; |
| 6482 | case 'start': |
| 6483 | if (t.angle > 0) { |
| 6484 | shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; |
| 6485 | } |
| 6486 | else { |
| 6487 | shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; |
| 6488 | } |
| 6489 | break; |
| 6490 | case 'middle': |
| 6491 | // if (t.angle > 0) { |
| 6492 | // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; |
| 6493 | // } |
| 6494 | // else { |
| 6495 | // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; |
| 6496 | // } |
| 6497 | shim = -t.getHeight()/2; |
| 6498 | break; |
| 6499 | default: |
| 6500 | shim = -t.getHeight()/2; |
| 6501 | break; |
| 6502 | } |
| 6503 | } |
| 6504 | else { |
| 6505 | shim = -t.getHeight()/2; |
| 6506 | } |
| 6507 | |
| 6508 | var val = this.u2p(t.value) + shim + 'px'; |
| 6509 | t._elem.css('top', val); |
| 6510 | t.pack(); |
| 6511 | } |
| 6512 | } |
| 6513 | if (lshow) { |
| 6514 | var h = this._label._elem.outerHeight(true); |
| 6515 | this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px'); |
| 6516 | if (this.name == 'yaxis') { |
| 6517 | this._label._elem.css('left', '0px'); |
| 6518 | } |
| 6519 | else { |
| 6520 | this._label._elem.css('right', '0px'); |
| 6521 | } |
| 6522 | this._label.pack(); |
| 6523 | } |
| 6524 | } |
| 6525 | } |
| 6526 | |
| 6527 | ticks = null; |
| 6528 | }; |
| 6529 | |
| 6530 | |
| 6531 | /** |
| 6532 | * The following code was generaously given to me a while back by Scott Prahl. |
| 6533 | * He did a good job at computing axes min, max and number of ticks for the |
| 6534 | * case where the user has not set any scale related parameters (tickInterval, |
| 6535 | * numberTicks, min or max). I had ignored this use case for a long time, |
| 6536 | * focusing on the more difficult case where user has set some option controlling |
| 6537 | * tick generation. Anyway, about time I got this into jqPlot. |
| 6538 | * Thanks Scott!! |
| 6539 | */ |
| 6540 | |
| 6541 | /** |
| 6542 | * Copyright (c) 2010 Scott Prahl |
| 6543 | * The next three routines are currently available for use in all personal |
| 6544 | * or commercial projects under both the MIT and GPL version 2.0 licenses. |
| 6545 | * This means that you can choose the license that best suits your project |
| 6546 | * and use it accordingly. |
| 6547 | */ |
| 6548 | |
| 6549 | // A good format string depends on the interval. If the interval is greater |
| 6550 | // than 1 then there is no need to show any decimal digits. If it is < 1.0, then |
| 6551 | // use the magnitude of the interval to determine the number of digits to show. |
| 6552 | function bestFormatString (interval) |
| 6553 | { |
| 6554 | var fstr; |
| 6555 | interval = Math.abs(interval); |
| 6556 | if (interval >= 10) { |
| 6557 | fstr = '%d'; |
| 6558 | } |
| 6559 | |
| 6560 | else if (interval > 1) { |
| 6561 | if (interval === parseInt(interval)) { |
| 6562 | fstr = '%d'; |
| 6563 | } |
| 6564 | else { |
| 6565 | fstr = '%.1f'; |
| 6566 | } |
| 6567 | } |
| 6568 | |
| 6569 | else { |
| 6570 | var expv = -Math.floor(Math.log(interval)/Math.LN10); |
| 6571 | fstr = '%.' + expv + 'f'; |
| 6572 | } |
| 6573 | |
| 6574 | return fstr; |
| 6575 | } |
| 6576 | |
| 6577 | var _factors = [0.1, 0.2, 0.3, 0.4, 0.5, 0.8, 1, 2, 3, 4, 5]; |
| 6578 | |
| 6579 | var _getLowerFactor = function(f) { |
| 6580 | var i = _factors.indexOf(f); |
| 6581 | if (i > 0) { |
| 6582 | return _factors[i-1]; |
| 6583 | } |
| 6584 | else { |
| 6585 | return _factors[_factors.length - 1] / 100; |
| 6586 | } |
| 6587 | }; |
| 6588 | |
| 6589 | var _getHigherFactor = function(f) { |
| 6590 | var i = _factors.indexOf(f); |
| 6591 | if (i < _factors.length-1) { |
| 6592 | return _factors[i+1]; |
| 6593 | } |
| 6594 | else { |
| 6595 | return _factors[0] * 100; |
| 6596 | } |
| 6597 | }; |
| 6598 | |
| 6599 | // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n |
| 6600 | // it is based soley on the range and number of ticks. So if user specifies |
| 6601 | // number of ticks, use this. |
| 6602 | function bestInterval(range, numberTicks) { |
| 6603 | var minimum = range / (numberTicks - 1); |
| 6604 | var magnitude = Math.pow(10, Math.floor(Math.log(minimum) / Math.LN10)); |
| 6605 | var residual = minimum / magnitude; |
| 6606 | var interval; |
| 6607 | // "nicest" ranges are 1, 2, 5 or powers of these. |
| 6608 | // for magnitudes below 1, only allow these. |
| 6609 | if (magnitude < 1) { |
| 6610 | if (residual > 5) { |
| 6611 | interval = 10 * magnitude; |
| 6612 | } |
| 6613 | else if (residual > 2) { |
| 6614 | interval = 5 * magnitude; |
| 6615 | } |
| 6616 | else if (residual > 1) { |
| 6617 | interval = 2 * magnitude; |
| 6618 | } |
| 6619 | else { |
| 6620 | interval = magnitude; |
| 6621 | } |
| 6622 | } |
| 6623 | // for large ranges (whole integers), allow intervals like 3, 4 or powers of these. |
| 6624 | // this helps a lot with poor choices for number of ticks. |
| 6625 | else { |
| 6626 | if (residual > 5) { |
| 6627 | interval = 10 * magnitude; |
| 6628 | } |
| 6629 | else if (residual > 4) { |
| 6630 | interval = 5 * magnitude; |
| 6631 | } |
| 6632 | else if (residual > 3) { |
| 6633 | interval = 4 * magnitude; |
| 6634 | } |
| 6635 | else if (residual > 2) { |
| 6636 | interval = 3 * magnitude; |
| 6637 | } |
| 6638 | else if (residual > 1) { |
| 6639 | interval = 2 * magnitude; |
| 6640 | } |
| 6641 | else { |
| 6642 | interval = magnitude; |
| 6643 | } |
| 6644 | } |
| 6645 | |
| 6646 | return interval; |
| 6647 | } |
| 6648 | |
| 6649 | // This will return an interval of form 2 * 10^n, 5 * 10^n or 10 * 10^n |
| 6650 | // it is based soley on the range of data, number of ticks must be computed later. |
| 6651 | function bestLinearInterval(range, scalefact) { |
| 6652 | var expv = Math.floor(Math.log(range)/Math.LN10); |
| 6653 | var magnitude = Math.pow(10, expv); |
| 6654 | // 0 < f < 10 |
| 6655 | var f = range / magnitude; |
| 6656 | var fact; |
| 6657 | // for large plots, scalefact will decrease f and increase number of ticks. |
| 6658 | // for small plots, scalefact will increase f and decrease number of ticks. |
| 6659 | f = f/scalefact; |
| 6660 | |
| 6661 | // for large plots, smaller interval, more ticks. |
| 6662 | if (f<=0.38) { |
| 6663 | fact = 0.1; |
| 6664 | } |
| 6665 | else if (f<=1.6) { |
| 6666 | fact = 0.2; |
| 6667 | } |
| 6668 | else if (f<=4.0) { |
| 6669 | fact = 0.5; |
| 6670 | } |
| 6671 | else if (f<=8.0) { |
| 6672 | fact = 1.0; |
| 6673 | } |
| 6674 | // for very small plots, larger interval, less ticks in number ticks |
| 6675 | else if (f<=16.0) { |
| 6676 | fact = 2; |
| 6677 | } |
| 6678 | else { |
| 6679 | fact = 5; |
| 6680 | } |
| 6681 | |
| 6682 | return fact*magnitude; |
| 6683 | } |
| 6684 | |
| 6685 | function bestLinearComponents(range, scalefact) { |
| 6686 | var expv = Math.floor(Math.log(range)/Math.LN10); |
| 6687 | var magnitude = Math.pow(10, expv); |
| 6688 | // 0 < f < 10 |
| 6689 | var f = range / magnitude; |
| 6690 | var interval; |
| 6691 | var fact; |
| 6692 | // for large plots, scalefact will decrease f and increase number of ticks. |
| 6693 | // for small plots, scalefact will increase f and decrease number of ticks. |
| 6694 | f = f/scalefact; |
| 6695 | |
| 6696 | // for large plots, smaller interval, more ticks. |
| 6697 | if (f<=0.38) { |
| 6698 | fact = 0.1; |
| 6699 | } |
| 6700 | else if (f<=1.6) { |
| 6701 | fact = 0.2; |
| 6702 | } |
| 6703 | else if (f<=4.0) { |
| 6704 | fact = 0.5; |
| 6705 | } |
| 6706 | else if (f<=8.0) { |
| 6707 | fact = 1.0; |
| 6708 | } |
| 6709 | // for very small plots, larger interval, less ticks in number ticks |
| 6710 | else if (f<=16.0) { |
| 6711 | fact = 2; |
| 6712 | } |
| 6713 | // else if (f<=20.0) { |
| 6714 | // fact = 3; |
| 6715 | // } |
| 6716 | // else if (f<=24.0) { |
| 6717 | // fact = 4; |
| 6718 | // } |
| 6719 | else { |
| 6720 | fact = 5; |
| 6721 | } |
| 6722 | |
| 6723 | interval = fact * magnitude; |
| 6724 | |
| 6725 | return [interval, fact, magnitude]; |
| 6726 | } |
| 6727 | |
| 6728 | // Given the min and max for a dataset, return suitable endpoints |
| 6729 | // for the graphing, a good number for the number of ticks, and a |
| 6730 | // format string so that extraneous digits are not displayed. |
| 6731 | // returned is an array containing [min, max, nTicks, format] |
| 6732 | $.jqplot.LinearTickGenerator = function(axis_min, axis_max, scalefact, numberTicks) { |
| 6733 | // if endpoints are equal try to include zero otherwise include one |
| 6734 | if (axis_min === axis_max) { |
| 6735 | axis_max = (axis_max) ? 0 : 1; |
| 6736 | } |
| 6737 | |
| 6738 | scalefact = scalefact || 1.0; |
| 6739 | |
| 6740 | // make sure range is positive |
| 6741 | if (axis_max < axis_min) { |
| 6742 | var a = axis_max; |
| 6743 | axis_max = axis_min; |
| 6744 | axis_min = a; |
| 6745 | } |
| 6746 | |
| 6747 | var r = []; |
| 6748 | var ss = bestLinearInterval(axis_max - axis_min, scalefact); |
| 6749 | |
| 6750 | if (numberTicks == null) { |
| 6751 | |
| 6752 | // Figure out the axis min, max and number of ticks |
| 6753 | // the min and max will be some multiple of the tick interval, |
| 6754 | // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the |
| 6755 | // axis min is negative, 0 will be a tick. |
| 6756 | r[0] = Math.floor(axis_min / ss) * ss; // min |
| 6757 | r[1] = Math.ceil(axis_max / ss) * ss; // max |
| 6758 | r[2] = Math.round((r[1]-r[0])/ss+1.0); // number of ticks |
| 6759 | r[3] = bestFormatString(ss); // format string |
| 6760 | r[4] = ss; // tick Interval |
| 6761 | } |
| 6762 | |
| 6763 | else { |
| 6764 | var tempr = []; |
| 6765 | |
| 6766 | // Figure out the axis min, max and number of ticks |
| 6767 | // the min and max will be some multiple of the tick interval, |
| 6768 | // 1*10^n, 2*10^n or 5*10^n. This gaurantees that, if the |
| 6769 | // axis min is negative, 0 will be a tick. |
| 6770 | tempr[0] = Math.floor(axis_min / ss) * ss; // min |
| 6771 | tempr[1] = Math.ceil(axis_max / ss) * ss; // max |
| 6772 | tempr[2] = Math.round((tempr[1]-tempr[0])/ss+1.0); // number of ticks |
| 6773 | tempr[3] = bestFormatString(ss); // format string |
| 6774 | tempr[4] = ss; // tick Interval |
| 6775 | |
| 6776 | // first, see if we happen to get the right number of ticks |
| 6777 | if (tempr[2] === numberTicks) { |
| 6778 | r = tempr; |
| 6779 | } |
| 6780 | |
| 6781 | else { |
| 6782 | |
| 6783 | var newti = bestInterval(tempr[1] - tempr[0], numberTicks); |
| 6784 | |
| 6785 | r[0] = tempr[0]; |
| 6786 | r[2] = numberTicks; |
| 6787 | r[4] = newti; |
| 6788 | r[3] = bestFormatString(newti); |
| 6789 | r[1] = r[0] + (r[2] - 1) * r[4]; // max |
| 6790 | } |
| 6791 | } |
| 6792 | |
| 6793 | return r; |
| 6794 | }; |
| 6795 | |
| 6796 | $.jqplot.LinearTickGenerator.bestLinearInterval = bestLinearInterval; |
| 6797 | $.jqplot.LinearTickGenerator.bestInterval = bestInterval; |
| 6798 | $.jqplot.LinearTickGenerator.bestLinearComponents = bestLinearComponents; |
| 6799 | |
| 6800 | |
| 6801 | // class: $.jqplot.MarkerRenderer |
| 6802 | // The default jqPlot marker renderer, rendering the points on the line. |
| 6803 | $.jqplot.MarkerRenderer = function(options){ |
| 6804 | // Group: Properties |
| 6805 | |
| 6806 | // prop: show |
| 6807 | // wether or not to show the marker. |
| 6808 | this.show = true; |
| 6809 | // prop: style |
| 6810 | // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare |
| 6811 | this.style = 'filledCircle'; |
| 6812 | // prop: lineWidth |
| 6813 | // size of the line for non-filled markers. |
| 6814 | this.lineWidth = 2; |
| 6815 | // prop: size |
| 6816 | // Size of the marker (diameter or circle, length of edge of square, etc.) |
| 6817 | this.size = 9.0; |
| 6818 | // prop: color |
| 6819 | // color of marker. Will be set to color of series by default on init. |
| 6820 | this.color = '#666666'; |
| 6821 | // prop: shadow |
| 6822 | // wether or not to draw a shadow on the line |
| 6823 | this.shadow = true; |
| 6824 | // prop: shadowAngle |
| 6825 | // Shadow angle in degrees |
| 6826 | this.shadowAngle = 45; |
| 6827 | // prop: shadowOffset |
| 6828 | // Shadow offset from line in pixels |
| 6829 | this.shadowOffset = 1; |
| 6830 | // prop: shadowDepth |
| 6831 | // Number of times shadow is stroked, each stroke offset shadowOffset from the last. |
| 6832 | this.shadowDepth = 3; |
| 6833 | // prop: shadowAlpha |
| 6834 | // Alpha channel transparency of shadow. 0 = transparent. |
| 6835 | this.shadowAlpha = '0.07'; |
| 6836 | // prop: shadowRenderer |
| 6837 | // Renderer that will draws the shadows on the marker. |
| 6838 | this.shadowRenderer = new $.jqplot.ShadowRenderer(); |
| 6839 | // prop: shapeRenderer |
| 6840 | // Renderer that will draw the marker. |
| 6841 | this.shapeRenderer = new $.jqplot.ShapeRenderer(); |
| 6842 | |
| 6843 | $.extend(true, this, options); |
| 6844 | }; |
| 6845 | |
| 6846 | $.jqplot.MarkerRenderer.prototype.init = function(options) { |
| 6847 | $.extend(true, this, options); |
| 6848 | var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true}; |
| 6849 | if (this.style.indexOf('filled') != -1) { |
| 6850 | sdopt.fill = true; |
| 6851 | } |
| 6852 | if (this.style.indexOf('ircle') != -1) { |
| 6853 | sdopt.isarc = true; |
| 6854 | sdopt.closePath = false; |
| 6855 | } |
| 6856 | this.shadowRenderer.init(sdopt); |
| 6857 | |
| 6858 | var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true}; |
| 6859 | if (this.style.indexOf('filled') != -1) { |
| 6860 | shopt.fill = true; |
| 6861 | } |
| 6862 | if (this.style.indexOf('ircle') != -1) { |
| 6863 | shopt.isarc = true; |
| 6864 | shopt.closePath = false; |
| 6865 | } |
| 6866 | this.shapeRenderer.init(shopt); |
| 6867 | }; |
| 6868 | |
| 6869 | $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) { |
| 6870 | var stretch = 1.2; |
| 6871 | var dx = this.size/2/stretch; |
| 6872 | var dy = this.size/2*stretch; |
| 6873 | var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]]; |
| 6874 | if (this.shadow) { |
| 6875 | this.shadowRenderer.draw(ctx, points); |
| 6876 | } |
| 6877 | this.shapeRenderer.draw(ctx, points, options); |
| 6878 | }; |
| 6879 | |
| 6880 | $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) { |
| 6881 | var stretch = 1.0; |
| 6882 | var dx = this.size/2*stretch; |
| 6883 | var dy = this.size/2*stretch; |
| 6884 | var points1 = [[x, y-dy], [x, y+dy]]; |
| 6885 | var points2 = [[x+dx, y], [x-dx, y]]; |
| 6886 | var opts = $.extend(true, {}, this.options, {closePath:false}); |
| 6887 | if (this.shadow) { |
| 6888 | this.shadowRenderer.draw(ctx, points1, {closePath:false}); |
| 6889 | this.shadowRenderer.draw(ctx, points2, {closePath:false}); |
| 6890 | } |
| 6891 | this.shapeRenderer.draw(ctx, points1, opts); |
| 6892 | this.shapeRenderer.draw(ctx, points2, opts); |
| 6893 | }; |
| 6894 | |
| 6895 | $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) { |
| 6896 | var stretch = 1.0; |
| 6897 | var dx = this.size/2*stretch; |
| 6898 | var dy = this.size/2*stretch; |
| 6899 | var opts = $.extend(true, {}, this.options, {closePath:false}); |
| 6900 | var points1 = [[x-dx, y-dy], [x+dx, y+dy]]; |
| 6901 | var points2 = [[x-dx, y+dy], [x+dx, y-dy]]; |
| 6902 | if (this.shadow) { |
| 6903 | this.shadowRenderer.draw(ctx, points1, {closePath:false}); |
| 6904 | this.shadowRenderer.draw(ctx, points2, {closePath:false}); |
| 6905 | } |
| 6906 | this.shapeRenderer.draw(ctx, points1, opts); |
| 6907 | this.shapeRenderer.draw(ctx, points2, opts); |
| 6908 | }; |
| 6909 | |
| 6910 | $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) { |
| 6911 | var stretch = 1.0; |
| 6912 | var dx = this.size/2*stretch; |
| 6913 | var dy = this.size/2*stretch; |
| 6914 | var points = [[x-dx, y], [x+dx, y]]; |
| 6915 | if (this.shadow) { |
| 6916 | this.shadowRenderer.draw(ctx, points); |
| 6917 | } |
| 6918 | this.shapeRenderer.draw(ctx, points, options); |
| 6919 | }; |
| 6920 | |
| 6921 | $.jqplot.MarkerRenderer.prototype.drawLine = function(p1, p2, ctx, fill, options) { |
| 6922 | var points = [p1, p2]; |
| 6923 | if (this.shadow) { |
| 6924 | this.shadowRenderer.draw(ctx, points); |
| 6925 | } |
| 6926 | this.shapeRenderer.draw(ctx, points, options); |
| 6927 | }; |
| 6928 | |
| 6929 | $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) { |
| 6930 | var stretch = 1.0; |
| 6931 | var dx = this.size/2/stretch; |
| 6932 | var dy = this.size/2*stretch; |
| 6933 | var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]]; |
| 6934 | if (this.shadow) { |
| 6935 | this.shadowRenderer.draw(ctx, points); |
| 6936 | } |
| 6937 | this.shapeRenderer.draw(ctx, points, options); |
| 6938 | }; |
| 6939 | |
| 6940 | $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) { |
| 6941 | var radius = this.size/2; |
| 6942 | var end = 2*Math.PI; |
| 6943 | var points = [x, y, radius, 0, end, true]; |
| 6944 | if (this.shadow) { |
| 6945 | this.shadowRenderer.draw(ctx, points); |
| 6946 | } |
| 6947 | this.shapeRenderer.draw(ctx, points, options); |
| 6948 | }; |
| 6949 | |
| 6950 | $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) { |
| 6951 | options = options || {}; |
| 6952 | // hack here b/c shape renderer uses canvas based color style options |
| 6953 | // and marker uses css style names. |
| 6954 | if (options.show == null || options.show != false) { |
| 6955 | if (options.color && !options.fillStyle) { |
| 6956 | options.fillStyle = options.color; |
| 6957 | } |
| 6958 | if (options.color && !options.strokeStyle) { |
| 6959 | options.strokeStyle = options.color; |
| 6960 | } |
| 6961 | switch (this.style) { |
| 6962 | case 'diamond': |
| 6963 | this.drawDiamond(x,y,ctx, false, options); |
| 6964 | break; |
| 6965 | case 'filledDiamond': |
| 6966 | this.drawDiamond(x,y,ctx, true, options); |
| 6967 | break; |
| 6968 | case 'circle': |
| 6969 | this.drawCircle(x,y,ctx, false, options); |
| 6970 | break; |
| 6971 | case 'filledCircle': |
| 6972 | this.drawCircle(x,y,ctx, true, options); |
| 6973 | break; |
| 6974 | case 'square': |
| 6975 | this.drawSquare(x,y,ctx, false, options); |
| 6976 | break; |
| 6977 | case 'filledSquare': |
| 6978 | this.drawSquare(x,y,ctx, true, options); |
| 6979 | break; |
| 6980 | case 'x': |
| 6981 | this.drawX(x,y,ctx, true, options); |
| 6982 | break; |
| 6983 | case 'plus': |
| 6984 | this.drawPlus(x,y,ctx, true, options); |
| 6985 | break; |
| 6986 | case 'dash': |
| 6987 | this.drawDash(x,y,ctx, true, options); |
| 6988 | break; |
| 6989 | case 'line': |
| 6990 | this.drawLine(x, y, ctx, false, options); |
| 6991 | break; |
| 6992 | default: |
| 6993 | this.drawDiamond(x,y,ctx, false, options); |
| 6994 | break; |
| 6995 | } |
| 6996 | } |
| 6997 | }; |
| 6998 | |
| 6999 | // class: $.jqplot.shadowRenderer |
| 7000 | // The default jqPlot shadow renderer, rendering shadows behind shapes. |
| 7001 | $.jqplot.ShadowRenderer = function(options){ |
| 7002 | // Group: Properties |
| 7003 | |
| 7004 | // prop: angle |
| 7005 | // Angle of the shadow in degrees. Measured counter-clockwise from the x axis. |
| 7006 | this.angle = 45; |
| 7007 | // prop: offset |
| 7008 | // Pixel offset at the given shadow angle of each shadow stroke from the last stroke. |
| 7009 | this.offset = 1; |
| 7010 | // prop: alpha |
| 7011 | // alpha transparency of shadow stroke. |
| 7012 | this.alpha = 0.07; |
| 7013 | // prop: lineWidth |
| 7014 | // width of the shadow line stroke. |
| 7015 | this.lineWidth = 1.5; |
| 7016 | // prop: lineJoin |
| 7017 | // How line segments of the shadow are joined. |
| 7018 | this.lineJoin = 'miter'; |
| 7019 | // prop: lineCap |
| 7020 | // how ends of the shadow line are rendered. |
| 7021 | this.lineCap = 'round'; |
| 7022 | // prop; closePath |
| 7023 | // whether line path segment is closed upon itself. |
| 7024 | this.closePath = false; |
| 7025 | // prop: fill |
| 7026 | // whether to fill the shape. |
| 7027 | this.fill = false; |
| 7028 | // prop: depth |
| 7029 | // how many times the shadow is stroked. Each stroke will be offset by offset at angle degrees. |
| 7030 | this.depth = 3; |
| 7031 | this.strokeStyle = 'rgba(0,0,0,0.1)'; |
| 7032 | // prop: isarc |
| 7033 | // wether the shadow is an arc or not. |
| 7034 | this.isarc = false; |
| 7035 | |
| 7036 | $.extend(true, this, options); |
| 7037 | }; |
| 7038 | |
| 7039 | $.jqplot.ShadowRenderer.prototype.init = function(options) { |
| 7040 | $.extend(true, this, options); |
| 7041 | }; |
| 7042 | |
| 7043 | // function: draw |
| 7044 | // draws an transparent black (i.e. gray) shadow. |
| 7045 | // |
| 7046 | // ctx - canvas drawing context |
| 7047 | // points - array of points or [x, y, radius, start angle (rad), end angle (rad)] |
| 7048 | $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) { |
| 7049 | ctx.save(); |
| 7050 | var opts = (options != null) ? options : {}; |
| 7051 | var fill = (opts.fill != null) ? opts.fill : this.fill; |
| 7052 | var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect; |
| 7053 | var closePath = (opts.closePath != null) ? opts.closePath : this.closePath; |
| 7054 | var offset = (opts.offset != null) ? opts.offset : this.offset; |
| 7055 | var alpha = (opts.alpha != null) ? opts.alpha : this.alpha; |
| 7056 | var depth = (opts.depth != null) ? opts.depth : this.depth; |
| 7057 | var isarc = (opts.isarc != null) ? opts.isarc : this.isarc; |
| 7058 | var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern; |
| 7059 | ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth; |
| 7060 | ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin; |
| 7061 | ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap; |
| 7062 | ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')'; |
| 7063 | ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')'; |
| 7064 | for (var j=0; j<depth; j++) { |
| 7065 | var ctxPattern = $.jqplot.LinePattern(ctx, linePattern); |
| 7066 | ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset); |
| 7067 | ctxPattern.beginPath(); |
| 7068 | if (isarc) { |
| 7069 | ctx.arc(points[0], points[1], points[2], points[3], points[4], true); |
| 7070 | } |
| 7071 | else if (fillRect) { |
| 7072 | if (fillRect) { |
| 7073 | ctx.fillRect(points[0], points[1], points[2], points[3]); |
| 7074 | } |
| 7075 | } |
| 7076 | else if (points && points.length){ |
| 7077 | var move = true; |
| 7078 | for (var i=0; i<points.length; i++) { |
| 7079 | // skip to the first non-null point and move to it. |
| 7080 | if (points[i][0] != null && points[i][1] != null) { |
| 7081 | if (move) { |
| 7082 | ctxPattern.moveTo(points[i][0], points[i][1]); |
| 7083 | move = false; |
| 7084 | } |
| 7085 | else { |
| 7086 | ctxPattern.lineTo(points[i][0], points[i][1]); |
| 7087 | } |
| 7088 | } |
| 7089 | else { |
| 7090 | move = true; |
| 7091 | } |
| 7092 | } |
| 7093 | |
| 7094 | } |
| 7095 | if (closePath) { |
| 7096 | ctxPattern.closePath(); |
| 7097 | } |
| 7098 | if (fill) { |
| 7099 | ctx.fill(); |
| 7100 | } |
| 7101 | else { |
| 7102 | ctx.stroke(); |
| 7103 | } |
| 7104 | } |
| 7105 | ctx.restore(); |
| 7106 | }; |
| 7107 | |
| 7108 | // class: $.jqplot.shapeRenderer |
| 7109 | // The default jqPlot shape renderer. Given a set of points will |
| 7110 | // plot them and either stroke a line (fill = false) or fill them (fill = true). |
| 7111 | // If a filled shape is desired, closePath = true must also be set to close |
| 7112 | // the shape. |
| 7113 | $.jqplot.ShapeRenderer = function(options){ |
| 7114 | |
| 7115 | this.lineWidth = 1.5; |
| 7116 | // prop: linePattern |
| 7117 | // line pattern 'dashed', 'dotted', 'solid', some combination |
| 7118 | // of '-' and '.' characters such as '.-.' or a numerical array like |
| 7119 | // [draw, skip, draw, skip, ...] such as [1, 10] to draw a dotted line, |
| 7120 | // [1, 10, 20, 10] to draw a dot-dash line, and so on. |
| 7121 | this.linePattern = 'solid'; |
| 7122 | // prop: lineJoin |
| 7123 | // How line segments of the shadow are joined. |
| 7124 | this.lineJoin = 'miter'; |
| 7125 | // prop: lineCap |
| 7126 | // how ends of the shadow line are rendered. |
| 7127 | this.lineCap = 'round'; |
| 7128 | // prop; closePath |
| 7129 | // whether line path segment is closed upon itself. |
| 7130 | this.closePath = false; |
| 7131 | // prop: fill |
| 7132 | // whether to fill the shape. |
| 7133 | this.fill = false; |
| 7134 | // prop: isarc |
| 7135 | // wether the shadow is an arc or not. |
| 7136 | this.isarc = false; |
| 7137 | // prop: fillRect |
| 7138 | // true to draw shape as a filled rectangle. |
| 7139 | this.fillRect = false; |
| 7140 | // prop: strokeRect |
| 7141 | // true to draw shape as a stroked rectangle. |
| 7142 | this.strokeRect = false; |
| 7143 | // prop: clearRect |
| 7144 | // true to cear a rectangle. |
| 7145 | this.clearRect = false; |
| 7146 | // prop: strokeStyle |
| 7147 | // css color spec for the stoke style |
| 7148 | this.strokeStyle = '#999999'; |
| 7149 | // prop: fillStyle |
| 7150 | // css color spec for the fill style. |
| 7151 | this.fillStyle = '#999999'; |
| 7152 | |
| 7153 | $.extend(true, this, options); |
| 7154 | }; |
| 7155 | |
| 7156 | $.jqplot.ShapeRenderer.prototype.init = function(options) { |
| 7157 | $.extend(true, this, options); |
| 7158 | }; |
| 7159 | |
| 7160 | // function: draw |
| 7161 | // draws the shape. |
| 7162 | // |
| 7163 | // ctx - canvas drawing context |
| 7164 | // points - array of points for shapes or |
| 7165 | // [x, y, width, height] for rectangles or |
| 7166 | // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs. |
| 7167 | $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) { |
| 7168 | ctx.save(); |
| 7169 | var opts = (options != null) ? options : {}; |
| 7170 | var fill = (opts.fill != null) ? opts.fill : this.fill; |
| 7171 | var closePath = (opts.closePath != null) ? opts.closePath : this.closePath; |
| 7172 | var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect; |
| 7173 | var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect; |
| 7174 | var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect; |
| 7175 | var isarc = (opts.isarc != null) ? opts.isarc : this.isarc; |
| 7176 | var linePattern = (opts.linePattern != null) ? opts.linePattern : this.linePattern; |
| 7177 | var ctxPattern = $.jqplot.LinePattern(ctx, linePattern); |
| 7178 | ctx.lineWidth = opts.lineWidth || this.lineWidth; |
| 7179 | ctx.lineJoin = opts.lineJoin || this.lineJoin; |
| 7180 | ctx.lineCap = opts.lineCap || this.lineCap; |
| 7181 | ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle; |
| 7182 | ctx.fillStyle = opts.fillStyle || this.fillStyle; |
| 7183 | ctx.beginPath(); |
| 7184 | if (isarc) { |
| 7185 | ctx.arc(points[0], points[1], points[2], points[3], points[4], true); |
| 7186 | if (closePath) { |
| 7187 | ctx.closePath(); |
| 7188 | } |
| 7189 | if (fill) { |
| 7190 | ctx.fill(); |
| 7191 | } |
| 7192 | else { |
| 7193 | ctx.stroke(); |
| 7194 | } |
| 7195 | ctx.restore(); |
| 7196 | return; |
| 7197 | } |
| 7198 | else if (clearRect) { |
| 7199 | ctx.clearRect(points[0], points[1], points[2], points[3]); |
| 7200 | ctx.restore(); |
| 7201 | return; |
| 7202 | } |
| 7203 | else if (fillRect || strokeRect) { |
| 7204 | if (fillRect) { |
| 7205 | ctx.fillRect(points[0], points[1], points[2], points[3]); |
| 7206 | } |
| 7207 | if (strokeRect) { |
| 7208 | ctx.strokeRect(points[0], points[1], points[2], points[3]); |
| 7209 | ctx.restore(); |
| 7210 | return; |
| 7211 | } |
| 7212 | } |
| 7213 | else if (points && points.length){ |
| 7214 | var move = true; |
| 7215 | for (var i=0; i<points.length; i++) { |
| 7216 | // skip to the first non-null point and move to it. |
| 7217 | if (points[i][0] != null && points[i][1] != null) { |
| 7218 | if (move) { |
| 7219 | ctxPattern.moveTo(points[i][0], points[i][1]); |
| 7220 | move = false; |
| 7221 | } |
| 7222 | else { |
| 7223 | ctxPattern.lineTo(points[i][0], points[i][1]); |
| 7224 | } |
| 7225 | } |
| 7226 | else { |
| 7227 | move = true; |
| 7228 | } |
| 7229 | } |
| 7230 | if (closePath) { |
| 7231 | ctxPattern.closePath(); |
| 7232 | } |
| 7233 | if (fill) { |
| 7234 | ctx.fill(); |
| 7235 | } |
| 7236 | else { |
| 7237 | ctx.stroke(); |
| 7238 | } |
| 7239 | } |
| 7240 | ctx.restore(); |
| 7241 | }; |
| 7242 | |
| 7243 | // class $.jqplot.TableLegendRenderer |
| 7244 | // The default legend renderer for jqPlot. |
| 7245 | $.jqplot.TableLegendRenderer = function(){ |
| 7246 | // |
| 7247 | }; |
| 7248 | |
| 7249 | $.jqplot.TableLegendRenderer.prototype.init = function(options) { |
| 7250 | $.extend(true, this, options); |
| 7251 | }; |
| 7252 | |
| 7253 | $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) { |
| 7254 | var rs = (pad) ? this.rowSpacing+'px' : '0px'; |
| 7255 | var tr; |
| 7256 | var td; |
| 7257 | var elem; |
| 7258 | var div0; |
| 7259 | var div1; |
| 7260 | elem = document.createElement('tr'); |
| 7261 | tr = $(elem); |
| 7262 | tr.addClass('jqplot-table-legend'); |
| 7263 | elem = null; |
| 7264 | |
| 7265 | if (reverse){ |
| 7266 | tr.prependTo(this._elem); |
| 7267 | } |
| 7268 | |
| 7269 | else{ |
| 7270 | tr.appendTo(this._elem); |
| 7271 | } |
| 7272 | |
| 7273 | if (this.showSwatches) { |
| 7274 | td = $(document.createElement('td')); |
| 7275 | td.addClass('jqplot-table-legend jqplot-table-legend-swatch'); |
| 7276 | td.css({textAlign: 'center', paddingTop: rs}); |
| 7277 | |
| 7278 | div0 = $(document.createElement('div')); |
| 7279 | div0.addClass('jqplot-table-legend-swatch-outline'); |
| 7280 | div1 = $(document.createElement('div')); |
| 7281 | div1.addClass('jqplot-table-legend-swatch'); |
| 7282 | div1.css({backgroundColor: color, borderColor: color}); |
| 7283 | |
| 7284 | tr.append(td.append(div0.append(div1))); |
| 7285 | |
| 7286 | // $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+ |
| 7287 | // '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+ |
| 7288 | // '</div></td>').appendTo(tr); |
| 7289 | } |
| 7290 | if (this.showLabels) { |
| 7291 | td = $(document.createElement('td')); |
| 7292 | td.addClass('jqplot-table-legend jqplot-table-legend-label'); |
| 7293 | td.css('paddingTop', rs); |
| 7294 | tr.append(td); |
| 7295 | |
| 7296 | // elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>'); |
| 7297 | // elem.appendTo(tr); |
| 7298 | if (this.escapeHtml) { |
| 7299 | td.text(label); |
| 7300 | } |
| 7301 | else { |
| 7302 | td.html(label); |
| 7303 | } |
| 7304 | } |
| 7305 | td = null; |
| 7306 | div0 = null; |
| 7307 | div1 = null; |
| 7308 | tr = null; |
| 7309 | elem = null; |
| 7310 | }; |
| 7311 | |
| 7312 | // called with scope of legend |
| 7313 | $.jqplot.TableLegendRenderer.prototype.draw = function() { |
| 7314 | if (this._elem) { |
| 7315 | this._elem.emptyForce(); |
| 7316 | this._elem = null; |
| 7317 | } |
| 7318 | |
| 7319 | if (this.show) { |
| 7320 | var series = this._series; |
| 7321 | // make a table. one line label per row. |
| 7322 | var elem = document.createElement('table'); |
| 7323 | this._elem = $(elem); |
| 7324 | this._elem.addClass('jqplot-table-legend'); |
| 7325 | |
| 7326 | var ss = {position:'absolute'}; |
| 7327 | if (this.background) { |
| 7328 | ss['background'] = this.background; |
| 7329 | } |
| 7330 | if (this.border) { |
| 7331 | ss['border'] = this.border; |
| 7332 | } |
| 7333 | if (this.fontSize) { |
| 7334 | ss['fontSize'] = this.fontSize; |
| 7335 | } |
| 7336 | if (this.fontFamily) { |
| 7337 | ss['fontFamily'] = this.fontFamily; |
| 7338 | } |
| 7339 | if (this.textColor) { |
| 7340 | ss['textColor'] = this.textColor; |
| 7341 | } |
| 7342 | if (this.marginTop != null) { |
| 7343 | ss['marginTop'] = this.marginTop; |
| 7344 | } |
| 7345 | if (this.marginBottom != null) { |
| 7346 | ss['marginBottom'] = this.marginBottom; |
| 7347 | } |
| 7348 | if (this.marginLeft != null) { |
| 7349 | ss['marginLeft'] = this.marginLeft; |
| 7350 | } |
| 7351 | if (this.marginRight != null) { |
| 7352 | ss['marginRight'] = this.marginRight; |
| 7353 | } |
| 7354 | |
| 7355 | |
| 7356 | var pad = false, |
| 7357 | reverse = false, |
| 7358 | s; |
| 7359 | for (var i = 0; i< series.length; i++) { |
| 7360 | s = series[i]; |
| 7361 | if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){ |
| 7362 | reverse = true; |
| 7363 | } |
| 7364 | if (s.show && s.showLabel) { |
| 7365 | var lt = this.labels[i] || s.label.toString(); |
| 7366 | if (lt) { |
| 7367 | var color = s.color; |
| 7368 | if (reverse && i < series.length - 1){ |
| 7369 | pad = true; |
| 7370 | } |
| 7371 | else if (reverse && i == series.length - 1){ |
| 7372 | pad = false; |
| 7373 | } |
| 7374 | this.renderer.addrow.call(this, lt, color, pad, reverse); |
| 7375 | pad = true; |
| 7376 | } |
| 7377 | // let plugins add more rows to legend. Used by trend line plugin. |
| 7378 | for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) { |
| 7379 | var item = $.jqplot.addLegendRowHooks[j].call(this, s); |
| 7380 | if (item) { |
| 7381 | this.renderer.addrow.call(this, item.label, item.color, pad); |
| 7382 | pad = true; |
| 7383 | } |
| 7384 | } |
| 7385 | lt = null; |
| 7386 | } |
| 7387 | } |
| 7388 | } |
| 7389 | return this._elem; |
| 7390 | }; |
| 7391 | |
| 7392 | $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) { |
| 7393 | if (this.show) { |
| 7394 | if (this.placement == 'insideGrid') { |
| 7395 | switch (this.location) { |
| 7396 | case 'nw': |
| 7397 | var a = offsets.left; |
| 7398 | var b = offsets.top; |
| 7399 | this._elem.css('left', a); |
| 7400 | this._elem.css('top', b); |
| 7401 | break; |
| 7402 | case 'n': |
| 7403 | var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; |
| 7404 | var b = offsets.top; |
| 7405 | this._elem.css('left', a); |
| 7406 | this._elem.css('top', b); |
| 7407 | break; |
| 7408 | case 'ne': |
| 7409 | var a = offsets.right; |
| 7410 | var b = offsets.top; |
| 7411 | this._elem.css({right:a, top:b}); |
| 7412 | break; |
| 7413 | case 'e': |
| 7414 | var a = offsets.right; |
| 7415 | var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; |
| 7416 | this._elem.css({right:a, top:b}); |
| 7417 | break; |
| 7418 | case 'se': |
| 7419 | var a = offsets.right; |
| 7420 | var b = offsets.bottom; |
| 7421 | this._elem.css({right:a, bottom:b}); |
| 7422 | break; |
| 7423 | case 's': |
| 7424 | var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; |
| 7425 | var b = offsets.bottom; |
| 7426 | this._elem.css({left:a, bottom:b}); |
| 7427 | break; |
| 7428 | case 'sw': |
| 7429 | var a = offsets.left; |
| 7430 | var b = offsets.bottom; |
| 7431 | this._elem.css({left:a, bottom:b}); |
| 7432 | break; |
| 7433 | case 'w': |
| 7434 | var a = offsets.left; |
| 7435 | var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; |
| 7436 | this._elem.css({left:a, top:b}); |
| 7437 | break; |
| 7438 | default: // same as 'se' |
| 7439 | var a = offsets.right; |
| 7440 | var b = offsets.bottom; |
| 7441 | this._elem.css({right:a, bottom:b}); |
| 7442 | break; |
| 7443 | } |
| 7444 | |
| 7445 | } |
| 7446 | else if (this.placement == 'outside'){ |
| 7447 | switch (this.location) { |
| 7448 | case 'nw': |
| 7449 | var a = this._plotDimensions.width - offsets.left; |
| 7450 | var b = offsets.top; |
| 7451 | this._elem.css('right', a); |
| 7452 | this._elem.css('top', b); |
| 7453 | break; |
| 7454 | case 'n': |
| 7455 | var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; |
| 7456 | var b = this._plotDimensions.height - offsets.top; |
| 7457 | this._elem.css('left', a); |
| 7458 | this._elem.css('bottom', b); |
| 7459 | break; |
| 7460 | case 'ne': |
| 7461 | var a = this._plotDimensions.width - offsets.right; |
| 7462 | var b = offsets.top; |
| 7463 | this._elem.css({left:a, top:b}); |
| 7464 | break; |
| 7465 | case 'e': |
| 7466 | var a = this._plotDimensions.width - offsets.right; |
| 7467 | var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; |
| 7468 | this._elem.css({left:a, top:b}); |
| 7469 | break; |
| 7470 | case 'se': |
| 7471 | var a = this._plotDimensions.width - offsets.right; |
| 7472 | var b = offsets.bottom; |
| 7473 | this._elem.css({left:a, bottom:b}); |
| 7474 | break; |
| 7475 | case 's': |
| 7476 | var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; |
| 7477 | var b = this._plotDimensions.height - offsets.bottom; |
| 7478 | this._elem.css({left:a, top:b}); |
| 7479 | break; |
| 7480 | case 'sw': |
| 7481 | var a = this._plotDimensions.width - offsets.left; |
| 7482 | var b = offsets.bottom; |
| 7483 | this._elem.css({right:a, bottom:b}); |
| 7484 | break; |
| 7485 | case 'w': |
| 7486 | var a = this._plotDimensions.width - offsets.left; |
| 7487 | var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; |
| 7488 | this._elem.css({right:a, top:b}); |
| 7489 | break; |
| 7490 | default: // same as 'se' |
| 7491 | var a = offsets.right; |
| 7492 | var b = offsets.bottom; |
| 7493 | this._elem.css({right:a, bottom:b}); |
| 7494 | break; |
| 7495 | } |
| 7496 | } |
| 7497 | else { |
| 7498 | switch (this.location) { |
| 7499 | case 'nw': |
| 7500 | this._elem.css({left:0, top:offsets.top}); |
| 7501 | break; |
| 7502 | case 'n': |
| 7503 | var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; |
| 7504 | this._elem.css({left: a, top:offsets.top}); |
| 7505 | break; |
| 7506 | case 'ne': |
| 7507 | this._elem.css({right:0, top:offsets.top}); |
| 7508 | break; |
| 7509 | case 'e': |
| 7510 | var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; |
| 7511 | this._elem.css({right:offsets.right, top:b}); |
| 7512 | break; |
| 7513 | case 'se': |
| 7514 | this._elem.css({right:offsets.right, bottom:offsets.bottom}); |
| 7515 | break; |
| 7516 | case 's': |
| 7517 | var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; |
| 7518 | this._elem.css({left: a, bottom:offsets.bottom}); |
| 7519 | break; |
| 7520 | case 'sw': |
| 7521 | this._elem.css({left:offsets.left, bottom:offsets.bottom}); |
| 7522 | break; |
| 7523 | case 'w': |
| 7524 | var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; |
| 7525 | this._elem.css({left:offsets.left, top:b}); |
| 7526 | break; |
| 7527 | default: // same as 'se' |
| 7528 | this._elem.css({right:offsets.right, bottom:offsets.bottom}); |
| 7529 | break; |
| 7530 | } |
| 7531 | } |
| 7532 | } |
| 7533 | }; |
| 7534 | |
| 7535 | /** |
| 7536 | * Class: $.jqplot.ThemeEngine |
| 7537 | * Theme Engine provides a programatic way to change some of the more |
| 7538 | * common jqplot styling options such as fonts, colors and grid options. |
| 7539 | * A theme engine instance is created with each plot. The theme engine |
| 7540 | * manages a collection of themes which can be modified, added to, or |
| 7541 | * applied to the plot. |
| 7542 | * |
| 7543 | * The themeEngine class is not instantiated directly. |
| 7544 | * When a plot is initialized, the current plot options are scanned |
| 7545 | * an a default theme named "Default" is created. This theme is |
| 7546 | * used as the basis for other themes added to the theme engine and |
| 7547 | * is always available. |
| 7548 | * |
| 7549 | * A theme is a simple javascript object with styling parameters for |
| 7550 | * various entities of the plot. A theme has the form: |
| 7551 | * |
| 7552 | * |
| 7553 | * > { |
| 7554 | * > _name:f "Default", |
| 7555 | * > target: { |
| 7556 | * > backgroundColor: "transparent" |
| 7557 | * > }, |
| 7558 | * > legend: { |
| 7559 | * > textColor: null, |
| 7560 | * > fontFamily: null, |
| 7561 | * > fontSize: null, |
| 7562 | * > border: null, |
| 7563 | * > background: null |
| 7564 | * > }, |
| 7565 | * > title: { |
| 7566 | * > textColor: "rgb(102, 102, 102)", |
| 7567 | * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif", |
| 7568 | * > fontSize: "19.2px", |
| 7569 | * > textAlign: "center" |
| 7570 | * > }, |
| 7571 | * > seriesStyles: {}, |
| 7572 | * > series: [{ |
| 7573 | * > color: "#4bb2c5", |
| 7574 | * > lineWidth: 2.5, |
| 7575 | * > linePattern: "solid", |
| 7576 | * > shadow: true, |
| 7577 | * > fillColor: "#4bb2c5", |
| 7578 | * > showMarker: true, |
| 7579 | * > markerOptions: { |
| 7580 | * > color: "#4bb2c5", |
| 7581 | * > show: true, |
| 7582 | * > style: 'filledCircle', |
| 7583 | * > lineWidth: 1.5, |
| 7584 | * > size: 4, |
| 7585 | * > shadow: true |
| 7586 | * > } |
| 7587 | * > }], |
| 7588 | * > grid: { |
| 7589 | * > drawGridlines: true, |
| 7590 | * > gridLineColor: "#cccccc", |
| 7591 | * > gridLineWidth: 1, |
| 7592 | * > backgroundColor: "#fffdf6", |
| 7593 | * > borderColor: "#999999", |
| 7594 | * > borderWidth: 2, |
| 7595 | * > shadow: true |
| 7596 | * > }, |
| 7597 | * > axesStyles: { |
| 7598 | * > label: {}, |
| 7599 | * > ticks: {} |
| 7600 | * > }, |
| 7601 | * > axes: { |
| 7602 | * > xaxis: { |
| 7603 | * > borderColor: "#999999", |
| 7604 | * > borderWidth: 2, |
| 7605 | * > ticks: { |
| 7606 | * > show: true, |
| 7607 | * > showGridline: true, |
| 7608 | * > showLabel: true, |
| 7609 | * > showMark: true, |
| 7610 | * > size: 4, |
| 7611 | * > textColor: "", |
| 7612 | * > whiteSpace: "nowrap", |
| 7613 | * > fontSize: "12px", |
| 7614 | * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif" |
| 7615 | * > }, |
| 7616 | * > label: { |
| 7617 | * > textColor: "rgb(102, 102, 102)", |
| 7618 | * > whiteSpace: "normal", |
| 7619 | * > fontSize: "14.6667px", |
| 7620 | * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif", |
| 7621 | * > fontWeight: "400" |
| 7622 | * > } |
| 7623 | * > }, |
| 7624 | * > yaxis: { |
| 7625 | * > borderColor: "#999999", |
| 7626 | * > borderWidth: 2, |
| 7627 | * > ticks: { |
| 7628 | * > show: true, |
| 7629 | * > showGridline: true, |
| 7630 | * > showLabel: true, |
| 7631 | * > showMark: true, |
| 7632 | * > size: 4, |
| 7633 | * > textColor: "", |
| 7634 | * > whiteSpace: "nowrap", |
| 7635 | * > fontSize: "12px", |
| 7636 | * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif" |
| 7637 | * > }, |
| 7638 | * > label: { |
| 7639 | * > textColor: null, |
| 7640 | * > whiteSpace: null, |
| 7641 | * > fontSize: null, |
| 7642 | * > fontFamily: null, |
| 7643 | * > fontWeight: null |
| 7644 | * > } |
| 7645 | * > }, |
| 7646 | * > x2axis: {... |
| 7647 | * > }, |
| 7648 | * > ... |
| 7649 | * > y9axis: {... |
| 7650 | * > } |
| 7651 | * > } |
| 7652 | * > } |
| 7653 | * |
| 7654 | * "seriesStyles" is a style object that will be applied to all series in the plot. |
| 7655 | * It will forcibly override any styles applied on the individual series. "axesStyles" is |
| 7656 | * a style object that will be applied to all axes in the plot. It will also forcibly |
| 7657 | * override any styles on the individual axes. |
| 7658 | * |
| 7659 | * The example shown above has series options for a line series. Options for other |
| 7660 | * series types are shown below: |
| 7661 | * |
| 7662 | * Bar Series: |
| 7663 | * |
| 7664 | * > { |
| 7665 | * > color: "#4bb2c5", |
| 7666 | * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], |
| 7667 | * > lineWidth: 2.5, |
| 7668 | * > shadow: true, |
| 7669 | * > barPadding: 2, |
| 7670 | * > barMargin: 10, |
| 7671 | * > barWidth: 15.09375, |
| 7672 | * > highlightColors: ["rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)", "rgb(129,201,214)"] |
| 7673 | * > } |
| 7674 | * |
| 7675 | * Pie Series: |
| 7676 | * |
| 7677 | * > { |
| 7678 | * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], |
| 7679 | * > padding: 20, |
| 7680 | * > sliceMargin: 0, |
| 7681 | * > fill: true, |
| 7682 | * > shadow: true, |
| 7683 | * > startAngle: 0, |
| 7684 | * > lineWidth: 2.5, |
| 7685 | * > highlightColors: ["rgb(129,201,214)", "rgb(240,189,104)", "rgb(214,202,165)", "rgb(137,180,158)", "rgb(168,180,137)", "rgb(180,174,89)", "rgb(180,113,161)", "rgb(129,141,236)", "rgb(227,205,120)", "rgb(255,138,76)", "rgb(76,169,219)", "rgb(215,126,190)", "rgb(220,232,135)", "rgb(200,167,96)", "rgb(103,202,235)", "rgb(208,154,215)"] |
| 7686 | * > } |
| 7687 | * |
| 7688 | * Funnel Series: |
| 7689 | * |
| 7690 | * > { |
| 7691 | * > color: "#4bb2c5", |
| 7692 | * > lineWidth: 2, |
| 7693 | * > shadow: true, |
| 7694 | * > padding: { |
| 7695 | * > top: 20, |
| 7696 | * > right: 20, |
| 7697 | * > bottom: 20, |
| 7698 | * > left: 20 |
| 7699 | * > }, |
| 7700 | * > sectionMargin: 6, |
| 7701 | * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], |
| 7702 | * > highlightColors: ["rgb(147,208,220)", "rgb(242,199,126)", "rgb(220,210,178)", "rgb(154,191,172)", "rgb(180,191,154)", "rgb(191,186,112)", "rgb(191,133,174)", "rgb(147,157,238)", "rgb(231,212,139)", "rgb(255,154,102)", "rgb(102,181,224)", "rgb(221,144,199)", "rgb(225,235,152)", "rgb(200,167,96)", "rgb(124,210,238)", "rgb(215,169,221)"] |
| 7703 | * > } |
| 7704 | * |
| 7705 | */ |
| 7706 | $.jqplot.ThemeEngine = function(){ |
| 7707 | // Group: Properties |
| 7708 | // |
| 7709 | // prop: themes |
| 7710 | // hash of themes managed by the theme engine. |
| 7711 | // Indexed by theme name. |
| 7712 | this.themes = {}; |
| 7713 | // prop: activeTheme |
| 7714 | // Pointer to currently active theme |
| 7715 | this.activeTheme=null; |
| 7716 | |
| 7717 | }; |
| 7718 | |
| 7719 | // called with scope of plot |
| 7720 | $.jqplot.ThemeEngine.prototype.init = function() { |
| 7721 | // get the Default theme from the current plot settings. |
| 7722 | var th = new $.jqplot.Theme({_name:'Default'}); |
| 7723 | var n, i, nn; |
| 7724 | |
| 7725 | for (n in th.target) { |
| 7726 | if (n == "textColor") { |
| 7727 | th.target[n] = this.target.css('color'); |
| 7728 | } |
| 7729 | else { |
| 7730 | th.target[n] = this.target.css(n); |
| 7731 | } |
| 7732 | } |
| 7733 | |
| 7734 | if (this.title.show && this.title._elem) { |
| 7735 | for (n in th.title) { |
| 7736 | if (n == "textColor") { |
| 7737 | th.title[n] = this.title._elem.css('color'); |
| 7738 | } |
| 7739 | else { |
| 7740 | th.title[n] = this.title._elem.css(n); |
| 7741 | } |
| 7742 | } |
| 7743 | } |
| 7744 | |
| 7745 | for (n in th.grid) { |
| 7746 | th.grid[n] = this.grid[n]; |
| 7747 | } |
| 7748 | if (th.grid.backgroundColor == null && this.grid.background != null) { |
| 7749 | th.grid.backgroundColor = this.grid.background; |
| 7750 | } |
| 7751 | if (this.legend.show && this.legend._elem) { |
| 7752 | for (n in th.legend) { |
| 7753 | if (n == 'textColor') { |
| 7754 | th.legend[n] = this.legend._elem.css('color'); |
| 7755 | } |
| 7756 | else { |
| 7757 | th.legend[n] = this.legend._elem.css(n); |
| 7758 | } |
| 7759 | } |
| 7760 | } |
| 7761 | var s; |
| 7762 | |
| 7763 | for (i=0; i<this.series.length; i++) { |
| 7764 | s = this.series[i]; |
| 7765 | if (s.renderer.constructor == $.jqplot.LineRenderer) { |
| 7766 | th.series.push(new LineSeriesProperties()); |
| 7767 | } |
| 7768 | else if (s.renderer.constructor == $.jqplot.BarRenderer) { |
| 7769 | th.series.push(new BarSeriesProperties()); |
| 7770 | } |
| 7771 | else if (s.renderer.constructor == $.jqplot.PieRenderer) { |
| 7772 | th.series.push(new PieSeriesProperties()); |
| 7773 | } |
| 7774 | else if (s.renderer.constructor == $.jqplot.DonutRenderer) { |
| 7775 | th.series.push(new DonutSeriesProperties()); |
| 7776 | } |
| 7777 | else if (s.renderer.constructor == $.jqplot.FunnelRenderer) { |
| 7778 | th.series.push(new FunnelSeriesProperties()); |
| 7779 | } |
| 7780 | else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) { |
| 7781 | th.series.push(new MeterSeriesProperties()); |
| 7782 | } |
| 7783 | else { |
| 7784 | th.series.push({}); |
| 7785 | } |
| 7786 | for (n in th.series[i]) { |
| 7787 | th.series[i][n] = s[n]; |
| 7788 | } |
| 7789 | } |
| 7790 | var a, ax; |
| 7791 | for (n in this.axes) { |
| 7792 | ax = this.axes[n]; |
| 7793 | a = th.axes[n] = new AxisProperties(); |
| 7794 | a.borderColor = ax.borderColor; |
| 7795 | a.borderWidth = ax.borderWidth; |
| 7796 | if (ax._ticks && ax._ticks[0]) { |
| 7797 | for (nn in a.ticks) { |
| 7798 | if (ax._ticks[0].hasOwnProperty(nn)) { |
| 7799 | a.ticks[nn] = ax._ticks[0][nn]; |
| 7800 | } |
| 7801 | else if (ax._ticks[0]._elem){ |
| 7802 | a.ticks[nn] = ax._ticks[0]._elem.css(nn); |
| 7803 | } |
| 7804 | } |
| 7805 | } |
| 7806 | if (ax._label && ax._label.show) { |
| 7807 | for (nn in a.label) { |
| 7808 | // a.label[nn] = ax._label._elem.css(nn); |
| 7809 | if (ax._label[nn]) { |
| 7810 | a.label[nn] = ax._label[nn]; |
| 7811 | } |
| 7812 | else if (ax._label._elem){ |
| 7813 | if (nn == 'textColor') { |
| 7814 | a.label[nn] = ax._label._elem.css('color'); |
| 7815 | } |
| 7816 | else { |
| 7817 | a.label[nn] = ax._label._elem.css(nn); |
| 7818 | } |
| 7819 | } |
| 7820 | } |
| 7821 | } |
| 7822 | } |
| 7823 | this.themeEngine._add(th); |
| 7824 | this.themeEngine.activeTheme = this.themeEngine.themes[th._name]; |
| 7825 | }; |
| 7826 | /** |
| 7827 | * Group: methods |
| 7828 | * |
| 7829 | * method: get |
| 7830 | * |
| 7831 | * Get and return the named theme or the active theme if no name given. |
| 7832 | * |
| 7833 | * parameter: |
| 7834 | * |
| 7835 | * name - name of theme to get. |
| 7836 | * |
| 7837 | * returns: |
| 7838 | * |
| 7839 | * Theme instance of given name. |
| 7840 | */ |
| 7841 | $.jqplot.ThemeEngine.prototype.get = function(name) { |
| 7842 | if (!name) { |
| 7843 | // return the active theme |
| 7844 | return this.activeTheme; |
| 7845 | } |
| 7846 | else { |
| 7847 | return this.themes[name]; |
| 7848 | } |
| 7849 | }; |
| 7850 | |
| 7851 | function numericalOrder(a,b) { return a-b; } |
| 7852 | |
| 7853 | /** |
| 7854 | * method: getThemeNames |
| 7855 | * |
| 7856 | * Return the list of theme names in this manager in alpha-numerical order. |
| 7857 | * |
| 7858 | * parameter: |
| 7859 | * |
| 7860 | * None |
| 7861 | * |
| 7862 | * returns: |
| 7863 | * |
| 7864 | * A the list of theme names in this manager in alpha-numerical order. |
| 7865 | */ |
| 7866 | $.jqplot.ThemeEngine.prototype.getThemeNames = function() { |
| 7867 | var tn = []; |
| 7868 | for (var n in this.themes) { |
| 7869 | tn.push(n); |
| 7870 | } |
| 7871 | return tn.sort(numericalOrder); |
| 7872 | }; |
| 7873 | |
| 7874 | /** |
| 7875 | * method: getThemes |
| 7876 | * |
| 7877 | * Return a list of themes in alpha-numerical order by name. |
| 7878 | * |
| 7879 | * parameter: |
| 7880 | * |
| 7881 | * None |
| 7882 | * |
| 7883 | * returns: |
| 7884 | * |
| 7885 | * A list of themes in alpha-numerical order by name. |
| 7886 | */ |
| 7887 | $.jqplot.ThemeEngine.prototype.getThemes = function() { |
| 7888 | var tn = []; |
| 7889 | var themes = []; |
| 7890 | for (var n in this.themes) { |
| 7891 | tn.push(n); |
| 7892 | } |
| 7893 | tn.sort(numericalOrder); |
| 7894 | for (var i=0; i<tn.length; i++) { |
| 7895 | themes.push(this.themes[tn[i]]); |
| 7896 | } |
| 7897 | return themes; |
| 7898 | }; |
| 7899 | |
| 7900 | $.jqplot.ThemeEngine.prototype.activate = function(plot, name) { |
| 7901 | // sometimes need to redraw whole plot. |
| 7902 | var redrawPlot = false; |
| 7903 | if (!name && this.activeTheme && this.activeTheme._name) { |
| 7904 | name = this.activeTheme._name; |
| 7905 | } |
| 7906 | if (!this.themes.hasOwnProperty(name)) { |
| 7907 | throw new Error("No theme of that name"); |
| 7908 | } |
| 7909 | else { |
| 7910 | var th = this.themes[name]; |
| 7911 | this.activeTheme = th; |
| 7912 | var val, checkBorderColor = false, checkBorderWidth = false; |
| 7913 | var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis']; |
| 7914 | |
| 7915 | for (i=0; i<arr.length; i++) { |
| 7916 | var ax = arr[i]; |
| 7917 | if (th.axesStyles.borderColor != null) { |
| 7918 | plot.axes[ax].borderColor = th.axesStyles.borderColor; |
| 7919 | } |
| 7920 | if (th.axesStyles.borderWidth != null) { |
| 7921 | plot.axes[ax].borderWidth = th.axesStyles.borderWidth; |
| 7922 | } |
| 7923 | } |
| 7924 | |
| 7925 | for (var axname in plot.axes) { |
| 7926 | var axis = plot.axes[axname]; |
| 7927 | if (axis.show) { |
| 7928 | var thaxis = th.axes[axname] || {}; |
| 7929 | var thaxstyle = th.axesStyles; |
| 7930 | var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle); |
| 7931 | val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor; |
| 7932 | if (thax.borderColor != null) { |
| 7933 | axis.borderColor = thax.borderColor; |
| 7934 | redrawPlot = true; |
| 7935 | } |
| 7936 | val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth; |
| 7937 | if (thax.borderWidth != null) { |
| 7938 | axis.borderWidth = thax.borderWidth; |
| 7939 | redrawPlot = true; |
| 7940 | } |
| 7941 | if (axis._ticks && axis._ticks[0]) { |
| 7942 | for (var nn in thax.ticks) { |
| 7943 | // val = null; |
| 7944 | // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) { |
| 7945 | // val = th.axesStyles.ticks[nn]; |
| 7946 | // } |
| 7947 | // else if (thax.ticks[nn] != null){ |
| 7948 | // val = thax.ticks[nn] |
| 7949 | // } |
| 7950 | val = thax.ticks[nn]; |
| 7951 | if (val != null) { |
| 7952 | axis.tickOptions[nn] = val; |
| 7953 | axis._ticks = []; |
| 7954 | redrawPlot = true; |
| 7955 | } |
| 7956 | } |
| 7957 | } |
| 7958 | if (axis._label && axis._label.show) { |
| 7959 | for (var nn in thax.label) { |
| 7960 | // val = null; |
| 7961 | // if (th.axesStyles.label && th.axesStyles.label[nn] != null) { |
| 7962 | // val = th.axesStyles.label[nn]; |
| 7963 | // } |
| 7964 | // else if (thax.label && thax.label[nn] != null){ |
| 7965 | // val = thax.label[nn] |
| 7966 | // } |
| 7967 | val = thax.label[nn]; |
| 7968 | if (val != null) { |
| 7969 | axis.labelOptions[nn] = val; |
| 7970 | redrawPlot = true; |
| 7971 | } |
| 7972 | } |
| 7973 | } |
| 7974 | |
| 7975 | } |
| 7976 | } |
| 7977 | |
| 7978 | for (var n in th.grid) { |
| 7979 | if (th.grid[n] != null) { |
| 7980 | plot.grid[n] = th.grid[n]; |
| 7981 | } |
| 7982 | } |
| 7983 | if (!redrawPlot) { |
| 7984 | plot.grid.draw(); |
| 7985 | } |
| 7986 | |
| 7987 | if (plot.legend.show) { |
| 7988 | for (n in th.legend) { |
| 7989 | if (th.legend[n] != null) { |
| 7990 | plot.legend[n] = th.legend[n]; |
| 7991 | } |
| 7992 | } |
| 7993 | } |
| 7994 | if (plot.title.show) { |
| 7995 | for (n in th.title) { |
| 7996 | if (th.title[n] != null) { |
| 7997 | plot.title[n] = th.title[n]; |
| 7998 | } |
| 7999 | } |
| 8000 | } |
| 8001 | |
| 8002 | var i; |
| 8003 | for (i=0; i<th.series.length; i++) { |
| 8004 | var opts = {}; |
| 8005 | var redrawSeries = false; |
| 8006 | for (n in th.series[i]) { |
| 8007 | val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n]; |
| 8008 | if (val != null) { |
| 8009 | opts[n] = val; |
| 8010 | if (n == 'color') { |
| 8011 | plot.series[i].renderer.shapeRenderer.fillStyle = val; |
| 8012 | plot.series[i].renderer.shapeRenderer.strokeStyle = val; |
| 8013 | plot.series[i][n] = val; |
| 8014 | } |
| 8015 | else if ((n == 'lineWidth') || (n == 'linePattern')) { |
| 8016 | plot.series[i].renderer.shapeRenderer[n] = val; |
| 8017 | plot.series[i][n] = val; |
| 8018 | } |
| 8019 | else if (n == 'markerOptions') { |
| 8020 | merge (plot.series[i].markerOptions, val); |
| 8021 | merge (plot.series[i].markerRenderer, val); |
| 8022 | } |
| 8023 | else { |
| 8024 | plot.series[i][n] = val; |
| 8025 | } |
| 8026 | redrawPlot = true; |
| 8027 | } |
| 8028 | } |
| 8029 | } |
| 8030 | |
| 8031 | if (redrawPlot) { |
| 8032 | plot.target.empty(); |
| 8033 | plot.draw(); |
| 8034 | } |
| 8035 | |
| 8036 | for (n in th.target) { |
| 8037 | if (th.target[n] != null) { |
| 8038 | plot.target.css(n, th.target[n]); |
| 8039 | } |
| 8040 | } |
| 8041 | } |
| 8042 | |
| 8043 | }; |
| 8044 | |
| 8045 | $.jqplot.ThemeEngine.prototype._add = function(theme, name) { |
| 8046 | if (name) { |
| 8047 | theme._name = name; |
| 8048 | } |
| 8049 | if (!theme._name) { |
| 8050 | theme._name = Date.parse(new Date()); |
| 8051 | } |
| 8052 | if (!this.themes.hasOwnProperty(theme._name)) { |
| 8053 | this.themes[theme._name] = theme; |
| 8054 | } |
| 8055 | else { |
| 8056 | throw new Error("jqplot.ThemeEngine Error: Theme already in use"); |
| 8057 | } |
| 8058 | }; |
| 8059 | |
| 8060 | // method remove |
| 8061 | // Delete the named theme, return true on success, false on failure. |
| 8062 | |
| 8063 | |
| 8064 | /** |
| 8065 | * method: remove |
| 8066 | * |
| 8067 | * Remove the given theme from the themeEngine. |
| 8068 | * |
| 8069 | * parameters: |
| 8070 | * |
| 8071 | * name - name of the theme to remove. |
| 8072 | * |
| 8073 | * returns: |
| 8074 | * |
| 8075 | * true on success, false on failure. |
| 8076 | */ |
| 8077 | $.jqplot.ThemeEngine.prototype.remove = function(name) { |
| 8078 | if (name == 'Default') { |
| 8079 | return false; |
| 8080 | } |
| 8081 | return delete this.themes[name]; |
| 8082 | }; |
| 8083 | |
| 8084 | /** |
| 8085 | * method: newTheme |
| 8086 | * |
| 8087 | * Create a new theme based on the default theme, adding it the themeEngine. |
| 8088 | * |
| 8089 | * parameters: |
| 8090 | * |
| 8091 | * name - name of the new theme. |
| 8092 | * obj - optional object of styles to be applied to this new theme. |
| 8093 | * |
| 8094 | * returns: |
| 8095 | * |
| 8096 | * new Theme object. |
| 8097 | */ |
| 8098 | $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) { |
| 8099 | if (typeof(name) == 'object') { |
| 8100 | obj = obj || name; |
| 8101 | name = null; |
| 8102 | } |
| 8103 | if (obj && obj._name) { |
| 8104 | name = obj._name; |
| 8105 | } |
| 8106 | else { |
| 8107 | name = name || Date.parse(new Date()); |
| 8108 | } |
| 8109 | // var th = new $.jqplot.Theme(name); |
| 8110 | var th = this.copy(this.themes['Default']._name, name); |
| 8111 | $.jqplot.extend(th, obj); |
| 8112 | return th; |
| 8113 | }; |
| 8114 | |
| 8115 | // function clone(obj) { |
| 8116 | // return eval(obj.toSource()); |
| 8117 | // } |
| 8118 | |
| 8119 | function clone(obj){ |
| 8120 | if(obj == null || typeof(obj) != 'object'){ |
| 8121 | return obj; |
| 8122 | } |
| 8123 | |
| 8124 | var temp = new obj.constructor(); |
| 8125 | for(var key in obj){ |
| 8126 | temp[key] = clone(obj[key]); |
| 8127 | } |
| 8128 | return temp; |
| 8129 | } |
| 8130 | |
| 8131 | $.jqplot.clone = clone; |
| 8132 | |
| 8133 | function merge(obj1, obj2) { |
| 8134 | if (obj2 == null || typeof(obj2) != 'object') { |
| 8135 | return; |
| 8136 | } |
| 8137 | for (var key in obj2) { |
| 8138 | if (key == 'highlightColors') { |
| 8139 | obj1[key] = clone(obj2[key]); |
| 8140 | } |
| 8141 | if (obj2[key] != null && typeof(obj2[key]) == 'object') { |
| 8142 | if (!obj1.hasOwnProperty(key)) { |
| 8143 | obj1[key] = {}; |
| 8144 | } |
| 8145 | merge(obj1[key], obj2[key]); |
| 8146 | } |
| 8147 | else { |
| 8148 | obj1[key] = obj2[key]; |
| 8149 | } |
| 8150 | } |
| 8151 | } |
| 8152 | |
| 8153 | $.jqplot.merge = merge; |
| 8154 | |
| 8155 | // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic |
| 8156 | $.jqplot.extend = function() { |
| 8157 | // copy reference to target object |
| 8158 | var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; |
| 8159 | |
| 8160 | // Handle a deep copy situation |
| 8161 | if ( typeof target === "boolean" ) { |
| 8162 | deep = target; |
| 8163 | target = arguments[1] || {}; |
| 8164 | // skip the boolean and the target |
| 8165 | i = 2; |
| 8166 | } |
| 8167 | |
| 8168 | // Handle case when target is a string or something (possible in deep copy) |
| 8169 | if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) { |
| 8170 | target = {}; |
| 8171 | } |
| 8172 | |
| 8173 | for ( ; i < length; i++ ){ |
| 8174 | // Only deal with non-null/undefined values |
| 8175 | if ( (options = arguments[ i ]) != null ) { |
| 8176 | // Extend the base object |
| 8177 | for ( var name in options ) { |
| 8178 | var src = target[ name ], copy = options[ name ]; |
| 8179 | |
| 8180 | // Prevent never-ending loop |
| 8181 | if ( target === copy ) { |
| 8182 | continue; |
| 8183 | } |
| 8184 | |
| 8185 | // Recurse if we're merging object values |
| 8186 | if ( deep && copy && typeof copy === "object" && !copy.nodeType ) { |
| 8187 | target[ name ] = $.jqplot.extend( deep, |
| 8188 | // Never move original objects, clone them |
| 8189 | src || ( copy.length != null ? [ ] : { } ) |
| 8190 | , copy ); |
| 8191 | } |
| 8192 | // Don't bring in undefined values |
| 8193 | else if ( copy !== undefined ) { |
| 8194 | target[ name ] = copy; |
| 8195 | } |
| 8196 | } |
| 8197 | } |
| 8198 | } |
| 8199 | // Return the modified object |
| 8200 | return target; |
| 8201 | }; |
| 8202 | |
| 8203 | /** |
| 8204 | * method: rename |
| 8205 | * |
| 8206 | * Rename a theme. |
| 8207 | * |
| 8208 | * parameters: |
| 8209 | * |
| 8210 | * oldName - current name of the theme. |
| 8211 | * newName - desired name of the theme. |
| 8212 | * |
| 8213 | * returns: |
| 8214 | * |
| 8215 | * new Theme object. |
| 8216 | */ |
| 8217 | $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) { |
| 8218 | if (oldName == 'Default' || newName == 'Default') { |
| 8219 | throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default"); |
| 8220 | } |
| 8221 | if (this.themes.hasOwnProperty(newName)) { |
| 8222 | throw new Error ("jqplot.ThemeEngine Error: New name already in use."); |
| 8223 | } |
| 8224 | else if (this.themes.hasOwnProperty(oldName)) { |
| 8225 | var th = this.copy (oldName, newName); |
| 8226 | this.remove(oldName); |
| 8227 | return th; |
| 8228 | } |
| 8229 | throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid"); |
| 8230 | }; |
| 8231 | |
| 8232 | /** |
| 8233 | * method: copy |
| 8234 | * |
| 8235 | * Create a copy of an existing theme in the themeEngine, adding it the themeEngine. |
| 8236 | * |
| 8237 | * parameters: |
| 8238 | * |
| 8239 | * sourceName - name of the existing theme. |
| 8240 | * targetName - name of the copy. |
| 8241 | * obj - optional object of style parameter to apply to the new theme. |
| 8242 | * |
| 8243 | * returns: |
| 8244 | * |
| 8245 | * new Theme object. |
| 8246 | */ |
| 8247 | $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) { |
| 8248 | if (targetName == 'Default') { |
| 8249 | throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme"); |
| 8250 | } |
| 8251 | if (!this.themes.hasOwnProperty(sourceName)) { |
| 8252 | var s = "jqplot.ThemeEngine Error: Source name invalid"; |
| 8253 | throw new Error(s); |
| 8254 | } |
| 8255 | if (this.themes.hasOwnProperty(targetName)) { |
| 8256 | var s = "jqplot.ThemeEngine Error: Target name invalid"; |
| 8257 | throw new Error(s); |
| 8258 | } |
| 8259 | else { |
| 8260 | var th = clone(this.themes[sourceName]); |
| 8261 | th._name = targetName; |
| 8262 | $.jqplot.extend(true, th, obj); |
| 8263 | this._add(th); |
| 8264 | return th; |
| 8265 | } |
| 8266 | }; |
| 8267 | |
| 8268 | |
| 8269 | $.jqplot.Theme = function(name, obj) { |
| 8270 | if (typeof(name) == 'object') { |
| 8271 | obj = obj || name; |
| 8272 | name = null; |
| 8273 | } |
| 8274 | name = name || Date.parse(new Date()); |
| 8275 | this._name = name; |
| 8276 | this.target = { |
| 8277 | backgroundColor: null |
| 8278 | }; |
| 8279 | this.legend = { |
| 8280 | textColor: null, |
| 8281 | fontFamily: null, |
| 8282 | fontSize: null, |
| 8283 | border: null, |
| 8284 | background: null |
| 8285 | }; |
| 8286 | this.title = { |
| 8287 | textColor: null, |
| 8288 | fontFamily: null, |
| 8289 | fontSize: null, |
| 8290 | textAlign: null |
| 8291 | }; |
| 8292 | this.seriesStyles = {}; |
| 8293 | this.series = []; |
| 8294 | this.grid = { |
| 8295 | drawGridlines: null, |
| 8296 | gridLineColor: null, |
| 8297 | gridLineWidth: null, |
| 8298 | backgroundColor: null, |
| 8299 | borderColor: null, |
| 8300 | borderWidth: null, |
| 8301 | shadow: null |
| 8302 | }; |
| 8303 | this.axesStyles = {label:{}, ticks:{}}; |
| 8304 | this.axes = {}; |
| 8305 | if (typeof(obj) == 'string') { |
| 8306 | this._name = obj; |
| 8307 | } |
| 8308 | else if(typeof(obj) == 'object') { |
| 8309 | $.jqplot.extend(true, this, obj); |
| 8310 | } |
| 8311 | }; |
| 8312 | |
| 8313 | var AxisProperties = function() { |
| 8314 | this.borderColor = null; |
| 8315 | this.borderWidth = null; |
| 8316 | this.ticks = new AxisTicks(); |
| 8317 | this.label = new AxisLabel(); |
| 8318 | }; |
| 8319 | |
| 8320 | var AxisTicks = function() { |
| 8321 | this.show = null; |
| 8322 | this.showGridline = null; |
| 8323 | this.showLabel = null; |
| 8324 | this.showMark = null; |
| 8325 | this.size = null; |
| 8326 | this.textColor = null; |
| 8327 | this.whiteSpace = null; |
| 8328 | this.fontSize = null; |
| 8329 | this.fontFamily = null; |
| 8330 | }; |
| 8331 | |
| 8332 | var AxisLabel = function() { |
| 8333 | this.textColor = null; |
| 8334 | this.whiteSpace = null; |
| 8335 | this.fontSize = null; |
| 8336 | this.fontFamily = null; |
| 8337 | this.fontWeight = null; |
| 8338 | }; |
| 8339 | |
| 8340 | var LineSeriesProperties = function() { |
| 8341 | this.color=null; |
| 8342 | this.lineWidth=null; |
| 8343 | this.linePattern=null; |
| 8344 | this.shadow=null; |
| 8345 | this.fillColor=null; |
| 8346 | this.showMarker=null; |
| 8347 | this.markerOptions = new MarkerOptions(); |
| 8348 | }; |
| 8349 | |
| 8350 | var MarkerOptions = function() { |
| 8351 | this.show = null; |
| 8352 | this.style = null; |
| 8353 | this.lineWidth = null; |
| 8354 | this.size = null; |
| 8355 | this.color = null; |
| 8356 | this.shadow = null; |
| 8357 | }; |
| 8358 | |
| 8359 | var BarSeriesProperties = function() { |
| 8360 | this.color=null; |
| 8361 | this.seriesColors=null; |
| 8362 | this.lineWidth=null; |
| 8363 | this.shadow=null; |
| 8364 | this.barPadding=null; |
| 8365 | this.barMargin=null; |
| 8366 | this.barWidth=null; |
| 8367 | this.highlightColors=null; |
| 8368 | }; |
| 8369 | |
| 8370 | var PieSeriesProperties = function() { |
| 8371 | this.seriesColors=null; |
| 8372 | this.padding=null; |
| 8373 | this.sliceMargin=null; |
| 8374 | this.fill=null; |
| 8375 | this.shadow=null; |
| 8376 | this.startAngle=null; |
| 8377 | this.lineWidth=null; |
| 8378 | this.highlightColors=null; |
| 8379 | }; |
| 8380 | |
| 8381 | var DonutSeriesProperties = function() { |
| 8382 | this.seriesColors=null; |
| 8383 | this.padding=null; |
| 8384 | this.sliceMargin=null; |
| 8385 | this.fill=null; |
| 8386 | this.shadow=null; |
| 8387 | this.startAngle=null; |
| 8388 | this.lineWidth=null; |
| 8389 | this.innerDiameter=null; |
| 8390 | this.thickness=null; |
| 8391 | this.ringMargin=null; |
| 8392 | this.highlightColors=null; |
| 8393 | }; |
| 8394 | |
| 8395 | var FunnelSeriesProperties = function() { |
| 8396 | this.color=null; |
| 8397 | this.lineWidth=null; |
| 8398 | this.shadow=null; |
| 8399 | this.padding=null; |
| 8400 | this.sectionMargin=null; |
| 8401 | this.seriesColors=null; |
| 8402 | this.highlightColors=null; |
| 8403 | }; |
| 8404 | |
| 8405 | var MeterSeriesProperties = function() { |
| 8406 | this.padding=null; |
| 8407 | this.backgroundColor=null; |
| 8408 | this.ringColor=null; |
| 8409 | this.tickColor=null; |
| 8410 | this.ringWidth=null; |
| 8411 | this.intervalColors=null; |
| 8412 | this.intervalInnerRadius=null; |
| 8413 | this.intervalOuterRadius=null; |
| 8414 | this.hubRadius=null; |
| 8415 | this.needleThickness=null; |
| 8416 | this.needlePad=null; |
| 8417 | }; |
| 8418 | |
| 8419 | |
| 8420 | |
| 8421 | |
| 8422 | $.fn.jqplotChildText = function() { |
| 8423 | return $(this).contents().filter(function() { |
| 8424 | return this.nodeType == 3; // Node.TEXT_NODE not defined in I7 |
| 8425 | }).text(); |
| 8426 | }; |
| 8427 | |
| 8428 | // Returns font style as abbreviation for "font" property. |
| 8429 | $.fn.jqplotGetComputedFontStyle = function() { |
| 8430 | var css = window.getComputedStyle ? window.getComputedStyle(this[0]) : this[0].currentStyle; |
| 8431 | var attrs = css['font-style'] ? ['font-style', 'font-weight', 'font-size', 'font-family'] : ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily']; |
| 8432 | var style = []; |
| 8433 | |
| 8434 | for (var i=0 ; i < attrs.length; ++i) { |
| 8435 | var attr = String(css[attrs[i]]); |
| 8436 | |
| 8437 | if (attr && attr != 'normal') { |
| 8438 | style.push(attr); |
| 8439 | } |
| 8440 | } |
| 8441 | return style.join(' '); |
| 8442 | }; |
| 8443 | |
| 8444 | /** |
| 8445 | * Namespace: $.fn |
| 8446 | * jQuery namespace to attach functions to jQuery elements. |
| 8447 | * |
| 8448 | */ |
| 8449 | |
| 8450 | $.fn.jqplotToImageCanvas = function(options) { |
| 8451 | |
| 8452 | options = options || {}; |
| 8453 | var x_offset = (options.x_offset == null) ? 0 : options.x_offset; |
| 8454 | var y_offset = (options.y_offset == null) ? 0 : options.y_offset; |
| 8455 | var backgroundColor = (options.backgroundColor == null) ? 'rgb(255,255,255)' : options.backgroundColor; |
| 8456 | |
| 8457 | if ($(this).width() == 0 || $(this).height() == 0) { |
| 8458 | return null; |
| 8459 | } |
| 8460 | |
| 8461 | // excanvas and hence IE < 9 do not support toDataURL and cannot export images. |
| 8462 | if (!$.jqplot.support_canvas) { |
| 8463 | return null; |
| 8464 | } |
| 8465 | |
| 8466 | var newCanvas = document.createElement("canvas"); |
| 8467 | var h = $(this).outerHeight(true); |
| 8468 | var w = $(this).outerWidth(true); |
| 8469 | var offs = $(this).offset(); |
| 8470 | var plotleft = offs.left; |
| 8471 | var plottop = offs.top; |
| 8472 | var transx = 0, transy = 0; |
| 8473 | |
| 8474 | // have to check if any elements are hanging outside of plot area before rendering, |
| 8475 | // since changing width of canvas will erase canvas. |
| 8476 | |
| 8477 | var clses = ['jqplot-table-legend', 'jqplot-xaxis-tick', 'jqplot-x2axis-tick', 'jqplot-yaxis-tick', 'jqplot-y2axis-tick', 'jqplot-y3axis-tick', |
| 8478 | 'jqplot-y4axis-tick', 'jqplot-y5axis-tick', 'jqplot-y6axis-tick', 'jqplot-y7axis-tick', 'jqplot-y8axis-tick', 'jqplot-y9axis-tick', |
| 8479 | 'jqplot-xaxis-label', 'jqplot-x2axis-label', 'jqplot-yaxis-label', 'jqplot-y2axis-label', 'jqplot-y3axis-label', 'jqplot-y4axis-label', |
| 8480 | 'jqplot-y5axis-label', 'jqplot-y6axis-label', 'jqplot-y7axis-label', 'jqplot-y8axis-label', 'jqplot-y9axis-label' ]; |
| 8481 | |
| 8482 | var temptop, templeft, tempbottom, tempright; |
| 8483 | |
| 8484 | for (var i in clses) { |
| 8485 | $(this).find('.'+clses[i]).each(function() { |
| 8486 | temptop = $(this).offset().top - plottop; |
| 8487 | templeft = $(this).offset().left - plotleft; |
| 8488 | tempright = templeft + $(this).outerWidth(true) + transx; |
| 8489 | tempbottom = temptop + $(this).outerHeight(true) + transy; |
| 8490 | if (templeft < -transx) { |
| 8491 | w = w - transx - templeft; |
| 8492 | transx = -templeft; |
| 8493 | } |
| 8494 | if (temptop < -transy) { |
| 8495 | h = h - transy - temptop; |
| 8496 | transy = - temptop; |
| 8497 | } |
| 8498 | if (tempright > w) { |
| 8499 | w = tempright; |
| 8500 | } |
| 8501 | if (tempbottom > h) { |
| 8502 | h = tempbottom; |
| 8503 | } |
| 8504 | }); |
| 8505 | } |
| 8506 | |
| 8507 | newCanvas.width = w + Number(x_offset); |
| 8508 | newCanvas.height = h + Number(y_offset); |
| 8509 | |
| 8510 | var newContext = newCanvas.getContext("2d"); |
| 8511 | |
| 8512 | newContext.save(); |
| 8513 | newContext.fillStyle = backgroundColor; |
| 8514 | newContext.fillRect(0,0, newCanvas.width, newCanvas.height); |
| 8515 | newContext.restore(); |
| 8516 | |
| 8517 | newContext.translate(transx, transy); |
| 8518 | newContext.textAlign = 'left'; |
| 8519 | newContext.textBaseline = 'top'; |
| 8520 | |
| 8521 | function getLineheight(el) { |
| 8522 | var lineheight = parseInt($(el).css('line-height')); |
| 8523 | |
| 8524 | if (isNaN(lineheight)) { |
| 8525 | lineheight = parseInt($(el).css('font-size')) * 1.2; |
| 8526 | } |
| 8527 | return lineheight; |
| 8528 | } |
| 8529 | |
| 8530 | function writeWrappedText (el, context, text, left, top, canvasWidth) { |
| 8531 | var lineheight = getLineheight(el); |
| 8532 | var tagwidth = $(el).innerWidth(); |
| 8533 | var tagheight = $(el).innerHeight(); |
| 8534 | var words = text.split(/\s+/); |
| 8535 | var wl = words.length; |
| 8536 | var w = ''; |
| 8537 | var breaks = []; |
| 8538 | var temptop = top; |
| 8539 | var templeft = left; |
| 8540 | |
| 8541 | for (var i=0; i<wl; i++) { |
| 8542 | w += words[i]; |
| 8543 | if (context.measureText(w).width > tagwidth) { |
| 8544 | breaks.push(i); |
| 8545 | w = ''; |
| 8546 | } |
| 8547 | } |
| 8548 | if (breaks.length === 0) { |
| 8549 | // center text if necessary |
| 8550 | if ($(el).css('textAlign') === 'center') { |
| 8551 | templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; |
| 8552 | } |
| 8553 | context.fillText(text, templeft, top); |
| 8554 | } |
| 8555 | else { |
| 8556 | w = words.slice(0, breaks[0]).join(' '); |
| 8557 | // center text if necessary |
| 8558 | if ($(el).css('textAlign') === 'center') { |
| 8559 | templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; |
| 8560 | } |
| 8561 | context.fillText(w, templeft, temptop); |
| 8562 | temptop += lineheight; |
| 8563 | for (var i=1, l=breaks.length; i<l; i++) { |
| 8564 | w = words.slice(breaks[i-1], breaks[i]).join(' '); |
| 8565 | // center text if necessary |
| 8566 | if ($(el).css('textAlign') === 'center') { |
| 8567 | templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; |
| 8568 | } |
| 8569 | context.fillText(w, templeft, temptop); |
| 8570 | temptop += lineheight; |
| 8571 | } |
| 8572 | w = words.slice(breaks[i-1], words.length).join(' '); |
| 8573 | // center text if necessary |
| 8574 | if ($(el).css('textAlign') === 'center') { |
| 8575 | templeft = left + (canvasWidth - context.measureText(w).width)/2 - transx; |
| 8576 | } |
| 8577 | context.fillText(w, templeft, temptop); |
| 8578 | } |
| 8579 | |
| 8580 | } |
| 8581 | |
| 8582 | function _jqpToImage(el, x_offset, y_offset) { |
| 8583 | var tagname = el.tagName.toLowerCase(); |
| 8584 | var p = $(el).position(); |
| 8585 | var css = window.getComputedStyle ? window.getComputedStyle(el) : el.currentStyle; // for IE < 9 |
| 8586 | var left = x_offset + p.left + parseInt(css.marginLeft) + parseInt(css.borderLeftWidth) + parseInt(css.paddingLeft); |
| 8587 | var top = y_offset + p.top + parseInt(css.marginTop) + parseInt(css.borderTopWidth)+ parseInt(css.paddingTop); |
| 8588 | var w = newCanvas.width; |
| 8589 | // var left = x_offset + p.left + $(el).css('marginLeft') + $(el).css('borderLeftWidth') |
| 8590 | |
| 8591 | if ((tagname == 'div' || tagname == 'span') && !$(el).hasClass('jqplot-highlighter-tooltip')) { |
| 8592 | $(el).children().each(function() { |
| 8593 | _jqpToImage(this, left, top); |
| 8594 | }); |
| 8595 | var text = $(el).jqplotChildText(); |
| 8596 | |
| 8597 | if (text) { |
| 8598 | newContext.font = $(el).jqplotGetComputedFontStyle(); |
| 8599 | newContext.fillStyle = $(el).css('color'); |
| 8600 | |
| 8601 | writeWrappedText(el, newContext, text, left, top, w); |
| 8602 | } |
| 8603 | } |
| 8604 | |
| 8605 | // handle the standard table legend |
| 8606 | |
| 8607 | else if (tagname === 'table' && $(el).hasClass('jqplot-table-legend')) { |
| 8608 | newContext.strokeStyle = $(el).css('border-top-color'); |
| 8609 | newContext.fillStyle = $(el).css('background-color'); |
| 8610 | newContext.fillRect(left, top, $(el).innerWidth(), $(el).innerHeight()); |
| 8611 | if (parseInt($(el).css('border-top-width')) > 0) { |
| 8612 | newContext.strokeRect(left, top, $(el).innerWidth(), $(el).innerHeight()); |
| 8613 | } |
| 8614 | |
| 8615 | // find all the swatches |
| 8616 | $(el).find('div.jqplot-table-legend-swatch-outline').each(function() { |
| 8617 | // get the first div and stroke it |
| 8618 | var elem = $(this); |
| 8619 | newContext.strokeStyle = elem.css('border-top-color'); |
| 8620 | var l = left + elem.position().left; |
| 8621 | var t = top + elem.position().top; |
| 8622 | newContext.strokeRect(l, t, elem.innerWidth(), elem.innerHeight()); |
| 8623 | |
| 8624 | // now fill the swatch |
| 8625 | |
| 8626 | l += parseInt(elem.css('padding-left')); |
| 8627 | t += parseInt(elem.css('padding-top')); |
| 8628 | var h = elem.innerHeight() - 2 * parseInt(elem.css('padding-top')); |
| 8629 | var w = elem.innerWidth() - 2 * parseInt(elem.css('padding-left')); |
| 8630 | |
| 8631 | var swatch = elem.children('div.jqplot-table-legend-swatch'); |
| 8632 | newContext.fillStyle = swatch.css('background-color'); |
| 8633 | newContext.fillRect(l, t, w, h); |
| 8634 | }); |
| 8635 | |
| 8636 | // now add text |
| 8637 | |
| 8638 | $(el).find('td.jqplot-table-legend-label').each(function(){ |
| 8639 | var elem = $(this); |
| 8640 | var l = left + elem.position().left; |
| 8641 | var t = top + elem.position().top + parseInt(elem.css('padding-top')); |
| 8642 | newContext.font = elem.jqplotGetComputedFontStyle(); |
| 8643 | newContext.fillStyle = elem.css('color'); |
| 8644 | newContext.fillText(elem.text(), l, t); |
| 8645 | }); |
| 8646 | |
| 8647 | var elem = null; |
| 8648 | } |
| 8649 | |
| 8650 | else if (tagname == 'canvas') { |
| 8651 | newContext.drawImage(el, left, top); |
| 8652 | } |
| 8653 | } |
| 8654 | $(this).children().each(function() { |
| 8655 | _jqpToImage(this, x_offset, y_offset); |
| 8656 | }); |
| 8657 | return newCanvas; |
| 8658 | }; |
| 8659 | |
| 8660 | $.fn.jqplotToImageStr = function(options) { |
| 8661 | var imgCanvas = $(this).jqplotToImageCanvas(options); |
| 8662 | if (imgCanvas) { |
| 8663 | return imgCanvas.toDataURL("image/png"); |
| 8664 | } |
| 8665 | else { |
| 8666 | return null; |
| 8667 | } |
| 8668 | }; |
| 8669 | |
| 8670 | // create an <img> element and return it. |
| 8671 | // Should work on canvas supporting browsers. |
| 8672 | $.fn.jqplotToImageElem = function(options) { |
| 8673 | var elem = document.createElement("img"); |
| 8674 | var str = $(this).jqplotToImageStr(options); |
| 8675 | elem.src = str; |
| 8676 | return elem; |
| 8677 | }; |
| 8678 | |
| 8679 | // create an <img> element and return it. |
| 8680 | // Should work on canvas supporting browsers. |
| 8681 | $.fn.jqplotToImageElemStr = function(options) { |
| 8682 | var str = '<img src='+$(this).jqplotToImageStr(options)+' />'; |
| 8683 | return str; |
| 8684 | }; |
| 8685 | |
| 8686 | // Not gauranteed to work, even on canvas supporting browsers due to |
| 8687 | // limitations with location.href and browser support. |
| 8688 | $.fn.jqplotSaveImage = function() { |
| 8689 | var imgData = $(this).jqplotToImageStr({}); |
| 8690 | if (imgData) { |
| 8691 | window.location.href = imgData.replace("image/png", "image/octet-stream"); |
| 8692 | } |
| 8693 | |
| 8694 | }; |
| 8695 | |
| 8696 | // Not gauranteed to work, even on canvas supporting browsers due to |
| 8697 | // limitations with window.open and arbitrary data. |
| 8698 | $.fn.jqplotViewImage = function() { |
| 8699 | var imgStr = $(this).jqplotToImageElemStr({}); |
| 8700 | var imgData = $(this).jqplotToImageStr({}); |
| 8701 | if (imgStr) { |
| 8702 | var w = window.open(''); |
| 8703 | w.document.open("image/png"); |
| 8704 | w.document.write(imgStr); |
| 8705 | w.document.close(); |
| 8706 | w = null; |
| 8707 | } |
| 8708 | }; |
| 8709 | |
| 8710 | |
| 8711 | |
| 8712 | /** |
| 8713 | * @description |
| 8714 | * <p>Object with extended date parsing and formatting capabilities. |
| 8715 | * This library borrows many concepts and ideas from the Date Instance |
| 8716 | * Methods by Ken Snyder along with some parts of Ken's actual code.</p> |
| 8717 | * |
| 8718 | * <p>jsDate takes a different approach by not extending the built-in |
| 8719 | * Date Object, improving date parsing, allowing for multiple formatting |
| 8720 | * syntaxes and multiple and more easily expandable localization.</p> |
| 8721 | * |
| 8722 | * @author Chris Leonello |
| 8723 | * @date #date# |
| 8724 | * @version #VERSION# |
| 8725 | * @copyright (c) 2010 Chris Leonello |
| 8726 | * jsDate is currently available for use in all personal or commercial projects |
| 8727 | * under both the MIT and GPL version 2.0 licenses. This means that you can |
| 8728 | * choose the license that best suits your project and use it accordingly. |
| 8729 | * |
| 8730 | * <p>Ken's origianl Date Instance Methods and copyright notice:</p> |
| 8731 | * <pre> |
| 8732 | * Ken Snyder (ken d snyder at gmail dot com) |
| 8733 | * 2008-09-10 |
| 8734 | * version 2.0.2 (http://kendsnyder.com/sandbox/date/) |
| 8735 | * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/) |
| 8736 | * </pre> |
| 8737 | * |
| 8738 | * @class |
| 8739 | * @name jsDate |
| 8740 | * @param {String | Number | Array | Date Object | Options Object} arguments Optional arguments, either a parsable date/time string, |
| 8741 | * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds], |
| 8742 | * a Date object, or an options object of form {syntax: "perl", date:some Date} where all options are optional. |
| 8743 | */ |
| 8744 | |
| 8745 | var jsDate = function () { |
| 8746 | |
| 8747 | this.syntax = jsDate.config.syntax; |
| 8748 | this._type = "jsDate"; |
| 8749 | this.utcOffset = new Date().getTimezoneOffset * 60000; |
| 8750 | this.proxy = new Date(); |
| 8751 | this.options = {}; |
| 8752 | this.locale = jsDate.regional.getLocale(); |
| 8753 | this.formatString = ''; |
| 8754 | this.defaultCentury = jsDate.config.defaultCentury; |
| 8755 | |
| 8756 | switch ( arguments.length ) { |
| 8757 | case 0: |
| 8758 | break; |
| 8759 | case 1: |
| 8760 | // other objects either won't have a _type property or, |
| 8761 | // if they do, it shouldn't be set to "jsDate", so |
| 8762 | // assume it is an options argument. |
| 8763 | if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") { |
| 8764 | var opts = this.options = arguments[0]; |
| 8765 | this.syntax = opts.syntax || this.syntax; |
| 8766 | this.defaultCentury = opts.defaultCentury || this.defaultCentury; |
| 8767 | this.proxy = jsDate.createDate(opts.date); |
| 8768 | } |
| 8769 | else { |
| 8770 | this.proxy = jsDate.createDate(arguments[0]); |
| 8771 | } |
| 8772 | break; |
| 8773 | default: |
| 8774 | var a = []; |
| 8775 | for ( var i=0; i<arguments.length; i++ ) { |
| 8776 | a.push(arguments[i]); |
| 8777 | } |
| 8778 | this.proxy = new Date( this.utcOffset ); |
| 8779 | this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) ); |
| 8780 | if ( a.slice(3).length ) { |
| 8781 | this.proxy.setHours.apply( this.proxy, a.slice(3) ); |
| 8782 | } |
| 8783 | break; |
| 8784 | } |
| 8785 | }; |
| 8786 | |
| 8787 | /** |
| 8788 | * @namespace Configuration options that will be used as defaults for all instances on the page. |
| 8789 | * @property {String} defaultLocale The default locale to use [en]. |
| 8790 | * @property {String} syntax The default syntax to use [perl]. |
| 8791 | */ |
| 8792 | jsDate.config = { |
| 8793 | defaultLocale: 'en', |
| 8794 | syntax: 'perl', |
| 8795 | defaultCentury: 1900 |
| 8796 | }; |
| 8797 | |
| 8798 | /** |
| 8799 | * Add an arbitrary amount to the currently stored date |
| 8800 | * |
| 8801 | * @param {Number} number |
| 8802 | * @param {String} unit |
| 8803 | * @returns {jsDate} |
| 8804 | */ |
| 8805 | |
| 8806 | jsDate.prototype.add = function(number, unit) { |
| 8807 | var factor = multipliers[unit] || multipliers.day; |
| 8808 | if (typeof factor == 'number') { |
| 8809 | this.proxy.setTime(this.proxy.getTime() + (factor * number)); |
| 8810 | } else { |
| 8811 | factor.add(this, number); |
| 8812 | } |
| 8813 | return this; |
| 8814 | }; |
| 8815 | |
| 8816 | /** |
| 8817 | * Create a new jqplot.date object with the same date |
| 8818 | * |
| 8819 | * @returns {jsDate} |
| 8820 | */ |
| 8821 | |
| 8822 | jsDate.prototype.clone = function() { |
| 8823 | return new jsDate(this.proxy.getTime()); |
| 8824 | }; |
| 8825 | |
| 8826 | /** |
| 8827 | * Find the difference between this jsDate and another date. |
| 8828 | * |
| 8829 | * @param {String| Number| Array| jsDate Object| Date Object} dateObj |
| 8830 | * @param {String} unit |
| 8831 | * @param {Boolean} allowDecimal |
| 8832 | * @returns {Number} Number of units difference between dates. |
| 8833 | */ |
| 8834 | |
| 8835 | jsDate.prototype.diff = function(dateObj, unit, allowDecimal) { |
| 8836 | // ensure we have a Date object |
| 8837 | dateObj = new jsDate(dateObj); |
| 8838 | if (dateObj === null) { |
| 8839 | return null; |
| 8840 | } |
| 8841 | // get the multiplying factor integer or factor function |
| 8842 | var factor = multipliers[unit] || multipliers.day; |
| 8843 | if (typeof factor == 'number') { |
| 8844 | // multiply |
| 8845 | var unitDiff = (this.proxy.getTime() - dateObj.proxy.getTime()) / factor; |
| 8846 | } else { |
| 8847 | // run function |
| 8848 | var unitDiff = factor.diff(this.proxy, dateObj.proxy); |
| 8849 | } |
| 8850 | // if decimals are not allowed, round toward zero |
| 8851 | return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff)); |
| 8852 | }; |
| 8853 | |
| 8854 | /** |
| 8855 | * Get the abbreviated name of the current week day |
| 8856 | * |
| 8857 | * @returns {String} |
| 8858 | */ |
| 8859 | |
| 8860 | jsDate.prototype.getAbbrDayName = function() { |
| 8861 | return jsDate.regional[this.locale]["dayNamesShort"][this.proxy.getDay()]; |
| 8862 | }; |
| 8863 | |
| 8864 | /** |
| 8865 | * Get the abbreviated name of the current month |
| 8866 | * |
| 8867 | * @returns {String} |
| 8868 | */ |
| 8869 | |
| 8870 | jsDate.prototype.getAbbrMonthName = function() { |
| 8871 | return jsDate.regional[this.locale]["monthNamesShort"][this.proxy.getMonth()]; |
| 8872 | }; |
| 8873 | |
| 8874 | /** |
| 8875 | * Get UPPER CASE AM or PM for the current time |
| 8876 | * |
| 8877 | * @returns {String} |
| 8878 | */ |
| 8879 | |
| 8880 | jsDate.prototype.getAMPM = function() { |
| 8881 | return this.proxy.getHours() >= 12 ? 'PM' : 'AM'; |
| 8882 | }; |
| 8883 | |
| 8884 | /** |
| 8885 | * Get lower case am or pm for the current time |
| 8886 | * |
| 8887 | * @returns {String} |
| 8888 | */ |
| 8889 | |
| 8890 | jsDate.prototype.getAmPm = function() { |
| 8891 | return this.proxy.getHours() >= 12 ? 'pm' : 'am'; |
| 8892 | }; |
| 8893 | |
| 8894 | /** |
| 8895 | * Get the century (19 for 20th Century) |
| 8896 | * |
| 8897 | * @returns {Integer} Century (19 for 20th century). |
| 8898 | */ |
| 8899 | jsDate.prototype.getCentury = function() { |
| 8900 | return parseInt(this.proxy.getFullYear()/100, 10); |
| 8901 | }; |
| 8902 | |
| 8903 | /** |
| 8904 | * Implements Date functionality |
| 8905 | */ |
| 8906 | jsDate.prototype.getDate = function() { |
| 8907 | return this.proxy.getDate(); |
| 8908 | }; |
| 8909 | |
| 8910 | /** |
| 8911 | * Implements Date functionality |
| 8912 | */ |
| 8913 | jsDate.prototype.getDay = function() { |
| 8914 | return this.proxy.getDay(); |
| 8915 | }; |
| 8916 | |
| 8917 | /** |
| 8918 | * Get the Day of week 1 (Monday) thru 7 (Sunday) |
| 8919 | * |
| 8920 | * @returns {Integer} Day of week 1 (Monday) thru 7 (Sunday) |
| 8921 | */ |
| 8922 | jsDate.prototype.getDayOfWeek = function() { |
| 8923 | var dow = this.proxy.getDay(); |
| 8924 | return dow===0?7:dow; |
| 8925 | }; |
| 8926 | |
| 8927 | /** |
| 8928 | * Get the day of the year |
| 8929 | * |
| 8930 | * @returns {Integer} 1 - 366, day of the year |
| 8931 | */ |
| 8932 | jsDate.prototype.getDayOfYear = function() { |
| 8933 | var d = this.proxy; |
| 8934 | var ms = d - new Date('' + d.getFullYear() + '/1/1 GMT'); |
| 8935 | ms += d.getTimezoneOffset()*60000; |
| 8936 | d = null; |
| 8937 | return parseInt(ms/60000/60/24, 10)+1; |
| 8938 | }; |
| 8939 | |
| 8940 | /** |
| 8941 | * Get the name of the current week day |
| 8942 | * |
| 8943 | * @returns {String} |
| 8944 | */ |
| 8945 | |
| 8946 | jsDate.prototype.getDayName = function() { |
| 8947 | return jsDate.regional[this.locale]["dayNames"][this.proxy.getDay()]; |
| 8948 | }; |
| 8949 | |
| 8950 | /** |
| 8951 | * Get the week number of the given year, starting with the first Sunday as the first week |
| 8952 | * @returns {Integer} Week number (13 for the 13th full week of the year). |
| 8953 | */ |
| 8954 | jsDate.prototype.getFullWeekOfYear = function() { |
| 8955 | var d = this.proxy; |
| 8956 | var doy = this.getDayOfYear(); |
| 8957 | var rdow = 6-d.getDay(); |
| 8958 | var woy = parseInt((doy+rdow)/7, 10); |
| 8959 | return woy; |
| 8960 | }; |
| 8961 | |
| 8962 | /** |
| 8963 | * Implements Date functionality |
| 8964 | */ |
| 8965 | jsDate.prototype.getFullYear = function() { |
| 8966 | return this.proxy.getFullYear(); |
| 8967 | }; |
| 8968 | |
| 8969 | /** |
| 8970 | * Get the GMT offset in hours and minutes (e.g. +06:30) |
| 8971 | * |
| 8972 | * @returns {String} |
| 8973 | */ |
| 8974 | |
| 8975 | jsDate.prototype.getGmtOffset = function() { |
| 8976 | // divide the minutes offset by 60 |
| 8977 | var hours = this.proxy.getTimezoneOffset() / 60; |
| 8978 | // decide if we are ahead of or behind GMT |
| 8979 | var prefix = hours < 0 ? '+' : '-'; |
| 8980 | // remove the negative sign if any |
| 8981 | hours = Math.abs(hours); |
| 8982 | // add the +/- to the padded number of hours to : to the padded minutes |
| 8983 | return prefix + addZeros(Math.floor(hours), 2) + ':' + addZeros((hours % 1) * 60, 2); |
| 8984 | }; |
| 8985 | |
| 8986 | /** |
| 8987 | * Implements Date functionality |
| 8988 | */ |
| 8989 | jsDate.prototype.getHours = function() { |
| 8990 | return this.proxy.getHours(); |
| 8991 | }; |
| 8992 | |
| 8993 | /** |
| 8994 | * Get the current hour on a 12-hour scheme |
| 8995 | * |
| 8996 | * @returns {Integer} |
| 8997 | */ |
| 8998 | |
| 8999 | jsDate.prototype.getHours12 = function() { |
| 9000 | var hours = this.proxy.getHours(); |
| 9001 | return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours); |
| 9002 | }; |
| 9003 | |
| 9004 | |
| 9005 | jsDate.prototype.getIsoWeek = function() { |
| 9006 | var d = this.proxy; |
| 9007 | var woy = d.getWeekOfYear(); |
| 9008 | var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay(); |
| 9009 | // First week is 01 and not 00 as in the case of %U and %W, |
| 9010 | // so we add 1 to the final result except if day 1 of the year |
| 9011 | // is a Monday (then %W returns 01). |
| 9012 | // We also need to subtract 1 if the day 1 of the year is |
| 9013 | // Friday-Sunday, so the resulting equation becomes: |
| 9014 | var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1); |
| 9015 | if(idow == 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4) |
| 9016 | { |
| 9017 | idow = 1; |
| 9018 | } |
| 9019 | else if(idow === 0) |
| 9020 | { |
| 9021 | d = new jsDate(new Date('' + (d.getFullYear()-1) + '/12/31')); |
| 9022 | idow = d.getIsoWeek(); |
| 9023 | } |
| 9024 | d = null; |
| 9025 | return idow; |
| 9026 | }; |
| 9027 | |
| 9028 | /** |
| 9029 | * Implements Date functionality |
| 9030 | */ |
| 9031 | jsDate.prototype.getMilliseconds = function() { |
| 9032 | return this.proxy.getMilliseconds(); |
| 9033 | }; |
| 9034 | |
| 9035 | /** |
| 9036 | * Implements Date functionality |
| 9037 | */ |
| 9038 | jsDate.prototype.getMinutes = function() { |
| 9039 | return this.proxy.getMinutes(); |
| 9040 | }; |
| 9041 | |
| 9042 | /** |
| 9043 | * Implements Date functionality |
| 9044 | */ |
| 9045 | jsDate.prototype.getMonth = function() { |
| 9046 | return this.proxy.getMonth(); |
| 9047 | }; |
| 9048 | |
| 9049 | /** |
| 9050 | * Get the name of the current month |
| 9051 | * |
| 9052 | * @returns {String} |
| 9053 | */ |
| 9054 | |
| 9055 | jsDate.prototype.getMonthName = function() { |
| 9056 | return jsDate.regional[this.locale]["monthNames"][this.proxy.getMonth()]; |
| 9057 | }; |
| 9058 | |
| 9059 | /** |
| 9060 | * Get the number of the current month, 1-12 |
| 9061 | * |
| 9062 | * @returns {Integer} |
| 9063 | */ |
| 9064 | |
| 9065 | jsDate.prototype.getMonthNumber = function() { |
| 9066 | return this.proxy.getMonth() + 1; |
| 9067 | }; |
| 9068 | |
| 9069 | /** |
| 9070 | * Implements Date functionality |
| 9071 | */ |
| 9072 | jsDate.prototype.getSeconds = function() { |
| 9073 | return this.proxy.getSeconds(); |
| 9074 | }; |
| 9075 | |
| 9076 | /** |
| 9077 | * Return a proper two-digit year integer |
| 9078 | * |
| 9079 | * @returns {Integer} |
| 9080 | */ |
| 9081 | |
| 9082 | jsDate.prototype.getShortYear = function() { |
| 9083 | return this.proxy.getYear() % 100; |
| 9084 | }; |
| 9085 | |
| 9086 | /** |
| 9087 | * Implements Date functionality |
| 9088 | */ |
| 9089 | jsDate.prototype.getTime = function() { |
| 9090 | return this.proxy.getTime(); |
| 9091 | }; |
| 9092 | |
| 9093 | /** |
| 9094 | * Get the timezone abbreviation |
| 9095 | * |
| 9096 | * @returns {String} Abbreviation for the timezone |
| 9097 | */ |
| 9098 | jsDate.prototype.getTimezoneAbbr = function() { |
| 9099 | return this.proxy.toString().replace(/^.*\(([^)]+)\)$/, '$1'); |
| 9100 | }; |
| 9101 | |
| 9102 | /** |
| 9103 | * Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time) |
| 9104 | * |
| 9105 | * @returns {String} |
| 9106 | */ |
| 9107 | jsDate.prototype.getTimezoneName = function() { |
| 9108 | var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString()); |
| 9109 | return match[1] || match[2] || 'GMT' + this.getGmtOffset(); |
| 9110 | }; |
| 9111 | |
| 9112 | /** |
| 9113 | * Implements Date functionality |
| 9114 | */ |
| 9115 | jsDate.prototype.getTimezoneOffset = function() { |
| 9116 | return this.proxy.getTimezoneOffset(); |
| 9117 | }; |
| 9118 | |
| 9119 | |
| 9120 | /** |
| 9121 | * Get the week number of the given year, starting with the first Monday as the first week |
| 9122 | * @returns {Integer} Week number (13 for the 13th week of the year). |
| 9123 | */ |
| 9124 | jsDate.prototype.getWeekOfYear = function() { |
| 9125 | var doy = this.getDayOfYear(); |
| 9126 | var rdow = 7 - this.getDayOfWeek(); |
| 9127 | var woy = parseInt((doy+rdow)/7, 10); |
| 9128 | return woy; |
| 9129 | }; |
| 9130 | |
| 9131 | /** |
| 9132 | * Get the current date as a Unix timestamp |
| 9133 | * |
| 9134 | * @returns {Integer} |
| 9135 | */ |
| 9136 | |
| 9137 | jsDate.prototype.getUnix = function() { |
| 9138 | return Math.round(this.proxy.getTime() / 1000, 0); |
| 9139 | }; |
| 9140 | |
| 9141 | /** |
| 9142 | * Implements Date functionality |
| 9143 | */ |
| 9144 | jsDate.prototype.getYear = function() { |
| 9145 | return this.proxy.getYear(); |
| 9146 | }; |
| 9147 | |
| 9148 | /** |
| 9149 | * Return a date one day ahead (or any other unit) |
| 9150 | * |
| 9151 | * @param {String} unit Optional, year | month | day | week | hour | minute | second | millisecond |
| 9152 | * @returns {jsDate} |
| 9153 | */ |
| 9154 | |
| 9155 | jsDate.prototype.next = function(unit) { |
| 9156 | unit = unit || 'day'; |
| 9157 | return this.clone().add(1, unit); |
| 9158 | }; |
| 9159 | |
| 9160 | /** |
| 9161 | * Set the jsDate instance to a new date. |
| 9162 | * |
| 9163 | * @param {String | Number | Array | Date Object | jsDate Object | Options Object} arguments Optional arguments, |
| 9164 | * either a parsable date/time string, |
| 9165 | * a JavaScript timestamp, an array of numbers of form [year, month, day, hours, minutes, seconds, milliseconds], |
| 9166 | * a Date object, jsDate Object or an options object of form {syntax: "perl", date:some Date} where all options are optional. |
| 9167 | */ |
| 9168 | jsDate.prototype.set = function() { |
| 9169 | switch ( arguments.length ) { |
| 9170 | case 0: |
| 9171 | this.proxy = new Date(); |
| 9172 | break; |
| 9173 | case 1: |
| 9174 | // other objects either won't have a _type property or, |
| 9175 | // if they do, it shouldn't be set to "jsDate", so |
| 9176 | // assume it is an options argument. |
| 9177 | if (get_type(arguments[0]) == "[object Object]" && arguments[0]._type != "jsDate") { |
| 9178 | var opts = this.options = arguments[0]; |
| 9179 | this.syntax = opts.syntax || this.syntax; |
| 9180 | this.defaultCentury = opts.defaultCentury || this.defaultCentury; |
| 9181 | this.proxy = jsDate.createDate(opts.date); |
| 9182 | } |
| 9183 | else { |
| 9184 | this.proxy = jsDate.createDate(arguments[0]); |
| 9185 | } |
| 9186 | break; |
| 9187 | default: |
| 9188 | var a = []; |
| 9189 | for ( var i=0; i<arguments.length; i++ ) { |
| 9190 | a.push(arguments[i]); |
| 9191 | } |
| 9192 | this.proxy = new Date( this.utcOffset ); |
| 9193 | this.proxy.setFullYear.apply( this.proxy, a.slice(0,3) ); |
| 9194 | if ( a.slice(3).length ) { |
| 9195 | this.proxy.setHours.apply( this.proxy, a.slice(3) ); |
| 9196 | } |
| 9197 | break; |
| 9198 | } |
| 9199 | }; |
| 9200 | |
| 9201 | /** |
| 9202 | * Sets the day of the month for a specified date according to local time. |
| 9203 | * @param {Integer} dayValue An integer from 1 to 31, representing the day of the month. |
| 9204 | */ |
| 9205 | jsDate.prototype.setDate = function(n) { |
| 9206 | return this.proxy.setDate(n); |
| 9207 | }; |
| 9208 | |
| 9209 | /** |
| 9210 | * Sets the full year for a specified date according to local time. |
| 9211 | * @param {Integer} yearValue The numeric value of the year, for example, 1995. |
| 9212 | * @param {Integer} monthValue Optional, between 0 and 11 representing the months January through December. |
| 9213 | * @param {Integer} dayValue Optional, between 1 and 31 representing the day of the month. If you specify the dayValue parameter, you must also specify the monthValue. |
| 9214 | */ |
| 9215 | jsDate.prototype.setFullYear = function() { |
| 9216 | return this.proxy.setFullYear.apply(this.proxy, arguments); |
| 9217 | }; |
| 9218 | |
| 9219 | /** |
| 9220 | * Sets the hours for a specified date according to local time. |
| 9221 | * |
| 9222 | * @param {Integer} hoursValue An integer between 0 and 23, representing the hour. |
| 9223 | * @param {Integer} minutesValue Optional, An integer between 0 and 59, representing the minutes. |
| 9224 | * @param {Integer} secondsValue Optional, An integer between 0 and 59, representing the seconds. |
| 9225 | * If you specify the secondsValue parameter, you must also specify the minutesValue. |
| 9226 | * @param {Integer} msValue Optional, A number between 0 and 999, representing the milliseconds. |
| 9227 | * If you specify the msValue parameter, you must also specify the minutesValue and secondsValue. |
| 9228 | */ |
| 9229 | jsDate.prototype.setHours = function() { |
| 9230 | return this.proxy.setHours.apply(this.proxy, arguments); |
| 9231 | }; |
| 9232 | |
| 9233 | /** |
| 9234 | * Implements Date functionality |
| 9235 | */ |
| 9236 | jsDate.prototype.setMilliseconds = function(n) { |
| 9237 | return this.proxy.setMilliseconds(n); |
| 9238 | }; |
| 9239 | |
| 9240 | /** |
| 9241 | * Implements Date functionality |
| 9242 | */ |
| 9243 | jsDate.prototype.setMinutes = function() { |
| 9244 | return this.proxy.setMinutes.apply(this.proxy, arguments); |
| 9245 | }; |
| 9246 | |
| 9247 | /** |
| 9248 | * Implements Date functionality |
| 9249 | */ |
| 9250 | jsDate.prototype.setMonth = function() { |
| 9251 | return this.proxy.setMonth.apply(this.proxy, arguments); |
| 9252 | }; |
| 9253 | |
| 9254 | /** |
| 9255 | * Implements Date functionality |
| 9256 | */ |
| 9257 | jsDate.prototype.setSeconds = function() { |
| 9258 | return this.proxy.setSeconds.apply(this.proxy, arguments); |
| 9259 | }; |
| 9260 | |
| 9261 | /** |
| 9262 | * Implements Date functionality |
| 9263 | */ |
| 9264 | jsDate.prototype.setTime = function(n) { |
| 9265 | return this.proxy.setTime(n); |
| 9266 | }; |
| 9267 | |
| 9268 | /** |
| 9269 | * Implements Date functionality |
| 9270 | */ |
| 9271 | jsDate.prototype.setYear = function() { |
| 9272 | return this.proxy.setYear.apply(this.proxy, arguments); |
| 9273 | }; |
| 9274 | |
| 9275 | /** |
| 9276 | * Provide a formatted string representation of this date. |
| 9277 | * |
| 9278 | * @param {String} formatString A format string. |
| 9279 | * See: {@link jsDate.formats}. |
| 9280 | * @returns {String} Date String. |
| 9281 | */ |
| 9282 | |
| 9283 | jsDate.prototype.strftime = function(formatString) { |
| 9284 | formatString = formatString || this.formatString || jsDate.regional[this.locale]['formatString']; |
| 9285 | return jsDate.strftime(this, formatString, this.syntax); |
| 9286 | }; |
| 9287 | |
| 9288 | /** |
| 9289 | * Return a String representation of this jsDate object. |
| 9290 | * @returns {String} Date string. |
| 9291 | */ |
| 9292 | |
| 9293 | jsDate.prototype.toString = function() { |
| 9294 | return this.proxy.toString(); |
| 9295 | }; |
| 9296 | |
| 9297 | /** |
| 9298 | * Convert the current date to an 8-digit integer (%Y%m%d) |
| 9299 | * |
| 9300 | * @returns {Integer} |
| 9301 | */ |
| 9302 | |
| 9303 | jsDate.prototype.toYmdInt = function() { |
| 9304 | return (this.proxy.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.proxy.getDate(); |
| 9305 | }; |
| 9306 | |
| 9307 | /** |
| 9308 | * @namespace Holds localizations for month/day names. |
| 9309 | * <p>jsDate attempts to detect locale when loaded and defaults to 'en'. |
| 9310 | * If a localization is detected which is not available, jsDate defaults to 'en'. |
| 9311 | * Additional localizations can be added after jsDate loads. After adding a localization, |
| 9312 | * call the jsDate.regional.getLocale() method. Currently, en, fr and de are defined.</p> |
| 9313 | * |
| 9314 | * <p>Localizations must be an object and have the following properties defined: monthNames, monthNamesShort, dayNames, dayNamesShort and Localizations are added like:</p> |
| 9315 | * <pre class="code"> |
| 9316 | * jsDate.regional['en'] = { |
| 9317 | * monthNames : 'January February March April May June July August September October November December'.split(' '), |
| 9318 | * monthNamesShort : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '), |
| 9319 | * dayNames : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '), |
| 9320 | * dayNamesShort : 'Sun Mon Tue Wed Thu Fri Sat'.split(' ') |
| 9321 | * }; |
| 9322 | * </pre> |
| 9323 | * <p>After adding localizations, call <code>jsDate.regional.getLocale();</code> to update the locale setting with the |
| 9324 | * new localizations.</p> |
| 9325 | */ |
| 9326 | |
| 9327 | jsDate.regional = { |
| 9328 | 'en': { |
| 9329 | monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'], |
| 9330 | monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], |
| 9331 | dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], |
| 9332 | dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], |
| 9333 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9334 | }, |
| 9335 | |
| 9336 | 'fr': { |
| 9337 | monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'], |
| 9338 | monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'], |
| 9339 | dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], |
| 9340 | dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'], |
| 9341 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9342 | }, |
| 9343 | |
| 9344 | 'de': { |
| 9345 | monthNames: ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'], |
| 9346 | monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'], |
| 9347 | dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], |
| 9348 | dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], |
| 9349 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9350 | }, |
| 9351 | |
| 9352 | 'es': { |
| 9353 | monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'], |
| 9354 | monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', 'Jul','Ago','Sep','Oct','Nov','Dic'], |
| 9355 | dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'], |
| 9356 | dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'], |
| 9357 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9358 | }, |
| 9359 | |
| 9360 | 'ru': { |
| 9361 | monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'], |
| 9362 | monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'], |
| 9363 | dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'], |
| 9364 | dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'], |
| 9365 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9366 | }, |
| 9367 | |
| 9368 | 'ar': { |
| 9369 | monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران','تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], |
| 9370 | monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'], |
| 9371 | dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'], |
| 9372 | dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'], |
| 9373 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9374 | }, |
| 9375 | |
| 9376 | 'pt': { |
| 9377 | monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], |
| 9378 | monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'], |
| 9379 | dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], |
| 9380 | dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], |
| 9381 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9382 | }, |
| 9383 | |
| 9384 | 'pt-BR': { |
| 9385 | monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], |
| 9386 | monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'], |
| 9387 | dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sábado'], |
| 9388 | dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb'], |
| 9389 | formatString: '%Y-%m-%d %H:%M:%S' |
| 9390 | } |
| 9391 | |
| 9392 | |
| 9393 | }; |
| 9394 | |
| 9395 | // Set english variants to 'en' |
| 9396 | jsDate.regional['en-US'] = jsDate.regional['en-GB'] = jsDate.regional['en']; |
| 9397 | |
| 9398 | /** |
| 9399 | * Try to determine the users locale based on the lang attribute of the html page. Defaults to 'en' |
| 9400 | * if it cannot figure out a locale of if the locale does not have a localization defined. |
| 9401 | * @returns {String} locale |
| 9402 | */ |
| 9403 | |
| 9404 | jsDate.regional.getLocale = function () { |
| 9405 | var l = jsDate.config.defaultLocale; |
| 9406 | |
| 9407 | if ( document && document.getElementsByTagName('html') && document.getElementsByTagName('html')[0].lang ) { |
| 9408 | l = document.getElementsByTagName('html')[0].lang; |
| 9409 | if (!jsDate.regional.hasOwnProperty(l)) { |
| 9410 | l = jsDate.config.defaultLocale; |
| 9411 | } |
| 9412 | } |
| 9413 | |
| 9414 | return l; |
| 9415 | }; |
| 9416 | |
| 9417 | // ms in day |
| 9418 | var day = 24 * 60 * 60 * 1000; |
| 9419 | |
| 9420 | // padd a number with zeros |
| 9421 | var addZeros = function(num, digits) { |
| 9422 | num = String(num); |
| 9423 | var i = digits - num.length; |
| 9424 | var s = String(Math.pow(10, i)).slice(1); |
| 9425 | return s.concat(num); |
| 9426 | }; |
| 9427 | |
| 9428 | // representations used for calculating differences between dates. |
| 9429 | // This borrows heavily from Ken Snyder's work. |
| 9430 | var multipliers = { |
| 9431 | millisecond: 1, |
| 9432 | second: 1000, |
| 9433 | minute: 60 * 1000, |
| 9434 | hour: 60 * 60 * 1000, |
| 9435 | day: day, |
| 9436 | week: 7 * day, |
| 9437 | month: { |
| 9438 | // add a number of months |
| 9439 | add: function(d, number) { |
| 9440 | // add any years needed (increments of 12) |
| 9441 | multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12)); |
| 9442 | // ensure that we properly wrap betwen December and January |
| 9443 | var prevMonth = d.getMonth() + (number % 12); |
| 9444 | if (prevMonth == 12) { |
| 9445 | prevMonth = 0; |
| 9446 | d.setYear(d.getFullYear() + 1); |
| 9447 | } else if (prevMonth == -1) { |
| 9448 | prevMonth = 11; |
| 9449 | d.setYear(d.getFullYear() - 1); |
| 9450 | } |
| 9451 | d.setMonth(prevMonth); |
| 9452 | }, |
| 9453 | // get the number of months between two Date objects (decimal to the nearest day) |
| 9454 | diff: function(d1, d2) { |
| 9455 | // get the number of years |
| 9456 | var diffYears = d1.getFullYear() - d2.getFullYear(); |
| 9457 | // get the number of remaining months |
| 9458 | var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12); |
| 9459 | // get the number of remaining days |
| 9460 | var diffDays = d1.getDate() - d2.getDate(); |
| 9461 | // return the month difference with the days difference as a decimal |
| 9462 | return diffMonths + (diffDays / 30); |
| 9463 | } |
| 9464 | }, |
| 9465 | year: { |
| 9466 | // add a number of years |
| 9467 | add: function(d, number) { |
| 9468 | d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number)); |
| 9469 | }, |
| 9470 | // get the number of years between two Date objects (decimal to the nearest day) |
| 9471 | diff: function(d1, d2) { |
| 9472 | return multipliers.month.diff(d1, d2) / 12; |
| 9473 | } |
| 9474 | } |
| 9475 | }; |
| 9476 | // |
| 9477 | // Alias each multiplier with an 's' to allow 'year' and 'years' for example. |
| 9478 | // This comes from Ken Snyders work. |
| 9479 | // |
| 9480 | for (var unit in multipliers) { |
| 9481 | if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :| |
| 9482 | multipliers[unit + 's'] = multipliers[unit]; |
| 9483 | } |
| 9484 | } |
| 9485 | |
| 9486 | // |
| 9487 | // take a jsDate instance and a format code and return the formatted value. |
| 9488 | // This is a somewhat modified version of Ken Snyder's method. |
| 9489 | // |
| 9490 | var format = function(d, code, syntax) { |
| 9491 | // if shorcut codes are used, recursively expand those. |
| 9492 | if (jsDate.formats[syntax]["shortcuts"][code]) { |
| 9493 | return jsDate.strftime(d, jsDate.formats[syntax]["shortcuts"][code], syntax); |
| 9494 | } else { |
| 9495 | // get the format code function and addZeros() argument |
| 9496 | var getter = (jsDate.formats[syntax]["codes"][code] || '').split('.'); |
| 9497 | var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : ''; |
| 9498 | if (getter[1]) { |
| 9499 | nbr = addZeros(nbr, getter[1]); |
| 9500 | } |
| 9501 | return nbr; |
| 9502 | } |
| 9503 | }; |
| 9504 | |
| 9505 | /** |
| 9506 | * @static |
| 9507 | * Static function for convert a date to a string according to a given format. Also acts as namespace for strftime format codes. |
| 9508 | * <p>strftime formatting can be accomplished without creating a jsDate object by calling jsDate.strftime():</p> |
| 9509 | * <pre class="code"> |
| 9510 | * var formattedDate = jsDate.strftime('Feb 8, 2006 8:48:32', '%Y-%m-%d %H:%M:%S'); |
| 9511 | * </pre> |
| 9512 | * @param {String | Number | Array | jsDate 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. |
| 9513 | * @param {String} formatString String with embedded date formatting codes. |
| 9514 | * See: {@link jsDate.formats}. |
| 9515 | * @param {String} syntax Optional syntax to use [default perl]. |
| 9516 | * @param {String} locale Optional locale to use. |
| 9517 | * @returns {String} Formatted representation of the date. |
| 9518 | */ |
| 9519 | // |
| 9520 | // Logic as implemented here is very similar to Ken Snyder's Date Instance Methods. |
| 9521 | // |
| 9522 | jsDate.strftime = function(d, formatString, syntax, locale) { |
| 9523 | var syn = 'perl'; |
| 9524 | var loc = jsDate.regional.getLocale(); |
| 9525 | |
| 9526 | // check if syntax and locale are available or reversed |
| 9527 | if (syntax && jsDate.formats.hasOwnProperty(syntax)) { |
| 9528 | syn = syntax; |
| 9529 | } |
| 9530 | else if (syntax && jsDate.regional.hasOwnProperty(syntax)) { |
| 9531 | loc = syntax; |
| 9532 | } |
| 9533 | |
| 9534 | if (locale && jsDate.formats.hasOwnProperty(locale)) { |
| 9535 | syn = locale; |
| 9536 | } |
| 9537 | else if (locale && jsDate.regional.hasOwnProperty(locale)) { |
| 9538 | loc = locale; |
| 9539 | } |
| 9540 | |
| 9541 | if (get_type(d) != "[object Object]" || d._type != "jsDate") { |
| 9542 | d = new jsDate(d); |
| 9543 | d.locale = loc; |
| 9544 | } |
| 9545 | if (!formatString) { |
| 9546 | formatString = d.formatString || jsDate.regional[loc]['formatString']; |
| 9547 | } |
| 9548 | // default the format string to year-month-day |
| 9549 | var source = formatString || '%Y-%m-%d', |
| 9550 | result = '', |
| 9551 | match; |
| 9552 | // replace each format code |
| 9553 | while (source.length > 0) { |
| 9554 | if (match = source.match(jsDate.formats[syn].codes.matcher)) { |
| 9555 | result += source.slice(0, match.index); |
| 9556 | result += (match[1] || '') + format(d, match[2], syn); |
| 9557 | source = source.slice(match.index + match[0].length); |
| 9558 | } else { |
| 9559 | result += source; |
| 9560 | source = ''; |
| 9561 | } |
| 9562 | } |
| 9563 | return result; |
| 9564 | }; |
| 9565 | |
| 9566 | /** |
| 9567 | * @namespace |
| 9568 | * Namespace to hold format codes and format shortcuts. "perl" and "php" format codes |
| 9569 | * and shortcuts are defined by default. Additional codes and shortcuts can be |
| 9570 | * added like: |
| 9571 | * |
| 9572 | * <pre class="code"> |
| 9573 | * jsDate.formats["perl"] = { |
| 9574 | * "codes": { |
| 9575 | * matcher: /someregex/, |
| 9576 | * Y: "fullYear", // name of "get" method without the "get", |
| 9577 | * ..., // more codes |
| 9578 | * }, |
| 9579 | * "shortcuts": { |
| 9580 | * F: '%Y-%m-%d', |
| 9581 | * ..., // more shortcuts |
| 9582 | * } |
| 9583 | * }; |
| 9584 | * </pre> |
| 9585 | * |
| 9586 | * <p>Additionally, ISO and SQL shortcuts are defined and can be accesses via: |
| 9587 | * <code>jsDate.formats.ISO</code> and <code>jsDate.formats.SQL</code> |
| 9588 | */ |
| 9589 | |
| 9590 | jsDate.formats = { |
| 9591 | ISO:'%Y-%m-%dT%H:%M:%S.%N%G', |
| 9592 | SQL:'%Y-%m-%d %H:%M:%S' |
| 9593 | }; |
| 9594 | |
| 9595 | /** |
| 9596 | * Perl format codes and shortcuts for strftime. |
| 9597 | * |
| 9598 | * A hash (object) of codes where each code must be an array where the first member is |
| 9599 | * the name of a Date.prototype or jsDate.prototype function to call |
| 9600 | * and optionally a second member indicating the number to pass to addZeros() |
| 9601 | * |
| 9602 | * <p>The following format codes are defined:</p> |
| 9603 | * |
| 9604 | * <pre class="code"> |
| 9605 | * Code Result Description |
| 9606 | * == Years == |
| 9607 | * %Y 2008 Four-digit year |
| 9608 | * %y 08 Two-digit year |
| 9609 | * |
| 9610 | * == Months == |
| 9611 | * %m 09 Two-digit month |
| 9612 | * %#m 9 One or two-digit month |
| 9613 | * %B September Full month name |
| 9614 | * %b Sep Abbreviated month name |
| 9615 | * |
| 9616 | * == Days == |
| 9617 | * %d 05 Two-digit day of month |
| 9618 | * %#d 5 One or two-digit day of month |
| 9619 | * %e 5 One or two-digit day of month |
| 9620 | * %A Sunday Full name of the day of the week |
| 9621 | * %a Sun Abbreviated name of the day of the week |
| 9622 | * %w 0 Number of the day of the week (0 = Sunday, 6 = Saturday) |
| 9623 | * |
| 9624 | * == Hours == |
| 9625 | * %H 23 Hours in 24-hour format (two digits) |
| 9626 | * %#H 3 Hours in 24-hour integer format (one or two digits) |
| 9627 | * %I 11 Hours in 12-hour format (two digits) |
| 9628 | * %#I 3 Hours in 12-hour integer format (one or two digits) |
| 9629 | * %p PM AM or PM |
| 9630 | * |
| 9631 | * == Minutes == |
| 9632 | * %M 09 Minutes (two digits) |
| 9633 | * %#M 9 Minutes (one or two digits) |
| 9634 | * |
| 9635 | * == Seconds == |
| 9636 | * %S 02 Seconds (two digits) |
| 9637 | * %#S 2 Seconds (one or two digits) |
| 9638 | * %s 1206567625723 Unix timestamp (Seconds past 1970-01-01 00:00:00) |
| 9639 | * |
| 9640 | * == Milliseconds == |
| 9641 | * %N 008 Milliseconds (three digits) |
| 9642 | * %#N 8 Milliseconds (one to three digits) |
| 9643 | * |
| 9644 | * == Timezone == |
| 9645 | * %O 360 difference in minutes between local time and GMT |
| 9646 | * %Z Mountain Standard Time Name of timezone as reported by browser |
| 9647 | * %G 06:00 Hours and minutes between GMT |
| 9648 | * |
| 9649 | * == Shortcuts == |
| 9650 | * %F 2008-03-26 %Y-%m-%d |
| 9651 | * %T 05:06:30 %H:%M:%S |
| 9652 | * %X 05:06:30 %H:%M:%S |
| 9653 | * %x 03/26/08 %m/%d/%y |
| 9654 | * %D 03/26/08 %m/%d/%y |
| 9655 | * %#c Wed Mar 26 15:31:00 2008 %a %b %e %H:%M:%S %Y |
| 9656 | * %v 3-Sep-2008 %e-%b-%Y |
| 9657 | * %R 15:31 %H:%M |
| 9658 | * %r 03:31:00 PM %I:%M:%S %p |
| 9659 | * |
| 9660 | * == Characters == |
| 9661 | * %n \n Newline |
| 9662 | * %t \t Tab |
| 9663 | * %% % Percent Symbol |
| 9664 | * </pre> |
| 9665 | * |
| 9666 | * <p>Formatting shortcuts that will be translated into their longer version. |
| 9667 | * Be sure that format shortcuts do not refer to themselves: this will cause an infinite loop.</p> |
| 9668 | * |
| 9669 | * <p>Format codes and format shortcuts can be redefined after the jsDate |
| 9670 | * module is imported.</p> |
| 9671 | * |
| 9672 | * <p>Note that if you redefine the whole hash (object), you must supply a "matcher" |
| 9673 | * regex for the parser. The default matcher is:</p> |
| 9674 | * |
| 9675 | * <code>/()%(#?(%|[a-z]))/i</code> |
| 9676 | * |
| 9677 | * <p>which corresponds to the Perl syntax used by default.</p> |
| 9678 | * |
| 9679 | * <p>By customizing the matcher and format codes, nearly any strftime functionality is possible.</p> |
| 9680 | */ |
| 9681 | |
| 9682 | jsDate.formats.perl = { |
| 9683 | codes: { |
| 9684 | // |
| 9685 | // 2-part regex matcher for format codes |
| 9686 | // |
| 9687 | // first match must be the character before the code (to account for escaping) |
| 9688 | // second match must be the format code character(s) |
| 9689 | // |
| 9690 | matcher: /()%(#?(%|[a-z]))/i, |
| 9691 | // year |
| 9692 | Y: 'FullYear', |
| 9693 | y: 'ShortYear.2', |
| 9694 | // month |
| 9695 | m: 'MonthNumber.2', |
| 9696 | '#m': 'MonthNumber', |
| 9697 | B: 'MonthName', |
| 9698 | b: 'AbbrMonthName', |
| 9699 | // day |
| 9700 | d: 'Date.2', |
| 9701 | '#d': 'Date', |
| 9702 | e: 'Date', |
| 9703 | A: 'DayName', |
| 9704 | a: 'AbbrDayName', |
| 9705 | w: 'Day', |
| 9706 | // hours |
| 9707 | H: 'Hours.2', |
| 9708 | '#H': 'Hours', |
| 9709 | I: 'Hours12.2', |
| 9710 | '#I': 'Hours12', |
| 9711 | p: 'AMPM', |
| 9712 | // minutes |
| 9713 | M: 'Minutes.2', |
| 9714 | '#M': 'Minutes', |
| 9715 | // seconds |
| 9716 | S: 'Seconds.2', |
| 9717 | '#S': 'Seconds', |
| 9718 | s: 'Unix', |
| 9719 | // milliseconds |
| 9720 | N: 'Milliseconds.3', |
| 9721 | '#N': 'Milliseconds', |
| 9722 | // timezone |
| 9723 | O: 'TimezoneOffset', |
| 9724 | Z: 'TimezoneName', |
| 9725 | G: 'GmtOffset' |
| 9726 | }, |
| 9727 | |
| 9728 | shortcuts: { |
| 9729 | // date |
| 9730 | F: '%Y-%m-%d', |
| 9731 | // time |
| 9732 | T: '%H:%M:%S', |
| 9733 | X: '%H:%M:%S', |
| 9734 | // local format date |
| 9735 | x: '%m/%d/%y', |
| 9736 | D: '%m/%d/%y', |
| 9737 | // local format extended |
| 9738 | '#c': '%a %b %e %H:%M:%S %Y', |
| 9739 | // local format short |
| 9740 | v: '%e-%b-%Y', |
| 9741 | R: '%H:%M', |
| 9742 | r: '%I:%M:%S %p', |
| 9743 | // tab and newline |
| 9744 | t: '\t', |
| 9745 | n: '\n', |
| 9746 | '%': '%' |
| 9747 | } |
| 9748 | }; |
| 9749 | |
| 9750 | /** |
| 9751 | * PHP format codes and shortcuts for strftime. |
| 9752 | * |
| 9753 | * A hash (object) of codes where each code must be an array where the first member is |
| 9754 | * the name of a Date.prototype or jsDate.prototype function to call |
| 9755 | * and optionally a second member indicating the number to pass to addZeros() |
| 9756 | * |
| 9757 | * <p>The following format codes are defined:</p> |
| 9758 | * |
| 9759 | * <pre class="code"> |
| 9760 | * Code Result Description |
| 9761 | * === Days === |
| 9762 | * %a Sun through Sat An abbreviated textual representation of the day |
| 9763 | * %A Sunday - Saturday A full textual representation of the day |
| 9764 | * %d 01 to 31 Two-digit day of the month (with leading zeros) |
| 9765 | * %e 1 to 31 Day of the month, with a space preceding single digits. |
| 9766 | * %j 001 to 366 Day of the year, 3 digits with leading zeros |
| 9767 | * %u 1 - 7 (Mon - Sun) ISO-8601 numeric representation of the day of the week |
| 9768 | * %w 0 - 6 (Sun - Sat) Numeric representation of the day of the week |
| 9769 | * |
| 9770 | * === Week === |
| 9771 | * %U 13 Full Week number, starting with the first Sunday as the first week |
| 9772 | * %V 01 through 53 ISO-8601:1988 week number, starting with the first week of the year |
| 9773 | * with at least 4 weekdays, with Monday being the start of the week |
| 9774 | * %W 46 A numeric representation of the week of the year, |
| 9775 | * starting with the first Monday as the first week |
| 9776 | * === Month === |
| 9777 | * %b Jan through Dec Abbreviated month name, based on the locale |
| 9778 | * %B January - December Full month name, based on the locale |
| 9779 | * %h Jan through Dec Abbreviated month name, based on the locale (an alias of %b) |
| 9780 | * %m 01 - 12 (Jan - Dec) Two digit representation of the month |
| 9781 | * |
| 9782 | * === Year === |
| 9783 | * %C 19 Two digit century (year/100, truncated to an integer) |
| 9784 | * %y 09 for 2009 Two digit year |
| 9785 | * %Y 2038 Four digit year |
| 9786 | * |
| 9787 | * === Time === |
| 9788 | * %H 00 through 23 Two digit representation of the hour in 24-hour format |
| 9789 | * %I 01 through 12 Two digit representation of the hour in 12-hour format |
| 9790 | * %l 1 through 12 Hour in 12-hour format, with a space preceeding single digits |
| 9791 | * %M 00 through 59 Two digit representation of the minute |
| 9792 | * %p AM/PM UPPER-CASE 'AM' or 'PM' based on the given time |
| 9793 | * %P am/pm lower-case 'am' or 'pm' based on the given time |
| 9794 | * %r 09:34:17 PM Same as %I:%M:%S %p |
| 9795 | * %R 00:35 Same as %H:%M |
| 9796 | * %S 00 through 59 Two digit representation of the second |
| 9797 | * %T 21:34:17 Same as %H:%M:%S |
| 9798 | * %X 03:59:16 Preferred time representation based on locale, without the date |
| 9799 | * %z -0500 or EST Either the time zone offset from UTC or the abbreviation |
| 9800 | * %Z -0500 or EST The time zone offset/abbreviation option NOT given by %z |
| 9801 | * |
| 9802 | * === Time and Date === |
| 9803 | * %D 02/05/09 Same as %m/%d/%y |
| 9804 | * %F 2009-02-05 Same as %Y-%m-%d (commonly used in database datestamps) |
| 9805 | * %s 305815200 Unix Epoch Time timestamp (same as the time() function) |
| 9806 | * %x 02/05/09 Preferred date representation, without the time |
| 9807 | * |
| 9808 | * === Miscellaneous === |
| 9809 | * %n --- A newline character (\n) |
| 9810 | * %t --- A Tab character (\t) |
| 9811 | * %% --- A literal percentage character (%) |
| 9812 | * </pre> |
| 9813 | */ |
| 9814 | |
| 9815 | jsDate.formats.php = { |
| 9816 | codes: { |
| 9817 | // |
| 9818 | // 2-part regex matcher for format codes |
| 9819 | // |
| 9820 | // first match must be the character before the code (to account for escaping) |
| 9821 | // second match must be the format code character(s) |
| 9822 | // |
| 9823 | matcher: /()%((%|[a-z]))/i, |
| 9824 | // day |
| 9825 | a: 'AbbrDayName', |
| 9826 | A: 'DayName', |
| 9827 | d: 'Date.2', |
| 9828 | e: 'Date', |
| 9829 | j: 'DayOfYear.3', |
| 9830 | u: 'DayOfWeek', |
| 9831 | w: 'Day', |
| 9832 | // week |
| 9833 | U: 'FullWeekOfYear.2', |
| 9834 | V: 'IsoWeek.2', |
| 9835 | W: 'WeekOfYear.2', |
| 9836 | // month |
| 9837 | b: 'AbbrMonthName', |
| 9838 | B: 'MonthName', |
| 9839 | m: 'MonthNumber.2', |
| 9840 | h: 'AbbrMonthName', |
| 9841 | // year |
| 9842 | C: 'Century.2', |
| 9843 | y: 'ShortYear.2', |
| 9844 | Y: 'FullYear', |
| 9845 | // time |
| 9846 | H: 'Hours.2', |
| 9847 | I: 'Hours12.2', |
| 9848 | l: 'Hours12', |
| 9849 | p: 'AMPM', |
| 9850 | P: 'AmPm', |
| 9851 | M: 'Minutes.2', |
| 9852 | S: 'Seconds.2', |
| 9853 | s: 'Unix', |
| 9854 | O: 'TimezoneOffset', |
| 9855 | z: 'GmtOffset', |
| 9856 | Z: 'TimezoneAbbr' |
| 9857 | }, |
| 9858 | |
| 9859 | shortcuts: { |
| 9860 | D: '%m/%d/%y', |
| 9861 | F: '%Y-%m-%d', |
| 9862 | T: '%H:%M:%S', |
| 9863 | X: '%H:%M:%S', |
| 9864 | x: '%m/%d/%y', |
| 9865 | R: '%H:%M', |
| 9866 | r: '%I:%M:%S %p', |
| 9867 | t: '\t', |
| 9868 | n: '\n', |
| 9869 | '%': '%' |
| 9870 | } |
| 9871 | }; |
| 9872 | // |
| 9873 | // Conceptually, the logic implemented here is similar to Ken Snyder's Date Instance Methods. |
| 9874 | // I use his idea of a set of parsers which can be regular expressions or functions, |
| 9875 | // iterating through those, and then seeing if Date.parse() will create a date. |
| 9876 | // The parser expressions and functions are a little different and some bugs have been |
| 9877 | // worked out. Also, a lot of "pre-parsing" is done to fix implementation |
| 9878 | // variations of Date.parse() between browsers. |
| 9879 | // |
| 9880 | jsDate.createDate = function(date) { |
| 9881 | // if passing in multiple arguments, try Date constructor |
| 9882 | if (date == null) { |
| 9883 | return new Date(); |
| 9884 | } |
| 9885 | // If the passed value is already a date object, return it |
| 9886 | if (date instanceof Date) { |
| 9887 | return date; |
| 9888 | } |
| 9889 | // if (typeof date == 'number') return new Date(date * 1000); |
| 9890 | // If the passed value is an integer, interpret it as a javascript timestamp |
| 9891 | if (typeof date == 'number') { |
| 9892 | return new Date(date); |
| 9893 | } |
| 9894 | |
| 9895 | // Before passing strings into Date.parse(), have to normalize them for certain conditions. |
| 9896 | // If strings are not formatted staccording to the EcmaScript spec, results from Date parse will be implementation dependent. |
| 9897 | // |
| 9898 | // For example: |
| 9899 | // * FF and Opera assume 2 digit dates are pre y2k, Chome assumes <50 is pre y2k, 50+ is 21st century. |
| 9900 | // * Chrome will correctly parse '1984-1-25' into localtime, FF and Opera will not parse. |
| 9901 | // * Both FF, Chrome and Opera will parse '1984/1/25' into localtime. |
| 9902 | |
| 9903 | // remove leading and trailing spaces |
| 9904 | var parsable = String(date).replace(/^\s*(.+)\s*$/g, '$1'); |
| 9905 | |
| 9906 | // replace dahses (-) with slashes (/) in dates like n[nnn]/n[n]/n[nnn] |
| 9907 | parsable = parsable.replace(/^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,4})/, "$1/$2/$3"); |
| 9908 | |
| 9909 | ///////// |
| 9910 | // Need to check for '15-Dec-09' also. |
| 9911 | // FF will not parse, but Chrome will. |
| 9912 | // Chrome will set date to 2009 as well. |
| 9913 | ///////// |
| 9914 | |
| 9915 | // first check for 'dd-mmm-yyyy' or 'dd/mmm/yyyy' like '15-Dec-2010' |
| 9916 | parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{4})/i, "$1 $2 $3"); |
| 9917 | |
| 9918 | // Now check for 'dd-mmm-yy' or 'dd/mmm/yy' and normalize years to default century. |
| 9919 | var match = parsable.match(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i); |
| 9920 | if (match && match.length > 3) { |
| 9921 | var m3 = parseFloat(match[3]); |
| 9922 | var ny = jsDate.config.defaultCentury + m3; |
| 9923 | ny = String(ny); |
| 9924 | |
| 9925 | // now replace 2 digit year with 4 digit year |
| 9926 | parsable = parsable.replace(/^(3[01]|[0-2]?\d)[-\/]([a-z]{3,})[-\/](\d{2})\D*/i, match[1] +' '+ match[2] +' '+ ny); |
| 9927 | |
| 9928 | } |
| 9929 | |
| 9930 | // Check for '1/19/70 8:14PM' |
| 9931 | // where starts with mm/dd/yy or yy/mm/dd and have something after |
| 9932 | // Check if 1st postiion is greater than 31, assume it is year. |
| 9933 | // Assme all 2 digit years are 1900's. |
| 9934 | // Finally, change them into US style mm/dd/yyyy representations. |
| 9935 | match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})[^0-9]/); |
| 9936 | |
| 9937 | function h1(parsable, match) { |
| 9938 | var m1 = parseFloat(match[1]); |
| 9939 | var m2 = parseFloat(match[2]); |
| 9940 | var m3 = parseFloat(match[3]); |
| 9941 | var cent = jsDate.config.defaultCentury; |
| 9942 | var ny, nd, nm, str; |
| 9943 | |
| 9944 | if (m1 > 31) { // first number is a year |
| 9945 | nd = m3; |
| 9946 | nm = m2; |
| 9947 | ny = cent + m1; |
| 9948 | } |
| 9949 | |
| 9950 | else { // last number is the year |
| 9951 | nd = m2; |
| 9952 | nm = m1; |
| 9953 | ny = cent + m3; |
| 9954 | } |
| 9955 | |
| 9956 | str = nm+'/'+nd+'/'+ny; |
| 9957 | |
| 9958 | // now replace 2 digit year with 4 digit year |
| 9959 | return parsable.replace(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})/, str); |
| 9960 | |
| 9961 | } |
| 9962 | |
| 9963 | if (match && match.length > 3) { |
| 9964 | parsable = h1(parsable, match); |
| 9965 | } |
| 9966 | |
| 9967 | // Now check for '1/19/70' with nothing after and do as above |
| 9968 | var match = parsable.match(/^([0-9]{1,2})[-\/]([0-9]{1,2})[-\/]([0-9]{1,2})$/); |
| 9969 | |
| 9970 | if (match && match.length > 3) { |
| 9971 | parsable = h1(parsable, match); |
| 9972 | } |
| 9973 | |
| 9974 | |
| 9975 | var i = 0; |
| 9976 | var length = jsDate.matchers.length; |
| 9977 | var pattern, |
| 9978 | ms, |
| 9979 | current = parsable, |
| 9980 | obj; |
| 9981 | while (i < length) { |
| 9982 | ms = Date.parse(current); |
| 9983 | if (!isNaN(ms)) { |
| 9984 | return new Date(ms); |
| 9985 | } |
| 9986 | pattern = jsDate.matchers[i]; |
| 9987 | if (typeof pattern == 'function') { |
| 9988 | obj = pattern.call(jsDate, current); |
| 9989 | if (obj instanceof Date) { |
| 9990 | return obj; |
| 9991 | } |
| 9992 | } else { |
| 9993 | current = parsable.replace(pattern[0], pattern[1]); |
| 9994 | } |
| 9995 | i++; |
| 9996 | } |
| 9997 | return NaN; |
| 9998 | }; |
| 9999 | |
| 10000 | |
| 10001 | /** |
| 10002 | * @static |
| 10003 | * Handy static utility function to return the number of days in a given month. |
| 10004 | * @param {Integer} year Year |
| 10005 | * @param {Integer} month Month (1-12) |
| 10006 | * @returns {Integer} Number of days in the month. |
| 10007 | */ |
| 10008 | // |
| 10009 | // handy utility method Borrowed right from Ken Snyder's Date Instance Mehtods. |
| 10010 | // |
| 10011 | jsDate.daysInMonth = function(year, month) { |
| 10012 | if (month == 2) { |
| 10013 | return new Date(year, 1, 29).getDate() == 29 ? 29 : 28; |
| 10014 | } |
| 10015 | return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month]; |
| 10016 | }; |
| 10017 | |
| 10018 | |
| 10019 | // |
| 10020 | // An Array of regular expressions or functions that will attempt to match the date string. |
| 10021 | // Functions are called with scope of a jsDate instance. |
| 10022 | // |
| 10023 | jsDate.matchers = [ |
| 10024 | // convert dd.mmm.yyyy to mm/dd/yyyy (world date to US date). |
| 10025 | [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'], |
| 10026 | // convert yyyy-mm-dd to mm/dd/yyyy (ISO date to US date). |
| 10027 | [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'], |
| 10028 | // Handle 12 hour or 24 hour time with milliseconds am/pm and optional date part. |
| 10029 | function(str) { |
| 10030 | var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i); |
| 10031 | // opt. date hour opt. minute opt. second opt. msec opt. am or pm |
| 10032 | if (match) { |
| 10033 | if (match[1]) { |
| 10034 | var d = this.createDate(match[1]); |
| 10035 | if (isNaN(d)) { |
| 10036 | return; |
| 10037 | } |
| 10038 | } else { |
| 10039 | var d = new Date(); |
| 10040 | d.setMilliseconds(0); |
| 10041 | } |
| 10042 | var hour = parseFloat(match[2]); |
| 10043 | if (match[6]) { |
| 10044 | hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12); |
| 10045 | } |
| 10046 | d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000); |
| 10047 | return d; |
| 10048 | } |
| 10049 | else { |
| 10050 | return str; |
| 10051 | } |
| 10052 | }, |
| 10053 | // Handle ISO timestamp with time zone. |
| 10054 | function(str) { |
| 10055 | var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i); |
| 10056 | if (match) { |
| 10057 | if (match[1]) { |
| 10058 | var d = this.createDate(match[1]); |
| 10059 | if (isNaN(d)) { |
| 10060 | return; |
| 10061 | } |
| 10062 | } else { |
| 10063 | var d = new Date(); |
| 10064 | d.setMilliseconds(0); |
| 10065 | } |
| 10066 | var hour = parseFloat(match[2]); |
| 10067 | d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000); |
| 10068 | return d; |
| 10069 | } |
| 10070 | else { |
| 10071 | return str; |
| 10072 | } |
| 10073 | }, |
| 10074 | // Try to match ambiguous strings like 12/8/22. |
| 10075 | // Use FF date assumption that 2 digit years are 20th century (i.e. 1900's). |
| 10076 | // This may be redundant with pre processing of date already performed. |
| 10077 | function(str) { |
| 10078 | var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/); |
| 10079 | if (match) { |
| 10080 | var d = new Date(); |
| 10081 | var cent = jsDate.config.defaultCentury; |
| 10082 | var m1 = parseFloat(match[1]); |
| 10083 | var m3 = parseFloat(match[3]); |
| 10084 | var ny, nd, nm; |
| 10085 | if (m1 > 31) { // first number is a year |
| 10086 | nd = m3; |
| 10087 | ny = cent + m1; |
| 10088 | } |
| 10089 | |
| 10090 | else { // last number is the year |
| 10091 | nd = m1; |
| 10092 | ny = cent + m3; |
| 10093 | } |
| 10094 | |
| 10095 | var nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNamesShort"]); |
| 10096 | |
| 10097 | if (nm == -1) { |
| 10098 | nm = inArray(match[2], jsDate.regional[jsDate.regional.getLocale()]["monthNames"]); |
| 10099 | } |
| 10100 | |
| 10101 | d.setFullYear(ny, nm, nd); |
| 10102 | d.setHours(0,0,0,0); |
| 10103 | return d; |
| 10104 | } |
| 10105 | |
| 10106 | else { |
| 10107 | return str; |
| 10108 | } |
| 10109 | } |
| 10110 | ]; |
| 10111 | |
| 10112 | // |
| 10113 | // I think John Reisig published this method on his blog, ejohn. |
| 10114 | // |
| 10115 | function inArray( elem, array ) { |
| 10116 | if ( array.indexOf ) { |
| 10117 | return array.indexOf( elem ); |
| 10118 | } |
| 10119 | |
| 10120 | for ( var i = 0, length = array.length; i < length; i++ ) { |
| 10121 | if ( array[ i ] === elem ) { |
| 10122 | return i; |
| 10123 | } |
| 10124 | } |
| 10125 | |
| 10126 | return -1; |
| 10127 | } |
| 10128 | |
| 10129 | // |
| 10130 | // Thanks to Kangax, Christian Sciberras and Stack Overflow for this method. |
| 10131 | // |
| 10132 | function get_type(thing){ |
| 10133 | if(thing===null) return "[object Null]"; // special case |
| 10134 | return Object.prototype.toString.call(thing); |
| 10135 | } |
| 10136 | |
| 10137 | $.jsDate = jsDate; |
| 10138 | |
| 10139 | |
| 10140 | /** |
| 10141 | * JavaScript printf/sprintf functions. |
| 10142 | * |
| 10143 | * This code has been adapted from the publicly available sprintf methods |
| 10144 | * by Ash Searle. His original header follows: |
| 10145 | * |
| 10146 | * This code is unrestricted: you are free to use it however you like. |
| 10147 | * |
| 10148 | * The functions should work as expected, performing left or right alignment, |
| 10149 | * truncating strings, outputting numbers with a required precision etc. |
| 10150 | * |
| 10151 | * For complex cases, these functions follow the Perl implementations of |
| 10152 | * (s)printf, allowing arguments to be passed out-of-order, and to set the |
| 10153 | * precision or length of the output based on arguments instead of fixed |
| 10154 | * numbers. |
| 10155 | * |
| 10156 | * See http://perldoc.perl.org/functions/sprintf.html for more information. |
| 10157 | * |
| 10158 | * Implemented: |
| 10159 | * - zero and space-padding |
| 10160 | * - right and left-alignment, |
| 10161 | * - base X prefix (binary, octal and hex) |
| 10162 | * - positive number prefix |
| 10163 | * - (minimum) width |
| 10164 | * - precision / truncation / maximum width |
| 10165 | * - out of order arguments |
| 10166 | * |
| 10167 | * Not implemented (yet): |
| 10168 | * - vector flag |
| 10169 | * - size (bytes, words, long-words etc.) |
| 10170 | * |
| 10171 | * Will not implement: |
| 10172 | * - %n or %p (no pass-by-reference in JavaScript) |
| 10173 | * |
| 10174 | * @version 2007.04.27 |
| 10175 | * @author Ash Searle |
| 10176 | * |
| 10177 | * You can see the original work and comments on his blog: |
| 10178 | * http://hexmen.com/blog/2007/03/printf-sprintf/ |
| 10179 | * http://hexmen.com/js/sprintf.js |
| 10180 | */ |
| 10181 | |
| 10182 | /** |
| 10183 | * @Modifications 2009.05.26 |
| 10184 | * @author Chris Leonello |
| 10185 | * |
| 10186 | * Added %p %P specifier |
| 10187 | * Acts like %g or %G but will not add more significant digits to the output than present in the input. |
| 10188 | * Example: |
| 10189 | * Format: '%.3p', Input: 0.012, Output: 0.012 |
| 10190 | * Format: '%.3g', Input: 0.012, Output: 0.0120 |
| 10191 | * Format: '%.4p', Input: 12.0, Output: 12.0 |
| 10192 | * Format: '%.4g', Input: 12.0, Output: 12.00 |
| 10193 | * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5 |
| 10194 | * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5 |
| 10195 | * |
| 10196 | * Example: |
| 10197 | * >>> $.jqplot.sprintf('%.2f, %d', 23.3452, 43.23) |
| 10198 | * "23.35, 43" |
| 10199 | * >>> $.jqplot.sprintf("no value: %n, decimal with thousands separator: %'d", 23.3452, 433524) |
| 10200 | * "no value: , decimal with thousands separator: 433,524" |
| 10201 | */ |
| 10202 | $.jqplot.sprintf = function() { |
| 10203 | function pad(str, len, chr, leftJustify) { |
| 10204 | var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr); |
| 10205 | return leftJustify ? str + padding : padding + str; |
| 10206 | |
| 10207 | } |
| 10208 | |
| 10209 | function thousand_separate(value) { |
| 10210 | var value_str = new String(value); |
| 10211 | for (var i=10; i>0; i--) { |
| 10212 | if (value_str == (value_str = value_str.replace(/^(\d+)(\d{3})/, "$1"+$.jqplot.sprintf.thousandsSeparator+"$2"))) break; |
| 10213 | } |
| 10214 | return value_str; |
| 10215 | } |
| 10216 | |
| 10217 | function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) { |
| 10218 | var diff = minWidth - value.length; |
| 10219 | if (diff > 0) { |
| 10220 | var spchar = ' '; |
| 10221 | if (htmlSpace) { spchar = ' '; } |
| 10222 | if (leftJustify || !zeroPad) { |
| 10223 | value = pad(value, minWidth, spchar, leftJustify); |
| 10224 | } else { |
| 10225 | value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length); |
| 10226 | } |
| 10227 | } |
| 10228 | return value; |
| 10229 | } |
| 10230 | |
| 10231 | function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) { |
| 10232 | // Note: casts negative numbers to positive ones |
| 10233 | var number = value >>> 0; |
| 10234 | prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || ''; |
| 10235 | value = prefix + pad(number.toString(base), precision || 0, '0', false); |
| 10236 | return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); |
| 10237 | } |
| 10238 | |
| 10239 | function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) { |
| 10240 | if (precision != null) { |
| 10241 | value = value.slice(0, precision); |
| 10242 | } |
| 10243 | return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace); |
| 10244 | } |
| 10245 | |
| 10246 | var a = arguments, i = 0, format = a[i++]; |
| 10247 | |
| 10248 | return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) { |
| 10249 | if (substring == '%%') { return '%'; } |
| 10250 | |
| 10251 | // parse flags |
| 10252 | var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false, thousandSeparation = false; |
| 10253 | for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) { |
| 10254 | case ' ': positivePrefix = ' '; break; |
| 10255 | case '+': positivePrefix = '+'; break; |
| 10256 | case '-': leftJustify = true; break; |
| 10257 | case '0': zeroPad = true; break; |
| 10258 | case '#': prefixBaseX = true; break; |
| 10259 | case '&': htmlSpace = true; break; |
| 10260 | case '\'': thousandSeparation = true; break; |
| 10261 | } |
| 10262 | |
| 10263 | // parameters may be null, undefined, empty-string or real valued |
| 10264 | // we want to ignore null, undefined and empty-string values |
| 10265 | |
| 10266 | if (!minWidth) { |
| 10267 | minWidth = 0; |
| 10268 | } |
| 10269 | else if (minWidth == '*') { |
| 10270 | minWidth = +a[i++]; |
| 10271 | } |
| 10272 | else if (minWidth.charAt(0) == '*') { |
| 10273 | minWidth = +a[minWidth.slice(1, -1)]; |
| 10274 | } |
| 10275 | else { |
| 10276 | minWidth = +minWidth; |
| 10277 | } |
| 10278 | |
| 10279 | // Note: undocumented perl feature: |
| 10280 | if (minWidth < 0) { |
| 10281 | minWidth = -minWidth; |
| 10282 | leftJustify = true; |
| 10283 | } |
| 10284 | |
| 10285 | if (!isFinite(minWidth)) { |
| 10286 | throw new Error('$.jqplot.sprintf: (minimum-)width must be finite'); |
| 10287 | } |
| 10288 | |
| 10289 | if (!precision) { |
| 10290 | precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0); |
| 10291 | } |
| 10292 | else if (precision == '*') { |
| 10293 | precision = +a[i++]; |
| 10294 | } |
| 10295 | else if (precision.charAt(0) == '*') { |
| 10296 | precision = +a[precision.slice(1, -1)]; |
| 10297 | } |
| 10298 | else { |
| 10299 | precision = +precision; |
| 10300 | } |
| 10301 | |
| 10302 | // grab value using valueIndex if required? |
| 10303 | var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++]; |
| 10304 | |
| 10305 | switch (type) { |
| 10306 | case 's': { |
| 10307 | if (value == null) { |
| 10308 | return ''; |
| 10309 | } |
| 10310 | return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace); |
| 10311 | } |
| 10312 | case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace); |
| 10313 | case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace); |
| 10314 | case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); |
| 10315 | case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); |
| 10316 | case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase(); |
| 10317 | case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); |
| 10318 | case 'i': { |
| 10319 | var number = parseInt(+value, 10); |
| 10320 | if (isNaN(number)) { |
| 10321 | return ''; |
| 10322 | } |
| 10323 | var prefix = number < 0 ? '-' : positivePrefix; |
| 10324 | var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number)); |
| 10325 | value = prefix + pad(number_str, precision, '0', false); |
| 10326 | //value = prefix + pad(String(Math.abs(number)), precision, '0', false); |
| 10327 | return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); |
| 10328 | } |
| 10329 | case 'd': { |
| 10330 | var number = Math.round(+value); |
| 10331 | if (isNaN(number)) { |
| 10332 | return ''; |
| 10333 | } |
| 10334 | var prefix = number < 0 ? '-' : positivePrefix; |
| 10335 | var number_str = thousandSeparation ? thousand_separate(String(Math.abs(number))): String(Math.abs(number)); |
| 10336 | value = prefix + pad(number_str, precision, '0', false); |
| 10337 | return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); |
| 10338 | } |
| 10339 | case 'e': |
| 10340 | case 'E': |
| 10341 | case 'f': |
| 10342 | case 'F': |
| 10343 | case 'g': |
| 10344 | case 'G': |
| 10345 | { |
| 10346 | var number = +value; |
| 10347 | if (isNaN(number)) { |
| 10348 | return ''; |
| 10349 | } |
| 10350 | var prefix = number < 0 ? '-' : positivePrefix; |
| 10351 | var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())]; |
| 10352 | var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2]; |
| 10353 | var number_str = Math.abs(number)[method](precision); |
| 10354 | number_str = thousandSeparation ? thousand_separate(number_str): number_str; |
| 10355 | value = prefix + number_str; |
| 10356 | return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform](); |
| 10357 | } |
| 10358 | case 'p': |
| 10359 | case 'P': |
| 10360 | { |
| 10361 | // make sure number is a number |
| 10362 | var number = +value; |
| 10363 | if (isNaN(number)) { |
| 10364 | return ''; |
| 10365 | } |
| 10366 | var prefix = number < 0 ? '-' : positivePrefix; |
| 10367 | |
| 10368 | var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/); |
| 10369 | var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length; |
| 10370 | var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0; |
| 10371 | |
| 10372 | if (Math.abs(number) < 1) { |
| 10373 | if (sd + zeros <= precision) { |
| 10374 | value = prefix + Math.abs(number).toPrecision(sd); |
| 10375 | } |
| 10376 | else { |
| 10377 | if (sd <= precision - 1) { |
| 10378 | value = prefix + Math.abs(number).toExponential(sd-1); |
| 10379 | } |
| 10380 | else { |
| 10381 | value = prefix + Math.abs(number).toExponential(precision-1); |
| 10382 | } |
| 10383 | } |
| 10384 | } |
| 10385 | else { |
| 10386 | var prec = (sd <= precision) ? sd : precision; |
| 10387 | value = prefix + Math.abs(number).toPrecision(prec); |
| 10388 | } |
| 10389 | var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2]; |
| 10390 | return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform](); |
| 10391 | } |
| 10392 | case 'n': return ''; |
| 10393 | default: return substring; |
| 10394 | } |
| 10395 | }); |
| 10396 | }; |
| 10397 | |
| 10398 | $.jqplot.sprintf.thousandsSeparator = ','; |
| 10399 | |
| 10400 | $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0&\' ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g; |
| 10401 | |
| 10402 | $.jqplot.getSignificantFigures = function(number) { |
| 10403 | var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/); |
| 10404 | // total significant digits |
| 10405 | var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length; |
| 10406 | var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0; |
| 10407 | // exponent |
| 10408 | var expn = parseInt(parts[1]); |
| 10409 | // digits to the left of the decimal place |
| 10410 | var dleft = (expn + 1 > 0) ? expn + 1 : 0; |
| 10411 | // digits to the right of the decimal place |
| 10412 | var dright = (sd <= dleft) ? 0 : sd - expn - 1; |
| 10413 | return {significantDigits: sd, digitsLeft: dleft, digitsRight: dright, zeros: zeros, exponent: expn} ; |
| 10414 | }; |
| 10415 | |
| 10416 | $.jqplot.getPrecision = function(number) { |
| 10417 | var arr = $.jqplot.getSignificantFigures(number); |
| 10418 | var p = arr[1] - 1 - parseInt(arr[0][1]); |
| 10419 | return p; |
| 10420 | }; |
| 10421 | |
| 10422 | })(jQuery); |