1/* Javascript plotting library for jQuery, version 3.0.0. 2 3Copyright (c) 2007-2014 IOLA and Ole Laursen. 4Licensed under the MIT license. 5 6*/ 7 8// the actual Flot code 9(function($) { 10 "use strict"; 11 12 var Canvas = window.Flot.Canvas; 13 14 function defaultTickGenerator(axis) { 15 var ticks = [], 16 start = $.plot.saturated.saturate($.plot.saturated.floorInBase(axis.min, axis.tickSize)), 17 i = 0, 18 v = Number.NaN, 19 prev; 20 21 if (start === -Number.MAX_VALUE) { 22 ticks.push(start); 23 start = $.plot.saturated.floorInBase(axis.min + axis.tickSize, axis.tickSize); 24 } 25 26 do { 27 prev = v; 28 //v = start + i * axis.tickSize; 29 v = $.plot.saturated.multiplyAdd(axis.tickSize, i, start); 30 ticks.push(v); 31 ++i; 32 } while (v < axis.max && v !== prev); 33 34 return ticks; 35 } 36 37 function defaultTickFormatter(value, axis, precision) { 38 var oldTickDecimals = axis.tickDecimals, 39 expPosition = ("" + value).indexOf("e"); 40 41 if (expPosition !== -1) { 42 return expRepTickFormatter(value, axis, precision); 43 } 44 45 if (precision > 0) { 46 axis.tickDecimals = precision; 47 } 48 49 var factor = axis.tickDecimals ? parseFloat('1e' + axis.tickDecimals) : 1, 50 formatted = "" + Math.round(value * factor) / factor; 51 52 // If tickDecimals was specified, ensure that we have exactly that 53 // much precision; otherwise default to the value's own precision. 54 if (axis.tickDecimals != null) { 55 var decimal = formatted.indexOf("."), 56 decimalPrecision = decimal === -1 ? 0 : formatted.length - decimal - 1; 57 if (decimalPrecision < axis.tickDecimals) { 58 var decimals = ("" + factor).substr(1, axis.tickDecimals - decimalPrecision); 59 formatted = (decimalPrecision ? formatted : formatted + ".") + decimals; 60 } 61 } 62 63 axis.tickDecimals = oldTickDecimals; 64 return formatted; 65 }; 66 67 function expRepTickFormatter(value, axis, precision) { 68 var expPosition = ("" + value).indexOf("e"), 69 exponentValue = parseInt(("" + value).substr(expPosition + 1)), 70 tenExponent = expPosition !== -1 ? exponentValue : (value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0), 71 roundWith = parseFloat('1e' + tenExponent), 72 x = value / roundWith; 73 74 if (precision) { 75 var updatedPrecision = recomputePrecision(value, precision); 76 return (value / roundWith).toFixed(updatedPrecision) + 'e' + tenExponent; 77 } 78 79 if (axis.tickDecimals > 0) { 80 return x.toFixed(recomputePrecision(value, axis.tickDecimals)) + 'e' + tenExponent; 81 } 82 return x.toFixed() + 'e' + tenExponent; 83 } 84 85 function recomputePrecision(num, precision) { 86 //for numbers close to zero, the precision from flot will be a big number 87 //while for big numbers, the precision will be negative 88 var log10Value = Math.log(Math.abs(num)) * Math.LOG10E, 89 newPrecision = Math.abs(log10Value + precision); 90 91 return newPrecision <= 20 ? Math.floor(newPrecision) : 20; 92 } 93 94 /////////////////////////////////////////////////////////////////////////// 95 // The top-level container for the entire plot. 96 function Plot(placeholder, data_, options_, plugins) { 97 // data is on the form: 98 // [ series1, series2 ... ] 99 // where series is either just the data as [ [x1, y1], [x2, y2], ... ] 100 // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } 101 102 var series = [], 103 options = { 104 // the color theme used for graphs 105 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], 106 xaxis: { 107 show: null, // null = auto-detect, true = always, false = never 108 position: "bottom", // or "top" 109 mode: null, // null or "time" 110 font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } 111 color: null, // base color, labels, ticks
112 tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" 113 transform: null, // null or f: number -> number to transform axis 114 inverseTransform: null, // if transform is set, this should be the inverse function 115 min: null, // min. value to show, null means set automatically 116 max: null, // max. value to show, null means set automatically 117 autoScaleMargin: null, // margin in % to add if autoScale option is on "loose" mode, 118 autoScale: "exact", // Available modes: "none", "loose", "exact", "sliding-window" 119 windowSize: null, // null or number. This is the size of sliding-window. 120 growOnly: null, // grow only, useful for smoother auto-scale, the scales will grow to accomodate data but won't shrink back. 121 ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks 122 tickFormatter: null, // fn: number -> string 123 showTickLabels: "major", // "none", "endpoints", "major", "all" 124 labelWidth: null, // size of tick labels in pixels 125 labelHeight: null, 126 reserveSpace: null, // whether to reserve space even if axis isn't shown 127 tickLength: null, // size in pixels of major tick marks 128 showMinorTicks: null, // true = show minor tick marks, false = hide minor tick marks 129 showTicks: null, // true = show tick marks, false = hide all tick marks 130 gridLines: null, // true = show grid lines, false = hide grid lines 131 alignTicksWithAxis: null, // axis number or null for no sync 132 tickDecimals: null, // no. of decimals, null means auto 133 tickSize: null, // number or [number, "unit"] 134 minTickSize: null, // number or [number, "unit"] 135 offset: { below: 0, above: 0 }, // the plot drawing offset. this is calculated by the flot.navigate for each axis 136 boxPosition: { centerX: 0, centerY: 0 } //position of the axis on the corresponding axis box 137 }, 138 yaxis: { 139 autoScaleMargin: 0.02, // margin in % to add if autoScale option is on "loose" mode 140 autoScale: "loose", // Available modes: "none", "loose", "exact" 141 growOnly: null, // grow only, useful for smoother auto-scale, the scales will grow to accomodate data but won't shrink back. 142 position: "left", // or "right" 143 showTickLabels: "major", // "none", "endpoints", "major", "all" 144 offset: { below: 0, above: 0 }, // the plot drawing offset. this is calculated by the flot.navigate for each axis 145 boxPosition: { centerX: 0, centerY: 0 } //position of the axis on the corresponding axis box 146 }, 147 xaxes: [], 148 yaxes: [], 149 series: { 150 points: { 151 show: false, 152 radius: 3, 153 lineWidth: 2, // in pixels 154 fill: true, 155 fillColor: "#ffffff", 156 symbol: 'circle' // or callback 157 }, 158 lines: { 159 // we don't put in show: false so we can see 160 // whether lines were actively disabled 161 lineWidth: 1, // in pixels 162 fill: false, 163 fillColor: null, 164 steps: false 165 // Omit 'zero', so we can later default its value to 166 // match that of the 'fill' option. 167 }, 168 bars: { 169 show: false, 170 lineWidth: 2, // in pixels 171 // barWidth: number or [number, absolute] 172 // when 'absolute' is false, 'number' is relative to the minimum distance between points for the series 173 // when 'absolute' is true, 'number' is considered to be in units of the x-axis 174 horizontal: false, 175 barWidth: 0.8, 176 fill: true, 177 fillColor: null, 178 align: "left", // "left", "right", or "center" 179 zero: true 180 }, 181 shadowSize: 3, 182 highlightColor: null 183 }, 184 grid: { 185 show: true, 186 aboveData: false, 187 color: "#545454", // primary color used for outline and labels 188 backgroundColor: null, // null for transparent, else color 189 borderColor: null, // set if different from the grid color 190 tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" 191 margin: 0, // distance from the canvas edge to the grid 192 labelMargin: 5, // in pixels 193 axisMargin: 8, // in pixels 194 borderWidth: 1, // in pixels 195 minBorderMargin: null, // in pixels, null means taken from points radius 196 markings: null, // array of ranges or fn: axes -> array of ranges 197 markingsColor: "#f4f4f4", 198 markingsLineWidth: 2, 199 // interactive stuff 200 clickable: false, 201 hoverable: false, 202 autoHighlight: true, // highlight in case mouse is near
203 mouseActiveRadius: 15 // how far the mouse can be away to activate an item 204 }, 205 interaction: { 206 redrawOverlayInterval: 1000 / 60 // time between updates, -1 means in same flow 207 }, 208 hooks: {} 209 }, 210 surface = null, // the canvas for the plot itself 211 overlay = null, // canvas for interactive stuff on top of plot 212 eventHolder = null, // jQuery object that events should be bound to 213 ctx = null, 214 octx = null, 215 xaxes = [], 216 yaxes = [], 217 plotOffset = { 218 left: 0, 219 right: 0, 220 top: 0, 221 bottom: 0 222 }, 223 plotWidth = 0, 224 plotHeight = 0, 225 hooks = { 226 processOptions: [], 227 processRawData: [], 228 processDatapoints: [], 229 processOffset: [], 230 setupGrid: [], 231 adjustSeriesDataRange: [], 232 setRange: [], 233 drawBackground: [], 234 drawSeries: [], 235 drawAxis: [], 236 draw: [], 237 axisReserveSpace: [], 238 bindEvents: [], 239 drawOverlay: [], 240 resize: [], 241 shutdown: [] 242 }, 243 plot = this; 244 245 var eventManager = {}; 246 247 // interactive features 248 249 var redrawTimeout = null; 250 251 // public functions 252 plot.setData = setData; 253 plot.setupGrid = setupGrid; 254 plot.draw = draw; 255 plot.getPlaceholder = function() { 256 return placeholder; 257 }; 258 plot.getCanvas = function() { 259 return surface.element; 260 }; 261 plot.getSurface = function() { 262 return surface; 263 }; 264 plot.getEventHolder = function() { 265 return eventHolder[0]; 266 }; 267 plot.getPlotOffset = function() { 268 return plotOffset; 269 }; 270 plot.width = function() { 271 return plotWidth; 272 }; 273 plot.height = function() { 274 return plotHeight; 275 }; 276 plot.offset = function() { 277 var o = eventHolder.offset(); 278 o.left += plotOffset.left; 279 o.top += plotOffset.top; 280 return o; 281 }; 282 plot.getData = function() { 283 return series; 284 }; 285 plot.getAxes = function() { 286 var res = {}; 287 $.each(xaxes.concat(yaxes), function(_, axis) { 288 if (axis) { 289 res[axis.direction + (axis.n !== 1 ? axis.n : "") + "axis"] = axis; 290 } 291 }); 292 return res; 293 }; 294 plot.getXAxes = function() { 295 return xaxes; 296 }; 297 plot.getYAxes = function() { 298 return yaxes; 299 }; 300 plot.c2p = canvasToCartesianAxisCoords; 301 plot.p2c = cartesianAxisToCanvasCoords; 302 plot.getOptions = function() { 303 return options; 304 }; 305 plot.triggerRedrawOverlay = triggerRedrawOverlay; 306 plot.pointOffset = function(point) { 307 return { 308 left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), 309 top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) 310 }; 311 }; 312 plot.shutdown = shutdown; 313 plot.destroy = function() { 314 shutdown(); 315 placeholder.removeData("plot").empty(); 316 317 series = []; 318 options = null; 319 surface = null; 320 overlay = null; 321 eventHolder = null; 322 ctx = null; 323 octx = null; 324 xaxes = []; 325 yaxes = []; 326 hooks = null; 327 plot = null; 328 }; 329 330 plot.resize = function() { 331 var width = placeholder.width(), 332 height = placeholder.height(); 333 surface.resize(width, height); 334 overlay.resize(width, height); 335 336 executeHooks(hooks.resize, [width, height]); 337 }; 338 339 plot.clearTextCache = function () { 340 surface.clearCache(); 341 overlay.clearCache(); 342 }; 343
344 plot.autoScaleAxis = autoScaleAxis; 345 plot.computeRangeForDataSeries = computeRangeForDataSeries; 346 plot.adjustSeriesDataRange = adjustSeriesDataRange; 347 plot.findNearbyItem = findNearbyItem; 348 plot.findNearbyInterpolationPoint = findNearbyInterpolationPoint; 349 plot.computeValuePrecision = computeValuePrecision; 350 plot.computeTickSize = computeTickSize; 351 plot.addEventHandler = addEventHandler; 352 353 // public attributes 354 plot.hooks = hooks; 355 356 // initialize 357 var MINOR_TICKS_COUNT_CONSTANT = $.plot.uiConstants.MINOR_TICKS_COUNT_CONSTANT; 358 var TICK_LENGTH_CONSTANT = $.plot.uiConstants.TICK_LENGTH_CONSTANT; 359 initPlugins(plot); 360 setupCanvases(); 361 parseOptions(options_); 362 setData(data_); 363 setupGrid(true); 364 draw(); 365 bindEvents(); 366 367 function executeHooks(hook, args) { 368 args = [plot].concat(args); 369 for (var i = 0; i < hook.length; ++i) { 370 hook[i].apply(this, args); 371 } 372 } 373 374 function initPlugins() { 375 // References to key classes, allowing plugins to modify them 376 377 var classes = { 378 Canvas: Canvas 379 }; 380 381 for (var i = 0; i < plugins.length; ++i) { 382 var p = plugins[i]; 383 p.init(plot, classes); 384 if (p.options) { 385 $.extend(true, options, p.options); 386 } 387 } 388 } 389 390 function parseOptions(opts) { 391 $.extend(true, options, opts); 392 393 // $.extend merges arrays, rather than replacing them. When less 394 // colors are provided than the size of the default palette, we 395 // end up with those colors plus the remaining defaults, which is 396 // not expected behavior; avoid it by replacing them here. 397 398 if (opts && opts.colors) { 399 options.colors = opts.colors; 400 } 401 402 if (options.xaxis.color == null) { 403 options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); 404 } 405 406 if (options.yaxis.color == null) { 407 options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); 408 } 409 410 if (options.xaxis.tickColor == null) { 411 // grid.tickColor for back-compatibility 412 options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; 413 } 414 415 if (options.yaxis.tickColor == null) { 416 // grid.tickColor for back-compatibility 417 options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; 418 } 419 420 if (options.grid.borderColor == null) { 421 options.grid.borderColor = options.grid.color; 422 } 423 424 if (options.grid.tickColor == null) { 425 options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); 426 } 427 428 // Fill in defaults for axis options, including any unspecified 429 // font-spec fields, if a font-spec was provided. 430 431 // If no x/y axis options were provided, create one of each anyway, 432 // since the rest of the code assumes that they exist. 433 434 var i, axisOptions, axisCount, 435 fontSize = placeholder.css("font-size"), 436 fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, 437 fontDefaults = { 438 style: placeholder.css("font-style"), 439 size: Math.round(0.8 * fontSizeDefault), 440 variant: placeholder.css("font-variant"), 441 weight: placeholder.css("font-weight"), 442 family: placeholder.css("font-family") 443 }; 444 445 axisCount = options.xaxes.length || 1; 446 for (i = 0; i < axisCount; ++i) { 447 axisOptions = options.xaxes[i]; 448 if (axisOptions && !axisOptions.tickColor) { 449 axisOptions.tickColor = axisOptions.color; 450 } 451 452 axisOptions = $.extend(true, {}, options.xaxis, axisOptions); 453 options.xaxes[i] = axisOptions; 454 455 if (axisOptions.font) { 456 axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); 457 if (!axisOptions.font.color) { 458 axisOptions.font.color = axisOptions.color; 459 } 460 if (!axisOptions.font.lineHeight) { 461 axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); 462 } 463 } 464 } 465 466 axisCount = options.yaxes.length || 1; 467 for (i = 0; i < axisCount; ++i) { 468 axisOptions = options.yaxes[i]; 469 if (axisOptions && !axisOptions.tickColor) { 470 axisOptions.tickColor = axisOptions.color; 471 } 472 473 axisOptions = $.extend(true, {}, options.yaxis, axisOptions); 474 options.yaxes[i] = axisOptions; 475 476 if (axisOptions.font) { 477 axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); 478 if (!axisOptions.font.color) { 479 axisOptions.font.color = axisOptions.color; 480 } 481 if (!axisOptions.font.lineHeight) { 482 axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); 483 } 484 } 485 } 486 487 // save options on axes for future reference 488 for (i = 0; i < options.xaxes.length; ++i) { 489 getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; 490 } 491 492 for (i = 0; i < options.yaxes.length; ++i) { 493 getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; 494 } 495 496 //process boxPosition options used for axis.box size 497 $.each(allAxes(), function(_, axis) { 498 axis.boxPosition = axis.options.boxPosition || {centerX: 0, centerY: 0}; 499 }); 500 501 // add hooks from options 502 for (var n in hooks) { 503 if (options.hooks[n] && options.hooks[n].length) { 504 hooks[n] = hooks[n].concat(options.hooks[n]); 505 } 506 } 507 508 executeHooks(hooks.processOptions, [options]); 509 } 510 511 function setData(d) { 512 var oldseries = series; 513 series = parseData(d); 514 fillInSeriesOptions();
515 processData(oldseries); 516 } 517 518 function parseData(d) { 519 var res = []; 520 for (var i = 0; i < d.length; ++i) { 521 var s = $.extend(true, {}, options.series); 522 523 if (d[i].data != null) { 524 s.data = d[i].data; // move the data instead of deep-copy 525 delete d[i].data; 526 527 $.extend(true, s, d[i]); 528 529 d[i].data = s.data; 530 } else { 531 s.data = d[i]; 532 } 533 534 res.push(s); 535 } 536 537 return res; 538 } 539 540 function axisNumber(obj, coord) { 541 var a = obj[coord + "axis"]; 542 if (typeof a === "object") { 543 // if we got a real axis, extract number 544 a = a.n; 545 } 546 547 if (typeof a !== "number") { 548 a = 1; // default to first axis 549 } 550 551 return a; 552 } 553 554 function allAxes() { 555 // return flat array without annoying null entries 556 return xaxes.concat(yaxes).filter(function(a) { 557 return a; 558 }); 559 } 560 561 // canvas to axis for cartesian axes 562 function canvasToCartesianAxisCoords(pos) { 563 // return an object with x/y corresponding to all used axes 564 var res = {}, 565 i, axis; 566 for (i = 0; i < xaxes.length; ++i) { 567 axis = xaxes[i]; 568 if (axis && axis.used) { 569 res["x" + axis.n] = axis.c2p(pos.left); 570 } 571 } 572 573 for (i = 0; i < yaxes.length; ++i) { 574 axis = yaxes[i]; 575 if (axis && axis.used) { 576 res["y" + axis.n] = axis.c2p(pos.top); 577 } 578 } 579 580 if (res.x1 !== undefined) { 581 res.x = res.x1; 582 } 583 584 if (res.y1 !== undefined) { 585 res.y = res.y1; 586 } 587 588 return res; 589 } 590 591 // axis to canvas for cartesian axes 592 function cartesianAxisToCanvasCoords(pos) { 593 // get canvas coords from the first pair of x/y found in pos 594 var res = {}, 595 i, axis, key; 596 597 for (i = 0; i < xaxes.length; ++i) { 598 axis = xaxes[i]; 599 if (axis && axis.used) { 600 key = "x" + axis.n; 601 if (pos[key] == null && axis.n === 1) { 602 key = "x"; 603 } 604 605 if (pos[key] != null) { 606 res.left = axis.p2c(pos[key]); 607 break; 608 } 609 } 610 } 611 612 for (i = 0; i < yaxes.length; ++i) { 613 axis = yaxes[i]; 614 if (axis && axis.used) { 615 key = "y" + axis.n; 616 if (pos[key] == null && axis.n === 1) { 617 key = "y"; 618 } 619 620 if (pos[key] != null) { 621 res.top = axis.p2c(pos[key]); 622 break; 623 } 624 } 625 } 626 627 return res; 628 } 629 630 function getOrCreateAxis(axes, number) { 631 if (!axes[number - 1]) { 632 axes[number - 1] = { 633 n: number, // save the number for future reference 634 direction: axes === xaxes ? "x" : "y", 635 options: $.extend(true, {}, axes === xaxes ? options.xaxis : options.yaxis) 636 }; 637 } 638 639 return axes[number - 1]; 640 } 641 642 function fillInSeriesOptions() { 643 var neededColors = series.length, 644 maxIndex = -1, 645 i; 646 647 // Subtract the number of series that already have fixed colors or 648 // color indexes from the number that we still need to generate. 649 650 for (i = 0; i < series.length; ++i) { 651 var sc = series[i].color; 652 if (sc != null) { 653 neededColors--; 654 if (typeof sc === "number" && sc > maxIndex) { 655 maxIndex = sc; 656 } 657 } 658 } 659 660 // If any of the series have fixed color indexes, then we need to 661 // generate at least as many colors as the highest index. 662 663 if (neededColors <= maxIndex) { 664 neededColors = maxIndex + 1; 665 } 666 667 // Generate all the colors, using first the option colors and then 668 // variations on those colors once they're exhausted. 669 670 var c, colors = [], 671 colorPool = options.colors, 672 colorPoolSize = colorPool.length, 673 variation = 0, 674 definedColors = Math.max(0, series.length - neededColors); 675 676 for (i = 0; i < neededColors; i++) { 677 c = $.color.parse(colorPool[(definedColors + i) % colorPoolSize] || "#666"); 678 679 // Each time we exhaust the colors in the pool we adjust 680 // a scaling factor used to produce more variations on 681 // those colors. The factor alternates negative/positive 682 // to produce lighter/darker colors. 683 684 // Reset the variation after every few cycles, or else 685 // it will end up producing only white or black colors. 686 687 if (i % colorPoolSize === 0 && i) { 688 if (variation >= 0) { 689 if (variation < 0.5) { 690 variation = -variation - 0.2; 691 } else variation = 0; 692 } else variation = -variation; 693 } 694 695 colors[i] = c.scale('rgb', 1 + variation); 696 } 697 698 // Finalize the series options, filling in their colors 699 700 var colori = 0, 701 s; 702 for (i = 0; i < series.length; ++i) { 703 s = series[i]; 704 705 // assign colors 706 if (s.color == null) { 707 s.color = colors[colori].toString(); 708 ++colori; 709 } else if (typeof s.color === "number") { 710 s.color = colors[s.color].toString(); 711 } 712 713 // turn on lines automatically in case nothing is set 714 if (s.lines.show == null) { 715 var v, show = true; 716 for (v in s) { 717 if (s[v] && s[v].show) { 718 show = false;
719 break; 720 } 721 } 722 723 if (show) { 724 s.lines.show = true; 725 } 726 } 727 728 // If nothing was provided for lines.zero, default it to match 729 // lines.fill, since areas by default should extend to zero. 730 731 if (s.lines.zero == null) { 732 s.lines.zero = !!s.lines.fill; 733 } 734 735 // setup axes 736 s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); 737 s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); 738 } 739 } 740 741 function processData(prevSeries) { 742 var topSentry = Number.POSITIVE_INFINITY, 743 bottomSentry = Number.NEGATIVE_INFINITY, 744 i, j, k, m, 745 s, points, ps, val, f, p, 746 data, format; 747 748 function updateAxis(axis, min, max) { 749 if (min < axis.datamin && min !== -Infinity) { 750 axis.datamin = min; 751 } 752 753 if (max > axis.datamax && max !== Infinity) { 754 axis.datamax = max; 755 } 756 } 757 758 function reusePoints(prevSeries, i) { 759 if (prevSeries && prevSeries[i] && prevSeries[i].datapoints && prevSeries[i].datapoints.points) { 760 return prevSeries[i].datapoints.points; 761 } 762 763 return []; 764 } 765 766 $.each(allAxes(), function(_, axis) { 767 // init axis 768 if (axis.options.growOnly !== true) { 769 axis.datamin = topSentry; 770 axis.datamax = bottomSentry; 771 } else { 772 if (axis.datamin === undefined) { 773 axis.datamin = topSentry; 774 } 775 if (axis.datamax === undefined) { 776 axis.datamax = bottomSentry; 777 } 778 } 779 axis.used = false; 780 }); 781 782 for (i = 0; i < series.length; ++i) { 783 s = series[i]; 784 s.datapoints = { 785 points: [] 786 }; 787 788 if (s.datapoints.points.length === 0) { 789 s.datapoints.points = reusePoints(prevSeries, i); 790 } 791 792 executeHooks(hooks.processRawData, [s, s.data, s.datapoints]); 793 } 794 795 // first pass: clean and copy data 796 for (i = 0; i < series.length; ++i) { 797 s = series[i]; 798 799 data = s.data; 800 format = s.datapoints.format; 801 802 if (!format) { 803 format = []; 804 // find out how to copy 805 format.push({ 806 x: true, 807 y: false, 808 number: true, 809 required: true, 810 computeRange: s.xaxis.options.autoScale !== 'none', 811 defaultValue: null 812 }); 813 814 format.push({ 815 x: false, 816 y: true, 817 number: true, 818 required: true, 819 computeRange: s.yaxis.options.autoScale !== 'none', 820 defaultValue: null 821 }); 822 823 if (s.stack || s.bars.show || (s.lines.show && s.lines.fill)) { 824 var expectedPs = s.datapoints.pointsize != null ? s.datapoints.pointsize : (s.data && s.data[0] && s.data[0].length ? s.data[0].length : 3); 825 if (expectedPs > 2) { 826 format.push({ 827 x: false, 828 y: true, 829 number: true, 830 required: false, 831 computeRange: s.yaxis.options.autoScale !== 'none', 832 defaultValue: 0 833 }); 834 } 835 } 836 837 s.datapoints.format = format; 838 } 839 840 s.xaxis.used = s.yaxis.used = true; 841 842 if (s.datapoints.pointsize != null) continue; // already filled in 843 844 s.datapoints.pointsize = format.length; 845 ps = s.datapoints.pointsize; 846 points = s.datapoints.points; 847 848 var insertSteps = s.lines.show && s.lines.steps; 849 850 for (j = k = 0; j < data.length; ++j, k += ps) { 851 p = data[j]; 852 853 var nullify = p == null; 854 if (!nullify) { 855 for (m = 0; m < ps; ++m) { 856 val = p[m]; 857 f = format[m]; 858 859 if (f) { 860 if (f.number && val != null) { 861 val = +val; // convert to number 862 if (isNaN(val)) { 863 val = null; 864 } 865 } 866 867 if (val == null) { 868 if (f.required) nullify = true; 869 870 if (f.defaultValue != null) val = f.defaultValue; 871 } 872 } 873 874 points[k + m] = val; 875 } 876 } 877 878 if (nullify) { 879 for (m = 0; m < ps; ++m) { 880 val = points[k + m]; 881 if (val != null) { 882 f = format[m]; 883 // extract min/max info 884 if (f.computeRange) { 885 if (f.x) { 886 updateAxis(s.xaxis, val, val); 887 } 888 if (f.y) { 889 updateAxis(s.yaxis, val, val); 890 } 891 } 892 } 893 points[k + m] = null; 894 } 895 } 896 } 897 898 points.length = k; //trims the internal buffer to the correct length 899 } 900 901 // give the hooks a chance to run 902 for (i = 0; i < series.length; ++i) { 903 s = series[i]; 904 905 executeHooks(hooks.processDatapoints, [s, s.datapoints]); 906 } 907 908 // second pass: find datamax/datamin for auto-scaling 909 for (i = 0; i < series.length; ++i) { 910 s = series[i]; 911 format = s.datapoints.format; 912 913 if (format.every(function (f) { return !f.computeRange; })) { 914 continue; 915 } 916 917 var range = plot.adjustSeriesDataRange(s, 918 plot.computeRangeForDataSeries(s)); 919 920 executeHooks(hooks.adjustSeriesDataRange, [s, range]); 921 922 updateAxis(s.xaxis, range.xmin, range.xmax); 923 updateAxis(s.yaxis, range.ymin, range.ymax); 924 } 925 926 $.each(allAxes(), function(_, axis) { 927 if (axis.datamin === topSentry) { 928 axis.datamin = null; 929 } 930 931 if (axis.datamax === bottomSentry) { 932 axis.datamax = null; 933 } 934 }); 935 } 936 937 function setupCanvases() { 938 // Make sure the placeholder is clear of everything except canvase
938s 939 // from a previous plot in this container that we'll try to re-use. 940 941 placeholder.css("padding", 0) // padding messes up the positioning 942 .children().filter(function() { 943 return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); 944 }).remove(); 945 946 if (placeholder.css("position") === 'static') { 947 placeholder.css("position", "relative"); // for positioning labels and overlay 948 } 949 950 surface = new Canvas("flot-base", placeholder[0]); 951 overlay = new Canvas("flot-overlay", placeholder[0]); // overlay canvas for interactive features 952 953 ctx = surface.context; 954 octx = overlay.context; 955 956 // define which element we're listening for events on 957 eventHolder = $(overlay.element).unbind(); 958 959 // If we're re-using a plot object, shut down the old one 960 961 var existing = placeholder.data("plot"); 962 963 if (existing) { 964 existing.shutdown(); 965 overlay.clear(); 966 } 967 968 // save in case we get replotted 969 placeholder.data("plot", plot); 970 } 971 972 function bindEvents() { 973 executeHooks(hooks.bindEvents, [eventHolder]); 974 } 975 976 function addEventHandler(event, handler, eventHolder, priority) { 977 var key = eventHolder + event; 978 var eventList = eventManager[key] || []; 979 980 eventList.push({"event": event, "handler": handler, "eventHolder": eventHolder, "priority": priority}); 981 eventList.sort((a, b) => b.priority - a.priority ); 982 eventList.forEach( eventData => { 983 eventData.eventHolder.unbind(eventData.event, eventData.handler); 984 eventData.eventHolder.bind(eventData.event, eventData.handler); 985 }); 986 987 eventManager[key] = eventList; 988 } 989 990 function shutdown() { 991 if (redrawTimeout) { 992 clearTimeout(redrawTimeout); 993 } 994 995 executeHooks(hooks.shutdown, [eventHolder]); 996 } 997 998 function setTransformationHelpers(axis) { 999 // set helper functions on the axis, assumes plot area 1000 // has been computed already 1001 1002 function identity(x) { 1003 return x; 1004 } 1005 1006 var s, m, t = axis.options.transform || identity, 1007 it = axis.options.inverseTransform; 1008 1009 // precompute how much the axis is scaling a point 1010 // in canvas space 1011 if (axis.direction === "x") { 1012 if (isFinite(t(axis.max) - t(axis.min))) { 1013 s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); 1014 } else { 1015 s = axis.scale = 1 / Math.abs($.plot.saturated.delta(t(axis.min), t(axis.max), plotWidth)); 1016 } 1017 m = Math.min(t(axis.max), t(axis.min)); 1018 } else { 1019 if (isFinite(t(axis.max) - t(axis.min))) { 1020 s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); 1021 } else { 1022 s = axis.scale = 1 / Math.abs($.plot.saturated.delta(t(axis.min), t(axis.max), plotHeight)); 1023 } 1024 s = -s; 1025 m = Math.max(t(axis.max), t(axis.min)); 1026 } 1027 1028 // data point to canvas coordinate 1029 if (t === identity) { 1030 // slight optimization 1031 axis.p2c = function(p) { 1032 if (isFinite(p - m)) { 1033 return (p - m) * s; 1034 } else { 1035 return (p / 4 - m / 4) * s * 4; 1036 } 1037 }; 1038 } else { 1039 axis.p2c = function(p) { 1040 var tp = t(p); 1041 1042 if (isFinite(tp - m)) { 1043 return (tp - m) * s; 1044 } else { 1045 return (tp / 4 - m / 4) * s * 4; 1046 } 1047 }; 1048 } 1049 1050 // canvas coordinate to data point 1051 if (!it) { 1052 axis.c2p = function(c) { 1053 return m + c / s; 1054 }; 1055 } else { 1056 axis.c2p = function(c) { 1057 return it(m + c / s); 1058 }; 1059 } 1060 } 1061 1062 function measureTickLabels(axis) { 1063 var opts = axis.options, 1064 ticks = opts.showTickLabels !== 'none' && axis.ticks ? axis.ticks : [], 1065 showMajorTickLabels = opts.showTickLabels === 'major' || opts.showTickLabels === 'all', 1066 showEndpointsTickLabels = opts.showTickLabels === 'endpoints' || opts.showTickLabels === 'all', 1067 labelWidth = opts.labelWidth || 0, 1068 labelHeight = opts.labelHeight || 0, 1069 legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", 1070 layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, 1071 font = opts.font || "flot-tick-label tickLabel"; 1072 1073 for (var i = 0; i < ticks.length; ++i) { 1074 var t = ticks[i]; 1075 var label = t.label; 1076 1077 if (!t.label || 1078 (showMajorTickLabels === false && i > 0 && i < ticks.length - 1) || 1079 (showEndpointsTickLabels === false && (i === 0 || i === ticks.length - 1))) { 1080 continue; 1081 } 1082 1083 if (typeof t.label === 'object') { 1084 label = t.label.name; 1085 } 1086 1087 var info = surface.getTextInfo(layer, label, font); 1088 1089 labelWidth = Math.max(labelWidth, info.width); 1090 labelHeight = Math.max(labelHeight, info.height); 1091 } 1092 1093 axis.labelWidth = opts.labelWidth || labelWidth; 1094 axis.labelHeight = opts.labelHeight || labelHeight; 1095 } 1096 1097 function allocateAxisBoxFirstPhase(axis) { 1098 // find the bounding box of the axis by looking at label 1099 // widths/heights and ticks, make room by diminishing the 1100 // plotOffset; this first phase only looks at one 1101 // dimension per axis, the other dimension depends on the 1102 // other axes so will have to wait 1103 1104 // here reserve additional space 1105 executeHooks(hooks.axisReserveSpace, [axis]); 1106 1107 var lw = axis.labelWidth, 1108 lh = axis.labelHeight, 1109 pos = axis.options.position, 1110 isXAxis = axis.direction === "x", 1111 tickLength = axis.options.tickLength, 1112 showTicks = axis.options.showTicks, 1113 showMinorTicks = axis.options.showMinorTicks, 1114 gridLines = axis.options.gridLines, 1115 axisMargin = options.grid.axisMargin, 1116 padding = options.grid.labelMargin, 1117 innermost = true, 1118 outermost = true, 1119 found = false;
1120 1121 // Determine the axis's position in its direction and on its side 1122 1123 $.each(isXAxis ? xaxes : yaxes, function(i, a) { 1124 if (a && (a.show || a.reserveSpace)) { 1125 if (a === axis) { 1126 found = true; 1127 } else if (a.options.position === pos) { 1128 if (found) { 1129 outermost = false; 1130 } else { 1131 innermost = false; 1132 } 1133 } 1134 } 1135 }); 1136 1137 // The outermost axis on each side has no margin 1138 if (outermost) { 1139 axisMargin = 0; 1140 } 1141 1142 // Set the default tickLength if necessary 1143 if (tickLength == null) { 1144 tickLength = TICK_LENGTH_CONSTANT; 1145 } 1146 1147 // By default, major tick marks are visible 1148 if (showTicks == null) { 1149 showTicks = true; 1150 } 1151 1152 // By default, minor tick marks are visible 1153 if (showMinorTicks == null) { 1154 showMinorTicks = true; 1155 } 1156 1157 // By default, grid lines are visible 1158 if (gridLines == null) { 1159 if (innermost) { 1160 gridLines = true; 1161 } else { 1162 gridLines = false; 1163 } 1164 } 1165 1166 if (!isNaN(+tickLength)) { 1167 padding += showTicks ? +tickLength : 0; 1168 } 1169 1170 if (isXAxis) { 1171 lh += padding; 1172 1173 if (pos === "bottom") { 1174 plotOffset.bottom += lh + axisMargin; 1175 axis.box = { 1176 top: surface.height - plotOffset.bottom, 1177 height: lh 1178 }; 1179 } else { 1180 axis.box = { 1181 top: plotOffset.top + axisMargin, 1182 height: lh 1183 }; 1184 plotOffset.top += lh + axisMargin; 1185 } 1186 } else { 1187 lw += padding; 1188 1189 if (pos === "left") { 1190 axis.box = { 1191 left: plotOffset.left + axisMargin, 1192 width: lw 1193 }; 1194 plotOffset.left += lw + axisMargin; 1195 } else { 1196 plotOffset.right += lw + axisMargin; 1197 axis.box = { 1198 left: surface.width - plotOffset.right, 1199 width: lw 1200 }; 1201 } 1202 } 1203 1204 // save for future reference 1205 axis.position = pos; 1206 axis.tickLength = tickLength; 1207 axis.showMinorTicks = showMinorTicks; 1208 axis.showTicks = showTicks; 1209 axis.gridLines = gridLines; 1210 axis.box.padding = padding; 1211 axis.innermost = innermost; 1212 } 1213 1214 function allocateAxisBoxSecondPhase(axis) { 1215 // now that all axis boxes have been placed in one 1216 // dimension, we can set the remaining dimension coordinates 1217 if (axis.direction === "x") { 1218 axis.box.left = plotOffset.left - axis.labelWidth / 2; 1219 axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; 1220 } else { 1221 axis.box.top = plotOffset.top - axis.labelHeight / 2;
1222 axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; 1223 } 1224 } 1225 1226 function adjustLayoutForThingsStickingOut() { 1227 // possibly adjust plot offset to ensure everything stays 1228 // inside the canvas and isn't clipped off 1229 1230 var minMargin = options.grid.minBorderMargin, 1231 i; 1232 1233 // check stuff from the plot (FIXME: this should just read 1234 // a value from the series, otherwise it's impossible to 1235 // customize) 1236 if (minMargin == null) { 1237 minMargin = 0; 1238 for (i = 0; i < series.length; ++i) { 1239 minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth / 2)); 1240 } 1241 } 1242 1243 var a, offset = {}, 1244 margins = { 1245 left: minMargin, 1246 right: minMargin, 1247 top: minMargin, 1248 bottom: minMargin 1249 }; 1250 1251 // check axis labels, note we don't check the actual 1252 // labels but instead use the overall width/height to not 1253 // jump as much around with replots 1254 $.each(allAxes(), function(_, axis) { 1255 if (axis.reserveSpace && axis.ticks && axis.ticks.length) { 1256 if (axis.direction === "x") { 1257 margins.left = Math.max(margins.left, axis.labelWidth / 2); 1258 margins.right = Math.max(margins.right, axis.labelWidth / 2); 1259 } else { 1260 margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); 1261 margins.top = Math.max(margins.top, axis.labelHeight / 2); 1262 } 1263 } 1264 }); 1265 1266 for (a in margins) { 1267 offset[a] = margins[a] - plotOffset[a]; 1268 } 1269 $.each(xaxes.concat(yaxes), function(_, axis) { 1270 alignAxisWithGrid(axis, offset, function (offset) { 1271 return offset > 0; 1272 }); 1273 }); 1274
1275 plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); 1276 plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); 1277 plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); 1278 plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); 1279 } 1280 1281 function alignAxisWithGrid(axis, offset, isValid) { 1282 if (axis.direction === "x") { 1283 if (axis.position === "bottom" && isValid(offset.bottom)) { 1284 axis.box.top -= Math.ceil(offset.bottom); 1285 } 1286 if (axis.position === "top" && isValid(offset.top)) { 1287 axis.box.top += Math.ceil(offset.top); 1288 } 1289 } else { 1290 if (axis.position === "left" && isValid(offset.left)) { 1291 axis.box.left += Math.ceil(offset.left); 1292 } 1293 if (axis.position === "right" && isValid(offset.right)) { 1294 axis.box.left -= Math.ceil(offset.right); 1295 } 1296 } 1297 } 1298 1299 function setupGrid(autoScale) { 1300 var i, a, axes = allAxes(), 1301 showGrid = options.grid.show; 1302 1303 // Initialize the plot's offset from the edge of the canvas 1304 1305 for (a in plotOffset) { 1306 plotOffset[a] = 0; 1307 } 1308 1309 executeHooks(hooks.processOffset, [plotOffset]); 1310 1311 // If the grid is visible, add its border width to the offset 1312 for (a in plotOffset) { 1313 if (typeof (options.grid.borderWidth) === "object") { 1314 plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; 1315 } else { 1316 plotOffset[a] += showGrid ? options.grid.borderWidth : 0; 1317 } 1318 } 1319 1320 $.each(axes, function(_, axis) { 1321 var axisOpts = axis.options; 1322 axis.show = axisOpts.show == null ? axis.used : axisOpts.show; 1323 axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; 1324 setupTickFormatter(axis); 1325 executeHooks(hooks.setRange, [axis, autoScale]); 1326 setRange(axis, autoScale); 1327 }); 1328 1329 if (showGrid) { 1330 plotWidth = surface.width - plotOffset.left - plotOffset.right;
1331 plotHeight = surface.height - plotOffset.bottom - plotOffset.top; 1332 1333 var allocatedAxes = $.grep(axes, function(axis) { 1334 return axis.show || axis.reserveSpace; 1335 }); 1336 1337 $.each(allocatedAxes, function(_, axis) { 1338 // make the ticks 1339 setupTickGeneration(axis); 1340 setMajorTicks(axis); 1341 snapRangeToTicks(axis, axis.ticks, series); 1342 1343 //for computing the endpoints precision, transformationHelpers are needed 1344 setTransformationHelpers(axis); 1345 setEndpointTicks(axis, series); 1346 1347 // find labelWidth/Height for axis 1348 measureTickLabels(axis); 1349 }); 1350 1351 // with all dimensions calculated, we can compute the 1352 // axis bounding boxes, start from the outside 1353 // (reverse order) 1354 for (i = allocatedAxes.length - 1; i >= 0; --i) { 1355 allocateAxisBoxFirstPhase(allocatedAxes[i]); 1356 } 1357 1358 // make sure we've got enough space for things that 1359 // might stick out 1360 adjustLayoutForThingsStickingOut(); 1361 1362 $.each(allocatedAxes, function(_, axis) { 1363 allocateAxisBoxSecondPhase(axis); 1364 }); 1365 } 1366 1367 //adjust axis and plotOffset according to grid.margins 1368 if (options.grid.margin) { 1369 for (a in plotOffset) { 1370 var margin = options.grid.margin || 0; 1371 plotOffset[a] += typeof margin === "number" ? margin : (margin[a] || 0); 1372 } 1373 $.each(xaxes.concat(yaxes), function(_, axis) { 1374 alignAxisWithGrid(axis, options.grid.margin, function(offset) { 1375 return offset !== undefined && offset !== null; 1376 }); 1377 }); 1378 } 1379 1380 //after adjusting the axis, plot width and height will be modified 1381 plotWidth = surface.width - plotOffset.left - plotOffset.right;
1382 plotHeight = surface.height - plotOffset.bottom - plotOffset.top; 1383 1384 // now we got the proper plot dimensions, we can compute the scaling 1385 $.each(axes, function(_, axis) { 1386 setTransformationHelpers(axis); 1387 }); 1388 1389 if (showGrid) { 1390 drawAxisLabels(); 1391 } 1392 1393 executeHooks(hooks.setupGrid, []); 1394 } 1395 1396 function widenMinMax(minimum, maximum) { 1397 var min = (minimum === undefined ? null : minimum); 1398 var max = (maximum === undefined ? null : maximum); 1399 var delta = max - min; 1400 if (delta === 0.0) { 1401 // degenerate case 1402 var widen = max === 0 ? 1 : 0.01; 1403 var wmin = null; 1404 if (min == null) { 1405 wmin -= widen; 1406 } 1407 1408 // always widen max if we couldn't widen min to ensure we 1409 // don't fall into min == max which doesn't work 1410 if (max == null || min != null) { 1411 max += widen; 1412 } 1413 1414 if (wmin != null) { 1415 min = wmin; 1416 } 1417 } 1418 1419 return { 1420 min: min, 1421 max: max 1422 }; 1423 } 1424 1425 function autoScaleAxis(axis) { 1426 var opts = axis.options, 1427 min = opts.min, 1428 max = opts.max, 1429 datamin = axis.datamin, 1430 datamax = axis.datamax, 1431 delta; 1432 1433 switch (opts.autoScale) { 1434 case "none": 1435 min = +(opts.min != null ? opts.min : datamin); 1436 max = +(opts.max != null ? opts.max : datamax); 1437 break; 1438 case "loose": 1439 if (datamin != null && datamax != null) { 1440 min = datamin; 1441 max = datamax; 1442 delta = $.plot.saturated.saturate(max - min); 1443 var margin = ((typeof opts.autoScaleMargin === 'number') ? opts.autoScaleMargin : 0.02); 1444 min = $.plot.saturated.saturate(min - delta * margin); 1445 max = $.plot.saturated.saturate(max + delta * margin); 1446 1447 // make sure we don't go below zero if all values are positive 1448 if (min < 0 && datamin >= 0) { 1449 min = 0; 1450 } 1451 } else { 1452 min = opts.min; 1453 max = opts.max; 1454 } 1455 break; 1456 case "exact": 1457 min = (datamin != null ? datamin : opts.min); 1458 max = (datamax != null ? datamax : opts.max); 1459 break; 1460 case "sliding-window": 1461 if (datamax > max) { 1462 // move the window to fit the new data, 1463 // keeping the axis range constant 1464 max = datamax; 1465 min = Math.max(datamax - (opts.windowSize || 100), min); 1466 } 1467 break; 1468 } 1469 1470 var widenedMinMax = widenMinMax(min, max); 1471 min = widenedMinMax.min; 1472 max = widenedMinMax.max; 1473 1474 // grow loose or grow exact supported 1475 if (opts.growOnly === true && opts.autoScale !== "none" && opts.autoScale !== "sliding-window") { 1476 min = (min < datamin) ? min : (datamin !== null ? datamin : min); 1477 max = (max > datamax) ? max : (datamax !== null ? datamax : max); 1478 } 1479 1480 axis.autoScaledMin = min; 1481 axis.autoScaledMax = max; 1482 } 1483 1484 function setRange(axis, autoScale) { 1485 var min = typeof axis.options.min === 'number' ? axis.options.min : axis.min, 1486 max = typeof axis.options.max === 'number' ? axis.options.max : axis.max, 1487 plotOffset = axis.options.offset; 1488 1489 if (autoScale) { 1490 autoScaleAxis(axis); 1491 min = axis.autoScaledMin; 1492 max = axis.autoScaledMax; 1493 } 1494 1495 min = (min != null ? min : -1) + (plotOffset.below || 0); 1496 max = (max != null ? max : 1) + (plotOffset.above || 0); 1497 1498 if (min > max) { 1499 var tmp = min; 1500 min = max; 1501 max = tmp; 1502 axis.options.offset = { above: 0, below: 0 }; 1503 } 1504 1505 axis.min = $.plot.saturated.saturate(min); 1506 axis.max = $.plot.saturated.saturate(max); 1507 } 1508 1509 function computeValuePrecision (min, max, direction, ticks, tickDecimals) { 1510 var noTicks = fixupNumberOfTicks(direction, surface, ticks); 1511 1512 var delta = $.plot.saturated.delta(min, max, noTicks),
1513 dec = -Math.floor(Math.log(delta) / Math.LN10); 1514 1515 //if it is called with tickDecimals, then the precision should not be greather then that 1516 if (tickDecimals && dec > tickDecimals) { 1517 dec = tickDecimals; 1518 } 1519 1520 var magn = parseFloat('1e' + (-dec)), 1521 norm = delta / magn; 1522 1523 if (norm > 2.25 && norm < 3 && (dec + 1) <= tickDecimals) { 1524 //we need an extra decimals when tickSize is 2.5 1525 ++dec; 1526 } 1527 1528 return isFinite(dec) ? dec : 0; 1529 }; 1530 1531 function computeTickSize (min, max, noTicks, tickDecimals) { 1532 var delta = $.plot.saturated.delta(min, max, noTicks), 1533 dec = -Math.floor(Math.log(delta) / Math.LN10); 1534 1535 //if it is called with tickDecimals, then the precision should not be greather then that 1536 if (tickDecimals && dec > tickDecimals) { 1537 dec = tickDecimals; 1538 } 1539 1540 var magn = parseFloat('1e' + (-dec)), 1541 norm = delta / magn, // norm is between 1.0 and 10.0 1542 size; 1543 1544 if (norm < 1.5) { 1545 size = 1; 1546 } else if (norm < 3) { 1547 size = 2; 1548 if (norm > 2.25 && (tickDecimals == null || (dec + 1) <= tickDecimals)) { 1549 size = 2.5; 1550 } 1551 } else if (norm < 7.5) { 1552 size = 5; 1553 } else { 1554 size = 10; 1555 } 1556 1557 size *= magn; 1558 return size; 1559 } 1560 1561 function getAxisTickSize(min, max, direction, options, tickDecimals) { 1562 var noTicks; 1563 1564 if (typeof options.ticks === "number" && options.ticks > 0) { 1565 noTicks = options.ticks; 1566 } else { 1567 // heuristic based on the model a*sqrt(x) fitted to 1568 // some data points that seemed reasonable 1569 noTicks = 0.3 * Math.sqrt(direction === "x" ? surface.width : surface.he
1569ight); 1570 } 1571 1572 var size = computeTickSize(min, max, noTicks, tickDecimals); 1573 1574 if (options.minTickSize != null && size < options.minTickSize) { 1575 size = options.minTickSize; 1576 } 1577 1578 return options.tickSize || size; 1579 }; 1580 1581 function fixupNumberOfTicks(direction, surface, ticksOption) { 1582 var noTicks; 1583 1584 if (typeof ticksOption === "number" && ticksOption > 0) { 1585 noTicks = ticksOption; 1586 } else { 1587 noTicks = 0.3 * Math.sqrt(direction === "x" ? surface.width : surface.height); 1588 } 1589 1590 return noTicks; 1591 } 1592 1593 function setupTickFormatter(axis) { 1594 var opts = axis.options; 1595 if (!axis.tickFormatter) { 1596 if (typeof opts.tickFormatter === 'function') { 1597 axis.tickFormatter = function() { 1598 var args = Array.prototype.slice.call(arguments); 1599 return "" + opts.tickFormatter.apply(null, args); 1600 }; 1601 } else { 1602 axis.tickFormatter = defaultTickFormatter; 1603 } 1604 } 1605 } 1606 1607 function setupTickGeneration(axis) { 1608 var opts = axis.options; 1609 var noTicks; 1610 1611 noTicks = fixupNumberOfTicks(axis.direction, surface, opts.ticks); 1612 1613 axis.delta = $.plot.saturated.delta(axis.min, axis.max, noTicks); 1614 var precision = plot.computeValuePrecision(axis.min, axis.max, axis.direction, noTicks, opts.tickDecimals); 1615 1616 axis.tickDecimals = Math.max(0, opts.tickDecimals != null ? opts.tickDecimals : precision); 1617 axis.tickSize = getAxisTickSize(axis.min, axis.max, axis.direction, opts, opts.tickDecimals); 1618 1619 // Flot supports base-10 axes; any other mode else is handled by a plug-in, 1620 // like flot.time.js. 1621 1622 if (!axis.tickGenerator) { 1623 if (typeof opts.tickGenerator === 'function') { 1624 axis.tickGenerator = opts.tickGenerator; 1625 } else { 1626 axis.tickGenerator = defaultTickGenerator; 1627 } 1628 } 1629 1630 if (opts.alignTicksWithAxis != null) { 1631 var otherAxis = (axis.direction === "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; 1632 if (otherAxis && otherAxis.used && otherAxis !== axis) { 1633 // consider snapping min/max to outermost nice ticks 1634 var niceTicks = axis.tickGenerator(axis, plot); 1635 if (niceTicks.length > 0) { 1636 if (opts.min == null) { 1637 axis.min = Math.min(axis.min, niceTicks[0]); 1638 } 1639 1640 if (opts.max == null && niceTicks.length > 1) { 1641 axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); 1642 } 1643 } 1644 1645 axis.tickGenerator = function(axis) { 1646 // copy ticks, scaled to this axis 1647 var ticks = [], 1648 v, i; 1649 for (i = 0; i < otherAxis.ticks.length; ++i) { 1650 v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); 1651 v = axis.min + v * (axis.max - axis.min); 1652 ticks.push(v); 1653 } 1654 return ticks; 1655 }; 1656 1657 // we might need an extra decimal since forced 1658 // ticks don't necessarily fit naturally 1659 if (!axis.mode && opts.tickDecimals == null) { 1660 var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), 1661 ts = axis.tickGenerator(axis, plot); 1662 1663 // only proceed if the tick interval rounded 1664 // with an extra decimal doesn't give us a 1665 // zero at end 1666 if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) { 1667 axis.tickDecimals = extraDec; 1668 } 1669 } 1670 } 1671 } 1672 } 1673 1674 function setMajorTicks(axis) { 1675 var oticks = axis.options.ticks, 1676 ticks = []; 1677 if (oticks == null || (typeof oticks === "number" && oticks > 0)) { 1678 ticks = axis.tickGenerator(axis, plot); 1679 } else if (oticks) { 1680 if ($.isFunction(oticks)) { 1681 // generate the ticks 1682 ticks = oticks(axis); 1683 } else { 1684 ticks = oticks; 1685 } 1686 } 1687 1688 // clean up/labelify the supplied ticks, copy them over 1689 var i, v; 1690 axis.ticks = []; 1691 for (i = 0; i < ticks.length; ++i) { 1692 var label = null; 1693 var t = ticks[i]; 1694 if (typeof t === "object") { 1695 v = +t[0]; 1696 if (t.length > 1) { 1697 label = t[1]; 1698 } 1699 } else { 1700 v = +t; 1701 } 1702 1703 if (!isNaN(v)) { 1704 axis.ticks.push( 1705 newTick(v, label, axis, 'major')); 1706 } 1707 } 1708 } 1709 1710 function newTick(v, label, axis, type) { 1711 if (label === null) { 1712 switch (type) { 1713 case 'min': 1714 case 'max': 1715 //improving the precision of endpoints 1716 var precision = getEndpointPrecision(v, axis); 1717 label = isFinite(precision) ? axis.tickFormatter(v, axis, precision, plot) : axis.tickFormatter(v, axis, precision, plot); 1718 break; 1719 case 'major': 1720 label = axis.tickFormatter(v, axis, undefined, plot); 1721 } 1722 } 1723 return { 1724 v: v, 1725 label: label 1726 }; 1727 } 1728 1729 function snapRangeToTicks(axis, ticks, series) { 1730 var anyDataInSeries = function(series) { 1731 return series.some(e => e.datapoints.points.length > 0); 1732 } 1733 1734 if (axis.options.autoScale === "loose" && ticks.length >
1734 0 && anyDataInSeries(series)) { 1735 // snap to ticks 1736 axis.min = Math.min(axis.min, ticks[0].v); 1737 axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); 1738 } 1739 } 1740 1741 function getEndpointPrecision(value, axis) { 1742 var canvas1 = Math.floor(axis.p2c(value)), 1743 canvas2 = axis.direction === "x" ? canvas1 + 1 : canvas1 - 1, 1744 point1 = axis.c2p(canvas1), 1745 point2 = axis.c2p(canvas2), 1746 precision = computeValuePrecision(point1, point2, axis.direction, 1); 1747 1748 return precision; 1749 } 1750 1751 function setEndpointTicks(axis, series) { 1752 if (isValidEndpointTick(axis, series)) { 1753 axis.ticks.unshift(newTick(axis.min, null, axis, 'min')); 1754 axis.ticks.push(newTick(axis.max, null, axis, 'max')); 1755 } 1756 } 1757 1758 function isValidEndpointTick(axis, series) { 1759 if (axis.options.showTickLabels === 'endpoints') { 1760 return true; 1761 } 1762 if (axis.options.showTickLabels === 'all') { 1763 var associatedSeries = series.filter(function(s) { 1764 return s.xaxis === axis; 1765 }), 1766 notAllBarSeries = associatedSeries.some(function(s) { 1767 return !s.bars.show; 1768 }); 1769 return associatedSeries.length === 0 || notAllBarSeries; 1770 } 1771 if (axis.options.showTickLabels === 'major' || axis.options.showTickLabels === 'none') { 1772 return false; 1773 } 1774 } 1775 1776 function draw() { 1777 surface.clear(); 1778 executeHooks(hooks.drawBackground, [ctx]); 1779 1780 var grid = options.grid; 1781 1782 // draw background, if any 1783 if (grid.show && grid.backgroundColor) { 1784 drawBackground(); 1785 } 1786 1787 if (grid.show && !grid.aboveData) { 1788 drawGrid(); 1789 } 1790 1791 for (var i = 0; i < series.length; ++i) { 1792 executeHooks(hooks.drawSeries, [ctx, series[i], i, getColorOrGradient]); 1793 drawSeries(series[i]); 1794 } 1795 1796 executeHooks(hooks.draw, [ctx]); 1797 1798 if (grid.show && grid.aboveData) { 1799 drawGrid(); 1800 } 1801 1802 surface.render(); 1803 1804 // A draw implies that either the axes or data have changed, so we 1805 // should probably update the overlay highlights as well. 1806 triggerRedrawOverlay(); 1807 } 1808 1809 function extractRange(ranges, coord) { 1810 var axis, from, to, key, axes = allAxes(); 1811 1812 for (var i = 0; i < axes.length; ++i) { 1813 axis = axes[i]; 1814 if (axis.direction === coord) { 1815 key = coord + axis.n + "axis"; 1816 if (!ranges[key] && axis.n === 1) { 1817 // support x1axis as xaxis 1818 key = coord + "axis"; 1819 } 1820 1821 if (ranges[key]) { 1822 from = ranges[key].from; 1823 to = ranges[key].to; 1824 break; 1825 } 1826 } 1827 } 1828 1829 // backwards-compat stuff - to be removed in future 1830 if (!ranges[key]) { 1831 axis = coord === "x" ? xaxes[0] : yaxes[0]; 1832 from = ranges[coord + "1"]; 1833 to = ranges[coord + "2"]; 1834 } 1835 1836 // auto-reverse as an added bonus 1837 if (from != null && to != null && from > to) { 1838 var tmp = from; 1839 from = to; 1840 to = tmp; 1841 } 1842 1843 return { 1844 from: from, 1845 to: to, 1846 axis: axis 1847 }; 1848 } 1849 1850 function drawBackground() { 1851 ctx.save(); 1852 ctx.translate(plotOffset.left, plotOffset.top); 1853 1854 ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); 1855 ctx.fillRect(0, 0, plotWidth, plotHeight);
1856 ctx.restore(); 1857 } 1858 1859 function drawMarkings() { 1860 // draw markings 1861 var markings = options.grid.markings, 1862 axes; 1863 1864 if (markings) { 1865 if ($.isFunction(markings)) { 1866 axes = plot.getAxes(); 1867 // xmin etc. is backwards compatibility, to be 1868 // removed in the future 1869 axes.xmin = axes.xaxis.min; 1870 axes.xmax = axes.xaxis.max; 1871 axes.ymin = axes.yaxis.min; 1872 axes.ymax = axes.yaxis.max; 1873 1874 markings = markings(axes); 1875 } 1876 1877 var i; 1878 for (i = 0; i < markings.length; ++i) { 1879 var m = markings[i], 1880 xrange = extractRange(m, "x"), 1881 yrange = extractRange(m, "y"); 1882 1883 // fill in missing 1884 if (xrange.from == null) { 1885 xrange.from = xrange.axis.min; 1886 } 1887 1888 if (xrange.to == null) { 1889 xrange.to = xrange.axis.max; 1890 } 1891 1892 if (yrange.from == null) { 1893 yrange.from = yrange.axis.min; 1894 } 1895 1896 if (yrange.to == null) { 1897 yrange.to = yrange.axis.max; 1898 } 1899 1900 // clip 1901 if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || 1902 yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) { 1903 continue; 1904 } 1905 1906 xrange.from = Math.max(xrange.from, xrange.axis.min); 1907 xrange.to = Math.min(xrange.to, xrange.axis.max); 1908 yrange.from = Math.max(yrange.from, yrange.axis.min); 1909 yrange.to = Math.min(yrange.to, yrange.axis.max); 1910 1911 var xequal = xrange.from === xrange.to, 1912 yequal = yrange.from === yrange.to; 1913 1914 if (xequal && yequal) { 1915 continue; 1916 } 1917 1918 // then draw 1919 xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); 1920 xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); 1921 yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); 1922 yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); 1923 1924 if (xequal || yequal) { 1925 var lineWidth = m.lineWidth || options.grid.markingsLineWidth, 1926 subPixel = lineWidth % 2 ? 0.5 : 0; 1927 ctx.beginPath(); 1928 ctx.strokeStyle = m.color || options.grid.markingsColor; 1929 ctx.lineWidth = lineWidth; 1930 if (xequal) { 1931 ctx.moveTo(xrange.to + subPixel, yrange.from); 1932 ctx.lineTo(xrange.to + subPixel, yrange.to); 1933 } else { 1934 ctx.moveTo(xrange.from, yrange.to + subPixel); 1935 ctx.lineTo(xrange.to, yrange.to + subPixel); 1936 } 1937 ctx.stroke(); 1938 } else { 1939 ctx.fillStyle = m.color || options.grid.markingsColor; 1940 ctx.fillRect(xrange.from, yrange.to, 1941 xrange.to - xrange.from, 1942 yrange.from - yrange.to); 1943 } 1944 } 1945 } 1946 } 1947 1948 function findEdges(axis) { 1949 var box = axis.box, 1950 x = 0, 1951 y = 0; 1952 1953 // find the edges 1954 if (axis.direction === "x") { 1955 x = 0; 1956 y = box.top - plotOffset.top + (axis.position === "top" ? box.height : 0); 1957 } else { 1958 y = 0; 1959 x = box.left - plotOffset.left + (axis.position === "left" ? box.width : 0) + axis.boxPosition.centerX; 1960 } 1961 1962 return { 1963 x: x, 1964 y: y 1965 }; 1966 }; 1967 1968 function alignPosition(lineWidth, pos) { 1969 return ((lineWidth % 2) !== 0) ? Math.floor(pos) + 0.5 : pos; 1970 }; 1971 1972 function drawTickBar(axis) { 1973 ctx.lineWidth = 1; 1974 var edges = findEdges(axis), 1975 x = edges.x, 1976 y = edges.y; 1977 1978 // draw tick bar 1979 if (axis.show) { 1980 var xoff = 0, 1981 yoff = 0; 1982 1983 ctx.strokeStyle = axis.options.color; 1984 ctx.beginPath(); 1985 if (axis.direction === "x") { 1986 xoff = plotWidth + 1; 1987 } else { 1988 yoff = plotHeight + 1; 1989 } 1990 1991 if (axis.direction === "x") { 1992 y = alignPosition(ctx.lineWidth, y); 1993 } else { 1994 x = alignPosition(ctx.lineWidth, x); 1995 } 1996 1997 ctx.moveTo(x, y); 1998 ctx.lineTo(x + xoff, y + yoff); 1999 ctx.stroke(); 2000 } 2001 }; 2002 2003 function drawTickMarks(axis) { 2004 var t = axis.tickLength, 2005 minorTicks = axis.showMinorTicks, 2006 minorTicksNr = MINOR_TICKS_COUNT_CONSTANT, 2007 edges = findEdges(axis), 2008 x = edges.x, 2009 y = edges.y, 2010 i = 0; 2011 2012 // draw major tick marks 2013 ctx.strokeStyle = axis.options.color; 2014 ctx.beginPath(); 2015 2016 for (i = 0; i < axis.ticks.length; ++i) { 2017 var v = axis.ticks[i].v, 2018 xoff = 0, 2019 yoff = 0, 2020 xminor = 0, 2021 yminor = 0, 2022 j; 2023 2024 if (!isNaN(v) && v >= axis.min && v <= axis.max) { 2025 if (axis.direction === "x") { 2026 x = axis.p2c(v); 2027 yoff = t; 2028 2029 if (axis.position === "top") { 2030 yoff = -yoff; 2031 } 2032 } else { 2033 y = axis.p2c(v); 2034 xoff = t; 2035 2036 if (axis.position === "left") { 2037 xoff = -xoff; 2038 } 2039 } 2040 2041 if (axis.direction === "x") { 2042 x = alignPosition(ctx.lineWidth, x); 2043 } else { 2044 y = alignPosition(ctx.lineWidth, y); 2045 } 2046 2047 ctx.moveTo(x, y); 2048 ctx.lineTo(x + xoff, y + yoff); 2049 } 2050 2051 //draw minor tick marks 2052 if (minorTicks === true && i < axis.ticks.length - 1) { 2053 var v1 = axis.ticks[i].v, 2054 v2 = axis.ticks[i + 1].v, 2055 step = (v2 - v1) / (minorTicksNr + 1); 2056 2057 for (j = 1; j <= minorTicksNr; j++) { 2058 // compute minor tick position 2059 if (axis.direction === "x") { 2060 yminor = t / 2; // minor ticks are half length 2061 x = alignPosition(ctx.lineWidth, axis.p2c(v1 + j * step)) 2062 2063 if (axis.position === "top") { 2064 yminor = -yminor; 2065 } 2066 2067 // don't go over the plot borders 2068 if ((x < 0) || (x > plotWidth)) { 2069 continue; 2070 } 2071 } else { 2072 xminor = t / 2; // minor ticks are half length 2073 y = alignPosition(ctx.lineWidth, axis.p2c(v1 + j * step)); 2074 2075 if (axis.position === "left") { 2076 xminor = -xminor; 2077 } 2078 2079 // don't go over the plot borders 2080 if ((y < 0) || (y > plotHeight)) { 2081 continue; 2082 } 2083 } 2084 2085 ctx.moveTo(x, y); 2086 ctx.lineTo(x + xminor, y + yminor); 2087 } 2088 } 2089 } 2090 2091 ctx.stroke(); 2092 }; 2093 2094 function drawGridLines(axis) { 2095 // check if the line will be overlapped with a border 2096 var overlappedWithBorder = function (value) { 2097 var bw = options.grid.borderWidth; 2098 return (((typeof bw === "object" && bw[axis.position] > 0) || bw > 0) && (value === axis.min || value === axis.max)); 2099 }; 2100 2101 ctx.strokeStyle = options.grid.tickColor; 2102 ctx.beginPath(); 2103 var i; 2104 for (i = 0; i < axis.ticks.length; ++i) { 2105 var v = axis.ticks[i].v, 2106 xoff = 0, 2107 yoff = 0, 2108 x = 0, 2109 y = 0; 2110 2111 if (isNaN(v) || v < axis.min || v > axis.max) continue; 2112 2113 // skip those lying on the axes if we got a border 2114 if (overlappedWithBorder(v)) continue; 2115 2116 if (axis.direction === "x") { 2117 x = axis.p2c(v); 2118 y = plotHeight; 2119 yoff = -plotHeight; 2120 } else { 2121 x = 0; 2122 y = axis.p2c(v); 2123 xoff = plotWidth; 2124 } 2125 2126 if (axis.direction === "x") { 2127 x = alignPosition(ctx.lineWidth, x); 2128 } else { 2129 y = alignPosition(ctx.lineWidth, y); 2130 } 2131 2132 ctx.moveTo(x, y); 2133 ctx.lineTo(x + xoff, y + yoff); 2134 } 2135 2136 ctx.stroke(); 2137 }; 2138 2139 function drawBorder() { 2140 // If either borderWidth or borderColor is an object, then draw the border 2141 // line by line instead of as one rectangle 2142 var bw = options.grid.borderWidth, 2143 bc = options.grid.borderColor; 2144 2145 if (typeof bw === "object" || typeof bc === "object") { 2146 if (typeof bw !== "object") { 2147 bw = { 2148 top: bw,
2149 right: bw, 2150 bottom: bw, 2151 left: bw 2152 }; 2153 } 2154 if (typeof bc !== "object") { 2155 bc = { 2156 top: bc, 2157 right: bc, 2158 bottom: bc, 2159 left: bc 2160 }; 2161 } 2162 2163 if (bw.top > 0) { 2164 ctx.strokeStyle = bc.top; 2165 ctx.lineWidth = bw.top; 2166 ctx.beginPath(); 2167 ctx.moveTo(0 - bw.left, 0 - bw.top / 2); 2168 ctx.lineTo(plotWidth, 0 - bw.top / 2); 2169 ctx.stroke(); 2170 } 2171 2172 if (bw.right > 0) { 2173 ctx.strokeStyle = bc.right; 2174 ctx.lineWidth = bw.right; 2175 ctx.beginPath(); 2176 ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); 2177 ctx.lineTo(plotWidth + bw.right / 2, plotHeight); 2178 ctx.stroke(); 2179 } 2180 2181 if (bw.bottom > 0) { 2182 ctx.strokeStyle = bc.bottom; 2183 ctx.lineWidth = bw.bottom; 2184 ctx.beginPath(); 2185 ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); 2186 ctx.lineTo(0, plotHeight + bw.bottom / 2); 2187 ctx.stroke(); 2188 } 2189 2190 if (bw.left > 0) { 2191 ctx.strokeStyle = bc.left; 2192 ctx.lineWidth = bw.left; 2193 ctx.beginPath(); 2194 ctx.moveTo(0 - bw.left / 2, plotHeight + bw.bottom); 2195 ctx.lineTo(0 - bw.left / 2, 0); 2196 ctx.stroke(); 2197 } 2198 } else { 2199 ctx.lineWidth = bw; 2200 ctx.strokeStyle = options.grid.borderColor; 2201 ctx.strokeRect(-bw / 2, -bw / 2, plotWidth + bw, plotHeight + bw); 2202 } 2203 }; 2204 2205 function drawGrid() { 2206 var axes, bw; 2207 2208 ctx.save(); 2209 ctx.translate(plotOffset.left, plotOffset.top); 2210 2211 drawMarkings(); 2212 2213 axes = allAxes(); 2214 bw = options.grid.borderWidth; 2215 2216 for (var j = 0; j < axes.length; ++j) { 2217 var axis = axes[j]; 2218 2219 if (!axis.show) { 2220 continue; 2221 } 2222 2223 drawTickBar(axis); 2224 if (axis.showTicks === true) { 2225 drawTickMarks(axis); 2226 } 2227 2228 if (axis.gridLines === true) { 2229 drawGridLines(axis, bw); 2230 } 2231 } 2232 2233 // draw border 2234 if (bw) { 2235 drawBorder(); 2236 } 2237 2238 ctx.restore(); 2239 } 2240 2241 function drawAxisLabels() { 2242 $.each(allAxes(), function(_, axis) { 2243 var box = axis.box, 2244 legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", 2245 layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, 2246 font = axis.options.font || "flot-tick-label tickLabel", 2247 i, x, y, halign, valign, info, 2248 margin = 3, 2249 nullBox = {x: NaN, y: NaN, width: NaN, height: NaN}, newLabelBox, labelBoxes = [], 2250 overlapping = function(x11, y11, x12, y12, x21, y21, x22, y22) { 2251 return ((x11 <= x21 && x21 <= x12) || (x21 <= x11 && x11 <= x22)) && 2252 ((y11 <= y21 && y21 <= y12) || (y21 <= y11 && y11 <= y22)); 2253 }, 2254 overlapsOtherLabels = function(newLabelBox, previousLabelBoxes) { 2255 return previousLabelBoxes.some(function(labelBox) { 2256 return overlapping( 2257 newLabelBox.x, newLabelBox.y, newLabelBox.x + newLabelBox.width, newLabelBox.y + newLabelBox.height, 2258 labelBox.x, labelBox.y, labelBox.x + labelBox.width, labelBox.y + labelBox.height); 2259 }); 2260 }, 2261 drawAxisLabel = function (tick, labelBoxes) { 2262 if (!tick || !tick.label || tick.v < axis.min || tick.v > axis.max) { 2263 return nullBox; 2264 } 2265 2266 info = surface.getTextInfo(layer, tick.label, font); 2267 2268 if (axis.direction === "x") { 2269 halign = "center"; 2270 x = plotOffset.left + axis.p2c(tick.v); 2271 if (axis.position === "bottom") { 2272 y = box.top + box.padding - axis.boxPosition.centerY; 2273 } else { 2274 y = box.top + box.height - box.padding + axis.boxPosition.centerY; 2275 valign = "bottom"; 2276 } 2277 newLabelBox = {x: x - info.width / 2 - margin, y: y - margin, width: info.width + 2 * margin, height: info.height + 2 * margin}; 2278 } else { 2279 valign = "middle"; 2280 y = plotOffset.top + axis.p2c(tick.v); 2281 if (axis.position === "left") { 2282 x = box.left + box.width - box.padding - axis.boxPosition.centerX; 2283 halign = "right"; 2284 } else { 2285 x = box.left + box.padding + axis.boxPosition.centerX; 2286 } 2287 newLabelBox = {x: x - info.width / 2 - margin, y: y - margin, width: info.width + 2 * margin, height: info.height + 2 * margin}; 2288 } 2289 2290 if (overlapsOtherLabels(newLabelBox, labelBoxes)) { 2291 return nullBox; 2292 } 2293 2294 surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); 2295 2296 return newLabelBox; 2297 }; 2298 2299 // Remove text before checking for axis.show and ticks.length; 2300 // otherwise plugins, like flot-tickrotor, that draw their own 2301 // tick labels will end up with both theirs and the defaults. 2302 2303 surface.removeText(layer); 2304 2305 executeHooks(hooks.drawAxis, [axis, surface]); 2306 2307 if (!axis.show) { 2308 return; 2309 } 2310 2311 switch (axis.options.showTickLabels) { 2312 case 'none': 2313 break; 2314 case 'endpoints': 2315 labelBoxes.push(drawAxisLabel(axis.ticks[0], labelBoxes)); 2316 labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes)); 2317 break; 2318 case 'major': 2319 labelBoxes.push(drawAxisLabel(axis.ticks[0], labelBoxes)); 2320 labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes)); 2321 for (i = 1; i < axis.ticks.length - 1; ++i) { 2322 labelBoxes.push(drawAxisLabel(axis.ticks[i], labelBoxes)); 2323 } 2324 break; 2325 case 'all': 2326 labelBoxes.push(drawAxisLabel(axis.ticks[0], [])); 2327 labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes)); 2328 for (i = 1; i < axis.ticks.length - 1; ++i) { 2329 labelBoxes.push(drawAxisLabel(axis.ticks[i], labelBoxes)); 2330 } 2331 break; 2332 } 2333 }); 2334 } 2335
2336 function drawSeries(series) { 2337 if (series.lines.show) { 2338 $.plot.drawSeries.drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient); 2339 } 2340 2341 if (series.bars.show) { 2342 $.plot.drawSeries.drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient); 2343 } 2344 2345 if (series.points.show) { 2346 $.plot.drawSeries.drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient); 2347 } 2348 } 2349 2350 function computeRangeForDataSeries(series, force, isValid) { 2351 var points = series.datapoints.points, 2352 ps = series.datapoints.pointsize, 2353 format = series.datapoints.format, 2354 topSentry = Number.POSITIVE_INFINITY, 2355 bottomSentry = Number.NEGATIVE_INFINITY, 2356 range = { 2357 xmin: topSentry, 2358 ymin: topSentry, 2359 xmax: bottomSentry, 2360 ymax: bottomSentry 2361 }; 2362 2363 for (var j = 0; j < points.length; j += ps) { 2364 if (points[j] === null) { 2365 continue; 2366 } 2367 2368 if (typeof (isValid) === 'function' && !isValid(points[j])) { 2369 continue; 2370 } 2371 2372 for (var m = 0; m < ps; ++m) { 2373 var val = points[j + m], 2374 f = format[m]; 2375 if (f === null || f === undefined) { 2376 continue; 2377 } 2378 2379 if (typeof (isValid) === 'function' && !isValid(val)) { 2380 continue; 2381 } 2382 2383 if ((!force && !f.computeRange) || val === Infinity || val === -Infinity) { 2384 continue; 2385 } 2386 2387 if (f.x === true) { 2388 if (val < range.xmin) { 2389 range.xmin = val; 2390 } 2391 2392 if (val > range.xmax) { 2393 range.xmax = val; 2394 } 2395 } 2396 2397 if (f.y === true) { 2398 if (val < range.ymin) { 2399 range.ymin = val; 2400 } 2401 2402 if (val > range.ymax) { 2403 range.ymax = val; 2404 } 2405 } 2406 } 2407 } 2408 2409 return range; 2410 }; 2411 2412 function adjustSeriesDataRange(series, range) { 2413 if (series.bars.show) { 2414 // make sure we got room for the bar on the dancing floor 2415 var delta; 2416 2417 // update bar width if needed 2418 var useAbsoluteBarWidth = series.bars.barWidth[1]; 2419 if (series.datapoints && series.datapoints.points && !useAbsoluteBarWidth) { 2420 computeBarWidth(series); 2421 } 2422 2423 var barWidth = series.bars.barWidth[0] || series.bars.barWidth; 2424 switch (series.bars.align) { 2425 case "left": 2426 delta = 0; 2427 break; 2428 case "right": 2429 delta = -barWidth; 2430 break; 2431 default: 2432 delta = -barWidth / 2; 2433 } 2434 2435 if (series.bars.horizontal) { 2436 range.ymin += delta; 2437 range.ymax += delta + barWidth; 2438 } 2439 else { 2440 range.xmin += delta; 2441 range.xmax += delta + barWidth; 2442 } 2443 } 2444 2445 if ((series.bars.show && series.bars.zero) || (series.lines.show && series.lines.zero)) { 2446 var ps = series.datapoints.pointsize; 2447 2448 // make sure the 0 point is included in the computed y range when requested 2449 if (ps <= 2) { 2450 /*if ps > 0 the points were already taken into account for autoScale */ 2451 range.ymin = Math.min(0, range.ymin); 2452 range.ymax = Math.max(0, range.ymax); 2453 } 2454 } 2455 2456 return range; 2457 }; 2458 2459 function computeBarWidth(series) { 2460 var xValues = []; 2461 var pointsize = series.datapoints.pointsize, minDistance = Number.MAX_VALUE; 2462 2463 if (series.datapoints.points.length <= pointsize) { 2464 minDistance = 1; 2465 } 2466 2467 var start = series.bars.horizontal ? 1 : 0; 2468 for (var j = start; j < series.datapoints.points.length; j += pointsize) { 2469 if (isFinite(series.datapoints.points[j]) && series.datapoints.points[j] !== null) { 2470 xValues.push(series.datapoints.points[j]); 2471 } 2472 } 2473 2474 function onlyUnique(value, index, self) { 2475 return self.indexOf(value) === index; 2476 } 2477 2478 xValues = xValues.filter( onlyUnique ); 2479 xValues.sort(function(a, b){return a - b}); 2480 2481 for (var j = 1; j < xValues.length; j++) { 2482 var distance = Math.abs(xValues[j] - xValues[j - 1]); 2483 if (distance < minDistance && isFinite(distance)) { 2484 minDistance = distance; 2485 } 2486 } 2487 2488 if (typeof series.bars.barWidth === "number") { 2489 series.bars.barWidth = series.bars.barWidth * minDistance; 2490 } else { 2491 series.bars.barWidth[0] = series.bars.barWidth[0] * minDistance; 2492 } 2493 } 2494 2495 // returns the data item the mouse is over/ the cursor is closest to, or null if none is found 2496 function findNearbyItem(mouseX, mouseY, seriesFilter, radius, computeDistance) { 2497 var i, j, 2498 item = null, 2499 smallestDistance = radius * radius + 1; 2500 2501 for (var i = series.length - 1; i >= 0; --i) { 2502 if (!seriesFilter(i)) continue; 2503 2504 var s = series[i]; 2505 if (!s.datapoints) return; 2506 2507 if (s.lines.show || s.points.show) { 2508 var found = findNearbyPoint(s, mouseX, mouseY, radius, smallestDistance, computeDistance); 2509 if (found) { 2510 smallestDistance = found.distance; 2511 item = [i, found.dataIndex]; 2512 } 2513 } 2514 2515 if (s.bars.show && !item) { // no other point can be nearby 2516 var foundIndex = findNearbyBar(s, mouseX, mouseY); 2517 if (foundIndex >= 0) item = [i, foundIndex]; 2518 } 2519 } 2520 2521 if (item) { 2522 i = item[0]; 2523 j = item[1]; 2524 var ps = series[i].datapoints.pointsize; 2525 2526 return { 2527 datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), 2528 dataIndex: j, 2529 series: series[i], 2530 seriesIndex: i 2531 }; 2532 } 2533 2534 return null; 2535 } 2536 2537 function findNearbyPoint (series, mouseX, mouseY, maxDistance, smallestDistance, computeDistance) { 2538 var mx = series.xaxis.c2p(mouseX),
2539 my = series.yaxis.c2p(mouseY), 2540 maxx = maxDistance / series.xaxis.scale, 2541 maxy = maxDistance / series.yaxis.scale, 2542 points = series.datapoints.points, 2543 ps = series.datapoints.pointsize; 2544 2545 // with inverse transforms, we can't use the maxx/maxy 2546 // optimization, sadly 2547 if (series.xaxis.options.inverseTransform) { 2548 maxx = Number.MAX_VALUE; 2549 } 2550 2551 if (series.yaxis.options.inverseTransform) { 2552 maxy = Number.MAX_VALUE; 2553 } 2554 2555 var found = null; 2556 for (var j = 0; j < points.length; j += ps) { 2557 var x = points[j]; 2558 var y = points[j + 1]; 2559 if (x == null) { 2560 continue; 2561 } 2562 2563 if (x - mx > maxx || x - mx < -maxx || 2564 y - my > maxy || y - my < -maxy) { 2565 continue; 2566 } 2567 2568 // We have to calculate distances in pixels, not in 2569 // data units, because the scales of the axes may be different 2570 var dx = Math.abs(series.xaxis.p2c(x) - mouseX); 2571 var dy = Math.abs(series.yaxis.p2c(y) - mouseY); 2572 var dist = computeDistance ? computeDistance(dx, dy) : dx * dx + dy * dy; 2573 2574 // use <= to ensure last point takes precedence 2575 // (last generally means on top of) 2576 if (dist < smallestDistance) { 2577 smallestDistance = dist; 2578 found = { dataIndex: j / ps, distance: dist }; 2579 } 2580 } 2581 2582 return found; 2583 } 2584 2585 function findNearbyBar (series, mouseX, mouseY) { 2586 var barLeft, barRight, 2587 barWidth = series.bars.barWidth[0] || series.bars.barWidth, 2588 mx = series.xaxis.c2p(mouseX), 2589 my = series.yaxis.c2p(mouseY), 2590 points = series.datapoints.points, 2591 ps = series.datapoints.pointsize; 2592 2593 switch (series.bars.align) { 2594 case "left": 2595 barLeft = 0; 2596 break; 2597 case "right": 2598 barLeft = -barWidth; 2599 break; 2600 default: 2601 barLeft = -barWidth / 2; 2602 } 2603 2604 barRight = barLeft + barWidth; 2605 2606 var fillTowards = series.bars.fillTowards || 0; 2607 var bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min; 2608 2609 var foundIndex = -1; 2610 for (var j = 0; j < points.length; j += ps) { 2611 var x = points[j], y = points[j + 1]; 2612 if (x == null) 2613 continue; 2614 2615 // for a bar graph, the cursor must be inside the bar 2616 if (series.bars.horizontal ? 2617 (mx <= Math.max(bottom, x) && mx >= Math.min(bottom, x) && 2618 my >= y + barLeft && my <= y + barRight) : 2619 (mx >= x + barLeft && mx <= x + barRight && 2620 my >= Math.min(bottom, y) && my <= Math.max(bottom, y))) 2621 foundIndex = j / ps; 2622 } 2623 2624 return foundIndex; 2625 } 2626 2627 function findNearbyInterpolationPoint(posX, posY, seriesFilter) { 2628 var i, j, dist, dx, dy, ps, 2629 item, 2630 smallestDistance = Number.MAX_VALUE; 2631 2632 for (i = 0; i < series.length; ++i) { 2633 if (!seriesFilter(i)) { 2634 continue; 2635 } 2636 var points = series[i].datapoints.points; 2637 ps = series[i].datapoints.pointsize; 2638 2639 // if the data is coming from positive -> negative, reverse the comparison 2640 const comparer = points[points.length - ps] < points[0] 2641 ? function (x1, x2) { return x1 > x2 } 2642 : function (x1, x2) { return x2 > x1 }; 2643 2644 // do not interpolate outside the bounds of the data. 2645 if (comparer(posX, points[0])) { 2646 continue; 2647 } 2648 2649 // Find the nearest points, x-wise 2650 for (j = ps; j < points.length; j += ps) { 2651 if (comparer(posX, points[j])) { 2652 break; 2653 } 2654 } 2655 2656 // Now Interpolate 2657 var y, 2658 p1x = points[j - ps], 2659 p1y = points[j - ps + 1], 2660 p2x = points[j], 2661 p2y = points[j + 1]; 2662 2663 if ((p1x === undefined) || (p2x === undefined) || 2664 (p1y === undefined) || (p2y === undefined)) { 2665 continue; 2666 } 2667 2668 if (p1x === p2x) { 2669 y = p2y 2670 } else { 2671 y = p1y + (p2y - p1y) * (posX - p1x) / (p2x - p1x); 2672 } 2673 2674 posY = y; 2675 2676 dx = Math.abs(series[i].xaxis.p2c(p2x) - posX); 2677 dy = Math.abs(series[i].yaxis.p2c(p2y) - posY); 2678 dist = dx * dx + dy * dy; 2679 2680 if (dist < smallestDistance) { 2681 smallestDistance = dist; 2682 item = [posX, posY, i, j]; 2683 } 2684 } 2685 2686 if (item) { 2687 i = item[2]; 2688 j = item[3]; 2689 ps = series[i].datapoints.pointsize; 2690 points = series[i].datapoints.points; 2691 p1x = points[j - ps]; 2692 p1y = points[j - ps + 1]; 2693 p2x = points[j]; 2694 p2y = points[j + 1]; 2695 2696 return { 2697 datapoint: [item[0], item[1]], 2698 leftPoint: [p1x, p1y], 2699 rightPoint: [p2x, p2y], 2700 seriesIndex: i 2701 }; 2702 } 2703 2704 return null; 2705 } 2706 2707 function triggerRedrawOverlay() { 2708 var t = options.interaction.redrawOverlayInterval; 2709 if (t === -1) { // skip event queue 2710 drawOverlay(); 2711 return; 2712 } 2713 2714 if (!redrawTimeout) { 2715 redrawTimeout = setTimeout(function() { 2716 drawOverlay(plot); 2717 }, t); 2718 } 2719 } 2720 2721 function drawOverlay(plot) { 2722 redrawTimeout = null; 2723 2724 if (!octx) { 2725 return; 2726 } 2727 overlay.clear(); 2728 executeHooks(hooks.drawOverlay, [octx, overlay]); 2729 var event = new CustomEvent('onDrawingDone'); 2730 plot.getEventHolder().dispatchEvent(event); 2731 plot.getPlaceholder().trigger('drawingdone'); 2732 } 2733 2734 function getColorOrGradient(spec, bottom, top, defaultColor) { 2735 if (typeof spec === "string") { 2736 return spec; 2737 } else { 2738 // assume this is a gradient spec; IE currently only 2739 // supports a simple vertical gradient properly, so that's 2740 // what we support too 2741 var gradient = ctx.createLinearGradient(0, top, 0, bottom); 2742 2743 for (var i = 0, l = spec.colors.length; i < l; ++i) { 2744 var c = spec.colors[i]; 2745 if (typeof c !== "string") { 2746 var co = $.color.parse(defaultColor); 2747 if (c.brightness != null) { 2748 co = co.scale('rgb', c.brightness); 2749 } 2750 2751 if (c.opacity != null) { 2752 co.a *= c.opacity; 2753 } 2754 2755 c = co.toString(); 2756 } 2757 gradient.addColorStop(i / (l - 1), c); 2758 } 2759 2760 return gradient; 2761 } 2762 } 2763 } 2764 2765 // Add the plot function to the top level of the jQuery object 2766 2767 $.plot = function(placeholder, data, options) { 2768 var plot = new Plot($(placeholder), data, options, $.plot.plugins); 2769 return plot; 2770 }; 2771 2772 $.plot.version = "3.0.0"; 2773 2774 $.plot.plugins = []; 2775 2776 // Also add the plot function as a chainable property 2777 $.fn.plot = function(data, options) { 2778 return this.each(function() { 2779 $.plot(this, data, options); 2780 }); 2781 }; 2782 2783 $.plot.linearTickGenerator = defaultTickGenerator; 2784 $.plot.defaultTickFormatter = defaultTickFormatter; 2785 $.plot.expRepTickFormatter = expRepTickFormatter; 2786})(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.