1/** 2 * Title: jqPlot Charts 3 * 4 * Pure JavaScript plotting plugin for jQuery. 5 * 6 * About: Version 7 * 8 * 0.9.7r618 9 * 10 * About: Copyright & License 11 * 12 * Copyright (c) 2009 - 2010 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 * jqPlot includes `date instance methods and printf/sprintf functions by other authors: 27 * 28 * Date instance methods: 29 * 30 * author Ken Snyder (ken d snyder at gmail dot com) 31 * date 2008-09-10 32 * version 2.0.2 (http://kendsnyder.com/sandbox/date/) 33 * license Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/) 34 * 35 * JavaScript printf/sprintf functions: 36 * 37 * version 2007.04.27 38 * author Ash Searle 39 * http://hexmen.com/blog/2007/03/printf-sprintf/ 40 * http://hexmen.com/js/sprintf.js 41 * The author (Ash Searle) has placed this code in the public domain: 42 * "This code is unrestricted: you are free to use it however you like." 43 * 44 * 45 * About: Introduction 46 * 47 * jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.4.1 is included in the distribution. 48 * To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and optionally 49 * the excanvas script for IE support in your web page: 50 * 51 * > <!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]--> 52 * > <script language="javascript" type="text/javascript" src="jquery-1.4.2.min.js"></script> 53 * > <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script> 54 * > <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" /> 55 * 56 * jqPlot can be customized by overriding the defaults of any of the objects which make 57 * up the plot. The general usage of jqplot is: 58 * 59 * > chart = $.jqplot('targetElemId', [dataArray,...], {optionsObject}); 60 * 61 * The options available to jqplot are detailed in <jqPlot Options> in the jqPlotOptions.txt file. 62 * 63 * An actual call to $.jqplot() may look like the 64 * examples below: 65 * 66 * > chart = $.jqplot('chartdiv', [[[1, 2],[3,5.12],[5,13.1],[7,33.6],[9,85.9],[11,219.9]]]); 67 * 68 * or 69 * 70 * > dataArray = [34,12,43,55,77]; 71 * > chart = $.jqplot('targetElemId', [dataArray, ...], {title:'My Plot', axes:{yaxis:{min:20, max:100}}}); 72 * 73 * For more inforrmation, see <jqPlot Usage>. 74 * 75 * About: Usage 76 * 77 * See <jqPlot Usage> 78 * 79 * About: Available Options 80 * 81 * See <jqPlot Options> for a list of options available thorugh the options object (not complete yet!) 82 * 83 * About: Options Usage 84 * 85 * See <Options Tutorial> 86 * 87 * About: Changes 88 * 89 * See <Change Log> 90 * 91 */ 92 93(function($) { 94 // make sure undefined is undefined 95 var undefined; 96 97 /** 98 * Class: $.jqplot 99 * jQuery function called by the user to create a plot. 100 * 101 * Parameters: 102 * target - ID of target element to render the plot into. 103 * data - an array of data series. 104 * options - user defined options object. See the individual classes for available options. 105 * 106 * Properties: 107 * config - object to hold configuration information for jqPlot plot object. 108 * 109 * attributes: 110 * enablePlugins - False to disable plugins by default. Plugins must then be explicitly 111 * enabled in the individual plot options. Default: false. 112 * This property sets the "show" property of certain plugins to true or false. 113 * Only plugins that can be immediately active upon loading are affected. This includes 114 * non-renderer plugins like cursor, dragable, highlighter, and trendline. 115 * defaultHeight - Default height for plots where no css height specification exists. This 116 * is a jqplot wide default. 117 * defaultWidth - Default height for plots where no css height specification exists. This 118 * is a jqplot wide default. 119 */ 120 121 $.jqplot = function(target, data, options) { 122 var _data, _options; 123 124 if (options == null) { 125 if (data instanceof Array) { 126 _data = data; 127 _options = null; 128 } 129 130 else if (data.constructor == Object) { 131 _data = null; 132 _options = data; 133 } 134 } 135 else { 136 _data = data; 137 _options = options; 138 } 139 var plot = new jqPlot(); 140 // remove any error class that may be stuck on target. 141 $('#'+target).removeClass('jqplot-error'); 142 143 if ($.jqplot.config.catchErrors) { 144 try { 145 plot.init(target, _data, _options); 146 plot.draw(); 147 plot.themeEngine.init.call(plot); 148 return plot; 149 } 150 catch(e) { 151 var msg = $.jqplot.config.errorMessage || e.message; 152 $('#'+target).append('<div class="jqplot-error-message">'+msg+'</div>'); 153 $('#'+target).addClass('jqplot-error'); 154 document.getElementById(target).style.background = $.jqplot.config.errorBackgroun
154d; 155 document.getElementById(target).style.border = $.jqplot.config.errorBorder; 156 document.getElementById(target).style.fontFamily = $.jqplot.config.errorFontFamily; 157 document.getElementById(target).style.fontSize = $.jqplot.config.errorFontSize; 158 document.getElementById(target).style.fontStyle = $.jqplot.config.errorFontStyle; 159 document.getElementById(target).style.fontWeight = $.jqplot.config.errorFontWeight; 160 } 161 } 162 else { 163 plot.init(target, _data, _options); 164 plot.draw(); 165 plot.themeEngine.init.call(plot); 166 return plot; 167 } 168 }; 169 170 $.jqplot.debug = 1; 171 $.jqplot.config = { 172 debug:1, 173 enablePlugins:false, 174 defaultHeight:300, 175 defaultWidth:400, 176 UTCAdjust:false, 177 timezoneOffset: new Date(new Date().getTimezoneOffset() * 60000), 178 errorMessage: '', 179 errorBackground: '', 180 errorBorder: '', 181 errorFontFamily: '', 182 errorFontSize: '', 183 errorFontStyle: '', 184 errorFontWeight: '', 185 catchErrors: false, 186 defaultTickFormatString: "%.1f" 187 }; 188 189 $.jqplot.enablePlugins = $.jqplot.config.enablePlugins; 190 191 /** 192 * 193 * Hooks: jqPlot Pugin Hooks 194 * 195 * $.jqplot.preInitHooks - called before initialization. 196 * $.jqplot.postInitHooks - called after initialization. 197 * $.jqplot.preParseOptionsHooks - called before user options are parsed. 198 * $.jqplot.postParseOptionsHooks - called after user options are parsed. 199 * $.jqplot.preDrawHooks - called before plot draw. 200 * $.jqplot.postDrawHooks - called after plot draw. 201 * $.jqplot.preDrawSeriesHooks - called before each series is drawn. 202 * $.jqplot.postDrawSeriesHooks - called after each series is drawn. 203 * $.jqplot.preDrawLegendHooks - called before the legend is drawn. 204 * $.jqplot.addLegendRowHooks - called at the end of legend draw, so plugins 205 * can add rows to the legend table. 206 * $.jqplot.preSeriesInitHooks - called before series is initialized. 207 * $.jqplot.postSeriesInitHooks - called after series is initialized. 208 * $.jqplot.preParseSeriesOptionsHooks - called before series related options 209 * are parsed. 210 * $.jqplot.postParseSeriesOptionsHooks - called after series related options 211 * are parsed. 212 * $.jqplot.eventListenerHooks - called at the end of plot drawing, binds 213 * listeners to the event canvas which lays on top of the grid area. 214 * $.jqplot.preDrawSeriesShadowHooks - called before series shadows are drawn. 215 * $.jqplot.postDrawSeriesShadowHooks - called after series shadows are drawn. 216 * 217 */ 218 219 $.jqplot.preInitHooks = []; 220 $.jqplot.postInitHooks = []; 221 $.jqplot.preParseOptionsHooks = []; 222 $.jqplot.postParseOptionsHooks = []; 223 $.jqplot.preDrawHooks = []; 224 $.jqplot.postDrawHooks = []; 225 $.jqplot.preDrawSeriesHooks = []; 226 $.jqplot.postDrawSeriesHooks = []; 227 $.jqplot.preDrawLegendHooks = []; 228 $.jqplot.addLegendRowHooks = []; 229 $.jqplot.preSeriesInitHooks = []; 230 $.jqplot.postSeriesInitHooks = []; 231 $.jqplot.preParseSeriesOptionsHooks = []; 232 $.jqplot.postParseSeriesOptionsHooks = []; 233 $.jqplot.eventListenerHooks = []; 234 $.jqplot.preDrawSeriesShadowHooks = []; 235 $.jqplot.postDrawSeriesShadowHooks = []; 236 237 // A superclass holding some common properties and methods. 238 $.jqplot.ElemContainer = function() { 239 this._elem; 240 this._plotWidth; 241 this._plotHeight; 242 this._plotDimensions = {height:null, width:null}; 243 }; 244 245 $.jqplot.ElemContainer.prototype.createElement = function(el, offsets, clss, cssopts, attrib) { 246 this._offsets = offsets; 247 var klass = clss || 'jqplot'; 248 var elem = document.createElement(el); 249 this._elem = $(elem); 250 this._elem.addClass(klass); 251 this._elem.css(cssopts); 252 this._elem.attr(attrib); 253 return this._elem; 254 }; 255 256 $.jqplot.ElemContainer.prototype.getWidth = function() { 257 if (this._elem) { 258 return this._elem.outerWidth(true); 259 } 260 else { 261 return null; 262 } 263 }; 264 265 $.jqplot.ElemContainer.prototype.getHeight = function() { 266 if (this._elem) { 267 return this._elem.outerHeight(true); 268 } 269 else { 270 return null; 271 } 272 }; 273 274 $.jqplot.ElemContainer.prototype.getPosition = function() { 275 if (this._elem) { 276 return this._elem.position(); 277 } 278 else { 279 return {top:null, left:null, bottom:null, right:null}; 280 } 281 }; 282 283 $.jqplot.ElemContainer.prototype.getTop = function() { 284 return this.getPosition().top; 285 }; 286 287 $.jqplot.ElemContainer.prototype.getLeft = function() { 288 return this.getPosition().left; 289 }; 290 291 $.jqplot.ElemContainer.prototype.getBottom = function() { 292 return this._elem.css('bottom'); 293 }; 294 295 $.jqplot.ElemContainer.prototype.getRight = function() { 296 return this._elem.css('right'); 297 }; 298 299 300 /** 301 * Class: Axis 302 * An individual axis object. Cannot be instantiated directly, but created 303 * by the Plot oject. Axis properties can be set or overriden by the 304 * options passed in from the user. 305 * 306 */ 307 function Axis(name) { 308 $.jqplot.ElemContainer.call(this); 309 // Group: Properties 310 // 311 // Axes options are specified within an axes object at the top level of the 312 // plot options like so: 313 // > { 314 // > axes: { 315 // > xaxis: {min: 5}, 316 // > yaxis: {min: 2, max: 8, numberTicks:4}, 317 // > x2axis: {pad: 1.5}, 318 // > y2axis: {ticks:[22, 44, 66, 88]} 319 // > } 320 // > } 321 // There are 4 axes, 'xaxis', 'yaxis', 'x2axis', 'y2axis'. Any or all of 322 // which may be specified. 323 this.name = name; 324 this._series = []; 325 // prop: show 326 // Wether to display the axis on the graph. 327 this.show = false; 328 // prop: tickRenderer 329 // A class of a rendering engine for creating the ticks labels displayed on the plot, 330 // See <$.jqplot.AxisTickRenderer>. 331 this.tickRenderer = $.jqplot.AxisTickRenderer; 332 // prop: tickOptions 333 // Options that will be passed to the tickRenderer, see <$.jqplot.AxisTickRenderer> options. 334 this.tickOptions = {}; 335 // prop: labelRenderer 336 // A class of a rendering engine for creating an axis label. 337 this.labelRenderer = $.jqplot.AxisLabelRenderer; 338 // prop: labelOptions 339 // Options passed to the label renderer. 340 this.labelOptions = {}; 341 // prop: label 342 // Label for the axis 343 this.label = null; 344 // prop: showLabel 345 // true to show the axis label. 346 this.showLabel = true; 347 // prop: min 348 // minimum value of the axis (in data units, not pixels). 349 this.min=null; 350 // prop: max 351 // maximum value of the axis (in data units, not pixels). 352 this.max=null; 353 // prop: autoscale 354 // Autoscale the axis min and max values to provide sensible tick spacing. 355 // If axis min or max are set, autoscale will be turned off. 356 // The numberTicks, tickInterval and pad options do work with 357 // autoscale, although tickInterval has not been tested yet. 358 // padMin and padMax do nothing when autoscale is on. 359 this.autoscale = false; 360 // prop: pad 361 // Padding to extend the range above and below the data bounds. 362 // The data range is multiplied by this factor to determine minimum and maximum axis boun
362ds. 363 // A value of 0 will be interpreted to mean no padding, and pad will be set to 1.0. 364 this.pad = 1.2; 365 // prop: padMax 366 // Padding to extend the range above data bounds. 367 // The top of the data range is multiplied by this factor to determine maximum axis bounds. 368 // A value of 0 will be interpreted to mean no padding, and padMax will be set to 1.0. 369 this.padMax = null; 370 // prop: padMin 371 // Padding to extend the range below data bounds. 372 // The bottom of the data range is multiplied by this factor to determine minimum axis bounds. 373 // A value of 0 will be interpreted to mean no padding, and padMin will be set to 1.0. 374 this.padMin = null; 375 // prop: ticks 376 // 1D [val, val, ...] or 2D [[val, label], [val, label], ...] array of ticks for the axis. 377 // If no label is specified, the value is formatted into an appropriate label. 378 this.ticks = []; 379 // prop: numberTicks 380 // Desired number of ticks. Default is to compute automatically. 381 this.numberTicks; 382 // prop: tickInterval 383 // number of units between ticks. Mutually exclusive with numberTicks. 384 this.tickInterval; 385 // prop: renderer 386 // A class of a rendering engine that handles tick generation, 387 // scaling input data to pixel grid units and drawing the axis element. 388 this.renderer = $.jqplot.LinearAxisRenderer; 389 // prop: rendererOptions 390 // renderer specific options. See <$.jqplot.LinearAxisRenderer> for options. 391 this.rendererOptions = {}; 392 // prop: showTicks 393 // wether to show the ticks (both marks and labels) or not. 394 this.showTicks = true; 395 // prop: showTickMarks 396 // wether to show the tick marks (line crossing grid) or not. 397 this.showTickMarks = true; 398 // prop: showMinorTicks 399 // Wether or not to show minor ticks. This is renderer dependent. 400 // The default <$.jqplot.LinearAxisRenderer> does not have minor ticks. 401 this.showMinorTicks = true; 402 // prop: useSeriesColor 403 // Use the color of the first series associated with this axis for the 404 // tick marks and line bordering this axis. 405 this.useSeriesColor = false; 406 // prop: borderWidth 407 // width of line stroked at the border of the axis. Defaults 408 // to the width of the grid boarder. 409 this.borderWidth = null; 410 // prop: borderColor 411 // color of the border adjacent to the axis. Defaults to grid border color. 412 this.borderColor = null; 413 // minimum and maximum values on the axis. 414 this._dataBounds = {min:null, max:null}; 415 // pixel position from the top left of the min value and max value on the axis. 416 this._offsets = {min:null, max:null}; 417 this._ticks=[]; 418 this._label = null; 419 // prop: syncTicks 420 // true to try and synchronize tick spacing across multiple axes so that ticks and 421 // grid lines line up. This has an impact on autoscaling algorithm, however. 422 // In general, autoscaling an individual axis will work better if it does not 423 // have to sync ticks. 424 this.syncTicks = null; 425 // prop: tickSpacing 426 // Approximate pixel spacing between ticks on graph. Used during autoscaling. 427 // This number will be an upper bound, actual spacing will be less. 428 this.tickSpacing = 75; 429 // Properties to hold the original values for min, max, ticks, tickInterval and numberTicks 430 // so they can be restored if altered by plugins. 431 this._min = null; 432 this._max = null; 433 this._tickInterval = null; 434 this._numberTicks = null; 435 this.__ticks = null; 436 } 437 438 Axis.prototype = new $.jqplot.ElemContainer(); 439 Axis.prototype.constructor = Axis; 440 441 Axis.prototype.init = function() { 442 this.renderer = new this.renderer(); 443 // set the axis name 444 this.tickOptions.axis = this.name; 445 if (this.label == null || this.label == '') { 446 this.showLabel = false; 447 } 448 else { 449 this.labelOptions.label = this.label; 450 } 451 if (this.showLabel == false) { 452 this.labelOptions.show = false; 453 } 454 // set the default padMax, padMin if not specified 455 // special check, if no padding desired, padding 456 // should be set to 1.0 457 if (this.pad == 0) { 458 this.pad = 1.0; 459 } 460 if (this.padMax == 0) { 461 this.padMax = 1.0; 462 } 463 if (this.padMin == 0) { 464 this.padMin = 1.0; 465 } 466 if (this.padMax == null) { 467 this.padMax = (this.pad-1)/2 + 1; 468 } 469 if (this.padMin == null) { 470 this.padMin = (this.pad-1)/2 + 1; 471 } 472 // now that padMin and padMax are correctly set, reset pad in case user has supplied 473 // padMin and/or padMax 474 this.pad = this.padMax + this.padMin - 1; 475 if (this.min != null || this.max != null) { 476 this.autoscale = false; 477 } 478 // if not set, sync ticks for y axes but not x by default. 479 if (this.syncTicks == null && this.name.indexOf('y') > -1) { 480 this.syncTicks = true; 481 } 482 else if (this.syncTicks == null){ 483 this.syncTicks = false; 484 } 485 this.renderer.init.call(this, this.rendererOptions); 486 487 }; 488 489 Axis.prototype.draw = function(ctx) { 490 return this.renderer.draw.call(this, ctx); 491 492 }; 493 494 Axis.prototype.set = function() { 495 this.renderer.set.call(this); 496 }; 497 498 Axis.prototype.pack = function(pos, offsets) { 499 if (this.show) { 500 this.renderer.pack.call(this, pos, offsets); 501 } 502 // these properties should all be available now. 503 if (this._min == null) { 504 this._min = this.min; 505 this._max = this.max; 506 this._tickInterval = this.tickInterval; 507 this._numberTicks = this.numberTicks; 508 this.__ticks = this._ticks; 509 } 510 }; 511 512 // reset the axis back to original values if it has been scaled, zoomed, etc. 513 Axis.prototype.reset = function() { 514 this.renderer.reset.call(this); 515 }; 516 517 Axis.prototype.resetScale = function() { 518 this.min = null; 519 this.max = null; 520 this.numberTicks = null; 521 this.tickInterval = null; 522 }; 523 524 /** 525 * Class: Legend 526 * Legend object. Cannot be instantiated directly, but created 527 * by the Plot oject. Legend properties can be set or overriden by the 528 * options passed in from the user. 529 */ 530 function Legend(options) { 531 $.jqplot.ElemContainer.call(this); 532 // Group: Properties 533 534 // prop: show 535 // Wether to display the legend on the graph. 536 this.show = false; 537 // prop: location 538 // Placement of the legend. one of the compass directions: nw, n, ne, e, se, s, sw, w
539 this.location = 'ne'; 540 // prop: labels 541 // Array of labels to use. By default the renderer will look for labels on the series. 542 // Labels specified in this array will override labels specified on the series. 543 this.labels = []; 544 // prop: showLabels 545 // true to show the label text on the legend. 546 this.showLabels = true; 547 // prop: showSwatch 548 // true to show the color swatches on the legend. 549 this.showSwatches = true; 550 // prop: placement 551 // "insideGrid" places legend inside the grid area of the plot. 552 // "outsideGrid" places the legend outside the grid but inside the plot container, 553 // shrinking the grid to accomodate the legend. 554 // "inside" synonym for "insideGrid", 555 // "outside" places the legend ouside the grid area, but does not shrink the grid which 556 // can cause the legend to overflow the plot container. 557 this.placement = "insideGrid"; 558 // prop: xoffset 559 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc. 560 // properties or via CSS margin styling of the .jqplot-table-legend class. 561 this.xoffset = 0; 562 // prop: yoffset 563 // DEPRECATED. Set the margins on the legend using the marginTop, marginLeft, etc. 564 // properties or via CSS margin styling of the .jqplot-table-legend class. 565 this.yoffset = 0; 566 // prop: border 567 // css spec for the border around the legend box. 568 this.border; 569 // prop: background 570 // css spec for the background of the legend box. 571 this.background; 572 // prop: textColor 573 // css color spec for the legend text. 574 this.textColor; 575 // prop: fontFamily 576 // css font-family spec for the legend text. 577 this.fontFamily; 578 // prop: fontSize 579 // css font-size spec for the legend text. 580 this.fontSize ; 581 // prop: rowSpacing 582 // css padding-top spec for the rows in the legend. 583 this.rowSpacing = '0.5em'; 584 // renderer 585 // A class that will create a DOM object for the legend, 586 // see <$.jqplot.TableLegendRenderer>. 587 this.renderer = $.jqplot.TableLegendRenderer; 588 // prop: rendererOptions 589 // renderer specific options passed to the renderer. 590 this.rendererOptions = {}; 591 // prop: predraw 592 // Wether to draw the legend before the series or not. 593 // Used with series specific legend renderers for pie, donut, mekko charts, etc. 594 this.preDraw = false; 595 // prop: marginTop 596 // CSS margin for the legend DOM element. This will set an element 597 // CSS style for the margin which will override any style sheet setting. 598 // The default will be taken from the stylesheet. 599 this.marginTop = null; 600 // prop: marginRight 601 // CSS margin for the legend DOM element. This will set an element 602 // CSS style for the margin which will override any style sheet setting. 603 // The default will be taken from the stylesheet. 604 this.marginRight = null; 605 // prop: marginBottom 606 // CSS margin for the legend DOM element. This will set an element 607 // CSS style for the margin which will override any style sheet setting. 608 // The default will be taken from the stylesheet. 609 this.marginBottom = null; 610 // prop: marginLeft 611 // CSS margin for the legend DOM element. This will set an element 612 // CSS style for the margin which will override any style sheet setting. 613 // The default will be taken from the stylesheet. 614 this.marginLeft = null; 615 616 this.escapeHtml = false; 617 this._series = []; 618 619 $.extend(true, this, options); 620 } 621 622 Legend.prototype = new $.jqplot.ElemContainer(); 623 Legend.prototype.constructor = Legend; 624 625 Legend.prototype.setOptions = function(options) { 626 $.extend(true, this, options); 627 628 // Try to emulate deprecated behaviour 629 // if user has specified xoffset or yoffset, copy these to 630 // the margin properties. 631 632 if (this.placement == 'inside') this.placement = 'insideGrid'; 633 634 if (this.xoffset >0) { 635 if (this.placement == 'insideGrid') { 636 switch (this.location) { 637 case 'nw': 638 case 'w': 639 case 'sw': 640 if (this.marginLeft == null) { 641 this.marginLeft = this.xoffset + 'px'; 642 } 643 this.marginRight = '0px'; 644 break;
645 case 'ne': 646 case 'e': 647 case 'se': 648 default: 649 if (this.marginRight == null) { 650 this.marginRight = this.xoffset + 'px'; 651 } 652 this.marginLeft = '0px'; 653 break; 654 } 655 } 656 else if (this.placement == 'outside') { 657 switch (this.location) { 658 case 'nw': 659 case 'w': 660 case 'sw': 661 if (this.marginRight == null) { 662 this.marginRight = this.xoffset + 'px'; 663 } 664 this.marginLeft = '0px'; 665 break; 666 case 'ne': 667 case 'e': 668 case 'se': 669 default: 670 if (this.marginLeft == null) { 671 this.marginLeft = this.xoffset + 'px'; 672 } 673 this.marginRight = '0px'; 674 break; 675 } 676 } 677 this.xoffset = 0; 678 } 679 680 if (this.yoffset >0) { 681 if (this.placement == 'outside') { 682 switch (this.location) { 683 case 'sw': 684 case 's': 685 case 'se': 686 if (this.marginTop == null) { 687 this.marginTop = this.yoffset + 'px'; 688 } 689 this.marginBottom = '0px'; 690 break; 691 case 'ne': 692 case 'n': 693 case 'nw': 694 default: 695 if (this.marginBottom == null) { 696 this.marginBottom = this.yoffset + 'px'; 697 } 698 this.marginTop = '0px'; 699 break; 700 } 701 } 702 else if (this.placement == 'insideGrid') { 703 switch (this.location) { 704 case 'sw': 705 case 's': 706 case 'se': 707 if (this.marginBottom == null) { 708 this.marginBottom = this.yoffset + 'px'; 709 } 710 this.marginTop = '0px'; 711 break; 712 case 'ne': 713 case 'n': 714 case 'nw': 715 default: 716 if (this.marginTop == null) { 717 this.marginTop = this.yoffset + 'px'; 718 } 719 this.marginBottom = '0px'; 720 break; 721 } 722 } 723 this.yoffset = 0; 724 } 725 726 // TO-DO: 727 // Handle case where offsets are < 0. 728 // 729 }; 730 731 Legend.prototype.init = function() { 732 this.renderer = new this.renderer(); 733 this.renderer.init.call(this, this.rendererOptions); 734 }; 735 736 Legend.prototype.draw = function(offsets) { 737 for (var i=0; i<$.jqplot.preDrawLegendHooks.length; i++){ 738 $.jqplot.preDrawLegendHooks[i].call(this, offsets); 739 } 740 return this.renderer.draw.call(this, offsets); 741 }; 742 743 Legend.prototype.pack = function(offsets) { 744 this.renderer.pack.call(this, offsets); 745 }; 746 747 /** 748 * Class: Title 749 * Plot Title object. Cannot be instantiated directly, but created 750 * by the Plot oject. Title properties can be set or overriden by the 751 * options passed in from the user. 752 * 753 * Parameters: 754 * text - text of the title. 755 */ 756 function Title(text) { 757 $.jqplot.ElemContainer.call(this); 758 // Group: Properties 759 760 // prop: text 761 // text of the title; 762 this.text = text; 763 // prop: show 764 // wether or not to show the title 765 this.show = true; 766 // prop: fontFamily 767 // css font-family spec for the text. 768 this.fontFamily; 769 // prop: fontSize 770 // css font-size spec for the text. 771 this.fontSize ; 772 // prop: textAlign 773 // css text-align spec for the text. 774 this.textAlign; 775 // prop: textColor 776 // css color spec for the text. 777 this.textColor; 778 // prop: renderer 779 // A class for creating a DOM element for the title, 780 // see <$.jqplot.DivTitleRenderer>. 781 this.renderer = $.jqplot.DivTitleRenderer; 782 // prop: rendererOptions 783 // renderer specific options passed to the renderer. 784 this.rendererOptions = {}; 785 } 786 787 Title.prototype = new $.jqplot.ElemContainer(); 788 Title.prototype.constructor = Title; 789 790 Title.prototype.init = function() { 791 this.renderer = new this.renderer(); 792 this.renderer.init.call(this, this.rendererOptions); 793 }; 794 795 Title.prototype.draw = function(width) { 796 return this.renderer.draw.call(this, width); 797 }; 798 799 Title.prototype.pack = function() { 800 this.renderer.pack.call(this); 801 }; 802 803 804 /** 805 * Class: Series 806 * An individual data series object. Cannot be instantiated directly, but created 807 * by the Plot oject. Series properties can be set or overriden by the 808 * options passed in from the user. 809 */ 810 function Series() { 811 $.jqplot.ElemContainer.call(this); 812 // Group: Properties 813 // Properties will be assigned from a series array at the top level of the 814 // options. If you had two series and wanted to change the color and line 815 // width of the first and set the second to use the secondary y axis with 816 // no shadow and supply custom labels for each: 817 // > { 818 // > series:[ 819 // > {color: '#ff4466', lineWidth: 5, label:'good line'}, 820 // > {yaxis: 'y2axis', shadow: false, label:'bad line'} 821 // > ] 822 // > } 823 824 // prop: show 825 // wether or not to draw the series. 826 this.show = true; 827 // prop: xaxis 828 // which x axis to use with this series, either 'xaxis' or 'x2axis'. 829 this.xaxis = 'xaxis'; 830 this._xaxis; 831 // prop: yaxis 832 // which y axis to use with this series, either 'yaxis' or 'y2axis'. 833 this.yaxis = 'yaxis'; 834 this._yaxis; 835 this.gridBorderWidth = 2.0; 836 // prop: renderer 837 // A class of a renderer which will draw the series, 838 // see <$.jqplot.LineRenderer>. 839 this.renderer = $.jqplot.LineRenderer; 840 // prop: rendererOptions 841 // Options to pass on to the renderer. 842 this.rendererOptions = {}; 843 this.data = []; 844 this.gridData = []; 845 // prop: label 846 // Line label to use in the legend. 847 this.label = ''; 848 // prop: showLabel 849 // true to show label for this series in the legend. 850 this.showLabel = true; 851 // prop: color 852 // css color spec for the series 853 this.color; 854 // prop: lineWidth 855 // width of the line in pixels. May have different meanings depending on renderer. 856 this.lineWidth = 2.5; 857 // prop: shadow 858 // wether or not to draw a shadow on the line 859 this.shadow = true; 860 // prop: shadowAngle 861 // Shadow angle in degrees 862 this.shadowAngle = 45; 863 // prop: shadowOffset 864 // Shadow offset from line in pixels 865 this.shadowOffset = 1.25; 866 // prop: shadowDepth 867 // Number of times shadow is stroked, each stroke offset shadowOffset from the last. 868 this.shadowDepth = 3; 869 // prop: shadowAlpha 870 // Alpha channel transparency of shadow. 0 = transparent. 871 this.shadowAlpha = '0.1'; 872 // prop: breakOnNull 873 // Not implemented. wether line segments should be be broken at null value. 874 // False will join point on either side of line. 875 this.breakOnNull = false; 876 // prop: markerRenderer 877 // A class of a renderer which will draw marker (e.g. circle, square, ...) at the data points, 878 // see <$.jqplot.MarkerRenderer>. 879 this.markerRenderer = $.jqplot.MarkerRenderer; 880 // prop: markerOptions 881 // renderer specific options to pass to the markerRenderer, 882 // see <$.jqplot.MarkerRenderer>. 883 this.markerOptions = {}; 884 // prop: showLine 885 // wether to actually draw the line or not. Series will still be renderered, even if no line is drawn. 886 this.showLine = true; 887 // prop: showMarker 888 // wether or not to show the markers at the data points. 889 this.showMarker = true; 890 // prop: index 891 // 0 based index of this series in the plot series array. 892 this.index; 893 // prop: fill 894 // true or false, wether to fill under lines or in bars. 895 // May not be implemented in all renderers. 896 this.fill = false; 897 // prop: fillColor 898 // CSS color spec to use for fill under line. Defaults to line color. 899 this.fillColor; 900 // prop: fillAlpha 901 // Alpha transparency to apply to the fill under the line. 902 // Use this to adjust alpha separate from fill color. 903 this.fillAlpha; 904 // prop: fillAndStroke 905 // If true will stroke the line (with color this.color) as well as fill under it. 906 // Applies only when fill is true. 907 this.fillAndStroke = false; 908 // prop: disableStack 909 // true to not stack this series with other series in the plot. 910 // To render properly, non-stacked series must come after any stacked series 911 // in the plot's data series array. So, the plot's data series array would look like: 912 // > [stackedSeries1, stackedSeries2, ..., nonStackedSeries1, nonStackedSeries2, ...] 913 // disableStack will put a gap in the stacking order of series, and subsequent 914 // stacked series will not fill down through the non-stacked series and will 915 // most likely not stack properly on top of the non-stacked series. 916 this.disableStack = false; 917 // _stack is set by the Plot if the plot is a stacked chart. 918 // will stack lines or bars on top of one another to build a "mountain" style chart. 919 // May not be implemented in all renderers. 920 this._stack = false; 921 // prop: neighborThreshold 922 // how close or far (in pixels) the cursor must be from a point marker to detect the point. 923 this.neighborThreshold = 4; 924 // prop: fillToZero 925 // true will force bar and filled series to fill toward zero on the fill Axis. 926 this.fillToZero = false; 927 // prop: fillToValue 928 // fill a filled series to this value on the fill axis. 929 // Works in conjunction with fillToZero, so that must be true. 930 this.fillToValue = 0; 931 // prop: fillAxis 932 // Either 'x' or 'y'. Which axis to fill the line toward if fillToZero is true. 933 // 'y' means fill up/down to 0 on the y axis for this series.
934 this.fillAxis = 'y'; 935 // prop: useNegativeColors 936 // true to color negative values differently in filled and bar charts. 937 this.useNegativeColors = true; 938 this._stackData = []; 939 // _plotData accounts for stacking. If plots not stacked, _plotData and data are same. If 940 // stacked, _plotData is accumulation of stacking data. 941 this._plotData = []; 942 // _plotValues hold the individual x and y values that will be plotted for this series. 943 this._plotValues = {x:[], y:[]}; 944 // statistics about the intervals between data points. Used for auto scaling. 945 this._intervals = {x:{}, y:{}}; 946 // data from the previous series, for stacked charts. 947 this._prevPlotData = []; 948 this._prevGridData = []; 949 this._stackAxis = 'y'; 950 this._primaryAxis = '_xaxis'; 951 // give each series a canvas to draw on. This should allow for redrawing speedups. 952 this.canvas = new $.jqplot.GenericCanvas(); 953 this.shadowCanvas = new $.jqplot.GenericCanvas(); 954 this.plugins = {}; 955 // sum of y values in this series. 956 this._sumy = 0; 957 this._sumx = 0; 958 } 959 960 Series.prototype = new $.jqplot.ElemContainer(); 961 Series.prototype.constructor = Series; 962 963 Series.prototype.init = function(index, gridbw, plot) { 964 // weed out any null values in the data. 965 this.index = index; 966 this.gridBorderWidth = gridbw; 967 var d = this.data; 968 var temp = [], i; 969 for (i=0; i<d.length; i++) { 970 if (! this.breakOnNull) { 971 if (d[i] == null || d[i][0] == null || d[i][1] == null) { 972 continue; 973 } 974 else { 975 temp.push(d[i]); 976 } 977 } 978 else {
979 // TODO: figure out what to do with null values 980 // probably involve keeping nulls in data array 981 // and then updating renderers to break line 982 // when it hits null value. 983 // For now, just keep value. 984 temp.push(d[i]); 985 } 986 } 987 this.data = temp; 988 if (!this.fillColor) { 989 this.fillColor = this.color; 990 } 991 if (this.fillAlpha) { 992 var comp = $.jqplot.normalize2rgb(this.fillColor); 993 var comp = $.jqplot.getColorComponents(comp); 994 this.fillColor = 'rgba('+comp[0]+','+comp[1]+','+comp[2]+','+this.fillAlpha+')'; 995 } 996 this.renderer = new this.renderer(); 997 this.renderer.init.call(this, this.rendererOptions, plot); 998 this.markerRenderer = new this.markerRenderer(); 999 if (!this.markerOptions.color) { 1000 this.markerOptions.color = this.color; 1001 } 1002 if (this.markerOptions.show == null) { 1003 this.markerOptions.show = this.showMarker; 1004 } 1005 this.showMarker = this.markerOptions.show; 1006 // the markerRenderer is called within it's own scaope, don't want to overwrite series options!! 1007 this.markerRenderer.init(this.markerOptions); 1008 }; 1009 1010 // data - optional data point array to draw using this series renderer 1011 // gridData - optional grid data point array to draw using this series renderer 1012 // stackData - array of cumulative data for stacked plots. 1013 Series.prototype.draw = function(sctx, opts, plot) { 1014 var options = (opts == undefined) ? {} : opts; 1015 sctx = (sctx == undefined) ? this.canvas._ctx : sctx; 1016 // hooks get called even if series not shown 1017 // we don't clear canvas here, it would wipe out all other series as well. 1018 for (var j=0; j<$.jqplot.preDrawSeriesHooks.length; j++) { 1019 $.jqplot.preDrawSeriesHooks[j].call(this, sctx, options); 1020 } 1021 if (this.show) { 1022 this.renderer.setGridData.call(this, plot); 1023 if (!options.preventJqPlotSeriesDrawTrigger) { 1024 $(sctx.canvas).trigger('jqplotSeriesDraw', [this.data, this.gridData]); 1025 } 1026 var data = []; 1027 if (options.data) { 1028 data = options.data; 1029 } 1030 else if (!this._stack) { 1031 data = this.data; 1032 } 1033 else { 1034 data = this._plotData; 1035 } 1036 var gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot); 1037 this.renderer.draw.call(this, sctx, gridData, options, plot); 1038 } 1039 1040 for (var j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) { 1041 $.jqplot.postDrawSeriesHooks[j].call(this, sctx, options); 1042 } 1043 }; 1044 1045 Series.prototype.drawShadow = function(sctx, opts, plot) { 1046 var options = (opts == undefined) ? {} : opts; 1047 sctx = (sctx == undefined) ? this.shadowCanvas._ctx : sctx; 1048 // hooks get called even if series not shown 1049 // we don't clear canvas here, it would wipe out all other series as well. 1050 for (var j=0; j<$.jqplot.preDrawSeriesShadowHooks.length; j++) { 1051 $.jqplot.preDrawSeriesShadowHooks[j].call(this, sctx, options); 1052 } 1053 if (this.shadow) { 1054 this.renderer.setGridData.call(this, plot); 1055 1056 var data = []; 1057 if (options.data) { 1058 data = options.data; 1059 } 1060 else if (!this._stack) { 1061 data = this.data; 1062 } 1063 else { 1064 data = this._plotData; 1065 } 1066 var gridData = options.gridData || this.renderer.makeGridData.call(this, data, plot); 1067 1068 this.renderer.drawShadow.call(this, sctx, gridData, options); 1069 } 1070 1071 for (var j=0; j<$.jqplot.postDrawSeriesShadowHooks.length; j++) { 1072 $.jqplot.postDrawSeriesShadowHooks[j].call(this, sctx, options); 1073 } 1074 1075 }; 1076 1077 // toggles series display on plot, e.g. show/hide series 1078 Series.prototype.toggleDisplay = function(ev) { 1079 var s, speed; 1080 if (ev.data.series) { 1081 s = ev.data.series; 1082 } 1083 else { 1084 s = this; 1085 } 1086 if (ev.data.speed) { 1087 speed = ev.data.speed; 1088 } 1089 if (speed) { 1090 if (s.canvas._elem.is(':hidden')) { 1091 if (s.shadowCanvas._elem) { 1092 s.shadowCanvas._elem.fadeIn(speed); 1093 } 1094 s.canvas._elem.fadeIn(speed); 1095 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeIn(speed); 1096 } 1097 else { 1098 if (s.shadowCanvas._elem) { 1099 s.shadowCanvas._elem.fadeOut(speed); 1100 } 1101 s.canvas._elem.fadeOut(speed); 1102 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).fadeOut(speed); 1103 } 1104 } 1105 else { 1106 if (s.canvas._elem.is(':hidden')) { 1107 if (s.shadowCanvas._elem) { 1108 s.shadowCanvas._elem.show(); 1109 } 1110 s.canvas._elem.show(); 1111 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).show(); 1112 } 1113 else { 1114 if (s.shadowCanvas._elem) { 1115 s.shadowCanvas._elem.hide(); 1116 } 1117 s.canvas._elem.hide(); 1118 s.canvas._elem.nextAll('.jqplot-point-label.jqplot-series-'+s.index).hide(); 1119 } 1120 } 1121 }; 1122 1123 1124 1125 /** 1126 * Class: Grid 1127 * 1128 * Object representing the grid on which the plot is drawn. The grid in this 1129 * context is the area bounded by the axes, the area which will contain the series. 1130 * Note, the series are drawn on their own canvas. 1131 * The Grid object cannot be instantiated directly, but is created by the Plot oject. 1132 * Grid properties can be set or overriden by the options passed in from the user. 1133 */ 1134 function Grid() { 1135 $.jqplot.ElemContainer.call(this); 1136 // Group: Properties 1137 1138 // prop: drawGridlines 1139 // wether to draw the gridlines on the plot.
1140 this.drawGridlines = true; 1141 // prop: gridLineColor 1142 // color of the grid lines. 1143 this.gridLineColor = '#cccccc'; 1144 // prop: gridLineWidth 1145 // width of the grid lines. 1146 this.gridLineWidth = 1.0; 1147 // prop: background 1148 // css spec for the background color. 1149 this.background = '#fffdf6'; 1150 // prop: borderColor 1151 // css spec for the color of the grid border. 1152 this.borderColor = '#999999'; 1153 // prop: borderWidth 1154 // width of the border in pixels. 1155 this.borderWidth = 2.0; 1156 // prop: drawBorder 1157 // True to draw border around grid. 1158 this.drawBorder = true; 1159 // prop: shadow 1160 // wether to show a shadow behind the grid. 1161 this.shadow = true; 1162 // prop: shadowAngle 1163 // shadow angle in degrees 1164 this.shadowAngle = 45; 1165 // prop: shadowOffset 1166 // Offset of each shadow stroke from the border in pixels 1167 this.shadowOffset = 1.5; 1168 // prop: shadowWidth 1169 // width of the stoke for the shadow 1170 this.shadowWidth = 3; 1171 // prop: shadowDepth 1172 // Number of times shadow is stroked, each stroke offset shadowOffset from the last. 1173 this.shadowDepth = 3; 1174 // prop: shadowColor 1175 // an optional css color spec for the shadow in 'rgba(n, n, n, n)' form 1176 this.shadowColor = null; 1177 // prop: shadowAlpha 1178 // Alpha channel transparency of shadow. 0 = transparent. 1179 this.shadowAlpha = '0.07'; 1180 this._left; 1181 this._top; 1182 this._right; 1183 this._bottom; 1184 this._width; 1185 this._height; 1186 this._axes = []; 1187 // prop: renderer 1188 // Instance of a renderer which will actually render the grid, 1189 // see <$.jqplot.CanvasGridRenderer>. 1190 this.renderer = $.jqplot.CanvasGridRenderer; 1191 // prop: rendererOptions 1192 // Options to pass on to the renderer, 1193 // see <$.jqplot.CanvasGridRenderer>. 1194 this.rendererOptions = {}; 1195 this._offsets = {top:null, bottom:null, left:null, right:null}; 1196 } 1197 1198 Grid.prototype = new $.jqplot.ElemContainer(); 1199 Grid.prototype.constructor = Grid; 1200 1201 Grid.prototype.init = function() { 1202 this.renderer = new this.renderer(); 1203 this.renderer.init.call(this, this.rendererOptions); 1204 }; 1205 1206 Grid.prototype.createElement = function(offsets) { 1207 this._offsets = offsets; 1208 return this.renderer.createElement.call(this); 1209 }; 1210 1211 Grid.prototype.draw = function() { 1212 this.renderer.draw.call(this); 1213 }; 1214 1215 $.jqplot.GenericCanvas = function() { 1216 $.jqplot.ElemContainer.call(this); 1217 this._ctx; 1218 }; 1219 1220 $.jqplot.GenericCanvas.prototype = new $.jqplot.ElemContainer(); 1221 $.jqplot.GenericCanvas.prototype.constructor = $.jqplot.GenericCanvas; 1222 1223 $.jqplot.GenericCanvas.prototype.createElement = function(offsets, clss, plotDimensions) { 1224 this._offsets = offsets; 1225 var klass = 'jqplot'; 1226 if (clss != undefined) { 1227 klass = clss; 1228 } 1229 var elem; 1230 // if this canvas already has a dom element, don't make a new one. 1231 if (this._elem) { 1232 elem = this._elem.get(0); 1233 } 1234 else { 1235 elem = document.createElement('canvas'); 1236 } 1237 // if new plotDimensions supplied, use them. 1238 if (plotDimensions != undefined) { 1239 this._plotDimensions = plotDimensions; 1240 } 1241 1242 elem.width = this._plotDimensions.width - this._offsets.left - this._offsets.right; 1243 elem.height = this._plotDimensions.height - this._offsets.top - this._offsets.bottom; 1244 this._elem = $(elem); 1245 this._elem.css({ position: 'absolute', left: this._offsets.left, top: this._offsets.top }); 1246 1247 this._elem.addClass(klass); 1248 if ($.browser.msie) { 1249 window.G_vmlCanvasManager.init_(document); 1250 elem = window.G_vmlCanvasManager.initElement(elem); 1251 } 1252 return this._elem; 1253 }; 1254 1255 $.jqplot.GenericCanvas.prototype.setContext = function() { 1256 this._ctx = this._elem.get(0).getContext("2d"); 1257 return this._ctx; 1258 }; 1259 1260 $.jqplot.HooksManager = function () { 1261 this.hooks =[]; 1262 }; 1263 1264 $.jqplot.HooksManager.prototype.addOnce = function(fn) { 1265 var havehook = false, i; 1266 for (i=0; i<this.hooks.length; i++) { 1267 if (this.hooks[i][0] == fn) { 1268 havehook = true; 1269 } 1270 } 1271 if (!havehook) { 1272 this.hooks.push(fn); 1273 } 1274 }; 1275 1276 $.jqplot.HooksManager.prototype.add = function(fn) { 1277 this.hooks.push(fn); 1278 }; 1279 1280 $.jqplot.EventListenerManager = function () { 1281 this.hooks =[]; 1282 }; 1283 1284 $.jqplot.EventListenerManager.prototype.addOnce = function(ev, fn) { 1285 var havehook = false, h, i;
1286 for (i=0; i<this.hooks.length; i++) { 1287 h = this.hooks[i]; 1288 if (h[0] == ev && h[1] == fn) { 1289 havehook = true; 1290 } 1291 } 1292 if (!havehook) { 1293 this.hooks.push([ev, fn]); 1294 } 1295 }; 1296 1297 $.jqplot.EventListenerManager.prototype.add = function(ev, fn) { 1298 this.hooks.push([ev, fn]); 1299 }; 1300 1301 /** 1302 * Class: jqPlot 1303 * Plot object returned by call to $.jqplot. Handles parsing user options, 1304 * creating sub objects (Axes, legend, title, series) and rendering the plot. 1305 */ 1306 function jqPlot() { 1307 // Group: Properties 1308 // These properties are specified at the top of the options object 1309 // like so: 1310 // > { 1311 // > axesDefaults:{min:0}, 1312 // > series:[{color:'#6633dd'}], 1313 // > title: 'A Plot' 1314 // > } 1315 // 1316 // prop: data 1317 // user's data. Data should *NOT* be specified in the options object, 1318 // but be passed in as the second argument to the $.jqplot() function. 1319 // The data property is described here soley for reference. 1320 // The data should be in the form of an array of 2D or 1D arrays like 1321 // > [ [[x1, y1], [x2, y2],...], [y1, y2, ...] ]. 1322 this.data = []; 1323 // The id of the dom element to render the plot into 1324 this.targetId = null; 1325 // the jquery object for the dom target. 1326 this.target = null; 1327 this.defaults = { 1328 // prop: axesDefaults 1329 // default options that will be applied to all axes. 1330 // see <Axis> for axes options. 1331 axesDefaults: {}, 1332 axes: {xaxis:{}, yaxis:{}, x2axis:{}, y2axis:{}, y3axis:{}, y4axis:{}, y5axis:{}, y6axis:{}, y7axis:{}, y8axis:{}, y9axis:{}}, 1333 // prop: seriesDefaults 1334 // default options that will be applied to all series. 1335 // see <Series> for series options. 1336 seriesDefaults: {}, 1337 gridPadding: {top:10, right:10, bottom:23, left:10}, 1338 series:[] 1339 }; 1340 // prop: series 1341 // Array of series object options. 1342 // see <Series> for series specific options. 1343 this.series = []; 1344 // prop: axes 1345 // up to 4 axes are supported, each with it's own options, 1346 // See <Axis> for axis specific options. 1347 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')}; 1348 // prop: grid 1349 // See <Grid> for grid specific options. 1350 this.grid = new Grid(); 1351 // prop: legend 1352 // see <$.jqplot.TableLegendRenderer> 1353 this.legend = new Legend(); 1354 this.baseCanvas = new $.jqplot.GenericCanvas(); 1355 // array of series indicies. Keep track of order 1356 // which series canvases are displayed, lowest 1357 // to highest, back to front. 1358 this.seriesStack = []; 1359 this.previousSeriesStack = []; 1360 this.eventCanvas = new $.jqplot.GenericCanvas(); 1361 this._width = null; 1362 this._height = null; 1363 this._plotDimensions = {height:null, width:null}; 1364 this._gridPadding = {top:10, right:10, bottom:10, left:10}; 1365 // a shortcut for axis syncTicks options. Not implemented yet. 1366 this.syncXTicks = true; 1367 // a shortcut for axis syncTicks options. Not implemented yet. 1368 this.syncYTicks = true; 1369 // prop: seriesColors 1370 // Ann array of CSS color specifications that will be applied, in order, 1371 // to the series in the plot. Colors will wrap around so, if their 1372 // are more series than colors, colors will be reused starting at the 1373 // beginning. For pie charts, this specifies the colors of the slices. 1374 this.seriesColors = [ "#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"]; 1375 this.negativeSeriesColors = [ "#498991", "#C08840", "#9F9274", "#546D61", "#646C4A", "#6F6621", "#6E3F5F", "#4F64B0", "#A89050", "#C45923", "#187399", "#945381", "#959E5C", "#C7AF7B", "#478396", "#907294"]; 1376 // prop: sortData 1377 // false to not sort the data passed in by the user. 1378 // Many bar, stakced and other graphs as well as many plugins depend on 1379 // having sorted data. 1380 this.sortData = true; 1381 var seriesColorsIndex = 0; 1382 // prop textColor 1383 // css spec for the css color attribute. Default for the entire plot. 1384 this.textColor; 1385 // prop; fontFamily 1386 // css spec for the font-family attribute. Default for the entire plot. 1387 this.fontFamily; 1388 // prop: fontSize 1389 // css spec for the font-size attribute. Default for the entire plot. 1390 this.fontSize; 1391 // prop: title 1392 // Title object. See <Title> for specific options. As a shortcut, you 1393 // can specify the title option as just a string like: title: 'My Plot' 1394 // and this will create a new title object with the specified text. 1395 this.title = new Title(); 1396 // container to hold all of the merged options. Convienence for plugins. 1397 this.options = {}; 1398 // prop: stackSeries 1399 // true or false, creates a stack or "mountain" plot. 1400 // Not all series renderers may implement this option. 1401 this.stackSeries = false; 1402 // prop: defaultAxisStart
1403 // 1-D data series are internally converted into 2-D [x,y] data point arrays 1404 // by jqPlot. This is the default starting value for the missing x or y value. 1405 // The added data will be a monotonically increasing series (e.g. [1, 2, 3, ...]) 1406 // starting at this value. 1407 this.defaultAxisStart = 1; 1408 // array to hold the cumulative stacked series data. 1409 // used to ajust the individual series data, which won't have access to other 1410 // series data. 1411 this._stackData = []; 1412 // array that holds the data to be plotted. This will be the series data 1413 // merged with the the appropriate data from _stackData according to the stackAxis. 1414 this._plotData = []; 1415 // Namespece to hold plugins. Generally non-renderer plugins add themselves to here. 1416 this.plugins = {}; 1417 // Count how many times the draw method has been called while the plot is visible. 1418 // Mostly used to test if plot has never been dran (=0), has been successfully drawn 1419 // into a visible container once (=1) or draw more than once into a visible container. 1420 // Can use this in tests to see if plot has been visibly drawn at least one time. 1421 // After plot has been visibly drawn once, it generally doesn't need redrawn if its 1422 // container is hidden and shown. 1423 this._drawCount = 0; 1424 // this.doCustomEventBinding = true; 1425 // prop: drawIfHidden 1426 // True to execute the draw method even if the plot target is hidden. 1427 // Generally, this should be false. Most plot elements will not be sized/ 1428 // positioned correclty if renderered into a hidden container. To render into 1429 // a hidden container, call the replot method when the container is shown. 1430 this.drawIfHidden = false; 1431 // true to intercept right click events and fire a 'jqplotRightClick' event. 1432 // this will also block the context menu. 1433 this.captureRightClick = false; 1434 this.themeEngine = new $.jqplot.ThemeEngine(); 1435 // sum of y values for all series in plot. 1436 // used in mekko chart. 1437 this._sumy = 0; 1438 this._sumx = 0; 1439 this.preInitHooks = new $.jqplot.HooksManager(); 1440 this.postInitHooks = new $.jqplot.HooksManager(); 1441 this.preParseOptionsHooks = new $.jqplot.HooksManager(); 1442 this.postParseOptionsHooks = new $.jqplot.HooksManager(); 1443 this.preDrawHooks = new $.jqplot.HooksManager(); 1444 this.postDrawHooks = new $.jqplot.HooksManager(); 1445 this.preDrawSeriesHooks = new $.jqplot.HooksManager(); 1446 this.postDrawSeriesHooks = new $.jqplot.HooksManager(); 1447 this.preDrawLegendHooks = new $.jqplot.HooksManager(); 1448 this.addLegendRowHooks = new $.jqplot.HooksManager(); 1449 this.preSeriesInitHooks = new $.jqplot.HooksManager(); 1450 this.postSeriesInitHooks = new $.jqplot.HooksManager(); 1451 this.preParseSeriesOptionsHooks = new $.jqplot.HooksManager(); 1452 this.postParseSeriesOptionsHooks = new $.jqplot.HooksManager(); 1453 this.eventListenerHooks = new $.jqplot.EventListenerManager(); 1454 this.preDrawSeriesShadowHooks = new $.jqplot.HooksManager(); 1455 this.postDrawSeriesShadowHooks = new $.jqplot.HooksManager(); 1456 1457 this.colorGenerator = $.jqplot.ColorGenerator; 1458 1459 // Group: methods 1460 // 1461 // method: init 1462 // sets the plot target, checks data and applies user 1463 // options to plot. 1464 this.init = function(target, data, options) { 1465 for (var i=0; i<$.jqplot.preInitHooks.length; i++) { 1466 $.jqplot.preInitHooks[i].call(this, target, data, options); 1467 } 1468 1469 for (var i=0; i<this.preInitHooks.hooks.length; i++) { 1470 this.preInitHooks.hooks[i].call(this, target, data, options); 1471 } 1472 1473 this.targetId = '#'+target; 1474 this.target = $('#'+target); 1475 // remove any error class that may be stuck on target. 1476 this.target.removeClass('jqplot-error'); 1477 if (!this.target.get(0)) { 1478 throw "No plot target specified"; 1479 } 1480 1481 // make sure the target is positioned by some means and set css 1482 if (this.target.css('position') == 'static') { 1483 this.target.css('position', 'relative'); 1484 } 1485 if (!this.target.hasClass('jqplot-target')) { 1486 this.target.addClass('jqplot-target'); 1487 } 1488 1489 // if no height or width specified, use a default. 1490 if (!this.target.height()) { 1491 var h; 1492 if (options && options.height) { 1493 h = parseInt(options.height, 10); 1494 } 1495 else if (this.target.attr('data-height')) { 1496 h = parseInt(this.target.attr('data-height'), 10); 1497 } 1498 else { 1499 h = parseInt($.jqplot.config.defaultHeight, 10); 1500 } 1501 this._height = h; 1502 this.target.css('height', h+'px'); 1503 } 1504 else { 1505 this._height = this.target.height(); 1506 } 1507 if (!this.target.width()) { 1508 var w; 1509 if (options && options.width) { 1510 w = parseInt(options.width, 10); 1511 } 1512 else if (this.target.attr('data-width')) { 1513 w = parseInt(this.target.attr('data-width'), 10); 1514 } 1515 else { 1516 w = parseInt($.jqplot.config.defaultWidth, 10); 1517 } 1518 this._width = w; 1519 this.target.css('width', w+'px'); 1520 } 1521 else { 1522 this._width = this.target.width(); 1523 } 1524 1525 this._plotDimensions.height = this._height; 1526 this._plotDimensions.width = this._width; 1527 this.grid._plotDimensions = this._plotDimensions; 1528 this.title._plotDimensions = this._plotDimensions; 1529 this.baseCanvas._plotDimensions = this._plotDimensions; 1530 this.eventCanvas._plotDimensions = this._plotDimensions; 1531 this.legend._plotDimensions = this._plotDimensions; 1532 if (this._height <=0 || this._width <=0 || !this._height || !this._width) { 1533 throw "Canvas dimension not set"; 1534 } 1535 1536 if (data == null) { 1537 throw{ 1538 name: "DataError", 1539 message: "No data to plot." 1540 }; 1541 } 1542 if (data.constructor != Array || data.length == 0 || data[0].constructor != Array || data[0].length == 0) { 1543 throw{ 1544 name: "DataError", 1545 message: "No data to plot." 1546 }; 1547 } 1548 1549 this.data = data; 1550 1551 this.parseOptions(options); 1552 1553 if (this.textColor) { 1554 this.target.css('color', this.textColor); 1555 } 1556 if (this.fontFamily) { 1557 this.target.css('font-family', this.fontFamily); 1558 } 1559 if (this.fontSize) { 1560 this.target.css('font-size', this.fontSize); 1561 } 1562
1563 this.title.init(); 1564 this.legend.init(); 1565 this._sumy = 0; 1566 this._sumx = 0; 1567 for (var i=0; i<this.series.length; i++) { 1568 // set default stacking order for series canvases 1569 this.seriesStack.push(i); 1570 this.previousSeriesStack.push(i); 1571 this.series[i].shadowCanvas._plotDimensions = this._plotDimensions; 1572 this.series[i].canvas._plotDimensions = this._plotDimensions; 1573 for (var j=0; j<$.jqplot.preSeriesInitHooks.length; j++) { 1574 $.jqplot.preSeriesInitHooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); 1575 } 1576 for (var j=0; j<this.preSeriesInitHooks.hooks.length; j++) { 1577 this.preSeriesInitHooks.hooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); 1578 } 1579 this.populatePlotData(this.series[i], i); 1580 this.series[i]._plotDimensions = this._plotDimensions; 1581 this.series[i].init(i, this.grid.borderWidth, this); 1582 for (var j=0; j<$.jqplot.postSeriesInitHooks.length; j++) { 1583 $.jqplot.postSeriesInitHooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); 1584 } 1585 for (var j=0; j<this.postSeriesInitHooks.hooks.length; j++) { 1586 this.postSeriesInitHooks.hooks[j].call(this.series[i], target, data, this.options.seriesDefaults, this.options.series[i], this); 1587 } 1588 this._sumy += this.series[i]._sumy; 1589 this._sumx += this.series[i]._sumx; 1590 } 1591 1592 for (var name in this.axes) { 1593 this.axes[name]._plotDimensions = this._plotDimensions; 1594 this.axes[name].init(); 1595 } 1596 1597 if (this.sortData) { 1598 sortData(this.series); 1599 } 1600 this.grid.init(); 1601 this.grid._axes = this.axes; 1602 1603 this.legend._series = this.series; 1604 1605 for (var i=0; i<$.jqplot.postInitHooks.length; i++) { 1606 $.jqplot.postInitHooks[i].call(this, target, data, options); 1607 } 1608 1609 for (var i=0; i<this.postInitHooks.hooks.length; i++) { 1610 this.postInitHooks.hooks[i].call(this, target, data, options); 1611 } 1612 }; 1613 1614 // method: resetAxesScale 1615 // Reset the specified axes min, max, numberTicks and tickInterval properties to null 1616 // or reset these properties on all axes if no list of axes is provided. 1617 // 1618 // Parameters: 1619 // axes - Boolean to reset or not reset all axes or an array or object of axis names to reset. 1620 this.resetAxesScale = function(axes) { 1621 var ax = (axes != undefined) ? axes : this.axes; 1622 if (ax === true) { 1623 ax = this.axes; 1624 } 1625 if (ax.constructor === Array) { 1626 for (var i = 0; i < ax.length; i++) { 1627 this.axes[ax[i]].resetScale(); 1628 } 1629 } 1630 else if (ax.constructor === Object) { 1631 for (var name in ax) { 1632 this.axes[name].resetScale(); 1633 } 1634 } 1635 }; 1636 // method: reInitialize 1637 // reinitialize plot for replotting. 1638 // not called directly. 1639 this.reInitialize = function () { 1640 // Plot should be visible and have a height and width. 1641 // If plot doesn't have height and width for some 1642 // reason, set it by other means. Plot must not have 1643 // a display:none attribute, however. 1644 if (!this.target.height()) { 1645 var h; 1646 if (options && options.height) { 1647 h = parseInt(options.height, 10); 1648 } 1649 else if (this.target.attr('data-height')) { 1650 h = parseInt(this.target.attr('data-height'), 10); 1651 } 1652 else { 1653 h = parseInt($.jqplot.config.defaultHeight, 10); 1654 } 1655 this._height = h; 1656 this.target.css('height', h+'px'); 1657 } 1658 else { 1659 this._height = this.target.height(); 1660 } 1661 if (!this.target.width()) { 1662 var w; 1663 if (options && options.width) { 1664 w = parseInt(options.width, 10); 1665 } 1666 else if (this.target.attr('data-width')) { 1667 w = parseInt(this.target.attr('data-width'), 10); 1668 } 1669 else { 1670 w = parseInt($.jqplot.config.defaultWidth, 10); 1671 } 1672 this._width = w; 1673 this.target.css('width', w+'px'); 1674 } 1675 else { 1676 this._width = this.target.width(); 1677 } 1678 1679 if (this._height <=0 || this._width <=0 || !this._height || !this._width) { 1680 throw "Target dimension not set"; 1681 } 1682 1683 this._plotDimensions.height = this._height; 1684 this._plotDimensions.width = this._width; 1685 this.grid._plotDimensions = this._plotDimensions; 1686 this.title._plotDimensions = this._plotDimensions; 1687 this.baseCanvas._plotDimensions = this._plotDimensions; 1688 this.eventCanvas._plotDimensions = this._plotDimensions; 1689 this.legend._plotDimensions = this._plotDimensions; 1690 1691 for (var n in this.axes) { 1692 this.axes[n]._plotWidth = this._width; 1693 this.axes[n]._plotHeight = this._height; 1694 } 1695 1696 this.title._plotWidth = this._width; 1697 1698 if (this.textColor) { 1699 this.target.css('color', this.textColor); 1700 } 1701 if (this.fontFamily) { 1702 this.target.css('font-family', this.fontFamily); 1703 } 1704 if (this.fontSize) { 1705 this.target.css('font-size', this.fontSize); 1706 } 1707 1708 this._sumy = 0; 1709 this._sumx = 0; 1710 for (var i=0; i<this.series.length; i++) { 1711 this.populatePlotData(this.series[i], i); 1712 this.series[i]._plotDimensions = this._plotDimensions; 1713 this.series[i].canvas._plotDimensions = this._plotDimensions; 1714 //this.series[i].init(i, this.grid.borderWidth);
1715 this._sumy += this.series[i]._sumy; 1716 this._sumx += this.series[i]._sumx; 1717 } 1718 1719 for (var name in this.axes) { 1720 this.axes[name]._plotDimensions = this._plotDimensions; 1721 this.axes[name]._ticks = []; 1722 this.axes[name].renderer.init.call(this.axes[name], {}); 1723 } 1724 1725 if (this.sortData) { 1726 sortData(this.series); 1727 } 1728 1729 this.grid._axes = this.axes; 1730 1731 this.legend._series = this.series; 1732 }; 1733 1734 // sort the series data in increasing order. 1735 function sortData(series) { 1736 var d, sd, pd, ppd, ret; 1737 for (var i=0; i<series.length; i++) { 1738 // d = series[i].data; 1739 // sd = series[i]._stackData; 1740 // pd = series[i]._plotData; 1741 // ppd = series[i]._prevPlotData; 1742 var check; 1743 var bat = [series[i].data, series[i]._stackData, series[i]._plotData, series[i]._prevPlotData]; 1744 for (var n=0; n<4; n++) { 1745 check = true; 1746 d = bat[n]; 1747 if (series[i]._stackAxis == 'x') { 1748 for (var j = 0; j < d.length; j++) { 1749 if (typeof(d[j][1]) != "number") { 1750 check = false; 1751 break; 1752 } 1753 } 1754 if (check) { 1755 d.sort(function(a,b) { return a[1] - b[1]; }); 1756 // sd.sort(function(a,b) { return a[1] - b[1]; }); 1757 // pd.sort(function(a,b) { return a[1] - b[1]; }); 1758 // ppd.sort(function(a,b) { return a[1] - b[1]; }); 1759 } 1760 } 1761 else { 1762 for (var j = 0; j < d.length; j++) { 1763 if (typeof(d[j][0]) != "number") { 1764 check = false; 1765 break; 1766 } 1767 } 1768 if (check) { 1769 d.sort(function(a,b) { return a[0] - b[0]; }); 1770 // sd.sort(function(a,b) { return a[0] - b[0]; }); 1771 // pd.sort(function(a,b) { return a[0] - b[0]; }); 1772 // ppd.sort(function(a,b) { return a[0] - b[0]; }); 1773 } 1774 } 1775 } 1776 1777 } 1778 } 1779 1780 // populate the _stackData and _plotData arrays for the plot and the series. 1781 this.populatePlotData = function(series, index) { 1782 // if a stacked chart, compute the stacked data 1783 this._plotData = []; 1784 this._stackData = []; 1785 series._stackData = []; 1786 series._plotData = []; 1787 var plotValues = {x:[], y:[]}; 1788 if (this.stackSeries && !series.disableStack) { 1789 series._stack = true; 1790 var sidx = series._stackAxis == 'x' ? 0 : 1; 1791 var idx = sidx ? 0 : 1; 1792 // push the current data into stackData 1793 //this._stackData.push(this.series[i].data); 1794 var temp = $.extend(true, [], series.data); 1795 // create the data that will be plotted for this series 1796 var plotdata = $.extend(true, [], series.data); 1797 // for first series, nothing to add to stackData. 1798 for (var j=0; j<index; j++) { 1799 var cd = this.series[j].data; 1800 for (var k=0; k<cd.length; k++) { 1801 temp[k][0] += cd[k][0]; 1802 temp[k][1] += cd[k][1]; 1803 // only need to sum up the stack axis column of data 1804 plotdata[k][sidx] += cd[k][sidx]; 1805 } 1806 } 1807 for (var i=0; i<plotdata.length; i++) { 1808 plotValues.x.push(plotdata[i][0]); 1809 plotValues.y.push(plotdata[i][1]); 1810 } 1811 this._plotData.push(plotdata); 1812 this._stackData.push(temp); 1813 series._stackData = temp; 1814 series._plotData = plotdata; 1815 series._plotValues = plotValues; 1816 } 1817 else { 1818 for (var i=0; i<series.data.length; i++) { 1819 plotValues.x.push(series.data[i][0]); 1820 plotValues.y.push(series.data[i][1]); 1821 } 1822 this._stackData.push(series.data); 1823 this.series[index]._stackData = series.data; 1824 this._plotData.push(series.data); 1825 series._plotData = series.data; 1826 series._plotValues = plotValues; 1827 } 1828 if (index>0) { 1829 series._prevPlotData = this.series[index-1]._plotData; 1830 } 1831 series._sumy = 0; 1832 series._sumx = 0; 1833 for (i=series.data.length-1; i>-1; i--) { 1834 series._sumy += series.data[i][1]; 1835 series._sumx += series.data[i][0]; 1836 } 1837 }; 1838 1839 // function to safely return colors from the color array and wrap around at the end. 1840 this.getNextSeriesColor = (function(t) { 1841 var idx = 0; 1842 var sc = t.seriesColors; 1843 1844 return function () { 1845 if (idx < sc.length) { 1846 return sc[idx++]; 1847 } 1848 else { 1849 idx = 0; 1850 return sc[idx++]; 1851 } 1852 }; 1853 })(this); 1854 1855 this.parseOptions = function(options){ 1856 for (var i=0; i<this.preParseOptionsHooks.hooks.length; i++) { 1857 this.preParseOptionsHooks.hooks[i].call(this, options); 1858 } 1859 for (var i=0; i<$.jqplot.preParseOptionsHooks.length; i++) { 1860 $.jqplot.preParseOptionsHooks[i].call(this, options); 1861 } 1862 this.options = $.extend(true, {}, this.defaults, options); 1863 this.stackSeries = this.options.stackSeries; 1864 if (this.options.seriesColors) { 1865 this.seriesColors = this.options.seriesColors; 1866 } 1867 if (this.options.negativeSeriesColors) { 1868 this.negativeSeriesColors = this.options.negativeSeriesColors; 1869 } 1870 if (this.options.captureRightClick) { 1871 this.captureRightClick = this.options.captureRightClick; 1872 } 1873 this.defaultAxisStart = (options && options.defaultAxisStart != null) ? options.defaultAxisStart : this.defaultAxisStart; 1874 var cg = new this.colorGenerator(this.seriesColors); 1875 // this._gridPadding = this.options.gridPadding; 1876 $.extend(true, this._gridPadding, this.options.gridPadding); 1877 this.sortData = (this.options.sortData != null) ? this.options.sortData : this.sortData; 1878 for (var n in this.axes) { 1879 var axis = this.axes[n]; 1880 $.extend(true, axis, this.options.axesDefaults, this.options.axes[n]); 1881 axis._plotWidth = this._width; 1882 axis._plotHeight = this._height; 1883 } 1884 if (this.data.length == 0) { 1885 this.data = []; 1886 for (var i=0; i<this.options.series.length; i++) { 1887 this.data.push(this.options.series.data); 1888 } 1889 } 1890 1891 var normalizeData = function(data, dir, start) { 1892 // return data as an array of point arrays, 1893 // in form [[x1,y1...], [x2,y2...], ...] 1894 var temp = []; 1895 var i; 1896 dir = dir || 'vertical'; 1897 if (!(data[0] instanceof Array)) { 1898 // we have a series of scalars. One line with just y values. 1899 // turn the scalar list of data into a data array of form: 1900 // [[1, data[0]], [2, data[1]], ...] 1901 for (i=0; i<data.length; i++) { 1902 if (dir == 'vertical') { 1903 temp.push([start + i, data[i]]); 1904 } 1905 else { 1906 temp.push([data[i], start+i]); 1907 } 1908 } 1909 } 1910 else { 1911 // we have a properly formatted data series, copy it. 1912 $.extend(true, temp, data); 1913 } 1914 return temp; 1915 }; 1916 1917 for (var i=0; i<this.data.length; i++) { 1918 var temp = new Series(); 1919 for (var j=0; j<$.jqplot.preParseSeriesOptionsHooks.length; j++) { 1920 $.jqplot.preParseSeriesOptionsHooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]); 1921 } 1922 for (var j=0; j<this.preParseSeriesOptionsHooks.hooks.length; j++) { 1923 this.preParseSeriesOptionsHooks.hooks[j].call(temp, this.options.seriesDefaults, this.options.series[i]); 1924 } 1925 $.extend(true, temp, {seriesColors:this.seriesColors, negativeSeriesColors:this.negativeSeriesColors}, this.options.seriesDefaults, this.options.series[i]); 1926 var dir = 'vertical'; 1927 if (temp.renderer.constructor == $.jqplot.barRenderer && temp.rendererOptions && temp.rendererOptions.barDirection == 'horizontal') { 1928 dir = 'horizontal'; 1929 } 1930 temp.data = normalizeData(this.data[i], dir, this.defaultAxisStart); 1931 switch (temp.xaxis) { 1932 case 'xaxis': 1933 temp._xaxis = this.axes.xaxis; 1934 break; 1935 case 'x2axis': 1936 temp._xaxis = this.axes.x2axis; 1937 break; 1938 default: 1939 break; 1940 } 1941 temp._yaxis = this.axes[temp.yaxis]; 1942 temp._xaxis._series.push(temp); 1943 temp._yaxis._series.push(temp); 1944 if (temp.show) { 1945 temp._xaxis.show = true; 1946 temp._yaxis.show = true; 1947 } 1948 1949 // parse the renderer options and apply default colors if not provided 1950 if (!temp.color && temp.show != false) { 1951 temp.color = cg.next(); 1952 } 1953 if (!temp.label) { 1954 temp.label = 'Series '+ (i+1).toString(); 1955 } 1956 // temp.rendererOptions.show = temp.show; 1957 // $.extend(true, temp.renderer, {color:this.seriesColors[i]}, this.rendererOptions); 1958 this.series.push(temp); 1959 for (var j=0; j<$.jqplot.postParseSeriesOptionsHooks.length; j++) { 1960 $.jqplot.postParseSeriesOptionsHooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]); 1961 } 1962 for (var j=0; j<this.postParseSeriesOptionsHooks.hooks.length; j++) { 1963 this.postParseSeriesOptionsHooks.hooks[j].call(this.series[i], this.options.seriesDefaults, this.options.series[i]); 1964 } 1965 } 1966 1967 // copy the grid and title options into this object. 1968 $.extend(true, this.grid, this.options.grid); 1969 // if axis border properties aren't set, set default. 1970 for (var n in this.axes) { 1971 var axis = this.axes[n]; 1972 if (axis.borderWidth == null) { 1973 axis.borderWidth =this.grid.borderWidth; 1974 } 1975 if (axis.borderColor == null) { 1976 if (n != 'xaxis' && n != 'x2axis' && axis.useSeriesColor === true && axis.show) {
1977 axis.borderColor = axis._series[0].color; 1978 } 1979 else { 1980 axis.borderColor = this.grid.borderColor; 1981 } 1982 } 1983 } 1984 1985 if (typeof this.options.title == 'string') { 1986 this.title.text = this.options.title; 1987 } 1988 else if (typeof this.options.title == 'object') { 1989 $.extend(true, this.title, this.options.title); 1990 } 1991 this.title._plotWidth = this._width; 1992 this.legend.setOptions(this.options.legend); 1993 1994 for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) { 1995 $.jqplot.postParseOptionsHooks[i].call(this, options); 1996 } 1997 for (var i=0; i<this.postParseOptionsHooks.hooks.length; i++) { 1998 this.postParseOptionsHooks.hooks[i].call(this, options); 1999 } 2000 }; 2001 2002 // method: replot 2003 // Does a reinitialization of the plot followed by 2004 // a redraw. Method could be used to interactively 2005 // change plot characteristics and then replot. 2006 // 2007 // Parameters: 2008 // options - Options used for replotting. 2009 // 2010 // Properties: 2011 // clear - false to not clear (empty) the plot container before replotting (default: true). 2012 // resetAxes - true to reset all axes min, max, numberTicks and tickInterval setting so axes will rescale themselves. 2013 // optionally pass in list of axes to reset (e.g. ['xaxis', 'y2axis']) (default: false). 2014 this.replot = function(options) { 2015 var opts = (options != undefined) ? options : {}; 2016 var clear = (opts.clear != undefined) ? opts.clear : true; 2017 var resetAxes = (opts.resetAxes != undefined) ? opts.resetAxes : false; 2018 this.target.trigger('jqplotPreReplot'); 2019 if (clear) { 2020 this.target.empty(); 2021 } 2022 if (resetAxes) { 2023 this.resetAxesScale(resetAxes); 2024 } 2025 this.reInitialize(); 2026 this.draw(); 2027 this.target.trigger('jqplotPostReplot'); 2028 }; 2029 2030 // method: redraw 2031 // Empties the plot target div and redraws the plot. 2032 // This enables plot data and properties to be changed 2033 // and then to comletely clear the plot and redraw. 2034 // redraw *will not* reinitialize any plot elements. 2035 // That is, axes will not be autoscaled and defaults 2036 // will not be reapplied to any plot elements. redraw 2037 // is used primarily with zooming. 2038 // 2039 // Parameters: 2040 // clear - false to not clear (empty) the plot container before redrawing (default: true). 2041 this.redraw = function(clear) { 2042 clear = (clear != null) ? clear : true; 2043 this.target.trigger('jqplotPreRedraw'); 2044 if (clear) { 2045 this.target.empty(); 2046 } 2047 for (var ax in this.axes) { 2048 this.axes[ax]._ticks = []; 2049 } 2050 for (var i=0; i<this.series.length; i++) { 2051 this.populatePlotData(this.series[i], i); 2052 } 2053 this._sumy = 0; 2054 this._sumx = 0; 2055 for (i=0; i<this.series.length; i++) { 2056 this._sumy += this.series[i]._sumy; 2057 this._sumx += this.series[i]._sumx; 2058 } 2059 this.draw(); 2060 this.target.trigger('jqplotPostRedraw'); 2061 }; 2062 2063 // method: draw 2064 // Draws all elements of the plot into the container. 2065 // Does not clear the container before drawing. 2066 this.draw = function(){ 2067 if (this.drawIfHidden || this.target.is(':visible')) { 2068 this.target.trigger('jqplotPreDraw'); 2069 var i, j; 2070 for (i=0; i<$.jqplot.preDrawHooks.length; i++) { 2071 $.jqplot.preDrawHooks[i].call(this); 2072 } 2073 for (i=0; i<this.preDrawHooks.hooks.length; i++) { 2074 this.preDrawHooks.hooks[i].call(this); 2075 } 2076 // create an underlying canvas to be used for special features. 2077 this.target.append(this.baseCanvas.createElement({left:0, right:0, top:0, bottom:0}, 'jqplot-base-canvas')); 2078 this.baseCanvas.setContext(); 2079 this.target.append(this.title.draw());
2080 this.title.pack({top:0, left:0}); 2081 2082 // make room for the legend between the grid and the edge. 2083 var legendElem = this.legend.draw(); 2084 2085 var gridPadding = {top:0, left:0, bottom:0, right:0}; 2086 2087 if (this.legend.placement == "outsideGrid") { 2088 // temporarily append the legend to get dimensions 2089 this.target.append(legendElem); 2090 switch (this.legend.location) { 2091 case 'n': 2092 gridPadding.top += this.legend.getHeight(); 2093 break; 2094 case 's': 2095 gridPadding.bottom += this.legend.getHeight(); 2096 break; 2097 case 'ne': 2098 case 'e': 2099 case 'se': 2100 gridPadding.right += this.legend.getWidth(); 2101 break; 2102 case 'nw': 2103 case 'w': 2104 case 'sw': 2105 gridPadding.left += this.legend.getWidth(); 2106 break; 2107 default: // same as 'ne' 2108 gridPadding.right += this.legend.getWidth(); 2109 break; 2110 } 2111 legendElem = legendElem.detach(); 2112 } 2113 2114 var ax = this.axes; 2115 for (var name in ax) { 2116 this.target.append(ax[name].draw(this.baseCanvas._ctx)); 2117 ax[name].set(); 2118 } 2119 if (ax.yaxis.show) { 2120 gridPadding.left += ax.yaxis.getWidth(); 2121 } 2122 var ra = ['y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; 2123 var rapad = [0, 0, 0, 0, 0, 0, 0, 0]; 2124 var gpr = 0; 2125 var n; 2126 for (n=0; n<8; n++) { 2127 if (ax[ra[n]].show) { 2128 gpr += ax[ra[n]].getWidth(); 2129 rapad[n] = gpr; 2130 } 2131 } 2132 gridPadding.right += gpr; 2133 if (ax.x2axis.show) { 2134 gridPadding.top += ax.x2axis.getHeight(); 2135 } 2136 if (this.title.show) { 2137 gridPadding.top += this.title.getHeight(); 2138 } 2139 if (ax.xaxis.show) { 2140 gridPadding.bottom += ax.xaxis.getHeight(); 2141 } 2142 2143 // end of gridPadding adjustments. 2144 var arr = ['top', 'bottom', 'left', 'right']; 2145 for (var n in arr) { 2146 if (gridPadding[arr[n]]) { 2147 this._gridPadding[arr[n]] = gridPadding[arr[n]]; 2148 } 2149 } 2150 2151 var legendPadding = (this.legend.placement == 'outsideGrid') ? {top:this.title.getHeight(), left: 0, right: 0, bottom: 0} : this._gridPadding; 2152 2153 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}); 2154 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}); 2155 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}); 2156 for (i=8; i>0; i--) { 2157 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}); 2158 } 2159 // ax.y2axis.pack({position:'absolute', top:0, right:0}, {min:this._height - this._gridPadding.bottom, max: this._gridPadding.top}); 2160 2161 this.target.append(this.grid.createElement(this._gridPadding)); 2162 this.grid.draw(); 2163 2164 // put the shadow canvases behind the series canvases so shadows don't overlap on stacked bars. 2165 for (i=0; i<this.series.length; i++) { 2166 // draw series in order of stacking. This affects only 2167 // order in which canvases are added to dom. 2168 j = this.seriesStack[i]; 2169 this.target.append(this.series[j].shadowCanvas.createElement(this._gridPadding, 'jqplot-series-shadowCanvas')); 2170 this.series[j].shadowCanvas.setContext(); 2171 this.series[j].shadowCanvas._elem.data('seriesIndex', j); 2172 } 2173 2174 for (i=0; i<this.series.length; i++) { 2175 // draw series in order of stacking. This affects only 2176 // order in which canvases are added to dom. 2177 j = this.seriesStack[i]; 2178 this.target.append(this.series[j].canvas.createElement(this._gridPadding, 'jqplot-series-canvas')); 2179 this.series[j].canvas.setContext(); 2180 this.series[j].canvas._elem.data('seriesIndex', j); 2181 } 2182 // Need to use filled canvas to capture events in IE. 2183 // Also, canvas seems to block selection of other elements in document on FF. 2184 this.target.append(this.eventCanvas.createElement(this._gridPadding, 'jqplot-event-canvas')); 2185 this.eventCanvas.setContext(); 2186 this.eventCanvas._ctx.fillStyle = 'rgba(0,0,0,0)'; 2187 this.eventCanvas._ctx.fillRect(0,0,this.eventCanvas._ctx.canvas.width, this.eventCanvas._ctx.canvas.height); 2188 2189 // bind custom event handlers to regular events. 2190 this.bindCustomEvents(); 2191 2192 // draw legend before series if the series needs to know the legend dimensions. 2193 if (this.legend.preDraw) { 2194 this.eventCanvas._elem.before(legendElem); 2195 this.legend.pack(legendPadding); 2196 if (this.legend._elem) { 2197 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}}); 2198 } 2199 else { 2200 this.drawSeries(); 2201 } 2202 } 2203 else { // draw series before legend 2204 this.drawSeries(); 2205 $(this.series[this.series.length-1].canvas._elem).after(legendElem); 2206 this.legend.pack(legendPadding); 2207 } 2208 2209 // register event listeners on the overlay canvas 2210 for (var i=0; i<$.jqplot.eventListenerHooks.length; i++) { 2211 // in the handler, this will refer to the eventCanvas dom element. 2212 // make sure there are references back into plot objects. 2213 this.eventCanvas._elem.bind($.jqplot.eventListenerHooks[i][0], {plot:this}, $.jqplot.eventListenerHooks[i][1]); 2214 } 2215 2216 // register event listeners on the overlay canvas 2217 for (var i=0; i<this.eventListenerHooks.hooks.length; i++) { 2218 // in the handler, this will refer to the eventCanvas dom element. 2219 // make sure there are references back into plot objects. 2220 this.eventCanvas._elem.bind(this.eventListenerHooks.hooks[i][0], {plot:this}, this.eventListenerHooks.hooks[i][1]); 2221 } 2222 2223 for (var i=0; i<$.jqplot.postDrawHooks.length; i++) { 2224 $.jqplot.postDrawHooks[i].call(this); 2225 } 2226 2227 for (var i=0; i<this.postDrawHooks.hooks.length; i++) { 2228 this.postDrawHooks.hooks[i].call(this); 2229 } 2230 2231 if (this.target.is(':visible')) { 2232 this._drawCount += 1; 2233 } 2234 2235 this.target.trigger('jqplotPostDraw', [this]); 2236 } 2237 }; 2238 2239 this.bindCustomEvents = function() { 2240 this.eventCanvas._elem.bind('click', {plot:this}, this.onClick); 2241 this.eventCanvas._elem.bind('dblclick', {plot:this}, this.onDblClick); 2242 this.eventCanvas._elem.bind('mousedown', {plot:this}, this.onMouseDown);
2243 this.eventCanvas._elem.bind('mousemove', {plot:this}, this.onMouseMove); 2244 this.eventCanvas._elem.bind('mouseenter', {plot:this}, this.onMouseEnter); 2245 this.eventCanvas._elem.bind('mouseleave', {plot:this}, this.onMouseLeave); 2246 if (this.captureRightClick) { 2247 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onRightClick); 2248 this.eventCanvas._elem.get(0).oncontextmenu = function() { 2249 return false; 2250 }; 2251 } 2252 else { 2253 this.eventCanvas._elem.bind('mouseup', {plot:this}, this.onMouseUp); 2254 } 2255 }; 2256 2257 function getEventPosition(ev) { 2258 var plot = ev.data.plot; 2259 var go = plot.eventCanvas._elem.offset(); 2260 var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top}; 2261 var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null}; 2262 var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; 2263 var ax = plot.axes; 2264 var n, axis; 2265 for (n=11; n>0; n--) { 2266 axis = an[n-1]; 2267 if (ax[axis].show) { 2268 dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]); 2269 } 2270 } 2271 2272 return {offsets:go, gridPos:gridPos, dataPos:dataPos}; 2273 } 2274 2275 // function getNeighborPoint(plot, x, y) { 2276 // var ret = null; 2277 // var s, i, d0, d, j, r, k; 2278 // var threshold, t; 2279 // for (var k=plot.seriesStack.length-1; k>-1; k--) { 2280 // i = plot.seriesStack[k]; 2281 // s = plot.series[i]; 2282 // r = s.renderer; 2283 // if (s.show) { 2284 // t = s.markerRenderer.size/2+s.neighborThreshold; 2285 // threshold = (t > 0) ? t : 0; 2286 // for (var j=0; j<s.gridData.length; j++) { 2287 // p = s.gridData[j]; 2288 // // neighbor looks different to OHLC chart. 2289 // if (r.constructor == $.jqplot.OHLCRenderer) { 2290 // if (r.candleStick) { 2291 // var yp = s._yaxis.series_u2p; 2292 // 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])) { 2293 // return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2294 // } 2295 // } 2296 // // if an open hi low close chart 2297 // else if (!r.hlc){ 2298 // var yp = s._yaxis.series_u2p; 2299 // if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { 2300 // return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2301 // } 2302 // } 2303 // // a hi low close chart 2304 // else { 2305 // var yp = s._yaxis.series_u2p; 2306 // if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { 2307 // return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2308 // } 2309 // } 2310 // 2311 // } 2312 // else { 2313 // d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); 2314 // if (d <= threshold && (d <= d0 || d0 == null)) { 2315 // d0 = d; 2316 // return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2317 // } 2318 // } 2319 // } 2320 // } 2321 // } 2322 // return ret; 2323 // } 2324 2325 2326 // function to check if event location is over a area area 2327 function checkIntersection(gridpos, plot) { 2328 var series = plot.series; 2329 var i, j, k, s, r, x, y, theta, sm, sa, minang, maxang; 2330 var d0, d, p, pp, points, bw; 2331 var threshold, t; 2332 for (k=plot.seriesStack.length-1; k>=0; k--) { 2333 i = plot.seriesStack[k]; 2334 s = series[i];
2335 switch (s.renderer.constructor) { 2336 case $.jqplot.BarRenderer: 2337 x = gridpos.x; 2338 y = gridpos.y; 2339 for (j=s.gridData.length-1; j>=0; j--) { 2340 points = s._barPoints[j]; 2341 if (x>points[0][0] && x<points[2][0] && y>points[2][1] && y<points[0][1]) { 2342 return {seriesIndex:s.index, pointIndex:j, gridData:p, data:s.data[j], points:s._barPoints[j]}; 2343 } 2344 } 2345 break; 2346 2347 case $.jqplot.DonutRenderer: 2348 sa = s.startAngle/180*Math.PI; 2349 x = gridpos.x - s._center[0]; 2350 y = gridpos.y - s._center[1]; 2351 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); 2352 if (x > 0 && -y >= 0) { 2353 theta = 2*Math.PI - Math.atan(-y/x); 2354 } 2355 else if (x > 0 && -y < 0) { 2356 theta = -Math.atan(-y/x); 2357 } 2358 else if (x < 0) { 2359 theta = Math.PI - Math.atan(-y/x); 2360 } 2361 else if (x == 0 && -y > 0) { 2362 theta = 3*Math.PI/2; 2363 } 2364 else if (x == 0 && -y < 0) { 2365 theta = Math.PI/2; 2366 } 2367 else if (x == 0 && y == 0) { 2368 theta = 0; 2369 } 2370 if (sa) { 2371 theta -= sa; 2372 if (theta < 0) { 2373 theta += 2*Math.PI; 2374 } 2375 else if (theta > 2*Math.PI) { 2376 theta -= 2*Math.PI; 2377 } 2378 } 2379 2380 sm = s.sliceMargin/180*Math.PI; 2381 if (r < s._radius && r > s._innerRadius) { 2382 for (j=0; j<s.gridData.length; j++) { 2383 minang = (j>0) ? s.gridData[j-1][1]+sm : sm; 2384 maxang = s.gridData[j][1]; 2385 if (theta > minang && theta < maxang) { 2386 return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]}; 2387 } 2388 } 2389 } 2390 break; 2391 2392 case $.jqplot.PieRenderer: 2393 sa = s.startAngle/180*Math.PI; 2394 x = gridpos.x - s._center[0]; 2395 y = gridpos.y - s._center[1]; 2396 r = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); 2397 if (x > 0 && -y >= 0) { 2398 theta = 2*Math.PI - Math.atan(-y/x); 2399 } 2400 else if (x > 0 && -y < 0) { 2401 theta = -Math.atan(-y/x); 2402 } 2403 else if (x < 0) { 2404 theta = Math.PI - Math.atan(-y/x); 2405 } 2406 else if (x == 0 && -y > 0) { 2407 theta = 3*Math.PI/2; 2408 } 2409 else if (x == 0 && -y < 0) { 2410 theta = Math.PI/2; 2411 } 2412 else if (x == 0 && y == 0) { 2413 theta = 0; 2414 } 2415 if (sa) { 2416 theta -= sa; 2417 if (theta < 0) { 2418 theta += 2*Math.PI; 2419 } 2420 else if (theta > 2*Math.PI) { 2421 theta -= 2*Math.PI; 2422 } 2423 } 2424 2425 sm = s.sliceMargin/180*Math.PI; 2426 if (r < s._radius) { 2427 for (j=0; j<s.gridData.length; j++) { 2428 minang = (j>0) ? s.gridData[j-1][1]+sm : sm; 2429 maxang = s.gridData[j][1]; 2430 if (theta > minang && theta < maxang) { 2431 return {seriesIndex:s.index, pointIndex:j, gridData:s.gridData[j], data:s.data[j]}; 2432 } 2433 } 2434 } 2435 break; 2436 2437 case $.jqplot.BubbleRenderer: 2438 x = gridpos.x; 2439 y = gridpos.y; 2440 var ret = null; 2441 2442 if (s.show) { 2443 for (var j=0; j<s.gridData.length; j++) { 2444 p = s.gridData[j]; 2445 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-
2445p[1]) * (y-p[1]) ); 2446 if (d <= p[2] && (d <= d0 || d0 == null)) { 2447 d0 = d; 2448 ret = {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2449 } 2450 } 2451 if (ret != null) return ret; 2452 } 2453 break; 2454 2455 case $.jqplot.FunnelRenderer: 2456 x = gridpos.x; 2457 y = gridpos.y; 2458 var v = s._vertices, 2459 vfirst = v[0], 2460 vlast = v[v.length-1], 2461 lex, 2462 rex; 2463 2464 // equations of right and left sides, returns x, y values given height of section (y value and 2 points) 2465 2466 function findedge (l, p1 , p2) { 2467 var m = (p1[1] - p2[1])/(p1[0] - p2[0]); 2468 var b = p1[1] - m*p1[0]; 2469 var y = l + p1[1]; 2470 2471 return [(y - b)/m, y]; 2472 } 2473 2474 // check each section 2475 lex = findedge(y, vfirst[0], vlast[3]); 2476 rex = findedge(y, vfirst[1], vlast[2]); 2477 for (j=0; j<v.length; j++) { 2478 cv = v[j]; 2479 if (y >= cv[0][1] && y <= cv[3][1] && x >= lex[0] && x <= rex[0]) { 2480 return {seriesIndex:s.index, pointIndex:j, gridData:null, data:s.data[j]}; 2481 } 2482 } 2483 break; 2484 2485 case $.jqplot.LineRenderer: 2486 x = gridpos.x; 2487 y = gridpos.y; 2488 r = s.renderer; 2489 if (s.show) { 2490 if (s.fill) { 2491 // first check if it is in bounding box 2492 var inside = false; 2493 if (x>s._boundingBox[0][0] && x<s._boundingBox[1][0] && y>s._boundingBox[1][1] && y<s._boundingBox[0][1]) { 2494 // now check the crossing number 2495 2496 var numPoints = s._areaPoints.length; 2497 var ii; 2498 var j = numPoints-1; 2499 2500 for(var ii=0; ii < numPoints; ii++) { 2501 var vertex1 = [s._areaPoints[ii][0], s._areaPoints[ii][1]]; 2502 var vertex2 = [s._areaPoints[j][0], s._areaPoints[j][1]]; 2503 2504 if (vertex1[1] < y && vertex2[1] >= y || vertex2[1] < y && vertex1[1] >= y) { 2505 if (vertex1[0] + (y - vertex1[1]) / (vertex2[1] - vertex1[1]) * (vertex2[0] - vertex1[0]) < x) { 2506 inside = !inside; 2507 } 2508 } 2509 2510 j = ii; 2511 } 2512 } 2513 if (inside) { 2514 return {seriesIndex:i, pointIndex:null, gridData:s.gridData, data:s.data, points:s._areaPoints}; 2515 } 2516 break; 2517 2518 } 2519 else { 2520 t = s.markerRenderer.size/2+s.neighborThreshold; 2521 threshold = (t > 0) ? t : 0; 2522 for (var j=0; j<s.gridData.length; j++) { 2523 p = s.gridData[j]; 2524 // neighbor looks different to OHLC chart. 2525 if (r.constructor == $.jqplot.OHLCRenderer) { 2526 if (r.candleStick) { 2527 var yp = s._yaxis.series_u2p; 2528 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])) { 2529 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2530 } 2531 } 2532 // if an open hi low close chart 2533 else if (!r.hlc){ 2534 var yp = s._yaxis.series_u2p; 2535 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { 2536 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2537 } 2538 } 2539 // a hi low close chart 2540 else { 2541 var yp = s._yaxis.series_u2p; 2542 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { 2543 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2544 } 2545 } 2546 2547 } 2548 else if (p[0] != null && p[1] != null){ 2549 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-
2549p[1]) * (y-p[1]) ); 2550 if (d <= threshold && (d <= d0 || d0 == null)) { 2551 d0 = d; 2552 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2553 } 2554 } 2555 } 2556 } 2557 } 2558 break; 2559 2560 default: 2561 x = gridpos.x; 2562 y = gridpos.y; 2563 r = s.renderer; 2564 if (s.show) { 2565 t = s.markerRenderer.size/2+s.neighborThreshold; 2566 threshold = (t > 0) ? t : 0; 2567 for (var j=0; j<s.gridData.length; j++) { 2568 p = s.gridData[j]; 2569 // neighbor looks different to OHLC chart. 2570 if (r.constructor == $.jqplot.OHLCRenderer) { 2571 if (r.candleStick) { 2572 var yp = s._yaxis.series_u2p; 2573 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])) { 2574 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2575 } 2576 } 2577 // if an open hi low close chart 2578 else if (!r.hlc){ 2579 var yp = s._yaxis.series_u2p; 2580 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][2]) && y <= yp(s.data[j][3])) { 2581 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2582 } 2583 } 2584 // a hi low close chart 2585 else { 2586 var yp = s._yaxis.series_u2p; 2587 if (x >= p[0]-r._tickLength && x <= p[0]+r._tickLength && y >= yp(s.data[j][1]) && y <= yp(s.data[j][2])) { 2588 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2589 } 2590 } 2591 2592 } 2593 else { 2594 d = Math.sqrt( (x-p[0]) * (x-p[0]) + (y-p[1]) * (y-p[1]) ); 2595 if (d <= threshold && (d <= d0 || d0 == null)) { 2596 d0 = d; 2597 return {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}; 2598 } 2599 } 2600 } 2601 } 2602 break; 2603 } 2604 } 2605 2606 return null; 2607 } 2608 2609 2610 2611 this.onClick = function(ev) { 2612 // Event passed in is normalized and will have data attribute. 2613 // Event passed out is unnormalized. 2614 var positions = getEventPosition(ev); 2615 var p = ev.data.plot; 2616 var neighbor = checkIntersection(positions.gridPos, p); 2617 var evt = jQuery.Event('jqplotClick'); 2618 evt.pageX = ev.pageX; 2619 evt.pageY = ev.pageY; 2620 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 2621 }; 2622 2623 this.onDblClick = function(ev) { 2624 // Event passed in is normalized and will have data attribute. 2625 // Event passed out is unnormalized. 2626 var positions = getEventPosition(ev); 2627 var p = ev.data.plot; 2628 var neighbor = checkIntersection(positions.gridPos, p); 2629 var evt = jQuery.Event('jqplotDblClick'); 2630 evt.pageX = ev.pageX; 2631 evt.pageY = ev.pageY; 2632 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 2633 }; 2634 2635 this.onMouseDown = function(ev) { 2636 var positions = getEventPosition(ev); 2637 var p = ev.data.plot; 2638 var neighbor = checkIntersection(positions.gridPos, p); 2639 var evt = jQuery.Event('jqplotMouseDown'); 2640 evt.pageX = ev.pageX; 2641 evt.pageY = ev.pageY; 2642 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 2643 }; 2644 2645 this.onMouseUp = function(ev) { 2646 var positions = getEventPosition(ev); 2647 var evt = jQuery.Event('jqplotMouseUp'); 2648 evt.pageX = ev.pageX; 2649 evt.pageY = ev.pageY; 2650 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, ev.data.plot]); 2651 }; 2652 2653 this.onRightClick = function(ev) { 2654 var positions = getEventPosition(ev); 2655 var p = ev.data.plot; 2656 var neighbor = checkIntersection(positions.gridPos, p); 2657 if (p.captureRightClick) { 2658 if (ev.which == 3) { 2659 var evt = jQuery.Event('jqplotRightClick'); 2660 evt.pageX = ev.pageX; 2661 evt.pageY = ev.pageY; 2662 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 2663 } 2664 else { 2665 var evt = jQuery.Event('jqplotMouseUp'); 2666 evt.pageX = ev.pageX; 2667 evt.pageY = ev.pageY; 2668 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 2669 } 2670 } 2671 }; 2672 2673 this.onMouseMove = function(ev) { 2674 var positions = getEventPosition(ev); 2675 var p = ev.data.plot; 2676 var neighbor = checkIntersection(positions.gridPos, p); 2677 var evt = jQuery.Event('jqplotMouseMove'); 2678 evt.pageX = ev.pageX; 2679 evt.pageY = ev.pageY; 2680 $(this).trigger(evt, [positions.gridPos, positions.dataPos, neighbor, p]); 2681 }; 2682 2683 this.onMouseEnter = function(ev) { 2684 var positions = getEventPosition(ev); 2685 var p = ev.data.plot; 2686 var evt = jQuery.Event('jqplotMouseEnter'); 2687 evt.pageX = ev.pageX; 2688 evt.pageY = ev.pageY; 2689 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]); 2690 }; 2691 2692 this.onMouseLeave = function(ev) { 2693 var positions = getEventPosition(ev); 2694 var p = ev.data.plot; 2695 var evt = jQuery.Event('jqplotMouseLeave'); 2696 evt.pageX = ev.pageX; 2697 evt.pageY = ev.pageY; 2698 $(this).trigger(evt, [positions.gridPos, positions.dataPos, null, p]); 2699 }; 2700 2701 // method: drawSeries 2702 // Redraws all or just one series on the plot. No axis scaling 2703 // is performed and no other elements on the plot are redrawn. 2704 // options is an options object to pass on to the series renderers. 2705 // It can be an empty object {}. idx is the series index 2706 // to redraw if only one series is to be redrawn. 2707 this.drawSeries = function(options, idx){ 2708 var i, series, ctx; 2709 // if only one argument passed in and it is a number, use it ad idx. 2710 idx = (typeof(options) == "number" && idx == null) ? options : idx; 2711 options = (typeof(options) == "object") ? options : {}; 2712 // draw specified series 2713 if (idx != undefined) { 2714 series = this.series[idx]; 2715 ctx = series.shadowCanvas._ctx; 2716 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 2717 series.drawShadow(ctx, options, this); 2718 ctx = series.canvas._ctx; 2719 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 2720 series.draw(ctx, options, this); 2721 if (series.renderer.constructor == $.jqplot.BezierCurveRenderer) { 2722 if (idx < this.series.length - 1) { 2723 this.drawSeries(idx+1); 2724 } 2725 } 2726 } 2727 2728 else { 2729 // if call series drawShadow method first, in case all series shadows 2730 // should be drawn before any series. This will ensure, like for 2731 // stacked bar plots, that shadows don't overlap series. 2732 for (i=0; i<this.series.length; i++) { 2733 // first clear the canvas 2734 series = this.series[i];
2735 ctx = series.shadowCanvas._ctx; 2736 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 2737 series.drawShadow(ctx, options, this); 2738 ctx = series.canvas._ctx; 2739 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 2740 series.draw(ctx, options, this); 2741 } 2742 } 2743 }; 2744 2745 // method: moveSeriesToFront 2746 // This method requires jQuery 1.4+ 2747 // Moves the specified series canvas in front of all other series canvases. 2748 // This effectively "draws" the specified series on top of all other series, 2749 // although it is performed through DOM manipulation, no redrawing is performed. 2750 // 2751 // Parameters: 2752 // idx - 0 based index of the series to move. This will be the index of the series 2753 // as it was first passed into the jqplot function. 2754 this.moveSeriesToFront = function (idx) { 2755 idx = parseInt(idx, 10); 2756 var stackIndex = $.inArray(idx, this.seriesStack); 2757 // if already in front, return 2758 if (stackIndex == -1) { 2759 return; 2760 } 2761 if (stackIndex == this.seriesStack.length -1) { 2762 this.previousSeriesStack = this.seriesStack.slice(0); 2763 return; 2764 } 2765 var opidx = this.seriesStack[this.seriesStack.length -1]; 2766 var serelem = this.series[idx].canvas._elem.detach(); 2767 var shadelem = this.series[idx].shadowCanvas._elem.detach(); 2768 this.series[opidx].shadowCanvas._elem.after(shadelem); 2769 this.series[opidx].canvas._elem.after(serelem); 2770 this.previousSeriesStack = this.seriesStack.slice(0); 2771 this.seriesStack.splice(stackIndex, 1); 2772 this.seriesStack.push(idx); 2773 }; 2774 2775 // method: moveSeriesToBack 2776 // This method requires jQuery 1.4+ 2777 // Moves the specified series canvas behind all other series canvases. 2778 // 2779 // Parameters: 2780 // idx - 0 based index of the series to move. This will be the index of the series 2781 // as it was first passed into the jqplot function. 2782 this.moveSeriesToBack = function (idx) { 2783 idx = parseInt(idx, 10); 2784 var stackIndex = $.inArray(idx, this.seriesStack); 2785 // if already in back, return 2786 if (stackIndex == 0 || stackIndex == -1) { 2787 return; 2788 } 2789 var opidx = this.seriesStack[0]; 2790 var serelem = this.series[idx].canvas._elem.detach(); 2791 var shadelem = this.series[idx].shadowCanvas._elem.detach(); 2792 this.series[opidx].shadowCanvas._elem.before(shadelem); 2793 this.series[opidx].canvas._elem.before(serelem); 2794 this.previousSeriesStack = this.seriesStack.slice(0); 2795 this.seriesStack.splice(stackIndex, 1); 2796 this.seriesStack.unshift(idx); 2797 }; 2798 2799 // method: restorePreviousSeriesOrder 2800 // This method requires jQuery 1.4+ 2801 // Restore the series canvas order to its previous state. 2802 // Useful to put a series back where it belongs after moving 2803 // it to the front. 2804 this.restorePreviousSeriesOrder = function () { 2805 var i, j, serelem, shadelem, temp; 2806 // if no change, return. 2807 if (this.seriesStack == this.previousSeriesStack) { 2808 return; 2809 } 2810 for (i=1; i<this.previousSeriesStack.length; i++) { 2811 move = this.previousSeriesStack[i]; 2812 keep = this.previousSeriesStack[i-1]; 2813 serelem = this.series[move].canvas._elem.detach(); 2814 shadelem = this.series[move].shadowCanvas._elem.detach(); 2815 this.series[keep].shadowCanvas._elem.after(shadelem); 2816 this.series[keep].canvas._elem.after(serelem); 2817 } 2818 temp = this.seriesStack.slice(0); 2819 this.seriesStack = this.previousSeriesStack.slice(0); 2820 this.previousSeriesStack = temp; 2821 }; 2822 2823 // method: restoreOriginalSeriesOrder 2824 // This method requires jQuery 1.4+ 2825 // Restore the series canvas order to its original order 2826 // when the plot was created. 2827 this.restoreOriginalSeriesOrder = function () { 2828 var i, j, arr=[]; 2829 for (i=0; i<this.series.length; i++) { 2830 arr.push(i); 2831 } 2832 if (this.seriesStack == arr) { 2833 return; 2834 } 2835 this.previousSeriesStack = this.seriesStack.slice(0); 2836 this.seriesStack = arr; 2837 for (i=1; i<this.seriesStack.length; i++) { 2838 serelem = this.series[i].canvas._elem.detach(); 2839 shadelem = this.series[i].shadowCanvas._elem.detach(); 2840 this.series[i-1].shadowCanvas._elem.after(shadelem); 2841 this.series[i-1].canvas._elem.after(serelem); 2842 } 2843 }; 2844 2845 this.activateTheme = function (name) { 2846 this.themeEngine.activate(this, name); 2847 }; 2848 } 2849 2850 2851 // conpute a highlight color or array of highlight colors from given colors. 2852 $.jqplot.computeHighlightColors = function(colors) { 2853 var ret; 2854 if (typeof(colors) == "array") { 2855 ret = []; 2856 for (var i=0; i<colors.length; i++){ 2857 var rgba = $.jqplot.getColorComponents(colors[i]); 2858 var newrgb = [rgba[0], rgba[1], rgba[2]]; 2859 var sum = newrgb[0] + newrgb[1] + newrgb[2]; 2860 for (var j=0; j<3; j++) { 2861 // when darkening, lowest color component can be is 60. 2862 newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]); 2863 newrgb[j] = parseInt(newrgb[j], 10); 2864 }
2865 ret.push('rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')'); 2866 } 2867 } 2868 else { 2869 var rgba = $.jqplot.getColorComponents(colors); 2870 var newrgb = [rgba[0], rgba[1], rgba[2]]; 2871 var sum = newrgb[0] + newrgb[1] + newrgb[2]; 2872 for (var j=0; j<3; j++) { 2873 // when darkening, lowest color component can be is 60. 2874 newrgb[j] = (sum > 570) ? newrgb[j] * 0.8 : newrgb[j] + 0.3 * (255 - newrgb[j]); 2875 newrgb[j] = parseInt(newrgb[j], 10); 2876 } 2877 ret = 'rgb('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+')'; 2878 } 2879 return ret; 2880 }; 2881 2882 $.jqplot.ColorGenerator = function(colors) { 2883 var idx = 0; 2884 2885 this.next = function () { 2886 if (idx < colors.length) { 2887 return colors[idx++]; 2888 } 2889 else { 2890 idx = 0; 2891 return colors[idx++]; 2892 } 2893 }; 2894 2895 this.previous = function () { 2896 if (idx > 0) { 2897 return colors[idx--]; 2898 } 2899 else { 2900 idx = colors.length-1; 2901 return colors[idx]; 2902 } 2903 }; 2904 2905 // get a color by index without advancing pointer. 2906 this.get = function(i) { 2907 var idx = i - colors.length * Math.floor(i/colors.length); 2908 return colors[idx]; 2909 }; 2910 2911 this.setColors = function(c) { 2912 colors = c; 2913 }; 2914 2915 this.reset = function() { 2916 idx = 0; 2917 }; 2918 }; 2919 2920 // convert a hex color string to rgb string. 2921 // h - 3 or 6 character hex string, with or without leading # 2922 // a - optional alpha 2923 $.jqplot.hex2rgb = function(h, a) { 2924 h = h.replace('#', ''); 2925 if (h.length == 3) { 2926 h = h[0]+h[0]+h[1]+h[1]+h[2]+h[2]; 2927 } 2928 var rgb; 2929 rgb = 'rgba('+parseInt(h.slice(0,2), 16)+', '+parseInt(h.slice(2,4), 16)+', '+parseInt(h.slice(4,6), 16); 2930 if (a) { 2931 rgb += ', '+a; 2932 } 2933 rgb += ')'; 2934 return rgb; 2935 }; 2936 2937 // convert an rgb color spec to a hex spec. ignore any alpha specification. 2938 $.jqplot.rgb2hex = function(s) { 2939 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *(?:, *[0-9.]*)?\)/; 2940 var m = s.match(pat); 2941 var h = '#'; 2942 for (i=1; i<4; i++) { 2943 var temp; 2944 if (m[i].search(/%/) != -1) { 2945 temp = parseInt(255*m[i]/100, 10).toString(16); 2946 if (temp.length == 1) { 2947 temp = '0'+temp; 2948 } 2949 } 2950 else { 2951 temp = parseInt(m[i], 10).toString(16); 2952 if (temp.length == 1) { 2953 temp = '0'+temp; 2954 } 2955 } 2956 h += temp; 2957 } 2958 return h; 2959 }; 2960 2961 // given a css color spec, return an rgb css color spec 2962 $.jqplot.normalize2rgb = function(s, a) { 2963 if (s.search(/^ *rgba?\(/) != -1) { 2964 return s; 2965 } 2966 else if (s.search(/^ *#?[0-9a-fA-F]?[0-9a-fA-F]/) != -1) { 2967 return $.jqplot.hex2rgb(s, a); 2968 } 2969 else { 2970 throw 'invalid color spec'; 2971 } 2972 }; 2973 2974 // extract the r, g, b, a color components out of a css color spec. 2975 $.jqplot.getColorComponents = function(s) { 2976 // check to see if a color keyword. 2977 s = $.jqplot.colorKeywordMap[s] || s; 2978 var rgb = $.jqplot.normalize2rgb(s); 2979 var pat = /rgba?\( *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *, *([0-9]{1,3}\.?[0-9]*%?) *,? *([0-9.]* *)?\)/; 2980 var m = rgb.match(pat); 2981 var ret = []; 2982 for (i=1; i<4; i++) { 2983 if (m[i].search(/%/) != -1) { 2984 ret[i-1] = parseInt(255*m[i]/100, 10); 2985 } 2986 else { 2987 ret[i-1] = parseInt(m[i], 10); 2988 } 2989 } 2990 ret[3] = parseFloat(m[4]) ? parseFloat(m[4]) : 1.0; 2991 return ret; 2992 }; 2993 2994 $.jqplot.colorKeywordMap = {aliceblue: 'rgb(240, 248, 255)', antiquewhite: 'rgb(250, 235, 215)', aqua: 'rgb( 0, 255, 255)', aquamarine: 'rgb(127, 255, 212)', azure: 'rgb(240, 255, 255)', beige: 'rgb(245, 245, 220)', bisque: 'rgb(255, 228, 196)', black: 'rgb( 0, 0, 0)', blanchedalmond: 'rgb(255, 235, 205)', blue: 'rgb( 0, 0, 255)', blueviolet: 'rgb(138, 43, 226)', brown: 'rgb(165, 42, 42)', burlywood: 'rgb(222, 184, 135)', cadetblue: 'rgb( 95, 158, 160)', chartreuse: 'rgb(127, 255, 0)', chocolate: 'rgb(210, 105, 30)', coral: 'rgb(255, 127, 80)', cornflowerblue: 'rgb(100, 149, 237)', cornsilk: 'rgb(255, 248, 220)', crimson: 'rgb(220, 20, 60)', cyan: 'rgb( 0, 255, 255)', darkblue: 'rgb( 0, 0, 139)', darkcyan: 'rgb( 0, 139, 139)', darkgoldenrod: 'rgb(184, 134, 11)', darkgray: 'rgb(169, 169, 169)', darkgreen: 'rgb( 0, 100, 0)', darkgrey: 'rgb(169, 169, 169)', darkkhaki: 'rgb(189, 183, 107)', darkmagenta: 'rgb(139, 0, 139)', darkolivegreen: 'rgb( 85, 107, 47)', darkorange: 'rgb(255, 140, 0)', darkorchid: 'rgb(153, 50, 204)', darkred: 'rgb(139, 0, 0
2994)', darksalmon: 'rgb(233, 150, 122)', darkseagreen: 'rgb(143, 188, 143)', darkslateblue: 'rgb( 72, 61, 139)', darkslategray: 'rgb( 47, 79, 79)', darkslategrey: 'rgb( 47, 79, 79)', darkturquoise: 'rgb( 0, 206, 209)', darkviolet: 'rgb(148, 0, 211)', deeppink: 'rgb(255, 20, 147)', deepskyblue: 'rgb( 0, 191, 255)', dimgray: 'rgb(105, 105, 105)', dimgrey: 'rgb(105, 105, 105)', dodgerblue: 'rgb( 30, 144, 255)', firebrick: 'rgb(178, 34, 34)', floralwhite: 'rgb(255, 250, 240)', forestgreen: 'rgb( 34, 139, 34)', fuchsia: 'rgb(255, 0, 255)', gainsboro: 'rgb(220, 220, 220)', ghostwhite: 'rgb(248, 248, 255)', gold: 'rgb(255, 215, 0)', goldenrod: 'rgb(218, 165, 32)', gray: 'rgb(128, 128, 128)', grey: 'rgb(128, 128, 128)', green: 'rgb( 0, 128, 0)', greenyellow: 'rgb(173, 255, 47)', honeydew: 'rgb(240, 255, 240)', hotpink: 'rgb(255, 105, 180)', indianred: 'rgb(205, 92, 92)', indigo: 'rgb( 75, 0, 130)', ivory: 'rgb(255, 255, 240)', khaki: 'rgb(240, 230, 140)', lavender: 'rgb(230, 230, 250)', lavenderblush: 'rgb(255, 240, 245)', lawngreen: 'rgb(124, 252, 0)', lemonchiffon: 'rgb(255, 250, 205)', lightblue: 'rgb(173, 216, 230)', lightcoral: 'rgb(240, 128, 128)', lightcyan: 'rgb(224, 255, 255)', lightgoldenrodyellow: 'rgb(250, 250, 210)', lightgray: 'rgb(211, 211, 211)', lightgreen: 'rgb(144, 238, 144)', lightgrey: 'rgb(211, 211, 211)', lightpink: 'rgb(255, 182, 193)', lightsalmon: 'rgb(255, 160, 122)', lightseagreen: 'rgb( 32, 178, 170)', lightskyblue: 'rgb(135, 206, 250)', lightslategray: 'rgb(119, 136, 153)', lightslategrey: 'rgb(119, 136, 153)', lightsteelblue: 'rgb(176, 196, 222)', lightyellow: 'rgb(255, 255, 224)', lime: 'rgb( 0, 255, 0)', limegreen: 'rgb( 50, 205, 50)', linen: 'rgb(250, 240, 230)', magenta: 'rgb(255, 0, 255)', maroon: 'rgb(128, 0, 0
2994)', mediumaquamarine: 'rgb(102, 205, 170)', mediumblue: 'rgb( 0, 0, 205)', mediumorchid: 'rgb(186, 85, 211)', mediumpurple: 'rgb(147, 112, 219)', mediumseagreen: 'rgb( 60, 179, 113)', mediumslateblue: 'rgb(123, 104, 238)', mediumspringgreen: 'rgb( 0, 250, 154)', mediumturquoise: 'rgb( 72, 209, 204)', mediumvioletred: 'rgb(199, 21, 133)', midnightblue: 'rgb( 25, 25, 112)', mintcream: 'rgb(245, 255, 250)', mistyrose: 'rgb(255, 228, 225)', moccasin: 'rgb(255, 228, 181)', navajowhite: 'rgb(255, 222, 173)', navy: 'rgb( 0, 0, 128)', oldlace: 'rgb(253, 245, 230)', olive: 'rgb(128, 128, 0)', olivedrab: 'rgb(107, 142, 35)', orange: 'rgb(255, 165, 0)', orangered: 'rgb(255, 69, 0)', orchid: 'rgb(218, 112, 214)', palegoldenrod: 'rgb(238, 232, 170)', palegreen: 'rgb(152, 251, 152)', paleturquoise: 'rgb(175, 238, 238)', palevioletred: 'rgb(219, 112, 147)', papayawhip: 'rgb(255, 239, 213)', peachpuff: 'rgb(255, 218, 185)', peru: 'rgb(205, 133, 63)', pink: 'rgb(255, 192, 203)', plum: 'rgb(221, 160, 221)', powderblue: 'rgb(176, 224, 230)', purple: 'rgb(128, 0, 128)', red: 'rgb(255, 0, 0)', rosybrown: 'rgb(188, 143, 143)', royalblue: 'rgb( 65, 105, 225)', saddlebrown: 'rgb(139, 69, 19)', salmon: 'rgb(250, 128, 114)', sandybrown: 'rgb(244, 164, 96)', seagreen: 'rgb( 46, 139, 87)', seashell: 'rgb(255, 245, 238)', sienna: 'rgb(160, 82, 45)', silver: 'rgb(192, 192, 192)', skyblue: 'rgb(135, 206, 235)', slateblue: 'rgb(106, 90, 205)', slategray: 'rgb(112, 128, 144)', slategrey: 'rgb(112, 128, 144)', snow: 'rgb(255, 250, 250)', springgreen: 'rgb( 0, 255, 127)', steelblue: 'rgb( 70, 130, 180)', tan: 'rgb(210, 180, 140)', teal: 'rgb( 0, 128, 128)', thistle: 'rgb(216, 191, 216)', tomato: 'rgb(255, 99, 71)', turquoise: 'rgb( 64, 224, 208)', violet: 'rgb(238, 130, 238)', wheat: 'rgb(245, 222, 179)', white: 'rgb(255, 255, 255)', whitesmoke: 'rgb(245, 245, 245)', yellow: 'rgb(255, 255, 0)', yellowgreen: 'rgb(154, 205, 50)'}; 2995 2996 // Convienence function that won't hang IE. 2997 $.jqplot.log = function() { 2998 if (window.console && $.jqplot.debug) { 2999 if (arguments.length == 1) { 3000 console.log (arguments[0]); 3001 } 3002 else { 3003 console.log(arguments); 3004 } 3005 } 3006 }; 3007 var log = $.jqplot.log; 3008 3009 3010 // class: $.jqplot.AxisLabelRenderer 3011 // Renderer to place labels on the axes. 3012 $.jqplot.AxisLabelRenderer = function(options) { 3013 // Group: Properties 3014 $.jqplot.ElemContainer.call(this); 3015 // name of the axis associated with this tick 3016 this.axis; 3017 // prop: show 3018 // wether or not to show the tick (mark and label). 3019 this.show = true; 3020 // prop: label 3021 // The text or html for the label. 3022 this.label = ''; 3023 this.fontFamily = null; 3024 this.fontSize = null; 3025 this.textColor = null; 3026 this._elem; 3027 // prop: escapeHTML 3028 // true to escape HTML entities in the label. 3029 this.escapeHTML = false; 3030 3031 $.extend(true, this, options); 3032 }; 3033 3034 $.jqplot.AxisLabelRenderer.prototype = new $.jqplot.ElemContainer(); 3035 $.jqplot.AxisLabelRenderer.prototype.constructor = $.jqplot.AxisLabelRenderer; 3036 3037 $.jqplot.AxisLabelRenderer.prototype.init = function(options) { 3038 $.extend(true, this, options); 3039 }; 3040 3041 $.jqplot.AxisLabelRenderer.prototype.draw = function() { 3042 this._elem = $('<div style="position:absolute;" class="jqplot-'+this.axis+'-label"></div>'); 3043 3044 if (Number(this.label)) { 3045 this._elem.css('white-space', 'nowrap'); 3046 } 3047 3048 if (!this.escapeHTML) { 3049 this._elem.html(this.label); 3050 } 3051 else { 3052 this._elem.text(this.label); 3053 } 3054 if (this.fontFamily) { 3055 this._elem.css('font-family', this.fontFamily); 3056 } 3057 if (this.fontSize) { 3058 this._elem.css('font-size', this.fontSize); 3059 } 3060 if (this.textColor) { 3061 this._elem.css('color', this.textColor); 3062 } 3063 3064 return this._elem; 3065 }; 3066 3067 $.jqplot.AxisLabelRenderer.prototype.pack = function() { 3068 }; 3069 3070 // class: $.jqplot.AxisTickRenderer 3071 // A "tick" object showing the value of a tick/gridline on the plot. 3072 $.jqplot.AxisTickRenderer = function(options) { 3073 // Group: Properties 3074 $.jqplot.ElemContainer.call(this); 3075 // prop: mark 3076 // tick mark on the axis. One of 'inside', 'outside', 'cross', '' or null. 3077 this.mark = 'outside'; 3078 // name of the axis associated with this tick 3079 this.axis; 3080 // prop: showMark 3081 // wether or not to show the mark on the axis. 3082 this.showMark = true; 3083 // prop: showGridline 3084 // wether or not to draw the gridline on the grid at this tick. 3085 this.showGridline = true; 3086 // prop: isMinorTick 3087 // if this is a minor tick. 3088 this.isMinorTick = false; 3089 // prop: size 3090 // Length of the tick beyond the grid in pixels. 3091 // DEPRECATED: This has been superceeded by markSize 3092 this.size = 4; 3093 // prop: markSize 3094 // Length of the tick marks in pixels. For 'cross' style, length 3095 // will be stoked above and below axis, so total length will be twice this. 3096 this.markSize = 6; 3097 // prop: show 3098 // wether or not to show the tick (mark and label).
3099 // Setting this to false requires more testing. It is recommended 3100 // to set showLabel and showMark to false instead. 3101 this.show = true; 3102 // prop: showLabel 3103 // wether or not to show the label. 3104 this.showLabel = true; 3105 this.label = ''; 3106 this.value = null; 3107 this._styles = {}; 3108 // prop: formatter 3109 // A class of a formatter for the tick text. sprintf by default. 3110 this.formatter = $.jqplot.DefaultTickFormatter; 3111 // prop: prefix 3112 // string appended to the tick label if no formatString is specified. 3113 this.prefix = ''; 3114 // prop: formatString 3115 // string passed to the formatter. 3116 this.formatString = ''; 3117 // prop: fontFamily 3118 // css spec for the font-family css attribute. 3119 this.fontFamily; 3120 // prop: fontSize 3121 // css spec for the font-size css attribute. 3122 this.fontSize; 3123 // prop: textColor 3124 // css spec for the color attribute. 3125 this.textColor; 3126 this._elem; 3127 3128 $.extend(true, this, options); 3129 }; 3130 3131 $.jqplot.AxisTickRenderer.prototype.init = function(options) { 3132 $.extend(true, this, options); 3133 }; 3134 3135 $.jqplot.AxisTickRenderer.prototype = new $.jqplot.ElemContainer(); 3136 $.jqplot.AxisTickRenderer.prototype.constructor = $.jqplot.AxisTickRenderer; 3137 3138 $.jqplot.AxisTickRenderer.prototype.setTick = function(value, axisName, isMinor) { 3139 this.value = value; 3140 this.axis = axisName; 3141 if (isMinor) { 3142 this.isMinorTick = true; 3143 } 3144 return this; 3145 }; 3146 3147 $.jqplot.AxisTickRenderer.prototype.draw = function() { 3148 if (!this.label) { 3149 this.label = this.formatter(this.formatString, this.value); 3150 } 3151 // add prefix if needed 3152 if (this.prefix && !this.formatString) { 3153 this.label = this.prefix + this.label; 3154 } 3155 style ='style="position:absolute;'; 3156 if (Number(this.label)) { 3157 style +='white-space:nowrap;'; 3158 } 3159 style += '"'; 3160 this._elem = $('<div '+style+' class="jqplot-'+this.axis+'-tick">'+this.label+'</div>'); 3161 for (var s in this._styles) { 3162 this._elem.css(s, this._styles[s]); 3163 } 3164 if (this.fontFamily) { 3165 this._elem.css('font-family', this.fontFamily); 3166 } 3167 if (this.fontSize) { 3168 this._elem.css('font-size', this.fontSize); 3169 } 3170 if (this.textColor) { 3171 this._elem.css('color', this.textColor); 3172 } 3173 return this._elem; 3174 }; 3175 3176 $.jqplot.DefaultTickFormatter = function (format, val) { 3177 if (typeof val == 'number') { 3178 if (!format) { 3179 format = $.jqplot.config.defaultTickFormatString; 3180 } 3181 return $.jqplot.sprintf(format, val); 3182 } 3183 else { 3184 return String(val); 3185 } 3186 }; 3187 3188 $.jqplot.AxisTickRenderer.prototype.pack = function() { 3189 }; 3190 3191 // Class: $.jqplot.CanvasGridRenderer 3192 // The default jqPlot grid renderer, creating a grid on a canvas element. 3193 // The renderer has no additional options beyond the <Grid> class. 3194 $.jqplot.CanvasGridRenderer = function(){ 3195 this.shadowRenderer = new $.jqplot.ShadowRenderer(); 3196 }; 3197 3198 // called with context of Grid object 3199 $.jqplot.CanvasGridRenderer.prototype.init = function(options) { 3200 this._ctx; 3201 $.extend(true, this, options); 3202 // set the shadow renderer options 3203 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}; 3204 this.renderer.shadowRenderer.init(sopts); 3205 }; 3206 3207 // called with context of Grid. 3208 $.jqplot.CanvasGridRenderer.prototype.createElement = function() { 3209 var elem = document.createElement('canvas'); 3210 var w = this._plotDimensions.width; 3211 var h = this._plotDimensions.height; 3212 elem.width = w; 3213 elem.height = h; 3214 this._elem = $(elem); 3215 this._elem.addClass('jqplot-grid-canvas'); 3216 this._elem.css({ position: 'absolute', left: 0, top: 0 }); 3217 if ($.browser.msie) { 3218 window.G_vmlCanvasManager.init_(document); 3219 } 3220 if ($.browser.msie) { 3221 elem = window.G_vmlCanvasManager.initElement(elem); 3222 } 3223 this._top = this._offsets.top; 3224 this._bottom = h - this._offsets.bottom; 3225 this._left = this._offsets.left; 3226 this._right = w - this._offsets.right; 3227 this._width = this._right - this._left; 3228 this._height = this._bottom - this._top; 3229 return this._elem; 3230 }; 3231 3232 $.jqplot.CanvasGridRenderer.prototype.draw = function() { 3233 this._ctx = this._elem.get(0).getContext("2d"); 3234 var ctx = this._ctx; 3235 var axes = this._axes; 3236 // Add the grid onto the grid canvas. This is the bottom most layer. 3237 ctx.save(); 3238 ctx.clearRect(0, 0, this._plotDimensions.width, this._plotDimensions.height); 3239 ctx.fillStyle = this.backgroundColor || this.background; 3240 ctx.fillRect(this._left, this._top, this._width, this._height); 3241
3242 if (this.drawGridlines) { 3243 ctx.save(); 3244 ctx.lineJoin = 'miter'; 3245 ctx.lineCap = 'butt'; 3246 ctx.lineWidth = this.gridLineWidth; 3247 ctx.strokeStyle = this.gridLineColor; 3248 var b, e; 3249 var ax = ['xaxis', 'yaxis', 'x2axis', 'y2axis']; 3250 for (var i=4; i>0; i--) { 3251 var name = ax[i-1]; 3252 var axis = axes[name]; 3253 var ticks = axis._ticks; 3254 if (axis.show) { 3255 for (var j=ticks.length; j>0; j--) { 3256 var t = ticks[j-1]; 3257 if (t.show) { 3258 var pos = Math.round(axis.u2p(t.value)) + 0.5; 3259 switch (name) { 3260 case 'xaxis': 3261 // draw the grid line 3262 if (t.showGridline) { 3263 drawLine(pos, this._top, pos, this._bottom); 3264 } 3265 3266 // draw the mark 3267 if (t.showMark && t.mark) { 3268 s = t.markSize; 3269 m = t.mark; 3270 var pos = Math.round(axis.u2p(t.value)) + 0.5; 3271 switch (m) { 3272 case 'outside': 3273 b = this._bottom; 3274 e = this._bottom+s; 3275 break; 3276 case 'inside': 3277 b = this._bottom-s; 3278 e = this._bottom; 3279 break; 3280 case 'cross': 3281 b = this._bottom-s; 3282 e = this._bottom+s; 3283 break; 3284 default: 3285 b = this._bottom; 3286 e = this._bottom+s; 3287 break; 3288 } 3289 // draw the shadow 3290 if (this.shadow) { 3291 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}); 3292 } 3293 // draw the line 3294 drawLine(pos, b, pos, e); 3295 } 3296 break; 3297 case 'yaxis': 3298 // draw the grid line 3299 if (t.showGridline) { 3300 drawLine(this._right, pos, this._left, pos); 3301 } 3302 // draw the mark 3303 if (t.showMark && t.mark) { 3304 s = t.markSize; 3305 m = t.mark; 3306 var pos = Math.round(axis.u2p(t.value)) + 0.5; 3307 switch (m) { 3308 case 'outside': 3309 b = this._left-s; 3310 e = this._left; 3311 break; 3312 case 'inside': 3313 b = this._left; 3314 e = this._left+s; 3315 break; 3316 case 'cross': 3317 b = this._left-s; 3318 e = this._left+s; 3319 break; 3320 default: 3321 b = this._left-s; 3322 e = this._left; 3323 break; 3324 } 3325 // draw the shadow 3326 if (this.shadow) { 3327 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}); 3328 } 3329 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); 3330 } 3331 break; 3332 case 'x2axis': 3333 // draw the grid line 3334 if (t.showGridline) { 3335 drawLine(pos, this._bottom, pos, this._top); 3336 } 3337 // draw the mark 3338 if (t.showMark && t.mark) { 3339 s = t.markSize; 3340 m = t.mark; 3341 var pos = Math.round(axis.u2p(t.value)) + 0.5; 3342 switch (m) { 3343 case 'outside': 3344 b = this._top-s; 3345 e = this._top; 3346 break; 3347 case 'inside': 3348 b = this._top; 3349 e = this._top+s; 3350 break; 3351 case 'cross': 3352 b = this._top-s; 3353 e = this._top+s; 3354 break; 3355 default: 3356 b = this._top-s; 3357 e = this._top; 3358 break; 3359 } 3360 // draw the shadow 3361 if (this.shadow) { 3362 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}); 3363 } 3364 drawLine(pos, b, pos, e); 3365 } 3366 break; 3367 case 'y2axis': 3368 // draw the grid line 3369 if (t.showGridline) { 3370 drawLine(this._left, pos, this._right, pos); 3371 } 3372 // draw the mark 3373 if (t.showMark && t.mark) { 3374 s = t.markSize; 3375 m = t.mark; 3376 var pos = Math.round(axis.u2p(t.value)) + 0.5; 3377 switch (m) { 3378 case 'outside': 3379 b = this._right; 3380 e = this._right+s; 3381 break; 3382 case 'inside': 3383 b = this._right-s; 3384 e = this._right; 3385 break; 3386 case 'cross': 3387 b = this._right-s; 3388 e = this._right+s; 3389 break; 3390 default: 3391 b = this._right; 3392 e = this._right+s; 3393 break; 3394 } 3395 // draw the shadow 3396 if (this.shadow) { 3397 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}); 3398 } 3399 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); 3400 } 3401 break; 3402 default: 3403 break; 3404 } 3405 } 3406 } 3407 } 3408 } 3409 // Now draw grid lines for additional y axes 3410 ax = ['y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis']; 3411 for (var i=7; i>0; i--) { 3412 var axis = axes[ax[i-1]]; 3413 var ticks = axis._ticks; 3414 if (axis.show) { 3415 var tn = ticks[axis.numberTicks-1]; 3416 var t0 = ticks[0]; 3417 var left = axis.getLeft(); 3418 var points = [[left, tn.getTop() + tn.getHeight()/2], [left, t0.getTop() + t0.getHeight()/2 + 1.0]]; 3419 // draw the shadow 3420 if (this.shadow) { 3421 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', fill:false, closePath:false}); 3422 } 3423 // draw the line 3424 drawLine(points[0][0], points[0][1], points[1][0], points[1][1], {lineCap:'butt', strokeStyle:axis.borderColor, lineWidth:axis.borderWidth}); 3425 // draw the tick marks 3426 for (var j=ticks.length; j>0; j--) { 3427 var t = ticks[j-1]; 3428 s = t.markSize; 3429 m = t.mark; 3430 var pos = Math.round(axis.u2p(t.value)) + 0.5; 3431 if (t.showMark && t.mark) { 3432 switch (m) { 3433 case 'outside': 3434 b = left; 3435 e = left+s; 3436 break; 3437 case 'inside': 3438 b = left-s; 3439 e = left; 3440 break; 3441 case 'cross': 3442 b = left-s; 3443 e = left+s; 3444 break; 3445 default: 3446 b = left; 3447 e = left+s; 3448 break; 3449 } 3450 points = [[b,pos], [e,pos]]; 3451 // draw the shadow 3452 if (this.shadow) { 3453 this.renderer.shadowRenderer.draw(ctx, points, {lineCap:'butt', lineWidth:this.gridLineWidth*1.5, offset:this.gridLineWidth*0.75, fill:false, closePath:false}); 3454 } 3455 // draw the line 3456 drawLine(b, pos, e, pos, {strokeStyle:axis.borderColor}); 3457 } 3458 } 3459 } 3460 } 3461
3462 ctx.restore(); 3463 } 3464 3465 function drawLine(bx, by, ex, ey, opts) { 3466 ctx.save(); 3467 opts = opts || {}; 3468 if (opts.lineWidth == null || opts.lineWidth != 0){ 3469 $.extend(true, ctx, opts); 3470 ctx.beginPath(); 3471 ctx.moveTo(bx, by); 3472 ctx.lineTo(ex, ey); 3473 ctx.stroke(); 3474 ctx.restore(); 3475 } 3476 } 3477 3478 if (this.shadow) { 3479 var points = [[this._left, this._bottom], [this._right, this._bottom], [this._right, this._top]]; 3480 this.renderer.shadowRenderer.draw(ctx, points); 3481 } 3482 // Now draw border around grid. Use axis border definitions. start at 3483 // upper left and go clockwise. 3484 if (this.borderWidth != 0 && this.drawBorder) { 3485 drawLine (this._left, this._top, this._right, this._top, {lineCap:'round', strokeStyle:axes.x2axis.borderColor, lineWidth:axes.x2axis.borderWidth}); 3486 drawLine (this._right, this._top, this._right, this._bottom, {lineCap:'round', strokeStyle:axes.y2axis.borderColor, lineWidth:axes.y2axis.borderWidth}); 3487 drawLine (this._right, this._bottom, this._left, this._bottom, {lineCap:'round', strokeStyle:axes.xaxis.borderColor, lineWidth:axes.xaxis.borderWidth}); 3488 drawLine (this._left, this._bottom, this._left, this._top, {lineCap:'round', strokeStyle:axes.yaxis.borderColor, lineWidth:axes.yaxis.borderWidth}); 3489 } 3490 // ctx.lineWidth = this.borderWidth; 3491 // ctx.strokeStyle = this.borderColor; 3492 // ctx.strokeRect(this._left, this._top, this._width, this._height); 3493 3494 3495 ctx.restore(); 3496 }; 3497 3498 /** 3499 * Date instance methods 3500 * 3501 * @author Ken Snyder (ken d snyder at gmail dot com) 3502 * @date 2008-09-10 3503 * @version 2.0.2 (http://kendsnyder.com/sandbox/date/) 3504 * @license Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/) 3505 * 3506 * @contributions Chris Leonello 3507 * @comment Bug fix to 12 hour time and additions to handle milliseconds and 3508 * @comment 24 hour time without am/pm suffix 3509 * 3510 */ 3511 3512 // begin by creating a scope for utility variables 3513 3514 // 3515 // pre-calculate the number of milliseconds in a day 3516 // 3517 3518 var day = 24 * 60 * 60 * 1000; 3519 // 3520 // function to add leading zeros 3521 // 3522 var zeroPad = function(number, digits) { 3523 number = String(number); 3524 while (number.length < digits) { 3525 number = '0' + number; 3526 } 3527 return number; 3528 }; 3529 // 3530 // set up integers and functions for adding to a date or subtracting two dates 3531 // 3532 var multipliers = { 3533 millisecond: 1, 3534 second: 1000, 3535 minute: 60 * 1000, 3536 hour: 60 * 60 * 1000, 3537 day: day, 3538 week: 7 * day, 3539 month: { 3540 // add a number of months 3541 add: function(d, number) { 3542 // add any years needed (increments of 12) 3543 multipliers.year.add(d, Math[number > 0 ? 'floor' : 'ceil'](number / 12)); 3544 // ensure that we properly wrap betwen December and January 3545 var prevMonth = d.getMonth() + (number % 12); 3546 if (prevMonth == 12) { 3547 prevMonth = 0; 3548 d.setYear(d.getFullYear() + 1); 3549 } else if (prevMonth == -1) { 3550 prevMonth = 11; 3551 d.setYear(d.getFullYear() - 1); 3552 } 3553 d.setMonth(prevMonth); 3554 }, 3555 // get the number of months between two Date objects (decimal to the nearest day) 3556 diff: function(d1, d2) { 3557 // get the number of years 3558 var diffYears = d1.getFullYear() - d2.getFullYear(); 3559 // get the number of remaining months 3560 var diffMonths = d1.getMonth() - d2.getMonth() + (diffYears * 12); 3561 // get the number of remaining days 3562 var diffDays = d1.getDate() - d2.getDate(); 3563 // return the month difference with the days difference as a decimal 3564 return diffMonths + (diffDays / 30); 3565 } 3566 }, 3567 year: { 3568 // add a number of years 3569 add: function(d, number) { 3570 d.setYear(d.getFullYear() + Math[number > 0 ? 'floor' : 'ceil'](number)); 3571 }, 3572 // get the number of years between two Date objects (decimal to the nearest day) 3573 diff: function(d1, d2) { 3574 return multipliers.month.diff(d1, d2) / 12; 3575 } 3576 } 3577 }; 3578 // 3579 // alias each multiplier with an 's' to allow 'year' and 'years' for example 3580 // 3581 for (var unit in multipliers) { 3582 if (unit.substring(unit.length - 1) != 's') { // IE will iterate newly added properties :| 3583 multipliers[unit + 's'] = multipliers[unit]; 3584 } 3585 } 3586 // 3587 // take a date instance and a format code and return the formatted value 3588 // 3589 var format = function(d, code) {
3590 if (Date.prototype.strftime.formatShortcuts[code]) { 3591 // process any shortcuts recursively 3592 return d.strftime(Date.prototype.strftime.formatShortcuts[code]); 3593 } else { 3594 // get the format code function and toPaddedString() argument 3595 var getter = (Date.prototype.strftime.formatCodes[code] || '').split('.'); 3596 var nbr = d['get' + getter[0]] ? d['get' + getter[0]]() : ''; 3597 // run toPaddedString() if specified 3598 if (getter[1]) { 3599 nbr = zeroPad(nbr, getter[1]); 3600 } 3601 // prepend the leading character 3602 return nbr; 3603 } 3604 }; 3605 // 3606 // Add methods to Date instances 3607 // 3608 var instanceMethods = { 3609 // 3610 // Return a date one day ahead (or any other unit) 3611 // 3612 // @param string unit 3613 // units: year | month | day | week | hour | minute | second | millisecond 3614 // @return object Date 3615 // 3616 succ: function(unit) { 3617 return this.clone().add(1, unit); 3618 }, 3619 // 3620 // Add an arbitrary amount to the currently stored date 3621 // 3622 // @param integer/float number 3623 // @param string unit 3624 // @return object Date (chainable) 3625 // 3626 add: function(number, unit) { 3627 var factor = multipliers[unit] || multipliers.day; 3628 if (typeof factor == 'number') { 3629 this.setTime(this.getTime() + (factor * number)); 3630 } else { 3631 factor.add(this, number); 3632 } 3633 return this; 3634 }, 3635 // 3636 // Find the difference between the current and another date 3637 // 3638 // @param string/object dateObj 3639 // @param string unit 3640 // @param boolean allowDecimal 3641 // @return integer/float 3642 // 3643 diff: function(dateObj, unit, allowDecimal) { 3644 // ensure we have a Date object 3645 dateObj = Date.create(dateObj); 3646 if (dateObj === null) { 3647 return null; 3648 } 3649 // get the multiplying factor integer or factor function 3650 var factor = multipliers[unit] || multipliers.day; 3651 if (typeof factor == 'number') { 3652 // multiply 3653 var unitDiff = (this.getTime() - dateObj.getTime()) / factor; 3654 } else { 3655 // run function 3656 var unitDiff = factor.diff(this, dateObj); 3657 } 3658 // if decimals are not allowed, round toward zero 3659 return (allowDecimal ? unitDiff : Math[unitDiff > 0 ? 'floor' : 'ceil'](unitDiff)); 3660 }, 3661 // 3662 // Convert a date to a string using traditional strftime format codes 3663 // 3664 // @param string formatStr 3665 // @return string 3666 // 3667 strftime: function(formatStr) { 3668 // default the format string to year-month-day 3669 var source = formatStr || '%Y-%m-%d', result = '', match; 3670 // Account for display of time in local time or as UTC time 3671 // var val = ($.jqplot.comfig.convertUTCtoLocaltime) ? this : 3672 // replace each format code 3673 while (source.length > 0) { 3674 if (match = source.match(Date.prototype.strftime.formatCodes.matcher)) { 3675 result += source.slice(0, match.index); 3676 result += (match[1] || '') + format(this, match[2]); 3677 source = source.slice(match.index + match[0].length); 3678 } else { 3679 result += source; 3680 source = ''; 3681 } 3682 } 3683 return result; 3684 }, 3685 // 3686 // Return a proper two-digit year integer 3687 // 3688 // @return integer 3689 // 3690 getShortYear: function() { 3691 return this.getYear() % 100; 3692 }, 3693 // 3694 // Get the number of the current month, 1-12 3695 // 3696 // @return integer 3697 // 3698 getMonthNumber: function() { 3699 return this.getMonth() + 1; 3700 }, 3701 // 3702 // Get the name of the current month 3703 // 3704 // @return string 3705 // 3706 getMonthName: function() { 3707 return Date.MONTHNAMES[this.getMonth()];
3708 }, 3709 // 3710 // Get the abbreviated name of the current month 3711 // 3712 // @return string 3713 // 3714 getAbbrMonthName: function() { 3715 return Date.ABBR_MONTHNAMES[this.getMonth()]; 3716 }, 3717 // 3718 // Get the name of the current week day 3719 // 3720 // @return string 3721 // 3722 getDayName: function() { 3723 return Date.DAYNAMES[this.getDay()]; 3724 }, 3725 // 3726 // Get the abbreviated name of the current week day 3727 // 3728 // @return string 3729 // 3730 getAbbrDayName: function() { 3731 return Date.ABBR_DAYNAMES[this.getDay()]; 3732 }, 3733 // 3734 // Get the ordinal string associated with the day of the month (i.e. st, nd, rd, th) 3735 // 3736 // @return string 3737 // 3738 getDayOrdinal: function() { 3739 return Date.ORDINALNAMES[this.getDate() % 10]; 3740 }, 3741 // 3742 // Get the current hour on a 12-hour scheme 3743 // 3744 // @return integer 3745 // 3746 getHours12: function() { 3747 var hours = this.getHours(); 3748 return hours > 12 ? hours - 12 : (hours == 0 ? 12 : hours); 3749 }, 3750 // 3751 // Get the AM or PM for the current time 3752 // 3753 // @return string 3754 // 3755 getAmPm: function() { 3756 return this.getHours() >= 12 ? 'PM' : 'AM'; 3757 }, 3758 // 3759 // Get the current date as a Unix timestamp 3760 // 3761 // @return integer 3762 // 3763 getUnix: function() { 3764 return Math.round(this.getTime() / 1000, 0); 3765 }, 3766 // 3767 // Get the GMT offset in hours and minutes (e.g. +06:30) 3768 // 3769 // @return string 3770 // 3771 getGmtOffset: function() { 3772 // divide the minutes offset by 60 3773 var hours = this.getTimezoneOffset() / 60; 3774 // decide if we are ahead of or behind GMT 3775 var prefix = hours < 0 ? '+' : '-'; 3776 // remove the negative sign if any 3777 hours = Math.abs(hours); 3778 // add the +/- to the padded number of hours to : to the padded minutes 3779 return prefix + zeroPad(Math.floor(hours), 2) + ':' + zeroPad((hours % 1) * 60, 2); 3780 }, 3781 // 3782 // Get the browser-reported name for the current timezone (e.g. MDT, Mountain Daylight Time) 3783 // 3784 // @return string 3785 // 3786 getTimezoneName: function() { 3787 var match = /(?:\((.+)\)$| ([A-Z]{3}) )/.exec(this.toString()); 3788 return match[1] || match[2] || 'GMT' + this.getGmtOffset(); 3789 }, 3790 // 3791 // Convert the current date to an 8-digit integer (%Y%m%d) 3792 // 3793 // @return int 3794 // 3795 toYmdInt: function() { 3796 return (this.getFullYear() * 10000) + (this.getMonthNumber() * 100) + this.getDate(); 3797 }, 3798 // 3799 // Create a copy of a date object 3800 // 3801 // @return object 3802 // 3803 clone: function() { 3804 return new Date(this.getTime()); 3805 } 3806 }; 3807 for (var name in instanceMethods) { 3808 Date.prototype[name] = instanceMethods[name]; 3809 } 3810 // 3811 // Add static methods to the date object 3812 // 3813 var staticMethods = { 3814 // 3815 // The heart of the date functionality: returns a date object if given a convertable value 3816 // 3817 // @param string/object/integer date 3818 // @return object Date 3819 // 3820 create: function(date) { 3821 // If the passed value is already a date object, return it 3822 if (date instanceof Date) { 3823 return date; 3824 } 3825 // if (typeof date == 'number') return new Date(date); 3826 // If the passed value is an integer, interpret it as a javascript timestamp 3827 if (typeof date == 'number') { 3828 return new Date(date); 3829 } 3830 // If the passed value is a string, attempt to parse it using Date.parse() 3831 var parsable = String(date).replace(/^\s*(.+)\s*$/, '$1'), i = 0, length = Date.create.patterns.length, pattern; 3832 var current = parsable; 3833 while (i < length) { 3834 ms = Date.parse(current); 3835 if (!isNaN(ms)) { 3836 return new Date(ms); 3837 } 3838 pattern = Date.create.patterns[i]; 3839 if (typeof pattern == 'function') { 3840 obj = pattern(current); 3841 if (obj instanceof Date) { 3842 return obj;
3843 } 3844 } else { 3845 current = parsable.replace(pattern[0], pattern[1]); 3846 } 3847 i++; 3848 } 3849 return NaN; 3850 }, 3851 // 3852 // constants representing month names, day names, and ordinal names 3853 // (same names as Ruby Date constants) 3854 // 3855 MONTHNAMES : 'January February March April May June July August September October November December'.split(' '), 3856 ABBR_MONTHNAMES : 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' '), 3857 DAYNAMES : 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' '), 3858 ABBR_DAYNAMES : 'Sun Mon Tue Wed Thu Fri Sat'.split(' '), 3859 ORDINALNAMES : 'th st nd rd th th th th th th'.split(' '), 3860 // 3861 // Shortcut for full ISO-8601 date conversion 3862 // 3863 ISO: '%Y-%m-%dT%H:%M:%S.%N%G', 3864 // 3865 // Shortcut for SQL-type formatting 3866 // 3867 SQL: '%Y-%m-%d %H:%M:%S', 3868 // 3869 // Setter method for month, day, and ordinal names for i18n 3870 // 3871 // @param object newNames 3872 // 3873 daysInMonth: function(year, month) { 3874 if (month == 2) { 3875 return new Date(year, 1, 29).getDate() == 29 ? 29 : 28; 3876 } 3877 return [undefined,31,undefined,31,30,31,30,31,31,30,31,30,31][month]; 3878 } 3879 }; 3880 for (var name in staticMethods) { 3881 Date[name] = staticMethods[name]; 3882 } 3883 // 3884 // format codes for strftime 3885 // 3886 // each code must be an array where the first member is the name of a Date.prototype function 3887 // and optionally a second member indicating the number to pass to Number#toPaddedString() 3888 // 3889 Date.prototype.strftime.formatCodes = { 3890 // 3891 // 2-part regex matcher for format codes 3892 // 3893 // first match must be the character before the code (to account for escaping) 3894 // second match must be the format code character(s) 3895 // 3896 matcher: /()%(#?(%|[a-z]))/i, 3897 // year 3898 Y: 'FullYear', 3899 y: 'ShortYear.2', 3900 // month 3901 m: 'MonthNumber.2', 3902 '#m': 'MonthNumber', 3903 B: 'MonthName', 3904 b: 'AbbrMonthName', 3905 // day 3906 d: 'Date.2', 3907 '#d': 'Date', 3908 e: 'Date', 3909 A: 'DayName', 3910 a: 'AbbrDayName', 3911 w: 'Day', 3912 o: 'DayOrdinal', 3913 // hours 3914 H: 'Hours.2', 3915 '#H': 'Hours', 3916 I: 'Hours12.2', 3917 '#I': 'Hours12', 3918 p: 'AmPm', 3919 // minutes 3920 M: 'Minutes.2', 3921 '#M': 'Minutes', 3922 // seconds 3923 S: 'Seconds.2', 3924 '#S': 'Seconds', 3925 s: 'Unix', 3926 // milliseconds 3927 N: 'Milliseconds.3', 3928 '#N': 'Milliseconds', 3929 // timezone 3930 O: 'TimezoneOffset', 3931 Z: 'TimezoneName', 3932 G: 'GmtOffset' 3933 }; 3934 // 3935 // shortcuts that will be translated into their longer version 3936 // 3937 // be sure that format shortcuts do not refer to themselves: this will cause an infinite loop 3938 // 3939 Date.prototype.strftime.formatShortcuts = { 3940 // date 3941 F: '%Y-%m-%d', 3942 // time 3943 T: '%H:%M:%S', 3944 X: '%H:%M:%S', 3945 // local format date 3946 x: '%m/%d/%y', 3947 D: '%m/%d/%y', 3948 // local format extended 3949 '#c': '%a %b %e %H:%M:%S %Y', 3950 // local format short 3951 v: '%e-%b-%Y', 3952 R: '%H:%M', 3953 r: '%I:%M:%S %p', 3954 // tab and newline 3955 t: '\t', 3956 n: '\n', 3957 '%': '%' 3958 }; 3959 // 3960 // A list of conversion patterns (array arguments sent directly to gsub) 3961 // Add, remove or splice a patterns to customize date parsing ability 3962 // 3963 Date.create.patterns = [ 3964 [/-/g, '/'], // US-style time with dashes => Parsable US-style time 3965 [/st|nd|rd|th/g, ''], // remove st, nd, rd and th 3966 [/(3[01]|[0-2]\d)\s*\.\s*(1[0-2]|0\d)\s*\.\s*([1-9]\d{3})/, '$2/$1/$3'], // World time => Parsable US-style time 3967 [/([1-9]\d{3})\s*-\s*(1[0-2]|0\d)\s*-\s*(3[01]|[0-2]\d)/, '$2/$3/$1'], // ISO-style time => Parsable US-style time 3968 function(str) { // 12-hour or 24 hour time with milliseconds 3969 // var match = str.match(/^(?:(.+)\s+)?([1-9]|1[012])(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d))?\s*(am|pm)\s*$/i); 3970 var match = str.match(/^(?:(.+)\s+)?([012]?\d)(?:\s*\:\s*(\d\d))?(?:\s*\:\s*(\d\d(\.\d*)?))?\s*(am|pm)?\s*$/i); 3971 // opt. date hour opt. minute opt. second opt. msec opt. am or pm 3972 if (match) { 3973 if (match[1]) { 3974 var d = Date.create(match[1]); 3975 if (isNaN(d)) { 3976 return; 3977 } 3978 } else { 3979 var d = new Date(); 3980 d.setMilliseconds(0); 3981 } 3982 var hour = parseFloat(match[2]); 3983 if (match[6]) { 3984 hour = match[6].toLowerCase() == 'am' ? (hour == 12 ? 0 : hour) : (hour == 12 ? 12 : hour + 12); 3985 } 3986 d.setHours(hour, parseInt(match[3] || 0, 10), parseInt(match[4] || 0, 10), ((parseFloat(match[5] || 0)) || 0)*1000); 3987 return d; 3988 } 3989 else { 3990 return str; 3991 } 3992 }, 3993 function(str) { // ISO timestamp with time zone.
3994 var match = str.match(/^(?:(.+))[T|\s+]([012]\d)(?:\:(\d\d))(?:\:(\d\d))(?:\.\d+)([\+\-]\d\d\:\d\d)$/i); 3995 if (match) { 3996 if (match[1]) { 3997 var d = Date.create(match[1]); 3998 if (isNaN(d)) { 3999 return; 4000 } 4001 } else { 4002 var d = new Date(); 4003 d.setMilliseconds(0); 4004 } 4005 var hour = parseFloat(match[2]); 4006 d.setHours(hour, parseInt(match[3], 10), parseInt(match[4], 10), parseFloat(match[5])*1000); 4007 return d; 4008 } 4009 else { 4010 return str; 4011 } 4012 }, 4013 function(str) { 4014 var match = str.match(/^([0-3]?\d)\s*[-\/.\s]{1}\s*([a-zA-Z]{3,9})\s*[-\/.\s]{1}\s*([0-3]?\d)$/); 4015 if (match) { 4016 var d = new Date(); 4017 var y = parseFloat(String(d.getFullYear()).slice(2,4)); 4018 var cent = parseInt(String(d.getFullYear())/100, 10)*100; 4019 var centoffset = 1; 4020 var m1 = parseFloat(match[1]); 4021 var m3 = parseFloat(match[3]); 4022 var ny, nd, nm; 4023 if (m1 > 31) { // first number is a year 4024 nd = match[3]; 4025 if (m1 < y+centoffset) { // if less than 1 year out, assume it is this century. 4026 ny = cent + m1; 4027 } 4028 else { 4029 ny = cent - 100 + m1; 4030 } 4031 } 4032 4033 else { // last number is the year 4034 nd = match[1]; 4035 if (m3 < y+centoffset) { // if less than 1 year out, assume it is this century. 4036 ny = cent + m3; 4037 } 4038 else { 4039 ny = cent - 100 + m3; 4040 } 4041 } 4042 4043 var nm = $.inArray(match[2], Date.ABBR_MONTHNAMES); 4044 4045 if (nm == -1) { 4046 nm = $.inArray(match[2], Date.MONTHNAMES); 4047 } 4048 4049 d.setFullYear(ny, nm, nd); 4050 d.setHours(0,0,0,0); 4051 return d; 4052 } 4053 4054 else { 4055 return str; 4056 } 4057 } 4058 ]; 4059 4060 if ($.jqplot.config.debug) { 4061 $.date = Date.create; 4062 } 4063 4064 // Class: $.jqplot.DivTitleRenderer 4065 // The default title renderer for jqPlot. This class has no options beyond the <Title> class. 4066 $.jqplot.DivTitleRenderer = function() { 4067 }; 4068 4069 $.jqplot.DivTitleRenderer.prototype.init = function(options) { 4070 $.extend(true, this, options); 4071 }; 4072 4073 $.jqplot.DivTitleRenderer.prototype.draw = function() { 4074 var r = this.renderer; 4075 if (!this.text) { 4076 this.show = false; 4077 this._elem = $('<div class="jqplot-title" style="height:0px;width:0px;"></div>'); 4078 } 4079 else if (this.text) { 4080 var color; 4081 if (this.color) { 4082 color = this.color; 4083 } 4084 else if (this.textColor) { 4085 color = this.textColor; 4086 } 4087 // don't trust that a stylesheet is present, set the position. 4088 var styletext = 'position:absolute;top:0px;left:0px;'; 4089 styletext += (this._plotWidth) ? 'width:'+this._plotWidth+'px;' : ''; 4090 styletext += (this.fontSize) ? 'font-size:'+this.fontSize+';' : ''; 4091 styletext += (this.textAlign) ? 'text-align:'+this.textAlign+';' : 'text-align:center;'; 4092 styletext += (color) ? 'color:'+color+';' : ''; 4093 styletext += (this.paddingBottom) ? 'padding-bottom:'+this.paddingBottom+';' : ''; 4094 this._elem = $('<div class="jqplot-title" style="'+styletext+'">'+this.text+'</div>'); 4095 if (this.fontFamily) { 4096 this._elem.css('font-family', this.fontFamily); 4097 } 4098 } 4099 4100 return this._elem; 4101 }; 4102 4103 $.jqplot.DivTitleRenderer.prototype.pack = function() { 4104 // nothing to do here 4105 }; 4106 4107 // Class: $.jqplot.LineRenderer 4108 // The default line renderer for jqPlot, this class has no options beyond the <Series> class. 4109 // Draws series as a line. 4110 $.jqplot.LineRenderer = function(){ 4111 this.shapeRenderer = new $.jqplot.ShapeRenderer(); 4112 this.shadowRenderer = new $.jqplot.ShadowRenderer(); 4113 }; 4114 4115 // called with scope of series. 4116 $.jqplot.LineRenderer.prototype.init = function(options, plot) { 4117 options = options || {}; 4118 var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor}; 4119 4120 delete (options.highlightMouseOver); 4121 delete (options.highlightMouseDown);
4122 delete (options.highlightColor); 4123 4124 $.extend(true, this.renderer, options); 4125 // set the shape renderer options 4126 var opts = {lineJoin:'round', lineCap:'round', fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, closePath:this.fill}; 4127 this.renderer.shapeRenderer.init(opts); 4128 // set the shadow renderer options 4129 // scale the shadowOffset to the width of the line. 4130 if (this.lineWidth > 2.5) { 4131 var shadow_offset = this.shadowOffset* (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6); 4132 // var shadow_offset = this.shadowOffset; 4133 } 4134 // for skinny lines, don't make such a big shadow. 4135 else { 4136 var shadow_offset = this.shadowOffset*Math.atan((this.lineWidth/2.5))/0.785398163; 4137 } 4138 var sopts = {lineJoin:'round', lineCap:'round', fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, closePath:this.fill}; 4139 this.renderer.shadowRenderer.init(sopts); 4140 this._areaPoints = []; 4141 this._boundingBox = [[],[]]; 4142 4143 if (!this.isTrendline && this.fill) { 4144 4145 // prop: highlightMouseOver 4146 // True to highlight slice when moused over. 4147 // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot. 4148 this.highlightMouseOver = true; 4149 // prop: highlightMouseDown 4150 // True to highlight when a mouse button is pressed over a area on a filled plot. 4151 // This will be disabled if highlightMouseOver is true. 4152 this.highlightMouseDown = false; 4153 // prop: highlightColor 4154 // color to use when highlighting an area on a filled plot. 4155 this.highlightColor = null; 4156 // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver 4157 if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) { 4158 lopts.highlightMouseOver = false; 4159 } 4160 4161 $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor}); 4162 4163 if (!this.highlightColor) { 4164 this.highlightColor = $.jqplot.computeHighlightColors(this.fillColor); 4165 } 4166 // turn off traditional highlighter 4167 if (this.highlighter) { 4168 this.highlighter.show = false; 4169 } 4170 plot.postInitHooks.addOnce(postInit); 4171 plot.postDrawHooks.addOnce(postPlotDraw); 4172 plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove); 4173 plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown); 4174 plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp); 4175 plot.eventListenerHooks.addOnce('jqplotClick', handleClick); 4176 plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick); 4177 } 4178 4179 }; 4180 4181 // Method: setGridData 4182 // converts the user data values to grid coordinates and stores them 4183 // in the gridData array. 4184 // Called with scope of a series. 4185 $.jqplot.LineRenderer.prototype.setGridData = function(plot) { 4186 // recalculate the grid data 4187 var xp = this._xaxis.series_u2p; 4188 var yp = this._yaxis.series_u2p; 4189 var data = this._plotData; 4190 var pdata = this._prevPlotData; 4191 this.gridData = []; 4192 this._prevGridData = []; 4193 for (var i=0; i<this.data.length; i++) { 4194 // if not a line series or if no nulls in data, push the converted point onto the array. 4195 if (data[i][0] != null && data[i][1] != null) { 4196 this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]); 4197 } 4198 // else if there is a null, preserve it. 4199 else if (data[i][0] == null) { 4200 this.gridData.push([null, yp.call(this._yaxis, data[i][1])]); 4201 } 4202 else if (data[i][1] == null) { 4203 this.gridData.push([xp.call(this._xaxis, data[i][0]), null]); 4204 } 4205 // if not a line series or if no nulls in data, push the converted point onto the array. 4206 if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) { 4207 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]); 4208 } 4209 // else if there is a null, preserve it. 4210 else if (pdata[i] != null && pdata[i][0] == null) { 4211 this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]); 4212 } 4213 else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) { 4214 this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]); 4215 } 4216 } 4217 }; 4218 4219 // Method: makeGridData 4220 // converts any arbitrary data values to grid coordinates and 4221 // returns them. This method exists so that plugins can use a series' 4222 // linerenderer to generate grid data points without overwriting the 4223 // grid data associated with that series. 4224 // Called with scope of a series. 4225 $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) { 4226 // recalculate the grid data 4227 var xp = this._xaxis.series_u2p; 4228 var yp = this._yaxis.series_u2p; 4229 var gd = []; 4230 var pgd = []; 4231 for (var i=0; i<data.length; i++) { 4232 // if not a line series or if no nulls in data, push the converted point onto the array. 4233 if (data[i][0] != null && data[i][1] != null) { 4234 gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]); 4235 } 4236 // else if there is a null, preserve it. 4237 else if (data[i][0] == null) { 4238 gd.push([null, yp.call(this._yaxis, data[i][1])]); 4239 } 4240 else if (data[i][1] == null) { 4241 gd.push([xp.call(this._xaxis, data[i][0]), null]); 4242 } 4243 } 4244 return gd; 4245 }; 4246 4247 4248 // called within scope of series. 4249 $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options) { 4250 var i; 4251 var opts = (options != undefined) ? options : {}; 4252 var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow; 4253 var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine; 4254 var fill = (opts.fill != undefined) ? opts.fill : this.fill; 4255 var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke; 4256 var xmin, ymin, xmax, ymax; 4257 ctx.save(); 4258 if (gd.length) { 4259 if (showLine) { 4260 // if we fill, we'll have to add points to close the curve. 4261 if (fill) { 4262 if (this.fillToZero) { 4263 // have to break line up into shapes at axis crossings 4264 var negativeColors = new $.jqplot.ColorGenerator(this.negativeSeriesColors); 4265 var negativeColor = negativeColors.get(this.index); 4266 if (! this.useNegativeColors) { 4267 negativeColor = opts.fillStyle; 4268 } 4269 var isnegative = false; 4270 var posfs = opts.fillStyle; 4271 4272 // if stoking line as well as filling, get a copy of line data. 4273 if (fillAndStroke) { 4274 var fasgd = gd.slice(0); 4275 } 4276 // if not stacked, fill down to axis 4277 if (this.index == 0 || !this._stack) { 4278 4279 var tempgd = []; 4280 this._areaPoints = []; 4281 var pyzero = this._yaxis.series_u2p(this.fillToValue); 4282 var pxzero = this._xaxis.series_u2p(this.fillToValue); 4283
4284 if (this.fillAxis == 'y') { 4285 tempgd.push([gd[0][0], pyzero]); 4286 this._areaPoints.push([gd[0][0], pyzero]); 4287 4288 for (var i=0; i<gd.length-1; i++) { 4289 tempgd.push(gd[i]); 4290 this._areaPoints.push(gd[i]); 4291 // do we have an axis crossing? 4292 if (this._plotData[i][1] * this._plotData[i+1][1] < 0) { 4293 if (this._plotData[i][1] < 0) { 4294 isnegative = true; 4295 opts.fillStyle = negativeColor; 4296 } 4297 else { 4298 isnegative = false; 4299 opts.fillStyle = posfs; 4300 } 4301 4302 var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]); 4303 tempgd.push([xintercept, pyzero]); 4304 this._areaPoints.push([xintercept, pyzero]); 4305 // now draw this shape and shadow. 4306 if (shadow) { 4307 this.renderer.shadowRenderer.draw(ctx, tempgd, opts); 4308 } 4309 this.renderer.shapeRenderer.draw(ctx, tempgd, opts); 4310 // now empty temp array and continue 4311 tempgd = [[xintercept, pyzero]]; 4312 // this._areaPoints = [[xintercept, pyzero]]; 4313 } 4314 } 4315 if (this._plotData[gd.length-1][1] < 0) { 4316 isnegative = true; 4317 opts.fillStyle = negativeColor; 4318 } 4319 else { 4320 isnegative = false; 4321 opts.fillStyle = posfs; 4322 } 4323 tempgd.push(gd[gd.length-1]); 4324 this._areaPoints.push(gd[gd.length-1]); 4325 tempgd.push([gd[gd.length-1][0], pyzero]); 4326 this._areaPoints.push([gd[gd.length-1][0], pyzero]); 4327 } 4328 // now draw this shape and shadow. 4329 if (shadow) { 4330 this.renderer.shadowRenderer.draw(ctx, tempgd, opts); 4331 } 4332 this.renderer.shapeRenderer.draw(ctx, tempgd, opts); 4333 4334 4335 // var gridymin = this._yaxis.series_u2p(0); 4336 // // IE doesn't return new length on unshift 4337 // gd.unshift([gd[0][0], gridymin]); 4338 // len = gd.length; 4339 // gd.push([gd[len - 1][0], gridymin]); 4340 } 4341 // if stacked, fill to line below 4342 else { 4343 var prev = this._prevGridData; 4344 for (var i=prev.length; i>0; i--) { 4345 gd.push(prev[i-1]); 4346 // this._areaPoints.push(prev[i-1]); 4347 } 4348 if (shadow) { 4349 this.renderer.shadowRenderer.draw(ctx, gd, opts); 4350 } 4351 this._areaPoints = gd; 4352 this.renderer.shapeRenderer.draw(ctx, gd, opts); 4353 } 4354 }
4355 ///////////////////////// 4356 // Not filled to zero 4357 //////////////////////// 4358 else { 4359 // if stoking line as well as filling, get a copy of line data. 4360 if (fillAndStroke) { 4361 var fasgd = gd.slice(0); 4362 } 4363 // if not stacked, fill down to axis 4364 if (this.index == 0 || !this._stack) { 4365 // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2; 4366 var gridymin = ctx.canvas.height; 4367 // IE doesn't return new length on unshift 4368 gd.unshift([gd[0][0], gridymin]); 4369 len = gd.length; 4370 gd.push([gd[len - 1][0], gridymin]); 4371 } 4372 // if stacked, fill to line below 4373 else { 4374 var prev = this._prevGridData; 4375 for (var i=prev.length; i>0; i--) { 4376 gd.push(prev[i-1]); 4377 } 4378 } 4379 this._areaPoints = gd; 4380 4381 if (shadow) { 4382 this.renderer.shadowRenderer.draw(ctx, gd, opts); 4383 } 4384 4385 this.renderer.shapeRenderer.draw(ctx, gd, opts); 4386 } 4387 if (fillAndStroke) { 4388 var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false}); 4389 this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
4390 ////////// 4391 // TODO: figure out some way to do shadows nicely 4392 // if (shadow) { 4393 // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts); 4394 // } 4395 // now draw the markers 4396 if (this.markerRenderer.show) { 4397 for (i=0; i<fasgd.length; i++) { 4398 this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions); 4399 } 4400 } 4401 } 4402 } 4403 else { 4404 if (shadow) { 4405 this.renderer.shadowRenderer.draw(ctx, gd, opts); 4406 } 4407 4408 this.renderer.shapeRenderer.draw(ctx, gd, opts); 4409 } 4410 } 4411 // calculate the bounding box 4412 var xmin = xmax = ymin = ymax = null; 4413 for (i=0; i<this._areaPoints.length; i++) { 4414 var p = this._areaPoints[i]; 4415 if (xmin > p[0] || xmin == null) { 4416 xmin = p[0]; 4417 } 4418 if (ymax < p[1] || ymax == null) { 4419 ymax = p[1]; 4420 } 4421 if (xmax < p[0] || xmax == null) { 4422 xmax = p[0]; 4423 } 4424 if (ymin > p[1] || ymin == null) { 4425 ymin = p[1]; 4426 } 4427 } 4428 this._boundingBox = [[xmin, ymax], [xmax, ymin]]; 4429 4430 // now draw the markers 4431 if (this.markerRenderer.show && !fill) { 4432 for (i=0; i<gd.length; i++) { 4433 if (gd[i][0] != null && gd[i][1] != null) { 4434 this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions); 4435 } 4436 } 4437 } 4438 } 4439 4440 ctx.restore(); 4441 }; 4442 4443 $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) { 4444 // This is a no-op, shadows drawn with lines. 4445 }; 4446 4447 // called with scope of plot. 4448 // make sure to not leave anything highlighted. 4449 function postInit(target, data, options) { 4450 for (i=0; i<this.series.length; i++) { 4451 if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) { 4452 // don't allow mouseover and mousedown at same time. 4453 if (this.series[i].highlightMouseOver) { 4454 this.series[i].highlightMouseDown = false; 4455 } 4456 } 4457 } 4458 this.target.bind('mouseout', {plot:this}, function (ev) { unhighlight(ev.data.plot); }); 4459 } 4460 4461 // called within context of plot 4462 // create a canvas which we can draw on. 4463 // insert it before the eventCanvas, so eventCanvas will still capture events. 4464 function postPlotDraw() { 4465 this.plugins.lineRenderer = {highlightedSeriesIndex:null}; 4466 this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas(); 4467 4468 this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions)); 4469 var hctx = this.plugins.lineRenderer.highlightCanvas.setContext(); 4470 } 4471 4472 function highlight (plot, sidx, pidx, points) { 4473 var s = plot.series[sidx]; 4474 var canvas = plot.plugins.lineRenderer.highlightCanvas; 4475 canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height); 4476 s._highlightedPoint = pidx; 4477 plot.plugins.lineRenderer.highlightedSeriesIndex = sidx; 4478 var opts = {fillStyle: s.highlightColor}; 4479 s.renderer.shapeRenderer.draw(canvas._ctx, points, opts); 4480 } 4481 4482 function unhighlight (plot) { 4483 var canvas = plot.plugins.lineRenderer.highlightCanvas; 4484 canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height); 4485 for (var i=0; i<plot.series.length; i++) { 4486 plot.series[i]._highlightedPoint = null; 4487 } 4488 plot.plugins.lineRenderer.highlightedSeriesIndex = null; 4489 plot.target.trigger('jqplotDataUnhighlight'); 4490 } 4491 4492 4493 function handleMove(ev, gridpos, datapos, neighbor, plot) { 4494 if (neighbor) { 4495 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 4496 var evt1 = jQuery.Event('jqplotDataMouseOver'); 4497 evt1.pageX = ev.pageX; 4498 evt1.pageY = ev.pageY; 4499 plot.target.trigger(evt1, ins); 4500 if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) { 4501 var evt = jQuery.Event('jqplotDataHighlight'); 4502 evt.pageX = ev.pageX; 4503 evt.pageY = ev.pageY; 4504 plot.target.trigger(evt, ins);
4505 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); 4506 } 4507 } 4508 else if (neighbor == null) { 4509 unhighlight (plot); 4510 } 4511 } 4512 4513 function handleMouseDown(ev, gridpos, datapos, neighbor, plot) { 4514 if (neighbor) { 4515 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 4516 if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) { 4517 var evt = jQuery.Event('jqplotDataHighlight'); 4518 evt.pageX = ev.pageX; 4519 evt.pageY = ev.pageY; 4520 plot.target.trigger(evt, ins); 4521 highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points); 4522 } 4523 } 4524 else if (neighbor == null) { 4525 unhighlight (plot); 4526 } 4527 } 4528 4529 function handleMouseUp(ev, gridpos, datapos, neighbor, plot) { 4530 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex; 4531 if (idx != null && plot.series[idx].highlightMouseDown) { 4532 unhighlight(plot); 4533 } 4534 } 4535 4536 function handleClick(ev, gridpos, datapos, neighbor, plot) { 4537 if (neighbor) { 4538 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 4539 var evt = jQuery.Event('jqplotDataClick'); 4540 evt.pageX = ev.pageX; 4541 evt.pageY = ev.pageY; 4542 plot.target.trigger(evt, ins); 4543 } 4544 } 4545 4546 function handleRightClick(ev, gridpos, datapos, neighbor, plot) { 4547 if (neighbor) { 4548 var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data]; 4549 var idx = plot.plugins.lineRenderer.highlightedSeriesIndex; 4550 if (idx != null && plot.series[idx].highlightMouseDown) { 4551 unhighlight(plot); 4552 } 4553 var evt = jQuery.Event('jqplotDataRightClick'); 4554 evt.pageX = ev.pageX; 4555 evt.pageY = ev.pageY; 4556 plot.target.trigger(evt, ins); 4557 } 4558 } 4559 4560 4561 // class: $.jqplot.LinearAxisRenderer 4562 // The default jqPlot axis renderer, creating a numeric axis. 4563 // The renderer has no additional options beyond the <Axis> object. 4564 $.jqplot.LinearAxisRenderer = function() { 4565 }; 4566 4567 // called with scope of axis object. 4568 $.jqplot.LinearAxisRenderer.prototype.init = function(options){ 4569 $.extend(true, this, options); 4570 var db = this._dataBounds; 4571 // Go through all the series attached to this axis and find 4572 // the min/max bounds for this axis. 4573 for (var i=0; i<this._series.length; i++) { 4574 var s = this._series[i]; 4575 var d = s._plotData; 4576 4577 for (var j=0; j<d.length; j++) { 4578 if (this.name == 'xaxis' || this.name == 'x2axis') { 4579 if ((d[j][0] != null && d[j][0] < db.min) || db.min == null) { 4580 db.min = d[j][0]; 4581 } 4582 if ((d[j][0] != null && d[j][0] > db.max) || db.max == null) { 4583 db.max = d[j][0]; 4584 } 4585 } 4586 else { 4587 if ((d[j][1] != null && d[j][1] < db.min) || db.min == null) { 4588 db.min = d[j][1]; 4589 } 4590 if ((d[j][1] != null && d[j][1] > db.max) || db.max == null) { 4591 db.max = d[j][1]; 4592 } 4593 } 4594 } 4595 } 4596 }; 4597 4598 // called with scope of axis 4599 $.jqplot.LinearAxisRenderer.prototype.draw = function(ctx) { 4600 if (this.show) { 4601 // populate the axis label and value properties. 4602 // createTicks is a method on the renderer, but 4603 // call it within the scope of the axis. 4604 this.renderer.createTicks.call(this); 4605 // fill a div with axes labels in the right direction. 4606 // Need to pregenerate each axis to get it's bounds and 4607 // position it and the labels correctly on the plot. 4608 var dim=0; 4609 var temp; 4610 // Added for theming. 4611 if (this._elem) { 4612 this._elem.empty(); 4613 } 4614 4615 this._elem = $('<div class="jqplot-axis jqplot-'+this.name+'" style="position:absolute;"></div>'); 4616 4617 if (this.name == 'xaxis' || this.name == 'x2axis') { 4618 this._elem.width(this._plotDimensions.width); 4619 } 4620 else { 4621 this._elem.height(this._plotDimensions.height); 4622 } 4623 4624 // create a _label object. 4625 this.labelOptions.axis = this.name; 4626 this._label = new this.labelRenderer(this.labelOptions); 4627 if (this._label.show) { 4628 var elem = this._label.draw(ctx); 4629 elem.appendTo(this._elem); 4630 } 4631 4632 if (this.showTicks) { 4633 var t = this._ticks; 4634 for (var i=0; i<t.length; i++) { 4635 var tick = t[i]; 4636 if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) { 4637 var elem = tick.draw(ctx); 4638 elem.appendTo(this._elem); 4639 } 4640 } 4641 } 4642 } 4643 return this._elem; 4644 }; 4645 4646 // called with scope of an axis 4647 $.jqplot.LinearAxisRenderer.prototype.reset = function() { 4648 this.min = this._min; 4649 this.max = this._max; 4650 this.tickInterval = this._tickInterval; 4651 this.numberTicks = this._numberTicks; 4652 // this._ticks = this.__ticks; 4653 }; 4654 4655 // called with scope of axis 4656 $.jqplot.LinearAxisRenderer.prototype.set = function() { 4657 var dim = 0; 4658 var temp; 4659 var w = 0; 4660 var h = 0; 4661 var lshow = (this._label == null) ? false : this._label.show; 4662 if (this.show && this.showTicks) { 4663 var t = this._ticks; 4664 for (var i=0; i<t.length; i++) { 4665 var tick = t[i]; 4666 if (tick.showLabel && (!tick.isMinorTick || this.showMinorTicks)) { 4667 if (this.name == 'xaxis' || this.name == 'x2axis') { 4668 temp = tick._elem.outerHeight(true); 4669 } 4670 else { 4671 temp = tick._elem.outerWidth(true); 4672 } 4673 if (temp > dim) { 4674 dim = temp; 4675 } 4676 } 4677 } 4678 4679 if (lshow) { 4680 w = this._label._elem.outerWidth(true); 4681 h = this._label._elem.outerHeight(true); 4682 } 4683 if (this.name == 'xaxis') { 4684 dim = dim + h; 4685 this._elem.css({'height':dim+'px', left:'0px', bottom:'0px'}); 4686 } 4687 else if (this.name == 'x2axis') { 4688 dim = dim + h; 4689 this._elem.css({'height':dim+'px', left:'0px', top:'0px'}); 4690 } 4691 else if (this.name == 'yaxis') { 4692 dim = dim + w; 4693 this._elem.css({'width':dim+'px', left:'0px', top:'0px'}); 4694 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { 4695 this._label._elem.css('width', w+'px'); 4696 } 4697 } 4698 else { 4699 dim = dim + w; 4700 this._elem.css({'width':dim+'px', right:'0px', top:'0px'}); 4701 if (lshow && this._label.constructor == $.jqplot.AxisLabelRenderer) { 4702 this._label._elem.css('width', w+'px'); 4703 } 4704 } 4705 } 4706 }; 4707 4708 // called with scope of axis 4709 $.jqplot.LinearAxisRenderer.prototype.createTicks = function() { 4710 // we're are operating on an axis here 4711 var ticks = this._ticks; 4712 var userTicks = this.ticks; 4713 var name = this.name; 4714 // databounds were set on axis initialization. 4715 var db = this._dataBounds; 4716 var dim, interval; 4717 var min, max; 4718 var pos1, pos2; 4719 var tt, i; 4720 // get a copy of user's settings for min/max. 4721 var userMin = this.min; 4722 var userMax = this.max; 4723 var userNT = this.numberTicks; 4724 var userTI = this.tickInterval; 4725 4726 // if we already have ticks, use them. 4727 // ticks must be in order of increasing value. 4728 4729 if (userTicks.length) { 4730 // ticks could be 1D or 2D array of [val, val, ,,,] or [[val, label], [val, label], ...] or mixed 4731 for (i=0; i<userTicks.length; i++){ 4732 var ut = userTicks[i]; 4733 var t = new this.tickRenderer(this.tickOptions); 4734 if (ut.constructor == Array) { 4735 t.value = ut[0]; 4736 t.label = ut[1]; 4737 if (!this.showTicks) { 4738 t.showLabel = false; 4739 t.showMark = false; 4740 } 4741 else if (!this.showTickMarks) { 4742 t.showMark = false; 4743 } 4744 t.setTick(ut[0], this.name); 4745 this._ticks.push(t); 4746 } 4747 4748 else { 4749 t.value = ut; 4750 if (!this.showTicks) { 4751 t.showLabel = false; 4752 t.showMark = false; 4753 } 4754 else if (!this.showTickMarks) { 4755 t.showMark = false; 4756 } 4757 t.setTick(ut, this.name); 4758 this._ticks.push(t); 4759 } 4760 } 4761 this.numberTicks = userTicks.length; 4762 this.min = this._ticks[0].value; 4763 this.max = this._ticks[this.numberTicks-1].value; 4764 this.tickInterval = (this.max - this.min) / (this.numberTicks - 1); 4765 } 4766 4767 // we don't have any ticks yet, let's make some! 4768 else { 4769 if (name == 'xaxis' || name == 'x2axis') { 4770 dim = this._plotDimensions.width; 4771 } 4772 else { 4773 dim = this._plotDimensions.height; 4774 } 4775 4776 // if min, max and number of ticks specified, user can't specify interval. 4777 if (!this.autoscale && this.min != null && this.max != null && this.numberTicks != null) { 4778 this.tickInterval = null; 4779 } 4780 4781 // if max, min, and interval specified and interval won't fit, ignore interval. 4782 // if (this.min != null && this.max != null && this.tickInterval != null) { 4783 // if (parseInt((this.max-this.min)/this.tickInterval, 10) != (this.max-this.min)/this.tickInterval) { 4784 // this.tickInterval = null; 4785 // } 4786 // } 4787 4788 min = ((this.min != null) ? this.min : db.min); 4789 max = ((this.max != null) ? this.max : db.max); 4790 4791 // if min and max are same, space them out a bit 4792 if (min == max) { 4793 var adj = 0.05; 4794 if (min > 0) { 4795 adj = Math.max(Math.log(min)/Math.LN10, 0.05); 4796 } 4797 min -= adj; 4798 max += adj; 4799 } 4800 4801 var range = max - min; 4802 var rmin, rmax; 4803 var temp; 4804 4805 // autoscale. Can't autoscale if min or max is supplied.
4806 // Will use numberTicks and tickInterval if supplied. Ticks 4807 // across multiple axes may not line up depending on how 4808 // bars are to be plotted. 4809 if (this.autoscale && this.min == null && this.max == null) { 4810 var rrange, ti, margin; 4811 var forceMinZero = false; 4812 var forceZeroLine = false; 4813 var intervals = {min:null, max:null, average:null, stddev:null}; 4814 // if any series are bars, or if any are fill to zero, and if this 4815 // is the axis to fill toward, check to see if we can start axis at zero. 4816 for (var i=0; i<this._series.length; i++) { 4817 var s = this._series[i]; 4818 var faname = (s.fillAxis == 'x') ? s._xaxis.name : s._yaxis.name; 4819 // check to see if this is the fill axis 4820 if (this.name == faname) { 4821 var vals = s._plotValues[s.fillAxis]; 4822 var vmin = vals[0]; 4823 var vmax = vals[0]; 4824 for (var j=1; j<vals.length; j++) { 4825 if (vals[j] < vmin) { 4826 vmin = vals[j]; 4827 } 4828 else if (vals[j] > vmax) { 4829 vmax = vals[j]; 4830 } 4831 } 4832 var dp = (vmax - vmin) / vmax; 4833 // is this sries a bar? 4834 if (s.renderer.constructor == $.jqplot.BarRenderer) { 4835 // if no negative values and could also check range. 4836 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { 4837 forceMinZero = true; 4838 } 4839 else { 4840 forceMinZero = false; 4841 if (s.fill && s.fillToZero && vmin < 0 && vmax > 0) { 4842 forceZeroLine = true; 4843 } 4844 else { 4845 forceZeroLine = false; 4846 } 4847 } 4848 } 4849 4850 // if not a bar and filling, use appropriate method. 4851 else if (s.fill) { 4852 if (vmin >= 0 && (s.fillToZero || dp > 0.1)) { 4853 forceMinZero = true; 4854 } 4855 else if (vmin < 0 && vmax > 0 && s.fillToZero) { 4856 forceMinZero = false; 4857 forceZeroLine = true; 4858 } 4859 else { 4860 forceMinZero = false; 4861 forceZeroLine = false; 4862 } 4863 } 4864 4865 // if not a bar and not filling, only change existing state 4866 // if it doesn't make sense 4867 else if (vmin < 0) { 4868 forceMinZero = false; 4869 } 4870 } 4871 } 4872 4873 // check if we need make axis min at 0. 4874 if (forceMinZero) { 4875 // compute number of ticks 4876 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); 4877 this.min = 0; 4878 userMin = 0; 4879 // what order is this range? 4880 // what tick interval does that give us? 4881 ti = max/(this.numberTicks-1); 4882 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); 4883 if (ti/temp == parseInt(ti/temp, 10)) { 4884 ti += temp; 4885 }
4886 this.tickInterval = Math.ceil(ti/temp) * temp; 4887 this.max = this.tickInterval * (this.numberTicks - 1); 4888 } 4889 4890 // check if we need to make sure there is a tick at 0. 4891 else if (forceZeroLine) { 4892 // compute number of ticks 4893 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); 4894 var ntmin = Math.ceil(Math.abs(min)/range*(this.numberTicks-1)); 4895 var ntmax = this.numberTicks - 1 - ntmin; 4896 ti = Math.max(Math.abs(min/ntmin), Math.abs(max/ntmax)); 4897 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); 4898 this.tickInterval = Math.ceil(ti/temp) * temp; 4899 this.max = this.tickInterval * ntmax; 4900 this.min = -this.tickInterval * ntmin; 4901 } 4902 4903 // if nothing else, do autoscaling which will try to line up ticks across axes. 4904 else { 4905 if (this.numberTicks == null){ 4906 if (this.tickInterval) { 4907 this.numberTicks = 3 + Math.ceil(range / this.tickInterval); 4908 } 4909 else { 4910 this.numberTicks = 2 + Math.ceil((dim-(this.tickSpacing-1))/this.tickSpacing); 4911 } 4912 } 4913 4914 if (this.tickInterval == null) { 4915 // get a tick interval 4916 ti = range/(this.numberTicks - 1); 4917 4918 if (ti < 1) { 4919 temp = Math.pow(10, Math.abs(Math.floor(Math.log(ti)/Math.LN10))); 4920 } 4921 else { 4922 temp = 1; 4923 } 4924 this.tickInterval = Math.ceil(ti*temp*this.pad)/temp; 4925 } 4926 else { 4927 temp = 1 / this.tickInterval; 4928 } 4929 4930 // try to compute a nicer, more even tick interval 4931 // temp = Math.pow(10, Math.floor(Math.log(ti)/Math.LN10)); 4932 // this.tickInterval = Math.ceil(ti/temp) * temp; 4933 rrange = this.tickInterval * (this.numberTicks - 1); 4934 margin = (rrange - range)/2; 4935 4936 if (this.min == null) { 4937 this.min = Math.floor(temp*(min-margin))/temp; 4938 } 4939 if (this.max == null) { 4940 this.max = this.min + rrange; 4941 } 4942 } 4943 } 4944 4945 // Use the default algorithm which pads each axis to make the chart 4946 // centered nicely on the grid. 4947 else { 4948 rmin = (this.min != null) ? this.min : min - range*(this.padMin - 1); 4949 rmax = (this.max != null) ? this.max : max + range*(this.padMax - 1); 4950 this.min = rmin; 4951 this.max = rmax; 4952 range = this.max - this.min; 4953 4954 if (this.numberTicks == null){ 4955 // if tickInterval is specified by user, we will ignore computed maximum. 4956 // max will be equal or greater to fit even # of ticks. 4957 if (this.tickInterval != null) { 4958 this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval)+1; 4959 this.max = this.min + this.tickInterval*(this.numberTicks-1); 4960 } 4961 else if (dim > 100) { 4962 this.numberTicks = parseInt(3+(dim-100)/75, 10); 4963 } 4964 else { 4965 this.numberTicks = 2; 4966 } 4967 } 4968 4969 if (this.tickInterval == null) { 4970 this.tickInterval = range / (this.numberTicks-1); 4971 } 4972 } 4973 4974 if (this.renderer.constructor == $.jqplot.LinearAxisRenderer) { 4975 // fix for misleading tick display with small range and low precision. 4976 range = this.max - this.min; 4977 // figure out precision 4978 var temptick = new this.tickRenderer(this.tickOptions); 4979 // use the tick formatString or, the default. 4980 var fs = temptick.formatString || $.jqplot.config.defaultTickFormatString; 4981 var fs = fs.match($.jqplot.sprintf.regex)[0]; 4982 var precision = 0; 4983 if (fs) { 4984 if (fs.search(/[fFeEgGpP]/) > -1) { 4985 var m = fs.match(/\%\.(\d{0,})?[eEfFgGpP]/); 4986 if (m) precision = parseInt(m[1], 10); 4987 else precision = 6; 4988 } 4989 else if (fs.search(/[di]/) > -1) { 4990 precision = 0; 4991 } 4992 // fact will be <= 1; 4993 var fact = Math.pow(10, -precision); 4994 if (this.tickInterval < fact) { 4995 // need to correct underrange 4996 if (userNT == null && userTI == null) { 4997 this.tickInterval = fact; 4998 if (userMax == null && userMin == null) { 4999 // this.min = Math.floor((this._dataBounds.min - this.tickInterval)/fact) * fact; 5000 this.min = Math.floor(this._dataBounds.min/fact) * fact; 5001 if (this.min == this._dataBounds.min) { 5002 this.min = this._dataBounds.min - this.tickInterval; 5003 } 5004 // this.max = Math.ceil((this._dataBounds.max + this.tickInterval)/fact) * fact; 5005 this.max = Math.ceil(this._dataBounds.max/fact) * fact; 5006 if (this.max == this._dataBounds.max) { 5007 this.max = this._dataBounds.max + this.tickInterval; 5008 } 5009 var n = (this.max - this.min)/this.tickInterval; 5010 n = n.toFixed(11); 5011 n = Math.ceil(n); 5012 this.numberTicks = n + 1; 5013 } 5014 else if (userMax == null) { 5015 // add one tick for top of range. 5016 var n = (this._dataBounds.max - this.min) / this.tickInterval; 5017 n = n.toFixed(11); 5018 this.numberTicks = Math.ceil(n) + 2; 5019 this.max = this.min + this.tickInterval * (this.numberTicks-1); 5020 } 5021 else if (userMin == null) { 5022 // add one tick for bottom of range. 5023 var n = (this.max - this._dataBounds.min) / this.tickInterval; 5024 n = n.toFixed(11); 5025 this.numberTicks = Math.ceil(n) + 2; 5026 this.min = this.max - this.tickInterval * (this.numberTicks-1); 5027 } 5028 else { 5029 // calculate a number of ticks so max is within axis scale 5030 this.numberTicks = Math.ceil((userMax - userMin)/this.tickInterval) + 1; 5031 // if user's min and max don't fit evenly in ticks, adju
5031st. 5032 // This takes care of cases such as user min set to 0, max set to 3.5 but tick 5033 // format string set to %d (integer ticks) 5034 this.min = Math.floor(userMin*Math.pow(10, precision))/Math.pow(10, precision); 5035 this.max = Math.ceil(userMax*Math.pow(10, precision))/Math.pow(10, precision); 5036 // this.max = this.min + this.tickInterval*(this.numberTicks-1); 5037 this.numberTicks = Math.ceil((this.max - this.min)/this.tickInterval) + 1; 5038 } 5039 } 5040 } 5041 } 5042 } 5043 5044 5045 5046 for (var i=0; i<this.numberTicks; i++){ 5047 tt = this.min + i * this.tickInterval; 5048 var t = new this.tickRenderer(this.tickOptions); 5049 // var t = new $.jqplot.AxisTickRenderer(this.tickOptions); 5050 if (!this.showTicks) { 5051 t.showLabel = false; 5052 t.showMark = false; 5053 } 5054 else if (!this.showTickMarks) { 5055 t.showMark = false; 5056 } 5057 t.setTick(tt, this.name); 5058 this._ticks.push(t); 5059 } 5060 } 5061 }; 5062 5063 // called with scope of axis 5064 $.jqplot.LinearAxisRenderer.prototype.pack = function(pos, offsets) { 5065 var ticks = this._ticks; 5066 var max = this.max; 5067 var min = this.min; 5068 var offmax = offsets.max; 5069 var offmin = offsets.min; 5070 var lshow = (this._label == null) ? false : this._label.show; 5071 5072 for (var p in pos) { 5073 this._elem.css(p, pos[p]); 5074 } 5075 5076 this._offsets = offsets; 5077 // pixellength will be + for x axes and - for y axes becasue pixels always measured from top left. 5078 var pixellength = offmax - offmin; 5079 var unitlength = max - min; 5080 5081 // point to unit and unit to point conversions references to Plot DOM element top left corner. 5082 this.p2u = function(p){ 5083 return (p - offmin) * unitlength / pixellength + min; 5084 }; 5085 5086 this.u2p = function(u){ 5087 return (u - min) * pixellength / unitlength + offmin; 5088 }; 5089 5090 if (this.name == 'xaxis' || this.name == 'x2axis'){ 5091 this.series_u2p = function(u){ 5092 return (u - min) * pixellength / unitlength; 5093 }; 5094 this.series_p2u = function(p){ 5095 return p * unitlength / pixellength + min; 5096 }; 5097 } 5098 5099 else { 5100 this.series_u2p = function(u){ 5101 return (u - max) * pixellength / unitlength; 5102 }; 5103 this.series_p2u = function(p){ 5104 return p * unitlength / pixellength + max; 5105 }; 5106 } 5107 5108 if (this.show) { 5109 if (this.name == 'xaxis' || this.name == 'x2axis') { 5110 for (i=0; i<ticks.length; i++) { 5111 var t = ticks[i]; 5112 if (t.show && t.showLabel) { 5113 var shim; 5114 5115 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) { 5116 // will need to adjust auto positioning based on which axis this is. 5117 var temp = (this.name == 'xaxis') ? 1 : -1; 5118 switch (t.labelPosition) { 5119 case 'auto': 5120 // position at end 5121 if (temp * t.angle < 0) { 5122 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 5123 } 5124 // position at start 5125 else { 5126 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 5127 } 5128 break; 5129 case 'end':
5130 shim = -t.getWidth() + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 5131 break; 5132 case 'start': 5133 shim = -t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 5134 break; 5135 case 'middle': 5136 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 5137 break; 5138 default: 5139 shim = -t.getWidth()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 5140 break; 5141 } 5142 } 5143 else { 5144 shim = -t.getWidth()/2; 5145 } 5146 var val = this.u2p(t.value) + shim + 'px'; 5147 t._elem.css('left', val); 5148 t.pack(); 5149 } 5150 } 5151 if (lshow) { 5152 var w = this._label._elem.outerWidth(true); 5153 this._label._elem.css('left', offmin + pixellength/2 - w/2 + 'px'); 5154 if (this.name == 'xaxis') { 5155 this._label._elem.css('bottom', '0px'); 5156 } 5157 else { 5158 this._label._elem.css('top', '0px'); 5159 } 5160 this._label.pack(); 5161 } 5162 } 5163 else { 5164 for (i=0; i<ticks.length; i++) { 5165 var t = ticks[i]; 5166 if (t.show && t.showLabel) { 5167 var shim; 5168 if (t.constructor == $.jqplot.CanvasAxisTickRenderer && t.angle) { 5169 // will need to adjust auto positioning based on which axis this is. 5170 var temp = (this.name == 'yaxis') ? 1 : -1; 5171 switch (t.labelPosition) { 5172 case 'auto': 5173 // position at end 5174 case 'end': 5175 if (temp * t.angle < 0) { 5176 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; 5177 } 5178 else { 5179 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; 5180 } 5181 break; 5182 case 'start': 5183 if (t.angle > 0) { 5184 shim = -t._textRenderer.height * Math.cos(-t._textRenderer.angle) / 2; 5185 } 5186 else { 5187 shim = -t.getHeight() + t._textRenderer.height * Math.cos(t._textRenderer.angle) / 2; 5188 } 5189 break; 5190 case 'middle': 5191 // if (t.angle > 0) { 5192 // shim = -t.getHeight()/2 + t._textRenderer.height * Math.sin(-t._textRenderer.angle) / 2; 5193 // } 5194 // else { 5195 // shim = -t.getHeight()/2 - t._textRenderer.height * Math.sin(t._textRenderer.angle) / 2; 5196 // } 5197 shim = -t.getHeight()/2; 5198 break; 5199 default: 5200 shim = -t.getHeight()/2; 5201 break; 5202 } 5203 } 5204 else { 5205 shim = -t.getHeight()/2; 5206 } 5207 5208 var val = this.u2p(t.value) + shim + 'px'; 5209 t._elem.css('top', val); 5210 t.pack(); 5211 } 5212 } 5213 if (lshow) { 5214 var h = this._label._elem.outerHeight(true); 5215 this._label._elem.css('top', offmax - pixellength/2 - h/2 + 'px'); 5216 if (this.name == 'yaxis') { 5217 this._label._elem.css('left', '0px'); 5218 } 5219 else { 5220 this._label._elem.css('right', '0px'); 5221 } 5222 this._label.pack(); 5223 } 5224 } 5225 } 5226 }; 5227 5228 5229 // class: $.jqplot.MarkerRenderer 5230 // The default jqPlot marker renderer, rendering the points on the line. 5231 $.jqplot.MarkerRenderer = function(options){ 5232 // Group: Properties 5233 5234 // prop: show 5235 // wether or not to show the marker. 5236 this.show = true; 5237 // prop: style 5238 // One of diamond, circle, square, x, plus, dash, filledDiamond, filledCircle, filledSquare 5239 this.style = 'filledCircle'; 5240 // prop: lineWidth 5241 // size of the line for non-filled markers. 5242 this.lineWidth = 2; 5243 // prop: size 5244 // Size of the marker (diameter or circle, length of edge of square, etc.) 5245 this.size = 9.0; 5246 // prop: color 5247 // color of marker. Will be set to color of series by default on init. 5248 this.color = '#666666'; 5249 // prop: shadow 5250 // wether or not to draw a shadow on the line 5251 this.shadow = true; 5252 // prop: shadowAngle 5253 // Shadow angle in degrees 5254 this.shadowAngle = 45; 5255 // prop: shadowOffset 5256 // Shadow offset from line in pixels 5257 this.shadowOffset = 1; 5258 // prop: shadowDepth 5259 // Number of times shadow is stroked, each stroke offset shadowOffset from the last. 5260 this.shadowDepth = 3; 5261 // prop: shadowAlpha 5262 // Alpha channel transparency of shadow. 0 = transparent. 5263 this.shadowAlpha = '0.07'; 5264 // prop: shadowRenderer 5265 // Renderer that will draws the shadows on the marker. 5266 this.shadowRenderer = new $.jqplot.ShadowRenderer(); 5267 // prop: shapeRenderer 5268 // Renderer that will draw the marker. 5269 this.shapeRenderer = new $.jqplot.ShapeRenderer(); 5270 5271 $.extend(true, this, options); 5272 }; 5273 5274 $.jqplot.MarkerRenderer.prototype.init = function(options) { 5275 $.extend(true, this, options);
5276 var sdopt = {angle:this.shadowAngle, offset:this.shadowOffset, alpha:this.shadowAlpha, lineWidth:this.lineWidth, depth:this.shadowDepth, closePath:true}; 5277 if (this.style.indexOf('filled') != -1) { 5278 sdopt.fill = true; 5279 } 5280 if (this.style.indexOf('ircle') != -1) { 5281 sdopt.isarc = true; 5282 sdopt.closePath = false; 5283 } 5284 this.shadowRenderer.init(sdopt); 5285 5286 var shopt = {fill:false, isarc:false, strokeStyle:this.color, fillStyle:this.color, lineWidth:this.lineWidth, closePath:true}; 5287 if (this.style.indexOf('filled') != -1) { 5288 shopt.fill = true; 5289 } 5290 if (this.style.indexOf('ircle') != -1) { 5291 shopt.isarc = true; 5292 shopt.closePath = false; 5293 } 5294 this.shapeRenderer.init(shopt); 5295 }; 5296 5297 $.jqplot.MarkerRenderer.prototype.drawDiamond = function(x, y, ctx, fill, options) { 5298 var stretch = 1.2; 5299 var dx = this.size/2/stretch; 5300 var dy = this.size/2*stretch; 5301 var points = [[x-dx, y], [x, y+dy], [x+dx, y], [x, y-dy]]; 5302 if (this.shadow) { 5303 this.shadowRenderer.draw(ctx, points); 5304 } 5305 this.shapeRenderer.draw(ctx, points, options); 5306 5307 // ctx.restore(); 5308 }; 5309 5310 $.jqplot.MarkerRenderer.prototype.drawPlus = function(x, y, ctx, fill, options) { 5311 var stretch = 1.0; 5312 var dx = this.size/2*stretch; 5313 var dy = this.size/2*stretch; 5314 var points1 = [[x, y-dy], [x, y+dy]]; 5315 var points2 = [[x+dx, y], [x-dx, y]]; 5316 var opts = $.extend(true, {}, this.options, {closePath:false}); 5317 if (this.shadow) { 5318 this.shadowRenderer.draw(ctx, points1, {closePath:false}); 5319 this.shadowRenderer.draw(ctx, points2, {closePath:false}); 5320 } 5321 this.shapeRenderer.draw(ctx, points1, opts); 5322 this.shapeRenderer.draw(ctx, points2, opts); 5323 5324 // ctx.restore(); 5325 }; 5326 5327 $.jqplot.MarkerRenderer.prototype.drawX = function(x, y, ctx, fill, options) { 5328 var stretch = 1.0; 5329 var dx = this.size/2*stretch; 5330 var dy = this.size/2*stretch; 5331 var opts = $.extend(true, {}, this.options, {closePath:false}); 5332 var points1 = [[x-dx, y-dy], [x+dx, y+dy]]; 5333 var points2 = [[x-dx, y+dy], [x+dx, y-dy]]; 5334 if (this.shadow) { 5335 this.shadowRenderer.draw(ctx, points1, {closePath:false}); 5336 this.shadowRenderer.draw(ctx, points2, {closePath:false}); 5337 } 5338 this.shapeRenderer.draw(ctx, points1, opts); 5339 this.shapeRenderer.draw(ctx, points2, opts); 5340 5341 // ctx.restore(); 5342 }; 5343 5344 $.jqplot.MarkerRenderer.prototype.drawDash = function(x, y, ctx, fill, options) { 5345 var stretch = 1.0; 5346 var dx = this.size/2*stretch; 5347 var dy = this.size/2*stretch; 5348 var points = [[x-dx, y], [x+dx, y]]; 5349 if (this.shadow) { 5350 this.shadowRenderer.draw(ctx, points); 5351 } 5352 this.shapeRenderer.draw(ctx, points, options); 5353 5354 // ctx.restore(); 5355 }; 5356 5357 $.jqplot.MarkerRenderer.prototype.drawSquare = function(x, y, ctx, fill, options) { 5358 var stretch = 1.0; 5359 var dx = this.size/2/stretch; 5360 var dy = this.size/2*stretch; 5361 var points = [[x-dx, y-dy], [x-dx, y+dy], [x+dx, y+dy], [x+dx, y-dy]]; 5362 if (this.shadow) { 5363 this.shadowRenderer.draw(ctx, points); 5364 } 5365 this.shapeRenderer.draw(ctx, points, options); 5366 5367 // ctx.restore(); 5368 }; 5369 5370 $.jqplot.MarkerRenderer.prototype.drawCircle = function(x, y, ctx, fill, options) { 5371 var radius = this.size/2; 5372 var end = 2*Math.PI; 5373 var points = [x, y, radius, 0, end, true]; 5374 if (this.shadow) { 5375 this.shadowRenderer.draw(ctx, points); 5376 } 5377 this.shapeRenderer.draw(ctx, points, options); 5378 5379 // ctx.restore(); 5380 }; 5381 5382 $.jqplot.MarkerRenderer.prototype.draw = function(x, y, ctx, options) { 5383 options = options || {}; 5384 // hack here b/c shape renderer uses canvas based color style options 5385 // and marker uses css style names. 5386 if (options.show == null || options.show != false) { 5387 if (options.color && !options.fillStyle) { 5388 options.fillStyle = options.color; 5389 } 5390 if (options.color && !options.strokeStyle) { 5391 options.strokeStyle = options.color; 5392 } 5393 switch (this.style) { 5394 case 'diamond': 5395 this.drawDiamond(x,y,ctx, false, options); 5396 break; 5397 case 'filledDiamond': 5398 this.drawDiamond(x,y,ctx, true, options); 5399 break; 5400 case 'circle': 5401 this.drawCircle(x,y,ctx, false, options); 5402 break; 5403 case 'filledCircle': 5404 this.drawCircle(x,y,ctx, true, options); 5405 break; 5406 case 'square': 5407 this.drawSquare(x,y,ctx, false, options); 5408 break; 5409 case 'filledSquare': 5410 this.drawSquare(x,y,ctx, true, options); 5411 break; 5412 case 'x': 5413 this.drawX(x,y,ctx, true, options); 5414 break; 5415 case 'plus': 5416 this.drawPlus(x,y,ctx, true, options); 5417 break; 5418 case 'dash': 5419 this.drawDash(x,y,ctx, true, options); 5420 break; 5421 default: 5422 this.drawDiamond(x,y,ctx, false, options); 5423 break; 5424 } 5425 } 5426 }; 5427 5428 // class: $.jqplot.shadowRenderer 5429 // The default jqPlot shadow renderer, rendering shadows behind shapes. 5430 $.jqplot.ShadowRenderer = function(options){ 5431 // Group: Properties 5432 5433 // prop: angle 5434 // Angle of the shadow in degrees. Measured counter-clockwise from the x axis. 5435 this.angle = 45; 5436 // prop: offset 5437 // Pixel offset at the given shadow angle of each shadow stroke from the last stroke. 5438 this.offset = 1; 5439 // prop: alpha 5440 // alpha transparency of shadow stroke. 5441 this.alpha = 0.07; 5442 // prop: lineWidth 5443 // width of the shadow line stroke. 5444 this.lineWidth = 1.5; 5445 // prop: lineJoin 5446 // How line segments of the shadow are joined. 5447 this.lineJoin = 'miter'; 5448 // prop: lineCap 5449 // how ends of the shadow line are rendered. 5450 this.lineCap = 'round'; 5451 // prop; closePath 5452 // whether line path segment is closed upon itself. 5453 this.closePath = false; 5454 // prop: fill 5455 // whether to fill the shape. 5456 this.fill = false; 5457 // prop: depth 5458 // how many times the shadow is stroked. Each stroke will be offset by offset at angle degrees. 5459 this.depth = 3; 5460 this.strokeStyle = 'rgba(0,0,0,0.1)'; 5461 // prop: isarc 5462 // wether the shadow is an arc or not. 5463 this.isarc = false; 5464 5465 $.extend(true, this, options); 5466 }; 5467 5468 $.jqplot.ShadowRenderer.prototype.init = function(options) { 5469 $.extend(true, this, options); 5470 }; 5471 5472 // function: draw 5473 // draws an transparent black (i.e. gray) shadow. 5474 // 5475 // ctx - canvas drawing context 5476 // points - array of points or [x, y, radius, start angle (rad), end angle (rad)] 5477 $.jqplot.ShadowRenderer.prototype.draw = function(ctx, points, options) { 5478 ctx.save(); 5479 var opts = (options != null) ? options : {}; 5480 var fill = (opts.fill != null) ? opts.fill : this.fill; 5481 var closePath = (opts.closePath != null) ? opts.closePath
5481: this.closePath; 5482 var offset = (opts.offset != null) ? opts.offset : this.offset; 5483 var alpha = (opts.alpha != null) ? opts.alpha : this.alpha; 5484 var depth = (opts.depth != null) ? opts.depth : this.depth; 5485 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc; 5486 ctx.lineWidth = (opts.lineWidth != null) ? opts.lineWidth : this.lineWidth; 5487 ctx.lineJoin = (opts.lineJoin != null) ? opts.lineJoin : this.lineJoin; 5488 ctx.lineCap = (opts.lineCap != null) ? opts.lineCap : this.lineCap; 5489 ctx.strokeStyle = opts.strokeStyle || this.strokeStyle || 'rgba(0,0,0,'+alpha+')'; 5490 ctx.fillStyle = opts.fillStyle || this.fillStyle || 'rgba(0,0,0,'+alpha+')'; 5491 for (var j=0; j<depth; j++) { 5492 ctx.translate(Math.cos(this.angle*Math.PI/180)*offset, Math.sin(this.angle*Math.PI/180)*offset); 5493 ctx.beginPath(); 5494 if (isarc) { 5495 ctx.arc(points[0], points[1], points[2], points[3], points[4], true); 5496 } 5497 else { 5498 ctx.moveTo(points[0][0], points[0][1]); 5499 for (var i=1; i<points.length; i++) { 5500 if (points[i][0] != null && points[i][1] != null) { 5501 ctx.lineTo(points[i][0], points[i][1]); 5502 } 5503 // if null value, skip ahead a point and move to it 5504 else { 5505 i++; 5506 if (i < points.length) { 5507 ctx.moveTo(points[i][0], points[i][1]); 5508 } 5509 } 5510 } 5511 5512 } 5513 if (closePath) { 5514 ctx.closePath(); 5515 } 5516 if (fill) { 5517 ctx.fill(); 5518 } 5519 else { 5520 ctx.stroke(); 5521 } 5522 } 5523 ctx.restore(); 5524 }; 5525 5526 // class: $.jqplot.shapeRenderer 5527 // The default jqPlot shape renderer. Given a set of points will 5528 // plot them and either stroke a line (fill = false) or fill them (fill = true). 5529 // If a filled shape is desired, closePath = true must also be set to close 5530 // the shape. 5531 $.jqplot.ShapeRenderer = function(options){ 5532 5533 this.lineWidth = 1.5; 5534 // prop: lineJoin 5535 // How line segments of the shadow are joined. 5536 this.lineJoin = 'miter'; 5537 // prop: lineCap 5538 // how ends of the shadow line are rendered. 5539 this.lineCap = 'round'; 5540 // prop; closePath 5541 // whether line path segment is closed upon itself. 5542 this.closePath = false; 5543 // prop: fill 5544 // whether to fill the shape. 5545 this.fill = false; 5546 // prop: isarc 5547 // wether the shadow is an arc or not. 5548 this.isarc = false; 5549 // prop: fillRect 5550 // true to draw shape as a filled rectangle. 5551 this.fillRect = false; 5552 // prop: strokeRect 5553 // true to draw shape as a stroked rectangle. 5554 this.strokeRect = false; 5555 // prop: clearRect 5556 // true to cear a rectangle. 5557 this.clearRect = false; 5558 // prop: strokeStyle 5559 // css color spec for the stoke style 5560 this.strokeStyle = '#999999'; 5561 // prop: fillStyle 5562 // css color spec for the fill style. 5563 this.fillStyle = '#999999'; 5564 5565 $.extend(true, this, options); 5566 }; 5567 5568 $.jqplot.ShapeRenderer.prototype.init = function(options) { 5569 $.extend(true, this, options); 5570 }; 5571 5572 // function: draw 5573 // draws the shape. 5574 // 5575 // ctx - canvas drawing context 5576 // points - array of points for shapes or 5577 // [x, y, width, height] for rectangles or 5578 // [x, y, radius, start angle (rad), end angle (rad)] for circles and arcs. 5579 $.jqplot.ShapeRenderer.prototype.draw = function(ctx, points, options) { 5580 ctx.save(); 5581 var opts = (options != null) ? options : {}; 5582 var fill = (opts.fill != null) ? opts.fill : this.fill; 5583 var closePath = (opts.closePath != null) ? opts.closePath
5583: this.closePath; 5584 var fillRect = (opts.fillRect != null) ? opts.fillRect : this.fillRect; 5585 var strokeRect = (opts.strokeRect != null) ? opts.strokeRect : this.strokeRect; 5586 var clearRect = (opts.clearRect != null) ? opts.clearRect : this.clearRect; 5587 var isarc = (opts.isarc != null) ? opts.isarc : this.isarc; 5588 ctx.lineWidth = opts.lineWidth || this.lineWidth; 5589 ctx.lineJoin = opts.lineJoing || this.lineJoin; 5590 ctx.lineCap = opts.lineCap || this.lineCap; 5591 ctx.strokeStyle = (opts.strokeStyle || opts.color) || this.strokeStyle; 5592 ctx.fillStyle = opts.fillStyle || this.fillStyle; 5593 ctx.beginPath(); 5594 if (isarc) { 5595 ctx.arc(points[0], points[1], points[2], points[3], points[4], true); 5596 if (closePath) { 5597 ctx.closePath(); 5598 } 5599 if (fill) { 5600 ctx.fill(); 5601 } 5602 else { 5603 ctx.stroke(); 5604 } 5605 ctx.restore(); 5606 return; 5607 } 5608 else if (clearRect) { 5609 ctx.clearRect(points[0], points[1], points[2], points[3]); 5610 ctx.restore(); 5611 return; 5612 } 5613 else if (fillRect || strokeRect) { 5614 if (fillRect) { 5615 ctx.fillRect(points[0], points[1], points[2], points[3]); 5616 } 5617 if (strokeRect) { 5618 ctx.strokeRect(points[0], points[1], points[2], points[3]); 5619 ctx.restore(); 5620 return; 5621 } 5622 } 5623 else { 5624 ctx.moveTo(points[0][0], points[0][1]); 5625 for (var i=1; i<points.length; i++) { 5626 if (points[i][0] != null && points[i][1] != null) { 5627 ctx.lineTo(points[i][0], points[i][1]); 5628 } 5629 // if null value, skip ahead a point and move to it 5630 else { 5631 i++; 5632 if (i < points.length) { 5633 ctx.moveTo(points[i][0], points[i][1]); 5634 } 5635 } 5636 } 5637 if (closePath) { 5638 ctx.closePath(); 5639 } 5640 if (fill) { 5641 ctx.fill(); 5642 } 5643 else { 5644 ctx.stroke(); 5645 } 5646 } 5647 ctx.restore(); 5648 }; 5649 5650 // class $.jqplot.TableLegendRenderer 5651 // The default legend renderer for jqPlot. 5652 $.jqplot.TableLegendRenderer = function(){ 5653 // 5654 }; 5655 5656 $.jqplot.TableLegendRenderer.prototype.init = function(options) { 5657 $.extend(true, this, options); 5658 }; 5659 5660 $.jqplot.TableLegendRenderer.prototype.addrow = function (label, color, pad, reverse) { 5661 var rs = (pad) ? this.rowSpacing : '0'; 5662 if (reverse){ 5663 var tr = $('<tr class="jqplot-table-legend"></tr>').prependTo(this._elem); 5664 } 5665 else{ 5666 var tr = $('<tr class="jqplot-table-legend"></tr>').appendTo(this._elem); 5667 } 5668 if (this.showSwatches) { 5669 $('<td class="jqplot-table-legend" style="text-align:center;padding-top:'+rs+';">'+ 5670 '<div><div class="jqplot-table-legend-swatch" style="background-color:'+color+';border-color:'+color+';"></div>'+ 5671 '</div></td>').appendTo(tr); 5672 } 5673 if (this.showLabels) { 5674 var elem = $('<td class="jqplot-table-legend" style="padding-top:'+rs+';"></td>'); 5675 elem.appendTo(tr); 5676 if (this.escapeHtml) { 5677 elem.text(label); 5678 } 5679 else { 5680 elem.html(label); 5681 } 5682 } 5683 }; 5684 5685 // called with scope of legend 5686 $.jqplot.TableLegendRenderer.prototype.draw = function() { 5687 var legend = this; 5688 if (this.show) { 5689 var series = this._series; 5690 // make a table. one line label per row. 5691 var ss = 'position:absolute;'; 5692 ss += (this.background) ? 'background:'+this.background+';' : ''; 5693 ss += (this.border) ? 'border:'+this.border+';' : ''; 5694 ss += (this.fontSize) ? 'font-size:'+this.fontSize+';' : ''; 5695 ss += (this.fontFamily) ? 'font-family:'+this.fontFamily+';' : ''; 5696 ss += (this.textColor) ? 'color:'+this.textColor+';' : ''; 5697 ss += (this.marginTop != null) ? 'margin-top:'+this.marginTop+';' : ''; 5698 ss += (this.marginBottom != null) ? 'margin-bottom:'+this.marginBottom+';' : ''; 5699 ss += (this.marginLeft != null) ? 'margin-left:'+this.marginLeft+';' : ''; 5700 ss += (this.marginRight != null) ? 'margin-right:'+this.marginRight+';' : ''; 5701 this._elem = $('<table class="jqplot-table-legend" style="'+ss+'"></table>'); 5702 5703 var pad = false, 5704 reverse = false; 5705 for (var i = 0; i< series.length; i++) { 5706 s = series[i];
5707 if (s._stack || s.renderer.constructor == $.jqplot.BezierCurveRenderer){ 5708 reverse = true; 5709 } 5710 if (s.show && s.showLabel) { 5711 var lt = this.labels[i] || s.label.toString(); 5712 if (lt) { 5713 var color = s.color; 5714 if (reverse && i < series.length - 1){ 5715 pad = true; 5716 } 5717 else if (reverse && i == series.length - 1){ 5718 pad = false; 5719 } 5720 this.renderer.addrow.call(this, lt, color, pad, reverse); 5721 pad = true; 5722 } 5723 // let plugins add more rows to legend. Used by trend line plugin. 5724 for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) { 5725 var item = $.jqplot.addLegendRowHooks[j].call(this, s); 5726 if (item) { 5727 this.renderer.addrow.call(this, item.label, item.color, pad); 5728 pad = true; 5729 } 5730 } 5731 } 5732 } 5733 } 5734 return this._elem; 5735 }; 5736 5737 $.jqplot.TableLegendRenderer.prototype.pack = function(offsets) { 5738 if (this.show) { 5739 if (this.placement == 'insideGrid') { 5740 switch (this.location) { 5741 case 'nw': 5742 var a = offsets.left; 5743 var b = offsets.top; 5744 this._elem.css('left', a); 5745 this._elem.css('top', b); 5746 break; 5747 case 'n': 5748 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 5749 var b = offsets.top; 5750 this._elem.css('left', a); 5751 this._elem.css('top', b); 5752 break; 5753 case 'ne': 5754 var a = offsets.right; 5755 var b = offsets.top; 5756 this._elem.css({right:a, top:b}); 5757 break; 5758 case 'e': 5759 var a = offsets.right; 5760 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 5761 this._elem.css({right:a, top:b}); 5762 break; 5763 case 'se': 5764 var a = offsets.right; 5765 var b = offsets.bottom; 5766 this._elem.css({right:a, bottom:b}); 5767 break; 5768 case 's': 5769 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 5770 var b = offsets.bottom; 5771 this._elem.css({left:a, bottom:b}); 5772 break; 5773 case 'sw': 5774 var a = offsets.left; 5775 var b = offsets.bottom; 5776 this._elem.css({left:a, bottom:b}); 5777 break; 5778 case 'w': 5779 var a = offsets.left; 5780 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 5781 this._elem.css({left:a, top:b}); 5782 break; 5783 default: // same as 'se' 5784 var a = offsets.right; 5785 var b = offsets.bottom; 5786 this._elem.css({right:a, bottom:b}); 5787 break; 5788 } 5789 5790 } 5791 else if (this.placement == 'outside'){ 5792 switch (this.location) { 5793 case 'nw': 5794 var a = this._plotDimensions.width - offsets.left; 5795 var b = offsets.top; 5796 this._elem.css('right', a); 5797 this._elem.css('top', b); 5798 break; 5799 case 'n': 5800 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 5801 var b = this._plotDimensions.height - offsets.top; 5802 this._elem.css('left', a); 5803 this._elem.css('bottom', b); 5804 break;
5805 case 'ne': 5806 var a = this._plotDimensions.width - offsets.right; 5807 var b = offsets.top; 5808 this._elem.css({left:a, top:b}); 5809 break; 5810 case 'e': 5811 var a = this._plotDimensions.width - offsets.right; 5812 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 5813 this._elem.css({left:a, top:b}); 5814 break; 5815 case 'se': 5816 var a = this._plotDimensions.width - offsets.right; 5817 var b = offsets.bottom; 5818 this._elem.css({left:a, bottom:b}); 5819 break; 5820 case 's': 5821 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 5822 var b = this._plotDimensions.height - offsets.bottom; 5823 this._elem.css({left:a, top:b}); 5824 break; 5825 case 'sw': 5826 var a = this._plotDimensions.width - offsets.left; 5827 var b = offsets.bottom; 5828 this._elem.css({right:a, bottom:b}); 5829 break; 5830 case 'w': 5831 var a = this._plotDimensions.width - offsets.left; 5832 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 5833 this._elem.css({right:a, top:b}); 5834 break; 5835 default: // same as 'se' 5836 var a = offsets.right; 5837 var b = offsets.bottom; 5838 this._elem.css({right:a, bottom:b}); 5839 break; 5840 } 5841 } 5842 else { 5843 switch (this.location) { 5844 case 'nw': 5845 this._elem.css({left:0, top:offsets.top}); 5846 break; 5847 case 'n': 5848 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 5849 this._elem.css({left: a, top:offsets.top}); 5850 break; 5851 case 'ne': 5852 this._elem.css({right:0, top:offsets.top}); 5853 break; 5854 case 'e': 5855 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 5856 this._elem.css({right:offsets.right, top:b}); 5857 break; 5858 case 'se': 5859 this._elem.css({right:offsets.right, bottom:offsets.bottom}); 5860 break; 5861 case 's': 5862 var a = (offsets.left + (this._plotDimensions.width - offsets.right))/2 - this.getWidth()/2; 5863 this._elem.css({left: a, bottom:offsets.bottom}); 5864 break; 5865 case 'sw': 5866 this._elem.css({left:offsets.left, bottom:offsets.bottom}); 5867 break; 5868 case 'w': 5869 var b = (offsets.top + (this._plotDimensions.height - offsets.bottom))/2 - this.getHeight()/2; 5870 this._elem.css({left:offsets.left, top:b}); 5871 break; 5872 default: // same as 'se' 5873 this._elem.css({right:offsets.right, bottom:offsets.bottom}); 5874 break; 5875 } 5876 } 5877 } 5878 }; 5879 5880 /** 5881 * Class: $.jqplot.ThemeEngine 5882 * Theme Engine provides a programatic way to change some of the more 5883 * common jqplot styling options such as fonts, colors and grid options. 5884 * A theme engine instance is created with each plot. The theme engine 5885 * manages a collection of themes which can be modified, added to, or 5886 * applied to the plot. 5887 * 5888 * The themeEngine class is not instantiated directly. 5889 * When a plot is initialized, the current plot options are scanned 5890 * an a default theme named "Default" is created. This theme is 5891 * used as the basis for other themes added to the theme engine and 5892 * is always available. 5893 * 5894 * A theme is a simple javascript object with styling parameters for 5895 * various entities of the plot. A theme has the form: 5896 * 5897 * 5898 * > { 5899 * > _name:f "Default", 5900 * > target: { 5901 * > backgroundColor: "transparent" 5902 * > }, 5903 * > legend: { 5904 * > textColor: null, 5905 * > fontFamily: null, 5906 * > fontSize: null, 5907 * > border: null, 5908 * > background: null 5909 * > }, 5910 * > title: { 5911 * > textColor: "rgb(102, 102, 102)", 5912 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif", 5913 * > fontSize: "19.2px", 5914 * > textAlign: "center" 5915 * > }, 5916 * > seriesStyles: {}, 5917 * > series: [{ 5918 * > color: "#4bb2c5", 5919 * > lineWidth: 2.5, 5920 * > shadow: true, 5921 * > fillColor: "#4bb2c5", 5922 * > showMarker: true, 5923 * > markerOptions: { 5924 * > color: "#4bb2c5", 5925 * > show: true, 5926 * > style: 'filledCircle', 5927 * > lineWidth: 1.5, 5928 * > size: 4, 5929 * > shadow: true 5930 * > } 5931 * > }], 5932 * > grid: { 5933 * >
5933 drawGridlines: true, 5934 * > gridLineColor: "#cccccc", 5935 * > gridLineWidth: 1, 5936 * > backgroundColor: "#fffdf6", 5937 * > borderColor: "#999999", 5938 * > borderWidth: 2, 5939 * > shadow: true 5940 * > }, 5941 * > axesStyles: { 5942 * > label: {}, 5943 * > ticks: {} 5944 * > }, 5945 * > axes: { 5946 * > xaxis: { 5947 * > borderColor: "#999999", 5948 * > borderWidth: 2, 5949 * > ticks: { 5950 * > show: true, 5951 * > showGridline: true, 5952 * > showLabel: true, 5953 * > showMark: true, 5954 * > size: 4, 5955 * > textColor: "", 5956 * > whiteSpace: "nowrap", 5957 * > fontSize: "12px", 5958 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif" 5959 * > }, 5960 * > label: { 5961 * > textColor: "rgb(102, 102, 102)", 5962 * > whiteSpace: "normal", 5963 * > fontSize: "14.6667px", 5964 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif", 5965 * > fontWeight: "400" 5966 * > } 5967 * > }, 5968 * > yaxis: { 5969 * > borderColor: "#999999", 5970 * > borderWidth: 2, 5971 * > ticks: { 5972 * > show: true, 5973 * > showGridline: true, 5974 * > showLabel: true, 5975 * > showMark: true, 5976 * > size: 4, 5977 * > textColor: "", 5978 * > whiteSpace: "nowrap", 5979 * > fontSize: "12px", 5980 * > fontFamily: "'Trebuchet MS',Arial,Helvetica,sans-serif" 5981 * > }, 5982 * > label: { 5983 * > textColor: null, 5984 * > whiteSpace: null, 5985 * > fontSize: null, 5986 * > fontFamily: null, 5987 * > fontWeight: null 5988 * > } 5989 * > }, 5990 * > x2axis: {... 5991 * > }, 5992 * > ... 5993 * > y9axis: {... 5994 * > } 5995 * > } 5996 * > } 5997 * 5998 * "seriesStyles" is a style object that will be applied to all series in the plot. 5999 * It will forcibly override any styles applied on the individual series. "axesStyles" is 6000 * a style object that will be applied to all axes in the plot. It will also forcibly 6001 * override any styles on the individual axes. 6002 * 6003 * The example shown above has series options for a line series. Options for other 6004 * series types are shown below: 6005 * 6006 * Bar Series: 6007 * 6008 * > { 6009 * > color: "#4bb2c5", 6010 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 6011 * > lineWidth: 2.5, 6012 * > shadow: true, 6013 * > barPadding: 2, 6014 * > barMargin: 10, 6015 * > barWidth: 15.09375, 6016 * > 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)"] 6017 * > } 6018 * 6019 * Pie Series: 6020 * 6021 * > { 6022 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 6023 * > padding: 20, 6024 * > sliceMargin: 0, 6025 * > fill: true, 6026 * > shadow: true, 6027 * > startAngle: 0, 6028 * > lineWidth: 2.5, 6029 * > 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)"] 6030 * > } 6031 * 6032 * Funnel Series: 6033 * 6034 * > { 6035 * > color: "#4bb2c5", 6036 * > lineWidth: 2, 6037 * > shadow: true, 6038 * > padding: { 6039 * > top: 20, 6040 * > right: 20, 6041 * > bottom: 20, 6042 * > left: 20 6043 * > }, 6044 * > sectionMargin: 6, 6045 * > seriesColors: ["#4bb2c5", "#EAA228", "#c5b47f", "#579575", "#839557", "#958c12", "#953579", "#4b5de4", "#d8b83f", "#ff5800", "#0085cc", "#c747a3", "#cddf54", "#FBD178", "#26B4E3", "#bd70c7"], 6046 * > 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)"] 6047 * > } 6048 * 6049 */ 6050 $.jqplot.ThemeEngine = function(){ 6051 // Group: Properties 6052 // 6053 // prop: themes 6054 // hash of themes managed by the theme engine. 6055 // Indexed by theme name. 6056 this.themes = {}; 6057 // prop: activeTheme 6058 // Pointer to currently active theme 6059 this.activeTheme=null; 6060 6061 }; 6062 6063 // called with scope of plot 6064 $.jqplot.ThemeEngine.prototype.init = function() { 6065 // get the Default theme from the current plot settings. 6066 var th = new $.jqplot.Theme({_name:'Default'}); 6067 var n, i; 6068 6069 for (n in th.target) { 6070 if (n == "textColor") { 6071 th.target[n] = this.target.css('color'); 6072 } 6073 else { 6074 th.target[n] = this.target.css(n);
6075 } 6076 } 6077 6078 if (this.title.show && this.title._elem) { 6079 for (n in th.title) { 6080 if (n == "textColor") { 6081 th.title[n] = this.title._elem.css('color'); 6082 } 6083 else { 6084 th.title[n] = this.title._elem.css(n); 6085 } 6086 } 6087 } 6088 6089 for (n in th.grid) { 6090 th.grid[n] = this.grid[n]; 6091 } 6092 if (th.grid.backgroundColor == null && this.grid.background != null) { 6093 th.grid.backgroundColor = this.grid.background; 6094 } 6095 if (this.legend.show && this.legend._elem) { 6096 for (n in th.legend) { 6097 if (n == 'textColor') { 6098 th.legend[n] = this.legend._elem.css('color'); 6099 } 6100 else { 6101 th.legend[n] = this.legend._elem.css(n); 6102 } 6103 } 6104 } 6105 var s; 6106 6107 for (i=0; i<this.series.length; i++) { 6108 s = this.series[i]; 6109 if (s.renderer.constructor == $.jqplot.LineRenderer) { 6110 th.series.push(new LineSeriesProperties()); 6111 } 6112 else if (s.renderer.constructor == $.jqplot.BarRenderer) { 6113 th.series.push(new BarSeriesProperties()); 6114 } 6115 else if (s.renderer.constructor == $.jqplot.PieRenderer) { 6116 th.series.push(new PieSeriesProperties()); 6117 } 6118 else if (s.renderer.constructor == $.jqplot.DonutRenderer) { 6119 th.series.push(new DonutSeriesProperties()); 6120 } 6121 else if (s.renderer.constructor == $.jqplot.FunnelRenderer) { 6122 th.series.push(new FunnelSeriesProperties()); 6123 } 6124 else if (s.renderer.constructor == $.jqplot.MeterGaugeRenderer) { 6125 th.series.push(new MeterSeriesProperties()); 6126 } 6127 else { 6128 th.series.push({}); 6129 } 6130 for (n in th.series[i]) { 6131 th.series[i][n] = s[n]; 6132 } 6133 } 6134 var a, ax; 6135 for (n in this.axes) { 6136 ax = this.axes[n]; 6137 a = th.axes[n] = new AxisProperties(); 6138 a.borderColor = ax.borderColor; 6139 a.borderWidth = ax.borderWidth; 6140 if (ax._ticks && ax._ticks[0]) { 6141 for (nn in a.ticks) { 6142 if (ax._ticks[0].hasOwnProperty(nn)) { 6143 a.ticks[nn] = ax._ticks[0][nn]; 6144 } 6145 else if (ax._ticks[0]._elem){ 6146 a.ticks[nn] = ax._ticks[0]._elem.css(nn); 6147 } 6148 } 6149 } 6150 if (ax._label && ax._label.show) { 6151 for (nn in a.label) { 6152 // a.label[nn] = ax._label._elem.css(nn); 6153 if (ax._label[nn]) { 6154 a.label[nn] = ax._label[nn]; 6155 } 6156 else if (ax._label._elem){ 6157 if (nn == 'textColor') { 6158 a.label[nn] = ax._label._elem.css('color'); 6159 } 6160 else { 6161 a.label[nn] = ax._label._elem.css(nn); 6162 } 6163 } 6164 } 6165 } 6166 } 6167 this.themeEngine._add(th); 6168 this.themeEngine.activeTheme = this.themeEngine.themes[th._name]; 6169 }; 6170 /** 6171 * Group: methods 6172 * 6173 * method: get 6174 * 6175 * Get and return the named theme or the active theme if no name given. 6176 * 6177 * parameter: 6178 * 6179 * name - name of theme to get. 6180 * 6181 * returns: 6182 * 6183 * Theme instance of given name. 6184 */ 6185 $.jqplot.ThemeEngine.prototype.get = function(name) { 6186 if (!name) { 6187 // return the active theme 6188 return this.activeTheme; 6189 } 6190 else { 6191 return this.themes[name]; 6192 } 6193 }; 6194 6195 function numericalOrder(a,b) { return a-b; } 6196 6197 /** 6198 * method: getThemeNames 6199 * 6200 * Return the list of theme names in this manager in alpha-numerical order. 6201 * 6202 * parameter: 6203 * 6204 * None 6205 * 6206 * returns: 6207 * 6208 * A the list of theme names in this manager in alpha-numerical order. 6209 */ 6210 $.jqplot.ThemeEngine.prototype.getThemeNames = function() { 6211 var tn = []; 6212 for (var n in this.themes) { 6213 tn.push(n); 6214 } 6215 return tn.sort(numericalOrder); 6216 }; 6217 6218 /** 6219 * method: getThemes 6220 * 6221 * Return a list of themes in alpha-numerical order by name. 6222 * 6223 * parameter: 6224 * 6225 * None 6226 * 6227 * returns: 6228 * 6229 * A list of themes in alpha-numerical order by name. 6230 */ 6231 $.jqplot.ThemeEngine.prototype.getThemes = function() { 6232 var tn = []; 6233 var themes = []; 6234 for (var n in this.themes) { 6235 tn.push(n); 6236 } 6237 tn.sort(numericalOrder); 6238 for (var i=0; i<tn.length; i++) { 6239 themes.push(this.themes[tn[i]]); 6240 } 6241 return themes; 6242 }; 6243 6244 $.jqplot.ThemeEngine.prototype.activate = function(plot, name) { 6245 // sometimes need to redraw whole plot. 6246 var redrawPlot = false; 6247 if (!name && this.activeTheme && this.activeTheme._name) { 6248 name = this.activeTheme._name; 6249 } 6250 if (!this.themes.hasOwnProperty(name)) { 6251 throw new Error("No theme of that name"); 6252 } 6253 else { 6254 var th = this.themes[name]; 6255 this.activeTheme = th; 6256 var val, checkBorderColor = false, checkBorderWidth = false; 6257 var arr = ['xaxis', 'x2axis', 'yaxis', 'y2axis']; 6258 6259 for (i=0; i<arr.length; i++) { 6260 var ax = arr[i]; 6261 if (th.axesStyles.borderColor != null) { 6262 plot.axes[ax].borderColor = th.axesStyles.borderColor; 6263 } 6264 if (th.axesStyles.borderWidth != null) { 6265 plot.axes[ax].borderWidth = th.axesStyles.borderWidth; 6266 } 6267 } 6268 6269 for (axname in plot.axes) { 6270 var axis = plot.axes[axname]; 6271 if (axis.show) { 6272 var thaxis = th.axes[axname] || {}; 6273 var thaxstyle = th.axesStyles; 6274 var thax = $.jqplot.extend(true, {}, thaxis, thaxstyle); 6275 val = (th.axesStyles.borderColor != null) ? th.axesStyles.borderColor : thax.borderColor; 6276 if (thax.borderColor != null) { 6277 axis.borderColor = thax.borderColor; 6278 redrawPlot = true; 6279 } 6280 val = (th.axesStyles.borderWidth != null) ? th.axesStyles.borderWidth : thax.borderWidth; 6281 if (thax.borderWidth != null) { 6282 axis.borderWidth = thax.borderWidth; 6283 redrawPlot = true; 6284 } 6285 if (axis._ticks && axis._ticks[0]) { 6286 for (nn in thax.ticks) { 6287 // val = null; 6288 // if (th.axesStyles.ticks && th.axesStyles.ticks[nn] != null) { 6289 // val = th.axesStyles.ticks[nn]; 6290 // } 6291 // else if (thax.ticks[nn] != null){ 6292 // val = thax.ticks[nn] 6293 // } 6294 val = thax.ticks[nn]; 6295 if (val != null) { 6296 axis.tickOptions[nn] = val; 6297 axis._ticks = []; 6298 redrawPlot = true; 6299 } 6300 } 6301 } 6302 if (axis._label && axis._label.show) { 6303 for (nn in thax.label) { 6304 // val = null; 6305 // if (th.axesStyles.label && th.axesStyles.label[nn] != null) { 6306 // val = th.axesStyles.label[nn]; 6307 // } 6308 // else if (thax.label && thax.label[nn] != null){ 6309 // val = thax.label[nn] 6310 // } 6311 val = thax.label[nn]; 6312 if (val != null) { 6313 axis.labelOptions[nn] = val; 6314 redrawPlot = true; 6315 } 6316 } 6317 } 6318 6319 } 6320 } 6321 6322 for (var n in th.grid) { 6323 if (th.grid[n] != null) { 6324 plot.grid[n] = th.grid[n]; 6325 } 6326 } 6327 if (!redrawPlot) { 6328 plot.grid.draw(); 6329 } 6330 6331 if (plot.legend.show) { 6332 for (n in th.legend) { 6333 if (th.legend[n] != null) { 6334 plot.legend[n] = th.legend[n]; 6335 } 6336 } 6337 } 6338 if (plot.title.show) { 6339 for (n in th.title) { 6340 if (th.title[n] != null) { 6341 plot.title[n] = th.title[n]; 6342 } 6343 } 6344 } 6345 6346 var i; 6347 for (i=0; i<th.series.length; i++) { 6348 var opts = {}; 6349 var redrawSeries = false; 6350 for (n in th.series[i]) { 6351 val = (th.seriesStyles[n] != null) ? th.seriesStyles[n] : th.series[i][n];
6352 if (val != null) { 6353 opts[n] = val; 6354 if (n == 'color') { 6355 plot.series[i].renderer.shapeRenderer.fillStyle = val; 6356 plot.series[i].renderer.shapeRenderer.strokeStyle = val; 6357 plot.series[i][n] = val; 6358 } 6359 else if (n == 'lineWidth') { 6360 plot.series[i].renderer.shapeRenderer.lineWidth = val; 6361 plot.series[i][n] = val; 6362 } 6363 else if (n == 'markerOptions') { 6364 merge (plot.series[i].markerOptions, val); 6365 merge (plot.series[i].markerRenderer, val); 6366 } 6367 else { 6368 plot.series[i][n] = val; 6369 } 6370 redrawPlot = true; 6371 } 6372 } 6373 } 6374 6375 if (redrawPlot) { 6376 plot.target.empty(); 6377 plot.draw(); 6378 } 6379 6380 for (n in th.target) { 6381 if (th.target[n] != null) { 6382 plot.target.css(n, th.target[n]); 6383 } 6384 } 6385 } 6386 6387 }; 6388 6389 $.jqplot.ThemeEngine.prototype._add = function(theme, name) { 6390 if (name) { 6391 theme._name = name; 6392 } 6393 if (!theme._name) { 6394 theme._name = Date.parse(new Date()); 6395 } 6396 if (!this.themes.hasOwnProperty(theme._name)) { 6397 this.themes[theme._name] = theme; 6398 } 6399 else { 6400 throw new Error("jqplot.ThemeEngine Error: Theme already in use"); 6401 } 6402 }; 6403 6404 // method remove 6405 // Delete the named theme, return true on success, false on failure. 6406 6407 6408 /** 6409 * method: remove 6410 * 6411 * Remove the given theme from the themeEngine. 6412 * 6413 * parameters: 6414 * 6415 * name - name of the theme to remove. 6416 * 6417 * returns: 6418 * 6419 * true on success, false on failure. 6420 */ 6421 $.jqplot.ThemeEngine.prototype.remove = function(name) { 6422 if (name == 'Default') { 6423 return false; 6424 } 6425 return delete this.themes[name]; 6426 }; 6427 6428 /** 6429 * method: newTheme 6430 * 6431 * Create a new theme based on the default theme, adding it the themeEngine. 6432 * 6433 * parameters: 6434 * 6435 * name - name of the new theme. 6436 * obj - optional object of styles to be applied to this new theme. 6437 * 6438 * returns: 6439 * 6440 * new Theme object. 6441 */ 6442 $.jqplot.ThemeEngine.prototype.newTheme = function(name, obj) { 6443 if (typeof(name) == 'object') { 6444 obj = obj || name; 6445 name = null; 6446 } 6447 if (obj && obj._name) { 6448 name = obj._name; 6449 } 6450 else { 6451 name = name || Date.parse(new Date()); 6452 } 6453 // var th = new $.jqplot.Theme(name); 6454 var th = this.copy(this.themes['Default']._name, name); 6455 $.jqplot.extend(th, obj); 6456 return th; 6457 }; 6458 6459 // function clone(obj) { 6460 // return eval(obj.toSource()); 6461 // } 6462 6463 function clone(obj){ 6464 if(obj == null || typeof(obj) != 'object'){ 6465 return obj; 6466 } 6467 6468 var temp = new obj.constructor(); 6469 for(var key in obj){ 6470 temp[key] = clone(obj[key]); 6471 } 6472 return temp; 6473 } 6474 6475 $.jqplot.clone = clone; 6476 6477 function merge(obj1, obj2) { 6478 if (obj2 == null || typeof(obj2) != 'object') { 6479 return; 6480 } 6481 for (var key in obj2) { 6482 if (key == 'highlightColors') { 6483 obj1[key] = clone(obj2[key]); 6484 } 6485 if (obj2[key] != null && typeof(obj2[key]) == 'object') { 6486 if (!obj1.hasOwnProperty(key)) { 6487 obj1[key] = {}; 6488 }
6489 merge(obj1[key], obj2[key]); 6490 } 6491 else { 6492 obj1[key] = obj2[key]; 6493 } 6494 } 6495 } 6496 6497 $.jqplot.merge = merge; 6498 6499 // Use the jQuery 1.3.2 extend function since behaviour in jQuery 1.4 seems problematic 6500 $.jqplot.extend = function() { 6501 // copy reference to target object 6502 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; 6503 6504 // Handle a deep copy situation 6505 if ( typeof target === "boolean" ) { 6506 deep = target; 6507 target = arguments[1] || {}; 6508 // skip the boolean and the target 6509 i = 2; 6510 } 6511 6512 // Handle case when target is a string or something (possible in deep copy) 6513 if ( typeof target !== "object" && !toString.call(target) === "[object Function]" ) { 6514 target = {}; 6515 } 6516 6517 for ( ; i < length; i++ ){ 6518 // Only deal with non-null/undefined values 6519 if ( (options = arguments[ i ]) != null ) { 6520 // Extend the base object 6521 for ( var name in options ) { 6522 var src = target[ name ], copy = options[ name ]; 6523 6524 // Prevent never-ending loop 6525 if ( target === copy ) { 6526 continue; 6527 } 6528 6529 // Recurse if we're merging object values 6530 if ( deep && copy && typeof copy === "object" && !copy.nodeType ) { 6531 target[ name ] = $.jqplot.extend( deep, 6532 // Never move original objects, clone them 6533 src || ( copy.length != null ? [ ] : { } ) 6534 , copy ); 6535 } 6536 // Don't bring in undefined values 6537 else if ( copy !== undefined ) { 6538 target[ name ] = copy; 6539 } 6540 } 6541 } 6542 } 6543 // Return the modified object 6544 return target; 6545 }; 6546 6547 /** 6548 * method: rename 6549 * 6550 * Rename a theme. 6551 * 6552 * parameters: 6553 * 6554 * oldName - current name of the theme. 6555 * newName - desired name of the theme. 6556 * 6557 * returns: 6558 * 6559 * new Theme object. 6560 */ 6561 $.jqplot.ThemeEngine.prototype.rename = function (oldName, newName) { 6562 if (oldName == 'Default' || newName == 'Default') { 6563 throw new Error ("jqplot.ThemeEngine Error: Cannot rename from/to Default"); 6564 } 6565 if (this.themes.hasOwnProperty(newName)) { 6566 throw new Error ("jqplot.ThemeEngine Error: New name already in use."); 6567 } 6568 else if (this.themes.hasOwnProperty(oldName)) { 6569 var th = this.copy (oldName, newName); 6570 this.remove(oldName); 6571 return th; 6572 } 6573 throw new Error("jqplot.ThemeEngine Error: Old name or new name invalid"); 6574 }; 6575 6576 /** 6577 * method: copy 6578 * 6579 * Create a copy of an existing theme in the themeEngine, adding it the themeEngine. 6580 * 6581 * parameters: 6582 * 6583 * sourceName - name of the existing theme. 6584 * targetName - name of the copy. 6585 * obj - optional object of style parameter to apply to the new theme. 6586 * 6587 * returns: 6588 * 6589 * new Theme object. 6590 */ 6591 $.jqplot.ThemeEngine.prototype.copy = function (sourceName, targetName, obj) { 6592 if (targetName == 'Default') { 6593 throw new Error ("jqplot.ThemeEngine Error: Cannot copy over Default theme"); 6594 } 6595 if (!this.themes.hasOwnProperty(sourceName)) { 6596 var s = "jqplot.ThemeEngine Error: Source name invalid"; 6597 throw new Error(s); 6598 } 6599 if (this.themes.hasOwnProperty(targetName)) { 6600 var s = "jqplot.ThemeEngine Error: Target name invalid"; 6601 throw new Error(s); 6602 } 6603 else { 6604 var th = clone(this.themes[sourceName]); 6605 th._name = targetName; 6606 $.jqplot.extend(true, th, obj); 6607 this._add(th); 6608 return th; 6609 } 6610 }; 6611 6612 6613 $.jqplot.Theme = function(name, obj) { 6614 if (typeof(name) == 'object') { 6615 obj = obj || name; 6616 name = null; 6617 } 6618 name = name || Date.parse(new Date()); 6619 this._name = name; 6620 this.target = { 6621 backgroundColor: null 6622 }; 6623 this.legend = { 6624 textColor: null, 6625 fontFamily: null, 6626 fontSize: null, 6627 border: null, 6628 background: null 6629 }; 6630 this.title = { 6631 textColor: null, 6632 fontFamily: null, 6633 fontSize: null, 6634 textAlign: null 6635 }; 6636 this.seriesStyles = {}; 6637 this.series = []; 6638 this.grid = {
6639 drawGridlines: null, 6640 gridLineColor: null, 6641 gridLineWidth: null, 6642 backgroundColor: null, 6643 borderColor: null, 6644 borderWidth: null, 6645 shadow: null 6646 }; 6647 this.axesStyles = {label:{}, ticks:{}}; 6648 this.axes = {}; 6649 if (typeof(obj) == 'string') { 6650 this._name = obj; 6651 } 6652 else if(typeof(obj) == 'object') { 6653 $.jqplot.extend(true, this, obj); 6654 } 6655 }; 6656 6657 var AxisProperties = function() { 6658 this.borderColor = null; 6659 this.borderWidth = null; 6660 this.ticks = new AxisTicks(); 6661 this.label = new AxisLabel(); 6662 }; 6663 6664 var AxisTicks = function() { 6665 this.show = null; 6666 this.showGridline = null; 6667 this.showLabel = null; 6668 this.showMark = null; 6669 this.size = null; 6670 this.textColor = null; 6671 this.whiteSpace = null; 6672 this.fontSize = null; 6673 this.fontFamily = null; 6674 }; 6675 6676 var AxisLabel = function() { 6677 this.textColor = null; 6678 this.whiteSpace = null; 6679 this.fontSize = null; 6680 this.fontFamily = null; 6681 this.fontWeight = null; 6682 }; 6683 6684 var LineSeriesProperties = function() { 6685 this.color=null; 6686 this.lineWidth=null; 6687 this.shadow=null; 6688 this.fillColor=null; 6689 this.showMarker=null; 6690 this.markerOptions = new MarkerOptions(); 6691 }; 6692 6693 var MarkerOptions = function() { 6694 this.show = null; 6695 this.style = null; 6696 this.lineWidth = null; 6697 this.size = null; 6698 this.color = null; 6699 this.shadow = null; 6700 }; 6701 6702 var BarSeriesProperties = function() { 6703 this.color=null; 6704 this.seriesColors=null; 6705 this.lineWidth=null; 6706 this.shadow=null; 6707 this.barPadding=null; 6708 this.barMargin=null; 6709 this.barWidth=null; 6710 this.highlightColors=null; 6711 }; 6712 6713 var PieSeriesProperties = function() { 6714 this.seriesColors=null; 6715 this.padding=null; 6716 this.sliceMargin=null; 6717 this.fill=null; 6718 this.shadow=null; 6719 this.startAngle=null; 6720 this.lineWidth=null; 6721 this.highlightColors=null; 6722 }; 6723 6724 var DonutSeriesProperties = function() { 6725 this.seriesColors=null; 6726 this.padding=null; 6727 this.sliceMargin=null; 6728 this.fill=null; 6729 this.shadow=null; 6730 this.startAngle=null; 6731 this.lineWidth=null; 6732 this.innerDiameter=null; 6733 this.thickness=null; 6734 this.ringMargin=null; 6735 this.highlightColors=null; 6736 }; 6737 6738 var FunnelSeriesProperties = function() { 6739 this.color=null; 6740 this.lineWidth=null; 6741 this.shadow=null; 6742 this.padding=null; 6743 this.sectionMargin=null; 6744 this.seriesColors=null; 6745 this.highlightColors=null; 6746 }; 6747 6748 var MeterSeriesProperties = function() { 6749 this.padding=null; 6750 this.backgroundColor=null; 6751 this.ringColor=null; 6752 this.tickColor=null; 6753 this.ringWidth=null; 6754 this.intervalColors=null; 6755 this.intervalInnerRadius=null; 6756 this.intervalOuterRadius=null; 6757 this.hubRadius=null; 6758 this.needleThickness=null; 6759 this.needlePad=null; 6760 }; 6761 6762 6763 6764 /** 6765 * JavaScript printf/sprintf functions. 6766 * 6767 * This code has been adapted from the publicly available sprintf methods 6768 * by Ash Searle. His original header follows: 6769 * 6770 * This code is unrestricted: you are free to use it however you like. 6771 * 6772 * The functions should work as expected, performing left or right alignment, 6773 * truncating strings, outputting numbers with a required precision etc. 6774 * 6775 * For complex cases, these functions follow the Perl implementations of 6776 * (s)printf, allowing arguments to be passed out-of-order, and to set the 6777 * precision or length of the output based on arguments instead of fixed 6778 * numbers. 6779 * 6780 * See http://perldoc.perl.org/functions/sprintf.html for more information. 6781 * 6782 * Implemented: 6783 * - zero and space-padding 6784 * - right and left-alignment, 6785 * - base X prefix (binary, octal and hex) 6786 * - positive number prefix 6787 * - (minimum) width 6788 * - precision / truncation / maximum width 6789 * - out of order arguments 6790 * 6791 * Not implemented (yet): 6792 * - vector flag 6793 * - size (bytes, words, long-words etc.) 6794 * 6795 * Will not implement: 6796 * - %n or %p (no pass-by-reference in JavaScript) 6797 * 6798 * @version 2007.04.27 6799 * @author Ash Searle 6800 * 6801 * You can see the original work and comments on his blog:
6802 * http://hexmen.com/blog/2007/03/printf-sprintf/ 6803 * http://hexmen.com/js/sprintf.js 6804 */ 6805 6806 /** 6807 * @Modifications 2009.05.26 6808 * @author Chris Leonello 6809 * 6810 * Added %p %P specifier 6811 * Acts like %g or %G but will not add more significant digits to the output than present in the input. 6812 * Example: 6813 * Format: '%.3p', Input: 0.012, Output: 0.012 6814 * Format: '%.3g', Input: 0.012, Output: 0.0120 6815 * Format: '%.4p', Input: 12.0, Output: 12.0 6816 * Format: '%.4g', Input: 12.0, Output: 12.00 6817 * Format: '%.4p', Input: 4.321e-5, Output: 4.321e-5 6818 * Format: '%.4g', Input: 4.321e-5, Output: 4.3210e-5 6819 */ 6820 $.jqplot.sprintf = function() { 6821 function pad(str, len, chr, leftJustify) { 6822 var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr); 6823 return leftJustify ? str + padding : padding + str; 6824 6825 } 6826 6827 function justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace) { 6828 var diff = minWidth - value.length; 6829 if (diff > 0) { 6830 var spchar = ' '; 6831 if (htmlSpace) { spchar = ' '; } 6832 if (leftJustify || !zeroPad) { 6833 value = pad(value, minWidth, spchar, leftJustify); 6834 } else { 6835 value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length); 6836 } 6837 } 6838 return value; 6839 } 6840 6841 function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad, htmlSpace) { 6842 // Note: casts negative numbers to positive ones 6843 var number = value >>> 0; 6844 prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || ''; 6845 value = prefix + pad(number.toString(base), precision || 0, '0', false); 6846 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); 6847 } 6848 6849 function formatString(value, leftJustify, minWidth, precision, zeroPad, htmlSpace) { 6850 if (precision != null) { 6851 value = value.slice(0, precision); 6852 } 6853 return justify(value, '', leftJustify, minWidth, zeroPad, htmlSpace); 6854 } 6855 6856 var a = arguments, i = 0, format = a[i++]; 6857 6858 return format.replace($.jqplot.sprintf.regex, function(substring, valueIndex, flags, minWidth, _, precision, type) { 6859 if (substring == '%%') { return '%'; } 6860 6861 // parse flags 6862 var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false, htmlSpace = false; 6863 for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) { 6864 case ' ': positivePrefix = ' '; break; 6865 case '+': positivePrefix = '+'; break; 6866 case '-': leftJustify = true; break; 6867 case '0': zeroPad = true; break; 6868 case '#': prefixBaseX = true; break; 6869 case '&': htmlSpace = true; break; 6870 } 6871 6872 // parameters may be null, undefined, empty-string or real valued 6873 // we want to ignore null, undefined and empty-string values 6874 6875 if (!minWidth) { 6876 minWidth = 0; 6877 } 6878 else if (minWidth == '*') { 6879 minWidth = +a[i++]; 6880 } 6881 else if (minWidth.charAt(0) == '*') { 6882 minWidth = +a[minWidth.slice(1, -1)]; 6883 } 6884 else { 6885 minWidth = +minWidth; 6886 } 6887 6888 // Note: undocumented perl feature: 6889 if (minWidth < 0) { 6890 minWidth = -minWidth; 6891 leftJustify = true; 6892 } 6893 6894 if (!isFinite(minWidth)) { 6895 throw new Error('$.jqplot.sprintf: (minimum-)width must be finite'); 6896 } 6897 6898 if (!precision) { 6899 precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0); 6900 } 6901 else if (precision == '*') { 6902 precision = +a[i++]; 6903 } 6904 else if (precision.charAt(0) == '*') { 6905 precision = +a[precision.slice(1, -1)]; 6906 } 6907 else { 6908 precision = +precision; 6909 } 6910 6911 // grab value using valueIndex if required? 6912 var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++]; 6913 6914 switch (type) { 6915 case 's': { 6916 if (value == null) { 6917 return ''; 6918 } 6919 return formatString(String(value), leftJustify, minWidth, precision, zeroPad, htmlSpace); 6920 } 6921 case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad, htmlSpace); 6922 case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad,htmlSpace); 6923 case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); 6924 case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); 6925 case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace).toUpperCase(); 6926 case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad, htmlSpace); 6927 case 'i': { 6928 var number = parseInt(+value, 10); 6929 if (isNaN(number)) { 6930 return ''; 6931 } 6932 var prefix = number < 0 ? '-' : positivePrefix; 6933 value = prefix + pad(String(Math.abs(number)), precision, '0', false); 6934 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); 6935 } 6936 case 'd': { 6937 var number = Math.round(+value); 6938 if (isNaN(number)) { 6939 return ''; 6940 } 6941 var prefix = number < 0 ? '-' : positivePrefix; 6942 value = prefix + pad(String(Math.abs(number)), precision, '0', false); 6943 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace); 6944 } 6945 case 'e': 6946 case 'E': 6947 case 'f': 6948 case 'F': 6949 case 'g': 6950 case 'G': 6951 { 6952 var number = +value; 6953 if (isNaN(number)) { 6954 return ''; 6955 } 6956 var prefix = number < 0 ? '-' : positivePrefix; 6957 var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())]; 6958 var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2]; 6959 value = prefix + Math.abs(number)[method](precision); 6960 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform](); 6961 } 6962 case 'p': 6963 case 'P': 6964 { 6965 // make sure number is a number 6966 var number = +value; 6967 if (isNaN(number)) { 6968 return ''; 6969 } 6970 var prefix = number < 0 ? '-' : positivePrefix; 6971 6972 var parts = String(Number(Math.abs(number)).toExponential()).split(/e|E/); 6973 var sd = (parts[0].indexOf('.') != -1) ? parts[0].length - 1 : parts[0].length; 6974 var zeros = (parts[1] < 0) ? -parts[1] - 1 : 0; 6975 6976 if (Math.abs(number) < 1) { 6977 if (sd + zeros <= precision) { 6978 value = prefix + Math.abs(number).toPrecision(sd); 6979 } 6980 else { 6981 if (sd <= precision - 1) { 6982 value = prefix + Math.abs(number).toExponential(sd-1); 6983 } 6984 else { 6985 value = prefix + Math.abs(number).toExponential(precision-1); 6986 } 6987 } 6988 } 6989 else { 6990 var prec = (sd <= precision) ? sd : precision; 6991 value = prefix + Math.abs(number).toPrecision(prec); 6992 } 6993 var textTransform = ['toString', 'toUpperCase']['pP'.indexOf(type) % 2]; 6994 return justify(value, prefix, leftJustify, minWidth, zeroPad, htmlSpace)[textTransform](); 6995 } 6996 case 'n': return ''; 6997 default: return substring; 6998 } 6999 }); 7000 }; 7001 7002 $.jqplot.sprintf.regex = /%%|%(\d+\$)?([-+#0& ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([nAscboxXuidfegpEGP])/g; 7003 7004})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.