diff --git a/build/npm/Plugins.js b/build/npm/Plugins.js
index be017d54b..0390a2fb2 100644
--- a/build/npm/Plugins.js
+++ b/build/npm/Plugins.js
@@ -426,6 +426,11 @@ const Plugins = [
{
from: 'node_modules/dropzone/dist/',
to: 'plugins/dropzone'
+ },
+ // uPlot
+ {
+ from: 'node_modules/uplot/dist/',
+ to: 'plugins/uplot'
}
]
diff --git a/index.html b/index.html
index 99c517693..7271d3597 100644
--- a/index.html
+++ b/index.html
@@ -319,6 +319,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/index2.html b/index2.html
index 494067e2f..bbecab82a 100644
--- a/index2.html
+++ b/index2.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/index3.html b/index3.html
index 07f858b87..d4509f6e0 100644
--- a/index3.html
+++ b/index3.html
@@ -315,6 +315,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/package-lock.json b/package-lock.json
index f2a1a9f41..c54cb7ea9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11347,6 +11347,11 @@
}
}
},
+ "uplot": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/uplot/-/uplot-1.4.4.tgz",
+ "integrity": "sha512-vgV84+by3fGTU4bdpffSvA9FX8ide6MsmlBzOASPDdZCquXmCA+T2qodeNdnBen+7YOeqD9H91epVnF0dQgVKw=="
+ },
"upper-case": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
diff --git a/package.json b/package.json
index 814ae9c66..402ecf6e5 100644
--- a/package.json
+++ b/package.json
@@ -125,7 +125,8 @@
"summernote": "^0.8.18",
"sweetalert2": "^10.3.5",
"tempusdominus-bootstrap-4": "^5.1.2",
- "toastr": "^2.1.4"
+ "toastr": "^2.1.4",
+ "uplot": "^1.4.4"
},
"devDependencies": {
"@babel/core": "^7.11.6",
diff --git a/pages/UI/buttons.html b/pages/UI/buttons.html
index 8304808db..a14ce9ccb 100644
--- a/pages/UI/buttons.html
+++ b/pages/UI/buttons.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
+
+
+
+ uPlot
+
+
diff --git a/pages/charts/chartjs.html b/pages/charts/chartjs.html
index 2d0071b3f..243c2fb6b 100644
--- a/pages/charts/chartjs.html
+++ b/pages/charts/chartjs.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/charts/flot.html b/pages/charts/flot.html
index 39146143f..4a86f35c6 100644
--- a/pages/charts/flot.html
+++ b/pages/charts/flot.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/charts/inline.html b/pages/charts/inline.html
index 85702b854..1c9cf964c 100644
--- a/pages/charts/inline.html
+++ b/pages/charts/inline.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/charts/uplot.html b/pages/charts/uplot.html
new file mode 100644
index 000000000..f142da648
--- /dev/null
+++ b/pages/charts/uplot.html
@@ -0,0 +1,1004 @@
+
+
+
+
+
+ AdminLTE 3 | uPlot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Version 3.1.0-pre
+
+ Copyright © 2014-2020 AdminLTE.io . All rights reserved.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/examples/404.html b/pages/examples/404.html
index acb543bd8..4a6aed02c 100644
--- a/pages/examples/404.html
+++ b/pages/examples/404.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/500.html b/pages/examples/500.html
index afe827ba0..5e2284328 100644
--- a/pages/examples/500.html
+++ b/pages/examples/500.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/blank.html b/pages/examples/blank.html
index b4e866f96..50dd71b2e 100644
--- a/pages/examples/blank.html
+++ b/pages/examples/blank.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/contact-us.html b/pages/examples/contact-us.html
index ae2fe96ef..28711e0f7 100644
--- a/pages/examples/contact-us.html
+++ b/pages/examples/contact-us.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/contacts.html b/pages/examples/contacts.html
index dc0422b8e..d27db283d 100644
--- a/pages/examples/contacts.html
+++ b/pages/examples/contacts.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/e-commerce.html b/pages/examples/e-commerce.html
index aaab4bcbc..3ada8b220 100644
--- a/pages/examples/e-commerce.html
+++ b/pages/examples/e-commerce.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/faq.html b/pages/examples/faq.html
index d930c49d4..03728e485 100644
--- a/pages/examples/faq.html
+++ b/pages/examples/faq.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/invoice.html b/pages/examples/invoice.html
index 070c7d828..868f81bd3 100644
--- a/pages/examples/invoice.html
+++ b/pages/examples/invoice.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/language-menu.html b/pages/examples/language-menu.html
index ec487d879..49b9bf85c 100644
--- a/pages/examples/language-menu.html
+++ b/pages/examples/language-menu.html
@@ -317,6 +317,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/legacy-user-menu.html b/pages/examples/legacy-user-menu.html
index 472b26f63..1259df197 100644
--- a/pages/examples/legacy-user-menu.html
+++ b/pages/examples/legacy-user-menu.html
@@ -332,6 +332,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/pace.html b/pages/examples/pace.html
index 3e0581547..8bf57d625 100644
--- a/pages/examples/pace.html
+++ b/pages/examples/pace.html
@@ -307,6 +307,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/profile.html b/pages/examples/profile.html
index 2600f8d72..c26d0466b 100644
--- a/pages/examples/profile.html
+++ b/pages/examples/profile.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/project-add.html b/pages/examples/project-add.html
index c8d46f0ee..93d8eb4e6 100644
--- a/pages/examples/project-add.html
+++ b/pages/examples/project-add.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/project-detail.html b/pages/examples/project-detail.html
index f340f9462..180a9117a 100644
--- a/pages/examples/project-detail.html
+++ b/pages/examples/project-detail.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/project-edit.html b/pages/examples/project-edit.html
index 964b60fd6..dc1f7a8f7 100644
--- a/pages/examples/project-edit.html
+++ b/pages/examples/project-edit.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/examples/projects.html b/pages/examples/projects.html
index 3c8fc7656..3f0facca3 100644
--- a/pages/examples/projects.html
+++ b/pages/examples/projects.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/forms/advanced.html b/pages/forms/advanced.html
index 61049a255..c1c5fb81c 100644
--- a/pages/forms/advanced.html
+++ b/pages/forms/advanced.html
@@ -321,6 +321,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/forms/editors.html b/pages/forms/editors.html
index 76aa9a512..86d02cae7 100644
--- a/pages/forms/editors.html
+++ b/pages/forms/editors.html
@@ -311,6 +311,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/forms/general.html b/pages/forms/general.html
index e54c31576..bcf8e400b 100644
--- a/pages/forms/general.html
+++ b/pages/forms/general.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/forms/validation.html b/pages/forms/validation.html
index c710374a5..db8fac7ee 100644
--- a/pages/forms/validation.html
+++ b/pages/forms/validation.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/gallery.html b/pages/gallery.html
index ad9e67e74..155a683db 100644
--- a/pages/gallery.html
+++ b/pages/gallery.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/kanban.html b/pages/kanban.html
index 31b8e6b3c..9ee6ae5b7 100644
--- a/pages/kanban.html
+++ b/pages/kanban.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/boxed.html b/pages/layout/boxed.html
index f5e77087f..c7bed5996 100644
--- a/pages/layout/boxed.html
+++ b/pages/layout/boxed.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/collapsed-sidebar.html b/pages/layout/collapsed-sidebar.html
index d38f45ba2..f363e2142 100644
--- a/pages/layout/collapsed-sidebar.html
+++ b/pages/layout/collapsed-sidebar.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/fixed-footer.html b/pages/layout/fixed-footer.html
index da6298716..47f82d6a3 100644
--- a/pages/layout/fixed-footer.html
+++ b/pages/layout/fixed-footer.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/fixed-sidebar-custom.html b/pages/layout/fixed-sidebar-custom.html
index 8a9cc9bb9..54b916569 100644
--- a/pages/layout/fixed-sidebar-custom.html
+++ b/pages/layout/fixed-sidebar-custom.html
@@ -307,6 +307,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/fixed-sidebar.html b/pages/layout/fixed-sidebar.html
index 4dd15c76a..df3d64708 100644
--- a/pages/layout/fixed-sidebar.html
+++ b/pages/layout/fixed-sidebar.html
@@ -307,6 +307,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/fixed-topnav.html b/pages/layout/fixed-topnav.html
index de0673d9d..ef12e7773 100644
--- a/pages/layout/fixed-topnav.html
+++ b/pages/layout/fixed-topnav.html
@@ -305,6 +305,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/layout/top-nav-sidebar.html b/pages/layout/top-nav-sidebar.html
index 7a27ddb94..e970e7e72 100644
--- a/pages/layout/top-nav-sidebar.html
+++ b/pages/layout/top-nav-sidebar.html
@@ -350,6 +350,12 @@ scratch. This page gets rid of all links and provides the needed markup only.
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/mailbox/compose.html b/pages/mailbox/compose.html
index 8e8300672..f0e9472a8 100644
--- a/pages/mailbox/compose.html
+++ b/pages/mailbox/compose.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/mailbox/mailbox.html b/pages/mailbox/mailbox.html
index c49eca8d2..828019ce8 100644
--- a/pages/mailbox/mailbox.html
+++ b/pages/mailbox/mailbox.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/mailbox/read-mail.html b/pages/mailbox/read-mail.html
index 7ccbcc62f..c1768195f 100644
--- a/pages/mailbox/read-mail.html
+++ b/pages/mailbox/read-mail.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/search/enhanced-results.html b/pages/search/enhanced-results.html
index 9f4bf6aff..03ac35deb 100644
--- a/pages/search/enhanced-results.html
+++ b/pages/search/enhanced-results.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/search/enhanced.html b/pages/search/enhanced.html
index 9741cbc32..0502a4bb6 100644
--- a/pages/search/enhanced.html
+++ b/pages/search/enhanced.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/search/simple-results.html b/pages/search/simple-results.html
index 981daad3b..5c037c7e6 100644
--- a/pages/search/simple-results.html
+++ b/pages/search/simple-results.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/search/simple.html b/pages/search/simple.html
index 368d97b2f..4c8c6e589 100644
--- a/pages/search/simple.html
+++ b/pages/search/simple.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/tables/data.html b/pages/tables/data.html
index d0bf913ba..ecf3ca9c8 100644
--- a/pages/tables/data.html
+++ b/pages/tables/data.html
@@ -308,6 +308,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/tables/jsgrid.html b/pages/tables/jsgrid.html
index 1ae5c8474..5ed153e4e 100644
--- a/pages/tables/jsgrid.html
+++ b/pages/tables/jsgrid.html
@@ -307,6 +307,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/tables/simple.html b/pages/tables/simple.html
index b041380d9..bdfb0226d 100644
--- a/pages/tables/simple.html
+++ b/pages/tables/simple.html
@@ -304,6 +304,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/pages/widgets.html b/pages/widgets.html
index fea298b3a..955b48e8b 100644
--- a/pages/widgets.html
+++ b/pages/widgets.html
@@ -306,6 +306,12 @@
Inline
+
+
+
+ uPlot
+
+
diff --git a/plugins/uplot/uPlot.cjs.js b/plugins/uplot/uPlot.cjs.js
new file mode 100644
index 000000000..b0426a95f
--- /dev/null
+++ b/plugins/uplot/uPlot.cjs.js
@@ -0,0 +1,3451 @@
+/**
+* Copyright (c) 2020, Leon Sorokin
+* All rights reserved. (MIT Licensed)
+*
+* uPlot.js (μPlot)
+* A small, fast chart for time series, lines, areas, ohlc & bars
+* https://github.com/leeoniya/uPlot (v1.4.4)
+*/
+
+'use strict';
+
+function debounce(fn, time) {
+ var pending = null;
+
+ function run() {
+ pending = null;
+ fn();
+ }
+
+ return function() {
+ clearTimeout(pending);
+ pending = setTimeout(run, time);
+ }
+}
+
+// binary search for index of closest value
+function closestIdx(num, arr, lo, hi) {
+ var mid;
+ lo = lo || 0;
+ hi = hi || arr.length - 1;
+ var bitwise = hi <= 2147483647;
+
+ while (hi - lo > 1) {
+ mid = bitwise ? (lo + hi) >> 1 : floor((lo + hi) / 2);
+
+ if (arr[mid] < num)
+ { lo = mid; }
+ else
+ { hi = mid; }
+ }
+
+ if (num - arr[lo] <= arr[hi] - num)
+ { return lo; }
+
+ return hi;
+}
+
+function getMinMax(data, _i0, _i1, sorted) {
+// console.log("getMinMax()");
+
+ var _min = inf;
+ var _max = -inf;
+
+ if (sorted == 1) {
+ _min = data[_i0];
+ _max = data[_i1];
+ }
+ else if (sorted == -1) {
+ _min = data[_i1];
+ _max = data[_i0];
+ }
+ else {
+ for (var i = _i0; i <= _i1; i++) {
+ if (data[i] != null) {
+ _min = min(_min, data[i]);
+ _max = max(_max, data[i]);
+ }
+ }
+ }
+
+ return [_min, _max];
+}
+
+var _fixedTuple = [0, 0];
+
+function fixIncr(minIncr, maxIncr, minExp, maxExp) {
+ _fixedTuple[0] = minExp < 0 ? roundDec(minIncr, -minExp) : minIncr;
+ _fixedTuple[1] = maxExp < 0 ? roundDec(maxIncr, -maxExp) : maxIncr;
+ return _fixedTuple;
+}
+
+function rangeLog(min, max, base, fullMags) {
+ var logFn = base == 10 ? log10 : log2;
+
+ if (min == max) {
+ min /= base;
+ max *= base;
+ }
+
+ var minExp, maxExp, minMaxIncrs;
+
+ if (fullMags) {
+ minExp = floor(logFn(min));
+ maxExp = ceil(logFn(max));
+
+ minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
+
+ min = minMaxIncrs[0];
+ max = minMaxIncrs[1];
+ }
+ else {
+ minExp = floor(logFn(min));
+ maxExp = floor(logFn(max));
+
+ minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
+
+ min = incrRoundDn(min, minMaxIncrs[0]);
+ max = incrRoundUp(max, minMaxIncrs[1]);
+ }
+
+ return [min, max];
+}
+
+var _eqRangePart = {
+ pad: 0,
+ soft: null,
+ mode: 0,
+};
+
+var _eqRange = {
+ min: _eqRangePart,
+ max: _eqRangePart,
+};
+
+// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
+// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
+function rangeNum(_min, _max, mult, extra) {
+ if (isObj(mult))
+ { return _rangeNum(_min, _max, mult); }
+
+ _eqRangePart.pad = mult;
+ _eqRangePart.soft = extra ? 0 : null;
+ _eqRangePart.mode = extra ? 2 : 0;
+
+ return _rangeNum(_min, _max, _eqRange);
+}
+
+// nullish coalesce
+function ifNull(lh, rh) {
+ return lh == null ? rh : lh;
+}
+
+function _rangeNum(_min, _max, cfg) {
+ var cmin = cfg.min;
+ var cmax = cfg.max;
+
+ var padMin = ifNull(cmin.pad, 0);
+ var padMax = ifNull(cmax.pad, 0);
+
+ var hardMin = ifNull(cmin.hard, -inf);
+ var hardMax = ifNull(cmax.hard, inf);
+
+ var softMin = ifNull(cmin.soft, inf);
+ var softMax = ifNull(cmax.soft, -inf);
+
+ var softMinMode = ifNull(cmin.mode, 0);
+ var softMaxMode = ifNull(cmax.mode, 0);
+
+ var delta = _max - _min;
+ var nonZeroDelta = delta || abs(_max) || 1e3;
+ var mag = log10(nonZeroDelta);
+ var base = pow(10, floor(mag));
+
+ var _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? .1 : 1) : padMin);
+ var _newMin = roundDec(incrRoundDn(_min - _padMin, base/100), 6);
+ var _softMin = _min >= softMin && (softMinMode == 1 || softMinMode == 2 && _newMin < softMin) ? softMin : inf;
+ var minLim = max(hardMin, _newMin < _softMin && _min >= _softMin ? _softMin : min(_softMin, _newMin));
+
+ var _padMax = nonZeroDelta * (delta == 0 ? (_max == 0 ? .1 : 1) : padMax);
+ var _newMax = roundDec(incrRoundUp(_max + _padMax, base/100), 6);
+ var _softMax = _max <= softMax && (softMaxMode == 1 || softMaxMode == 2 && _newMax > softMax) ? softMax : -inf;
+ var maxLim = min(hardMax, _newMax > _softMax && _max <= _softMax ? _softMax : max(_softMax, _newMax));
+
+ if (minLim == maxLim && minLim == 0)
+ { maxLim = 100; }
+
+ return [minLim, maxLim];
+}
+
+// alternative: https://stackoverflow.com/a/2254896
+var fmtNum = new Intl.NumberFormat(navigator.language).format;
+
+var M = Math;
+
+var abs = M.abs;
+var floor = M.floor;
+var round = M.round;
+var ceil = M.ceil;
+var min = M.min;
+var max = M.max;
+var pow = M.pow;
+var log10 = M.log10;
+var log2 = M.log2;
+var PI = M.PI;
+
+var inf = Infinity;
+
+function incrRound(num, incr) {
+ return round(num/incr)*incr;
+}
+
+function clamp(num, _min, _max) {
+ return min(max(num, _min), _max);
+}
+
+function fnOrSelf(v) {
+ return typeof v == "function" ? v : function () { return v; };
+}
+
+function retArg1(_0, _1) {
+ return _1;
+}
+
+function incrRoundUp(num, incr) {
+ return ceil(num/incr)*incr;
+}
+
+function incrRoundDn(num, incr) {
+ return floor(num/incr)*incr;
+}
+
+function roundDec(val, dec) {
+ return round(val * (dec = Math.pow( 10, dec ))) / dec;
+}
+
+var fixedDec = new Map();
+
+function guessDec(num) {
+ return ((""+num).split(".")[1] || "").length;
+}
+
+function genIncrs(base, minExp, maxExp, mults) {
+ var incrs = [];
+
+ var multDec = mults.map(guessDec);
+
+ for (var exp = minExp; exp < maxExp; exp++) {
+ var expa = abs(exp);
+ var mag = roundDec(pow(base, exp), expa);
+
+ for (var i = 0; i < mults.length; i++) {
+ var _incr = mults[i] * mag;
+ var dec = (_incr >= 0 && exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]);
+ var incr = roundDec(_incr, dec);
+ incrs.push(incr);
+ fixedDec.set(incr, dec);
+ }
+ }
+
+ return incrs;
+}
+
+//export const assign = Object.assign;
+
+var EMPTY_OBJ = {};
+
+var isArr = Array.isArray;
+
+function isStr(v) {
+ return typeof v === 'string';
+}
+
+function isObj(v) {
+ return typeof v === 'object' && v !== null;
+}
+
+function copy(o) {
+ var out;
+
+ if (isArr(o))
+ { out = o.map(copy); }
+ else if (isObj(o)) {
+ out = {};
+ for (var k in o)
+ { out[k] = copy(o[k]); }
+ }
+ else
+ { out = o; }
+
+ return out;
+}
+
+function assign(targ) {
+ var args = arguments;
+
+ for (var i = 1; i < args.length; i++) {
+ var src = args[i];
+
+ for (var key in src) {
+ if (isObj(targ[key]))
+ { assign(targ[key], copy(src[key])); }
+ else
+ { targ[key] = copy(src[key]); }
+ }
+ }
+
+ return targ;
+}
+
+var microTask = typeof queueMicrotask == "undefined" ? function (fn) { return Promise.resolve().then(fn); } : queueMicrotask;
+
+var WIDTH = "width";
+var HEIGHT = "height";
+var TOP = "top";
+var BOTTOM = "bottom";
+var LEFT = "left";
+var RIGHT = "right";
+var firstChild = "firstChild";
+var createElement = "createElement";
+var hexBlack = "#000";
+var transparent = hexBlack + "0";
+var classList = "classList";
+
+var mousemove = "mousemove";
+var mousedown = "mousedown";
+var mouseup = "mouseup";
+var mouseenter = "mouseenter";
+var mouseleave = "mouseleave";
+var dblclick = "dblclick";
+var resize = "resize";
+var scroll = "scroll";
+
+var pre = "u-";
+
+var UPLOT = "uplot";
+var TITLE = pre + "title";
+var WRAP = pre + "wrap";
+var UNDER = pre + "under";
+var OVER = pre + "over";
+var OFF = pre + "off";
+var SELECT = pre + "select";
+var CURSOR_X = pre + "cursor-x";
+var CURSOR_Y = pre + "cursor-y";
+var CURSOR_PT = pre + "cursor-pt";
+var LEGEND = pre + "legend";
+var LEGEND_LIVE = pre + "live";
+var LEGEND_INLINE = pre + "inline";
+var LEGEND_THEAD = pre + "thead";
+var LEGEND_SERIES = pre + "series";
+var LEGEND_MARKER = pre + "marker";
+var LEGEND_LABEL = pre + "label";
+var LEGEND_VALUE = pre + "value";
+
+var rAF = requestAnimationFrame;
+var doc = document;
+var win = window;
+var pxRatio = devicePixelRatio;
+
+function addClass(el, c) {
+ c != null && el[classList].add(c);
+}
+
+function remClass(el, c) {
+ el[classList].remove(c);
+}
+
+function setStylePx(el, name, value) {
+ el.style[name] = value + "px";
+}
+
+function placeTag(tag, cls, targ, refEl) {
+ var el = doc[createElement](tag);
+
+ if (cls != null)
+ { addClass(el, cls); }
+
+ if (targ != null)
+ { targ.insertBefore(el, refEl); }
+
+ return el;
+}
+
+function placeDiv(cls, targ) {
+ return placeTag("div", cls, targ);
+}
+
+function trans(el, xPos, yPos, xMax, yMax) {
+ el.style.transform = "translate(" + xPos + "px," + yPos + "px)";
+
+ if (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax)
+ { addClass(el, OFF); }
+ else
+ { remClass(el, OFF); }
+}
+
+var evOpts = {passive: true};
+
+function on(ev, el, cb) {
+ el.addEventListener(ev, cb, evOpts);
+}
+
+function off(ev, el, cb) {
+ el.removeEventListener(ev, cb, evOpts);
+}
+
+var months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December" ];
+
+var days = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday" ];
+
+function slice3(str) {
+ return str.slice(0, 3);
+}
+
+var days3 = days.map(slice3);
+
+var months3 = months.map(slice3);
+
+var engNames = {
+ MMMM: months,
+ MMM: months3,
+ WWWW: days,
+ WWW: days3,
+};
+
+function zeroPad2(int) {
+ return (int < 10 ? '0' : '') + int;
+}
+
+function zeroPad3(int) {
+ return (int < 10 ? '00' : int < 100 ? '0' : '') + int;
+}
+
+/*
+function suffix(int) {
+ let mod10 = int % 10;
+
+ return int + (
+ mod10 == 1 && int != 11 ? "st" :
+ mod10 == 2 && int != 12 ? "nd" :
+ mod10 == 3 && int != 13 ? "rd" : "th"
+ );
+}
+*/
+
+var getFullYear = 'getFullYear';
+var getMonth = 'getMonth';
+var getDate = 'getDate';
+var getDay = 'getDay';
+var getHours = 'getHours';
+var getMinutes = 'getMinutes';
+var getSeconds = 'getSeconds';
+var getMilliseconds = 'getMilliseconds';
+
+var subs = {
+ // 2019
+ YYYY: function (d) { return d[getFullYear](); },
+ // 19
+ YY: function (d) { return (d[getFullYear]()+'').slice(2); },
+ // July
+ MMMM: function (d, names) { return names.MMMM[d[getMonth]()]; },
+ // Jul
+ MMM: function (d, names) { return names.MMM[d[getMonth]()]; },
+ // 07
+ MM: function (d) { return zeroPad2(d[getMonth]()+1); },
+ // 7
+ M: function (d) { return d[getMonth]()+1; },
+ // 09
+ DD: function (d) { return zeroPad2(d[getDate]()); },
+ // 9
+ D: function (d) { return d[getDate](); },
+ // Monday
+ WWWW: function (d, names) { return names.WWWW[d[getDay]()]; },
+ // Mon
+ WWW: function (d, names) { return names.WWW[d[getDay]()]; },
+ // 03
+ HH: function (d) { return zeroPad2(d[getHours]()); },
+ // 3
+ H: function (d) { return d[getHours](); },
+ // 9 (12hr, unpadded)
+ h: function (d) {var h = d[getHours](); return h == 0 ? 12 : h > 12 ? h - 12 : h;},
+ // AM
+ AA: function (d) { return d[getHours]() >= 12 ? 'PM' : 'AM'; },
+ // am
+ aa: function (d) { return d[getHours]() >= 12 ? 'pm' : 'am'; },
+ // a
+ a: function (d) { return d[getHours]() >= 12 ? 'p' : 'a'; },
+ // 09
+ mm: function (d) { return zeroPad2(d[getMinutes]()); },
+ // 9
+ m: function (d) { return d[getMinutes](); },
+ // 09
+ ss: function (d) { return zeroPad2(d[getSeconds]()); },
+ // 9
+ s: function (d) { return d[getSeconds](); },
+ // 374
+ fff: function (d) { return zeroPad3(d[getMilliseconds]()); },
+};
+
+function fmtDate(tpl, names) {
+ names = names || engNames;
+ var parts = [];
+
+ var R = /\{([a-z]+)\}|[^{]+/gi, m;
+
+ while (m = R.exec(tpl))
+ { parts.push(m[0][0] == '{' ? subs[m[1]] : m[0]); }
+
+ return function (d) {
+ var out = '';
+
+ for (var i = 0; i < parts.length; i++)
+ { out += typeof parts[i] == "string" ? parts[i] : parts[i](d, names); }
+
+ return out;
+ }
+}
+
+var localTz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+// https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone/53652131#53652131
+function tzDate(date, tz) {
+ var date2;
+
+ // perf optimization
+ if (tz == 'Etc/UTC')
+ { date2 = new Date(+date + date.getTimezoneOffset() * 6e4); }
+ else if (tz == localTz)
+ { date2 = date; }
+ else {
+ date2 = new Date(date.toLocaleString('en-US', {timeZone: tz}));
+ date2.setMilliseconds(date[getMilliseconds]());
+ }
+
+ return date2;
+}
+
+//export const series = [];
+
+// default formatters:
+
+var onlyWhole = function (v) { return v % 1 == 0; };
+
+var allMults = [1,2,2.5,5];
+
+var wholeMults = allMults.filter(onlyWhole);
+
+// ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5
+var decIncrs = genIncrs(10, -16, 0, allMults);
+
+// 1, 2, 2.5, 5, 10, 20, 25, 50...
+var oneIncrs = genIncrs(10, 0, 16, allMults);
+
+// 1, 2, 5, 10, 20, 25, 50...
+var wholeIncrs = oneIncrs.filter(onlyWhole);
+
+var numIncrs = decIncrs.concat(oneIncrs);
+
+var s = 1,
+ m = 60,
+ h = m * m,
+ d = h * 24,
+ mo = d * 30,
+ y = d * 365;
+
+// min of 1e-3 prevents setting a temporal x ticks too small since Date objects cannot advance ticks smaller than 1ms
+var timeIncrs = genIncrs(10, -3, 0, wholeMults).concat([
+ // minute divisors (# of secs)
+ 1,
+ 5,
+ 10,
+ 15,
+ 30,
+ // hour divisors (# of mins)
+ m,
+ m * 5,
+ m * 10,
+ m * 15,
+ m * 30,
+ // day divisors (# of hrs)
+ h,
+ h * 2,
+ h * 3,
+ h * 4,
+ h * 6,
+ h * 8,
+ h * 12,
+ // month divisors TODO: need more?
+ d,
+ d * 2,
+ d * 3,
+ d * 4,
+ d * 5,
+ d * 6,
+ d * 7,
+ d * 8,
+ d * 9,
+ d * 10,
+ d * 15,
+ // year divisors (# months, approx)
+ mo,
+ mo * 2,
+ mo * 3,
+ mo * 4,
+ mo * 6,
+ // century divisors
+ y,
+ y * 2,
+ y * 5,
+ y * 10,
+ y * 25,
+ y * 50,
+ y * 100 ]);
+
+// base 2
+var binIncrs = genIncrs(2, -53, 53, [1]);
+
+/*
+console.log({
+ decIncrs,
+ oneIncrs,
+ wholeIncrs,
+ numIncrs,
+ timeIncrs,
+ fixedDec,
+});
+*/
+
+function timeAxisStamps(stampCfg, fmtDate) {
+ return stampCfg.map(function (s) { return s.map(function (v, i) { return i == 0 || i == 8 || v == null ? v : fmtDate(i == 1 || s[8] == 0 ? v : s[1] + v); }
+ ); });
+}
+
+var NL = "\n";
+
+var yyyy = "{YYYY}";
+var NLyyyy = NL + yyyy;
+var md = "{M}/{D}";
+var NLmd = NL + md;
+var NLmdyy = NLmd + "/{YY}";
+
+var aa = "{aa}";
+var hmm = "{h}:{mm}";
+var hmmaa = hmm + aa;
+var NLhmmaa = NL + hmmaa;
+var ss = ":{ss}";
+
+var _ = null;
+
+// [0]: minimum num secs in the tick incr
+// [1]: default tick format
+// [2-7]: rollover tick formats
+// [8]: mode: 0: replace [1] -> [2-7], 1: concat [1] + [2-7]
+var _timeAxisStamps = [
+// tick incr default year month day hour min sec mode
+ [y, yyyy, _, _, _, _, _, _, 1],
+ [d * 28, "{MMM}", NLyyyy, _, _, _, _, _, 1],
+ [d, md, NLyyyy, _, _, _, _, _, 1],
+ [h, "{h}" + aa, NLmdyy, _, NLmd, _, _, _, 1],
+ [m, hmmaa, NLmdyy, _, NLmd, _, _, _, 1],
+ [s, ss, NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
+ [1e-3, ss + ".{fff}", NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1] ];
+
+// TODO: will need to accept spaces[] and pull incr into the loop when grid will be non-uniform, eg for log scales.
+// currently we ignore this for months since they're *nearly* uniform and the added complexity is not worth it
+function timeAxisVals(tzDate, stamps) {
+ return function (self, splits, axisIdx, foundSpace, foundIncr) {
+ var s = stamps.find(function (s) { return foundIncr >= s[0]; }) || stamps[stamps.length - 1];
+
+ // these track boundaries when a full label is needed again
+ var prevYear;
+ var prevMnth;
+ var prevDate;
+ var prevHour;
+ var prevMins;
+ var prevSecs;
+
+ return splits.map(function (split) {
+ var date = tzDate(split);
+
+ var newYear = date[getFullYear]();
+ var newMnth = date[getMonth]();
+ var newDate = date[getDate]();
+ var newHour = date[getHours]();
+ var newMins = date[getMinutes]();
+ var newSecs = date[getSeconds]();
+
+ var stamp = (
+ newYear != prevYear && s[2] ||
+ newMnth != prevMnth && s[3] ||
+ newDate != prevDate && s[4] ||
+ newHour != prevHour && s[5] ||
+ newMins != prevMins && s[6] ||
+ newSecs != prevSecs && s[7] ||
+ s[1]
+ );
+
+ prevYear = newYear;
+ prevMnth = newMnth;
+ prevDate = newDate;
+ prevHour = newHour;
+ prevMins = newMins;
+ prevSecs = newSecs;
+
+ return stamp(date);
+ });
+ }
+}
+
+// for when axis.values is defined as a static fmtDate template string
+function timeAxisVal(tzDate, dateTpl) {
+ var stamp = fmtDate(dateTpl);
+ return function (self, splits, axisIdx, foundSpace, foundIncr) { return splits.map(function (split) { return stamp(tzDate(split)); }); };
+}
+
+function mkDate(y, m, d) {
+ return new Date(y, m, d);
+}
+
+// the ensures that axis ticks, values & grid are aligned to logical temporal breakpoints and not an arbitrary timestamp
+// https://www.timeanddate.com/time/dst/
+// https://www.timeanddate.com/time/dst/2019.html
+// https://www.epochconverter.com/timezones
+function timeAxisSplits(tzDate) {
+ return function (self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) {
+ var splits = [];
+ var isYr = foundIncr >= y;
+ var isMo = foundIncr >= mo && foundIncr < y;
+
+ // get the timezone-adjusted date
+ var minDate = tzDate(scaleMin);
+ var minDateTs = minDate / 1e3;
+
+ // get ts of 12am (this lands us at or before the original scaleMin)
+ var minMin = mkDate(minDate[getFullYear](), isYr ? 0 : minDate[getMonth](), isMo || isYr ? 1 : minDate[getDate]());
+ var minMinTs = minMin / 1e3;
+
+ if (isMo || isYr) {
+ var moIncr = isMo ? foundIncr / mo : 0;
+ var yrIncr = isYr ? foundIncr / y : 0;
+ // let tzOffset = scaleMin - minDateTs; // needed?
+ var split = minDateTs == minMinTs ? minDateTs : mkDate(minMin[getFullYear]() + yrIncr, minMin[getMonth]() + moIncr, 1) / 1e3;
+ var splitDate = new Date(split * 1e3);
+ var baseYear = splitDate[getFullYear]();
+ var baseMonth = splitDate[getMonth]();
+
+ for (var i = 0; split <= scaleMax; i++) {
+ var next = mkDate(baseYear + yrIncr * i, baseMonth + moIncr * i, 1);
+ var offs = next - tzDate(next / 1e3);
+
+ split = (+next + offs) / 1e3;
+
+ if (split <= scaleMax)
+ { splits.push(split); }
+ }
+ }
+ else {
+ var incr0 = foundIncr >= d ? d : foundIncr;
+ var tzOffset = floor(scaleMin) - floor(minDateTs);
+ var split$1 = minMinTs + tzOffset + incrRoundUp(minDateTs - minMinTs, incr0);
+ splits.push(split$1);
+
+ var date0 = tzDate(split$1);
+
+ var prevHour = date0[getHours]() + (date0[getMinutes]() / m) + (date0[getSeconds]() / h);
+ var incrHours = foundIncr / h;
+
+ var minSpace = self.axes[axisIdx]._space;
+ var pctSpace = foundSpace / minSpace;
+
+ while (1) {
+ split$1 = roundDec(split$1 + foundIncr, 3);
+
+ if (split$1 > scaleMax)
+ { break; }
+
+ if (incrHours > 1) {
+ var expectedHour = floor(roundDec(prevHour + incrHours, 6)) % 24;
+ var splitDate$1 = tzDate(split$1);
+ var actualHour = splitDate$1.getHours();
+
+ var dstShift = actualHour - expectedHour;
+
+ if (dstShift > 1)
+ { dstShift = -1; }
+
+ split$1 -= dstShift * h;
+
+ prevHour = (prevHour + incrHours) % 24;
+
+ // add a tick only if it's further than 70% of the min allowed label spacing
+ var prevSplit = splits[splits.length - 1];
+ var pctIncr = roundDec((split$1 - prevSplit) / foundIncr, 3);
+
+ if (pctIncr * pctSpace >= .7)
+ { splits.push(split$1); }
+ }
+ else
+ { splits.push(split$1); }
+ }
+ }
+
+ return splits;
+ }
+}
+
+function timeSeriesStamp(stampCfg, fmtDate) {
+ return fmtDate(stampCfg);
+}
+var _timeSeriesStamp = '{YYYY}-{MM}-{DD} {h}:{mm}{aa}';
+
+function timeSeriesVal(tzDate, stamp) {
+ return function (self, val) { return stamp(tzDate(val)); };
+}
+
+function cursorPoint(self, si) {
+ var s = self.series[si];
+
+ var pt = placeDiv();
+
+ pt.style.background = s.stroke || hexBlack;
+
+ var dia = ptDia(s.width, 1);
+ var mar = (dia - 1) / -2;
+
+ setStylePx(pt, WIDTH, dia);
+ setStylePx(pt, HEIGHT, dia);
+ setStylePx(pt, "marginLeft", mar);
+ setStylePx(pt, "marginTop", mar);
+
+ return pt;
+}
+
+function dataIdx(self, seriesIdx, cursorIdx) {
+ return cursorIdx;
+}
+
+var moveTuple = [0,0];
+
+function cursorMove(self, mouseLeft1, mouseTop1) {
+ moveTuple[0] = mouseLeft1;
+ moveTuple[1] = mouseTop1;
+ return moveTuple;
+}
+
+function filtBtn0(self, targ, handle) {
+ return function (e) {
+ e.button == 0 && handle(e);
+ };
+}
+
+function passThru(self, targ, handle) {
+ return handle;
+}
+
+var cursorOpts = {
+ show: true,
+ x: true,
+ y: true,
+ lock: false,
+ move: cursorMove,
+ points: {
+ show: cursorPoint,
+ },
+
+ bind: {
+ mousedown: filtBtn0,
+ mouseup: filtBtn0,
+ click: filtBtn0,
+ dblclick: filtBtn0,
+
+ mousemove: passThru,
+ mouseleave: passThru,
+ mouseenter: passThru,
+ },
+
+ drag: {
+ setScale: true,
+ x: true,
+ y: false,
+ dist: 0,
+ uni: null,
+ _x: false,
+ _y: false,
+ },
+
+ focus: {
+ prox: -1,
+ },
+
+ left: -10,
+ top: -10,
+ idx: null,
+ dataIdx: dataIdx,
+};
+
+var grid = {
+ show: true,
+ stroke: "rgba(0,0,0,0.07)",
+ width: 2,
+// dash: [],
+ filter: retArg1,
+};
+
+var ticks = assign({}, grid, {size: 10});
+
+var font = '12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
+var labelFont = "bold " + font;
+var lineMult = 1.5; // font-size multiplier
+
+var xAxisOpts = {
+ show: true,
+ scale: "x",
+ space: 50,
+ gap: 5,
+ size: 50,
+ labelSize: 30,
+ labelFont: labelFont,
+ side: 2,
+// class: "x-vals",
+// incrs: timeIncrs,
+// values: timeVals,
+// filter: retArg1,
+ grid: grid,
+ ticks: ticks,
+ font: font,
+ rotate: 0,
+};
+
+var numSeriesLabel = "Value";
+var timeSeriesLabel = "Time";
+
+var xSeriesOpts = {
+ show: true,
+ scale: "x",
+ auto: false,
+ sorted: 1,
+// label: "Time",
+// value: v => stamp(new Date(v * 1e3)),
+
+ // internal caches
+ min: inf,
+ max: -inf,
+ idxs: [],
+};
+
+function numAxisVals(self, splits, axisIdx, foundSpace, foundIncr) {
+ return splits.map(function (v) { return v == null ? "" : fmtNum(v); });
+}
+
+function numAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
+ var splits = [];
+
+ var numDec = fixedDec.get(foundIncr) || 0;
+
+ scaleMin = forceMin ? scaleMin : roundDec(incrRoundUp(scaleMin, foundIncr), numDec);
+
+ for (var val = scaleMin; val <= scaleMax; val = roundDec(val + foundIncr, numDec))
+ { splits.push(Object.is(val, -0) ? 0 : val); } // coalesces -0
+
+ return splits;
+}
+
+function logAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
+ var splits = [];
+
+ var logBase = self.scales[self.axes[axisIdx].scale].log;
+
+ var logFn = logBase == 10 ? log10 : log2;
+
+ var exp = floor(logFn(scaleMin));
+
+ foundIncr = pow(logBase, exp);
+
+ if (exp < 0)
+ { foundIncr = roundDec(foundIncr, -exp); }
+
+ var split = scaleMin;
+
+ do {
+ splits.push(split);
+ split = roundDec(split + foundIncr, fixedDec.get(foundIncr));
+
+ if (split >= foundIncr * logBase)
+ { foundIncr = split; }
+
+ } while (split <= scaleMax);
+
+ return splits;
+}
+
+var RE_ALL = /./;
+var RE_12357 = /[12357]/;
+var RE_125 = /[125]/;
+var RE_1 = /1/;
+
+function logAxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {
+ var axis = self.axes[axisIdx];
+ var scaleKey = axis.scale;
+
+ if (self.scales[scaleKey].log == 2)
+ { return splits; }
+
+ var valToPos = self.valToPos;
+
+ var minSpace = axis._space;
+
+ var _10 = valToPos(10, scaleKey);
+
+ var re = (
+ valToPos(9, scaleKey) - _10 >= minSpace ? RE_ALL :
+ valToPos(7, scaleKey) - _10 >= minSpace ? RE_12357 :
+ valToPos(5, scaleKey) - _10 >= minSpace ? RE_125 :
+ RE_1
+ );
+
+ return splits.map(function (v) { return re.test(v) ? v : null; });
+}
+
+function numSeriesVal(self, val) {
+ return val == null ? "" : fmtNum(val);
+}
+
+var yAxisOpts = {
+ show: true,
+ scale: "y",
+ space: 30,
+ gap: 5,
+ size: 50,
+ labelSize: 30,
+ labelFont: labelFont,
+ side: 3,
+// class: "y-vals",
+// incrs: numIncrs,
+// values: (vals, space) => vals,
+// filter: retArg1,
+ grid: grid,
+ ticks: ticks,
+ font: font,
+ rotate: 0,
+};
+
+// takes stroke width
+function ptDia(width, mult) {
+ var dia = 3 + (width || 1) * 2;
+ return roundDec(dia * mult, 3);
+}
+
+function seriesPoints(self, si) {
+ var s = self.series[si];
+ var dia = ptDia(s.width, pxRatio);
+ var maxPts = self.bbox.width / (s.points.space * pxRatio);
+ var idxs = self.series[0].idxs;
+ return idxs[1] - idxs[0] <= maxPts;
+}
+
+function seriesFillTo(self, seriesIdx, dataMin, dataMax) {
+ var scale = self.scales[self.series[seriesIdx].scale];
+ return scale.distr == 3 ? scale.min : 0;
+}
+
+var ySeriesOpts = {
+ scale: "y",
+ auto: true,
+ sorted: 0,
+ show: true,
+ band: false,
+ spanGaps: false,
+ isGap: function (self, seriesIdx, dataIdx) { return true; },
+ alpha: 1,
+ points: {
+ show: seriesPoints,
+ // stroke: "#000",
+ // fill: "#fff",
+ // width: 1,
+ // size: 10,
+ },
+// label: "Value",
+// value: v => v,
+ values: null,
+
+ // internal caches
+ min: inf,
+ max: -inf,
+ idxs: [],
+
+ path: null,
+ clip: null,
+};
+
+var xScaleOpts = {
+ time: true,
+ auto: true,
+ distr: 1,
+ log: 10,
+ min: null,
+ max: null,
+};
+
+var yScaleOpts = assign({}, xScaleOpts, {
+ time: false,
+});
+
+var syncs = {};
+
+function _sync(opts) {
+ var clients = [];
+
+ return {
+ sub: function sub(client) {
+ clients.push(client);
+ },
+ unsub: function unsub(client) {
+ clients = clients.filter(function (c) { return c != client; });
+ },
+ pub: function pub(type, self, x, y, w, h, i) {
+ if (clients.length > 1) {
+ clients.forEach(function (client) {
+ client != self && client.pub(type, self, x, y, w, h, i);
+ });
+ }
+ }
+ };
+}
+
+function setDefaults(d, xo, yo, initY) {
+ var d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));
+ return d2.map(function (o, i) { return setDefault(o, i, xo, yo); });
+}
+
+function setDefault(o, i, xo, yo) {
+ return assign({}, (i == 0 || o && o.side % 2 == 0 ? xo : yo), o);
+}
+
+function getValPct(val, scale) {
+ return (
+ scale.distr == 3
+ ? log10(val / scale.min) / log10(scale.max / scale.min)
+ : (val - scale.min) / (scale.max - scale.min)
+ );
+}
+
+function getYPos(val, scale, hgt, top) {
+ var pctY = getValPct(val, scale);
+ return top + (1 - pctY) * hgt;
+}
+
+function getXPos(val, scale, wid, lft) {
+ var pctX = getValPct(val, scale);
+ return lft + pctX * wid;
+}
+
+var nullMinMax = [null, null];
+
+function snapNumX(self, dataMin, dataMax) {
+ return dataMin == null ? nullMinMax : [dataMin, dataMax];
+}
+
+var snapTimeX = snapNumX;
+
+// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
+// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
+function snapNumY(self, dataMin, dataMax) {
+ return dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, 0.1, true);
+}
+
+function snapLogY(self, dataMin, dataMax, scale) {
+ return dataMin == null ? nullMinMax : rangeLog(dataMin, dataMax, self.scales[scale].log, false);
+}
+
+var snapLogX = snapLogY;
+
+// dim is logical (getClientBoundingRect) pixels, not canvas pixels
+function findIncr(min, max, incrs, dim, minSpace) {
+ var pxPerUnit = dim / (max - min);
+
+ var minDec = (""+floor(min)).length;
+
+ for (var i = 0; i < incrs.length; i++) {
+ var space = incrs[i] * pxPerUnit;
+
+ var incrDec = incrs[i] < 10 ? fixedDec.get(incrs[i]) : 0;
+
+ if (space >= minSpace && minDec + incrDec < 17)
+ { return [incrs[i], space]; }
+ }
+
+ return [0, 0];
+}
+
+function pxRatioFont(font) {
+ var fontSize;
+ font = font.replace(/(\d+)px/, function (m, p1) { return (fontSize = round(p1 * pxRatio)) + 'px'; });
+ return [font, fontSize];
+}
+
+function uPlot(opts, data, then) {
+ var self = {};
+
+ var ready = false;
+ self.status = 0;
+
+ var root = self.root = placeDiv(UPLOT);
+
+ if (opts.id != null)
+ { root.id = opts.id; }
+
+ addClass(root, opts.class);
+
+ if (opts.title) {
+ var title = placeDiv(TITLE, root);
+ title.textContent = opts.title;
+ }
+
+ var can = placeTag("canvas");
+ var ctx = self.ctx = can.getContext("2d");
+
+ var wrap = placeDiv(WRAP, root);
+ var under = placeDiv(UNDER, wrap);
+ wrap.appendChild(can);
+ var over = placeDiv(OVER, wrap);
+
+ opts = copy(opts);
+
+ (opts.plugins || []).forEach(function (p) {
+ if (p.opts)
+ { opts = p.opts(self, opts) || opts; }
+ });
+
+
+
+ var series = self.series = setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false);
+ var axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);
+ var scales = self.scales = {};
+
+ var xScaleKey = series[0].scale;
+
+ function initScale(scaleKey) {
+ var sc = scales[scaleKey];
+
+ if (sc == null) {
+ var scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;
+
+ if (scaleOpts.from != null) {
+ // ensure parent is initialized
+ initScale(scaleOpts.from);
+ // dependent scales inherit
+ scales[scaleKey] = assign({}, scales[scaleOpts.from], scaleOpts);
+ }
+ else {
+ sc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);
+
+ var isTime = sc.time;
+ var isLog = sc.distr == 3;
+
+ var rn = sc.range;
+
+ if (scaleKey != xScaleKey && !isArr(rn) && isObj(rn)) {
+ var cfg = rn;
+ // this is similar to snapNumY
+ rn = function (self, dataMin, dataMax) { return dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, cfg); };
+ }
+
+ sc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ? (isLog ? snapLogX : snapNumX) : (isLog ? snapLogY : snapNumY)));
+
+ sc.auto = fnOrSelf(sc.auto);
+ }
+ }
+ }
+
+ initScale("x");
+ initScale("y");
+
+ series.forEach(function (s, i) {
+ initScale(s.scale);
+ });
+
+ for (var k in opts.scales)
+ { initScale(k); }
+
+ var xScaleDistr = scales[xScaleKey].distr;
+
+ var pendScales = {};
+
+ // explicitly-set initial scales
+ for (var k$1 in scales) {
+ var sc = scales[k$1];
+
+ if (sc.min != null || sc.max != null)
+ { pendScales[k$1] = {min: sc.min, max: sc.max}; }
+ }
+
+ var gutters = self.gutters = assign({
+ x: round(yAxisOpts.size / 2),
+ y: round(xAxisOpts.size / 3),
+ _x: null,
+ _y: null,
+ }, opts.gutters);
+
+ gutters.x = fnOrSelf(gutters.x);
+ gutters.y = fnOrSelf(gutters.y);
+ gutters._x = gutters.x(self);
+ gutters._y = gutters.y(self);
+
+// self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
+ var _tzDate = (opts.tzDate || (function (ts) { return new Date(ts * 1e3); }));
+ var _fmtDate = (opts.fmtDate || fmtDate);
+
+ var _timeAxisSplits = timeAxisSplits(_tzDate);
+ var _timeAxisVals = timeAxisVals(_tzDate, timeAxisStamps(_timeAxisStamps, _fmtDate));
+ var _timeSeriesVal = timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));
+
+ var legend = assign({show: true, live: true}, opts.legend);
+ var showLegend = legend.show;
+
+ var legendEl;
+ var legendRows = [];
+ var legendCols;
+ var multiValLegend = false;
+
+ if (showLegend) {
+ legendEl = placeTag("table", LEGEND, root);
+
+ var getMultiVals = series[1] ? series[1].values : null;
+ multiValLegend = getMultiVals != null;
+
+ if (multiValLegend) {
+ var head = placeTag("tr", LEGEND_THEAD, legendEl);
+ placeTag("th", null, head);
+ legendCols = getMultiVals(self, 1, 0);
+
+ for (var key in legendCols)
+ { placeTag("th", LEGEND_LABEL, head).textContent = key; }
+ }
+ else {
+ legendCols = {_: 0};
+ addClass(legendEl, LEGEND_INLINE);
+ legend.live && addClass(legendEl, LEGEND_LIVE);
+ }
+ }
+
+ function initLegendRow(s, i) {
+ if (i == 0 && (multiValLegend || !legend.live))
+ { return null; }
+
+ var _row = [];
+
+ var row = placeTag("tr", LEGEND_SERIES, legendEl, legendEl.childNodes[i]);
+
+ addClass(row, s.class);
+
+ if (!s.show)
+ { addClass(row, OFF); }
+
+ var label = placeTag("th", null, row);
+
+ var indic = placeDiv(LEGEND_MARKER, label);
+ indic.style.borderColor = s.width ? s.stroke : i > 0 && s.points.width ? s.points.stroke : null;
+ indic.style.backgroundColor = s.fill || null;
+
+ var text = placeDiv(LEGEND_LABEL, label);
+ text.textContent = s.label;
+
+ if (i > 0) {
+ onMouse("click", label, function (e) {
+ if ( cursor._lock)
+ { return; }
+
+ setSeries(series.indexOf(s), {show: !s.show}, syncOpts.setSeries);
+ });
+
+ if (cursorFocus) {
+ onMouse(mouseenter, label, function (e) {
+ if (cursor._lock)
+ { return; }
+
+ setSeries(series.indexOf(s), {focus: true}, syncOpts.setSeries);
+ });
+ }
+ }
+
+ for (var key in legendCols) {
+ var v = placeTag("td", LEGEND_VALUE, row);
+ v.textContent = "--";
+ _row.push(v);
+ }
+
+ return _row;
+ }
+
+ var mouseListeners = new Map();
+
+ function onMouse(ev, targ, fn) {
+ var targListeners = mouseListeners.get(targ) || {};
+ var listener = cursor.bind[ev](self, targ, fn);
+
+ if (listener) {
+ on(ev, targ, targListeners[ev] = listener);
+ mouseListeners.set(targ, targListeners);
+ }
+ }
+
+ function offMouse(ev, targ, fn) {
+ var targListeners = mouseListeners.get(targ) || {};
+ off(ev, targ, targListeners[ev]);
+ targListeners[ev] = null;
+ }
+
+ var fullWidCss = 0;
+ var fullHgtCss = 0;
+
+ var plotWidCss = 0;
+ var plotHgtCss = 0;
+
+ // plot margins to account for axes
+ var plotLftCss = 0;
+ var plotTopCss = 0;
+
+ var plotLft = 0;
+ var plotTop = 0;
+ var plotWid = 0;
+ var plotHgt = 0;
+
+ self.bbox = {};
+
+ var shouldSetScales = false;
+ var shouldSetSize = false;
+ var shouldConvergeSize = false;
+ var shouldSetCursor = false;
+ var shouldSetLegend = false;
+
+ function _setSize(width, height) {
+ if (width != self.width || height != self.height)
+ { calcSize(width, height); }
+
+ resetYSeries(false);
+
+ shouldConvergeSize = true;
+ shouldSetSize = true;
+ shouldSetCursor = true;
+ shouldSetLegend = true;
+ commit();
+ }
+
+ function calcSize(width, height) {
+ // log("calcSize()", arguments);
+
+ self.width = fullWidCss = plotWidCss = width;
+ self.height = fullHgtCss = plotHgtCss = height;
+ plotLftCss = plotTopCss = 0;
+
+ calcPlotRect();
+ calcAxesRects();
+
+ var bb = self.bbox;
+
+ plotLft = bb[LEFT] = incrRound(plotLftCss * pxRatio, 0.5);
+ plotTop = bb[TOP] = incrRound(plotTopCss * pxRatio, 0.5);
+ plotWid = bb[WIDTH] = incrRound(plotWidCss * pxRatio, 0.5);
+ plotHgt = bb[HEIGHT] = incrRound(plotHgtCss * pxRatio, 0.5);
+ }
+
+ function convergeSize() {
+ var converged = false;
+
+ while (!converged) {
+ var axesConverged = axesCalc();
+ var guttersConverged = guttersCalc();
+
+ converged = axesConverged && guttersConverged;
+
+ if (!converged) {
+ calcSize(self.width, self.height);
+ shouldSetSize = true;
+ }
+ }
+ }
+
+ function setSize(ref) {
+ var width = ref.width;
+ var height = ref.height;
+
+ _setSize(width, height);
+ }
+
+ self.setSize = setSize;
+
+ // accumulate axis offsets, reduce canvas width
+ function calcPlotRect() {
+ // easements for edge labels
+ var hasTopAxis = false;
+ var hasBtmAxis = false;
+ var hasRgtAxis = false;
+ var hasLftAxis = false;
+
+ axes.forEach(function (axis, i) {
+ if (axis.show && axis._show) {
+ var side = axis.side;
+ var _size = axis._size;
+ var isVt = side % 2;
+ var labelSize = axis.labelSize = (axis.label != null ? (axis.labelSize || 30) : 0);
+
+ var fullSize = _size + labelSize;
+
+ if (fullSize > 0) {
+ if (isVt) {
+ plotWidCss -= fullSize;
+
+ if (side == 3) {
+ plotLftCss += fullSize;
+ hasLftAxis = true;
+ }
+ else
+ { hasRgtAxis = true; }
+ }
+ else {
+ plotHgtCss -= fullSize;
+
+ if (side == 0) {
+ plotTopCss += fullSize;
+ hasTopAxis = true;
+ }
+ else
+ { hasBtmAxis = true; }
+ }
+ }
+ }
+ });
+
+ // hz gutters
+ if (hasTopAxis || hasBtmAxis) {
+ if (!hasRgtAxis)
+ { plotWidCss -= gutters._x; }
+ if (!hasLftAxis) {
+ plotWidCss -= gutters._x;
+ plotLftCss += gutters._x;
+ }
+ }
+
+ // vt gutters
+ if (hasLftAxis || hasRgtAxis) {
+ if (!hasBtmAxis)
+ { plotHgtCss -= gutters._y; }
+ if (!hasTopAxis) {
+ plotHgtCss -= gutters._y;
+ plotTopCss += gutters._y;
+ }
+ }
+ }
+
+ function calcAxesRects() {
+ // will accum +
+ var off1 = plotLftCss + plotWidCss;
+ var off2 = plotTopCss + plotHgtCss;
+ // will accum -
+ var off3 = plotLftCss;
+ var off0 = plotTopCss;
+
+ function incrOffset(side, size) {
+
+ switch (side) {
+ case 1: off1 += size; return off1 - size;
+ case 2: off2 += size; return off2 - size;
+ case 3: off3 -= size; return off3 + size;
+ case 0: off0 -= size; return off0 + size;
+ }
+ }
+
+ axes.forEach(function (axis, i) {
+ if (axis.show && axis._show) {
+ var side = axis.side;
+
+ axis._pos = incrOffset(side, axis._size);
+
+ if (axis.label != null)
+ { axis._lpos = incrOffset(side, axis.labelSize); }
+ }
+ });
+ }
+
+ var cursor = (self.cursor = assign({}, cursorOpts, opts.cursor));
+
+ (cursor._lock = false);
+ (cursor.points.show = fnOrSelf(cursor.points.show));
+
+ var focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);
+ var cursorFocus = focus.prox >= 0;
+
+ // series-intersection markers
+ var cursorPts = [null];
+
+ function initCursorPt(s, si) {
+ if (si > 0) {
+ var pt = cursor.points.show(self, si);
+
+ if (pt) {
+ addClass(pt, CURSOR_PT);
+ addClass(pt, s.class);
+ trans(pt, -10, -10, plotWidCss, plotHgtCss);
+ over.insertBefore(pt, cursorPts[si]);
+
+ return pt;
+ }
+ }
+ }
+
+ function initSeries(s, i) {
+ var isTime = scales[s.scale].time;
+
+ var sv = s.value;
+ s.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;
+ s.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);
+
+ if (i > 0) {
+ s.width = s.width == null ? 1 : s.width;
+ s.paths = s.paths || ( buildPaths);
+ s.fillTo = s.fillTo || seriesFillTo;
+ var _ptDia = ptDia(s.width, 1);
+ s.points = assign({}, {
+ size: _ptDia,
+ width: max(1, _ptDia * .2),
+ stroke: s.stroke,
+ space: _ptDia * 2,
+ }, s.points);
+ s.points.show = fnOrSelf(s.points.show);
+ s._paths = null;
+ }
+
+ if (showLegend)
+ { legendRows.splice(i, 0, initLegendRow(s, i)); }
+
+ if ( cursor.show) {
+ var pt = initCursorPt(s, i);
+ pt && cursorPts.splice(i, 0, pt);
+ }
+ }
+
+ function addSeries(opts, si) {
+ si = si == null ? series.length : si;
+
+ opts = setDefault(opts, si, xSeriesOpts, ySeriesOpts);
+ series.splice(si, 0, opts);
+ initSeries(series[si], si);
+ }
+
+ self.addSeries = addSeries;
+
+ function delSeries(i) {
+ series.splice(i, 1);
+ showLegend && legendRows.splice(i, 1)[0][0].parentNode.remove();
+ cursorPts.length > 1 && cursorPts.splice(i, 1)[0].remove();
+
+ // TODO: de-init no-longer-needed scales?
+ }
+
+ self.delSeries = delSeries;
+
+ series.forEach(initSeries);
+
+ function initAxis(axis, i) {
+ axis._show = axis.show;
+
+ if (axis.show) {
+ var isVt = axis.side % 2;
+
+ var sc = scales[axis.scale];
+
+ // this can occur if all series specify non-default scales
+ if (sc == null) {
+ axis.scale = isVt ? series[1].scale : xScaleKey;
+ sc = scales[axis.scale];
+ }
+
+ // also set defaults for incrs & values based on axis distr
+ var isTime = sc.time;
+
+ axis.size = fnOrSelf(axis.size);
+ axis.space = fnOrSelf(axis.space);
+ axis.rotate = fnOrSelf(axis.rotate);
+ axis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? timeIncrs : numIncrs)));
+ axis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : numAxisSplits));
+
+ var av = axis.values;
+ axis.values = (
+ isTime ? (
+ isArr(av) ?
+ timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :
+ isStr(av) ?
+ timeAxisVal(_tzDate, av) :
+ av || _timeAxisVals
+ ) : av || numAxisVals
+ );
+
+ axis.filter = fnOrSelf(axis.filter || ( sc.distr == 3 ? logAxisValsFilt : retArg1));
+
+ axis.font = pxRatioFont(axis.font);
+ axis.labelFont = pxRatioFont(axis.labelFont);
+
+ axis._size = axis.size(self, null, i);
+
+ axis._space =
+ axis._rotate =
+ axis._incrs =
+ axis._found = // foundIncrSpace
+ axis._splits =
+ axis._values = null;
+ }
+ }
+
+ // set axis defaults
+ axes.forEach(initAxis);
+
+ var dataLen;
+ var dataIsGap;
+
+ // rendered data window
+ var i0 = null;
+ var i1 = null;
+ var idxs = series[0].idxs;
+
+ var data0 = null;
+
+ var viaAutoScaleX = false;
+
+ function setData(_data, _resetScales) {
+ if (!isArr(_data) && isObj(_data)) {
+ dataIsGap = _data.isGap;
+ _data = _data.data;
+ }
+
+ _data = _data || [];
+ _data[0] = _data[0] || [];
+
+ self.data = _data;
+ data = _data.slice();
+ data0 = data[0];
+ dataLen = data0.length;
+
+ if (xScaleDistr == 2)
+ { data[0] = data0.map(function (v, i) { return i; }); }
+
+ resetYSeries(true);
+
+ fire("setData");
+
+ if (_resetScales !== false) {
+ var xsc = scales[xScaleKey];
+
+ if (xsc.auto(self, viaAutoScaleX))
+ { autoScaleX(); }
+ else
+ { _setScale(xScaleKey, xsc.min, xsc.max); }
+
+ shouldSetCursor = true;
+ shouldSetLegend = true;
+ commit();
+ }
+ }
+
+ self.setData = setData;
+
+ function autoScaleX() {
+ var assign, assign$1;
+
+ viaAutoScaleX = true;
+
+ var _min, _max;
+
+ if (dataLen > 0) {
+ i0 = idxs[0] = 0;
+ i1 = idxs[1] = dataLen - 1;
+
+ _min = data[0][i0];
+ _max = data[0][i1];
+
+ if (xScaleDistr == 2) {
+ _min = i0;
+ _max = i1;
+ }
+ else if (dataLen == 1) {
+ if (xScaleDistr == 3)
+ { (assign = rangeLog(_min, _min, scales[xScaleKey].log, false), _min = assign[0], _max = assign[1]); }
+ else if (scales[xScaleKey].time)
+ { _max = _min + 86400; }
+ else
+ { (assign$1 = rangeNum(_min, _max, 0.1, true), _min = assign$1[0], _max = assign$1[1]); }
+ }
+ }
+ else {
+ i0 = idxs[0] = _min = null;
+ i1 = idxs[1] = _max = null;
+ }
+
+ _setScale(xScaleKey, _min, _max);
+
+ viaAutoScaleX = false;
+ }
+
+ function setCtxStyle(stroke, width, dash, fill) {
+ ctx.strokeStyle = stroke || transparent;
+ ctx.lineWidth = width;
+ ctx.lineJoin = "round";
+ ctx.setLineDash(dash || []);
+ ctx.fillStyle = fill || transparent;
+ }
+
+ function setScales() {
+ // log("setScales()", arguments);
+
+ // wip scales
+ var wipScales = copy(scales);
+
+ for (var k in wipScales) {
+ var wsc = wipScales[k];
+ var psc = pendScales[k];
+
+ if (psc != null && psc.min != null) {
+ assign(wsc, psc);
+
+ // explicitly setting the x-scale invalidates everything (acts as redraw)
+ if (k == xScaleKey)
+ { resetYSeries(true); }
+ }
+ else if (k != xScaleKey) {
+ if (dataLen == 0 && wsc.from == null) {
+ var minMax = wsc.range(self, null, null, k);
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+ }
+ else {
+ wsc.min = inf;
+ wsc.max = -inf;
+ }
+ }
+ }
+
+ if (dataLen > 0) {
+ // pre-range y-scales from y series' data values
+ series.forEach(function (s, i) {
+ var k = s.scale;
+ var wsc = wipScales[k];
+ var psc = pendScales[k];
+
+ if (i == 0) {
+ var minMax = wsc.range(self, wsc.min, wsc.max, k);
+
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+
+ i0 = closestIdx(wsc.min, data[0]);
+ i1 = closestIdx(wsc.max, data[0]);
+
+ // closest indices can be outside of view
+ if (data[0][i0] < wsc.min)
+ { i0++; }
+ if (data[0][i1] > wsc.max)
+ { i1--; }
+
+ s.min = data0[i0];
+ s.max = data0[i1];
+ }
+ else if (s.show && s.auto && wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {
+ // only run getMinMax() for invalidated series data, else reuse
+ var minMax$1 = s.min == null ? getMinMax(data[i], i0, i1, s.sorted) : [s.min, s.max];
+
+ // initial min/max
+ wsc.min = min(wsc.min, s.min = minMax$1[0]);
+ wsc.max = max(wsc.max, s.max = minMax$1[1]);
+ }
+
+ s.idxs[0] = i0;
+ s.idxs[1] = i1;
+ });
+
+ // range independent scales
+ for (var k$1 in wipScales) {
+ var wsc$1 = wipScales[k$1];
+ var psc$1 = pendScales[k$1];
+
+ if (wsc$1.from == null && (psc$1 == null || psc$1.min == null)) {
+ var minMax$1 = wsc$1.range(
+ self,
+ wsc$1.min == inf ? null : wsc$1.min,
+ wsc$1.max == -inf ? null : wsc$1.max,
+ k$1
+ );
+ wsc$1.min = minMax$1[0];
+ wsc$1.max = minMax$1[1];
+ }
+ }
+ }
+
+ // range dependent scales
+ for (var k$2 in wipScales) {
+ var wsc$2 = wipScales[k$2];
+
+ if (wsc$2.from != null) {
+ var base = wipScales[wsc$2.from];
+ var minMax$2 = wsc$2.range(self, base.min, base.max, k$2);
+ wsc$2.min = minMax$2[0];
+ wsc$2.max = minMax$2[1];
+ }
+ }
+
+ var changed = {};
+ var anyChanged = false;
+
+ for (var k$3 in wipScales) {
+ var wsc$3 = wipScales[k$3];
+ var sc = scales[k$3];
+
+ if (sc.min != wsc$3.min || sc.max != wsc$3.max) {
+ sc.min = wsc$3.min;
+ sc.max = wsc$3.max;
+ changed[k$3] = anyChanged = true;
+ }
+ }
+
+ if (anyChanged) {
+ // invalidate paths of all series on changed scales
+ series.forEach(function (s) {
+ if (changed[s.scale])
+ { s._paths = null; }
+ });
+
+ for (var k$4 in changed) {
+ shouldConvergeSize = true;
+ fire("setScale", k$4);
+ }
+
+ if ( cursor.show)
+ { shouldSetCursor = true; }
+ }
+
+ for (var k$5 in pendScales)
+ { pendScales[k$5] = null; }
+ }
+
+ // TODO: drawWrap(si, drawPoints) (save, restore, translate, clip)
+
+ function drawPoints(si) {
+ // log("drawPoints()", arguments);
+
+ var s = series[si];
+ var p = s.points;
+
+ var width = roundDec(p.width * pxRatio, 3);
+ var offset = (width % 2) / 2;
+ var isStroked = p.width > 0;
+
+ var rad = (p.size - p.width) / 2 * pxRatio;
+ var dia = roundDec(rad * 2, 3);
+
+ ctx.translate(offset, offset);
+
+ ctx.save();
+
+ ctx.beginPath();
+ ctx.rect(
+ plotLft - dia,
+ plotTop - dia,
+ plotWid + dia * 2,
+ plotHgt + dia * 2
+ );
+ ctx.clip();
+
+ ctx.globalAlpha = s.alpha;
+
+ var path = new Path2D();
+
+ for (var pi = i0; pi <= i1; pi++) {
+ if (data[si][pi] != null) {
+ var x = round(getXPos(data[0][pi], scales[xScaleKey], plotWid, plotLft));
+ var y = round(getYPos(data[si][pi], scales[s.scale], plotHgt, plotTop));
+
+ path.moveTo(x + rad, y);
+ path.arc(x, y, rad, 0, PI * 2);
+ }
+ }
+
+ setCtxStyle(
+ p.stroke,
+ width,
+ null,
+ p.fill || (isStroked ? "#fff" : s.stroke)
+ );
+
+ ctx.fill(path);
+ isStroked && ctx.stroke(path);
+
+ ctx.globalAlpha = 1;
+
+ ctx.restore();
+
+ ctx.translate(-offset, -offset);
+ }
+
+ // grabs the nearest indices with y data outside of x-scale limits
+ function getOuterIdxs(ydata) {
+ var _i0 = clamp(i0 - 1, 0, dataLen - 1);
+ var _i1 = clamp(i1 + 1, 0, dataLen - 1);
+
+ while (ydata[_i0] == null && _i0 > 0)
+ { _i0--; }
+
+ while (ydata[_i1] == null && _i1 < dataLen - 1)
+ { _i1++; }
+
+ return [_i0, _i1];
+ }
+
+ var dir = 1;
+
+ function drawSeries() {
+ // path building loop must be before draw loop to ensure that all bands are fully constructed
+ series.forEach(function (s, i) {
+ if (i > 0 && s.show && s._paths == null) {
+ var _idxs = getOuterIdxs(data[i]);
+ s._paths = s.paths(self, i, _idxs[0], _idxs[1]);
+ }
+ });
+
+ series.forEach(function (s, i) {
+ if (i > 0 && s.show) {
+ if (s._paths)
+ { drawPath(i); }
+
+ if (s.points.show(self, i, i0, i1))
+ { drawPoints(i); }
+
+ fire("drawSeries", i);
+ }
+ });
+ }
+
+ function drawPath(si) {
+ var s = series[si];
+
+ if (dir == 1) {
+ var ref = s._paths;
+ var stroke = ref.stroke;
+ var fill = ref.fill;
+ var clip = ref.clip;
+ var width = roundDec(s[WIDTH] * pxRatio, 3);
+ var offset = (width % 2) / 2;
+
+ setCtxStyle(s.stroke, width, s.dash, s.fill);
+
+ ctx.globalAlpha = s.alpha;
+
+ ctx.translate(offset, offset);
+
+ ctx.save();
+
+ var lft = plotLft,
+ top = plotTop,
+ wid = plotWid,
+ hgt = plotHgt;
+
+ var halfWid = width * pxRatio / 2;
+
+ if (s.min == 0)
+ { hgt += halfWid; }
+
+ if (s.max == 0) {
+ top -= halfWid;
+ hgt += halfWid;
+ }
+
+ ctx.beginPath();
+ ctx.rect(lft, top, wid, hgt);
+ ctx.clip();
+
+ if (clip != null)
+ { ctx.clip(clip); }
+
+ if (s.band) {
+ ctx.fill(stroke);
+ width && ctx.stroke(stroke);
+ }
+ else {
+ width && ctx.stroke(stroke);
+
+ if (s.fill != null)
+ { ctx.fill(fill); }
+ }
+
+ ctx.restore();
+
+ ctx.translate(-offset, -offset);
+
+ ctx.globalAlpha = 1;
+ }
+
+ if (s.band)
+ { dir *= -1; }
+ }
+
+ function buildClip(is, gaps, nullHead, nullTail) {
+ var s = series[is];
+
+ var clip = null;
+
+ // create clip path (invert gaps and non-gaps)
+ if (gaps.length > 0 && !s.spanGaps) {
+ clip = new Path2D();
+
+ var prevGapEnd = plotLft;
+
+ for (var i = 0; i < gaps.length; i++) {
+ var g = gaps[i];
+
+ clip.rect(prevGapEnd, plotTop, g[0] - prevGapEnd, plotTop + plotHgt);
+
+ prevGapEnd = g[1];
+ }
+
+ clip.rect(prevGapEnd, plotTop, plotLft + plotWid - prevGapEnd, plotTop + plotHgt);
+ }
+
+ return clip;
+ }
+
+ function addGap(gaps, fromX, toX) {
+ if (toX > fromX) {
+ var prevGap = gaps[gaps.length - 1];
+
+ if (prevGap && prevGap[0] == fromX) // TODO: gaps must be encoded at stroke widths?
+ { prevGap[1] = toX; }
+ else
+ { gaps.push([fromX, toX]); }
+ }
+ }
+
+ function nonNullIdx(data, _i0, _i1, dir) {
+ for (var i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
+ if (data[i] != null)
+ { return i; }
+ }
+
+ return -1;
+ }
+
+ function buildPaths(self, is, _i0, _i1) {
+ var s = series[is];
+ var isGap = dataIsGap || s.isGap;
+
+ var xdata = data[0];
+ var ydata = data[is];
+ var scaleX = scales[xScaleKey];
+ var scaleY = scales[s.scale];
+
+ var _paths = dir == 1 ? {stroke: new Path2D(), fill: null, clip: null} : series[is-1]._paths;
+ var stroke = _paths.stroke;
+ var width = roundDec(s[WIDTH] * pxRatio, 3);
+
+ var minY = inf,
+ maxY = -inf,
+ outY, outX;
+
+ // todo: don't build gaps on dir = -1 pass
+ var gaps = [];
+
+ var accX = round(getXPos(xdata[dir == 1 ? _i0 : _i1], scaleX, plotWid, plotLft));
+ var accGaps = false;
+
+ // data edges
+ var lftIdx = nonNullIdx(ydata, _i0, _i1, 1);
+ var rgtIdx = nonNullIdx(ydata, _i0, _i1, -1);
+ var lftX = incrRound(getXPos(xdata[lftIdx], scaleX, plotWid, plotLft), 0.5);
+ var rgtX = incrRound(getXPos(xdata[rgtIdx], scaleX, plotWid, plotLft), 0.5);
+
+ if (lftX > plotLft)
+ { addGap(gaps, plotLft, lftX); }
+
+ // the moves the shape edge outside the canvas so stroke doesnt bleed in
+ if (s.band && dir == 1)
+ { stroke.lineTo(lftX - width * 2, round(getYPos(ydata[_i0], scaleY, plotHgt, plotTop))); }
+
+ for (var i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
+ var x = round(getXPos(xdata[i], scaleX, plotWid, plotLft));
+
+ if (x == accX) {
+ if (ydata[i] != null) {
+ outY = round(getYPos(ydata[i], scaleY, plotHgt, plotTop));
+ minY = min(outY, minY);
+ maxY = max(outY, maxY);
+ }
+ else if (!accGaps && isGap(self, is, i))
+ { accGaps = true; }
+ }
+ else {
+ var _addGap = false;
+
+ if (minY != inf) {
+ stroke.lineTo(accX, minY);
+ stroke.lineTo(accX, maxY);
+ stroke.lineTo(accX, outY);
+ outX = accX;
+ }
+ else if (accGaps) {
+ _addGap = true;
+ accGaps = false;
+ }
+
+ if (ydata[i] != null) {
+ outY = round(getYPos(ydata[i], scaleY, plotHgt, plotTop));
+ stroke.lineTo(x, outY);
+ minY = maxY = outY;
+
+ // prior pixel can have data but still start a gap if ends with null
+ if (x - accX > 1 && ydata[i-1] == null && isGap(self, is, i-1))
+ { _addGap = true; }
+ }
+ else {
+ minY = inf;
+ maxY = -inf;
+
+ if (!accGaps && isGap(self, is, i))
+ { accGaps = true; }
+ }
+
+ _addGap && addGap(gaps, outX, x);
+
+ accX = x;
+ }
+ }
+
+ if (rgtX < plotLft + plotWid)
+ { addGap(gaps, rgtX, plotLft + plotWid); }
+
+ if (s.band) {
+ var _x, _iy, ydata2;
+
+ // the moves the shape edge outside the canvas so stroke doesnt bleed in
+ if (dir == 1) {
+ _x = rgtX + width * 2;
+ _iy = rgtIdx;
+ ydata2 = data[is + 1];
+ }
+ else {
+ _x = lftX - width * 2;
+ _iy = lftIdx;
+ ydata2 = data[is - 1];
+ }
+
+ stroke.lineTo(_x, round(getYPos(ydata[_iy], scaleY, plotHgt, plotTop)));
+ stroke.lineTo(_x, round(getYPos(ydata2[_iy], scaleY, plotHgt, plotTop)));
+ }
+
+ if (dir == 1) {
+ _paths.clip = buildClip(is, gaps, ydata[_i0] == null, ydata[_i1] == null);
+
+ if (s.fill != null) {
+ var fill = _paths.fill = new Path2D(stroke);
+
+ var fillTo = round(getYPos(s.fillTo(self, is, s.min, s.max), scaleY, plotHgt, plotTop));
+ fill.lineTo(rgtX, fillTo);
+ fill.lineTo(lftX, fillTo);
+ }
+ }
+
+ if (s.band)
+ { dir *= -1; }
+
+ return _paths;
+ }
+
+ self.paths = buildPaths;
+
+ function getIncrSpace(axisIdx, min, max, fullDim) {
+ var axis = axes[axisIdx];
+
+ var incrSpace;
+
+ if (fullDim <= 0)
+ { incrSpace = [0, 0]; }
+ else {
+ var minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);
+ var incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);
+ incrSpace = axis._found = findIncr(min, max, incrs, fullDim, minSpace);
+ }
+
+ return incrSpace;
+ }
+
+ function drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash) {
+ var offset = (width % 2) / 2;
+
+ ctx.translate(offset, offset);
+
+ setCtxStyle(stroke, width, dash);
+
+ ctx.beginPath();
+
+ var x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);
+
+ if (ori == 0) {
+ y0 = pos0;
+ y1 = pos1;
+ }
+ else {
+ x0 = pos0;
+ x1 = pos1;
+ }
+
+ offs.forEach(function (off, i) {
+ if (filts[i] == null)
+ { return; }
+
+ if (ori == 0)
+ { x0 = x1 = off; }
+ else
+ { y0 = y1 = off; }
+
+ ctx.moveTo(x0, y0);
+ ctx.lineTo(x1, y1);
+ });
+
+ ctx.stroke();
+
+ ctx.translate(-offset, -offset);
+ }
+
+ function axesCalc() {
+ // log("axesCalc()", arguments);
+
+ var converged = true;
+
+ axes.forEach(function (axis, i) {
+ if (!axis.show)
+ { return; }
+
+ var scale = scales[axis.scale];
+
+ if (scale.min == null) {
+ if (axis._show) {
+ converged = false;
+ axis._show = false;
+ resetYSeries(false);
+ }
+ return;
+ }
+ else {
+ if (!axis._show) {
+ converged = false;
+ axis._show = true;
+ resetYSeries(false);
+ }
+ }
+
+ var side = axis.side;
+ var ori = side % 2;
+
+ var min = scale.min;
+ var max = scale.max; // // should this toggle them ._show = false
+
+ var ref = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);
+ var _incr = ref[0];
+ var _space = ref[1];
+
+ if (_space == 0)
+ { return; }
+
+ // if we're using index positions, force first tick to match passed index
+ var forceMin = scale.distr == 2;
+
+ var _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);
+
+ // tick labels
+ // BOO this assumes a specific data/series
+ var splits = scale.distr == 2 ? _splits.map(function (i) { return data0[i]; }) : _splits;
+ var incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
+
+ var values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);
+
+ // rotating of labels only supported on bottom x axis
+ axis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;
+
+ var oldSize = axis._size;
+
+ axis._size = axis.size(self, values, i);
+
+ if (oldSize != null && axis._size != oldSize) // ready && ?
+ { converged = false; }
+ });
+
+ return converged;
+ }
+
+ function guttersCalc() {
+ var converged = true;
+
+ var _x = gutters._x;
+ var _y = gutters._y;
+
+ gutters._x = gutters.x(self);
+ gutters._y = gutters.y(self);
+
+ if (gutters._x != _x || gutters._y != _y)
+ { converged = false; }
+
+ return converged;
+ }
+
+ function drawAxesGrid() {
+ axes.forEach(function (axis, i) {
+ if (!axis.show || !axis._show)
+ { return; }
+
+ var scale = scales[axis.scale];
+ var side = axis.side;
+ var ori = side % 2;
+
+ var getPos = ori == 0 ? getXPos : getYPos;
+ var plotDim = ori == 0 ? plotWid : plotHgt;
+ var plotOff = ori == 0 ? plotLft : plotTop;
+
+ var axisGap = round(axis.gap * pxRatio);
+
+ var ticks = axis.ticks;
+ var tickSize = ticks.show ? round(ticks.size * pxRatio) : 0;
+
+ var ref = axis._found;
+ var _incr = ref[0];
+ var _space = ref[1];
+ var _splits = axis._splits;
+
+ // tick labels
+ // BOO this assumes a specific data/series
+ var splits = scale.distr == 2 ? _splits.map(function (i) { return data0[i]; }) : _splits;
+ var incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
+
+ // rotating of labels only supported on bottom x axis
+ var angle = axis._rotate * -PI/180;
+
+ var basePos = round(axis._pos * pxRatio);
+ var shiftAmt = tickSize + axisGap;
+ var shiftDir = ori == 0 && side == 0 || ori == 1 && side == 3 ? -1 : 1;
+ var finalPos = basePos + shiftAmt * shiftDir;
+ var y = ori == 0 ? finalPos : 0;
+ var x = ori == 1 ? finalPos : 0;
+
+ ctx.font = axis.font[0];
+ ctx.fillStyle = axis.stroke || hexBlack; // rgba?
+ ctx.textAlign = axis.align == 1 ? LEFT :
+ axis.align == 2 ? RIGHT :
+ angle > 0 ? LEFT :
+ angle < 0 ? RIGHT :
+ ori == 0 ? "center" : side == 3 ? RIGHT : LEFT;
+ ctx.textBaseline = angle ||
+ ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM;
+
+ var lineHeight = axis.font[1] * lineMult;
+
+ var canOffs = _splits.map(function (val) { return round(getPos(val, scale, plotDim, plotOff)); });
+
+ axis._values.forEach(function (val, i) {
+ if (val == null)
+ { return; }
+
+ if (ori == 0)
+ { x = canOffs[i]; }
+ else
+ { y = canOffs[i]; }
+
+ (""+val).split(/\n/gm).forEach(function (text, j) {
+ if (angle) {
+ ctx.save();
+ ctx.translate(x, y + j * lineHeight);
+ ctx.rotate(angle);
+ ctx.fillText(text, 0, 0);
+ ctx.restore();
+ }
+ else
+ { ctx.fillText(text, x, y + j * lineHeight); }
+ });
+ });
+
+ // axis label
+ if (axis.label) {
+ ctx.save();
+
+ var baseLpos = round(axis._lpos * pxRatio);
+
+ if (ori == 1) {
+ x = y = 0;
+
+ ctx.translate(
+ baseLpos,
+ round(plotTop + plotHgt / 2)
+ );
+ ctx.rotate((side == 3 ? -PI : PI) / 2);
+
+ }
+ else {
+ x = round(plotLft + plotWid / 2);
+ y = baseLpos;
+ }
+
+ ctx.font = axis.labelFont[0];
+ // ctx.fillStyle = axis.labelStroke || hexBlack; // rgba?
+ ctx.textAlign = "center";
+ ctx.textBaseline = side == 2 ? TOP : BOTTOM;
+
+ ctx.fillText(axis.label, x, y);
+
+ ctx.restore();
+ }
+
+ // ticks
+ if (ticks.show) {
+ drawOrthoLines(
+ canOffs,
+ ticks.filter(self, splits, i, _space, incr),
+ ori,
+ side,
+ basePos,
+ tickSize,
+ roundDec(ticks[WIDTH] * pxRatio, 3),
+ ticks.stroke
+ );
+ }
+
+ // grid
+ var grid = axis.grid;
+
+ if (grid.show) {
+ drawOrthoLines(
+ canOffs,
+ grid.filter(self, splits, i, _space, incr),
+ ori,
+ ori == 0 ? 2 : 1,
+ ori == 0 ? plotTop : plotLft,
+ ori == 0 ? plotHgt : plotWid,
+ roundDec(grid[WIDTH] * pxRatio, 3),
+ grid.stroke,
+ grid.dash
+ );
+ }
+ });
+
+ fire("drawAxes");
+ }
+
+ function resetYSeries(minMax) {
+ // log("resetYSeries()", arguments);
+
+ series.forEach(function (s, i) {
+ if (i > 0) {
+ s._paths = null;
+
+ if (minMax) {
+ s.min = null;
+ s.max = null;
+ }
+ }
+ });
+ }
+
+ var queuedCommit = false;
+
+ // could do rAF instead of microTask, or Promose.resolve().then()
+ function commit() {
+ if (!queuedCommit) {
+ microTask(_commit);
+ queuedCommit = true;
+ }
+ }
+
+ function _commit() {
+ // log("_commit()", arguments);
+
+ if (shouldSetScales) {
+ setScales();
+ shouldSetScales = false;
+ }
+
+ if (shouldConvergeSize) {
+ convergeSize();
+ shouldConvergeSize = false;
+ }
+
+ if (shouldSetSize) {
+ setStylePx(under, LEFT, plotLftCss);
+ setStylePx(under, TOP, plotTopCss);
+ setStylePx(under, WIDTH, plotWidCss);
+ setStylePx(under, HEIGHT, plotHgtCss);
+
+ setStylePx(over, LEFT, plotLftCss);
+ setStylePx(over, TOP, plotTopCss);
+ setStylePx(over, WIDTH, plotWidCss);
+ setStylePx(over, HEIGHT, plotHgtCss);
+
+ setStylePx(wrap, WIDTH, fullWidCss);
+ setStylePx(wrap, HEIGHT, fullHgtCss);
+
+ can[WIDTH] = round(fullWidCss * pxRatio);
+ can[HEIGHT] = round(fullHgtCss * pxRatio);
+
+ syncRect();
+
+ fire("setSize");
+
+ shouldSetSize = false;
+ }
+
+ // if (shouldSetSelect) {
+ // TODO: update .u-select metrics (if visible)
+ // setStylePx(selectDiv, TOP, select[TOP] = 0);
+ // setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ // setStylePx(selectDiv, WIDTH, select[WIDTH] = 0);
+ // setStylePx(selectDiv, HEIGHT, select[HEIGHT] = 0);
+ // shouldSetSelect = false;
+ // }
+
+ if ( cursor.show && shouldSetCursor) {
+ updateCursor();
+ shouldSetCursor = false;
+ }
+
+ // if (true && legend.show && legend.live && shouldSetLegend) {}
+
+ if (fullWidCss > 0 && fullHgtCss > 0) {
+ ctx.clearRect(0, 0, can[WIDTH], can[HEIGHT]);
+ fire("drawClear");
+ drawAxesGrid();
+ dataLen > 0 && drawSeries();
+ fire("draw");
+ }
+
+ if (!ready) {
+ ready = true;
+ self.status = 1;
+
+ fire("ready");
+ }
+
+ queuedCommit = false;
+ }
+
+ self.redraw = function (rebuildPaths) {
+ if (rebuildPaths !== false)
+ { _setScale(xScaleKey, scales[xScaleKey].min, scales[xScaleKey].max); }
+ else
+ { commit(); }
+ };
+
+ // redraw() => setScale('x', scales.x.min, scales.x.max);
+
+ // explicit, never re-ranged (is this actually true? for x and y)
+ function setScale(key, opts) {
+ var sc = scales[key];
+
+ if (sc.from == null) {
+ if (dataLen == 0) {
+ var minMax = sc.range(self, opts.min, opts.max, key);
+ opts.min = minMax[0];
+ opts.max = minMax[1];
+ }
+
+ if (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)
+ { return; }
+
+ if (key == xScaleKey) {
+ if (sc.distr == 2 && dataLen > 0) {
+ opts.min = closestIdx(opts.min, data[0]);
+ opts.max = closestIdx(opts.max, data[0]);
+ }
+ }
+
+ // log("setScale()", arguments);
+
+ pendScales[key] = opts;
+
+ shouldSetScales = true;
+ commit();
+ }
+ }
+
+ self.setScale = setScale;
+
+// INTERACTION
+
+ var vt;
+ var hz;
+
+ // starting position before cursor.move
+ var rawMouseLeft0;
+ var rawMouseTop0;
+
+ // starting position
+ var mouseLeft0;
+ var mouseTop0;
+
+ // current position before cursor.move
+ var rawMouseLeft1;
+ var rawMouseTop1;
+
+ // current position
+ var mouseLeft1;
+ var mouseTop1;
+
+ var dragging = false;
+
+ var drag = cursor.drag;
+
+ var dragX = drag.x;
+ var dragY = drag.y;
+
+ if ( cursor.show) {
+ if (cursor.x) {
+ mouseLeft1 = cursor.left;
+ vt = placeDiv(CURSOR_X, over);
+ }
+
+ if (cursor.y) {
+ mouseTop1 = cursor.top;
+ hz = placeDiv(CURSOR_Y, over);
+ }
+ }
+
+ var select = self.select = assign({
+ show: true,
+ over: true,
+ left: 0,
+ width: 0,
+ top: 0,
+ height: 0,
+ }, opts.select);
+
+ var selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;
+
+ function setSelect(opts, _fire) {
+ if (select.show) {
+ for (var prop in opts)
+ { setStylePx(selectDiv, prop, select[prop] = opts[prop]); }
+
+ _fire !== false && fire("setSelect");
+ }
+ }
+
+ self.setSelect = setSelect;
+
+ function toggleDOM(i, onOff) {
+ var s = series[i];
+ var label = showLegend ? legendRows[i][0].parentNode : null;
+
+ if (s.show)
+ { label && remClass(label, OFF); }
+ else {
+ label && addClass(label, OFF);
+ cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
+ }
+ }
+
+ function _setScale(key, min, max) {
+ setScale(key, {min: min, max: max});
+ }
+
+ function setSeries(i, opts, pub) {
+ // log("setSeries()", arguments);
+
+ var s = series[i];
+
+ // will this cause redundant commit() if both show and focus are set?
+ if (opts.focus != null)
+ { setFocus(i); }
+
+ if (opts.show != null) {
+ s.show = opts.show;
+ toggleDOM(i, opts.show);
+
+ if (s.band) {
+ // not super robust, will break if two bands are adjacent
+ var ip = series[i+1] && series[i+1].band ? i+1 : i-1;
+ series[ip].show = s.show;
+ toggleDOM(ip, opts.show);
+ }
+
+ _setScale(s.scale, null, null);
+ commit();
+ }
+
+ fire("setSeries", i, opts);
+
+ pub && sync.pub("setSeries", self, i, opts);
+ }
+
+ self.setSeries = setSeries;
+
+ function _alpha(i, value) {
+ series[i].alpha = value;
+
+ if ( cursor.show && cursorPts[i])
+ { cursorPts[i].style.opacity = value; }
+
+ if ( showLegend && legendRows[i])
+ { legendRows[i][0].parentNode.style.opacity = value; }
+ }
+
+ function _setAlpha(i, value) {
+ var s = series[i];
+
+ _alpha(i, value);
+
+ if (s.band) {
+ // not super robust, will break if two bands are adjacent
+ var ip = series[i+1].band ? i+1 : i-1;
+ _alpha(ip, value);
+ }
+ }
+
+ // y-distance
+ var closestDist;
+ var closestSeries;
+ var focusedSeries;
+
+ function setFocus(i) {
+ if (i != focusedSeries) {
+ // log("setFocus()", arguments);
+
+ series.forEach(function (s, i2) {
+ _setAlpha(i2, i == null || i2 == 0 || i2 == i ? 1 : focus.alpha);
+ });
+
+ focusedSeries = i;
+ commit();
+ }
+ }
+
+ if (showLegend && cursorFocus) {
+ on(mouseleave, legendEl, function (e) {
+ if (cursor._lock)
+ { return; }
+ setSeries(null, {focus: false}, syncOpts.setSeries);
+ updateCursor();
+ });
+ }
+
+ function scaleValueAtPos(pos, scale) {
+ var dim = plotWidCss;
+
+ if (scale != xScaleKey) {
+ dim = plotHgtCss;
+ pos = dim - pos;
+ }
+
+ var pct = pos / dim;
+
+ var sc = scales[scale],
+ _min = sc.min,
+ _max = sc.max;
+
+ if (sc.distr == 3) {
+ _min = log10(_min);
+ _max = log10(_max);
+ return pow(10, _min + (_max - _min) * pct);
+ }
+ else
+ { return _min + (_max - _min) * pct; }
+ }
+
+ function closestIdxFromXpos(pos) {
+ var v = scaleValueAtPos(pos, xScaleKey);
+ return closestIdx(v, data[0], i0, i1);
+ }
+
+ self.valToIdx = function (val) { return closestIdx(val, data[0]); };
+ self.posToIdx = closestIdxFromXpos;
+ self.posToVal = scaleValueAtPos;
+ self.valToPos = function (val, scale, can) { return (
+ scale == xScaleKey ?
+ getXPos(val, scales[scale],
+ can ? plotWid : plotWidCss,
+ can ? plotLft : 0
+ ) :
+ getYPos(val, scales[scale],
+ can ? plotHgt : plotHgtCss,
+ can ? plotTop : 0
+ )
+ ); };
+
+ // defers calling expensive functions
+ function batch(fn) {
+ fn(self);
+ commit();
+ }
+
+ self.batch = batch;
+
+ (self.setCursor = function (opts) {
+ mouseLeft1 = opts.left;
+ mouseTop1 = opts.top;
+ // assign(cursor, opts);
+ updateCursor();
+ });
+
+ var cursorRaf = 0;
+
+ function updateCursor(ts, src) {
+ var assign;
+
+ // ts == null && log("updateCursor()", arguments);
+
+ cursorRaf = 0;
+
+ rawMouseLeft1 = mouseLeft1;
+ rawMouseTop1 = mouseTop1;
+
+ (assign = cursor.move(self, mouseLeft1, mouseTop1), mouseLeft1 = assign[0], mouseTop1 = assign[1]);
+
+ if (cursor.show) {
+ cursor.x && trans(vt, round(mouseLeft1), 0, plotWidCss, plotHgtCss);
+ cursor.y && trans(hz, 0, round(mouseTop1), plotWidCss, plotHgtCss);
+ }
+
+ var idx;
+
+ // when zooming to an x scale range between datapoints the binary search
+ // for nearest min/max indices results in this condition. cheap hack :D
+ var noDataInRange = i0 > i1;
+
+ closestDist = inf;
+
+ // if cursor hidden, hide points & clear legend vals
+ if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {
+ idx = null;
+
+ for (var i = 0; i < series.length; i++) {
+ if (i > 0) {
+ cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
+ }
+
+ if (showLegend && legend.live) {
+ if (i == 0 && multiValLegend)
+ { continue; }
+
+ for (var j = 0; j < legendRows[i].length; j++)
+ { legendRows[i][j][firstChild].nodeValue = '--'; }
+ }
+ }
+
+ if (cursorFocus)
+ { setSeries(null, {focus: true}, syncOpts.setSeries); }
+ }
+ else {
+ // let pctY = 1 - (y / rect[HEIGHT]);
+
+ var valAtPos = scaleValueAtPos(mouseLeft1, xScaleKey);
+
+ idx = closestIdx(valAtPos, data[0], i0, i1);
+
+ var scX = scales[xScaleKey];
+
+ var xPos = roundDec(getXPos(data[0][idx], scX, plotWidCss, 0), 3);
+
+ for (var i$1 = 0; i$1 < series.length; i$1++) {
+ var s = series[i$1];
+
+ var idx2 = cursor.dataIdx(self, i$1, idx, valAtPos);
+ var xPos2 = idx2 == idx ? xPos : roundDec(getXPos(data[0][idx2], scX, plotWidCss, 0), 3);
+
+ if (i$1 > 0 && s.show) {
+ var valAtIdx = data[i$1][idx2];
+
+ var yPos = valAtIdx == null ? -10 : roundDec(getYPos(valAtIdx, scales[s.scale], plotHgtCss, 0), 3);
+
+ if (yPos > 0) {
+ var dist = abs(yPos - mouseTop1);
+
+ if (dist <= closestDist) {
+ closestDist = dist;
+ closestSeries = i$1;
+ }
+ }
+
+ cursorPts.length > 1 && trans(cursorPts[i$1], xPos2, yPos, plotWidCss, plotHgtCss);
+ }
+
+ if (showLegend && legend.live) {
+ if ((idx2 == cursor.idx && !shouldSetLegend) || i$1 == 0 && multiValLegend)
+ { continue; }
+
+ var src$1 = i$1 == 0 && xScaleDistr == 2 ? data0 : data[i$1];
+
+ var vals = multiValLegend ? s.values(self, i$1, idx2) : {_: s.value(self, src$1[idx2], i$1, idx2)};
+
+ var j$1 = 0;
+
+ for (var k in vals)
+ { legendRows[i$1][j$1++][firstChild].nodeValue = vals[k]; }
+ }
+ }
+
+ shouldSetLegend = false;
+ }
+
+ // nit: cursor.drag.setSelect is assumed always true
+ if (select.show && dragging) {
+ if (src != null) {
+ var ref = syncOpts.scales;
+ var xKey = ref[0];
+ var yKey = ref[1];
+
+ // match the dragX/dragY implicitness/explicitness of src
+ var sdrag = src.cursor.drag;
+ dragX = sdrag._x;
+ dragY = sdrag._y;
+
+ if (xKey) {
+ var sc = scales[xKey];
+ var srcLeft = src.posToVal(src.select[LEFT], xKey);
+ var srcRight = src.posToVal(src.select[LEFT] + src.select[WIDTH], xKey);
+
+ select[LEFT] = getXPos(srcLeft, sc, plotWidCss, 0);
+ select[WIDTH] = abs(select[LEFT] - getXPos(srcRight, sc, plotWidCss, 0));
+
+ setStylePx(selectDiv, LEFT, select[LEFT]);
+ setStylePx(selectDiv, WIDTH, select[WIDTH]);
+
+ if (!yKey) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (yKey) {
+ var sc$1 = scales[yKey];
+ var srcTop = src.posToVal(src.select[TOP], yKey);
+ var srcBottom = src.posToVal(src.select[TOP] + src.select[HEIGHT], yKey);
+
+ select[TOP] = getYPos(srcTop, sc$1, plotHgtCss, 0);
+ select[HEIGHT] = abs(select[TOP] - getYPos(srcBottom, sc$1, plotHgtCss, 0));
+
+ setStylePx(selectDiv, TOP, select[TOP]);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT]);
+
+ if (!xKey) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
+ }
+ else {
+ var rawDX = abs(rawMouseLeft1 - rawMouseLeft0);
+ var rawDY = abs(rawMouseTop1 - rawMouseTop0);
+
+ dragX = drag.x && rawDX >= drag.dist;
+ dragY = drag.y && rawDY >= drag.dist;
+
+ var uni = drag.uni;
+
+ if (uni != null) {
+ // only calc drag status if they pass the dist thresh
+ if (dragX && dragY) {
+ dragX = rawDX >= uni;
+ dragY = rawDY >= uni;
+
+ // force unidirectionality when both are under uni limit
+ if (!dragX && !dragY) {
+ if (rawDY > rawDX)
+ { dragY = true; }
+ else
+ { dragX = true; }
+ }
+ }
+ }
+ else if (drag.x && drag.y && (dragX || dragY))
+ // if omni with no uni then both dragX / dragY should be true if either is true
+ { dragX = dragY = true; }
+
+ if (dragX) {
+ var minX = min(mouseLeft0, mouseLeft1);
+ var dx = abs(mouseLeft1 - mouseLeft0);
+
+ setStylePx(selectDiv, LEFT, select[LEFT] = minX);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = dx);
+
+ if (!dragY) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (dragY) {
+ var minY = min(mouseTop0, mouseTop1);
+ var dy = abs(mouseTop1 - mouseTop0);
+
+ setStylePx(selectDiv, TOP, select[TOP] = minY);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = dy);
+
+ if (!dragX) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
+
+ if (!dragX && !dragY) {
+ // the drag didn't pass the dist requirement
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = 0);
+ }
+ }
+ }
+
+ cursor.idx = idx;
+ cursor.left = mouseLeft1;
+ cursor.top = mouseTop1;
+ drag._x = dragX;
+ drag._y = dragY;
+
+ // if ts is present, means we're implicitly syncing own cursor as a result of debounced rAF
+ if (ts != null) {
+ // this is not technically a "mousemove" event, since it's debounced, rename to setCursor?
+ // since this is internal, we can tweak it later
+ sync.pub(mousemove, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, idx);
+
+ if (cursorFocus) {
+ setSeries(closestDist <= focus.prox ? closestSeries : null, {focus: true}, syncOpts.setSeries);
+ }
+ }
+
+ ready && fire("setCursor");
+ }
+
+ var rect = null;
+
+ function syncRect() {
+ rect = over.getBoundingClientRect();
+ }
+
+ function mouseMove(e, src, _x, _y, _w, _h, _i) {
+ if (cursor._lock)
+ { return; }
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, false, e != null);
+
+ if (e != null) {
+ if (cursorRaf == 0)
+ { cursorRaf = rAF(updateCursor); }
+ }
+ else
+ { updateCursor(null, src); }
+ }
+
+ function cacheMouse(e, src, _x, _y, _w, _h, _i, initial, snap) {
+ var assign;
+
+ if (e != null) {
+ _x = e.clientX - rect.left;
+ _y = e.clientY - rect.top;
+ }
+ else {
+ if (_x < 0 || _y < 0) {
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+ return;
+ }
+
+ var ref = syncOpts.scales;
+ var xKey = ref[0];
+ var yKey = ref[1];
+
+ if (xKey != null)
+ { _x = getXPos(src.posToVal(_x, xKey), scales[xKey], plotWidCss, 0); }
+ else
+ { _x = plotWidCss * (_x/_w); }
+
+ if (yKey != null)
+ { _y = getYPos(src.posToVal(_y, yKey), scales[yKey], plotHgtCss, 0); }
+ else
+ { _y = plotHgtCss * (_y/_h); }
+ }
+
+ if (snap) {
+ if (_x <= 1 || _x >= plotWidCss - 1)
+ { _x = incrRound(_x, plotWidCss); }
+
+ if (_y <= 1 || _y >= plotHgtCss - 1)
+ { _y = incrRound(_y, plotHgtCss); }
+ }
+
+ if (initial) {
+ rawMouseLeft0 = _x;
+ rawMouseTop0 = _y;
+
+ (assign = cursor.move(self, _x, _y), mouseLeft0 = assign[0], mouseTop0 = assign[1]);
+ }
+ else {
+ mouseLeft1 = _x;
+ mouseTop1 = _y;
+ }
+ }
+
+ function hideSelect() {
+ setSelect({
+ width: 0,
+ height: 0,
+ }, false);
+ }
+
+ function mouseDown(e, src, _x, _y, _w, _h, _i) {
+ dragging = true;
+ dragX = dragY = drag._x = drag._y = false;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, true, false);
+
+ if (e != null) {
+ onMouse(mouseup, doc, mouseUp);
+ sync.pub(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);
+ }
+ }
+
+ function mouseUp(e, src, _x, _y, _w, _h, _i) {
+ dragging = drag._x = drag._y = false;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, false, true);
+
+ var hasSelect = select[WIDTH] > 0 || select[HEIGHT] > 0;
+
+ hasSelect && setSelect(select);
+
+ if (drag.setScale && hasSelect) {
+ // if (syncKey != null) {
+ // dragX = drag.x;
+ // dragY = drag.y;
+ // }
+
+ if (dragX) {
+ _setScale(xScaleKey,
+ scaleValueAtPos(select[LEFT], xScaleKey),
+ scaleValueAtPos(select[LEFT] + select[WIDTH], xScaleKey)
+ );
+ }
+
+ if (dragY) {
+ for (var k in scales) {
+ var sc = scales[k];
+
+ if (k != xScaleKey && sc.from == null && sc.min != inf) {
+ _setScale(k,
+ scaleValueAtPos(select[TOP] + select[HEIGHT], k),
+ scaleValueAtPos(select[TOP], k)
+ );
+ }
+ }
+ }
+
+ hideSelect();
+ }
+ else if (cursor.lock) {
+ cursor._lock = !cursor._lock;
+
+ if (!cursor._lock)
+ { updateCursor(); }
+ }
+
+ if (e != null) {
+ offMouse(mouseup, doc);
+ sync.pub(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
+ }
+ }
+
+ function mouseLeave(e, src, _x, _y, _w, _h, _i) {
+ if (!cursor._lock) {
+ var _dragging = dragging;
+
+ if (dragging) {
+ // handle case when mousemove aren't fired all the way to edges by browser
+ var snapX = true;
+ var snapY = true;
+ var snapProx = 10;
+
+ if (dragX && dragY) {
+ // maybe omni corner snap
+ snapX = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;
+ snapY = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;
+ }
+
+ if (dragX && snapX) {
+ var dLft = mouseLeft1;
+ var dRgt = plotWidCss - mouseLeft1;
+
+ var xMin = min(dLft, dRgt);
+
+ if (xMin == dLft)
+ { mouseLeft1 = 0; }
+ if (xMin == dRgt)
+ { mouseLeft1 = plotWidCss; }
+ }
+
+ if (dragY && snapY) {
+ var dTop = mouseTop1;
+ var dBtm = plotHgtCss - mouseTop1;
+
+ var yMin = min(dTop, dBtm);
+
+ if (yMin == dTop)
+ { mouseTop1 = 0; }
+ if (yMin == dBtm)
+ { mouseTop1 = plotHgtCss; }
+ }
+
+ updateCursor(1);
+
+ dragging = false;
+ }
+
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+
+ // passing a non-null timestamp to force sync/mousemove event
+ updateCursor(1);
+
+ if (_dragging)
+ { dragging = _dragging; }
+ }
+ }
+
+ function dblClick(e, src, _x, _y, _w, _h, _i) {
+ autoScaleX();
+
+ hideSelect();
+
+ if (e != null)
+ { sync.pub(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null); }
+ }
+
+ // internal pub/sub
+ var events = {};
+
+ events[mousedown] = mouseDown;
+ events[mousemove] = mouseMove;
+ events[mouseup] = mouseUp;
+ events[dblclick] = dblClick;
+ events["setSeries"] = function (e, src, idx, opts) {
+ setSeries(idx, opts);
+ };
+
+ var deb;
+
+ if ( cursor.show) {
+ onMouse(mousedown, over, mouseDown);
+ onMouse(mousemove, over, mouseMove);
+ onMouse(mouseenter, over, syncRect);
+ // this has to be rAF'd so it always fires after the last queued/rAF'd updateCursor
+ onMouse(mouseleave, over, function (e) { rAF(mouseLeave); });
+
+ onMouse(dblclick, over, dblClick);
+
+ deb = debounce(syncRect, 100);
+
+ on(resize, win, deb);
+ on(scroll, win, deb);
+
+ self.syncRect = syncRect;
+ }
+
+ // external on/off
+ var hooks = self.hooks = opts.hooks || {};
+
+ function fire(evName, a1, a2) {
+ if (evName in hooks) {
+ hooks[evName].forEach(function (fn) {
+ fn.call(null, self, a1, a2);
+ });
+ }
+ }
+
+ (opts.plugins || []).forEach(function (p) {
+ for (var evName in p.hooks)
+ { hooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]); }
+ });
+
+ var syncOpts = assign({
+ key: null,
+ setSeries: false,
+ scales: [xScaleKey, null]
+ }, cursor.sync);
+
+ var syncKey = syncOpts.key;
+
+ var sync = (syncKey != null ? (syncs[syncKey] = syncs[syncKey] || _sync()) : _sync());
+
+ sync.sub(self);
+
+ function pub(type, src, x, y, w, h, i) {
+ events[type](null, src, x, y, w, h, i);
+ }
+
+ (self.pub = pub);
+
+ function destroy() {
+ sync.unsub(self);
+ off(resize, win, deb);
+ off(scroll, win, deb);
+ root.remove();
+ fire("destroy");
+ }
+
+ self.destroy = destroy;
+
+ function _init() {
+ fire("init", opts, data);
+
+ setData(data || opts.data, false);
+
+ if (pendScales[xScaleKey])
+ { setScale(xScaleKey, pendScales[xScaleKey]); }
+ else
+ { autoScaleX(); }
+
+ _setSize(opts[WIDTH], opts[HEIGHT]);
+
+ setSelect(select, false);
+ }
+
+ if (then) {
+ if (then instanceof HTMLElement) {
+ then.appendChild(root);
+ _init();
+ }
+ else
+ { then(self, _init); }
+ }
+ else
+ { _init(); }
+
+ return self;
+}
+
+uPlot.assign = assign;
+uPlot.fmtNum = fmtNum;
+uPlot.rangeNum = rangeNum;
+uPlot.rangeLog = rangeLog;
+
+{
+ uPlot.fmtDate = fmtDate;
+ uPlot.tzDate = tzDate;
+}
+
+module.exports = uPlot;
diff --git a/plugins/uplot/uPlot.esm.js b/plugins/uplot/uPlot.esm.js
new file mode 100644
index 000000000..21f55f5ee
--- /dev/null
+++ b/plugins/uplot/uPlot.esm.js
@@ -0,0 +1,3431 @@
+/**
+* Copyright (c) 2020, Leon Sorokin
+* All rights reserved. (MIT Licensed)
+*
+* uPlot.js (μPlot)
+* A small, fast chart for time series, lines, areas, ohlc & bars
+* https://github.com/leeoniya/uPlot (v1.4.4)
+*/
+
+function debounce(fn, time) {
+ let pending = null;
+
+ function run() {
+ pending = null;
+ fn();
+ }
+
+ return function() {
+ clearTimeout(pending);
+ pending = setTimeout(run, time);
+ }
+}
+
+// binary search for index of closest value
+function closestIdx(num, arr, lo, hi) {
+ let mid;
+ lo = lo || 0;
+ hi = hi || arr.length - 1;
+ let bitwise = hi <= 2147483647;
+
+ while (hi - lo > 1) {
+ mid = bitwise ? (lo + hi) >> 1 : floor((lo + hi) / 2);
+
+ if (arr[mid] < num)
+ lo = mid;
+ else
+ hi = mid;
+ }
+
+ if (num - arr[lo] <= arr[hi] - num)
+ return lo;
+
+ return hi;
+}
+
+function getMinMax(data, _i0, _i1, sorted) {
+// console.log("getMinMax()");
+
+ let _min = inf;
+ let _max = -inf;
+
+ if (sorted == 1) {
+ _min = data[_i0];
+ _max = data[_i1];
+ }
+ else if (sorted == -1) {
+ _min = data[_i1];
+ _max = data[_i0];
+ }
+ else {
+ for (let i = _i0; i <= _i1; i++) {
+ if (data[i] != null) {
+ _min = min(_min, data[i]);
+ _max = max(_max, data[i]);
+ }
+ }
+ }
+
+ return [_min, _max];
+}
+
+const _fixedTuple = [0, 0];
+
+function fixIncr(minIncr, maxIncr, minExp, maxExp) {
+ _fixedTuple[0] = minExp < 0 ? roundDec(minIncr, -minExp) : minIncr;
+ _fixedTuple[1] = maxExp < 0 ? roundDec(maxIncr, -maxExp) : maxIncr;
+ return _fixedTuple;
+}
+
+function rangeLog(min, max, base, fullMags) {
+ let logFn = base == 10 ? log10 : log2;
+
+ if (min == max) {
+ min /= base;
+ max *= base;
+ }
+
+ let minExp, maxExp, minMaxIncrs;
+
+ if (fullMags) {
+ minExp = floor(logFn(min));
+ maxExp = ceil(logFn(max));
+
+ minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
+
+ min = minMaxIncrs[0];
+ max = minMaxIncrs[1];
+ }
+ else {
+ minExp = floor(logFn(min));
+ maxExp = floor(logFn(max));
+
+ minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
+
+ min = incrRoundDn(min, minMaxIncrs[0]);
+ max = incrRoundUp(max, minMaxIncrs[1]);
+ }
+
+ return [min, max];
+}
+
+const _eqRangePart = {
+ pad: 0,
+ soft: null,
+ mode: 0,
+};
+
+const _eqRange = {
+ min: _eqRangePart,
+ max: _eqRangePart,
+};
+
+// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
+// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
+function rangeNum(_min, _max, mult, extra) {
+ if (isObj(mult))
+ return _rangeNum(_min, _max, mult);
+
+ _eqRangePart.pad = mult;
+ _eqRangePart.soft = extra ? 0 : null;
+ _eqRangePart.mode = extra ? 2 : 0;
+
+ return _rangeNum(_min, _max, _eqRange);
+}
+
+// nullish coalesce
+function ifNull(lh, rh) {
+ return lh == null ? rh : lh;
+}
+
+function _rangeNum(_min, _max, cfg) {
+ let cmin = cfg.min;
+ let cmax = cfg.max;
+
+ let padMin = ifNull(cmin.pad, 0);
+ let padMax = ifNull(cmax.pad, 0);
+
+ let hardMin = ifNull(cmin.hard, -inf);
+ let hardMax = ifNull(cmax.hard, inf);
+
+ let softMin = ifNull(cmin.soft, inf);
+ let softMax = ifNull(cmax.soft, -inf);
+
+ let softMinMode = ifNull(cmin.mode, 0);
+ let softMaxMode = ifNull(cmax.mode, 0);
+
+ let delta = _max - _min;
+ let nonZeroDelta = delta || abs(_max) || 1e3;
+ let mag = log10(nonZeroDelta);
+ let base = pow(10, floor(mag));
+
+ let _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? .1 : 1) : padMin);
+ let _newMin = roundDec(incrRoundDn(_min - _padMin, base/100), 6);
+ let _softMin = _min >= softMin && (softMinMode == 1 || softMinMode == 2 && _newMin < softMin) ? softMin : inf;
+ let minLim = max(hardMin, _newMin < _softMin && _min >= _softMin ? _softMin : min(_softMin, _newMin));
+
+ let _padMax = nonZeroDelta * (delta == 0 ? (_max == 0 ? .1 : 1) : padMax);
+ let _newMax = roundDec(incrRoundUp(_max + _padMax, base/100), 6);
+ let _softMax = _max <= softMax && (softMaxMode == 1 || softMaxMode == 2 && _newMax > softMax) ? softMax : -inf;
+ let maxLim = min(hardMax, _newMax > _softMax && _max <= _softMax ? _softMax : max(_softMax, _newMax));
+
+ if (minLim == maxLim && minLim == 0)
+ maxLim = 100;
+
+ return [minLim, maxLim];
+}
+
+// alternative: https://stackoverflow.com/a/2254896
+const fmtNum = new Intl.NumberFormat(navigator.language).format;
+
+const M = Math;
+
+const abs = M.abs;
+const floor = M.floor;
+const round = M.round;
+const ceil = M.ceil;
+const min = M.min;
+const max = M.max;
+const pow = M.pow;
+const log10 = M.log10;
+const log2 = M.log2;
+const PI = M.PI;
+
+const inf = Infinity;
+
+function incrRound(num, incr) {
+ return round(num/incr)*incr;
+}
+
+function clamp(num, _min, _max) {
+ return min(max(num, _min), _max);
+}
+
+function fnOrSelf(v) {
+ return typeof v == "function" ? v : () => v;
+}
+
+function retArg1(_0, _1) {
+ return _1;
+}
+
+function incrRoundUp(num, incr) {
+ return ceil(num/incr)*incr;
+}
+
+function incrRoundDn(num, incr) {
+ return floor(num/incr)*incr;
+}
+
+function roundDec(val, dec) {
+ return round(val * (dec = 10**dec)) / dec;
+}
+
+const fixedDec = new Map();
+
+function guessDec(num) {
+ return ((""+num).split(".")[1] || "").length;
+}
+
+function genIncrs(base, minExp, maxExp, mults) {
+ let incrs = [];
+
+ let multDec = mults.map(guessDec);
+
+ for (let exp = minExp; exp < maxExp; exp++) {
+ let expa = abs(exp);
+ let mag = roundDec(pow(base, exp), expa);
+
+ for (let i = 0; i < mults.length; i++) {
+ let _incr = mults[i] * mag;
+ let dec = (_incr >= 0 && exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]);
+ let incr = roundDec(_incr, dec);
+ incrs.push(incr);
+ fixedDec.set(incr, dec);
+ }
+ }
+
+ return incrs;
+}
+
+//export const assign = Object.assign;
+
+const EMPTY_OBJ = {};
+
+const isArr = Array.isArray;
+
+function isStr(v) {
+ return typeof v === 'string';
+}
+
+function isObj(v) {
+ return typeof v === 'object' && v !== null;
+}
+
+function copy(o) {
+ let out;
+
+ if (isArr(o))
+ out = o.map(copy);
+ else if (isObj(o)) {
+ out = {};
+ for (var k in o)
+ out[k] = copy(o[k]);
+ }
+ else
+ out = o;
+
+ return out;
+}
+
+function assign(targ) {
+ let args = arguments;
+
+ for (let i = 1; i < args.length; i++) {
+ let src = args[i];
+
+ for (let key in src) {
+ if (isObj(targ[key]))
+ assign(targ[key], copy(src[key]));
+ else
+ targ[key] = copy(src[key]);
+ }
+ }
+
+ return targ;
+}
+
+const microTask = typeof queueMicrotask == "undefined" ? fn => Promise.resolve().then(fn) : queueMicrotask;
+
+const WIDTH = "width";
+const HEIGHT = "height";
+const TOP = "top";
+const BOTTOM = "bottom";
+const LEFT = "left";
+const RIGHT = "right";
+const firstChild = "firstChild";
+const createElement = "createElement";
+const hexBlack = "#000";
+const transparent = hexBlack + "0";
+const classList = "classList";
+
+const mousemove = "mousemove";
+const mousedown = "mousedown";
+const mouseup = "mouseup";
+const mouseenter = "mouseenter";
+const mouseleave = "mouseleave";
+const dblclick = "dblclick";
+const resize = "resize";
+const scroll = "scroll";
+
+const pre = "u-";
+
+const UPLOT = "uplot";
+const TITLE = pre + "title";
+const WRAP = pre + "wrap";
+const UNDER = pre + "under";
+const OVER = pre + "over";
+const OFF = pre + "off";
+const SELECT = pre + "select";
+const CURSOR_X = pre + "cursor-x";
+const CURSOR_Y = pre + "cursor-y";
+const CURSOR_PT = pre + "cursor-pt";
+const LEGEND = pre + "legend";
+const LEGEND_LIVE = pre + "live";
+const LEGEND_INLINE = pre + "inline";
+const LEGEND_THEAD = pre + "thead";
+const LEGEND_SERIES = pre + "series";
+const LEGEND_MARKER = pre + "marker";
+const LEGEND_LABEL = pre + "label";
+const LEGEND_VALUE = pre + "value";
+
+const rAF = requestAnimationFrame;
+const doc = document;
+const win = window;
+const pxRatio = devicePixelRatio;
+
+function addClass(el, c) {
+ c != null && el[classList].add(c);
+}
+
+function remClass(el, c) {
+ el[classList].remove(c);
+}
+
+function setStylePx(el, name, value) {
+ el.style[name] = value + "px";
+}
+
+function placeTag(tag, cls, targ, refEl) {
+ let el = doc[createElement](tag);
+
+ if (cls != null)
+ addClass(el, cls);
+
+ if (targ != null)
+ targ.insertBefore(el, refEl);
+
+ return el;
+}
+
+function placeDiv(cls, targ) {
+ return placeTag("div", cls, targ);
+}
+
+function trans(el, xPos, yPos, xMax, yMax) {
+ el.style.transform = "translate(" + xPos + "px," + yPos + "px)";
+
+ if (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax)
+ addClass(el, OFF);
+ else
+ remClass(el, OFF);
+}
+
+const evOpts = {passive: true};
+
+function on(ev, el, cb) {
+ el.addEventListener(ev, cb, evOpts);
+}
+
+function off(ev, el, cb) {
+ el.removeEventListener(ev, cb, evOpts);
+}
+
+const months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+];
+
+const days = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+];
+
+function slice3(str) {
+ return str.slice(0, 3);
+}
+
+const days3 = days.map(slice3);
+
+const months3 = months.map(slice3);
+
+const engNames = {
+ MMMM: months,
+ MMM: months3,
+ WWWW: days,
+ WWW: days3,
+};
+
+function zeroPad2(int) {
+ return (int < 10 ? '0' : '') + int;
+}
+
+function zeroPad3(int) {
+ return (int < 10 ? '00' : int < 100 ? '0' : '') + int;
+}
+
+/*
+function suffix(int) {
+ let mod10 = int % 10;
+
+ return int + (
+ mod10 == 1 && int != 11 ? "st" :
+ mod10 == 2 && int != 12 ? "nd" :
+ mod10 == 3 && int != 13 ? "rd" : "th"
+ );
+}
+*/
+
+const getFullYear = 'getFullYear';
+const getMonth = 'getMonth';
+const getDate = 'getDate';
+const getDay = 'getDay';
+const getHours = 'getHours';
+const getMinutes = 'getMinutes';
+const getSeconds = 'getSeconds';
+const getMilliseconds = 'getMilliseconds';
+
+const subs = {
+ // 2019
+ YYYY: d => d[getFullYear](),
+ // 19
+ YY: d => (d[getFullYear]()+'').slice(2),
+ // July
+ MMMM: (d, names) => names.MMMM[d[getMonth]()],
+ // Jul
+ MMM: (d, names) => names.MMM[d[getMonth]()],
+ // 07
+ MM: d => zeroPad2(d[getMonth]()+1),
+ // 7
+ M: d => d[getMonth]()+1,
+ // 09
+ DD: d => zeroPad2(d[getDate]()),
+ // 9
+ D: d => d[getDate](),
+ // Monday
+ WWWW: (d, names) => names.WWWW[d[getDay]()],
+ // Mon
+ WWW: (d, names) => names.WWW[d[getDay]()],
+ // 03
+ HH: d => zeroPad2(d[getHours]()),
+ // 3
+ H: d => d[getHours](),
+ // 9 (12hr, unpadded)
+ h: d => {let h = d[getHours](); return h == 0 ? 12 : h > 12 ? h - 12 : h;},
+ // AM
+ AA: d => d[getHours]() >= 12 ? 'PM' : 'AM',
+ // am
+ aa: d => d[getHours]() >= 12 ? 'pm' : 'am',
+ // a
+ a: d => d[getHours]() >= 12 ? 'p' : 'a',
+ // 09
+ mm: d => zeroPad2(d[getMinutes]()),
+ // 9
+ m: d => d[getMinutes](),
+ // 09
+ ss: d => zeroPad2(d[getSeconds]()),
+ // 9
+ s: d => d[getSeconds](),
+ // 374
+ fff: d => zeroPad3(d[getMilliseconds]()),
+};
+
+function fmtDate(tpl, names) {
+ names = names || engNames;
+ let parts = [];
+
+ let R = /\{([a-z]+)\}|[^{]+/gi, m;
+
+ while (m = R.exec(tpl))
+ parts.push(m[0][0] == '{' ? subs[m[1]] : m[0]);
+
+ return d => {
+ let out = '';
+
+ for (let i = 0; i < parts.length; i++)
+ out += typeof parts[i] == "string" ? parts[i] : parts[i](d, names);
+
+ return out;
+ }
+}
+
+const localTz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+// https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone/53652131#53652131
+function tzDate(date, tz) {
+ let date2;
+
+ // perf optimization
+ if (tz == 'Etc/UTC')
+ date2 = new Date(+date + date.getTimezoneOffset() * 6e4);
+ else if (tz == localTz)
+ date2 = date;
+ else {
+ date2 = new Date(date.toLocaleString('en-US', {timeZone: tz}));
+ date2.setMilliseconds(date[getMilliseconds]());
+ }
+
+ return date2;
+}
+
+//export const series = [];
+
+// default formatters:
+
+const onlyWhole = v => v % 1 == 0;
+
+const allMults = [1,2,2.5,5];
+
+const wholeMults = allMults.filter(onlyWhole);
+
+// ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5
+const decIncrs = genIncrs(10, -16, 0, allMults);
+
+// 1, 2, 2.5, 5, 10, 20, 25, 50...
+const oneIncrs = genIncrs(10, 0, 16, allMults);
+
+// 1, 2, 5, 10, 20, 25, 50...
+const wholeIncrs = oneIncrs.filter(onlyWhole);
+
+const numIncrs = decIncrs.concat(oneIncrs);
+
+let s = 1,
+ m = 60,
+ h = m * m,
+ d = h * 24,
+ mo = d * 30,
+ y = d * 365;
+
+// min of 1e-3 prevents setting a temporal x ticks too small since Date objects cannot advance ticks smaller than 1ms
+const timeIncrs = genIncrs(10, -3, 0, wholeMults).concat([
+ // minute divisors (# of secs)
+ 1,
+ 5,
+ 10,
+ 15,
+ 30,
+ // hour divisors (# of mins)
+ m,
+ m * 5,
+ m * 10,
+ m * 15,
+ m * 30,
+ // day divisors (# of hrs)
+ h,
+ h * 2,
+ h * 3,
+ h * 4,
+ h * 6,
+ h * 8,
+ h * 12,
+ // month divisors TODO: need more?
+ d,
+ d * 2,
+ d * 3,
+ d * 4,
+ d * 5,
+ d * 6,
+ d * 7,
+ d * 8,
+ d * 9,
+ d * 10,
+ d * 15,
+ // year divisors (# months, approx)
+ mo,
+ mo * 2,
+ mo * 3,
+ mo * 4,
+ mo * 6,
+ // century divisors
+ y,
+ y * 2,
+ y * 5,
+ y * 10,
+ y * 25,
+ y * 50,
+ y * 100,
+]);
+
+// base 2
+const binIncrs = genIncrs(2, -53, 53, [1]);
+
+/*
+console.log({
+ decIncrs,
+ oneIncrs,
+ wholeIncrs,
+ numIncrs,
+ timeIncrs,
+ fixedDec,
+});
+*/
+
+function timeAxisStamps(stampCfg, fmtDate) {
+ return stampCfg.map(s => s.map((v, i) =>
+ i == 0 || i == 8 || v == null ? v : fmtDate(i == 1 || s[8] == 0 ? v : s[1] + v)
+ ));
+}
+
+const NL = "\n";
+
+const yyyy = "{YYYY}";
+const NLyyyy = NL + yyyy;
+const md = "{M}/{D}";
+const NLmd = NL + md;
+const NLmdyy = NLmd + "/{YY}";
+
+const aa = "{aa}";
+const hmm = "{h}:{mm}";
+const hmmaa = hmm + aa;
+const NLhmmaa = NL + hmmaa;
+const ss = ":{ss}";
+
+const _ = null;
+
+// [0]: minimum num secs in the tick incr
+// [1]: default tick format
+// [2-7]: rollover tick formats
+// [8]: mode: 0: replace [1] -> [2-7], 1: concat [1] + [2-7]
+const _timeAxisStamps = [
+// tick incr default year month day hour min sec mode
+ [y, yyyy, _, _, _, _, _, _, 1],
+ [d * 28, "{MMM}", NLyyyy, _, _, _, _, _, 1],
+ [d, md, NLyyyy, _, _, _, _, _, 1],
+ [h, "{h}" + aa, NLmdyy, _, NLmd, _, _, _, 1],
+ [m, hmmaa, NLmdyy, _, NLmd, _, _, _, 1],
+ [s, ss, NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
+ [1e-3, ss + ".{fff}", NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
+];
+
+// TODO: will need to accept spaces[] and pull incr into the loop when grid will be non-uniform, eg for log scales.
+// currently we ignore this for months since they're *nearly* uniform and the added complexity is not worth it
+function timeAxisVals(tzDate, stamps) {
+ return (self, splits, axisIdx, foundSpace, foundIncr) => {
+ let s = stamps.find(s => foundIncr >= s[0]) || stamps[stamps.length - 1];
+
+ // these track boundaries when a full label is needed again
+ let prevYear;
+ let prevMnth;
+ let prevDate;
+ let prevHour;
+ let prevMins;
+ let prevSecs;
+
+ return splits.map(split => {
+ let date = tzDate(split);
+
+ let newYear = date[getFullYear]();
+ let newMnth = date[getMonth]();
+ let newDate = date[getDate]();
+ let newHour = date[getHours]();
+ let newMins = date[getMinutes]();
+ let newSecs = date[getSeconds]();
+
+ let stamp = (
+ newYear != prevYear && s[2] ||
+ newMnth != prevMnth && s[3] ||
+ newDate != prevDate && s[4] ||
+ newHour != prevHour && s[5] ||
+ newMins != prevMins && s[6] ||
+ newSecs != prevSecs && s[7] ||
+ s[1]
+ );
+
+ prevYear = newYear;
+ prevMnth = newMnth;
+ prevDate = newDate;
+ prevHour = newHour;
+ prevMins = newMins;
+ prevSecs = newSecs;
+
+ return stamp(date);
+ });
+ }
+}
+
+// for when axis.values is defined as a static fmtDate template string
+function timeAxisVal(tzDate, dateTpl) {
+ let stamp = fmtDate(dateTpl);
+ return (self, splits, axisIdx, foundSpace, foundIncr) => splits.map(split => stamp(tzDate(split)));
+}
+
+function mkDate(y, m, d) {
+ return new Date(y, m, d);
+}
+
+// the ensures that axis ticks, values & grid are aligned to logical temporal breakpoints and not an arbitrary timestamp
+// https://www.timeanddate.com/time/dst/
+// https://www.timeanddate.com/time/dst/2019.html
+// https://www.epochconverter.com/timezones
+function timeAxisSplits(tzDate) {
+ return (self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) => {
+ let splits = [];
+ let isYr = foundIncr >= y;
+ let isMo = foundIncr >= mo && foundIncr < y;
+
+ // get the timezone-adjusted date
+ let minDate = tzDate(scaleMin);
+ let minDateTs = minDate / 1e3;
+
+ // get ts of 12am (this lands us at or before the original scaleMin)
+ let minMin = mkDate(minDate[getFullYear](), isYr ? 0 : minDate[getMonth](), isMo || isYr ? 1 : minDate[getDate]());
+ let minMinTs = minMin / 1e3;
+
+ if (isMo || isYr) {
+ let moIncr = isMo ? foundIncr / mo : 0;
+ let yrIncr = isYr ? foundIncr / y : 0;
+ // let tzOffset = scaleMin - minDateTs; // needed?
+ let split = minDateTs == minMinTs ? minDateTs : mkDate(minMin[getFullYear]() + yrIncr, minMin[getMonth]() + moIncr, 1) / 1e3;
+ let splitDate = new Date(split * 1e3);
+ let baseYear = splitDate[getFullYear]();
+ let baseMonth = splitDate[getMonth]();
+
+ for (let i = 0; split <= scaleMax; i++) {
+ let next = mkDate(baseYear + yrIncr * i, baseMonth + moIncr * i, 1);
+ let offs = next - tzDate(next / 1e3);
+
+ split = (+next + offs) / 1e3;
+
+ if (split <= scaleMax)
+ splits.push(split);
+ }
+ }
+ else {
+ let incr0 = foundIncr >= d ? d : foundIncr;
+ let tzOffset = floor(scaleMin) - floor(minDateTs);
+ let split = minMinTs + tzOffset + incrRoundUp(minDateTs - minMinTs, incr0);
+ splits.push(split);
+
+ let date0 = tzDate(split);
+
+ let prevHour = date0[getHours]() + (date0[getMinutes]() / m) + (date0[getSeconds]() / h);
+ let incrHours = foundIncr / h;
+
+ let minSpace = self.axes[axisIdx]._space;
+ let pctSpace = foundSpace / minSpace;
+
+ while (1) {
+ split = roundDec(split + foundIncr, 3);
+
+ if (split > scaleMax)
+ break;
+
+ if (incrHours > 1) {
+ let expectedHour = floor(roundDec(prevHour + incrHours, 6)) % 24;
+ let splitDate = tzDate(split);
+ let actualHour = splitDate.getHours();
+
+ let dstShift = actualHour - expectedHour;
+
+ if (dstShift > 1)
+ dstShift = -1;
+
+ split -= dstShift * h;
+
+ prevHour = (prevHour + incrHours) % 24;
+
+ // add a tick only if it's further than 70% of the min allowed label spacing
+ let prevSplit = splits[splits.length - 1];
+ let pctIncr = roundDec((split - prevSplit) / foundIncr, 3);
+
+ if (pctIncr * pctSpace >= .7)
+ splits.push(split);
+ }
+ else
+ splits.push(split);
+ }
+ }
+
+ return splits;
+ }
+}
+
+function timeSeriesStamp(stampCfg, fmtDate) {
+ return fmtDate(stampCfg);
+}
+const _timeSeriesStamp = '{YYYY}-{MM}-{DD} {h}:{mm}{aa}';
+
+function timeSeriesVal(tzDate, stamp) {
+ return (self, val) => stamp(tzDate(val));
+}
+
+function cursorPoint(self, si) {
+ let s = self.series[si];
+
+ let pt = placeDiv();
+
+ pt.style.background = s.stroke || hexBlack;
+
+ let dia = ptDia(s.width, 1);
+ let mar = (dia - 1) / -2;
+
+ setStylePx(pt, WIDTH, dia);
+ setStylePx(pt, HEIGHT, dia);
+ setStylePx(pt, "marginLeft", mar);
+ setStylePx(pt, "marginTop", mar);
+
+ return pt;
+}
+
+function dataIdx(self, seriesIdx, cursorIdx) {
+ return cursorIdx;
+}
+
+const moveTuple = [0,0];
+
+function cursorMove(self, mouseLeft1, mouseTop1) {
+ moveTuple[0] = mouseLeft1;
+ moveTuple[1] = mouseTop1;
+ return moveTuple;
+}
+
+function filtBtn0(self, targ, handle) {
+ return e => {
+ e.button == 0 && handle(e);
+ };
+}
+
+function passThru(self, targ, handle) {
+ return handle;
+}
+
+const cursorOpts = {
+ show: true,
+ x: true,
+ y: true,
+ lock: false,
+ move: cursorMove,
+ points: {
+ show: cursorPoint,
+ },
+
+ bind: {
+ mousedown: filtBtn0,
+ mouseup: filtBtn0,
+ click: filtBtn0,
+ dblclick: filtBtn0,
+
+ mousemove: passThru,
+ mouseleave: passThru,
+ mouseenter: passThru,
+ },
+
+ drag: {
+ setScale: true,
+ x: true,
+ y: false,
+ dist: 0,
+ uni: null,
+ _x: false,
+ _y: false,
+ },
+
+ focus: {
+ prox: -1,
+ },
+
+ left: -10,
+ top: -10,
+ idx: null,
+ dataIdx,
+};
+
+const grid = {
+ show: true,
+ stroke: "rgba(0,0,0,0.07)",
+ width: 2,
+// dash: [],
+ filter: retArg1,
+};
+
+const ticks = assign({}, grid, {size: 10});
+
+const font = '12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
+const labelFont = "bold " + font;
+const lineMult = 1.5; // font-size multiplier
+
+const xAxisOpts = {
+ show: true,
+ scale: "x",
+ space: 50,
+ gap: 5,
+ size: 50,
+ labelSize: 30,
+ labelFont,
+ side: 2,
+// class: "x-vals",
+// incrs: timeIncrs,
+// values: timeVals,
+// filter: retArg1,
+ grid,
+ ticks,
+ font,
+ rotate: 0,
+};
+
+const numSeriesLabel = "Value";
+const timeSeriesLabel = "Time";
+
+const xSeriesOpts = {
+ show: true,
+ scale: "x",
+ auto: false,
+ sorted: 1,
+// label: "Time",
+// value: v => stamp(new Date(v * 1e3)),
+
+ // internal caches
+ min: inf,
+ max: -inf,
+ idxs: [],
+};
+
+function numAxisVals(self, splits, axisIdx, foundSpace, foundIncr) {
+ return splits.map(v => v == null ? "" : fmtNum(v));
+}
+
+function numAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
+ let splits = [];
+
+ let numDec = fixedDec.get(foundIncr) || 0;
+
+ scaleMin = forceMin ? scaleMin : roundDec(incrRoundUp(scaleMin, foundIncr), numDec);
+
+ for (let val = scaleMin; val <= scaleMax; val = roundDec(val + foundIncr, numDec))
+ splits.push(Object.is(val, -0) ? 0 : val); // coalesces -0
+
+ return splits;
+}
+
+function logAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
+ const splits = [];
+
+ const logBase = self.scales[self.axes[axisIdx].scale].log;
+
+ const logFn = logBase == 10 ? log10 : log2;
+
+ const exp = floor(logFn(scaleMin));
+
+ foundIncr = pow(logBase, exp);
+
+ if (exp < 0)
+ foundIncr = roundDec(foundIncr, -exp);
+
+ let split = scaleMin;
+
+ do {
+ splits.push(split);
+ split = roundDec(split + foundIncr, fixedDec.get(foundIncr));
+
+ if (split >= foundIncr * logBase)
+ foundIncr = split;
+
+ } while (split <= scaleMax);
+
+ return splits;
+}
+
+const RE_ALL = /./;
+const RE_12357 = /[12357]/;
+const RE_125 = /[125]/;
+const RE_1 = /1/;
+
+function logAxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {
+ let axis = self.axes[axisIdx];
+ let scaleKey = axis.scale;
+
+ if (self.scales[scaleKey].log == 2)
+ return splits;
+
+ let valToPos = self.valToPos;
+
+ let minSpace = axis._space;
+
+ let _10 = valToPos(10, scaleKey);
+
+ let re = (
+ valToPos(9, scaleKey) - _10 >= minSpace ? RE_ALL :
+ valToPos(7, scaleKey) - _10 >= minSpace ? RE_12357 :
+ valToPos(5, scaleKey) - _10 >= minSpace ? RE_125 :
+ RE_1
+ );
+
+ return splits.map(v => re.test(v) ? v : null);
+}
+
+function numSeriesVal(self, val) {
+ return val == null ? "" : fmtNum(val);
+}
+
+const yAxisOpts = {
+ show: true,
+ scale: "y",
+ space: 30,
+ gap: 5,
+ size: 50,
+ labelSize: 30,
+ labelFont,
+ side: 3,
+// class: "y-vals",
+// incrs: numIncrs,
+// values: (vals, space) => vals,
+// filter: retArg1,
+ grid,
+ ticks,
+ font,
+ rotate: 0,
+};
+
+// takes stroke width
+function ptDia(width, mult) {
+ let dia = 3 + (width || 1) * 2;
+ return roundDec(dia * mult, 3);
+}
+
+function seriesPoints(self, si) {
+ const s = self.series[si];
+ const dia = ptDia(s.width, pxRatio);
+ let maxPts = self.bbox.width / (s.points.space * pxRatio);
+ let idxs = self.series[0].idxs;
+ return idxs[1] - idxs[0] <= maxPts;
+}
+
+function seriesFillTo(self, seriesIdx, dataMin, dataMax) {
+ let scale = self.scales[self.series[seriesIdx].scale];
+ return scale.distr == 3 ? scale.min : 0;
+}
+
+const ySeriesOpts = {
+ scale: "y",
+ auto: true,
+ sorted: 0,
+ show: true,
+ band: false,
+ spanGaps: false,
+ isGap: (self, seriesIdx, dataIdx) => true,
+ alpha: 1,
+ points: {
+ show: seriesPoints,
+ // stroke: "#000",
+ // fill: "#fff",
+ // width: 1,
+ // size: 10,
+ },
+// label: "Value",
+// value: v => v,
+ values: null,
+
+ // internal caches
+ min: inf,
+ max: -inf,
+ idxs: [],
+
+ path: null,
+ clip: null,
+};
+
+const xScaleOpts = {
+ time: true,
+ auto: true,
+ distr: 1,
+ log: 10,
+ min: null,
+ max: null,
+};
+
+const yScaleOpts = assign({}, xScaleOpts, {
+ time: false,
+});
+
+const syncs = {};
+
+function _sync(opts) {
+ let clients = [];
+
+ return {
+ sub(client) {
+ clients.push(client);
+ },
+ unsub(client) {
+ clients = clients.filter(c => c != client);
+ },
+ pub(type, self, x, y, w, h, i) {
+ if (clients.length > 1) {
+ clients.forEach(client => {
+ client != self && client.pub(type, self, x, y, w, h, i);
+ });
+ }
+ }
+ };
+}
+
+function setDefaults(d, xo, yo, initY) {
+ let d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));
+ return d2.map((o, i) => setDefault(o, i, xo, yo));
+}
+
+function setDefault(o, i, xo, yo) {
+ return assign({}, (i == 0 || o && o.side % 2 == 0 ? xo : yo), o);
+}
+
+function getValPct(val, scale) {
+ return (
+ scale.distr == 3
+ ? log10(val / scale.min) / log10(scale.max / scale.min)
+ : (val - scale.min) / (scale.max - scale.min)
+ );
+}
+
+function getYPos(val, scale, hgt, top) {
+ let pctY = getValPct(val, scale);
+ return top + (1 - pctY) * hgt;
+}
+
+function getXPos(val, scale, wid, lft) {
+ let pctX = getValPct(val, scale);
+ return lft + pctX * wid;
+}
+
+const nullMinMax = [null, null];
+
+function snapNumX(self, dataMin, dataMax) {
+ return dataMin == null ? nullMinMax : [dataMin, dataMax];
+}
+
+const snapTimeX = snapNumX;
+
+// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
+// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
+function snapNumY(self, dataMin, dataMax) {
+ return dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, 0.1, true);
+}
+
+function snapLogY(self, dataMin, dataMax, scale) {
+ return dataMin == null ? nullMinMax : rangeLog(dataMin, dataMax, self.scales[scale].log, false);
+}
+
+const snapLogX = snapLogY;
+
+// dim is logical (getClientBoundingRect) pixels, not canvas pixels
+function findIncr(min, max, incrs, dim, minSpace) {
+ let pxPerUnit = dim / (max - min);
+
+ let minDec = (""+floor(min)).length;
+
+ for (var i = 0; i < incrs.length; i++) {
+ let space = incrs[i] * pxPerUnit;
+
+ let incrDec = incrs[i] < 10 ? fixedDec.get(incrs[i]) : 0;
+
+ if (space >= minSpace && minDec + incrDec < 17)
+ return [incrs[i], space];
+ }
+
+ return [0, 0];
+}
+
+function pxRatioFont(font) {
+ let fontSize;
+ font = font.replace(/(\d+)px/, (m, p1) => (fontSize = round(p1 * pxRatio)) + 'px');
+ return [font, fontSize];
+}
+
+function uPlot(opts, data, then) {
+ const self = {};
+
+ let ready = false;
+ self.status = 0;
+
+ const root = self.root = placeDiv(UPLOT);
+
+ if (opts.id != null)
+ root.id = opts.id;
+
+ addClass(root, opts.class);
+
+ if (opts.title) {
+ let title = placeDiv(TITLE, root);
+ title.textContent = opts.title;
+ }
+
+ const can = placeTag("canvas");
+ const ctx = self.ctx = can.getContext("2d");
+
+ const wrap = placeDiv(WRAP, root);
+ const under = placeDiv(UNDER, wrap);
+ wrap.appendChild(can);
+ const over = placeDiv(OVER, wrap);
+
+ opts = copy(opts);
+
+ (opts.plugins || []).forEach(p => {
+ if (p.opts)
+ opts = p.opts(self, opts) || opts;
+ });
+
+
+
+ const series = self.series = setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false);
+ const axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);
+ const scales = self.scales = {};
+
+ const xScaleKey = series[0].scale;
+
+ function initScale(scaleKey) {
+ let sc = scales[scaleKey];
+
+ if (sc == null) {
+ let scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;
+
+ if (scaleOpts.from != null) {
+ // ensure parent is initialized
+ initScale(scaleOpts.from);
+ // dependent scales inherit
+ scales[scaleKey] = assign({}, scales[scaleOpts.from], scaleOpts);
+ }
+ else {
+ sc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);
+
+ let isTime = sc.time;
+ let isLog = sc.distr == 3;
+
+ let rn = sc.range;
+
+ if (scaleKey != xScaleKey && !isArr(rn) && isObj(rn)) {
+ let cfg = rn;
+ // this is similar to snapNumY
+ rn = (self, dataMin, dataMax) => dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, cfg);
+ }
+
+ sc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ? (isLog ? snapLogX : snapNumX) : (isLog ? snapLogY : snapNumY)));
+
+ sc.auto = fnOrSelf(sc.auto);
+ }
+ }
+ }
+
+ initScale("x");
+ initScale("y");
+
+ series.forEach((s, i) => {
+ initScale(s.scale);
+ });
+
+ for (let k in opts.scales)
+ initScale(k);
+
+ const xScaleDistr = scales[xScaleKey].distr;
+
+ const pendScales = {};
+
+ // explicitly-set initial scales
+ for (let k in scales) {
+ let sc = scales[k];
+
+ if (sc.min != null || sc.max != null)
+ pendScales[k] = {min: sc.min, max: sc.max};
+ }
+
+ const gutters = self.gutters = assign({
+ x: round(yAxisOpts.size / 2),
+ y: round(xAxisOpts.size / 3),
+ _x: null,
+ _y: null,
+ }, opts.gutters);
+
+ gutters.x = fnOrSelf(gutters.x);
+ gutters.y = fnOrSelf(gutters.y);
+ gutters._x = gutters.x(self);
+ gutters._y = gutters.y(self);
+
+// self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
+ const _tzDate = (opts.tzDate || (ts => new Date(ts * 1e3)));
+ const _fmtDate = (opts.fmtDate || fmtDate);
+
+ const _timeAxisSplits = timeAxisSplits(_tzDate);
+ const _timeAxisVals = timeAxisVals(_tzDate, timeAxisStamps(_timeAxisStamps, _fmtDate));
+ const _timeSeriesVal = timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));
+
+ const legend = assign({show: true, live: true}, opts.legend);
+ const showLegend = legend.show;
+
+ let legendEl;
+ let legendRows = [];
+ let legendCols;
+ let multiValLegend = false;
+
+ if (showLegend) {
+ legendEl = placeTag("table", LEGEND, root);
+
+ const getMultiVals = series[1] ? series[1].values : null;
+ multiValLegend = getMultiVals != null;
+
+ if (multiValLegend) {
+ let head = placeTag("tr", LEGEND_THEAD, legendEl);
+ placeTag("th", null, head);
+ legendCols = getMultiVals(self, 1, 0);
+
+ for (var key in legendCols)
+ placeTag("th", LEGEND_LABEL, head).textContent = key;
+ }
+ else {
+ legendCols = {_: 0};
+ addClass(legendEl, LEGEND_INLINE);
+ legend.live && addClass(legendEl, LEGEND_LIVE);
+ }
+ }
+
+ function initLegendRow(s, i) {
+ if (i == 0 && (multiValLegend || !legend.live))
+ return null;
+
+ let _row = [];
+
+ let row = placeTag("tr", LEGEND_SERIES, legendEl, legendEl.childNodes[i]);
+
+ addClass(row, s.class);
+
+ if (!s.show)
+ addClass(row, OFF);
+
+ let label = placeTag("th", null, row);
+
+ let indic = placeDiv(LEGEND_MARKER, label);
+ indic.style.borderColor = s.width ? s.stroke : i > 0 && s.points.width ? s.points.stroke : null;
+ indic.style.backgroundColor = s.fill || null;
+
+ let text = placeDiv(LEGEND_LABEL, label);
+ text.textContent = s.label;
+
+ if (i > 0) {
+ onMouse("click", label, e => {
+ if ( cursor._lock)
+ return;
+
+ setSeries(series.indexOf(s), {show: !s.show}, syncOpts.setSeries);
+ });
+
+ if (cursorFocus) {
+ onMouse(mouseenter, label, e => {
+ if (cursor._lock)
+ return;
+
+ setSeries(series.indexOf(s), {focus: true}, syncOpts.setSeries);
+ });
+ }
+ }
+
+ for (var key in legendCols) {
+ let v = placeTag("td", LEGEND_VALUE, row);
+ v.textContent = "--";
+ _row.push(v);
+ }
+
+ return _row;
+ }
+
+ const mouseListeners = new Map();
+
+ function onMouse(ev, targ, fn) {
+ const targListeners = mouseListeners.get(targ) || {};
+ const listener = cursor.bind[ev](self, targ, fn);
+
+ if (listener) {
+ on(ev, targ, targListeners[ev] = listener);
+ mouseListeners.set(targ, targListeners);
+ }
+ }
+
+ function offMouse(ev, targ, fn) {
+ const targListeners = mouseListeners.get(targ) || {};
+ off(ev, targ, targListeners[ev]);
+ targListeners[ev] = null;
+ }
+
+ let fullWidCss = 0;
+ let fullHgtCss = 0;
+
+ let plotWidCss = 0;
+ let plotHgtCss = 0;
+
+ // plot margins to account for axes
+ let plotLftCss = 0;
+ let plotTopCss = 0;
+
+ let plotLft = 0;
+ let plotTop = 0;
+ let plotWid = 0;
+ let plotHgt = 0;
+
+ self.bbox = {};
+
+ let shouldSetScales = false;
+ let shouldSetSize = false;
+ let shouldConvergeSize = false;
+ let shouldSetCursor = false;
+ let shouldSetLegend = false;
+
+ function _setSize(width, height) {
+ if (width != self.width || height != self.height)
+ calcSize(width, height);
+
+ resetYSeries(false);
+
+ shouldConvergeSize = true;
+ shouldSetSize = true;
+ shouldSetCursor = true;
+ shouldSetLegend = true;
+ commit();
+ }
+
+ function calcSize(width, height) {
+ // log("calcSize()", arguments);
+
+ self.width = fullWidCss = plotWidCss = width;
+ self.height = fullHgtCss = plotHgtCss = height;
+ plotLftCss = plotTopCss = 0;
+
+ calcPlotRect();
+ calcAxesRects();
+
+ let bb = self.bbox;
+
+ plotLft = bb[LEFT] = incrRound(plotLftCss * pxRatio, 0.5);
+ plotTop = bb[TOP] = incrRound(plotTopCss * pxRatio, 0.5);
+ plotWid = bb[WIDTH] = incrRound(plotWidCss * pxRatio, 0.5);
+ plotHgt = bb[HEIGHT] = incrRound(plotHgtCss * pxRatio, 0.5);
+ }
+
+ function convergeSize() {
+ let converged = false;
+
+ while (!converged) {
+ let axesConverged = axesCalc();
+ let guttersConverged = guttersCalc();
+
+ converged = axesConverged && guttersConverged;
+
+ if (!converged) {
+ calcSize(self.width, self.height);
+ shouldSetSize = true;
+ }
+ }
+ }
+
+ function setSize({width, height}) {
+ _setSize(width, height);
+ }
+
+ self.setSize = setSize;
+
+ // accumulate axis offsets, reduce canvas width
+ function calcPlotRect() {
+ // easements for edge labels
+ let hasTopAxis = false;
+ let hasBtmAxis = false;
+ let hasRgtAxis = false;
+ let hasLftAxis = false;
+
+ axes.forEach((axis, i) => {
+ if (axis.show && axis._show) {
+ let {side, _size} = axis;
+ let isVt = side % 2;
+ let labelSize = axis.labelSize = (axis.label != null ? (axis.labelSize || 30) : 0);
+
+ let fullSize = _size + labelSize;
+
+ if (fullSize > 0) {
+ if (isVt) {
+ plotWidCss -= fullSize;
+
+ if (side == 3) {
+ plotLftCss += fullSize;
+ hasLftAxis = true;
+ }
+ else
+ hasRgtAxis = true;
+ }
+ else {
+ plotHgtCss -= fullSize;
+
+ if (side == 0) {
+ plotTopCss += fullSize;
+ hasTopAxis = true;
+ }
+ else
+ hasBtmAxis = true;
+ }
+ }
+ }
+ });
+
+ // hz gutters
+ if (hasTopAxis || hasBtmAxis) {
+ if (!hasRgtAxis)
+ plotWidCss -= gutters._x;
+ if (!hasLftAxis) {
+ plotWidCss -= gutters._x;
+ plotLftCss += gutters._x;
+ }
+ }
+
+ // vt gutters
+ if (hasLftAxis || hasRgtAxis) {
+ if (!hasBtmAxis)
+ plotHgtCss -= gutters._y;
+ if (!hasTopAxis) {
+ plotHgtCss -= gutters._y;
+ plotTopCss += gutters._y;
+ }
+ }
+ }
+
+ function calcAxesRects() {
+ // will accum +
+ let off1 = plotLftCss + plotWidCss;
+ let off2 = plotTopCss + plotHgtCss;
+ // will accum -
+ let off3 = plotLftCss;
+ let off0 = plotTopCss;
+
+ function incrOffset(side, size) {
+
+ switch (side) {
+ case 1: off1 += size; return off1 - size;
+ case 2: off2 += size; return off2 - size;
+ case 3: off3 -= size; return off3 + size;
+ case 0: off0 -= size; return off0 + size;
+ }
+ }
+
+ axes.forEach((axis, i) => {
+ if (axis.show && axis._show) {
+ let side = axis.side;
+
+ axis._pos = incrOffset(side, axis._size);
+
+ if (axis.label != null)
+ axis._lpos = incrOffset(side, axis.labelSize);
+ }
+ });
+ }
+
+ const cursor = (self.cursor = assign({}, cursorOpts, opts.cursor));
+
+ (cursor._lock = false);
+ (cursor.points.show = fnOrSelf(cursor.points.show));
+
+ const focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);
+ const cursorFocus = focus.prox >= 0;
+
+ // series-intersection markers
+ let cursorPts = [null];
+
+ function initCursorPt(s, si) {
+ if (si > 0) {
+ let pt = cursor.points.show(self, si);
+
+ if (pt) {
+ addClass(pt, CURSOR_PT);
+ addClass(pt, s.class);
+ trans(pt, -10, -10, plotWidCss, plotHgtCss);
+ over.insertBefore(pt, cursorPts[si]);
+
+ return pt;
+ }
+ }
+ }
+
+ function initSeries(s, i) {
+ let isTime = scales[s.scale].time;
+
+ let sv = s.value;
+ s.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;
+ s.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);
+
+ if (i > 0) {
+ s.width = s.width == null ? 1 : s.width;
+ s.paths = s.paths || ( buildPaths);
+ s.fillTo = s.fillTo || seriesFillTo;
+ let _ptDia = ptDia(s.width, 1);
+ s.points = assign({}, {
+ size: _ptDia,
+ width: max(1, _ptDia * .2),
+ stroke: s.stroke,
+ space: _ptDia * 2,
+ }, s.points);
+ s.points.show = fnOrSelf(s.points.show);
+ s._paths = null;
+ }
+
+ if (showLegend)
+ legendRows.splice(i, 0, initLegendRow(s, i));
+
+ if ( cursor.show) {
+ let pt = initCursorPt(s, i);
+ pt && cursorPts.splice(i, 0, pt);
+ }
+ }
+
+ function addSeries(opts, si) {
+ si = si == null ? series.length : si;
+
+ opts = setDefault(opts, si, xSeriesOpts, ySeriesOpts);
+ series.splice(si, 0, opts);
+ initSeries(series[si], si);
+ }
+
+ self.addSeries = addSeries;
+
+ function delSeries(i) {
+ series.splice(i, 1);
+ showLegend && legendRows.splice(i, 1)[0][0].parentNode.remove();
+ cursorPts.length > 1 && cursorPts.splice(i, 1)[0].remove();
+
+ // TODO: de-init no-longer-needed scales?
+ }
+
+ self.delSeries = delSeries;
+
+ series.forEach(initSeries);
+
+ function initAxis(axis, i) {
+ axis._show = axis.show;
+
+ if (axis.show) {
+ let isVt = axis.side % 2;
+
+ let sc = scales[axis.scale];
+
+ // this can occur if all series specify non-default scales
+ if (sc == null) {
+ axis.scale = isVt ? series[1].scale : xScaleKey;
+ sc = scales[axis.scale];
+ }
+
+ // also set defaults for incrs & values based on axis distr
+ let isTime = sc.time;
+
+ axis.size = fnOrSelf(axis.size);
+ axis.space = fnOrSelf(axis.space);
+ axis.rotate = fnOrSelf(axis.rotate);
+ axis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? timeIncrs : numIncrs)));
+ axis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : numAxisSplits));
+
+ let av = axis.values;
+ axis.values = (
+ isTime ? (
+ isArr(av) ?
+ timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :
+ isStr(av) ?
+ timeAxisVal(_tzDate, av) :
+ av || _timeAxisVals
+ ) : av || numAxisVals
+ );
+
+ axis.filter = fnOrSelf(axis.filter || ( sc.distr == 3 ? logAxisValsFilt : retArg1));
+
+ axis.font = pxRatioFont(axis.font);
+ axis.labelFont = pxRatioFont(axis.labelFont);
+
+ axis._size = axis.size(self, null, i);
+
+ axis._space =
+ axis._rotate =
+ axis._incrs =
+ axis._found = // foundIncrSpace
+ axis._splits =
+ axis._values = null;
+ }
+ }
+
+ // set axis defaults
+ axes.forEach(initAxis);
+
+ let dataLen;
+ let dataIsGap;
+
+ // rendered data window
+ let i0 = null;
+ let i1 = null;
+ const idxs = series[0].idxs;
+
+ let data0 = null;
+
+ let viaAutoScaleX = false;
+
+ function setData(_data, _resetScales) {
+ if (!isArr(_data) && isObj(_data)) {
+ dataIsGap = _data.isGap;
+ _data = _data.data;
+ }
+
+ _data = _data || [];
+ _data[0] = _data[0] || [];
+
+ self.data = _data;
+ data = _data.slice();
+ data0 = data[0];
+ dataLen = data0.length;
+
+ if (xScaleDistr == 2)
+ data[0] = data0.map((v, i) => i);
+
+ resetYSeries(true);
+
+ fire("setData");
+
+ if (_resetScales !== false) {
+ let xsc = scales[xScaleKey];
+
+ if (xsc.auto(self, viaAutoScaleX))
+ autoScaleX();
+ else
+ _setScale(xScaleKey, xsc.min, xsc.max);
+
+ shouldSetCursor = true;
+ shouldSetLegend = true;
+ commit();
+ }
+ }
+
+ self.setData = setData;
+
+ function autoScaleX() {
+ viaAutoScaleX = true;
+
+ let _min, _max;
+
+ if (dataLen > 0) {
+ i0 = idxs[0] = 0;
+ i1 = idxs[1] = dataLen - 1;
+
+ _min = data[0][i0];
+ _max = data[0][i1];
+
+ if (xScaleDistr == 2) {
+ _min = i0;
+ _max = i1;
+ }
+ else if (dataLen == 1) {
+ if (xScaleDistr == 3)
+ [_min, _max] = rangeLog(_min, _min, scales[xScaleKey].log, false);
+ else if (scales[xScaleKey].time)
+ _max = _min + 86400;
+ else
+ [_min, _max] = rangeNum(_min, _max, 0.1, true);
+ }
+ }
+ else {
+ i0 = idxs[0] = _min = null;
+ i1 = idxs[1] = _max = null;
+ }
+
+ _setScale(xScaleKey, _min, _max);
+
+ viaAutoScaleX = false;
+ }
+
+ function setCtxStyle(stroke, width, dash, fill) {
+ ctx.strokeStyle = stroke || transparent;
+ ctx.lineWidth = width;
+ ctx.lineJoin = "round";
+ ctx.setLineDash(dash || []);
+ ctx.fillStyle = fill || transparent;
+ }
+
+ function setScales() {
+ // log("setScales()", arguments);
+
+ // wip scales
+ let wipScales = copy(scales);
+
+ for (let k in wipScales) {
+ let wsc = wipScales[k];
+ let psc = pendScales[k];
+
+ if (psc != null && psc.min != null) {
+ assign(wsc, psc);
+
+ // explicitly setting the x-scale invalidates everything (acts as redraw)
+ if (k == xScaleKey)
+ resetYSeries(true);
+ }
+ else if (k != xScaleKey) {
+ if (dataLen == 0 && wsc.from == null) {
+ let minMax = wsc.range(self, null, null, k);
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+ }
+ else {
+ wsc.min = inf;
+ wsc.max = -inf;
+ }
+ }
+ }
+
+ if (dataLen > 0) {
+ // pre-range y-scales from y series' data values
+ series.forEach((s, i) => {
+ let k = s.scale;
+ let wsc = wipScales[k];
+ let psc = pendScales[k];
+
+ if (i == 0) {
+ let minMax = wsc.range(self, wsc.min, wsc.max, k);
+
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+
+ i0 = closestIdx(wsc.min, data[0]);
+ i1 = closestIdx(wsc.max, data[0]);
+
+ // closest indices can be outside of view
+ if (data[0][i0] < wsc.min)
+ i0++;
+ if (data[0][i1] > wsc.max)
+ i1--;
+
+ s.min = data0[i0];
+ s.max = data0[i1];
+ }
+ else if (s.show && s.auto && wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {
+ // only run getMinMax() for invalidated series data, else reuse
+ let minMax = s.min == null ? getMinMax(data[i], i0, i1, s.sorted) : [s.min, s.max];
+
+ // initial min/max
+ wsc.min = min(wsc.min, s.min = minMax[0]);
+ wsc.max = max(wsc.max, s.max = minMax[1]);
+ }
+
+ s.idxs[0] = i0;
+ s.idxs[1] = i1;
+ });
+
+ // range independent scales
+ for (let k in wipScales) {
+ let wsc = wipScales[k];
+ let psc = pendScales[k];
+
+ if (wsc.from == null && (psc == null || psc.min == null)) {
+ let minMax = wsc.range(
+ self,
+ wsc.min == inf ? null : wsc.min,
+ wsc.max == -inf ? null : wsc.max,
+ k
+ );
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+ }
+ }
+ }
+
+ // range dependent scales
+ for (let k in wipScales) {
+ let wsc = wipScales[k];
+
+ if (wsc.from != null) {
+ let base = wipScales[wsc.from];
+ let minMax = wsc.range(self, base.min, base.max, k);
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+ }
+ }
+
+ let changed = {};
+ let anyChanged = false;
+
+ for (let k in wipScales) {
+ let wsc = wipScales[k];
+ let sc = scales[k];
+
+ if (sc.min != wsc.min || sc.max != wsc.max) {
+ sc.min = wsc.min;
+ sc.max = wsc.max;
+ changed[k] = anyChanged = true;
+ }
+ }
+
+ if (anyChanged) {
+ // invalidate paths of all series on changed scales
+ series.forEach(s => {
+ if (changed[s.scale])
+ s._paths = null;
+ });
+
+ for (let k in changed) {
+ shouldConvergeSize = true;
+ fire("setScale", k);
+ }
+
+ if ( cursor.show)
+ shouldSetCursor = true;
+ }
+
+ for (let k in pendScales)
+ pendScales[k] = null;
+ }
+
+ // TODO: drawWrap(si, drawPoints) (save, restore, translate, clip)
+
+ function drawPoints(si) {
+ // log("drawPoints()", arguments);
+
+ let s = series[si];
+ let p = s.points;
+
+ const width = roundDec(p.width * pxRatio, 3);
+ const offset = (width % 2) / 2;
+ const isStroked = p.width > 0;
+
+ let rad = (p.size - p.width) / 2 * pxRatio;
+ let dia = roundDec(rad * 2, 3);
+
+ ctx.translate(offset, offset);
+
+ ctx.save();
+
+ ctx.beginPath();
+ ctx.rect(
+ plotLft - dia,
+ plotTop - dia,
+ plotWid + dia * 2,
+ plotHgt + dia * 2,
+ );
+ ctx.clip();
+
+ ctx.globalAlpha = s.alpha;
+
+ const path = new Path2D();
+
+ for (let pi = i0; pi <= i1; pi++) {
+ if (data[si][pi] != null) {
+ let x = round(getXPos(data[0][pi], scales[xScaleKey], plotWid, plotLft));
+ let y = round(getYPos(data[si][pi], scales[s.scale], plotHgt, plotTop));
+
+ path.moveTo(x + rad, y);
+ path.arc(x, y, rad, 0, PI * 2);
+ }
+ }
+
+ setCtxStyle(
+ p.stroke,
+ width,
+ null,
+ p.fill || (isStroked ? "#fff" : s.stroke),
+ );
+
+ ctx.fill(path);
+ isStroked && ctx.stroke(path);
+
+ ctx.globalAlpha = 1;
+
+ ctx.restore();
+
+ ctx.translate(-offset, -offset);
+ }
+
+ // grabs the nearest indices with y data outside of x-scale limits
+ function getOuterIdxs(ydata) {
+ let _i0 = clamp(i0 - 1, 0, dataLen - 1);
+ let _i1 = clamp(i1 + 1, 0, dataLen - 1);
+
+ while (ydata[_i0] == null && _i0 > 0)
+ _i0--;
+
+ while (ydata[_i1] == null && _i1 < dataLen - 1)
+ _i1++;
+
+ return [_i0, _i1];
+ }
+
+ let dir = 1;
+
+ function drawSeries() {
+ // path building loop must be before draw loop to ensure that all bands are fully constructed
+ series.forEach((s, i) => {
+ if (i > 0 && s.show && s._paths == null) {
+ let _idxs = getOuterIdxs(data[i]);
+ s._paths = s.paths(self, i, _idxs[0], _idxs[1]);
+ }
+ });
+
+ series.forEach((s, i) => {
+ if (i > 0 && s.show) {
+ if (s._paths)
+ drawPath(i);
+
+ if (s.points.show(self, i, i0, i1))
+ drawPoints(i);
+
+ fire("drawSeries", i);
+ }
+ });
+ }
+
+ function drawPath(si) {
+ const s = series[si];
+
+ if (dir == 1) {
+ const { stroke, fill, clip } = s._paths;
+ const width = roundDec(s[WIDTH] * pxRatio, 3);
+ const offset = (width % 2) / 2;
+
+ setCtxStyle(s.stroke, width, s.dash, s.fill);
+
+ ctx.globalAlpha = s.alpha;
+
+ ctx.translate(offset, offset);
+
+ ctx.save();
+
+ let lft = plotLft,
+ top = plotTop,
+ wid = plotWid,
+ hgt = plotHgt;
+
+ let halfWid = width * pxRatio / 2;
+
+ if (s.min == 0)
+ hgt += halfWid;
+
+ if (s.max == 0) {
+ top -= halfWid;
+ hgt += halfWid;
+ }
+
+ ctx.beginPath();
+ ctx.rect(lft, top, wid, hgt);
+ ctx.clip();
+
+ if (clip != null)
+ ctx.clip(clip);
+
+ if (s.band) {
+ ctx.fill(stroke);
+ width && ctx.stroke(stroke);
+ }
+ else {
+ width && ctx.stroke(stroke);
+
+ if (s.fill != null)
+ ctx.fill(fill);
+ }
+
+ ctx.restore();
+
+ ctx.translate(-offset, -offset);
+
+ ctx.globalAlpha = 1;
+ }
+
+ if (s.band)
+ dir *= -1;
+ }
+
+ function buildClip(is, gaps, nullHead, nullTail) {
+ let s = series[is];
+
+ let clip = null;
+
+ // create clip path (invert gaps and non-gaps)
+ if (gaps.length > 0 && !s.spanGaps) {
+ clip = new Path2D();
+
+ let prevGapEnd = plotLft;
+
+ for (let i = 0; i < gaps.length; i++) {
+ let g = gaps[i];
+
+ clip.rect(prevGapEnd, plotTop, g[0] - prevGapEnd, plotTop + plotHgt);
+
+ prevGapEnd = g[1];
+ }
+
+ clip.rect(prevGapEnd, plotTop, plotLft + plotWid - prevGapEnd, plotTop + plotHgt);
+ }
+
+ return clip;
+ }
+
+ function addGap(gaps, fromX, toX) {
+ if (toX > fromX) {
+ let prevGap = gaps[gaps.length - 1];
+
+ if (prevGap && prevGap[0] == fromX) // TODO: gaps must be encoded at stroke widths?
+ prevGap[1] = toX;
+ else
+ gaps.push([fromX, toX]);
+ }
+ }
+
+ function nonNullIdx(data, _i0, _i1, dir) {
+ for (let i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
+ if (data[i] != null)
+ return i;
+ }
+
+ return -1;
+ }
+
+ function buildPaths(self, is, _i0, _i1) {
+ const s = series[is];
+ const isGap = dataIsGap || s.isGap;
+
+ const xdata = data[0];
+ const ydata = data[is];
+ const scaleX = scales[xScaleKey];
+ const scaleY = scales[s.scale];
+
+ const _paths = dir == 1 ? {stroke: new Path2D(), fill: null, clip: null} : series[is-1]._paths;
+ const stroke = _paths.stroke;
+ const width = roundDec(s[WIDTH] * pxRatio, 3);
+
+ let minY = inf,
+ maxY = -inf,
+ outY, outX;
+
+ // todo: don't build gaps on dir = -1 pass
+ let gaps = [];
+
+ let accX = round(getXPos(xdata[dir == 1 ? _i0 : _i1], scaleX, plotWid, plotLft));
+ let accGaps = false;
+
+ // data edges
+ let lftIdx = nonNullIdx(ydata, _i0, _i1, 1);
+ let rgtIdx = nonNullIdx(ydata, _i0, _i1, -1);
+ let lftX = incrRound(getXPos(xdata[lftIdx], scaleX, plotWid, plotLft), 0.5);
+ let rgtX = incrRound(getXPos(xdata[rgtIdx], scaleX, plotWid, plotLft), 0.5);
+
+ if (lftX > plotLft)
+ addGap(gaps, plotLft, lftX);
+
+ // the moves the shape edge outside the canvas so stroke doesnt bleed in
+ if (s.band && dir == 1)
+ stroke.lineTo(lftX - width * 2, round(getYPos(ydata[_i0], scaleY, plotHgt, plotTop)));
+
+ for (let i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
+ let x = round(getXPos(xdata[i], scaleX, plotWid, plotLft));
+
+ if (x == accX) {
+ if (ydata[i] != null) {
+ outY = round(getYPos(ydata[i], scaleY, plotHgt, plotTop));
+ minY = min(outY, minY);
+ maxY = max(outY, maxY);
+ }
+ else if (!accGaps && isGap(self, is, i))
+ accGaps = true;
+ }
+ else {
+ let _addGap = false;
+
+ if (minY != inf) {
+ stroke.lineTo(accX, minY);
+ stroke.lineTo(accX, maxY);
+ stroke.lineTo(accX, outY);
+ outX = accX;
+ }
+ else if (accGaps) {
+ _addGap = true;
+ accGaps = false;
+ }
+
+ if (ydata[i] != null) {
+ outY = round(getYPos(ydata[i], scaleY, plotHgt, plotTop));
+ stroke.lineTo(x, outY);
+ minY = maxY = outY;
+
+ // prior pixel can have data but still start a gap if ends with null
+ if (x - accX > 1 && ydata[i-1] == null && isGap(self, is, i-1))
+ _addGap = true;
+ }
+ else {
+ minY = inf;
+ maxY = -inf;
+
+ if (!accGaps && isGap(self, is, i))
+ accGaps = true;
+ }
+
+ _addGap && addGap(gaps, outX, x);
+
+ accX = x;
+ }
+ }
+
+ if (rgtX < plotLft + plotWid)
+ addGap(gaps, rgtX, plotLft + plotWid);
+
+ if (s.band) {
+ let _x, _iy, ydata2;
+
+ // the moves the shape edge outside the canvas so stroke doesnt bleed in
+ if (dir == 1) {
+ _x = rgtX + width * 2;
+ _iy = rgtIdx;
+ ydata2 = data[is + 1];
+ }
+ else {
+ _x = lftX - width * 2;
+ _iy = lftIdx;
+ ydata2 = data[is - 1];
+ }
+
+ stroke.lineTo(_x, round(getYPos(ydata[_iy], scaleY, plotHgt, plotTop)));
+ stroke.lineTo(_x, round(getYPos(ydata2[_iy], scaleY, plotHgt, plotTop)));
+ }
+
+ if (dir == 1) {
+ _paths.clip = buildClip(is, gaps, ydata[_i0] == null, ydata[_i1] == null);
+
+ if (s.fill != null) {
+ let fill = _paths.fill = new Path2D(stroke);
+
+ let fillTo = round(getYPos(s.fillTo(self, is, s.min, s.max), scaleY, plotHgt, plotTop));
+ fill.lineTo(rgtX, fillTo);
+ fill.lineTo(lftX, fillTo);
+ }
+ }
+
+ if (s.band)
+ dir *= -1;
+
+ return _paths;
+ }
+
+ self.paths = buildPaths;
+
+ function getIncrSpace(axisIdx, min, max, fullDim) {
+ let axis = axes[axisIdx];
+
+ let incrSpace;
+
+ if (fullDim <= 0)
+ incrSpace = [0, 0];
+ else {
+ let minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);
+ let incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);
+ incrSpace = axis._found = findIncr(min, max, incrs, fullDim, minSpace);
+ }
+
+ return incrSpace;
+ }
+
+ function drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash) {
+ let offset = (width % 2) / 2;
+
+ ctx.translate(offset, offset);
+
+ setCtxStyle(stroke, width, dash);
+
+ ctx.beginPath();
+
+ let x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);
+
+ if (ori == 0) {
+ y0 = pos0;
+ y1 = pos1;
+ }
+ else {
+ x0 = pos0;
+ x1 = pos1;
+ }
+
+ offs.forEach((off, i) => {
+ if (filts[i] == null)
+ return;
+
+ if (ori == 0)
+ x0 = x1 = off;
+ else
+ y0 = y1 = off;
+
+ ctx.moveTo(x0, y0);
+ ctx.lineTo(x1, y1);
+ });
+
+ ctx.stroke();
+
+ ctx.translate(-offset, -offset);
+ }
+
+ function axesCalc() {
+ // log("axesCalc()", arguments);
+
+ let converged = true;
+
+ axes.forEach((axis, i) => {
+ if (!axis.show)
+ return;
+
+ let scale = scales[axis.scale];
+
+ if (scale.min == null) {
+ if (axis._show) {
+ converged = false;
+ axis._show = false;
+ resetYSeries(false);
+ }
+ return;
+ }
+ else {
+ if (!axis._show) {
+ converged = false;
+ axis._show = true;
+ resetYSeries(false);
+ }
+ }
+
+ let side = axis.side;
+ let ori = side % 2;
+
+ let {min, max} = scale; // // should this toggle them ._show = false
+
+ let [_incr, _space] = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);
+
+ if (_space == 0)
+ return;
+
+ // if we're using index positions, force first tick to match passed index
+ let forceMin = scale.distr == 2;
+
+ let _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);
+
+ // tick labels
+ // BOO this assumes a specific data/series
+ let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
+ let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
+
+ let values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);
+
+ // rotating of labels only supported on bottom x axis
+ axis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;
+
+ let oldSize = axis._size;
+
+ axis._size = axis.size(self, values, i);
+
+ if (oldSize != null && axis._size != oldSize) // ready && ?
+ converged = false;
+ });
+
+ return converged;
+ }
+
+ function guttersCalc() {
+ let converged = true;
+
+ let {_x, _y} = gutters;
+
+ gutters._x = gutters.x(self);
+ gutters._y = gutters.y(self);
+
+ if (gutters._x != _x || gutters._y != _y)
+ converged = false;
+
+ return converged;
+ }
+
+ function drawAxesGrid() {
+ axes.forEach((axis, i) => {
+ if (!axis.show || !axis._show)
+ return;
+
+ let scale = scales[axis.scale];
+ let side = axis.side;
+ let ori = side % 2;
+
+ let getPos = ori == 0 ? getXPos : getYPos;
+ let plotDim = ori == 0 ? plotWid : plotHgt;
+ let plotOff = ori == 0 ? plotLft : plotTop;
+
+ let axisGap = round(axis.gap * pxRatio);
+
+ let ticks = axis.ticks;
+ let tickSize = ticks.show ? round(ticks.size * pxRatio) : 0;
+
+ let [_incr, _space] = axis._found;
+ let _splits = axis._splits;
+
+ // tick labels
+ // BOO this assumes a specific data/series
+ let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
+ let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
+
+ // rotating of labels only supported on bottom x axis
+ let angle = axis._rotate * -PI/180;
+
+ let basePos = round(axis._pos * pxRatio);
+ let shiftAmt = tickSize + axisGap;
+ let shiftDir = ori == 0 && side == 0 || ori == 1 && side == 3 ? -1 : 1;
+ let finalPos = basePos + shiftAmt * shiftDir;
+ let y = ori == 0 ? finalPos : 0;
+ let x = ori == 1 ? finalPos : 0;
+
+ ctx.font = axis.font[0];
+ ctx.fillStyle = axis.stroke || hexBlack; // rgba?
+ ctx.textAlign = axis.align == 1 ? LEFT :
+ axis.align == 2 ? RIGHT :
+ angle > 0 ? LEFT :
+ angle < 0 ? RIGHT :
+ ori == 0 ? "center" : side == 3 ? RIGHT : LEFT;
+ ctx.textBaseline = angle ||
+ ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM;
+
+ let lineHeight = axis.font[1] * lineMult;
+
+ let canOffs = _splits.map(val => round(getPos(val, scale, plotDim, plotOff)));
+
+ axis._values.forEach((val, i) => {
+ if (val == null)
+ return;
+
+ if (ori == 0)
+ x = canOffs[i];
+ else
+ y = canOffs[i];
+
+ (""+val).split(/\n/gm).forEach((text, j) => {
+ if (angle) {
+ ctx.save();
+ ctx.translate(x, y + j * lineHeight);
+ ctx.rotate(angle);
+ ctx.fillText(text, 0, 0);
+ ctx.restore();
+ }
+ else
+ ctx.fillText(text, x, y + j * lineHeight);
+ });
+ });
+
+ // axis label
+ if (axis.label) {
+ ctx.save();
+
+ let baseLpos = round(axis._lpos * pxRatio);
+
+ if (ori == 1) {
+ x = y = 0;
+
+ ctx.translate(
+ baseLpos,
+ round(plotTop + plotHgt / 2),
+ );
+ ctx.rotate((side == 3 ? -PI : PI) / 2);
+
+ }
+ else {
+ x = round(plotLft + plotWid / 2);
+ y = baseLpos;
+ }
+
+ ctx.font = axis.labelFont[0];
+ // ctx.fillStyle = axis.labelStroke || hexBlack; // rgba?
+ ctx.textAlign = "center";
+ ctx.textBaseline = side == 2 ? TOP : BOTTOM;
+
+ ctx.fillText(axis.label, x, y);
+
+ ctx.restore();
+ }
+
+ // ticks
+ if (ticks.show) {
+ drawOrthoLines(
+ canOffs,
+ ticks.filter(self, splits, i, _space, incr),
+ ori,
+ side,
+ basePos,
+ tickSize,
+ roundDec(ticks[WIDTH] * pxRatio, 3),
+ ticks.stroke,
+ );
+ }
+
+ // grid
+ let grid = axis.grid;
+
+ if (grid.show) {
+ drawOrthoLines(
+ canOffs,
+ grid.filter(self, splits, i, _space, incr),
+ ori,
+ ori == 0 ? 2 : 1,
+ ori == 0 ? plotTop : plotLft,
+ ori == 0 ? plotHgt : plotWid,
+ roundDec(grid[WIDTH] * pxRatio, 3),
+ grid.stroke,
+ grid.dash,
+ );
+ }
+ });
+
+ fire("drawAxes");
+ }
+
+ function resetYSeries(minMax) {
+ // log("resetYSeries()", arguments);
+
+ series.forEach((s, i) => {
+ if (i > 0) {
+ s._paths = null;
+
+ if (minMax) {
+ s.min = null;
+ s.max = null;
+ }
+ }
+ });
+ }
+
+ let queuedCommit = false;
+
+ // could do rAF instead of microTask, or Promose.resolve().then()
+ function commit() {
+ if (!queuedCommit) {
+ microTask(_commit);
+ queuedCommit = true;
+ }
+ }
+
+ function _commit() {
+ // log("_commit()", arguments);
+
+ if (shouldSetScales) {
+ setScales();
+ shouldSetScales = false;
+ }
+
+ if (shouldConvergeSize) {
+ convergeSize();
+ shouldConvergeSize = false;
+ }
+
+ if (shouldSetSize) {
+ setStylePx(under, LEFT, plotLftCss);
+ setStylePx(under, TOP, plotTopCss);
+ setStylePx(under, WIDTH, plotWidCss);
+ setStylePx(under, HEIGHT, plotHgtCss);
+
+ setStylePx(over, LEFT, plotLftCss);
+ setStylePx(over, TOP, plotTopCss);
+ setStylePx(over, WIDTH, plotWidCss);
+ setStylePx(over, HEIGHT, plotHgtCss);
+
+ setStylePx(wrap, WIDTH, fullWidCss);
+ setStylePx(wrap, HEIGHT, fullHgtCss);
+
+ can[WIDTH] = round(fullWidCss * pxRatio);
+ can[HEIGHT] = round(fullHgtCss * pxRatio);
+
+ syncRect();
+
+ fire("setSize");
+
+ shouldSetSize = false;
+ }
+
+ // if (shouldSetSelect) {
+ // TODO: update .u-select metrics (if visible)
+ // setStylePx(selectDiv, TOP, select[TOP] = 0);
+ // setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ // setStylePx(selectDiv, WIDTH, select[WIDTH] = 0);
+ // setStylePx(selectDiv, HEIGHT, select[HEIGHT] = 0);
+ // shouldSetSelect = false;
+ // }
+
+ if ( cursor.show && shouldSetCursor) {
+ updateCursor();
+ shouldSetCursor = false;
+ }
+
+ // if (true && legend.show && legend.live && shouldSetLegend) {}
+
+ if (fullWidCss > 0 && fullHgtCss > 0) {
+ ctx.clearRect(0, 0, can[WIDTH], can[HEIGHT]);
+ fire("drawClear");
+ drawAxesGrid();
+ dataLen > 0 && drawSeries();
+ fire("draw");
+ }
+
+ if (!ready) {
+ ready = true;
+ self.status = 1;
+
+ fire("ready");
+ }
+
+ queuedCommit = false;
+ }
+
+ self.redraw = rebuildPaths => {
+ if (rebuildPaths !== false)
+ _setScale(xScaleKey, scales[xScaleKey].min, scales[xScaleKey].max);
+ else
+ commit();
+ };
+
+ // redraw() => setScale('x', scales.x.min, scales.x.max);
+
+ // explicit, never re-ranged (is this actually true? for x and y)
+ function setScale(key, opts) {
+ let sc = scales[key];
+
+ if (sc.from == null) {
+ if (dataLen == 0) {
+ let minMax = sc.range(self, opts.min, opts.max, key);
+ opts.min = minMax[0];
+ opts.max = minMax[1];
+ }
+
+ if (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)
+ return;
+
+ if (key == xScaleKey) {
+ if (sc.distr == 2 && dataLen > 0) {
+ opts.min = closestIdx(opts.min, data[0]);
+ opts.max = closestIdx(opts.max, data[0]);
+ }
+ }
+
+ // log("setScale()", arguments);
+
+ pendScales[key] = opts;
+
+ shouldSetScales = true;
+ commit();
+ }
+ }
+
+ self.setScale = setScale;
+
+// INTERACTION
+
+ let vt;
+ let hz;
+
+ // starting position before cursor.move
+ let rawMouseLeft0;
+ let rawMouseTop0;
+
+ // starting position
+ let mouseLeft0;
+ let mouseTop0;
+
+ // current position before cursor.move
+ let rawMouseLeft1;
+ let rawMouseTop1;
+
+ // current position
+ let mouseLeft1;
+ let mouseTop1;
+
+ let dragging = false;
+
+ const drag = cursor.drag;
+
+ let dragX = drag.x;
+ let dragY = drag.y;
+
+ if ( cursor.show) {
+ if (cursor.x) {
+ mouseLeft1 = cursor.left;
+ vt = placeDiv(CURSOR_X, over);
+ }
+
+ if (cursor.y) {
+ mouseTop1 = cursor.top;
+ hz = placeDiv(CURSOR_Y, over);
+ }
+ }
+
+ const select = self.select = assign({
+ show: true,
+ over: true,
+ left: 0,
+ width: 0,
+ top: 0,
+ height: 0,
+ }, opts.select);
+
+ const selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;
+
+ function setSelect(opts, _fire) {
+ if (select.show) {
+ for (let prop in opts)
+ setStylePx(selectDiv, prop, select[prop] = opts[prop]);
+
+ _fire !== false && fire("setSelect");
+ }
+ }
+
+ self.setSelect = setSelect;
+
+ function toggleDOM(i, onOff) {
+ let s = series[i];
+ let label = showLegend ? legendRows[i][0].parentNode : null;
+
+ if (s.show)
+ label && remClass(label, OFF);
+ else {
+ label && addClass(label, OFF);
+ cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
+ }
+ }
+
+ function _setScale(key, min, max) {
+ setScale(key, {min, max});
+ }
+
+ function setSeries(i, opts, pub) {
+ // log("setSeries()", arguments);
+
+ let s = series[i];
+
+ // will this cause redundant commit() if both show and focus are set?
+ if (opts.focus != null)
+ setFocus(i);
+
+ if (opts.show != null) {
+ s.show = opts.show;
+ toggleDOM(i, opts.show);
+
+ if (s.band) {
+ // not super robust, will break if two bands are adjacent
+ let ip = series[i+1] && series[i+1].band ? i+1 : i-1;
+ series[ip].show = s.show;
+ toggleDOM(ip, opts.show);
+ }
+
+ _setScale(s.scale, null, null);
+ commit();
+ }
+
+ fire("setSeries", i, opts);
+
+ pub && sync.pub("setSeries", self, i, opts);
+ }
+
+ self.setSeries = setSeries;
+
+ function _alpha(i, value) {
+ series[i].alpha = value;
+
+ if ( cursor.show && cursorPts[i])
+ cursorPts[i].style.opacity = value;
+
+ if ( showLegend && legendRows[i])
+ legendRows[i][0].parentNode.style.opacity = value;
+ }
+
+ function _setAlpha(i, value) {
+ let s = series[i];
+
+ _alpha(i, value);
+
+ if (s.band) {
+ // not super robust, will break if two bands are adjacent
+ let ip = series[i+1].band ? i+1 : i-1;
+ _alpha(ip, value);
+ }
+ }
+
+ // y-distance
+ let closestDist;
+ let closestSeries;
+ let focusedSeries;
+
+ function setFocus(i) {
+ if (i != focusedSeries) {
+ // log("setFocus()", arguments);
+
+ series.forEach((s, i2) => {
+ _setAlpha(i2, i == null || i2 == 0 || i2 == i ? 1 : focus.alpha);
+ });
+
+ focusedSeries = i;
+ commit();
+ }
+ }
+
+ if (showLegend && cursorFocus) {
+ on(mouseleave, legendEl, e => {
+ if (cursor._lock)
+ return;
+ setSeries(null, {focus: false}, syncOpts.setSeries);
+ updateCursor();
+ });
+ }
+
+ function scaleValueAtPos(pos, scale) {
+ let dim = plotWidCss;
+
+ if (scale != xScaleKey) {
+ dim = plotHgtCss;
+ pos = dim - pos;
+ }
+
+ let pct = pos / dim;
+
+ let sc = scales[scale],
+ _min = sc.min,
+ _max = sc.max;
+
+ if (sc.distr == 3) {
+ _min = log10(_min);
+ _max = log10(_max);
+ return pow(10, _min + (_max - _min) * pct);
+ }
+ else
+ return _min + (_max - _min) * pct;
+ }
+
+ function closestIdxFromXpos(pos) {
+ let v = scaleValueAtPos(pos, xScaleKey);
+ return closestIdx(v, data[0], i0, i1);
+ }
+
+ self.valToIdx = val => closestIdx(val, data[0]);
+ self.posToIdx = closestIdxFromXpos;
+ self.posToVal = scaleValueAtPos;
+ self.valToPos = (val, scale, can) => (
+ scale == xScaleKey ?
+ getXPos(val, scales[scale],
+ can ? plotWid : plotWidCss,
+ can ? plotLft : 0,
+ ) :
+ getYPos(val, scales[scale],
+ can ? plotHgt : plotHgtCss,
+ can ? plotTop : 0,
+ )
+ );
+
+ // defers calling expensive functions
+ function batch(fn) {
+ fn(self);
+ commit();
+ }
+
+ self.batch = batch;
+
+ (self.setCursor = opts => {
+ mouseLeft1 = opts.left;
+ mouseTop1 = opts.top;
+ // assign(cursor, opts);
+ updateCursor();
+ });
+
+ let cursorRaf = 0;
+
+ function updateCursor(ts, src) {
+ // ts == null && log("updateCursor()", arguments);
+
+ cursorRaf = 0;
+
+ rawMouseLeft1 = mouseLeft1;
+ rawMouseTop1 = mouseTop1;
+
+ [mouseLeft1, mouseTop1] = cursor.move(self, mouseLeft1, mouseTop1);
+
+ if (cursor.show) {
+ cursor.x && trans(vt, round(mouseLeft1), 0, plotWidCss, plotHgtCss);
+ cursor.y && trans(hz, 0, round(mouseTop1), plotWidCss, plotHgtCss);
+ }
+
+ let idx;
+
+ // when zooming to an x scale range between datapoints the binary search
+ // for nearest min/max indices results in this condition. cheap hack :D
+ let noDataInRange = i0 > i1;
+
+ closestDist = inf;
+
+ // if cursor hidden, hide points & clear legend vals
+ if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {
+ idx = null;
+
+ for (let i = 0; i < series.length; i++) {
+ if (i > 0) {
+ cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
+ }
+
+ if (showLegend && legend.live) {
+ if (i == 0 && multiValLegend)
+ continue;
+
+ for (let j = 0; j < legendRows[i].length; j++)
+ legendRows[i][j][firstChild].nodeValue = '--';
+ }
+ }
+
+ if (cursorFocus)
+ setSeries(null, {focus: true}, syncOpts.setSeries);
+ }
+ else {
+ // let pctY = 1 - (y / rect[HEIGHT]);
+
+ let valAtPos = scaleValueAtPos(mouseLeft1, xScaleKey);
+
+ idx = closestIdx(valAtPos, data[0], i0, i1);
+
+ let scX = scales[xScaleKey];
+
+ let xPos = roundDec(getXPos(data[0][idx], scX, plotWidCss, 0), 3);
+
+ for (let i = 0; i < series.length; i++) {
+ let s = series[i];
+
+ let idx2 = cursor.dataIdx(self, i, idx, valAtPos);
+ let xPos2 = idx2 == idx ? xPos : roundDec(getXPos(data[0][idx2], scX, plotWidCss, 0), 3);
+
+ if (i > 0 && s.show) {
+ let valAtIdx = data[i][idx2];
+
+ let yPos = valAtIdx == null ? -10 : roundDec(getYPos(valAtIdx, scales[s.scale], plotHgtCss, 0), 3);
+
+ if (yPos > 0) {
+ let dist = abs(yPos - mouseTop1);
+
+ if (dist <= closestDist) {
+ closestDist = dist;
+ closestSeries = i;
+ }
+ }
+
+ cursorPts.length > 1 && trans(cursorPts[i], xPos2, yPos, plotWidCss, plotHgtCss);
+ }
+
+ if (showLegend && legend.live) {
+ if ((idx2 == cursor.idx && !shouldSetLegend) || i == 0 && multiValLegend)
+ continue;
+
+ let src = i == 0 && xScaleDistr == 2 ? data0 : data[i];
+
+ let vals = multiValLegend ? s.values(self, i, idx2) : {_: s.value(self, src[idx2], i, idx2)};
+
+ let j = 0;
+
+ for (let k in vals)
+ legendRows[i][j++][firstChild].nodeValue = vals[k];
+ }
+ }
+
+ shouldSetLegend = false;
+ }
+
+ // nit: cursor.drag.setSelect is assumed always true
+ if (select.show && dragging) {
+ if (src != null) {
+ let [xKey, yKey] = syncOpts.scales;
+
+ // match the dragX/dragY implicitness/explicitness of src
+ let sdrag = src.cursor.drag;
+ dragX = sdrag._x;
+ dragY = sdrag._y;
+
+ if (xKey) {
+ let sc = scales[xKey];
+ let srcLeft = src.posToVal(src.select[LEFT], xKey);
+ let srcRight = src.posToVal(src.select[LEFT] + src.select[WIDTH], xKey);
+
+ select[LEFT] = getXPos(srcLeft, sc, plotWidCss, 0);
+ select[WIDTH] = abs(select[LEFT] - getXPos(srcRight, sc, plotWidCss, 0));
+
+ setStylePx(selectDiv, LEFT, select[LEFT]);
+ setStylePx(selectDiv, WIDTH, select[WIDTH]);
+
+ if (!yKey) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (yKey) {
+ let sc = scales[yKey];
+ let srcTop = src.posToVal(src.select[TOP], yKey);
+ let srcBottom = src.posToVal(src.select[TOP] + src.select[HEIGHT], yKey);
+
+ select[TOP] = getYPos(srcTop, sc, plotHgtCss, 0);
+ select[HEIGHT] = abs(select[TOP] - getYPos(srcBottom, sc, plotHgtCss, 0));
+
+ setStylePx(selectDiv, TOP, select[TOP]);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT]);
+
+ if (!xKey) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
+ }
+ else {
+ let rawDX = abs(rawMouseLeft1 - rawMouseLeft0);
+ let rawDY = abs(rawMouseTop1 - rawMouseTop0);
+
+ dragX = drag.x && rawDX >= drag.dist;
+ dragY = drag.y && rawDY >= drag.dist;
+
+ let uni = drag.uni;
+
+ if (uni != null) {
+ // only calc drag status if they pass the dist thresh
+ if (dragX && dragY) {
+ dragX = rawDX >= uni;
+ dragY = rawDY >= uni;
+
+ // force unidirectionality when both are under uni limit
+ if (!dragX && !dragY) {
+ if (rawDY > rawDX)
+ dragY = true;
+ else
+ dragX = true;
+ }
+ }
+ }
+ else if (drag.x && drag.y && (dragX || dragY))
+ // if omni with no uni then both dragX / dragY should be true if either is true
+ dragX = dragY = true;
+
+ if (dragX) {
+ let minX = min(mouseLeft0, mouseLeft1);
+ let dx = abs(mouseLeft1 - mouseLeft0);
+
+ setStylePx(selectDiv, LEFT, select[LEFT] = minX);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = dx);
+
+ if (!dragY) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (dragY) {
+ let minY = min(mouseTop0, mouseTop1);
+ let dy = abs(mouseTop1 - mouseTop0);
+
+ setStylePx(selectDiv, TOP, select[TOP] = minY);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = dy);
+
+ if (!dragX) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
+
+ if (!dragX && !dragY) {
+ // the drag didn't pass the dist requirement
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = 0);
+ }
+ }
+ }
+
+ cursor.idx = idx;
+ cursor.left = mouseLeft1;
+ cursor.top = mouseTop1;
+ drag._x = dragX;
+ drag._y = dragY;
+
+ // if ts is present, means we're implicitly syncing own cursor as a result of debounced rAF
+ if (ts != null) {
+ // this is not technically a "mousemove" event, since it's debounced, rename to setCursor?
+ // since this is internal, we can tweak it later
+ sync.pub(mousemove, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, idx);
+
+ if (cursorFocus) {
+ setSeries(closestDist <= focus.prox ? closestSeries : null, {focus: true}, syncOpts.setSeries);
+ }
+ }
+
+ ready && fire("setCursor");
+ }
+
+ let rect = null;
+
+ function syncRect() {
+ rect = over.getBoundingClientRect();
+ }
+
+ function mouseMove(e, src, _x, _y, _w, _h, _i) {
+ if (cursor._lock)
+ return;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, false, e != null);
+
+ if (e != null) {
+ if (cursorRaf == 0)
+ cursorRaf = rAF(updateCursor);
+ }
+ else
+ updateCursor(null, src);
+ }
+
+ function cacheMouse(e, src, _x, _y, _w, _h, _i, initial, snap) {
+ if (e != null) {
+ _x = e.clientX - rect.left;
+ _y = e.clientY - rect.top;
+ }
+ else {
+ if (_x < 0 || _y < 0) {
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+ return;
+ }
+
+ let [xKey, yKey] = syncOpts.scales;
+
+ if (xKey != null)
+ _x = getXPos(src.posToVal(_x, xKey), scales[xKey], plotWidCss, 0);
+ else
+ _x = plotWidCss * (_x/_w);
+
+ if (yKey != null)
+ _y = getYPos(src.posToVal(_y, yKey), scales[yKey], plotHgtCss, 0);
+ else
+ _y = plotHgtCss * (_y/_h);
+ }
+
+ if (snap) {
+ if (_x <= 1 || _x >= plotWidCss - 1)
+ _x = incrRound(_x, plotWidCss);
+
+ if (_y <= 1 || _y >= plotHgtCss - 1)
+ _y = incrRound(_y, plotHgtCss);
+ }
+
+ if (initial) {
+ rawMouseLeft0 = _x;
+ rawMouseTop0 = _y;
+
+ [mouseLeft0, mouseTop0] = cursor.move(self, _x, _y);
+ }
+ else {
+ mouseLeft1 = _x;
+ mouseTop1 = _y;
+ }
+ }
+
+ function hideSelect() {
+ setSelect({
+ width: 0,
+ height: 0,
+ }, false);
+ }
+
+ function mouseDown(e, src, _x, _y, _w, _h, _i) {
+ dragging = true;
+ dragX = dragY = drag._x = drag._y = false;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, true, false);
+
+ if (e != null) {
+ onMouse(mouseup, doc, mouseUp);
+ sync.pub(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);
+ }
+ }
+
+ function mouseUp(e, src, _x, _y, _w, _h, _i) {
+ dragging = drag._x = drag._y = false;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, false, true);
+
+ let hasSelect = select[WIDTH] > 0 || select[HEIGHT] > 0;
+
+ hasSelect && setSelect(select);
+
+ if (drag.setScale && hasSelect) {
+ // if (syncKey != null) {
+ // dragX = drag.x;
+ // dragY = drag.y;
+ // }
+
+ if (dragX) {
+ _setScale(xScaleKey,
+ scaleValueAtPos(select[LEFT], xScaleKey),
+ scaleValueAtPos(select[LEFT] + select[WIDTH], xScaleKey)
+ );
+ }
+
+ if (dragY) {
+ for (let k in scales) {
+ let sc = scales[k];
+
+ if (k != xScaleKey && sc.from == null && sc.min != inf) {
+ _setScale(k,
+ scaleValueAtPos(select[TOP] + select[HEIGHT], k),
+ scaleValueAtPos(select[TOP], k)
+ );
+ }
+ }
+ }
+
+ hideSelect();
+ }
+ else if (cursor.lock) {
+ cursor._lock = !cursor._lock;
+
+ if (!cursor._lock)
+ updateCursor();
+ }
+
+ if (e != null) {
+ offMouse(mouseup, doc);
+ sync.pub(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
+ }
+ }
+
+ function mouseLeave(e, src, _x, _y, _w, _h, _i) {
+ if (!cursor._lock) {
+ let _dragging = dragging;
+
+ if (dragging) {
+ // handle case when mousemove aren't fired all the way to edges by browser
+ let snapX = true;
+ let snapY = true;
+ let snapProx = 10;
+
+ if (dragX && dragY) {
+ // maybe omni corner snap
+ snapX = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;
+ snapY = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;
+ }
+
+ if (dragX && snapX) {
+ let dLft = mouseLeft1;
+ let dRgt = plotWidCss - mouseLeft1;
+
+ let xMin = min(dLft, dRgt);
+
+ if (xMin == dLft)
+ mouseLeft1 = 0;
+ if (xMin == dRgt)
+ mouseLeft1 = plotWidCss;
+ }
+
+ if (dragY && snapY) {
+ let dTop = mouseTop1;
+ let dBtm = plotHgtCss - mouseTop1;
+
+ let yMin = min(dTop, dBtm);
+
+ if (yMin == dTop)
+ mouseTop1 = 0;
+ if (yMin == dBtm)
+ mouseTop1 = plotHgtCss;
+ }
+
+ updateCursor(1);
+
+ dragging = false;
+ }
+
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+
+ // passing a non-null timestamp to force sync/mousemove event
+ updateCursor(1);
+
+ if (_dragging)
+ dragging = _dragging;
+ }
+ }
+
+ function dblClick(e, src, _x, _y, _w, _h, _i) {
+ autoScaleX();
+
+ hideSelect();
+
+ if (e != null)
+ sync.pub(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
+ }
+
+ // internal pub/sub
+ const events = {};
+
+ events[mousedown] = mouseDown;
+ events[mousemove] = mouseMove;
+ events[mouseup] = mouseUp;
+ events[dblclick] = dblClick;
+ events["setSeries"] = (e, src, idx, opts) => {
+ setSeries(idx, opts);
+ };
+
+ let deb;
+
+ if ( cursor.show) {
+ onMouse(mousedown, over, mouseDown);
+ onMouse(mousemove, over, mouseMove);
+ onMouse(mouseenter, over, syncRect);
+ // this has to be rAF'd so it always fires after the last queued/rAF'd updateCursor
+ onMouse(mouseleave, over, e => { rAF(mouseLeave); });
+
+ onMouse(dblclick, over, dblClick);
+
+ deb = debounce(syncRect, 100);
+
+ on(resize, win, deb);
+ on(scroll, win, deb);
+
+ self.syncRect = syncRect;
+ }
+
+ // external on/off
+ const hooks = self.hooks = opts.hooks || {};
+
+ function fire(evName, a1, a2) {
+ if (evName in hooks) {
+ hooks[evName].forEach(fn => {
+ fn.call(null, self, a1, a2);
+ });
+ }
+ }
+
+ (opts.plugins || []).forEach(p => {
+ for (let evName in p.hooks)
+ hooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]);
+ });
+
+ const syncOpts = assign({
+ key: null,
+ setSeries: false,
+ scales: [xScaleKey, null]
+ }, cursor.sync);
+
+ const syncKey = syncOpts.key;
+
+ const sync = (syncKey != null ? (syncs[syncKey] = syncs[syncKey] || _sync()) : _sync());
+
+ sync.sub(self);
+
+ function pub(type, src, x, y, w, h, i) {
+ events[type](null, src, x, y, w, h, i);
+ }
+
+ (self.pub = pub);
+
+ function destroy() {
+ sync.unsub(self);
+ off(resize, win, deb);
+ off(scroll, win, deb);
+ root.remove();
+ fire("destroy");
+ }
+
+ self.destroy = destroy;
+
+ function _init() {
+ fire("init", opts, data);
+
+ setData(data || opts.data, false);
+
+ if (pendScales[xScaleKey])
+ setScale(xScaleKey, pendScales[xScaleKey]);
+ else
+ autoScaleX();
+
+ _setSize(opts[WIDTH], opts[HEIGHT]);
+
+ setSelect(select, false);
+ }
+
+ if (then) {
+ if (then instanceof HTMLElement) {
+ then.appendChild(root);
+ _init();
+ }
+ else
+ then(self, _init);
+ }
+ else
+ _init();
+
+ return self;
+}
+
+uPlot.assign = assign;
+uPlot.fmtNum = fmtNum;
+uPlot.rangeNum = rangeNum;
+uPlot.rangeLog = rangeLog;
+
+{
+ uPlot.fmtDate = fmtDate;
+ uPlot.tzDate = tzDate;
+}
+
+export default uPlot;
diff --git a/plugins/uplot/uPlot.iife.js b/plugins/uplot/uPlot.iife.js
new file mode 100644
index 000000000..b01b9f717
--- /dev/null
+++ b/plugins/uplot/uPlot.iife.js
@@ -0,0 +1,3454 @@
+/**
+* Copyright (c) 2020, Leon Sorokin
+* All rights reserved. (MIT Licensed)
+*
+* uPlot.js (μPlot)
+* A small, fast chart for time series, lines, areas, ohlc & bars
+* https://github.com/leeoniya/uPlot (v1.4.4)
+*/
+
+var uPlot = (function () {
+ 'use strict';
+
+ function debounce(fn, time) {
+ var pending = null;
+
+ function run() {
+ pending = null;
+ fn();
+ }
+
+ return function() {
+ clearTimeout(pending);
+ pending = setTimeout(run, time);
+ }
+ }
+
+ // binary search for index of closest value
+ function closestIdx(num, arr, lo, hi) {
+ var mid;
+ lo = lo || 0;
+ hi = hi || arr.length - 1;
+ var bitwise = hi <= 2147483647;
+
+ while (hi - lo > 1) {
+ mid = bitwise ? (lo + hi) >> 1 : floor((lo + hi) / 2);
+
+ if (arr[mid] < num)
+ { lo = mid; }
+ else
+ { hi = mid; }
+ }
+
+ if (num - arr[lo] <= arr[hi] - num)
+ { return lo; }
+
+ return hi;
+ }
+
+ function getMinMax(data, _i0, _i1, sorted) {
+ // console.log("getMinMax()");
+
+ var _min = inf;
+ var _max = -inf;
+
+ if (sorted == 1) {
+ _min = data[_i0];
+ _max = data[_i1];
+ }
+ else if (sorted == -1) {
+ _min = data[_i1];
+ _max = data[_i0];
+ }
+ else {
+ for (var i = _i0; i <= _i1; i++) {
+ if (data[i] != null) {
+ _min = min(_min, data[i]);
+ _max = max(_max, data[i]);
+ }
+ }
+ }
+
+ return [_min, _max];
+ }
+
+ var _fixedTuple = [0, 0];
+
+ function fixIncr(minIncr, maxIncr, minExp, maxExp) {
+ _fixedTuple[0] = minExp < 0 ? roundDec(minIncr, -minExp) : minIncr;
+ _fixedTuple[1] = maxExp < 0 ? roundDec(maxIncr, -maxExp) : maxIncr;
+ return _fixedTuple;
+ }
+
+ function rangeLog(min, max, base, fullMags) {
+ var logFn = base == 10 ? log10 : log2;
+
+ if (min == max) {
+ min /= base;
+ max *= base;
+ }
+
+ var minExp, maxExp, minMaxIncrs;
+
+ if (fullMags) {
+ minExp = floor(logFn(min));
+ maxExp = ceil(logFn(max));
+
+ minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
+
+ min = minMaxIncrs[0];
+ max = minMaxIncrs[1];
+ }
+ else {
+ minExp = floor(logFn(min));
+ maxExp = floor(logFn(max));
+
+ minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
+
+ min = incrRoundDn(min, minMaxIncrs[0]);
+ max = incrRoundUp(max, minMaxIncrs[1]);
+ }
+
+ return [min, max];
+ }
+
+ var _eqRangePart = {
+ pad: 0,
+ soft: null,
+ mode: 0,
+ };
+
+ var _eqRange = {
+ min: _eqRangePart,
+ max: _eqRangePart,
+ };
+
+ // this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
+ // TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
+ function rangeNum(_min, _max, mult, extra) {
+ if (isObj(mult))
+ { return _rangeNum(_min, _max, mult); }
+
+ _eqRangePart.pad = mult;
+ _eqRangePart.soft = extra ? 0 : null;
+ _eqRangePart.mode = extra ? 2 : 0;
+
+ return _rangeNum(_min, _max, _eqRange);
+ }
+
+ // nullish coalesce
+ function ifNull(lh, rh) {
+ return lh == null ? rh : lh;
+ }
+
+ function _rangeNum(_min, _max, cfg) {
+ var cmin = cfg.min;
+ var cmax = cfg.max;
+
+ var padMin = ifNull(cmin.pad, 0);
+ var padMax = ifNull(cmax.pad, 0);
+
+ var hardMin = ifNull(cmin.hard, -inf);
+ var hardMax = ifNull(cmax.hard, inf);
+
+ var softMin = ifNull(cmin.soft, inf);
+ var softMax = ifNull(cmax.soft, -inf);
+
+ var softMinMode = ifNull(cmin.mode, 0);
+ var softMaxMode = ifNull(cmax.mode, 0);
+
+ var delta = _max - _min;
+ var nonZeroDelta = delta || abs(_max) || 1e3;
+ var mag = log10(nonZeroDelta);
+ var base = pow(10, floor(mag));
+
+ var _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? .1 : 1) : padMin);
+ var _newMin = roundDec(incrRoundDn(_min - _padMin, base/100), 6);
+ var _softMin = _min >= softMin && (softMinMode == 1 || softMinMode == 2 && _newMin < softMin) ? softMin : inf;
+ var minLim = max(hardMin, _newMin < _softMin && _min >= _softMin ? _softMin : min(_softMin, _newMin));
+
+ var _padMax = nonZeroDelta * (delta == 0 ? (_max == 0 ? .1 : 1) : padMax);
+ var _newMax = roundDec(incrRoundUp(_max + _padMax, base/100), 6);
+ var _softMax = _max <= softMax && (softMaxMode == 1 || softMaxMode == 2 && _newMax > softMax) ? softMax : -inf;
+ var maxLim = min(hardMax, _newMax > _softMax && _max <= _softMax ? _softMax : max(_softMax, _newMax));
+
+ if (minLim == maxLim && minLim == 0)
+ { maxLim = 100; }
+
+ return [minLim, maxLim];
+ }
+
+ // alternative: https://stackoverflow.com/a/2254896
+ var fmtNum = new Intl.NumberFormat(navigator.language).format;
+
+ var M = Math;
+
+ var abs = M.abs;
+ var floor = M.floor;
+ var round = M.round;
+ var ceil = M.ceil;
+ var min = M.min;
+ var max = M.max;
+ var pow = M.pow;
+ var log10 = M.log10;
+ var log2 = M.log2;
+ var PI = M.PI;
+
+ var inf = Infinity;
+
+ function incrRound(num, incr) {
+ return round(num/incr)*incr;
+ }
+
+ function clamp(num, _min, _max) {
+ return min(max(num, _min), _max);
+ }
+
+ function fnOrSelf(v) {
+ return typeof v == "function" ? v : function () { return v; };
+ }
+
+ function retArg1(_0, _1) {
+ return _1;
+ }
+
+ function incrRoundUp(num, incr) {
+ return ceil(num/incr)*incr;
+ }
+
+ function incrRoundDn(num, incr) {
+ return floor(num/incr)*incr;
+ }
+
+ function roundDec(val, dec) {
+ return round(val * (dec = Math.pow( 10, dec ))) / dec;
+ }
+
+ var fixedDec = new Map();
+
+ function guessDec(num) {
+ return ((""+num).split(".")[1] || "").length;
+ }
+
+ function genIncrs(base, minExp, maxExp, mults) {
+ var incrs = [];
+
+ var multDec = mults.map(guessDec);
+
+ for (var exp = minExp; exp < maxExp; exp++) {
+ var expa = abs(exp);
+ var mag = roundDec(pow(base, exp), expa);
+
+ for (var i = 0; i < mults.length; i++) {
+ var _incr = mults[i] * mag;
+ var dec = (_incr >= 0 && exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]);
+ var incr = roundDec(_incr, dec);
+ incrs.push(incr);
+ fixedDec.set(incr, dec);
+ }
+ }
+
+ return incrs;
+ }
+
+ //export const assign = Object.assign;
+
+ var EMPTY_OBJ = {};
+
+ var isArr = Array.isArray;
+
+ function isStr(v) {
+ return typeof v === 'string';
+ }
+
+ function isObj(v) {
+ return typeof v === 'object' && v !== null;
+ }
+
+ function copy(o) {
+ var out;
+
+ if (isArr(o))
+ { out = o.map(copy); }
+ else if (isObj(o)) {
+ out = {};
+ for (var k in o)
+ { out[k] = copy(o[k]); }
+ }
+ else
+ { out = o; }
+
+ return out;
+ }
+
+ function assign(targ) {
+ var args = arguments;
+
+ for (var i = 1; i < args.length; i++) {
+ var src = args[i];
+
+ for (var key in src) {
+ if (isObj(targ[key]))
+ { assign(targ[key], copy(src[key])); }
+ else
+ { targ[key] = copy(src[key]); }
+ }
+ }
+
+ return targ;
+ }
+
+ var microTask = typeof queueMicrotask == "undefined" ? function (fn) { return Promise.resolve().then(fn); } : queueMicrotask;
+
+ var WIDTH = "width";
+ var HEIGHT = "height";
+ var TOP = "top";
+ var BOTTOM = "bottom";
+ var LEFT = "left";
+ var RIGHT = "right";
+ var firstChild = "firstChild";
+ var createElement = "createElement";
+ var hexBlack = "#000";
+ var transparent = hexBlack + "0";
+ var classList = "classList";
+
+ var mousemove = "mousemove";
+ var mousedown = "mousedown";
+ var mouseup = "mouseup";
+ var mouseenter = "mouseenter";
+ var mouseleave = "mouseleave";
+ var dblclick = "dblclick";
+ var resize = "resize";
+ var scroll = "scroll";
+
+ var pre = "u-";
+
+ var UPLOT = "uplot";
+ var TITLE = pre + "title";
+ var WRAP = pre + "wrap";
+ var UNDER = pre + "under";
+ var OVER = pre + "over";
+ var OFF = pre + "off";
+ var SELECT = pre + "select";
+ var CURSOR_X = pre + "cursor-x";
+ var CURSOR_Y = pre + "cursor-y";
+ var CURSOR_PT = pre + "cursor-pt";
+ var LEGEND = pre + "legend";
+ var LEGEND_LIVE = pre + "live";
+ var LEGEND_INLINE = pre + "inline";
+ var LEGEND_THEAD = pre + "thead";
+ var LEGEND_SERIES = pre + "series";
+ var LEGEND_MARKER = pre + "marker";
+ var LEGEND_LABEL = pre + "label";
+ var LEGEND_VALUE = pre + "value";
+
+ var rAF = requestAnimationFrame;
+ var doc = document;
+ var win = window;
+ var pxRatio = devicePixelRatio;
+
+ function addClass(el, c) {
+ c != null && el[classList].add(c);
+ }
+
+ function remClass(el, c) {
+ el[classList].remove(c);
+ }
+
+ function setStylePx(el, name, value) {
+ el.style[name] = value + "px";
+ }
+
+ function placeTag(tag, cls, targ, refEl) {
+ var el = doc[createElement](tag);
+
+ if (cls != null)
+ { addClass(el, cls); }
+
+ if (targ != null)
+ { targ.insertBefore(el, refEl); }
+
+ return el;
+ }
+
+ function placeDiv(cls, targ) {
+ return placeTag("div", cls, targ);
+ }
+
+ function trans(el, xPos, yPos, xMax, yMax) {
+ el.style.transform = "translate(" + xPos + "px," + yPos + "px)";
+
+ if (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax)
+ { addClass(el, OFF); }
+ else
+ { remClass(el, OFF); }
+ }
+
+ var evOpts = {passive: true};
+
+ function on(ev, el, cb) {
+ el.addEventListener(ev, cb, evOpts);
+ }
+
+ function off(ev, el, cb) {
+ el.removeEventListener(ev, cb, evOpts);
+ }
+
+ var months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December" ];
+
+ var days = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday" ];
+
+ function slice3(str) {
+ return str.slice(0, 3);
+ }
+
+ var days3 = days.map(slice3);
+
+ var months3 = months.map(slice3);
+
+ var engNames = {
+ MMMM: months,
+ MMM: months3,
+ WWWW: days,
+ WWW: days3,
+ };
+
+ function zeroPad2(int) {
+ return (int < 10 ? '0' : '') + int;
+ }
+
+ function zeroPad3(int) {
+ return (int < 10 ? '00' : int < 100 ? '0' : '') + int;
+ }
+
+ /*
+ function suffix(int) {
+ let mod10 = int % 10;
+
+ return int + (
+ mod10 == 1 && int != 11 ? "st" :
+ mod10 == 2 && int != 12 ? "nd" :
+ mod10 == 3 && int != 13 ? "rd" : "th"
+ );
+ }
+ */
+
+ var getFullYear = 'getFullYear';
+ var getMonth = 'getMonth';
+ var getDate = 'getDate';
+ var getDay = 'getDay';
+ var getHours = 'getHours';
+ var getMinutes = 'getMinutes';
+ var getSeconds = 'getSeconds';
+ var getMilliseconds = 'getMilliseconds';
+
+ var subs = {
+ // 2019
+ YYYY: function (d) { return d[getFullYear](); },
+ // 19
+ YY: function (d) { return (d[getFullYear]()+'').slice(2); },
+ // July
+ MMMM: function (d, names) { return names.MMMM[d[getMonth]()]; },
+ // Jul
+ MMM: function (d, names) { return names.MMM[d[getMonth]()]; },
+ // 07
+ MM: function (d) { return zeroPad2(d[getMonth]()+1); },
+ // 7
+ M: function (d) { return d[getMonth]()+1; },
+ // 09
+ DD: function (d) { return zeroPad2(d[getDate]()); },
+ // 9
+ D: function (d) { return d[getDate](); },
+ // Monday
+ WWWW: function (d, names) { return names.WWWW[d[getDay]()]; },
+ // Mon
+ WWW: function (d, names) { return names.WWW[d[getDay]()]; },
+ // 03
+ HH: function (d) { return zeroPad2(d[getHours]()); },
+ // 3
+ H: function (d) { return d[getHours](); },
+ // 9 (12hr, unpadded)
+ h: function (d) {var h = d[getHours](); return h == 0 ? 12 : h > 12 ? h - 12 : h;},
+ // AM
+ AA: function (d) { return d[getHours]() >= 12 ? 'PM' : 'AM'; },
+ // am
+ aa: function (d) { return d[getHours]() >= 12 ? 'pm' : 'am'; },
+ // a
+ a: function (d) { return d[getHours]() >= 12 ? 'p' : 'a'; },
+ // 09
+ mm: function (d) { return zeroPad2(d[getMinutes]()); },
+ // 9
+ m: function (d) { return d[getMinutes](); },
+ // 09
+ ss: function (d) { return zeroPad2(d[getSeconds]()); },
+ // 9
+ s: function (d) { return d[getSeconds](); },
+ // 374
+ fff: function (d) { return zeroPad3(d[getMilliseconds]()); },
+ };
+
+ function fmtDate(tpl, names) {
+ names = names || engNames;
+ var parts = [];
+
+ var R = /\{([a-z]+)\}|[^{]+/gi, m;
+
+ while (m = R.exec(tpl))
+ { parts.push(m[0][0] == '{' ? subs[m[1]] : m[0]); }
+
+ return function (d) {
+ var out = '';
+
+ for (var i = 0; i < parts.length; i++)
+ { out += typeof parts[i] == "string" ? parts[i] : parts[i](d, names); }
+
+ return out;
+ }
+ }
+
+ var localTz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+ // https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone/53652131#53652131
+ function tzDate(date, tz) {
+ var date2;
+
+ // perf optimization
+ if (tz == 'Etc/UTC')
+ { date2 = new Date(+date + date.getTimezoneOffset() * 6e4); }
+ else if (tz == localTz)
+ { date2 = date; }
+ else {
+ date2 = new Date(date.toLocaleString('en-US', {timeZone: tz}));
+ date2.setMilliseconds(date[getMilliseconds]());
+ }
+
+ return date2;
+ }
+
+ //export const series = [];
+
+ // default formatters:
+
+ var onlyWhole = function (v) { return v % 1 == 0; };
+
+ var allMults = [1,2,2.5,5];
+
+ var wholeMults = allMults.filter(onlyWhole);
+
+ // ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5
+ var decIncrs = genIncrs(10, -16, 0, allMults);
+
+ // 1, 2, 2.5, 5, 10, 20, 25, 50...
+ var oneIncrs = genIncrs(10, 0, 16, allMults);
+
+ // 1, 2, 5, 10, 20, 25, 50...
+ var wholeIncrs = oneIncrs.filter(onlyWhole);
+
+ var numIncrs = decIncrs.concat(oneIncrs);
+
+ var s = 1,
+ m = 60,
+ h = m * m,
+ d = h * 24,
+ mo = d * 30,
+ y = d * 365;
+
+ // min of 1e-3 prevents setting a temporal x ticks too small since Date objects cannot advance ticks smaller than 1ms
+ var timeIncrs = genIncrs(10, -3, 0, wholeMults).concat([
+ // minute divisors (# of secs)
+ 1,
+ 5,
+ 10,
+ 15,
+ 30,
+ // hour divisors (# of mins)
+ m,
+ m * 5,
+ m * 10,
+ m * 15,
+ m * 30,
+ // day divisors (# of hrs)
+ h,
+ h * 2,
+ h * 3,
+ h * 4,
+ h * 6,
+ h * 8,
+ h * 12,
+ // month divisors TODO: need more?
+ d,
+ d * 2,
+ d * 3,
+ d * 4,
+ d * 5,
+ d * 6,
+ d * 7,
+ d * 8,
+ d * 9,
+ d * 10,
+ d * 15,
+ // year divisors (# months, approx)
+ mo,
+ mo * 2,
+ mo * 3,
+ mo * 4,
+ mo * 6,
+ // century divisors
+ y,
+ y * 2,
+ y * 5,
+ y * 10,
+ y * 25,
+ y * 50,
+ y * 100 ]);
+
+ // base 2
+ var binIncrs = genIncrs(2, -53, 53, [1]);
+
+ /*
+ console.log({
+ decIncrs,
+ oneIncrs,
+ wholeIncrs,
+ numIncrs,
+ timeIncrs,
+ fixedDec,
+ });
+ */
+
+ function timeAxisStamps(stampCfg, fmtDate) {
+ return stampCfg.map(function (s) { return s.map(function (v, i) { return i == 0 || i == 8 || v == null ? v : fmtDate(i == 1 || s[8] == 0 ? v : s[1] + v); }
+ ); });
+ }
+
+ var NL = "\n";
+
+ var yyyy = "{YYYY}";
+ var NLyyyy = NL + yyyy;
+ var md = "{M}/{D}";
+ var NLmd = NL + md;
+ var NLmdyy = NLmd + "/{YY}";
+
+ var aa = "{aa}";
+ var hmm = "{h}:{mm}";
+ var hmmaa = hmm + aa;
+ var NLhmmaa = NL + hmmaa;
+ var ss = ":{ss}";
+
+ var _ = null;
+
+ // [0]: minimum num secs in the tick incr
+ // [1]: default tick format
+ // [2-7]: rollover tick formats
+ // [8]: mode: 0: replace [1] -> [2-7], 1: concat [1] + [2-7]
+ var _timeAxisStamps = [
+ // tick incr default year month day hour min sec mode
+ [y, yyyy, _, _, _, _, _, _, 1],
+ [d * 28, "{MMM}", NLyyyy, _, _, _, _, _, 1],
+ [d, md, NLyyyy, _, _, _, _, _, 1],
+ [h, "{h}" + aa, NLmdyy, _, NLmd, _, _, _, 1],
+ [m, hmmaa, NLmdyy, _, NLmd, _, _, _, 1],
+ [s, ss, NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
+ [1e-3, ss + ".{fff}", NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1] ];
+
+ // TODO: will need to accept spaces[] and pull incr into the loop when grid will be non-uniform, eg for log scales.
+ // currently we ignore this for months since they're *nearly* uniform and the added complexity is not worth it
+ function timeAxisVals(tzDate, stamps) {
+ return function (self, splits, axisIdx, foundSpace, foundIncr) {
+ var s = stamps.find(function (s) { return foundIncr >= s[0]; }) || stamps[stamps.length - 1];
+
+ // these track boundaries when a full label is needed again
+ var prevYear;
+ var prevMnth;
+ var prevDate;
+ var prevHour;
+ var prevMins;
+ var prevSecs;
+
+ return splits.map(function (split) {
+ var date = tzDate(split);
+
+ var newYear = date[getFullYear]();
+ var newMnth = date[getMonth]();
+ var newDate = date[getDate]();
+ var newHour = date[getHours]();
+ var newMins = date[getMinutes]();
+ var newSecs = date[getSeconds]();
+
+ var stamp = (
+ newYear != prevYear && s[2] ||
+ newMnth != prevMnth && s[3] ||
+ newDate != prevDate && s[4] ||
+ newHour != prevHour && s[5] ||
+ newMins != prevMins && s[6] ||
+ newSecs != prevSecs && s[7] ||
+ s[1]
+ );
+
+ prevYear = newYear;
+ prevMnth = newMnth;
+ prevDate = newDate;
+ prevHour = newHour;
+ prevMins = newMins;
+ prevSecs = newSecs;
+
+ return stamp(date);
+ });
+ }
+ }
+
+ // for when axis.values is defined as a static fmtDate template string
+ function timeAxisVal(tzDate, dateTpl) {
+ var stamp = fmtDate(dateTpl);
+ return function (self, splits, axisIdx, foundSpace, foundIncr) { return splits.map(function (split) { return stamp(tzDate(split)); }); };
+ }
+
+ function mkDate(y, m, d) {
+ return new Date(y, m, d);
+ }
+
+ // the ensures that axis ticks, values & grid are aligned to logical temporal breakpoints and not an arbitrary timestamp
+ // https://www.timeanddate.com/time/dst/
+ // https://www.timeanddate.com/time/dst/2019.html
+ // https://www.epochconverter.com/timezones
+ function timeAxisSplits(tzDate) {
+ return function (self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) {
+ var splits = [];
+ var isYr = foundIncr >= y;
+ var isMo = foundIncr >= mo && foundIncr < y;
+
+ // get the timezone-adjusted date
+ var minDate = tzDate(scaleMin);
+ var minDateTs = minDate / 1e3;
+
+ // get ts of 12am (this lands us at or before the original scaleMin)
+ var minMin = mkDate(minDate[getFullYear](), isYr ? 0 : minDate[getMonth](), isMo || isYr ? 1 : minDate[getDate]());
+ var minMinTs = minMin / 1e3;
+
+ if (isMo || isYr) {
+ var moIncr = isMo ? foundIncr / mo : 0;
+ var yrIncr = isYr ? foundIncr / y : 0;
+ // let tzOffset = scaleMin - minDateTs; // needed?
+ var split = minDateTs == minMinTs ? minDateTs : mkDate(minMin[getFullYear]() + yrIncr, minMin[getMonth]() + moIncr, 1) / 1e3;
+ var splitDate = new Date(split * 1e3);
+ var baseYear = splitDate[getFullYear]();
+ var baseMonth = splitDate[getMonth]();
+
+ for (var i = 0; split <= scaleMax; i++) {
+ var next = mkDate(baseYear + yrIncr * i, baseMonth + moIncr * i, 1);
+ var offs = next - tzDate(next / 1e3);
+
+ split = (+next + offs) / 1e3;
+
+ if (split <= scaleMax)
+ { splits.push(split); }
+ }
+ }
+ else {
+ var incr0 = foundIncr >= d ? d : foundIncr;
+ var tzOffset = floor(scaleMin) - floor(minDateTs);
+ var split$1 = minMinTs + tzOffset + incrRoundUp(minDateTs - minMinTs, incr0);
+ splits.push(split$1);
+
+ var date0 = tzDate(split$1);
+
+ var prevHour = date0[getHours]() + (date0[getMinutes]() / m) + (date0[getSeconds]() / h);
+ var incrHours = foundIncr / h;
+
+ var minSpace = self.axes[axisIdx]._space;
+ var pctSpace = foundSpace / minSpace;
+
+ while (1) {
+ split$1 = roundDec(split$1 + foundIncr, 3);
+
+ if (split$1 > scaleMax)
+ { break; }
+
+ if (incrHours > 1) {
+ var expectedHour = floor(roundDec(prevHour + incrHours, 6)) % 24;
+ var splitDate$1 = tzDate(split$1);
+ var actualHour = splitDate$1.getHours();
+
+ var dstShift = actualHour - expectedHour;
+
+ if (dstShift > 1)
+ { dstShift = -1; }
+
+ split$1 -= dstShift * h;
+
+ prevHour = (prevHour + incrHours) % 24;
+
+ // add a tick only if it's further than 70% of the min allowed label spacing
+ var prevSplit = splits[splits.length - 1];
+ var pctIncr = roundDec((split$1 - prevSplit) / foundIncr, 3);
+
+ if (pctIncr * pctSpace >= .7)
+ { splits.push(split$1); }
+ }
+ else
+ { splits.push(split$1); }
+ }
+ }
+
+ return splits;
+ }
+ }
+
+ function timeSeriesStamp(stampCfg, fmtDate) {
+ return fmtDate(stampCfg);
+ }
+ var _timeSeriesStamp = '{YYYY}-{MM}-{DD} {h}:{mm}{aa}';
+
+ function timeSeriesVal(tzDate, stamp) {
+ return function (self, val) { return stamp(tzDate(val)); };
+ }
+
+ function cursorPoint(self, si) {
+ var s = self.series[si];
+
+ var pt = placeDiv();
+
+ pt.style.background = s.stroke || hexBlack;
+
+ var dia = ptDia(s.width, 1);
+ var mar = (dia - 1) / -2;
+
+ setStylePx(pt, WIDTH, dia);
+ setStylePx(pt, HEIGHT, dia);
+ setStylePx(pt, "marginLeft", mar);
+ setStylePx(pt, "marginTop", mar);
+
+ return pt;
+ }
+
+ function dataIdx(self, seriesIdx, cursorIdx) {
+ return cursorIdx;
+ }
+
+ var moveTuple = [0,0];
+
+ function cursorMove(self, mouseLeft1, mouseTop1) {
+ moveTuple[0] = mouseLeft1;
+ moveTuple[1] = mouseTop1;
+ return moveTuple;
+ }
+
+ function filtBtn0(self, targ, handle) {
+ return function (e) {
+ e.button == 0 && handle(e);
+ };
+ }
+
+ function passThru(self, targ, handle) {
+ return handle;
+ }
+
+ var cursorOpts = {
+ show: true,
+ x: true,
+ y: true,
+ lock: false,
+ move: cursorMove,
+ points: {
+ show: cursorPoint,
+ },
+
+ bind: {
+ mousedown: filtBtn0,
+ mouseup: filtBtn0,
+ click: filtBtn0,
+ dblclick: filtBtn0,
+
+ mousemove: passThru,
+ mouseleave: passThru,
+ mouseenter: passThru,
+ },
+
+ drag: {
+ setScale: true,
+ x: true,
+ y: false,
+ dist: 0,
+ uni: null,
+ _x: false,
+ _y: false,
+ },
+
+ focus: {
+ prox: -1,
+ },
+
+ left: -10,
+ top: -10,
+ idx: null,
+ dataIdx: dataIdx,
+ };
+
+ var grid = {
+ show: true,
+ stroke: "rgba(0,0,0,0.07)",
+ width: 2,
+ // dash: [],
+ filter: retArg1,
+ };
+
+ var ticks = assign({}, grid, {size: 10});
+
+ var font = '12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"';
+ var labelFont = "bold " + font;
+ var lineMult = 1.5; // font-size multiplier
+
+ var xAxisOpts = {
+ show: true,
+ scale: "x",
+ space: 50,
+ gap: 5,
+ size: 50,
+ labelSize: 30,
+ labelFont: labelFont,
+ side: 2,
+ // class: "x-vals",
+ // incrs: timeIncrs,
+ // values: timeVals,
+ // filter: retArg1,
+ grid: grid,
+ ticks: ticks,
+ font: font,
+ rotate: 0,
+ };
+
+ var numSeriesLabel = "Value";
+ var timeSeriesLabel = "Time";
+
+ var xSeriesOpts = {
+ show: true,
+ scale: "x",
+ auto: false,
+ sorted: 1,
+ // label: "Time",
+ // value: v => stamp(new Date(v * 1e3)),
+
+ // internal caches
+ min: inf,
+ max: -inf,
+ idxs: [],
+ };
+
+ function numAxisVals(self, splits, axisIdx, foundSpace, foundIncr) {
+ return splits.map(function (v) { return v == null ? "" : fmtNum(v); });
+ }
+
+ function numAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
+ var splits = [];
+
+ var numDec = fixedDec.get(foundIncr) || 0;
+
+ scaleMin = forceMin ? scaleMin : roundDec(incrRoundUp(scaleMin, foundIncr), numDec);
+
+ for (var val = scaleMin; val <= scaleMax; val = roundDec(val + foundIncr, numDec))
+ { splits.push(Object.is(val, -0) ? 0 : val); } // coalesces -0
+
+ return splits;
+ }
+
+ function logAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
+ var splits = [];
+
+ var logBase = self.scales[self.axes[axisIdx].scale].log;
+
+ var logFn = logBase == 10 ? log10 : log2;
+
+ var exp = floor(logFn(scaleMin));
+
+ foundIncr = pow(logBase, exp);
+
+ if (exp < 0)
+ { foundIncr = roundDec(foundIncr, -exp); }
+
+ var split = scaleMin;
+
+ do {
+ splits.push(split);
+ split = roundDec(split + foundIncr, fixedDec.get(foundIncr));
+
+ if (split >= foundIncr * logBase)
+ { foundIncr = split; }
+
+ } while (split <= scaleMax);
+
+ return splits;
+ }
+
+ var RE_ALL = /./;
+ var RE_12357 = /[12357]/;
+ var RE_125 = /[125]/;
+ var RE_1 = /1/;
+
+ function logAxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {
+ var axis = self.axes[axisIdx];
+ var scaleKey = axis.scale;
+
+ if (self.scales[scaleKey].log == 2)
+ { return splits; }
+
+ var valToPos = self.valToPos;
+
+ var minSpace = axis._space;
+
+ var _10 = valToPos(10, scaleKey);
+
+ var re = (
+ valToPos(9, scaleKey) - _10 >= minSpace ? RE_ALL :
+ valToPos(7, scaleKey) - _10 >= minSpace ? RE_12357 :
+ valToPos(5, scaleKey) - _10 >= minSpace ? RE_125 :
+ RE_1
+ );
+
+ return splits.map(function (v) { return re.test(v) ? v : null; });
+ }
+
+ function numSeriesVal(self, val) {
+ return val == null ? "" : fmtNum(val);
+ }
+
+ var yAxisOpts = {
+ show: true,
+ scale: "y",
+ space: 30,
+ gap: 5,
+ size: 50,
+ labelSize: 30,
+ labelFont: labelFont,
+ side: 3,
+ // class: "y-vals",
+ // incrs: numIncrs,
+ // values: (vals, space) => vals,
+ // filter: retArg1,
+ grid: grid,
+ ticks: ticks,
+ font: font,
+ rotate: 0,
+ };
+
+ // takes stroke width
+ function ptDia(width, mult) {
+ var dia = 3 + (width || 1) * 2;
+ return roundDec(dia * mult, 3);
+ }
+
+ function seriesPoints(self, si) {
+ var s = self.series[si];
+ var dia = ptDia(s.width, pxRatio);
+ var maxPts = self.bbox.width / (s.points.space * pxRatio);
+ var idxs = self.series[0].idxs;
+ return idxs[1] - idxs[0] <= maxPts;
+ }
+
+ function seriesFillTo(self, seriesIdx, dataMin, dataMax) {
+ var scale = self.scales[self.series[seriesIdx].scale];
+ return scale.distr == 3 ? scale.min : 0;
+ }
+
+ var ySeriesOpts = {
+ scale: "y",
+ auto: true,
+ sorted: 0,
+ show: true,
+ band: false,
+ spanGaps: false,
+ isGap: function (self, seriesIdx, dataIdx) { return true; },
+ alpha: 1,
+ points: {
+ show: seriesPoints,
+ // stroke: "#000",
+ // fill: "#fff",
+ // width: 1,
+ // size: 10,
+ },
+ // label: "Value",
+ // value: v => v,
+ values: null,
+
+ // internal caches
+ min: inf,
+ max: -inf,
+ idxs: [],
+
+ path: null,
+ clip: null,
+ };
+
+ var xScaleOpts = {
+ time: true,
+ auto: true,
+ distr: 1,
+ log: 10,
+ min: null,
+ max: null,
+ };
+
+ var yScaleOpts = assign({}, xScaleOpts, {
+ time: false,
+ });
+
+ var syncs = {};
+
+ function _sync(opts) {
+ var clients = [];
+
+ return {
+ sub: function sub(client) {
+ clients.push(client);
+ },
+ unsub: function unsub(client) {
+ clients = clients.filter(function (c) { return c != client; });
+ },
+ pub: function pub(type, self, x, y, w, h, i) {
+ if (clients.length > 1) {
+ clients.forEach(function (client) {
+ client != self && client.pub(type, self, x, y, w, h, i);
+ });
+ }
+ }
+ };
+ }
+
+ function setDefaults(d, xo, yo, initY) {
+ var d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));
+ return d2.map(function (o, i) { return setDefault(o, i, xo, yo); });
+ }
+
+ function setDefault(o, i, xo, yo) {
+ return assign({}, (i == 0 || o && o.side % 2 == 0 ? xo : yo), o);
+ }
+
+ function getValPct(val, scale) {
+ return (
+ scale.distr == 3
+ ? log10(val / scale.min) / log10(scale.max / scale.min)
+ : (val - scale.min) / (scale.max - scale.min)
+ );
+ }
+
+ function getYPos(val, scale, hgt, top) {
+ var pctY = getValPct(val, scale);
+ return top + (1 - pctY) * hgt;
+ }
+
+ function getXPos(val, scale, wid, lft) {
+ var pctX = getValPct(val, scale);
+ return lft + pctX * wid;
+ }
+
+ var nullMinMax = [null, null];
+
+ function snapNumX(self, dataMin, dataMax) {
+ return dataMin == null ? nullMinMax : [dataMin, dataMax];
+ }
+
+ var snapTimeX = snapNumX;
+
+ // this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
+ // TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
+ function snapNumY(self, dataMin, dataMax) {
+ return dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, 0.1, true);
+ }
+
+ function snapLogY(self, dataMin, dataMax, scale) {
+ return dataMin == null ? nullMinMax : rangeLog(dataMin, dataMax, self.scales[scale].log, false);
+ }
+
+ var snapLogX = snapLogY;
+
+ // dim is logical (getClientBoundingRect) pixels, not canvas pixels
+ function findIncr(min, max, incrs, dim, minSpace) {
+ var pxPerUnit = dim / (max - min);
+
+ var minDec = (""+floor(min)).length;
+
+ for (var i = 0; i < incrs.length; i++) {
+ var space = incrs[i] * pxPerUnit;
+
+ var incrDec = incrs[i] < 10 ? fixedDec.get(incrs[i]) : 0;
+
+ if (space >= minSpace && minDec + incrDec < 17)
+ { return [incrs[i], space]; }
+ }
+
+ return [0, 0];
+ }
+
+ function pxRatioFont(font) {
+ var fontSize;
+ font = font.replace(/(\d+)px/, function (m, p1) { return (fontSize = round(p1 * pxRatio)) + 'px'; });
+ return [font, fontSize];
+ }
+
+ function uPlot(opts, data, then) {
+ var self = {};
+
+ var ready = false;
+ self.status = 0;
+
+ var root = self.root = placeDiv(UPLOT);
+
+ if (opts.id != null)
+ { root.id = opts.id; }
+
+ addClass(root, opts.class);
+
+ if (opts.title) {
+ var title = placeDiv(TITLE, root);
+ title.textContent = opts.title;
+ }
+
+ var can = placeTag("canvas");
+ var ctx = self.ctx = can.getContext("2d");
+
+ var wrap = placeDiv(WRAP, root);
+ var under = placeDiv(UNDER, wrap);
+ wrap.appendChild(can);
+ var over = placeDiv(OVER, wrap);
+
+ opts = copy(opts);
+
+ (opts.plugins || []).forEach(function (p) {
+ if (p.opts)
+ { opts = p.opts(self, opts) || opts; }
+ });
+
+
+
+ var series = self.series = setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false);
+ var axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);
+ var scales = self.scales = {};
+
+ var xScaleKey = series[0].scale;
+
+ function initScale(scaleKey) {
+ var sc = scales[scaleKey];
+
+ if (sc == null) {
+ var scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;
+
+ if (scaleOpts.from != null) {
+ // ensure parent is initialized
+ initScale(scaleOpts.from);
+ // dependent scales inherit
+ scales[scaleKey] = assign({}, scales[scaleOpts.from], scaleOpts);
+ }
+ else {
+ sc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);
+
+ var isTime = sc.time;
+ var isLog = sc.distr == 3;
+
+ var rn = sc.range;
+
+ if (scaleKey != xScaleKey && !isArr(rn) && isObj(rn)) {
+ var cfg = rn;
+ // this is similar to snapNumY
+ rn = function (self, dataMin, dataMax) { return dataMin == null ? nullMinMax : rangeNum(dataMin, dataMax, cfg); };
+ }
+
+ sc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ? (isLog ? snapLogX : snapNumX) : (isLog ? snapLogY : snapNumY)));
+
+ sc.auto = fnOrSelf(sc.auto);
+ }
+ }
+ }
+
+ initScale("x");
+ initScale("y");
+
+ series.forEach(function (s, i) {
+ initScale(s.scale);
+ });
+
+ for (var k in opts.scales)
+ { initScale(k); }
+
+ var xScaleDistr = scales[xScaleKey].distr;
+
+ var pendScales = {};
+
+ // explicitly-set initial scales
+ for (var k$1 in scales) {
+ var sc = scales[k$1];
+
+ if (sc.min != null || sc.max != null)
+ { pendScales[k$1] = {min: sc.min, max: sc.max}; }
+ }
+
+ var gutters = self.gutters = assign({
+ x: round(yAxisOpts.size / 2),
+ y: round(xAxisOpts.size / 3),
+ _x: null,
+ _y: null,
+ }, opts.gutters);
+
+ gutters.x = fnOrSelf(gutters.x);
+ gutters.y = fnOrSelf(gutters.y);
+ gutters._x = gutters.x(self);
+ gutters._y = gutters.y(self);
+
+ // self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
+ var _tzDate = (opts.tzDate || (function (ts) { return new Date(ts * 1e3); }));
+ var _fmtDate = (opts.fmtDate || fmtDate);
+
+ var _timeAxisSplits = timeAxisSplits(_tzDate);
+ var _timeAxisVals = timeAxisVals(_tzDate, timeAxisStamps(_timeAxisStamps, _fmtDate));
+ var _timeSeriesVal = timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));
+
+ var legend = assign({show: true, live: true}, opts.legend);
+ var showLegend = legend.show;
+
+ var legendEl;
+ var legendRows = [];
+ var legendCols;
+ var multiValLegend = false;
+
+ if (showLegend) {
+ legendEl = placeTag("table", LEGEND, root);
+
+ var getMultiVals = series[1] ? series[1].values : null;
+ multiValLegend = getMultiVals != null;
+
+ if (multiValLegend) {
+ var head = placeTag("tr", LEGEND_THEAD, legendEl);
+ placeTag("th", null, head);
+ legendCols = getMultiVals(self, 1, 0);
+
+ for (var key in legendCols)
+ { placeTag("th", LEGEND_LABEL, head).textContent = key; }
+ }
+ else {
+ legendCols = {_: 0};
+ addClass(legendEl, LEGEND_INLINE);
+ legend.live && addClass(legendEl, LEGEND_LIVE);
+ }
+ }
+
+ function initLegendRow(s, i) {
+ if (i == 0 && (multiValLegend || !legend.live))
+ { return null; }
+
+ var _row = [];
+
+ var row = placeTag("tr", LEGEND_SERIES, legendEl, legendEl.childNodes[i]);
+
+ addClass(row, s.class);
+
+ if (!s.show)
+ { addClass(row, OFF); }
+
+ var label = placeTag("th", null, row);
+
+ var indic = placeDiv(LEGEND_MARKER, label);
+ indic.style.borderColor = s.width ? s.stroke : i > 0 && s.points.width ? s.points.stroke : null;
+ indic.style.backgroundColor = s.fill || null;
+
+ var text = placeDiv(LEGEND_LABEL, label);
+ text.textContent = s.label;
+
+ if (i > 0) {
+ onMouse("click", label, function (e) {
+ if ( cursor._lock)
+ { return; }
+
+ setSeries(series.indexOf(s), {show: !s.show}, syncOpts.setSeries);
+ });
+
+ if (cursorFocus) {
+ onMouse(mouseenter, label, function (e) {
+ if (cursor._lock)
+ { return; }
+
+ setSeries(series.indexOf(s), {focus: true}, syncOpts.setSeries);
+ });
+ }
+ }
+
+ for (var key in legendCols) {
+ var v = placeTag("td", LEGEND_VALUE, row);
+ v.textContent = "--";
+ _row.push(v);
+ }
+
+ return _row;
+ }
+
+ var mouseListeners = new Map();
+
+ function onMouse(ev, targ, fn) {
+ var targListeners = mouseListeners.get(targ) || {};
+ var listener = cursor.bind[ev](self, targ, fn);
+
+ if (listener) {
+ on(ev, targ, targListeners[ev] = listener);
+ mouseListeners.set(targ, targListeners);
+ }
+ }
+
+ function offMouse(ev, targ, fn) {
+ var targListeners = mouseListeners.get(targ) || {};
+ off(ev, targ, targListeners[ev]);
+ targListeners[ev] = null;
+ }
+
+ var fullWidCss = 0;
+ var fullHgtCss = 0;
+
+ var plotWidCss = 0;
+ var plotHgtCss = 0;
+
+ // plot margins to account for axes
+ var plotLftCss = 0;
+ var plotTopCss = 0;
+
+ var plotLft = 0;
+ var plotTop = 0;
+ var plotWid = 0;
+ var plotHgt = 0;
+
+ self.bbox = {};
+
+ var shouldSetScales = false;
+ var shouldSetSize = false;
+ var shouldConvergeSize = false;
+ var shouldSetCursor = false;
+ var shouldSetLegend = false;
+
+ function _setSize(width, height) {
+ if (width != self.width || height != self.height)
+ { calcSize(width, height); }
+
+ resetYSeries(false);
+
+ shouldConvergeSize = true;
+ shouldSetSize = true;
+ shouldSetCursor = true;
+ shouldSetLegend = true;
+ commit();
+ }
+
+ function calcSize(width, height) {
+ // log("calcSize()", arguments);
+
+ self.width = fullWidCss = plotWidCss = width;
+ self.height = fullHgtCss = plotHgtCss = height;
+ plotLftCss = plotTopCss = 0;
+
+ calcPlotRect();
+ calcAxesRects();
+
+ var bb = self.bbox;
+
+ plotLft = bb[LEFT] = incrRound(plotLftCss * pxRatio, 0.5);
+ plotTop = bb[TOP] = incrRound(plotTopCss * pxRatio, 0.5);
+ plotWid = bb[WIDTH] = incrRound(plotWidCss * pxRatio, 0.5);
+ plotHgt = bb[HEIGHT] = incrRound(plotHgtCss * pxRatio, 0.5);
+ }
+
+ function convergeSize() {
+ var converged = false;
+
+ while (!converged) {
+ var axesConverged = axesCalc();
+ var guttersConverged = guttersCalc();
+
+ converged = axesConverged && guttersConverged;
+
+ if (!converged) {
+ calcSize(self.width, self.height);
+ shouldSetSize = true;
+ }
+ }
+ }
+
+ function setSize(ref) {
+ var width = ref.width;
+ var height = ref.height;
+
+ _setSize(width, height);
+ }
+
+ self.setSize = setSize;
+
+ // accumulate axis offsets, reduce canvas width
+ function calcPlotRect() {
+ // easements for edge labels
+ var hasTopAxis = false;
+ var hasBtmAxis = false;
+ var hasRgtAxis = false;
+ var hasLftAxis = false;
+
+ axes.forEach(function (axis, i) {
+ if (axis.show && axis._show) {
+ var side = axis.side;
+ var _size = axis._size;
+ var isVt = side % 2;
+ var labelSize = axis.labelSize = (axis.label != null ? (axis.labelSize || 30) : 0);
+
+ var fullSize = _size + labelSize;
+
+ if (fullSize > 0) {
+ if (isVt) {
+ plotWidCss -= fullSize;
+
+ if (side == 3) {
+ plotLftCss += fullSize;
+ hasLftAxis = true;
+ }
+ else
+ { hasRgtAxis = true; }
+ }
+ else {
+ plotHgtCss -= fullSize;
+
+ if (side == 0) {
+ plotTopCss += fullSize;
+ hasTopAxis = true;
+ }
+ else
+ { hasBtmAxis = true; }
+ }
+ }
+ }
+ });
+
+ // hz gutters
+ if (hasTopAxis || hasBtmAxis) {
+ if (!hasRgtAxis)
+ { plotWidCss -= gutters._x; }
+ if (!hasLftAxis) {
+ plotWidCss -= gutters._x;
+ plotLftCss += gutters._x;
+ }
+ }
+
+ // vt gutters
+ if (hasLftAxis || hasRgtAxis) {
+ if (!hasBtmAxis)
+ { plotHgtCss -= gutters._y; }
+ if (!hasTopAxis) {
+ plotHgtCss -= gutters._y;
+ plotTopCss += gutters._y;
+ }
+ }
+ }
+
+ function calcAxesRects() {
+ // will accum +
+ var off1 = plotLftCss + plotWidCss;
+ var off2 = plotTopCss + plotHgtCss;
+ // will accum -
+ var off3 = plotLftCss;
+ var off0 = plotTopCss;
+
+ function incrOffset(side, size) {
+
+ switch (side) {
+ case 1: off1 += size; return off1 - size;
+ case 2: off2 += size; return off2 - size;
+ case 3: off3 -= size; return off3 + size;
+ case 0: off0 -= size; return off0 + size;
+ }
+ }
+
+ axes.forEach(function (axis, i) {
+ if (axis.show && axis._show) {
+ var side = axis.side;
+
+ axis._pos = incrOffset(side, axis._size);
+
+ if (axis.label != null)
+ { axis._lpos = incrOffset(side, axis.labelSize); }
+ }
+ });
+ }
+
+ var cursor = (self.cursor = assign({}, cursorOpts, opts.cursor));
+
+ (cursor._lock = false);
+ (cursor.points.show = fnOrSelf(cursor.points.show));
+
+ var focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);
+ var cursorFocus = focus.prox >= 0;
+
+ // series-intersection markers
+ var cursorPts = [null];
+
+ function initCursorPt(s, si) {
+ if (si > 0) {
+ var pt = cursor.points.show(self, si);
+
+ if (pt) {
+ addClass(pt, CURSOR_PT);
+ addClass(pt, s.class);
+ trans(pt, -10, -10, plotWidCss, plotHgtCss);
+ over.insertBefore(pt, cursorPts[si]);
+
+ return pt;
+ }
+ }
+ }
+
+ function initSeries(s, i) {
+ var isTime = scales[s.scale].time;
+
+ var sv = s.value;
+ s.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;
+ s.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);
+
+ if (i > 0) {
+ s.width = s.width == null ? 1 : s.width;
+ s.paths = s.paths || ( buildPaths);
+ s.fillTo = s.fillTo || seriesFillTo;
+ var _ptDia = ptDia(s.width, 1);
+ s.points = assign({}, {
+ size: _ptDia,
+ width: max(1, _ptDia * .2),
+ stroke: s.stroke,
+ space: _ptDia * 2,
+ }, s.points);
+ s.points.show = fnOrSelf(s.points.show);
+ s._paths = null;
+ }
+
+ if (showLegend)
+ { legendRows.splice(i, 0, initLegendRow(s, i)); }
+
+ if ( cursor.show) {
+ var pt = initCursorPt(s, i);
+ pt && cursorPts.splice(i, 0, pt);
+ }
+ }
+
+ function addSeries(opts, si) {
+ si = si == null ? series.length : si;
+
+ opts = setDefault(opts, si, xSeriesOpts, ySeriesOpts);
+ series.splice(si, 0, opts);
+ initSeries(series[si], si);
+ }
+
+ self.addSeries = addSeries;
+
+ function delSeries(i) {
+ series.splice(i, 1);
+ showLegend && legendRows.splice(i, 1)[0][0].parentNode.remove();
+ cursorPts.length > 1 && cursorPts.splice(i, 1)[0].remove();
+
+ // TODO: de-init no-longer-needed scales?
+ }
+
+ self.delSeries = delSeries;
+
+ series.forEach(initSeries);
+
+ function initAxis(axis, i) {
+ axis._show = axis.show;
+
+ if (axis.show) {
+ var isVt = axis.side % 2;
+
+ var sc = scales[axis.scale];
+
+ // this can occur if all series specify non-default scales
+ if (sc == null) {
+ axis.scale = isVt ? series[1].scale : xScaleKey;
+ sc = scales[axis.scale];
+ }
+
+ // also set defaults for incrs & values based on axis distr
+ var isTime = sc.time;
+
+ axis.size = fnOrSelf(axis.size);
+ axis.space = fnOrSelf(axis.space);
+ axis.rotate = fnOrSelf(axis.rotate);
+ axis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? timeIncrs : numIncrs)));
+ axis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : numAxisSplits));
+
+ var av = axis.values;
+ axis.values = (
+ isTime ? (
+ isArr(av) ?
+ timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :
+ isStr(av) ?
+ timeAxisVal(_tzDate, av) :
+ av || _timeAxisVals
+ ) : av || numAxisVals
+ );
+
+ axis.filter = fnOrSelf(axis.filter || ( sc.distr == 3 ? logAxisValsFilt : retArg1));
+
+ axis.font = pxRatioFont(axis.font);
+ axis.labelFont = pxRatioFont(axis.labelFont);
+
+ axis._size = axis.size(self, null, i);
+
+ axis._space =
+ axis._rotate =
+ axis._incrs =
+ axis._found = // foundIncrSpace
+ axis._splits =
+ axis._values = null;
+ }
+ }
+
+ // set axis defaults
+ axes.forEach(initAxis);
+
+ var dataLen;
+ var dataIsGap;
+
+ // rendered data window
+ var i0 = null;
+ var i1 = null;
+ var idxs = series[0].idxs;
+
+ var data0 = null;
+
+ var viaAutoScaleX = false;
+
+ function setData(_data, _resetScales) {
+ if (!isArr(_data) && isObj(_data)) {
+ dataIsGap = _data.isGap;
+ _data = _data.data;
+ }
+
+ _data = _data || [];
+ _data[0] = _data[0] || [];
+
+ self.data = _data;
+ data = _data.slice();
+ data0 = data[0];
+ dataLen = data0.length;
+
+ if (xScaleDistr == 2)
+ { data[0] = data0.map(function (v, i) { return i; }); }
+
+ resetYSeries(true);
+
+ fire("setData");
+
+ if (_resetScales !== false) {
+ var xsc = scales[xScaleKey];
+
+ if (xsc.auto(self, viaAutoScaleX))
+ { autoScaleX(); }
+ else
+ { _setScale(xScaleKey, xsc.min, xsc.max); }
+
+ shouldSetCursor = true;
+ shouldSetLegend = true;
+ commit();
+ }
+ }
+
+ self.setData = setData;
+
+ function autoScaleX() {
+ var assign, assign$1;
+
+ viaAutoScaleX = true;
+
+ var _min, _max;
+
+ if (dataLen > 0) {
+ i0 = idxs[0] = 0;
+ i1 = idxs[1] = dataLen - 1;
+
+ _min = data[0][i0];
+ _max = data[0][i1];
+
+ if (xScaleDistr == 2) {
+ _min = i0;
+ _max = i1;
+ }
+ else if (dataLen == 1) {
+ if (xScaleDistr == 3)
+ { (assign = rangeLog(_min, _min, scales[xScaleKey].log, false), _min = assign[0], _max = assign[1]); }
+ else if (scales[xScaleKey].time)
+ { _max = _min + 86400; }
+ else
+ { (assign$1 = rangeNum(_min, _max, 0.1, true), _min = assign$1[0], _max = assign$1[1]); }
+ }
+ }
+ else {
+ i0 = idxs[0] = _min = null;
+ i1 = idxs[1] = _max = null;
+ }
+
+ _setScale(xScaleKey, _min, _max);
+
+ viaAutoScaleX = false;
+ }
+
+ function setCtxStyle(stroke, width, dash, fill) {
+ ctx.strokeStyle = stroke || transparent;
+ ctx.lineWidth = width;
+ ctx.lineJoin = "round";
+ ctx.setLineDash(dash || []);
+ ctx.fillStyle = fill || transparent;
+ }
+
+ function setScales() {
+ // log("setScales()", arguments);
+
+ // wip scales
+ var wipScales = copy(scales);
+
+ for (var k in wipScales) {
+ var wsc = wipScales[k];
+ var psc = pendScales[k];
+
+ if (psc != null && psc.min != null) {
+ assign(wsc, psc);
+
+ // explicitly setting the x-scale invalidates everything (acts as redraw)
+ if (k == xScaleKey)
+ { resetYSeries(true); }
+ }
+ else if (k != xScaleKey) {
+ if (dataLen == 0 && wsc.from == null) {
+ var minMax = wsc.range(self, null, null, k);
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+ }
+ else {
+ wsc.min = inf;
+ wsc.max = -inf;
+ }
+ }
+ }
+
+ if (dataLen > 0) {
+ // pre-range y-scales from y series' data values
+ series.forEach(function (s, i) {
+ var k = s.scale;
+ var wsc = wipScales[k];
+ var psc = pendScales[k];
+
+ if (i == 0) {
+ var minMax = wsc.range(self, wsc.min, wsc.max, k);
+
+ wsc.min = minMax[0];
+ wsc.max = minMax[1];
+
+ i0 = closestIdx(wsc.min, data[0]);
+ i1 = closestIdx(wsc.max, data[0]);
+
+ // closest indices can be outside of view
+ if (data[0][i0] < wsc.min)
+ { i0++; }
+ if (data[0][i1] > wsc.max)
+ { i1--; }
+
+ s.min = data0[i0];
+ s.max = data0[i1];
+ }
+ else if (s.show && s.auto && wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {
+ // only run getMinMax() for invalidated series data, else reuse
+ var minMax$1 = s.min == null ? getMinMax(data[i], i0, i1, s.sorted) : [s.min, s.max];
+
+ // initial min/max
+ wsc.min = min(wsc.min, s.min = minMax$1[0]);
+ wsc.max = max(wsc.max, s.max = minMax$1[1]);
+ }
+
+ s.idxs[0] = i0;
+ s.idxs[1] = i1;
+ });
+
+ // range independent scales
+ for (var k$1 in wipScales) {
+ var wsc$1 = wipScales[k$1];
+ var psc$1 = pendScales[k$1];
+
+ if (wsc$1.from == null && (psc$1 == null || psc$1.min == null)) {
+ var minMax$1 = wsc$1.range(
+ self,
+ wsc$1.min == inf ? null : wsc$1.min,
+ wsc$1.max == -inf ? null : wsc$1.max,
+ k$1
+ );
+ wsc$1.min = minMax$1[0];
+ wsc$1.max = minMax$1[1];
+ }
+ }
+ }
+
+ // range dependent scales
+ for (var k$2 in wipScales) {
+ var wsc$2 = wipScales[k$2];
+
+ if (wsc$2.from != null) {
+ var base = wipScales[wsc$2.from];
+ var minMax$2 = wsc$2.range(self, base.min, base.max, k$2);
+ wsc$2.min = minMax$2[0];
+ wsc$2.max = minMax$2[1];
+ }
+ }
+
+ var changed = {};
+ var anyChanged = false;
+
+ for (var k$3 in wipScales) {
+ var wsc$3 = wipScales[k$3];
+ var sc = scales[k$3];
+
+ if (sc.min != wsc$3.min || sc.max != wsc$3.max) {
+ sc.min = wsc$3.min;
+ sc.max = wsc$3.max;
+ changed[k$3] = anyChanged = true;
+ }
+ }
+
+ if (anyChanged) {
+ // invalidate paths of all series on changed scales
+ series.forEach(function (s) {
+ if (changed[s.scale])
+ { s._paths = null; }
+ });
+
+ for (var k$4 in changed) {
+ shouldConvergeSize = true;
+ fire("setScale", k$4);
+ }
+
+ if ( cursor.show)
+ { shouldSetCursor = true; }
+ }
+
+ for (var k$5 in pendScales)
+ { pendScales[k$5] = null; }
+ }
+
+ // TODO: drawWrap(si, drawPoints) (save, restore, translate, clip)
+
+ function drawPoints(si) {
+ // log("drawPoints()", arguments);
+
+ var s = series[si];
+ var p = s.points;
+
+ var width = roundDec(p.width * pxRatio, 3);
+ var offset = (width % 2) / 2;
+ var isStroked = p.width > 0;
+
+ var rad = (p.size - p.width) / 2 * pxRatio;
+ var dia = roundDec(rad * 2, 3);
+
+ ctx.translate(offset, offset);
+
+ ctx.save();
+
+ ctx.beginPath();
+ ctx.rect(
+ plotLft - dia,
+ plotTop - dia,
+ plotWid + dia * 2,
+ plotHgt + dia * 2
+ );
+ ctx.clip();
+
+ ctx.globalAlpha = s.alpha;
+
+ var path = new Path2D();
+
+ for (var pi = i0; pi <= i1; pi++) {
+ if (data[si][pi] != null) {
+ var x = round(getXPos(data[0][pi], scales[xScaleKey], plotWid, plotLft));
+ var y = round(getYPos(data[si][pi], scales[s.scale], plotHgt, plotTop));
+
+ path.moveTo(x + rad, y);
+ path.arc(x, y, rad, 0, PI * 2);
+ }
+ }
+
+ setCtxStyle(
+ p.stroke,
+ width,
+ null,
+ p.fill || (isStroked ? "#fff" : s.stroke)
+ );
+
+ ctx.fill(path);
+ isStroked && ctx.stroke(path);
+
+ ctx.globalAlpha = 1;
+
+ ctx.restore();
+
+ ctx.translate(-offset, -offset);
+ }
+
+ // grabs the nearest indices with y data outside of x-scale limits
+ function getOuterIdxs(ydata) {
+ var _i0 = clamp(i0 - 1, 0, dataLen - 1);
+ var _i1 = clamp(i1 + 1, 0, dataLen - 1);
+
+ while (ydata[_i0] == null && _i0 > 0)
+ { _i0--; }
+
+ while (ydata[_i1] == null && _i1 < dataLen - 1)
+ { _i1++; }
+
+ return [_i0, _i1];
+ }
+
+ var dir = 1;
+
+ function drawSeries() {
+ // path building loop must be before draw loop to ensure that all bands are fully constructed
+ series.forEach(function (s, i) {
+ if (i > 0 && s.show && s._paths == null) {
+ var _idxs = getOuterIdxs(data[i]);
+ s._paths = s.paths(self, i, _idxs[0], _idxs[1]);
+ }
+ });
+
+ series.forEach(function (s, i) {
+ if (i > 0 && s.show) {
+ if (s._paths)
+ { drawPath(i); }
+
+ if (s.points.show(self, i, i0, i1))
+ { drawPoints(i); }
+
+ fire("drawSeries", i);
+ }
+ });
+ }
+
+ function drawPath(si) {
+ var s = series[si];
+
+ if (dir == 1) {
+ var ref = s._paths;
+ var stroke = ref.stroke;
+ var fill = ref.fill;
+ var clip = ref.clip;
+ var width = roundDec(s[WIDTH] * pxRatio, 3);
+ var offset = (width % 2) / 2;
+
+ setCtxStyle(s.stroke, width, s.dash, s.fill);
+
+ ctx.globalAlpha = s.alpha;
+
+ ctx.translate(offset, offset);
+
+ ctx.save();
+
+ var lft = plotLft,
+ top = plotTop,
+ wid = plotWid,
+ hgt = plotHgt;
+
+ var halfWid = width * pxRatio / 2;
+
+ if (s.min == 0)
+ { hgt += halfWid; }
+
+ if (s.max == 0) {
+ top -= halfWid;
+ hgt += halfWid;
+ }
+
+ ctx.beginPath();
+ ctx.rect(lft, top, wid, hgt);
+ ctx.clip();
+
+ if (clip != null)
+ { ctx.clip(clip); }
+
+ if (s.band) {
+ ctx.fill(stroke);
+ width && ctx.stroke(stroke);
+ }
+ else {
+ width && ctx.stroke(stroke);
+
+ if (s.fill != null)
+ { ctx.fill(fill); }
+ }
+
+ ctx.restore();
+
+ ctx.translate(-offset, -offset);
+
+ ctx.globalAlpha = 1;
+ }
+
+ if (s.band)
+ { dir *= -1; }
+ }
+
+ function buildClip(is, gaps, nullHead, nullTail) {
+ var s = series[is];
+
+ var clip = null;
+
+ // create clip path (invert gaps and non-gaps)
+ if (gaps.length > 0 && !s.spanGaps) {
+ clip = new Path2D();
+
+ var prevGapEnd = plotLft;
+
+ for (var i = 0; i < gaps.length; i++) {
+ var g = gaps[i];
+
+ clip.rect(prevGapEnd, plotTop, g[0] - prevGapEnd, plotTop + plotHgt);
+
+ prevGapEnd = g[1];
+ }
+
+ clip.rect(prevGapEnd, plotTop, plotLft + plotWid - prevGapEnd, plotTop + plotHgt);
+ }
+
+ return clip;
+ }
+
+ function addGap(gaps, fromX, toX) {
+ if (toX > fromX) {
+ var prevGap = gaps[gaps.length - 1];
+
+ if (prevGap && prevGap[0] == fromX) // TODO: gaps must be encoded at stroke widths?
+ { prevGap[1] = toX; }
+ else
+ { gaps.push([fromX, toX]); }
+ }
+ }
+
+ function nonNullIdx(data, _i0, _i1, dir) {
+ for (var i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
+ if (data[i] != null)
+ { return i; }
+ }
+
+ return -1;
+ }
+
+ function buildPaths(self, is, _i0, _i1) {
+ var s = series[is];
+ var isGap = dataIsGap || s.isGap;
+
+ var xdata = data[0];
+ var ydata = data[is];
+ var scaleX = scales[xScaleKey];
+ var scaleY = scales[s.scale];
+
+ var _paths = dir == 1 ? {stroke: new Path2D(), fill: null, clip: null} : series[is-1]._paths;
+ var stroke = _paths.stroke;
+ var width = roundDec(s[WIDTH] * pxRatio, 3);
+
+ var minY = inf,
+ maxY = -inf,
+ outY, outX;
+
+ // todo: don't build gaps on dir = -1 pass
+ var gaps = [];
+
+ var accX = round(getXPos(xdata[dir == 1 ? _i0 : _i1], scaleX, plotWid, plotLft));
+ var accGaps = false;
+
+ // data edges
+ var lftIdx = nonNullIdx(ydata, _i0, _i1, 1);
+ var rgtIdx = nonNullIdx(ydata, _i0, _i1, -1);
+ var lftX = incrRound(getXPos(xdata[lftIdx], scaleX, plotWid, plotLft), 0.5);
+ var rgtX = incrRound(getXPos(xdata[rgtIdx], scaleX, plotWid, plotLft), 0.5);
+
+ if (lftX > plotLft)
+ { addGap(gaps, plotLft, lftX); }
+
+ // the moves the shape edge outside the canvas so stroke doesnt bleed in
+ if (s.band && dir == 1)
+ { stroke.lineTo(lftX - width * 2, round(getYPos(ydata[_i0], scaleY, plotHgt, plotTop))); }
+
+ for (var i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
+ var x = round(getXPos(xdata[i], scaleX, plotWid, plotLft));
+
+ if (x == accX) {
+ if (ydata[i] != null) {
+ outY = round(getYPos(ydata[i], scaleY, plotHgt, plotTop));
+ minY = min(outY, minY);
+ maxY = max(outY, maxY);
+ }
+ else if (!accGaps && isGap(self, is, i))
+ { accGaps = true; }
+ }
+ else {
+ var _addGap = false;
+
+ if (minY != inf) {
+ stroke.lineTo(accX, minY);
+ stroke.lineTo(accX, maxY);
+ stroke.lineTo(accX, outY);
+ outX = accX;
+ }
+ else if (accGaps) {
+ _addGap = true;
+ accGaps = false;
+ }
+
+ if (ydata[i] != null) {
+ outY = round(getYPos(ydata[i], scaleY, plotHgt, plotTop));
+ stroke.lineTo(x, outY);
+ minY = maxY = outY;
+
+ // prior pixel can have data but still start a gap if ends with null
+ if (x - accX > 1 && ydata[i-1] == null && isGap(self, is, i-1))
+ { _addGap = true; }
+ }
+ else {
+ minY = inf;
+ maxY = -inf;
+
+ if (!accGaps && isGap(self, is, i))
+ { accGaps = true; }
+ }
+
+ _addGap && addGap(gaps, outX, x);
+
+ accX = x;
+ }
+ }
+
+ if (rgtX < plotLft + plotWid)
+ { addGap(gaps, rgtX, plotLft + plotWid); }
+
+ if (s.band) {
+ var _x, _iy, ydata2;
+
+ // the moves the shape edge outside the canvas so stroke doesnt bleed in
+ if (dir == 1) {
+ _x = rgtX + width * 2;
+ _iy = rgtIdx;
+ ydata2 = data[is + 1];
+ }
+ else {
+ _x = lftX - width * 2;
+ _iy = lftIdx;
+ ydata2 = data[is - 1];
+ }
+
+ stroke.lineTo(_x, round(getYPos(ydata[_iy], scaleY, plotHgt, plotTop)));
+ stroke.lineTo(_x, round(getYPos(ydata2[_iy], scaleY, plotHgt, plotTop)));
+ }
+
+ if (dir == 1) {
+ _paths.clip = buildClip(is, gaps, ydata[_i0] == null, ydata[_i1] == null);
+
+ if (s.fill != null) {
+ var fill = _paths.fill = new Path2D(stroke);
+
+ var fillTo = round(getYPos(s.fillTo(self, is, s.min, s.max), scaleY, plotHgt, plotTop));
+ fill.lineTo(rgtX, fillTo);
+ fill.lineTo(lftX, fillTo);
+ }
+ }
+
+ if (s.band)
+ { dir *= -1; }
+
+ return _paths;
+ }
+
+ self.paths = buildPaths;
+
+ function getIncrSpace(axisIdx, min, max, fullDim) {
+ var axis = axes[axisIdx];
+
+ var incrSpace;
+
+ if (fullDim <= 0)
+ { incrSpace = [0, 0]; }
+ else {
+ var minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);
+ var incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);
+ incrSpace = axis._found = findIncr(min, max, incrs, fullDim, minSpace);
+ }
+
+ return incrSpace;
+ }
+
+ function drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash) {
+ var offset = (width % 2) / 2;
+
+ ctx.translate(offset, offset);
+
+ setCtxStyle(stroke, width, dash);
+
+ ctx.beginPath();
+
+ var x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);
+
+ if (ori == 0) {
+ y0 = pos0;
+ y1 = pos1;
+ }
+ else {
+ x0 = pos0;
+ x1 = pos1;
+ }
+
+ offs.forEach(function (off, i) {
+ if (filts[i] == null)
+ { return; }
+
+ if (ori == 0)
+ { x0 = x1 = off; }
+ else
+ { y0 = y1 = off; }
+
+ ctx.moveTo(x0, y0);
+ ctx.lineTo(x1, y1);
+ });
+
+ ctx.stroke();
+
+ ctx.translate(-offset, -offset);
+ }
+
+ function axesCalc() {
+ // log("axesCalc()", arguments);
+
+ var converged = true;
+
+ axes.forEach(function (axis, i) {
+ if (!axis.show)
+ { return; }
+
+ var scale = scales[axis.scale];
+
+ if (scale.min == null) {
+ if (axis._show) {
+ converged = false;
+ axis._show = false;
+ resetYSeries(false);
+ }
+ return;
+ }
+ else {
+ if (!axis._show) {
+ converged = false;
+ axis._show = true;
+ resetYSeries(false);
+ }
+ }
+
+ var side = axis.side;
+ var ori = side % 2;
+
+ var min = scale.min;
+ var max = scale.max; // // should this toggle them ._show = false
+
+ var ref = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);
+ var _incr = ref[0];
+ var _space = ref[1];
+
+ if (_space == 0)
+ { return; }
+
+ // if we're using index positions, force first tick to match passed index
+ var forceMin = scale.distr == 2;
+
+ var _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);
+
+ // tick labels
+ // BOO this assumes a specific data/series
+ var splits = scale.distr == 2 ? _splits.map(function (i) { return data0[i]; }) : _splits;
+ var incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
+
+ var values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);
+
+ // rotating of labels only supported on bottom x axis
+ axis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;
+
+ var oldSize = axis._size;
+
+ axis._size = axis.size(self, values, i);
+
+ if (oldSize != null && axis._size != oldSize) // ready && ?
+ { converged = false; }
+ });
+
+ return converged;
+ }
+
+ function guttersCalc() {
+ var converged = true;
+
+ var _x = gutters._x;
+ var _y = gutters._y;
+
+ gutters._x = gutters.x(self);
+ gutters._y = gutters.y(self);
+
+ if (gutters._x != _x || gutters._y != _y)
+ { converged = false; }
+
+ return converged;
+ }
+
+ function drawAxesGrid() {
+ axes.forEach(function (axis, i) {
+ if (!axis.show || !axis._show)
+ { return; }
+
+ var scale = scales[axis.scale];
+ var side = axis.side;
+ var ori = side % 2;
+
+ var getPos = ori == 0 ? getXPos : getYPos;
+ var plotDim = ori == 0 ? plotWid : plotHgt;
+ var plotOff = ori == 0 ? plotLft : plotTop;
+
+ var axisGap = round(axis.gap * pxRatio);
+
+ var ticks = axis.ticks;
+ var tickSize = ticks.show ? round(ticks.size * pxRatio) : 0;
+
+ var ref = axis._found;
+ var _incr = ref[0];
+ var _space = ref[1];
+ var _splits = axis._splits;
+
+ // tick labels
+ // BOO this assumes a specific data/series
+ var splits = scale.distr == 2 ? _splits.map(function (i) { return data0[i]; }) : _splits;
+ var incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
+
+ // rotating of labels only supported on bottom x axis
+ var angle = axis._rotate * -PI/180;
+
+ var basePos = round(axis._pos * pxRatio);
+ var shiftAmt = tickSize + axisGap;
+ var shiftDir = ori == 0 && side == 0 || ori == 1 && side == 3 ? -1 : 1;
+ var finalPos = basePos + shiftAmt * shiftDir;
+ var y = ori == 0 ? finalPos : 0;
+ var x = ori == 1 ? finalPos : 0;
+
+ ctx.font = axis.font[0];
+ ctx.fillStyle = axis.stroke || hexBlack; // rgba?
+ ctx.textAlign = axis.align == 1 ? LEFT :
+ axis.align == 2 ? RIGHT :
+ angle > 0 ? LEFT :
+ angle < 0 ? RIGHT :
+ ori == 0 ? "center" : side == 3 ? RIGHT : LEFT;
+ ctx.textBaseline = angle ||
+ ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM;
+
+ var lineHeight = axis.font[1] * lineMult;
+
+ var canOffs = _splits.map(function (val) { return round(getPos(val, scale, plotDim, plotOff)); });
+
+ axis._values.forEach(function (val, i) {
+ if (val == null)
+ { return; }
+
+ if (ori == 0)
+ { x = canOffs[i]; }
+ else
+ { y = canOffs[i]; }
+
+ (""+val).split(/\n/gm).forEach(function (text, j) {
+ if (angle) {
+ ctx.save();
+ ctx.translate(x, y + j * lineHeight);
+ ctx.rotate(angle);
+ ctx.fillText(text, 0, 0);
+ ctx.restore();
+ }
+ else
+ { ctx.fillText(text, x, y + j * lineHeight); }
+ });
+ });
+
+ // axis label
+ if (axis.label) {
+ ctx.save();
+
+ var baseLpos = round(axis._lpos * pxRatio);
+
+ if (ori == 1) {
+ x = y = 0;
+
+ ctx.translate(
+ baseLpos,
+ round(plotTop + plotHgt / 2)
+ );
+ ctx.rotate((side == 3 ? -PI : PI) / 2);
+
+ }
+ else {
+ x = round(plotLft + plotWid / 2);
+ y = baseLpos;
+ }
+
+ ctx.font = axis.labelFont[0];
+ // ctx.fillStyle = axis.labelStroke || hexBlack; // rgba?
+ ctx.textAlign = "center";
+ ctx.textBaseline = side == 2 ? TOP : BOTTOM;
+
+ ctx.fillText(axis.label, x, y);
+
+ ctx.restore();
+ }
+
+ // ticks
+ if (ticks.show) {
+ drawOrthoLines(
+ canOffs,
+ ticks.filter(self, splits, i, _space, incr),
+ ori,
+ side,
+ basePos,
+ tickSize,
+ roundDec(ticks[WIDTH] * pxRatio, 3),
+ ticks.stroke
+ );
+ }
+
+ // grid
+ var grid = axis.grid;
+
+ if (grid.show) {
+ drawOrthoLines(
+ canOffs,
+ grid.filter(self, splits, i, _space, incr),
+ ori,
+ ori == 0 ? 2 : 1,
+ ori == 0 ? plotTop : plotLft,
+ ori == 0 ? plotHgt : plotWid,
+ roundDec(grid[WIDTH] * pxRatio, 3),
+ grid.stroke,
+ grid.dash
+ );
+ }
+ });
+
+ fire("drawAxes");
+ }
+
+ function resetYSeries(minMax) {
+ // log("resetYSeries()", arguments);
+
+ series.forEach(function (s, i) {
+ if (i > 0) {
+ s._paths = null;
+
+ if (minMax) {
+ s.min = null;
+ s.max = null;
+ }
+ }
+ });
+ }
+
+ var queuedCommit = false;
+
+ // could do rAF instead of microTask, or Promose.resolve().then()
+ function commit() {
+ if (!queuedCommit) {
+ microTask(_commit);
+ queuedCommit = true;
+ }
+ }
+
+ function _commit() {
+ // log("_commit()", arguments);
+
+ if (shouldSetScales) {
+ setScales();
+ shouldSetScales = false;
+ }
+
+ if (shouldConvergeSize) {
+ convergeSize();
+ shouldConvergeSize = false;
+ }
+
+ if (shouldSetSize) {
+ setStylePx(under, LEFT, plotLftCss);
+ setStylePx(under, TOP, plotTopCss);
+ setStylePx(under, WIDTH, plotWidCss);
+ setStylePx(under, HEIGHT, plotHgtCss);
+
+ setStylePx(over, LEFT, plotLftCss);
+ setStylePx(over, TOP, plotTopCss);
+ setStylePx(over, WIDTH, plotWidCss);
+ setStylePx(over, HEIGHT, plotHgtCss);
+
+ setStylePx(wrap, WIDTH, fullWidCss);
+ setStylePx(wrap, HEIGHT, fullHgtCss);
+
+ can[WIDTH] = round(fullWidCss * pxRatio);
+ can[HEIGHT] = round(fullHgtCss * pxRatio);
+
+ syncRect();
+
+ fire("setSize");
+
+ shouldSetSize = false;
+ }
+
+ // if (shouldSetSelect) {
+ // TODO: update .u-select metrics (if visible)
+ // setStylePx(selectDiv, TOP, select[TOP] = 0);
+ // setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ // setStylePx(selectDiv, WIDTH, select[WIDTH] = 0);
+ // setStylePx(selectDiv, HEIGHT, select[HEIGHT] = 0);
+ // shouldSetSelect = false;
+ // }
+
+ if ( cursor.show && shouldSetCursor) {
+ updateCursor();
+ shouldSetCursor = false;
+ }
+
+ // if (true && legend.show && legend.live && shouldSetLegend) {}
+
+ if (fullWidCss > 0 && fullHgtCss > 0) {
+ ctx.clearRect(0, 0, can[WIDTH], can[HEIGHT]);
+ fire("drawClear");
+ drawAxesGrid();
+ dataLen > 0 && drawSeries();
+ fire("draw");
+ }
+
+ if (!ready) {
+ ready = true;
+ self.status = 1;
+
+ fire("ready");
+ }
+
+ queuedCommit = false;
+ }
+
+ self.redraw = function (rebuildPaths) {
+ if (rebuildPaths !== false)
+ { _setScale(xScaleKey, scales[xScaleKey].min, scales[xScaleKey].max); }
+ else
+ { commit(); }
+ };
+
+ // redraw() => setScale('x', scales.x.min, scales.x.max);
+
+ // explicit, never re-ranged (is this actually true? for x and y)
+ function setScale(key, opts) {
+ var sc = scales[key];
+
+ if (sc.from == null) {
+ if (dataLen == 0) {
+ var minMax = sc.range(self, opts.min, opts.max, key);
+ opts.min = minMax[0];
+ opts.max = minMax[1];
+ }
+
+ if (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)
+ { return; }
+
+ if (key == xScaleKey) {
+ if (sc.distr == 2 && dataLen > 0) {
+ opts.min = closestIdx(opts.min, data[0]);
+ opts.max = closestIdx(opts.max, data[0]);
+ }
+ }
+
+ // log("setScale()", arguments);
+
+ pendScales[key] = opts;
+
+ shouldSetScales = true;
+ commit();
+ }
+ }
+
+ self.setScale = setScale;
+
+ // INTERACTION
+
+ var vt;
+ var hz;
+
+ // starting position before cursor.move
+ var rawMouseLeft0;
+ var rawMouseTop0;
+
+ // starting position
+ var mouseLeft0;
+ var mouseTop0;
+
+ // current position before cursor.move
+ var rawMouseLeft1;
+ var rawMouseTop1;
+
+ // current position
+ var mouseLeft1;
+ var mouseTop1;
+
+ var dragging = false;
+
+ var drag = cursor.drag;
+
+ var dragX = drag.x;
+ var dragY = drag.y;
+
+ if ( cursor.show) {
+ if (cursor.x) {
+ mouseLeft1 = cursor.left;
+ vt = placeDiv(CURSOR_X, over);
+ }
+
+ if (cursor.y) {
+ mouseTop1 = cursor.top;
+ hz = placeDiv(CURSOR_Y, over);
+ }
+ }
+
+ var select = self.select = assign({
+ show: true,
+ over: true,
+ left: 0,
+ width: 0,
+ top: 0,
+ height: 0,
+ }, opts.select);
+
+ var selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;
+
+ function setSelect(opts, _fire) {
+ if (select.show) {
+ for (var prop in opts)
+ { setStylePx(selectDiv, prop, select[prop] = opts[prop]); }
+
+ _fire !== false && fire("setSelect");
+ }
+ }
+
+ self.setSelect = setSelect;
+
+ function toggleDOM(i, onOff) {
+ var s = series[i];
+ var label = showLegend ? legendRows[i][0].parentNode : null;
+
+ if (s.show)
+ { label && remClass(label, OFF); }
+ else {
+ label && addClass(label, OFF);
+ cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
+ }
+ }
+
+ function _setScale(key, min, max) {
+ setScale(key, {min: min, max: max});
+ }
+
+ function setSeries(i, opts, pub) {
+ // log("setSeries()", arguments);
+
+ var s = series[i];
+
+ // will this cause redundant commit() if both show and focus are set?
+ if (opts.focus != null)
+ { setFocus(i); }
+
+ if (opts.show != null) {
+ s.show = opts.show;
+ toggleDOM(i, opts.show);
+
+ if (s.band) {
+ // not super robust, will break if two bands are adjacent
+ var ip = series[i+1] && series[i+1].band ? i+1 : i-1;
+ series[ip].show = s.show;
+ toggleDOM(ip, opts.show);
+ }
+
+ _setScale(s.scale, null, null);
+ commit();
+ }
+
+ fire("setSeries", i, opts);
+
+ pub && sync.pub("setSeries", self, i, opts);
+ }
+
+ self.setSeries = setSeries;
+
+ function _alpha(i, value) {
+ series[i].alpha = value;
+
+ if ( cursor.show && cursorPts[i])
+ { cursorPts[i].style.opacity = value; }
+
+ if ( showLegend && legendRows[i])
+ { legendRows[i][0].parentNode.style.opacity = value; }
+ }
+
+ function _setAlpha(i, value) {
+ var s = series[i];
+
+ _alpha(i, value);
+
+ if (s.band) {
+ // not super robust, will break if two bands are adjacent
+ var ip = series[i+1].band ? i+1 : i-1;
+ _alpha(ip, value);
+ }
+ }
+
+ // y-distance
+ var closestDist;
+ var closestSeries;
+ var focusedSeries;
+
+ function setFocus(i) {
+ if (i != focusedSeries) {
+ // log("setFocus()", arguments);
+
+ series.forEach(function (s, i2) {
+ _setAlpha(i2, i == null || i2 == 0 || i2 == i ? 1 : focus.alpha);
+ });
+
+ focusedSeries = i;
+ commit();
+ }
+ }
+
+ if (showLegend && cursorFocus) {
+ on(mouseleave, legendEl, function (e) {
+ if (cursor._lock)
+ { return; }
+ setSeries(null, {focus: false}, syncOpts.setSeries);
+ updateCursor();
+ });
+ }
+
+ function scaleValueAtPos(pos, scale) {
+ var dim = plotWidCss;
+
+ if (scale != xScaleKey) {
+ dim = plotHgtCss;
+ pos = dim - pos;
+ }
+
+ var pct = pos / dim;
+
+ var sc = scales[scale],
+ _min = sc.min,
+ _max = sc.max;
+
+ if (sc.distr == 3) {
+ _min = log10(_min);
+ _max = log10(_max);
+ return pow(10, _min + (_max - _min) * pct);
+ }
+ else
+ { return _min + (_max - _min) * pct; }
+ }
+
+ function closestIdxFromXpos(pos) {
+ var v = scaleValueAtPos(pos, xScaleKey);
+ return closestIdx(v, data[0], i0, i1);
+ }
+
+ self.valToIdx = function (val) { return closestIdx(val, data[0]); };
+ self.posToIdx = closestIdxFromXpos;
+ self.posToVal = scaleValueAtPos;
+ self.valToPos = function (val, scale, can) { return (
+ scale == xScaleKey ?
+ getXPos(val, scales[scale],
+ can ? plotWid : plotWidCss,
+ can ? plotLft : 0
+ ) :
+ getYPos(val, scales[scale],
+ can ? plotHgt : plotHgtCss,
+ can ? plotTop : 0
+ )
+ ); };
+
+ // defers calling expensive functions
+ function batch(fn) {
+ fn(self);
+ commit();
+ }
+
+ self.batch = batch;
+
+ (self.setCursor = function (opts) {
+ mouseLeft1 = opts.left;
+ mouseTop1 = opts.top;
+ // assign(cursor, opts);
+ updateCursor();
+ });
+
+ var cursorRaf = 0;
+
+ function updateCursor(ts, src) {
+ var assign;
+
+ // ts == null && log("updateCursor()", arguments);
+
+ cursorRaf = 0;
+
+ rawMouseLeft1 = mouseLeft1;
+ rawMouseTop1 = mouseTop1;
+
+ (assign = cursor.move(self, mouseLeft1, mouseTop1), mouseLeft1 = assign[0], mouseTop1 = assign[1]);
+
+ if (cursor.show) {
+ cursor.x && trans(vt, round(mouseLeft1), 0, plotWidCss, plotHgtCss);
+ cursor.y && trans(hz, 0, round(mouseTop1), plotWidCss, plotHgtCss);
+ }
+
+ var idx;
+
+ // when zooming to an x scale range between datapoints the binary search
+ // for nearest min/max indices results in this condition. cheap hack :D
+ var noDataInRange = i0 > i1;
+
+ closestDist = inf;
+
+ // if cursor hidden, hide points & clear legend vals
+ if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {
+ idx = null;
+
+ for (var i = 0; i < series.length; i++) {
+ if (i > 0) {
+ cursorPts.length > 1 && trans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
+ }
+
+ if (showLegend && legend.live) {
+ if (i == 0 && multiValLegend)
+ { continue; }
+
+ for (var j = 0; j < legendRows[i].length; j++)
+ { legendRows[i][j][firstChild].nodeValue = '--'; }
+ }
+ }
+
+ if (cursorFocus)
+ { setSeries(null, {focus: true}, syncOpts.setSeries); }
+ }
+ else {
+ // let pctY = 1 - (y / rect[HEIGHT]);
+
+ var valAtPos = scaleValueAtPos(mouseLeft1, xScaleKey);
+
+ idx = closestIdx(valAtPos, data[0], i0, i1);
+
+ var scX = scales[xScaleKey];
+
+ var xPos = roundDec(getXPos(data[0][idx], scX, plotWidCss, 0), 3);
+
+ for (var i$1 = 0; i$1 < series.length; i$1++) {
+ var s = series[i$1];
+
+ var idx2 = cursor.dataIdx(self, i$1, idx, valAtPos);
+ var xPos2 = idx2 == idx ? xPos : roundDec(getXPos(data[0][idx2], scX, plotWidCss, 0), 3);
+
+ if (i$1 > 0 && s.show) {
+ var valAtIdx = data[i$1][idx2];
+
+ var yPos = valAtIdx == null ? -10 : roundDec(getYPos(valAtIdx, scales[s.scale], plotHgtCss, 0), 3);
+
+ if (yPos > 0) {
+ var dist = abs(yPos - mouseTop1);
+
+ if (dist <= closestDist) {
+ closestDist = dist;
+ closestSeries = i$1;
+ }
+ }
+
+ cursorPts.length > 1 && trans(cursorPts[i$1], xPos2, yPos, plotWidCss, plotHgtCss);
+ }
+
+ if (showLegend && legend.live) {
+ if ((idx2 == cursor.idx && !shouldSetLegend) || i$1 == 0 && multiValLegend)
+ { continue; }
+
+ var src$1 = i$1 == 0 && xScaleDistr == 2 ? data0 : data[i$1];
+
+ var vals = multiValLegend ? s.values(self, i$1, idx2) : {_: s.value(self, src$1[idx2], i$1, idx2)};
+
+ var j$1 = 0;
+
+ for (var k in vals)
+ { legendRows[i$1][j$1++][firstChild].nodeValue = vals[k]; }
+ }
+ }
+
+ shouldSetLegend = false;
+ }
+
+ // nit: cursor.drag.setSelect is assumed always true
+ if (select.show && dragging) {
+ if (src != null) {
+ var ref = syncOpts.scales;
+ var xKey = ref[0];
+ var yKey = ref[1];
+
+ // match the dragX/dragY implicitness/explicitness of src
+ var sdrag = src.cursor.drag;
+ dragX = sdrag._x;
+ dragY = sdrag._y;
+
+ if (xKey) {
+ var sc = scales[xKey];
+ var srcLeft = src.posToVal(src.select[LEFT], xKey);
+ var srcRight = src.posToVal(src.select[LEFT] + src.select[WIDTH], xKey);
+
+ select[LEFT] = getXPos(srcLeft, sc, plotWidCss, 0);
+ select[WIDTH] = abs(select[LEFT] - getXPos(srcRight, sc, plotWidCss, 0));
+
+ setStylePx(selectDiv, LEFT, select[LEFT]);
+ setStylePx(selectDiv, WIDTH, select[WIDTH]);
+
+ if (!yKey) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (yKey) {
+ var sc$1 = scales[yKey];
+ var srcTop = src.posToVal(src.select[TOP], yKey);
+ var srcBottom = src.posToVal(src.select[TOP] + src.select[HEIGHT], yKey);
+
+ select[TOP] = getYPos(srcTop, sc$1, plotHgtCss, 0);
+ select[HEIGHT] = abs(select[TOP] - getYPos(srcBottom, sc$1, plotHgtCss, 0));
+
+ setStylePx(selectDiv, TOP, select[TOP]);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT]);
+
+ if (!xKey) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
+ }
+ else {
+ var rawDX = abs(rawMouseLeft1 - rawMouseLeft0);
+ var rawDY = abs(rawMouseTop1 - rawMouseTop0);
+
+ dragX = drag.x && rawDX >= drag.dist;
+ dragY = drag.y && rawDY >= drag.dist;
+
+ var uni = drag.uni;
+
+ if (uni != null) {
+ // only calc drag status if they pass the dist thresh
+ if (dragX && dragY) {
+ dragX = rawDX >= uni;
+ dragY = rawDY >= uni;
+
+ // force unidirectionality when both are under uni limit
+ if (!dragX && !dragY) {
+ if (rawDY > rawDX)
+ { dragY = true; }
+ else
+ { dragX = true; }
+ }
+ }
+ }
+ else if (drag.x && drag.y && (dragX || dragY))
+ // if omni with no uni then both dragX / dragY should be true if either is true
+ { dragX = dragY = true; }
+
+ if (dragX) {
+ var minX = min(mouseLeft0, mouseLeft1);
+ var dx = abs(mouseLeft1 - mouseLeft0);
+
+ setStylePx(selectDiv, LEFT, select[LEFT] = minX);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = dx);
+
+ if (!dragY) {
+ setStylePx(selectDiv, TOP, select[TOP] = 0);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = plotHgtCss);
+ }
+ }
+
+ if (dragY) {
+ var minY = min(mouseTop0, mouseTop1);
+ var dy = abs(mouseTop1 - mouseTop0);
+
+ setStylePx(selectDiv, TOP, select[TOP] = minY);
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = dy);
+
+ if (!dragX) {
+ setStylePx(selectDiv, LEFT, select[LEFT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = plotWidCss);
+ }
+ }
+
+ if (!dragX && !dragY) {
+ // the drag didn't pass the dist requirement
+ setStylePx(selectDiv, HEIGHT, select[HEIGHT] = 0);
+ setStylePx(selectDiv, WIDTH, select[WIDTH] = 0);
+ }
+ }
+ }
+
+ cursor.idx = idx;
+ cursor.left = mouseLeft1;
+ cursor.top = mouseTop1;
+ drag._x = dragX;
+ drag._y = dragY;
+
+ // if ts is present, means we're implicitly syncing own cursor as a result of debounced rAF
+ if (ts != null) {
+ // this is not technically a "mousemove" event, since it's debounced, rename to setCursor?
+ // since this is internal, we can tweak it later
+ sync.pub(mousemove, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, idx);
+
+ if (cursorFocus) {
+ setSeries(closestDist <= focus.prox ? closestSeries : null, {focus: true}, syncOpts.setSeries);
+ }
+ }
+
+ ready && fire("setCursor");
+ }
+
+ var rect = null;
+
+ function syncRect() {
+ rect = over.getBoundingClientRect();
+ }
+
+ function mouseMove(e, src, _x, _y, _w, _h, _i) {
+ if (cursor._lock)
+ { return; }
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, false, e != null);
+
+ if (e != null) {
+ if (cursorRaf == 0)
+ { cursorRaf = rAF(updateCursor); }
+ }
+ else
+ { updateCursor(null, src); }
+ }
+
+ function cacheMouse(e, src, _x, _y, _w, _h, _i, initial, snap) {
+ var assign;
+
+ if (e != null) {
+ _x = e.clientX - rect.left;
+ _y = e.clientY - rect.top;
+ }
+ else {
+ if (_x < 0 || _y < 0) {
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+ return;
+ }
+
+ var ref = syncOpts.scales;
+ var xKey = ref[0];
+ var yKey = ref[1];
+
+ if (xKey != null)
+ { _x = getXPos(src.posToVal(_x, xKey), scales[xKey], plotWidCss, 0); }
+ else
+ { _x = plotWidCss * (_x/_w); }
+
+ if (yKey != null)
+ { _y = getYPos(src.posToVal(_y, yKey), scales[yKey], plotHgtCss, 0); }
+ else
+ { _y = plotHgtCss * (_y/_h); }
+ }
+
+ if (snap) {
+ if (_x <= 1 || _x >= plotWidCss - 1)
+ { _x = incrRound(_x, plotWidCss); }
+
+ if (_y <= 1 || _y >= plotHgtCss - 1)
+ { _y = incrRound(_y, plotHgtCss); }
+ }
+
+ if (initial) {
+ rawMouseLeft0 = _x;
+ rawMouseTop0 = _y;
+
+ (assign = cursor.move(self, _x, _y), mouseLeft0 = assign[0], mouseTop0 = assign[1]);
+ }
+ else {
+ mouseLeft1 = _x;
+ mouseTop1 = _y;
+ }
+ }
+
+ function hideSelect() {
+ setSelect({
+ width: 0,
+ height: 0,
+ }, false);
+ }
+
+ function mouseDown(e, src, _x, _y, _w, _h, _i) {
+ dragging = true;
+ dragX = dragY = drag._x = drag._y = false;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, true, false);
+
+ if (e != null) {
+ onMouse(mouseup, doc, mouseUp);
+ sync.pub(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);
+ }
+ }
+
+ function mouseUp(e, src, _x, _y, _w, _h, _i) {
+ dragging = drag._x = drag._y = false;
+
+ cacheMouse(e, src, _x, _y, _w, _h, _i, false, true);
+
+ var hasSelect = select[WIDTH] > 0 || select[HEIGHT] > 0;
+
+ hasSelect && setSelect(select);
+
+ if (drag.setScale && hasSelect) {
+ // if (syncKey != null) {
+ // dragX = drag.x;
+ // dragY = drag.y;
+ // }
+
+ if (dragX) {
+ _setScale(xScaleKey,
+ scaleValueAtPos(select[LEFT], xScaleKey),
+ scaleValueAtPos(select[LEFT] + select[WIDTH], xScaleKey)
+ );
+ }
+
+ if (dragY) {
+ for (var k in scales) {
+ var sc = scales[k];
+
+ if (k != xScaleKey && sc.from == null && sc.min != inf) {
+ _setScale(k,
+ scaleValueAtPos(select[TOP] + select[HEIGHT], k),
+ scaleValueAtPos(select[TOP], k)
+ );
+ }
+ }
+ }
+
+ hideSelect();
+ }
+ else if (cursor.lock) {
+ cursor._lock = !cursor._lock;
+
+ if (!cursor._lock)
+ { updateCursor(); }
+ }
+
+ if (e != null) {
+ offMouse(mouseup, doc);
+ sync.pub(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
+ }
+ }
+
+ function mouseLeave(e, src, _x, _y, _w, _h, _i) {
+ if (!cursor._lock) {
+ var _dragging = dragging;
+
+ if (dragging) {
+ // handle case when mousemove aren't fired all the way to edges by browser
+ var snapX = true;
+ var snapY = true;
+ var snapProx = 10;
+
+ if (dragX && dragY) {
+ // maybe omni corner snap
+ snapX = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;
+ snapY = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;
+ }
+
+ if (dragX && snapX) {
+ var dLft = mouseLeft1;
+ var dRgt = plotWidCss - mouseLeft1;
+
+ var xMin = min(dLft, dRgt);
+
+ if (xMin == dLft)
+ { mouseLeft1 = 0; }
+ if (xMin == dRgt)
+ { mouseLeft1 = plotWidCss; }
+ }
+
+ if (dragY && snapY) {
+ var dTop = mouseTop1;
+ var dBtm = plotHgtCss - mouseTop1;
+
+ var yMin = min(dTop, dBtm);
+
+ if (yMin == dTop)
+ { mouseTop1 = 0; }
+ if (yMin == dBtm)
+ { mouseTop1 = plotHgtCss; }
+ }
+
+ updateCursor(1);
+
+ dragging = false;
+ }
+
+ mouseLeft1 = -10;
+ mouseTop1 = -10;
+
+ // passing a non-null timestamp to force sync/mousemove event
+ updateCursor(1);
+
+ if (_dragging)
+ { dragging = _dragging; }
+ }
+ }
+
+ function dblClick(e, src, _x, _y, _w, _h, _i) {
+ autoScaleX();
+
+ hideSelect();
+
+ if (e != null)
+ { sync.pub(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null); }
+ }
+
+ // internal pub/sub
+ var events = {};
+
+ events[mousedown] = mouseDown;
+ events[mousemove] = mouseMove;
+ events[mouseup] = mouseUp;
+ events[dblclick] = dblClick;
+ events["setSeries"] = function (e, src, idx, opts) {
+ setSeries(idx, opts);
+ };
+
+ var deb;
+
+ if ( cursor.show) {
+ onMouse(mousedown, over, mouseDown);
+ onMouse(mousemove, over, mouseMove);
+ onMouse(mouseenter, over, syncRect);
+ // this has to be rAF'd so it always fires after the last queued/rAF'd updateCursor
+ onMouse(mouseleave, over, function (e) { rAF(mouseLeave); });
+
+ onMouse(dblclick, over, dblClick);
+
+ deb = debounce(syncRect, 100);
+
+ on(resize, win, deb);
+ on(scroll, win, deb);
+
+ self.syncRect = syncRect;
+ }
+
+ // external on/off
+ var hooks = self.hooks = opts.hooks || {};
+
+ function fire(evName, a1, a2) {
+ if (evName in hooks) {
+ hooks[evName].forEach(function (fn) {
+ fn.call(null, self, a1, a2);
+ });
+ }
+ }
+
+ (opts.plugins || []).forEach(function (p) {
+ for (var evName in p.hooks)
+ { hooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]); }
+ });
+
+ var syncOpts = assign({
+ key: null,
+ setSeries: false,
+ scales: [xScaleKey, null]
+ }, cursor.sync);
+
+ var syncKey = syncOpts.key;
+
+ var sync = (syncKey != null ? (syncs[syncKey] = syncs[syncKey] || _sync()) : _sync());
+
+ sync.sub(self);
+
+ function pub(type, src, x, y, w, h, i) {
+ events[type](null, src, x, y, w, h, i);
+ }
+
+ (self.pub = pub);
+
+ function destroy() {
+ sync.unsub(self);
+ off(resize, win, deb);
+ off(scroll, win, deb);
+ root.remove();
+ fire("destroy");
+ }
+
+ self.destroy = destroy;
+
+ function _init() {
+ fire("init", opts, data);
+
+ setData(data || opts.data, false);
+
+ if (pendScales[xScaleKey])
+ { setScale(xScaleKey, pendScales[xScaleKey]); }
+ else
+ { autoScaleX(); }
+
+ _setSize(opts[WIDTH], opts[HEIGHT]);
+
+ setSelect(select, false);
+ }
+
+ if (then) {
+ if (then instanceof HTMLElement) {
+ then.appendChild(root);
+ _init();
+ }
+ else
+ { then(self, _init); }
+ }
+ else
+ { _init(); }
+
+ return self;
+ }
+
+ uPlot.assign = assign;
+ uPlot.fmtNum = fmtNum;
+ uPlot.rangeNum = rangeNum;
+ uPlot.rangeLog = rangeLog;
+
+ {
+ uPlot.fmtDate = fmtDate;
+ uPlot.tzDate = tzDate;
+ }
+
+ return uPlot;
+
+}());
diff --git a/plugins/uplot/uPlot.iife.min.js b/plugins/uplot/uPlot.iife.min.js
new file mode 100644
index 000000000..fccbf164b
--- /dev/null
+++ b/plugins/uplot/uPlot.iife.min.js
@@ -0,0 +1,2 @@
+/*! https://github.com/leeoniya/uPlot (v1.4.4) */
+var uPlot=function(){"use strict";function n(n,t,e,r){var i;e=e||0;for(var o=2147483647>=(r=r||t.length-1);r-e>1;)n>t[i=o?e+r>>1:v((e+r)/2)]?e=i:r=i;return n-t[e]>t[r]-n?r:e}var t=[0,0];function e(n,e,r,i){return t[0]=0>r?D(n,-r):n,t[1]=0>i?D(e,-i):e,t}function r(n,t,r,i){var o,l,a,u=10==r?w:x;return n==t&&(n/=r,t*=r),i?(o=v(u(n)),l=m(u(t)),n=(a=e(g(r,o),g(r,l),o,l))[0],t=a[1]):(o=v(u(n)),l=v(u(t)),n=z(n,(a=e(g(r,o),g(r,l),o,l))[0]),t=T(t,a[1])),[n,t]}var i={pad:0,soft:null,mode:0},o={min:i,max:i};function l(n,t,e,r){return H(e)?u(n,t,e):(i.pad=e,i.soft=r?0:null,i.mode=r?2:0,u(n,t,o))}function a(n,t){return null==n?t:n}function u(n,t,e){var r=e.min,i=e.max,o=a(r.pad,0),l=a(i.pad,0),u=a(r.hard,-b),s=a(i.hard,b),f=a(r.soft,b),h=a(i.soft,-b),m=a(r.mode,0),x=a(i.mode,0),_=t-n,y=_||c(t)||1e3,M=w(y),k=g(10,v(M)),S=D(z(n-y*(0==_?0==n?.1:1:o),k/100),6),E=n>=f&&(1==m||2==m&&f>S)?f:b,W=p(u,E>S&&n>=E?E:d(E,S)),Y=D(T(t+y*(0==_?0==t?.1:1:l),k/100),6),C=h>=t&&(1==x||2==x&&Y>h)?h:-b,A=d(s,Y>C&&C>=t?C:p(C,Y));return W==A&&0==W&&(A=100),[W,A]}var s=new Intl.NumberFormat(navigator.language).format,f=Math,c=f.abs,v=f.floor,h=f.round,m=f.ceil,d=f.min,p=f.max,g=f.pow,w=f.log10,x=f.log2,_=f.PI,b=1/0;function y(n,t){return h(n/t)*t}function M(n,t,e){return d(p(n,t),e)}function k(n){return"function"==typeof n?n:function(){return n}}function S(n,t){return t}function T(n,t){return m(n/t)*t}function z(n,t){return v(n/t)*t}function D(n,t){return h(n*(t=Math.pow(10,t)))/t}var E=new Map;function W(n){return((""+n).split(".")[1]||"").length}function Y(n,t,e,r){for(var i=[],o=r.map(W),l=t;e>l;l++)for(var a=c(l),u=D(g(n,l),a),s=0;r.length>s;s++){var f=r[s]*u,v=(0>f||0>l?a:0)+(o[s]>l?o[s]:0),h=D(f,v);i.push(h),E.set(h,v)}return i}var C={},A=Array.isArray;function F(n){return"string"==typeof n}function H(n){return"object"==typeof n&&null!==n}function P(n){var t;if(A(n))t=n.map(P);else if(H(n))for(var e in t={},n)t[e]=P(n[e]);else t=n;return t}function N(n){for(var t=arguments,e=1;t.length>e;e++){var r=t[e];for(var i in r)H(n[i])?N(n[i],P(r[i])):n[i]=P(r[i])}return n}var I="undefined"==typeof queueMicrotask?function(n){return Promise.resolve().then(n)}:queueMicrotask,V="width",L="height",O="top",j="bottom",B="left",G="right",R="#000",U="#0000",J="mousemove",q="mousedown",Z="mouseup",X="mouseenter",K="mouseleave",Q="dblclick",$="resize",nn="scroll",tn="u-off",en="u-label",rn=requestAnimationFrame,on=document,ln=window,an=devicePixelRatio;function un(n,t){null!=t&&n.classList.add(t)}function sn(n,t){n.classList.remove(t)}function fn(n,t,e){n.style[t]=e+"px"}function cn(n,t,e,r){var i=on.createElement(n);return null!=t&&un(i,t),null!=e&&e.insertBefore(i,r),i}function vn(n,t){return cn("div",n,t)}function hn(n,t,e,r,i){n.style.transform="translate("+t+"px,"+e+"px)",0>t||0>e||t>r||e>i?un(n,tn):sn(n,tn)}var mn={passive:!0};function dn(n,t,e){t.addEventListener(n,e,mn)}function pn(n,t,e){t.removeEventListener(n,e,mn)}var gn=["January","February","March","April","May","June","July","August","September","October","November","December"],wn=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function xn(n){return n.slice(0,3)}var _n=wn.map(xn),bn=gn.map(xn),yn={MMMM:gn,MMM:bn,WWWW:wn,WWW:_n};function Mn(n){return(10>n?"0":"")+n}var kn={YYYY:function(n){return n.getFullYear()},YY:function(n){return(n.getFullYear()+"").slice(2)},MMMM:function(n,t){return t.MMMM[n.getMonth()]},MMM:function(n,t){return t.MMM[n.getMonth()]},MM:function(n){return Mn(n.getMonth()+1)},M:function(n){return n.getMonth()+1},DD:function(n){return Mn(n.getDate())},D:function(n){return n.getDate()},WWWW:function(n,t){return t.WWWW[n.getDay()]},WWW:function(n,t){return t.WWW[n.getDay()]},HH:function(n){return Mn(n.getHours())},H:function(n){return n.getHours()},h:function(n){var t=n.getHours();return 0==t?12:t>12?t-12:t},AA:function(n){return 12>n.getHours()?"AM":"PM"},aa:function(n){return 12>n.getHours()?"am":"pm"},a:function(n){return 12>n.getHours()?"a":"p"},mm:function(n){return Mn(n.getMinutes())},m:function(n){return n.getMinutes()},ss:function(n){return Mn(n.getSeconds())},s:function(n){return n.getSeconds()},fff:function(n){return function(n){return(10>n?"00":100>n?"0":"")+n}(n.getMilliseconds())}};function Sn(n,t){t=t||yn;for(var e,r=[],i=/\{([a-z]+)\}|[^{]+/gi;e=i.exec(n);)r.push("{"==e[0][0]?kn[e[1]]:e[0]);return function(n){for(var e="",i=0;r.length>i;i++)e+="string"==typeof r[i]?r[i]:r[i](n,t);return e}}var Tn=(new Intl.DateTimeFormat).resolvedOptions().timeZone,zn=function(n){return n%1==0},Dn=[1,2,2.5,5],En=Dn.filter(zn),Wn=Y(10,-16,0,Dn),Yn=Y(10,0,16,Dn),Cn=Yn.filter(zn),An=Wn.concat(Yn),Fn=3600,Hn=24*Fn,Pn=30*Hn,Nn=365*Hn,In=Y(10,-3,0,En).concat([1,5,10,15,30,60,300,600,900,1800,Fn,2*Fn,3*Fn,4*Fn,6*Fn,8*Fn,12*Fn,Hn,2*Hn,3*Hn,4*Hn,5*Hn,6*Hn,7*Hn,8*Hn,9*Hn,10*Hn,15*Hn,Pn,2*Pn,3*Pn,4*Pn,6*Pn,Nn,2*Nn,5*Nn,10*Nn,25*Nn,50*Nn,100*Nn]);function Vn(n,t){return n.map((function(n){return n.map((function(e,r){return 0==r||8==r||null==e?e:t(1==r||0==n[8]?e:n[1]+e)}))}))}Y(2,-53,53,[1]);var Ln="{YYYY}",On="\n"+Ln,jn="{M}/{D}",Bn="\n"+jn,Gn=Bn+"/{YY}",Rn="{aa}",Un="{h}:{mm}"+Rn,Jn="\n"+Un,qn=":{ss}",Zn=null,Xn=[[Nn,Ln,Zn,Zn,Zn,Zn,Zn,Zn,1],[28*Hn,"{MMM}",On,Zn,Zn,Zn,Zn,Zn,1],[Hn,jn,On,Zn,Zn,Zn,Zn,Zn,1],[Fn,"{h}"+Rn,Gn,Zn,Bn,Zn,Zn,Zn,1],[60,Un,Gn,Zn,Bn,Zn,Zn,Zn,1],[1,qn,Gn+" "+Un,Zn,Bn+" "+Un,Zn,Jn,Zn,1],[.001,qn+".{fff}",Gn+" "+Un,Zn,Bn+" "+Un,Zn,Jn,Zn,1]];function Kn(n,t){return function(e,r,i,o,l){var a,u,s,f,c,v,h=t.find((function(n){return l>=n[0]}))||t[t.length-1];return r.map((function(t){var e=n(t),r=e.getFullYear(),i=e.getMonth(),o=e.getDate(),l=e.getHours(),m=e.getMinutes(),d=e.getSeconds(),p=r!=a&&h[2]||i!=u&&h[3]||o!=s&&h[4]||l!=f&&h[5]||m!=c&&h[6]||d!=v&&h[7]||h[1];return a=r,u=i,s=o,f=l,c=m,v=d,p(e)}))}}function Qn(n,t,e){return new Date(n,t,e)}function $n(n,t){return t(n)}function nt(n,t){return function(e,r){return t(n(r))}}var tt=[0,0];function et(n,t,e){return function(n){0==n.button&&e(n)}}function rt(n,t,e){return e}var it={show:!0,x:!0,y:!0,lock:!1,move:function(n,t,e){return tt[0]=t,tt[1]=e,tt},points:{show:function(n,t){var e=n.series[t],r=vn();r.style.background=e.stroke||R;var i=bt(e.width,1),o=(i-1)/-2;return fn(r,V,i),fn(r,L,i),fn(r,"marginLeft",o),fn(r,"marginTop",o),r}},bind:{mousedown:et,mouseup:et,click:et,dblclick:et,mousemove:rt,mouseleave:rt,mouseenter:rt},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,_x:!1,_y:!1},focus:{prox:-1},left:-10,top:-10,idx:null,dataIdx:function(n,t,e){return e}},ot={show:!0,stroke:"rgba(0,0,0,0.07)",width:2,filter:S},lt=N({},ot,{size:10}),at='12px system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',ut="bold "+at,st={show:!0,scale:"x",space:50,gap:5,size:50,labelSize:30,labelFont:ut,side:2,grid:ot,ticks:lt,font:at,rotate:0},ft={show:!0,scale:"x",auto:!1,sorted:1,min:b,max:-b,idxs:[]};function ct(n,t){return t.map((function(n){return null==n?"":s(n)}))}function vt(n,t,e,r,i,o,l){for(var a=[],u=E.get(i)||0,s=e=l?e:D(T(e,i),u);r>=s;s=D(s+i,u))a.push(Object.is(s,-0)?0:s);return a}function ht(n,t,e,r,i){var o=[],l=n.scales[n.axes[t].scale].log,a=v((10==l?w:x)(e));i=g(l,a),0>a&&(i=D(i,-a));var u=e;do{o.push(u),i*l>(u=D(u+i,E.get(i)))||(i=u)}while(r>=u);return o}var mt=/./,dt=/[12357]/,pt=/[125]/,gt=/1/;function wt(n,t,e){var r=n.axes[e],i=r.scale;if(2==n.scales[i].log)return t;var o=n.valToPos,l=r._space,a=o(10,i),u=o(9,i)-a=r[1]-r[0]}},values:null,min:b,max:-b,idxs:[],path:null,clip:null},kt={time:!0,auto:!0,distr:1,log:10,min:null,max:null},St=N({},kt,{time:!1}),Tt={};function zt(){var n=[];return{sub:function(t){n.push(t)},unsub:function(t){n=n.filter((function(n){return n!=t}))},pub:function(t,e,r,i,o,l,a){n.length>1&&n.forEach((function(n){n!=e&&n.pub(t,e,r,i,o,l,a)}))}}}function Dt(n,t,e,r){return(r?[n[0],n[1]].concat(n.slice(2)):[n[0]].concat(n.slice(1))).map((function(n,r){return Et(n,r,t,e)}))}function Et(n,t,e,r){return N({},0==t||n&&n.side%2==0?e:r,n)}function Wt(n,t){return 3==t.distr?w(n/t.min)/w(t.max/t.min):(n-t.min)/(t.max-t.min)}function Yt(n,t,e,r){return r+(1-Wt(n,t))*e}function Ct(n,t,e,r){return r+Wt(n,t)*e}var At=[null,null];function Ft(n,t,e){return null==t?At:[t,e]}var Ht=Ft;function Pt(n,t,e){return null==t?At:l(t,e,.1,!0)}function Nt(n,t,e,i){return null==t?At:r(t,e,n.scales[i].log,!1)}var It=Nt;function Vt(n){var t;return[n=n.replace(/(\d+)px/,(function(n,e){return(t=h(e*an))+"px"})),t]}function Lt(t,e,i){var o={},a=!1;o.status=0;var u=o.root=vn("uplot");null!=t.id&&(u.id=t.id),un(u,t.class),t.title&&(vn("u-title",u).textContent=t.title);var s=cn("canvas"),f=o.ctx=s.getContext("2d"),m=vn("u-wrap",u),x=vn("u-under",m);m.appendChild(s);var z=vn("u-over",m);((t=P(t)).plugins||[]).forEach((function(n){n.opts&&(t=n.opts(o,t)||t)}));var W=o.series=Dt(t.series||[],ft,Mt,!1),Y=o.axes=Dt(t.axes||[],st,_t,!0),mn=o.scales={},gn=W[0].scale;function wn(n){var e=mn[n];if(null==e){var r=(t.scales||C)[n]||C;if(null!=r.from)wn(r.from),mn[n]=N({},mn[r.from],r);else{var i=(e=mn[n]=N({},n==gn?kt:St,r)).time,o=3==e.distr,a=e.range;if(n!=gn&&!A(a)&&H(a)){var u=a;a=function(n,t,e){return null==t?At:l(t,e,u)}}e.range=k(a||(i?Ht:n==gn?o?It:Ft:o?Nt:Pt)),e.auto=k(e.auto)}}}for(var xn in wn("x"),wn("y"),W.forEach((function(n){wn(n.scale)})),t.scales)wn(xn);var _n=mn[gn].distr,bn={};for(var yn in mn){var Mn=mn[yn];null==Mn.min&&null==Mn.max||(bn[yn]={min:Mn.min,max:Mn.max})}var kn=o.gutters=N({x:h(_t.size/2),y:h(st.size/3),_x:null,_y:null},t.gutters);kn.x=k(kn.x),kn.y=k(kn.y),kn._x=kn.x(o),kn._y=kn.y(o);var Tn,zn,Dn=t.tzDate||function(n){return new Date(1e3*n)},En=t.fmtDate||Sn,Wn=function(n){return function(t,e,r,i,o,l){var a=[],u=o>=Nn,s=o>=Pn&&Nn>o,f=n(r),c=f/1e3,h=Qn(f.getFullYear(),u?0:f.getMonth(),s||u?1:f.getDate()),m=h/1e3;if(s||u)for(var d=s?o/Pn:0,p=u?o/Nn:0,g=c==m?c:Qn(h.getFullYear()+p,h.getMonth()+d,1)/1e3,w=new Date(1e3*g),x=w.getFullYear(),_=w.getMonth(),b=0;i>=g;b++){var y=Qn(x+p*b,_+d*b,1);(g=(+y+(y-n(y/1e3)))/1e3)>i||a.push(g)}else{var M=Hn>o?o:Hn,k=m+(v(r)-v(c))+T(c-m,M);a.push(k);for(var S=n(k),z=S.getHours()+S.getMinutes()/60+S.getSeconds()/Fn,E=o/Fn,W=l/t.axes[e]._space;(k=D(k+o,3))<=i;)if(E>1){var Y=v(D(z+E,6))%24,C=n(k).getHours()-Y;C>1&&(C=-1),z=(z+E)%24,.7>D(((k-=C*Fn)-a[a.length-1])/o,3)*W||a.push(k)}else a.push(k)}return a}}(Dn),Yn=Kn(Dn,Vn(Xn,En)),Ln=nt(Dn,$n("{YYYY}-{MM}-{DD} {h}:{mm}{aa}",En)),On=N({show:!0,live:!0},t.legend),jn=On.show,Bn=[],Gn=!1;if(jn){Tn=cn("table","u-legend",u);var Rn=W[1]?W[1].values:null;if(Gn=null!=Rn){var Un=cn("tr","u-thead",Tn);for(var Jn in cn("th",null,Un),zn=Rn(o,1,0))cn("th",en,Un).textContent=Jn}else zn={_:0},un(Tn,"u-inline"),On.live&&un(Tn,"u-live")}var qn=new Map;function Zn(n,t,e){var r=qn.get(t)||{},i=Rt.bind[n](o,t,e);i&&(dn(n,t,r[n]=i),qn.set(t,r))}var tt=0,et=0,rt=0,ot=0,lt=0,at=0,ut=0,mt=0,dt=0,pt=0;o.bbox={};var gt=!1,Wt=!1,Lt=!1,Ot=!1,jt=!1;function Bt(n,t){n==o.width&&t==o.height||Gt(n,t),he(!1),Lt=!0,Wt=!0,Ot=!0,jt=!0,Se()}function Gt(n,t){o.width=tt=rt=n,o.height=et=ot=t,lt=at=0,function(){var n=!1,t=!1,e=!1,r=!1;Y.forEach((function(i){if(i.show&&i._show){var o=i.side,l=o%2,a=i._size+(i.labelSize=null!=i.label?i.labelSize||30:0);a>0&&(l?(rt-=a,3==o?(lt+=a,r=!0):e=!0):(ot-=a,0==o?(at+=a,n=!0):t=!0))}})),(n||t)&&(e||(rt-=kn._x),r||(rt-=kn._x,lt+=kn._x)),(r||e)&&(t||(ot-=kn._y),n||(ot-=kn._y,at+=kn._y))}(),function(){var n=lt+rt,t=at+ot,e=lt,r=at;function i(i,o){switch(i){case 1:return(n+=o)-o;case 2:return(t+=o)-o;case 3:return(e-=o)+o;case 0:return(r-=o)+o}}Y.forEach((function(n){if(n.show&&n._show){var t=n.side;n._pos=i(t,n._size),null!=n.label&&(n._lpos=i(t,n.labelSize))}}))}();var e=o.bbox;ut=e.left=y(lt*an,.5),mt=e.top=y(at*an,.5),dt=e.width=y(rt*an,.5),pt=e.height=y(ot*an,.5)}o.setSize=function(n){Bt(n.width,n.height)};var Rt=o.cursor=N({},it,t.cursor);Rt._lock=!1,Rt.points.show=k(Rt.points.show);var Ut,Jt,qt=o.focus=N({},t.focus||{alpha:.3},Rt.focus),Zt=qt.prox>=0,Xt=[null];function Kt(n,t){var e=mn[n.scale].time,r=n.value;if(n.value=e?F(r)?nt(Dn,$n(r,En)):r||Ln:r||xt,n.label=n.label||(e?"Time":"Value"),t>0){n.width=null==n.width?1:n.width,n.paths=n.paths||se,n.fillTo=n.fillTo||yt;var i=bt(n.width,1);n.points=N({},{size:i,width:p(1,.2*i),stroke:n.stroke,space:2*i},n.points),n.points.show=k(n.points.show),n._paths=null}if(jn&&Bn.splice(t,0,function(n,t){if(0==t&&(Gn||!On.live))return null;var e=[],r=cn("tr","u-series",Tn,Tn.childNodes[t]);un(r,n.class),n.show||un(r,tn);var i=cn("th",null,r),o=vn("u-marker",i);o.style.borderColor=n.width?n.stroke:t>0&&n.points.width?n.points.stroke:null,o.style.backgroundColor=n.fill||null;var l=vn(en,i);for(var a in l.textContent=n.label,t>0&&(Zn("click",i,(function(){Rt._lock||Le(W.indexOf(n),{show:!n.show},ir.setSeries)})),Zt&&Zn(X,i,(function(){Rt._lock||Le(W.indexOf(n),{focus:!0},ir.setSeries)}))),zn){var u=cn("td","u-value",r);u.textContent="--",e.push(u)}return e}(n,t)),Rt.show){var l=function(n,t){if(t>0){var e=Rt.points.show(o,t);if(e)return un(e,"u-cursor-pt"),un(e,n.class),hn(e,-10,-10,rt,ot),z.insertBefore(e,Xt[t]),e}}(n,t);l&&Xt.splice(t,0,l)}}o.addSeries=function(n,t){n=Et(n,t=null==t?W.length:t,ft,Mt),W.splice(t,0,n),Kt(W[t],t)},o.delSeries=function(n){W.splice(n,1),jn&&Bn.splice(n,1)[0][0].parentNode.remove(),Xt.length>1&&Xt.splice(n,1)[0].remove()},W.forEach(Kt),Y.forEach((function(n,t){if(n._show=n.show,n.show){var e=mn[n.scale];null==e&&(n.scale=n.side%2?W[1].scale:gn,e=mn[n.scale]);var r=e.time;n.size=k(n.size),n.space=k(n.space),n.rotate=k(n.rotate),n.incrs=k(n.incrs||(2==e.distr?Cn:r?In:An)),n.splits=k(n.splits||(r&&1==e.distr?Wn:3==e.distr?ht:vt));var i=n.values;n.values=r?A(i)?Kn(Dn,Vn(i,En)):F(i)?function(n,t){var e=Sn(t);return function(t,r){return r.map((function(t){return e(n(t))}))}}(Dn,i):i||Yn:i||ct,n.filter=k(n.filter||(3==e.distr?wt:S)),n.font=Vt(n.font),n.labelFont=Vt(n.labelFont),n._size=n.size(o,null,t),n._space=n._rotate=n._incrs=n._found=n._splits=n._values=null}}));var Qt=null,$t=null,ne=W[0].idxs,te=null,ee=!1;function re(n,t){if(!A(n)&&H(n)&&(Jt=n.isGap,n=n.data),(n=n||[])[0]=n[0]||[],o.data=n,e=n.slice(),Ut=(te=e[0]).length,2==_n&&(e[0]=te.map((function(n,t){return t}))),he(!0),rr("setData"),!1!==t){var r=mn[gn];r.auto(o,ee)?ie():Ve(gn,r.min,r.max),Ot=!0,jt=!0,Se()}}function ie(){var n,t,i,o;ee=!0,Ut>0?(Qt=ne[0]=0,$t=ne[1]=Ut-1,i=e[0][Qt],o=e[0][$t],2==_n?(i=Qt,o=$t):1==Ut&&(3==_n?(i=(n=r(i,i,mn[gn].log,!1))[0],o=n[1]):mn[gn].time?o=i+86400:(i=(t=l(i,o,.1,!0))[0],o=t[1]))):(Qt=ne[0]=i=null,$t=ne[1]=o=null),Ve(gn,i,o),ee=!1}function oe(n,t,e,r){f.strokeStyle=n||U,f.lineWidth=t,f.lineJoin="round",f.setLineDash(e||[]),f.fillStyle=r||U}o.setData=re;var le=1;function ae(n,t,e){if(e>t){var r=n[n.length-1];r&&r[0]==t?r[1]=e:n.push([t,e])}}function ue(n,t,e,r){for(var i=1==r?t:e;i>=t&&e>=i;i+=r)if(null!=n[i])return i;return-1}function se(n,t,r,i){var o,l,a,u,s,f=W[t],c=Jt||f.isGap,v=e[0],m=e[t],g=mn[gn],w=mn[f.scale],x=1==le?{stroke:new Path2D,fill:null,clip:null}:W[t-1]._paths,_=x.stroke,M=D(f.width*an,3),k=b,S=-b,T=[],z=h(Ct(v[1==le?r:i],g,dt,ut)),E=!1,Y=ue(m,r,i,1),C=ue(m,r,i,-1),A=y(Ct(v[Y],g,dt,ut),.5),F=y(Ct(v[C],g,dt,ut),.5);A>ut&&ae(T,ut,A),f.band&&1==le&&_.lineTo(A-2*M,h(Yt(m[r],w,pt,mt)));for(var H=1==le?r:i;H>=r&&i>=H;H+=le){var P=h(Ct(v[H],g,dt,ut));if(P==z)null!=m[H]?(o=h(Yt(m[H],w,pt,mt)),k=d(o,k),S=p(o,S)):!E&&c(n,t,H)&&(E=!0);else{var N=!1;k!=b?(_.lineTo(z,k),_.lineTo(z,S),_.lineTo(z,o),l=z):E&&(N=!0,E=!1),null!=m[H]?(o=h(Yt(m[H],w,pt,mt)),_.lineTo(P,o),k=S=o,P-z>1&&null==m[H-1]&&c(n,t,H-1)&&(N=!0)):(k=b,S=-b,!E&&c(n,t,H)&&(E=!0)),N&&ae(T,l,P),z=P}}if(ut+dt>F&&ae(T,F,ut+dt),f.band&&(1==le?(a=F+2*M,u=C,s=e[t+1]):(a=A-2*M,u=Y,s=e[t-1]),_.lineTo(a,h(Yt(m[u],w,pt,mt))),_.lineTo(a,h(Yt(s[u],w,pt,mt)))),1==le&&(x.clip=function(n,t){var e=null;if(t.length>0&&!W[n].spanGaps){e=new Path2D;for(var r=ut,i=0;t.length>i;i++){var o=t[i];e.rect(r,mt,o[0]-r,mt+pt),r=o[1]}e.rect(r,mt,ut+dt-r,mt+pt)}return e}(t,T),null!=f.fill)){var I=x.fill=new Path2D(_),V=h(Yt(f.fillTo(n,t,f.min,f.max),w,pt,mt));I.lineTo(F,V),I.lineTo(A,V)}return f.band&&(le*=-1),x}function fe(n,t,e,r,i,o,l,a,u){var s=l%2/2;f.translate(s,s),oe(a,l,u),f.beginPath();var c,v,h,m,d=i+(0==r||3==r?-o:o);0==e?(v=i,m=d):(c=i,h=d),n.forEach((function(n,r){null!=t[r]&&(0==e?c=h=n:v=m=n,f.moveTo(c,v),f.lineTo(h,m))})),f.stroke(),f.translate(-s,-s)}function ce(){var n=!0;return Y.forEach((function(t,e){if(t.show){var r=mn[t.scale];if(null!=r.min){t._show||(n=!1,t._show=!0,he(!1));var i=t.side,l=r.min,a=r.max,u=function(n,t,e,r){var i,l=Y[n];if(r>0){var a=l._space=l.space(o,n,t,e,r),u=l._incrs=l.incrs(o,n,t,e,r,a);i=l._found=function(n,t,e,r,i){for(var o=r/(t-n),l=(""+v(n)).length,a=0;e.length>a;a++){var u=e[a]*o,s=10>e[a]?E.get(e[a]):0;if(u>=i&&17>l+s)return[e[a],u]}return[0,0]}(t,e,u,r,a)}else i=[0,0];return i}(e,l,a,0==i%2?rt:ot),s=u[0],f=u[1];if(0!=f){var c=t._splits=t.splits(o,e,l,a,s,f,2==r.distr),h=2==r.distr?c.map((function(n){return te[n]})):c,m=2==r.distr?te[c[1]]-te[c[0]]:s,d=t._values=t.values(o,t.filter(o,h,e,f,m),e,f,m);t._rotate=2==i?t.rotate(o,d,e,f):0;var p=t._size;t._size=t.size(o,d,e),null!=p&&t._size!=p&&(n=!1)}}else t._show&&(n=!1,t._show=!1,he(!1))}})),n}function ve(){var n=!0,t=kn._x,e=kn._y;return kn._x=kn.x(o),kn._y=kn.y(o),kn._x==t&&kn._y==e||(n=!1),n}function he(n){W.forEach((function(t,e){e>0&&(t._paths=null,n&&(t.min=null,t.max=null))}))}o.paths=se;var me,de,pe,ge,we,xe,_e,be,ye,Me,ke=!1;function Se(){ke||(I(Te),ke=!0)}function Te(){gt&&(function(){var t=P(mn);for(var r in t){var i=t[r],l=bn[r];if(null!=l&&null!=l.min)N(i,l),r==gn&&he(!0);else if(r!=gn)if(0==Ut&&null==i.from){var a=i.range(o,null,null,r);i.min=a[0],i.max=a[1]}else i.min=b,i.max=-b}if(Ut>0)for(var u in W.forEach((function(r,i){var l=r.scale,a=t[l],u=bn[l];if(0==i){var s=a.range(o,a.min,a.max,l);a.min=s[0],a.max=s[1],Qt=n(a.min,e[0]),$t=n(a.max,e[0]),a.min>e[0][Qt]&&Qt++,e[0][$t]>a.max&&$t--,r.min=te[Qt],r.max=te[$t]}else if(r.show&&r.auto&&a.auto(o,ee)&&(null==u||null==u.min)){var f=null==r.min?function(n,t,e,r){var i=b,o=-b;if(1==r)i=n[t],o=n[e];else if(-1==r)i=n[e],o=n[t];else for(var l=t;e>=l;l++)null!=n[l]&&(i=d(i,n[l]),o=p(o,n[l]));return[i,o]}(e[i],Qt,$t,r.sorted):[r.min,r.max];a.min=d(a.min,r.min=f[0]),a.max=p(a.max,r.max=f[1])}r.idxs[0]=Qt,r.idxs[1]=$t})),t){var s=t[u],f=bn[u];if(null==s.from&&(null==f||null==f.min)){var c=s.range(o,s.min==b?null:s.min,s.max==-b?null:s.max,u);s.min=c[0],s.max=c[1]}}for(var v in t){var h=t[v];if(null!=h.from){var m=t[h.from],g=h.range(o,m.min,m.max,v);h.min=g[0],h.max=g[1]}}var w={},x=!1;for(var _ in t){var y=t[_],M=mn[_];M.min==y.min&&M.max==y.max||(M.min=y.min,M.max=y.max,w[_]=x=!0)}if(x){for(var k in W.forEach((function(n){w[n.scale]&&(n._paths=null)})),w)Lt=!0,rr("setScale",k);Rt.show&&(Ot=!0)}for(var S in bn)bn[S]=null}(),gt=!1),Lt&&(function(){for(var n=!1;!n;){var t=ce(),e=ve();(n=t&&e)||(Gt(o.width,o.height),Wt=!0)}}(),Lt=!1),Wt&&(fn(x,B,lt),fn(x,O,at),fn(x,V,rt),fn(x,L,ot),fn(z,B,lt),fn(z,O,at),fn(z,V,rt),fn(z,L,ot),fn(m,V,tt),fn(m,L,et),s.width=h(tt*an),s.height=h(et*an),Ue(),rr("setSize"),Wt=!1),Rt.show&&Ot&&(Ge(),Ot=!1),tt>0&&et>0&&(f.clearRect(0,0,s.width,s.height),rr("drawClear"),function(){Y.forEach((function(n,t){if(n.show&&n._show){var e=mn[n.scale],r=n.side,i=r%2,l=0==i?Ct:Yt,a=0==i?dt:pt,u=0==i?ut:mt,s=h(n.gap*an),c=n.ticks,v=c.show?h(c.size*an):0,m=n._found,d=m[0],p=m[1],g=n._splits,w=2==e.distr?g.map((function(n){return te[n]})):g,x=2==e.distr?te[g[1]]-te[g[0]]:d,b=n._rotate*-_/180,y=h(n._pos*an),M=y+(v+s)*(0==i&&0==r||1==i&&3==r?-1:1),k=0==i?M:0,S=1==i?M:0;f.font=n.font[0],f.fillStyle=n.stroke||R,f.textAlign=1==n.align?B:2==n.align?G:b>0?B:0>b?G:0==i?"center":3==r?G:B,f.textBaseline=b||1==i?"middle":2==r?O:j;var T=1.5*n.font[1],z=g.map((function(n){return h(l(n,e,a,u))}));if(n._values.forEach((function(n,t){null!=n&&(0==i?S=z[t]:k=z[t],(""+n).split(/\n/gm).forEach((function(n,t){b?(f.save(),f.translate(S,k+t*T),f.rotate(b),f.fillText(n,0,0),f.restore()):f.fillText(n,S,k+t*T)})))})),n.label){f.save();var E=h(n._lpos*an);1==i?(S=k=0,f.translate(E,h(mt+pt/2)),f.rotate((3==r?-_:_)/2)):(S=h(ut+dt/2),k=E),f.font=n.labelFont[0],f.textAlign="center",f.textBaseline=2==r?O:j,f.fillText(n.label,S,k),f.restore()}c.show&&fe(z,c.filter(o,w,t,p,x),i,r,y,v,D(c.width*an,3),c.stroke);var W=n.grid;W.show&&fe(z,W.filter(o,w,t,p,x),i,0==i?2:1,0==i?mt:ut,0==i?pt:dt,D(W.width*an,3),W.stroke,W.dash)}})),rr("drawAxes")}(),Ut>0&&function(){W.forEach((function(n,t){if(t>0&&n.show&&null==n._paths){var r=function(n){for(var t=M(Qt-1,0,Ut-1),e=M($t+1,0,Ut-1);null==n[t]&&t>0;)t--;for(;null==n[e]&&Ut-1>e;)e++;return[t,e]}(e[t]);n._paths=n.paths(o,t,r[0],r[1])}})),W.forEach((function(n,t){t>0&&n.show&&(n._paths&&function(n){var t=W[n];if(1==le){var e=t._paths,r=e.stroke,i=e.fill,o=e.clip,l=D(t.width*an,3),a=l%2/2;oe(t.stroke,l,t.dash,t.fill),f.globalAlpha=t.alpha,f.translate(a,a),f.save();var u=ut,s=mt,c=dt,v=pt,h=l*an/2;0==t.min&&(v+=h),0==t.max&&(s-=h,v+=h),f.beginPath(),f.rect(u,s,c,v),f.clip(),null!=o&&f.clip(o),t.band?(f.fill(r),l&&f.stroke(r)):(l&&f.stroke(r),null!=t.fill&&f.fill(i)),f.restore(),f.translate(-a,-a),f.globalAlpha=1}t.band&&(le*=-1)}(t),n.points.show(o,t,Qt,$t)&&function(n){var t=W[n],r=t.points,i=D(r.width*an,3),o=i%2/2,l=r.width>0,a=(r.size-r.width)/2*an,u=D(2*a,3);f.translate(o,o),f.save(),f.beginPath(),f.rect(ut-u,mt-u,dt+2*u,pt+2*u),f.clip(),f.globalAlpha=t.alpha;for(var s=new Path2D,c=Qt;$t>=c;c++)if(null!=e[n][c]){var v=h(Ct(e[0][c],mn[gn],dt,ut)),m=h(Yt(e[n][c],mn[t.scale],pt,mt));s.moveTo(v+a,m),s.arc(v,m,a,0,2*_)}oe(r.stroke,i,null,r.fill||(l?"#fff":t.stroke)),f.fill(s),l&&f.stroke(s),f.globalAlpha=1,f.restore(),f.translate(-o,-o)}(t),rr("drawSeries",t))}))}(),rr("draw")),a||(a=!0,o.status=1,rr("ready")),ke=!1}function ze(t,r){var i=mn[t];if(null==i.from){if(0==Ut){var l=i.range(o,r.min,r.max,t);r.min=l[0],r.max=l[1]}if(Ut>1&&null!=r.min&&null!=r.max&&1e-16>r.max-r.min)return;t==gn&&2==i.distr&&Ut>0&&(r.min=n(r.min,e[0]),r.max=n(r.max,e[0])),bn[t]=r,gt=!0,Se()}}o.redraw=function(n){!1!==n?Ve(gn,mn[gn].min,mn[gn].max):Se()},o.setScale=ze;var De=!1,Ee=Rt.drag,We=Ee.x,Ye=Ee.y;Rt.show&&(Rt.x&&(ye=Rt.left,me=vn("u-cursor-x",z)),Rt.y&&(Me=Rt.top,de=vn("u-cursor-y",z)));var Ce,Ae,Fe,He=o.select=N({show:!0,over:!0,left:0,width:0,top:0,height:0},t.select),Pe=He.show?vn("u-select",He.over?z:x):null;function Ne(n,t){if(He.show){for(var e in n)fn(Pe,e,He[e]=n[e]);!1!==t&&rr("setSelect")}}function Ie(n){var t=jn?Bn[n][0].parentNode:null;W[n].show?t&&sn(t,tn):(t&&un(t,tn),Xt.length>1&&hn(Xt[n],-10,-10,rt,ot))}function Ve(n,t,e){ze(n,{min:t,max:e})}function Le(n,t,e){var r=W[n];if(null!=t.focus&&function(n){n!=Fe&&(W.forEach((function(t,e){!function(n,t){var e=W[n];Oe(n,t),e.band&&Oe(W[n+1].band?n+1:n-1,t)}(e,null==n||0==e||e==n?1:qt.alpha)})),Fe=n,Se())}(n),null!=t.show){if(r.show=t.show,Ie(n),r.band){var i=W[n+1]&&W[n+1].band?n+1:n-1;W[i].show=r.show,Ie(i)}Ve(r.scale,null,null),Se()}rr("setSeries",n,t),e&&lr.pub("setSeries",o,n,t)}function Oe(n,t){W[n].alpha=t,Rt.show&&Xt[n]&&(Xt[n].style.opacity=t),jn&&Bn[n]&&(Bn[n][0].parentNode.style.opacity=t)}function je(n,t){var e=rt;t!=gn&&(n=(e=ot)-n);var r=n/e,i=mn[t],o=i.min,l=i.max;return 3==i.distr?(o=w(o),l=w(l),g(10,o+(l-o)*r)):o+(l-o)*r}o.setSelect=Ne,o.setSeries=Le,jn&&Zt&&dn(K,Tn,(function(){Rt._lock||(Le(null,{focus:!1},ir.setSeries),Ge())})),o.valToIdx=function(t){return n(t,e[0])},o.posToIdx=function(t){return n(je(t,gn),e[0],Qt,$t)},o.posToVal=je,o.valToPos=function(n,t,e){return t==gn?Ct(n,mn[t],e?dt:rt,e?ut:0):Yt(n,mn[t],e?pt:ot,e?mt:0)},o.batch=function(n){n(o),Se()},o.setCursor=function(n){ye=n.left,Me=n.top,Ge()};var Be=0;function Ge(t,r){var i,l;if(Be=0,_e=ye,be=Me,i=Rt.move(o,ye,Me),ye=i[0],Me=i[1],Rt.show&&(Rt.x&&hn(me,h(ye),0,rt,ot),Rt.y&&hn(de,0,h(Me),rt,ot)),Ce=b,0>ye||0==Ut||Qt>$t){l=null;for(var u=0;W.length>u;u++)if(u>0&&Xt.length>1&&hn(Xt[u],-10,-10,rt,ot),jn&&On.live){if(0==u&&Gn)continue;for(var s=0;Bn[u].length>s;s++)Bn[u][s].firstChild.nodeValue="--"}Zt&&Le(null,{focus:!0},ir.setSeries)}else{var f=je(ye,gn);l=n(f,e[0],Qt,$t);for(var v=mn[gn],m=D(Ct(e[0][l],v,rt,0),3),p=0;W.length>p;p++){var g=W[p],w=Rt.dataIdx(o,p,l,f),x=w==l?m:D(Ct(e[0][w],v,rt,0),3);if(p>0&&g.show){var _=e[p][w],y=null==_?-10:D(Yt(_,mn[g.scale],ot,0),3);if(y>0){var M=c(y-Me);M>Ce||(Ce=M,Ae=p)}Xt.length>1&&hn(Xt[p],x,y,rt,ot)}if(jn&&On.live){if(w==Rt.idx&&!jt||0==p&&Gn)continue;var k=0==p&&2==_n?te:e[p],S=Gn?g.values(o,p,w):{_:g.value(o,k[w],p,w)},T=0;for(var z in S)Bn[p][T++].firstChild.nodeValue=S[z]}}jt=!1}if(He.show&&De)if(null!=r){var E=ir.scales,Y=E[0],C=E[1],A=r.cursor.drag;if(We=A._x,Ye=A._y,Y){var F=mn[Y],H=r.posToVal(r.select.left,Y),P=r.posToVal(r.select.left+r.select.width,Y);He.left=Ct(H,F,rt,0),He.width=c(He.left-Ct(P,F,rt,0)),fn(Pe,B,He.left),fn(Pe,V,He.width),C||(fn(Pe,O,He.top=0),fn(Pe,L,He.height=ot))}if(C){var N=mn[C],I=r.posToVal(r.select.top,C),j=r.posToVal(r.select.top+r.select.height,C);He.top=Yt(I,N,ot,0),He.height=c(He.top-Yt(j,N,ot,0)),fn(Pe,O,He.top),fn(Pe,L,He.height),Y||(fn(Pe,B,He.left=0),fn(Pe,V,He.width=rt))}}else{var G=c(_e-pe),R=c(be-ge);We=Ee.x&&G>=Ee.dist,Ye=Ee.y&&R>=Ee.dist;var U=Ee.uni;if(null!=U?We&&Ye&&(Ye=R>=U,(We=G>=U)||Ye||(R>G?Ye=!0:We=!0)):Ee.x&&Ee.y&&(We||Ye)&&(We=Ye=!0),We){var q=d(we,ye),Z=c(ye-we);fn(Pe,B,He.left=q),fn(Pe,V,He.width=Z),Ye||(fn(Pe,O,He.top=0),fn(Pe,L,He.height=ot))}if(Ye){var X=d(xe,Me),K=c(Me-xe);fn(Pe,O,He.top=X),fn(Pe,L,He.height=K),We||(fn(Pe,B,He.left=0),fn(Pe,V,He.width=rt))}We||Ye||(fn(Pe,L,He.height=0),fn(Pe,V,He.width=0))}Rt.idx=l,Rt.left=ye,Rt.top=Me,Ee._x=We,Ee._y=Ye,null!=t&&(lr.pub(J,o,ye,Me,rt,ot,l),Zt&&Le(Ce>qt.prox?null:Ae,{focus:!0},ir.setSeries)),a&&rr("setCursor")}var Re=null;function Ue(){Re=z.getBoundingClientRect()}function Je(n,t,e,r,i,o){Rt._lock||(qe(n,t,e,r,i,o,0,!1,null!=n),null!=n?0==Be&&(Be=rn(Ge)):Ge(null,t))}function qe(n,t,e,r,i,l,a,u,s){var f;if(null!=n)e=n.clientX-Re.left,r=n.clientY-Re.top;else{if(0>e||0>r)return ye=-10,void(Me=-10);var c=ir.scales,v=c[0],h=c[1];e=null!=v?Ct(t.posToVal(e,v),mn[v],rt,0):rt*(e/i),r=null!=h?Yt(t.posToVal(r,h),mn[h],ot,0):ot*(r/l)}s&&(e>1&&rt-1>e||(e=y(e,rt)),r>1&&ot-1>r||(r=y(r,ot))),u?(pe=e,ge=r,f=Rt.move(o,e,r),we=f[0],xe=f[1]):(ye=e,Me=r)}function Ze(){Ne({width:0,height:0},!1)}function Xe(n,t,e,r,i,l){De=!0,We=Ye=Ee._x=Ee._y=!1,qe(n,t,e,r,i,l,0,!0,!1),null!=n&&(Zn(Z,on,Ke),lr.pub(q,o,we,xe,rt,ot,null))}function Ke(n,t,e,r,i,l){De=Ee._x=Ee._y=!1,qe(n,t,e,r,i,l,0,!1,!0);var a=He.width>0||He.height>0;if(a&&Ne(He),Ee.setScale&&a){if(We&&Ve(gn,je(He.left,gn),je(He.left+He.width,gn)),Ye)for(var u in mn){var s=mn[u];u!=gn&&null==s.from&&s.min!=b&&Ve(u,je(He.top+He.height,u),je(He.top,u))}Ze()}else Rt.lock&&(Rt._lock=!Rt._lock,Rt._lock||Ge());null!=n&&(function(n,t){var e=qn.get(t)||{};pn(n,t,e[n]),e[n]=null}(Z,on),lr.pub(Z,o,ye,Me,rt,ot,null))}function Qe(){if(!Rt._lock){var n=De;if(De){var t=!0,e=!0;if(We&&Ye&&(t=10>=ye||ye>=rt-10,e=10>=Me||Me>=ot-10),We&&t){var r=ye,i=rt-ye,o=d(r,i);o==r&&(ye=0),o==i&&(ye=rt)}if(Ye&&e){var l=Me,a=ot-Me,u=d(l,a);u==l&&(Me=0),u==a&&(Me=ot)}Ge(1),De=!1}ye=-10,Me=-10,Ge(1),n&&(De=n)}}function $e(n){ie(),Ze(),null!=n&&lr.pub(Q,o,ye,Me,rt,ot,null)}var nr,tr={};tr.mousedown=Xe,tr.mousemove=Je,tr.mouseup=Ke,tr.dblclick=$e,tr.setSeries=function(n,t,e,r){Le(e,r)},Rt.show&&(Zn(q,z,Xe),Zn(J,z,Je),Zn(X,z,Ue),Zn(K,z,(function(){rn(Qe)})),Zn(Q,z,$e),nr=function(n){var t=null;function e(){t=null,n()}return function(){clearTimeout(t),t=setTimeout(e,100)}}(Ue),dn($,ln,nr),dn(nn,ln,nr),o.syncRect=Ue);var er=o.hooks=t.hooks||{};function rr(n,t,e){n in er&&er[n].forEach((function(n){n.call(null,o,t,e)}))}(t.plugins||[]).forEach((function(n){for(var t in n.hooks)er[t]=(er[t]||[]).concat(n.hooks[t])}));var ir=N({key:null,setSeries:!1,scales:[gn,null]},Rt.sync),or=ir.key,lr=null!=or?Tt[or]=Tt[or]||zt():zt();function ar(){rr("init",t,e),re(e||t.data,!1),bn[gn]?ze(gn,bn[gn]):ie(),Bt(t.width,t.height),Ne(He,!1)}return lr.sub(o),o.pub=function(n,t,e,r,i,o,l){tr[n](null,t,e,r,i,o,l)},o.destroy=function(){lr.unsub(o),pn($,ln,nr),pn(nn,ln,nr),u.remove(),rr("destroy")},i?i instanceof HTMLElement?(i.appendChild(u),ar()):i(o,ar):ar(),o}return Lt.assign=N,Lt.fmtNum=s,Lt.rangeNum=l,Lt.rangeLog=r,Lt.fmtDate=Sn,Lt.tzDate=function(n,t){var e;return"Etc/UTC"==t?e=new Date(+n+6e4*n.getTimezoneOffset()):t==Tn?e=n:(e=new Date(n.toLocaleString("en-US",{timeZone:t}))).setMilliseconds(n.getMilliseconds()),e},Lt}();
diff --git a/plugins/uplot/uPlot.min.css b/plugins/uplot/uPlot.min.css
new file mode 100644
index 000000000..f95d905cf
--- /dev/null
+++ b/plugins/uplot/uPlot.min.css
@@ -0,0 +1 @@
+.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: max-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;border: 2px solid transparent;}.u-series:first-child .u-marker {display: none;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;z-index: 100;}.u-cursor-x {height: 100%;border-right: 1px dashed #607D8B;}.u-cursor-y {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;filter: brightness(85%);pointer-events: none;will-change: transform;z-index: 100;}.u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
\ No newline at end of file