@kevinburke/flot 5.0.0

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