src/pyams_skin/resources/js/ext/flot/jquery.flot.js
changeset 566 a1707c607eec
parent 565 318533413200
child 567 bca1726b1d85
equal deleted inserted replaced
565:318533413200 566:a1707c607eec
     1 /* Javascript plotting library for jQuery, version 3.0.0.
       
     2 
       
     3 Copyright (c) 2007-2014 IOLA and Ole Laursen.
       
     4 Licensed 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 canvases
       
   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.height);
       
  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 > 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);