diff options
Diffstat (limited to 'webstats')
-rw-r--r-- | webstats/static/uPlot.iife.js | 6814 | ||||
-rw-r--r-- | webstats/static/uPlot.iife.min.js | 2 | ||||
-rw-r--r-- | webstats/static/uPlot.min.css | 131 | ||||
-rw-r--r-- | webstats/workforrobots.org | 10 |
4 files changed, 6955 insertions, 2 deletions
diff --git a/webstats/static/uPlot.iife.js b/webstats/static/uPlot.iife.js new file mode 100644 index 0000000..bcbec10 --- /dev/null +++ b/webstats/static/uPlot.iife.js @@ -0,0 +1,6814 @@ +/** + * Copyright (c) 2025, 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.6.32) + */ + +var uPlot = (function () { + "use strict"; + + const FEAT_TIME = true; + + const pre = "u-"; + + const UPLOT = "uplot"; + const ORI_HZ = pre + "hz"; + const ORI_VT = pre + "vt"; + const TITLE = pre + "title"; + const WRAP = pre + "wrap"; + const UNDER = pre + "under"; + const OVER = pre + "over"; + const AXIS = pre + "axis"; + 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_SERIES = pre + "series"; + const LEGEND_MARKER = pre + "marker"; + const LEGEND_LABEL = pre + "label"; + const LEGEND_VALUE = pre + "value"; + + const WIDTH = "width"; + const HEIGHT = "height"; + const TOP = "top"; + const BOTTOM = "bottom"; + const LEFT = "left"; + const RIGHT = "right"; + const hexBlack = "#000"; + const transparent = hexBlack + "0"; + + 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 change = "change"; + const dppxchange = "dppxchange"; + + const LEGEND_DISP = "--"; + + const domEnv = typeof window != "undefined"; + + const doc = domEnv ? document : null; + const win = domEnv ? window : null; + const nav = domEnv ? navigator : null; + + let pxRatio; + + //export const canHover = domEnv && !win.matchMedia('(hover: none)').matches; + + let query; + + function setPxRatio() { + let _pxRatio = devicePixelRatio; + + // during print preview, Chrome fires off these dppx queries even without changes + if (pxRatio != _pxRatio) { + pxRatio = _pxRatio; + + query && off(change, query, setPxRatio); + query = matchMedia( + `(min-resolution: ${pxRatio - 0.001}dppx) and (max-resolution: ${pxRatio + 0.001}dppx)`, + ); + on(change, query, setPxRatio); + + win.dispatchEvent(new CustomEvent(dppxchange)); + } + } + + function addClass(el, c) { + if (c != null) { + let cl = el.classList; + !cl.contains(c) && cl.add(c); + } + } + + function remClass(el, c) { + let cl = el.classList; + cl.contains(c) && cl.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); + } + + const xformCache = new WeakMap(); + + function elTrans(el, xPos, yPos, xMax, yMax) { + let xform = "translate(" + xPos + "px," + yPos + "px)"; + let xformOld = xformCache.get(el); + + if (xform != xformOld) { + el.style.transform = xform; + xformCache.set(el, xform); + + if (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax) addClass(el, OFF); + else remClass(el, OFF); + } + } + + const colorCache = new WeakMap(); + + function elColor(el, background, borderColor) { + let newColor = background + borderColor; + let oldColor = colorCache.get(el); + + if (newColor != oldColor) { + colorCache.set(el, newColor); + el.style.background = background; + el.style.borderColor = borderColor; + } + } + + const sizeCache = new WeakMap(); + + function elSize(el, newWid, newHgt, centered) { + let newSize = newWid + "" + newHgt; + let oldSize = sizeCache.get(el); + + if (newSize != oldSize) { + sizeCache.set(el, newSize); + el.style.height = newHgt + "px"; + el.style.width = newWid + "px"; + el.style.marginLeft = centered ? -newWid / 2 + "px" : 0; + el.style.marginTop = centered ? -newHgt / 2 + "px" : 0; + } + } + + const evOpts = { passive: true }; + const evOpts2 = { ...evOpts, capture: true }; + + function on(ev, el, cb, capt) { + el.addEventListener(ev, cb, capt ? evOpts2 : evOpts); + } + + function off(ev, el, cb, capt) { + el.removeEventListener(ev, cb, evOpts); + } + + domEnv && setPxRatio(); + + // 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 makeIndexOfs(predicate) { + let indexOfs = (data, _i0, _i1) => { + let i0 = -1; + let i1 = -1; + + for (let i = _i0; i <= _i1; i++) { + if (predicate(data[i])) { + i0 = i; + break; + } + } + + for (let i = _i1; i >= _i0; i--) { + if (predicate(data[i])) { + i1 = i; + break; + } + } + + return [i0, i1]; + }; + + return indexOfs; + } + + const notNullish = (v) => v != null; + const isPositive = (v) => v != null && v > 0; + + const nonNullIdxs = makeIndexOfs(notNullish); + const positiveIdxs = makeIndexOfs(isPositive); + + function getMinMax(data, _i0, _i1, sorted = 0, log = false) { + // console.log("getMinMax()"); + + let getEdgeIdxs = log ? positiveIdxs : nonNullIdxs; + let predicate = log ? isPositive : notNullish; + + [_i0, _i1] = getEdgeIdxs(data, _i0, _i1); + + let _min = data[_i0]; + let _max = data[_i0]; + + if (_i0 > -1) { + 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++) { + let v = data[i]; + + if (predicate(v)) { + if (v < _min) _min = v; + else if (v > _max) _max = v; + } + } + } + } + + return [_min ?? inf, _max ?? -inf]; // todo: fix to return nulls + } + + function rangeLog(min, max, base, fullMags) { + let minSign = sign(min); + let maxSign = sign(max); + + if (min == max) { + if (minSign == -1) { + min *= base; + max /= base; + } else { + min /= base; + max *= base; + } + } + + let logFn = base == 10 ? log10 : log2; + + let growMinAbs = minSign == 1 ? floor : ceil; + let growMaxAbs = maxSign == 1 ? ceil : floor; + + let minExp = growMinAbs(logFn(abs(min))); + let maxExp = growMaxAbs(logFn(abs(max))); + + let minIncr = pow(base, minExp); + let maxIncr = pow(base, maxExp); + + // fix values like Math.pow(10, -5) === 0.000009999999999999999 + if (base == 10) { + if (minExp < 0) minIncr = roundDec(minIncr, -minExp); + if (maxExp < 0) maxIncr = roundDec(maxIncr, -maxExp); + } + + if (fullMags || base == 2) { + min = minIncr * minSign; + max = maxIncr * maxSign; + } else { + min = incrRoundDn(min, minIncr); + max = incrRoundUp(max, maxIncr); + } + + return [min, max]; + } + + function rangeAsinh(min, max, base, fullMags) { + let minMax = rangeLog(min, max, base, fullMags); + + if (min == 0) minMax[0] = 0; + + if (max == 0) minMax[1] = 0; + + return minMax; + } + + const rangePad = 0.1; + + const autoRangePart = { + mode: 3, + pad: rangePad, + }; + + 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 ? 3 : 0; + + return _rangeNum(_min, _max, _eqRange); + } + + // nullish coalesce + function ifNull(lh, rh) { + return lh == null ? rh : lh; + } + + // checks if given index range in an array contains a non-null value + // aka a range-bounded Array.some() + function hasData(data, idx0, idx1) { + idx0 = ifNull(idx0, 0); + idx1 = ifNull(idx1, data.length - 1); + + while (idx0 <= idx1) { + if (data[idx0] != null) return true; + idx0++; + } + + return false; + } + + 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 deltaMag = log10(delta); + + let scalarMax = max(abs(_min), abs(_max)); + let scalarMag = log10(scalarMax); + + let scalarMagDelta = abs(scalarMag - deltaMag); + + // this handles situations like 89.7, 89.69999999999999 + // by assuming 0.001x deltas are precision errors + // if (delta > 0 && delta < abs(_max) / 1e3) + // delta = 0; + + // treat data as flat if delta is less than 1e-24 + // or range is 11+ orders of magnitude below raw values, e.g. 99999999.99999996 - 100000000.00000004 + if (delta < 1e-24 || scalarMagDelta > 10) { + delta = 0; + + // if soft mode is 2 and all vals are flat at 0, avoid the 0.1 * 1e3 fallback + // this prevents 0,0,0 from ranging to -100,100 when softMin/softMax are -1,1 + if (_min == 0 || _max == 0) { + delta = 1e-24; + + if (softMinMode == 2 && softMin != inf) padMin = 0; + + if (softMaxMode == 2 && softMax != -inf) padMax = 0; + } + } + + let nonZeroDelta = delta || scalarMax || 1e3; + let mag = log10(nonZeroDelta); + let base = pow(10, floor(mag)); + + let _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? 0.1 : 1) : padMin); + let _newMin = roundDec(incrRoundDn(_min - _padMin, base / 10), 24); + let _softMin = + _min >= softMin && + (softMinMode == 1 || + (softMinMode == 3 && _newMin <= softMin) || + (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 ? 0.1 : 1) : padMax); + let _newMax = roundDec(incrRoundUp(_max + _padMax, base / 10), 24); + let _softMax = + _max <= softMax && + (softMaxMode == 1 || + (softMaxMode == 3 && _newMax >= softMax) || + (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 numFormatter = new Intl.NumberFormat(domEnv ? nav.language : "en-US"); + const fmtNum = (val) => numFormatter.format(val); + + const M = Math; + + const PI = M.PI; + 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 sign = M.sign; + const log10 = M.log10; + const log2 = M.log2; + // TODO: seems like this needs to match asinh impl if the passed v is tweaked? + const sinh = (v, linthresh = 1) => M.sinh(v) * linthresh; + const asinh = (v, linthresh = 1) => M.asinh(v / linthresh); + + const inf = Infinity; + + function numIntDigits(x) { + return (log10((x ^ (x >> 31)) - (x >> 31)) | 0) + 1; + } + + function clamp(num, _min, _max) { + return min(max(num, _min), _max); + } + + function isFn(v) { + return typeof v == "function"; + } + + function fnOrSelf(v) { + return isFn(v) ? v : () => v; + } + + const noop = () => {}; + + // note: these identity fns may get deoptimized if reused for different arg types + // a TS version would enforce they stay monotyped and require making variants + const retArg0 = (_0) => _0; + + const retArg1 = (_0, _1) => _1; + + const retNull = (_) => null; + + const retTrue = (_) => true; + + const retEq = (a, b) => a == b; + + const regex6 = /\.\d*?(?=9{6,}|0{6,})/gm; + + // e.g. 17999.204999999998 -> 17999.205 + const fixFloat = (val) => { + if (isInt(val) || fixedDec.has(val)) return val; + + const str = `${val}`; + + const match = str.match(regex6); + + if (match == null) return val; + + let len = match[0].length - 1; + + // e.g. 1.0000000000000001e-24 + if (str.indexOf("e-") != -1) { + let [num, exp] = str.split("e"); + return +`${fixFloat(num)}e${exp}`; + } + + return roundDec(val, len); + }; + + function incrRound(num, incr) { + return fixFloat(roundDec(fixFloat(num / incr)) * incr); + } + + function incrRoundUp(num, incr) { + return fixFloat(ceil(fixFloat(num / incr)) * incr); + } + + function incrRoundDn(num, incr) { + return fixFloat(floor(fixFloat(num / incr)) * incr); + } + + // https://stackoverflow.com/a/48764436 + // rounds half away from zero + function roundDec(val, dec = 0) { + if (isInt(val)) return val; + // else if (dec == 0) + // return round(val); + + let p = 10 ** dec; + let n = val * p * (1 + Number.EPSILON); + return round(n) / p; + } + + 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 = base == 10 ? +`${mults[i]}e${exp}` : mults[i] * mag; + let dec = (exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]); + let incr = base == 10 ? _incr : roundDec(_incr, dec); + incrs.push(incr); + fixedDec.set(incr, dec); + } + } + + return incrs; + } + + //export const assign = Object.assign; + + const EMPTY_OBJ = {}; + const EMPTY_ARR = []; + + const nullNullTuple = [null, null]; + + const isArr = Array.isArray; + const isInt = Number.isInteger; + const isUndef = (v) => v === void 0; + + function isStr(v) { + return typeof v == "string"; + } + + function isObj(v) { + let is = false; + + if (v != null) { + let c = v.constructor; + is = c == null || c == Object; + } + + return is; + } + + function fastIsObj(v) { + return v != null && typeof v == "object"; + } + + const TypedArray = Object.getPrototypeOf(Uint8Array); + + const __proto__ = "__proto__"; + + function copy(o, _isObj = isObj) { + let out; + + if (isArr(o)) { + let val = o.find((v) => v != null); + + if (isArr(val) || _isObj(val)) { + out = Array(o.length); + for (let i = 0; i < o.length; i++) out[i] = copy(o[i], _isObj); + } else out = o.slice(); + } else if (o instanceof TypedArray) + // also (ArrayBuffer.isView(o) && !(o instanceof DataView)) + out = o.slice(); + else if (_isObj(o)) { + out = {}; + for (let k in o) { + if (k != __proto__) out[k] = copy(o[k], _isObj); + } + } 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 (key != __proto__) { + if (isObj(targ[key])) assign(targ[key], copy(src[key])); + else targ[key] = copy(src[key]); + } + } + } + + return targ; + } + + // nullModes + const NULL_REMOVE = 0; // nulls are converted to undefined (e.g. for spanGaps: true) + const NULL_RETAIN = 1; // nulls are retained, with alignment artifacts set to undefined (default) + const NULL_EXPAND = 2; // nulls are expanded to include any adjacent alignment artifacts + + // sets undefined values to nulls when adjacent to existing nulls (minesweeper) + function nullExpand(yVals, nullIdxs, alignedLen) { + for (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) { + let nullIdx = nullIdxs[i]; + + if (nullIdx > lastNullIdx) { + xi = nullIdx - 1; + while (xi >= 0 && yVals[xi] == null) yVals[xi--] = null; + + xi = nullIdx + 1; + while (xi < alignedLen && yVals[xi] == null) + yVals[(lastNullIdx = xi++)] = null; + } + } + } + + // nullModes is a tables-matched array indicating how to treat nulls in each series + // output is sorted ASC on the joined field (table[0]) and duplicate join values are collapsed + function join(tables, nullModes) { + if (allHeadersSame(tables)) { + // console.log('cheap join!'); + + let table = tables[0].slice(); + + for (let i = 1; i < tables.length; i++) table.push(...tables[i].slice(1)); + + if (!isAsc(table[0])) table = sortCols(table); + + return table; + } + + let xVals = new Set(); + + for (let ti = 0; ti < tables.length; ti++) { + let t = tables[ti]; + let xs = t[0]; + let len = xs.length; + + for (let i = 0; i < len; i++) xVals.add(xs[i]); + } + + let data = [Array.from(xVals).sort((a, b) => a - b)]; + + let alignedLen = data[0].length; + + let xIdxs = new Map(); + + for (let i = 0; i < alignedLen; i++) xIdxs.set(data[0][i], i); + + for (let ti = 0; ti < tables.length; ti++) { + let t = tables[ti]; + let xs = t[0]; + + for (let si = 1; si < t.length; si++) { + let ys = t[si]; + + let yVals = Array(alignedLen).fill(undefined); + + let nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN; + + let nullIdxs = []; + + for (let i = 0; i < ys.length; i++) { + let yVal = ys[i]; + let alignedIdx = xIdxs.get(xs[i]); + + if (yVal === null) { + if (nullMode != NULL_REMOVE) { + yVals[alignedIdx] = yVal; + + if (nullMode == NULL_EXPAND) nullIdxs.push(alignedIdx); + } + } else yVals[alignedIdx] = yVal; + } + + nullExpand(yVals, nullIdxs, alignedLen); + + data.push(yVals); + } + } + + return data; + } + + const microTask = + typeof queueMicrotask == "undefined" + ? (fn) => Promise.resolve().then(fn) + : queueMicrotask; + + // TODO: https://github.com/dy/sort-ids (~2x faster for 1e5+ arrays) + function sortCols(table) { + let head = table[0]; + let rlen = head.length; + + let idxs = Array(rlen); + for (let i = 0; i < idxs.length; i++) idxs[i] = i; + + idxs.sort((i0, i1) => head[i0] - head[i1]); + + let table2 = []; + for (let i = 0; i < table.length; i++) { + let row = table[i]; + let row2 = Array(rlen); + + for (let j = 0; j < rlen; j++) row2[j] = row[idxs[j]]; + + table2.push(row2); + } + + return table2; + } + + // test if we can do cheap join (all join fields same) + function allHeadersSame(tables) { + let vals0 = tables[0][0]; + let len0 = vals0.length; + + for (let i = 1; i < tables.length; i++) { + let vals1 = tables[i][0]; + + if (vals1.length != len0) return false; + + if (vals1 != vals0) { + for (let j = 0; j < len0; j++) { + if (vals1[j] != vals0[j]) return false; + } + } + } + + return true; + } + + function isAsc(vals, samples = 100) { + const len = vals.length; + + // empty or single value + if (len <= 1) return true; + + // skip leading & trailing nullish + let firstIdx = 0; + let lastIdx = len - 1; + + while (firstIdx <= lastIdx && vals[firstIdx] == null) firstIdx++; + + while (lastIdx >= firstIdx && vals[lastIdx] == null) lastIdx--; + + // all nullish or one value surrounded by nullish + if (lastIdx <= firstIdx) return true; + + const stride = max(1, floor((lastIdx - firstIdx + 1) / samples)); + + for ( + let prevVal = vals[firstIdx], i = firstIdx + stride; + i <= lastIdx; + i += stride + ) { + const v = vals[i]; + + if (v != null) { + if (v <= prevVal) return false; + + prevVal = v; + } + } + + return true; + } + + 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 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 == "UTC" || 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]; + + // ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5 + const decIncrs = genIncrs(10, -32, 0, allMults); + + // 1, 2, 2.5, 5, 10, 20, 25, 50... + const oneIncrs = genIncrs(10, 0, 32, allMults); + + // 1, 2, 5, 10, 20, 25, 50... + const wholeIncrs = oneIncrs.filter(onlyWhole); + + const numIncrs = decIncrs.concat(oneIncrs); + + 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; + + function genTimeStuffs(ms) { + let s = ms * 1e3, + m = s * 60, + h = m * 60, + 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 + let subSecIncrs = + ms == 1 + ? genIncrs(10, 0, 3, allMults).filter(onlyWhole) + : genIncrs(10, -3, 0, allMults); + + let timeIncrs = subSecIncrs.concat([ + // minute divisors (# of secs) + s, + s * 5, + s * 10, + s * 15, + s * 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, + ]); + + // [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], + [ + ms, + ss + ".{fff}", + NLmdyy + " " + hmmaa, + _, + NLmd + " " + hmmaa, + _, + NLhmmaa, + _, + 1, + ], + ]; + + // 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 = roundDec(minDate * ms, 3); + + // 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 = roundDec(minMin * ms, 3); + + 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 + : roundDec( + mkDate( + minMin.getFullYear() + yrIncr, + minMin.getMonth() + moIncr, + 1, + ) * ms, + 3, + ); + let splitDate = new Date(round(split / ms)); + 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(roundDec(next * ms, 3)); + + split = roundDec((+next + offs) * ms, 3); + + 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, ms == 1 ? 0 : 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 >= 0.7) splits.push(split); + } else splits.push(split); + } + } + + return splits; + }; + } + + return [timeIncrs, _timeAxisStamps, timeAxisSplits]; + } + + const [timeIncrsMs, _timeAxisStampsMs, timeAxisSplitsMs] = genTimeStuffs(1); + const [timeIncrsS, _timeAxisStampsS, timeAxisSplitsS] = genTimeStuffs(1e-3); + + // base 2 + 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), + ), + ); + } + + // 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); + } + + function timeSeriesStamp(stampCfg, fmtDate) { + return fmtDate(stampCfg); + } + const _timeSeriesStamp = "{YYYY}-{MM}-{DD} {h}:{mm}{aa}"; + + function timeSeriesVal(tzDate, stamp) { + return (self, val, seriesIdx, dataIdx) => + dataIdx == null ? LEGEND_DISP : stamp(tzDate(val)); + } + + function legendStroke(self, seriesIdx) { + let s = self.series[seriesIdx]; + return s.width + ? s.stroke(self, seriesIdx) + : s.points.width + ? s.points.stroke(self, seriesIdx) + : null; + } + + function legendFill(self, seriesIdx) { + return self.series[seriesIdx].fill(self, seriesIdx); + } + + const legendOpts = { + show: true, + live: true, + isolate: false, + mount: noop, + markers: { + show: true, + width: 2, + stroke: legendStroke, + fill: legendFill, + dash: "solid", + }, + idx: null, + idxs: null, + values: [], + }; + + function cursorPointShow(self, si) { + let o = self.cursor.points; + + let pt = placeDiv(); + + let size = o.size(self, si); + setStylePx(pt, WIDTH, size); + setStylePx(pt, HEIGHT, size); + + let mar = size / -2; + setStylePx(pt, "marginLeft", mar); + setStylePx(pt, "marginTop", mar); + + let width = o.width(self, si, size); + width && setStylePx(pt, "borderWidth", width); + + return pt; + } + + function cursorPointFill(self, si) { + let sp = self.series[si].points; + return sp._fill || sp._stroke; + } + + function cursorPointStroke(self, si) { + let sp = self.series[si].points; + return sp._stroke || sp._fill; + } + + function cursorPointSize(self, si) { + let sp = self.series[si].points; + return sp.size; + } + + const moveTuple = [0, 0]; + + function cursorMove(self, mouseLeft1, mouseTop1) { + moveTuple[0] = mouseLeft1; + moveTuple[1] = mouseTop1; + return moveTuple; + } + + function filtBtn0(self, targ, handle, onlyTarg = true) { + return (e) => { + e.button == 0 && (!onlyTarg || e.target == targ) && handle(e); + }; + } + + function filtTarg(self, targ, handle, onlyTarg = true) { + return (e) => { + (!onlyTarg || e.target == targ) && handle(e); + }; + } + + const cursorOpts = { + show: true, + x: true, + y: true, + lock: false, + move: cursorMove, + points: { + one: false, + show: cursorPointShow, + size: cursorPointSize, + width: 0, + stroke: cursorPointStroke, + fill: cursorPointFill, + }, + + bind: { + mousedown: filtBtn0, + mouseup: filtBtn0, + click: filtBtn0, // legend clicks, not .u-over clicks + dblclick: filtBtn0, + + mousemove: filtTarg, + mouseleave: filtTarg, + mouseenter: filtTarg, + }, + + drag: { + setScale: true, + x: true, + y: false, + dist: 0, + uni: null, + click: (self, e) => { + // e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + }, + _x: false, + _y: false, + }, + + focus: { + dist: (self, seriesIdx, dataIdx, valPos, curPos) => valPos - curPos, + prox: -1, + bias: 0, + }, + + hover: { + skip: [void 0], + prox: null, + bias: 0, + }, + + left: -10, + top: -10, + idx: null, + dataIdx: null, + idxs: null, + + event: null, + }; + + const axisLines = { + show: true, + stroke: "rgba(0,0,0,0.07)", + width: 2, + // dash: [], + }; + + const grid = assign({}, axisLines, { + filter: retArg1, + }); + + const ticks = assign({}, grid, { + size: 10, + }); + + const border = assign({}, axisLines, { + show: false, + }); + + 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 lineGap = 1.5; // font-size multiplier + + const xAxisOpts = { + show: true, + scale: "x", + stroke: hexBlack, + space: 50, + gap: 5, + alignTo: 1, + size: 50, + labelGap: 0, + labelSize: 30, + labelFont, + side: 2, + // class: "x-vals", + // incrs: timeIncrs, + // values: timeVals, + // filter: retArg1, + grid, + ticks, + border, + font, + lineGap, + 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; + } + + // this doesnt work for sin, which needs to come off from 0 independently in pos and neg dirs + 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); + + // boo: 10 ** -24 === 1.0000000000000001e-24 + // this grabs the proper 1e-24 one + if (logBase == 10) foundIncr = numIncrs[closestIdx(foundIncr, numIncrs)]; + + let split = scaleMin; + let nextMagIncr = foundIncr * logBase; + + if (logBase == 10) + nextMagIncr = numIncrs[closestIdx(nextMagIncr, numIncrs)]; + + do { + splits.push(split); + split = split + foundIncr; + + if (logBase == 10 && !fixedDec.has(split)) + split = roundDec(split, fixedDec.get(foundIncr)); + + if (split >= nextMagIncr) { + foundIncr = split; + nextMagIncr = foundIncr * logBase; + + if (logBase == 10) + nextMagIncr = numIncrs[closestIdx(nextMagIncr, numIncrs)]; + } + } while (split <= scaleMax); + + return splits; + } + + function asinhAxisSplits( + self, + axisIdx, + scaleMin, + scaleMax, + foundIncr, + foundSpace, + forceMin, + ) { + let sc = self.scales[self.axes[axisIdx].scale]; + + let linthresh = sc.asinh; + + let posSplits = + scaleMax > linthresh + ? logAxisSplits( + self, + axisIdx, + max(linthresh, scaleMin), + scaleMax, + foundIncr, + ) + : [linthresh]; + let zero = scaleMax >= 0 && scaleMin <= 0 ? [0] : []; + let negSplits = + scaleMin < -linthresh + ? logAxisSplits( + self, + axisIdx, + max(linthresh, -scaleMax), + -scaleMin, + foundIncr, + ) + : [linthresh]; + + return negSplits + .reverse() + .map((v) => -v) + .concat(zero, posSplits); + } + + const RE_ALL = /./; + const RE_12357 = /[12357]/; + const RE_125 = /[125]/; + const RE_1 = /1/; + + const _filt = (splits, distr, re, keepMod) => + splits.map((v, i) => + (distr == 4 && v == 0) || + (i % keepMod == 0 && re.test(v.toExponential()[v < 0 ? 1 : 0])) + ? v + : null, + ); + + function log10AxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) { + let axis = self.axes[axisIdx]; + let scaleKey = axis.scale; + let sc = self.scales[scaleKey]; + + // if (sc.distr == 3 && sc.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; + + if (re == RE_1) { + let magSpace = abs(valToPos(1, scaleKey) - _10); + + if (magSpace < minSpace) + return _filt( + splits.slice().reverse(), + sc.distr, + re, + ceil(minSpace / magSpace), + ).reverse(); // max->min skip + } + + return _filt(splits, sc.distr, re, 1); + } + + function log2AxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) { + let axis = self.axes[axisIdx]; + let scaleKey = axis.scale; + let minSpace = axis._space; + let valToPos = self.valToPos; + + let magSpace = abs(valToPos(1, scaleKey) - valToPos(2, scaleKey)); + + if (magSpace < minSpace) + return _filt( + splits.slice().reverse(), + 3, + RE_ALL, + ceil(minSpace / magSpace), + ).reverse(); // max->min skip + + return splits; + } + + function numSeriesVal(self, val, seriesIdx, dataIdx) { + return dataIdx == null ? LEGEND_DISP : val == null ? "" : fmtNum(val); + } + + const yAxisOpts = { + show: true, + scale: "y", + stroke: hexBlack, + space: 30, + gap: 5, + alignTo: 1, + size: 50, + labelGap: 0, + labelSize: 30, + labelFont, + side: 3, + // class: "y-vals", + // incrs: numIncrs, + // values: (vals, space) => vals, + // filter: retArg1, + grid, + ticks, + border, + font, + lineGap, + rotate: 0, + }; + + // takes stroke width + function ptDia(width, mult) { + let dia = 3 + (width || 1) * 2; + return roundDec(dia * mult, 3); + } + + function seriesPointsShow(self, si) { + let { scale, idxs } = self.series[0]; + let xData = self._data[0]; + let p0 = self.valToPos(xData[idxs[0]], scale, true); + let p1 = self.valToPos(xData[idxs[1]], scale, true); + let dim = abs(p1 - p0); + + let s = self.series[si]; + // const dia = ptDia(s.width, pxRatio); + let maxPts = dim / (s.points.space * pxRatio); + return idxs[1] - idxs[0] <= maxPts; + } + + const facet = { + scale: null, + auto: true, + sorted: 0, + + // internal caches + min: inf, + max: -inf, + }; + + const gaps = (self, seriesIdx, idx0, idx1, nullGaps) => nullGaps; + + const xySeriesOpts = { + show: true, + auto: true, + sorted: 0, + gaps, + alpha: 1, + facets: [ + assign({}, facet, { scale: "x" }), + assign({}, facet, { scale: "y" }), + ], + }; + + const ySeriesOpts = { + scale: "y", + auto: true, + sorted: 0, + show: true, + spanGaps: false, + gaps, + alpha: 1, + points: { + show: seriesPointsShow, + filter: null, + // paths: + // 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, + }; + + function clampScale(self, val, scaleMin, scaleMax, scaleKey) { + /* + if (val < 0) { + let cssHgt = self.bbox.height / pxRatio; + let absPos = self.valToPos(abs(val), scaleKey); + let fromBtm = cssHgt - absPos; + return self.posToVal(cssHgt + fromBtm, scaleKey); + } + */ + return scaleMin / 10; + } + + const xScaleOpts = { + time: FEAT_TIME, + auto: true, + distr: 1, + log: 10, + asinh: 1, + min: null, + max: null, + dir: 1, + ori: 0, + }; + + const yScaleOpts = assign({}, xScaleOpts, { + time: false, + ori: 1, + }); + + const syncs = {}; + + function _sync(key, opts) { + let s = syncs[key]; + + if (!s) { + s = { + key, + plots: [], + sub(plot) { + s.plots.push(plot); + }, + unsub(plot) { + s.plots = s.plots.filter((c) => c != plot); + }, + pub(type, self, x, y, w, h, i) { + for (let j = 0; j < s.plots.length; j++) + s.plots[j] != self && s.plots[j].pub(type, self, x, y, w, h, i); + }, + }; + + if (key != null) syncs[key] = s; + } + + return s; + } + + const BAND_CLIP_FILL = 1 << 0; + const BAND_CLIP_STROKE = 1 << 1; + + function orient(u, seriesIdx, cb) { + const mode = u.mode; + const series = u.series[seriesIdx]; + const data = mode == 2 ? u._data[seriesIdx] : u._data; + const scales = u.scales; + const bbox = u.bbox; + + let dx = data[0], + dy = mode == 2 ? data[1] : data[seriesIdx], + sx = + mode == 2 ? scales[series.facets[0].scale] : scales[u.series[0].scale], + sy = mode == 2 ? scales[series.facets[1].scale] : scales[series.scale], + l = bbox.left, + t = bbox.top, + w = bbox.width, + h = bbox.height, + H = u.valToPosH, + V = u.valToPosV; + + return sx.ori == 0 + ? cb( + series, + dx, + dy, + sx, + sy, + H, + V, + l, + t, + w, + h, + moveToH, + lineToH, + rectH, + arcH, + bezierCurveToH, + ) + : cb( + series, + dx, + dy, + sx, + sy, + V, + H, + t, + l, + h, + w, + moveToV, + lineToV, + rectV, + arcV, + bezierCurveToV, + ); + } + + function bandFillClipDirs(self, seriesIdx) { + let fillDir = 0; + + // 2 bits, -1 | 1 + let clipDirs = 0; + + let bands = ifNull(self.bands, EMPTY_ARR); + + for (let i = 0; i < bands.length; i++) { + let b = bands[i]; + + // is a "from" band edge + if (b.series[0] == seriesIdx) fillDir = b.dir; + // is a "to" band edge + else if (b.series[1] == seriesIdx) { + if (b.dir == 1) clipDirs |= 1; + else clipDirs |= 2; + } + } + + return [ + fillDir, + clipDirs == 1 + ? -1 // neg only + : clipDirs == 2 + ? 1 // pos only + : clipDirs == 3 + ? 2 // both + : 0, // neither + ]; + } + + function seriesFillTo(self, seriesIdx, dataMin, dataMax, bandFillDir) { + let mode = self.mode; + let series = self.series[seriesIdx]; + let scaleKey = mode == 2 ? series.facets[1].scale : series.scale; + let scale = self.scales[scaleKey]; + + return bandFillDir == -1 + ? scale.min + : bandFillDir == 1 + ? scale.max + : scale.distr == 3 + ? scale.dir == 1 + ? scale.min + : scale.max + : 0; + } + + // creates inverted band clip path (from stroke path -> yMax || yMin) + // clipDir is always inverse of fillDir + // default clip dir is upwards (1), since default band fill is downwards/fillBelowTo (-1) (highIdx -> lowIdx) + function clipBandLine(self, seriesIdx, idx0, idx1, strokePath, clipDir) { + return orient( + self, + seriesIdx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + ) => { + let pxRound = series.pxRound; + + const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1); + const lineTo = scaleX.ori == 0 ? lineToH : lineToV; + + let frIdx, toIdx; + + if (dir == 1) { + frIdx = idx0; + toIdx = idx1; + } else { + frIdx = idx1; + toIdx = idx0; + } + + // path start + let x0 = pxRound(valToPosX(dataX[frIdx], scaleX, xDim, xOff)); + let y0 = pxRound(valToPosY(dataY[frIdx], scaleY, yDim, yOff)); + // path end x + let x1 = pxRound(valToPosX(dataX[toIdx], scaleX, xDim, xOff)); + // upper or lower y limit + let yLimit = pxRound( + valToPosY(clipDir == 1 ? scaleY.max : scaleY.min, scaleY, yDim, yOff), + ); + + let clip = new Path2D(strokePath); + + lineTo(clip, x1, yLimit); + lineTo(clip, x0, yLimit); + lineTo(clip, x0, y0); + + return clip; + }, + ); + } + + function clipGaps(gaps, ori, plotLft, plotTop, plotWid, plotHgt) { + let clip = null; + + // create clip path (invert gaps and non-gaps) + if (gaps.length > 0) { + clip = new Path2D(); + + const rect = ori == 0 ? rectH : rectV; + + let prevGapEnd = plotLft; + + for (let i = 0; i < gaps.length; i++) { + let g = gaps[i]; + + if (g[1] > g[0]) { + let w = g[0] - prevGapEnd; + + w > 0 && rect(clip, prevGapEnd, plotTop, w, plotTop + plotHgt); + + prevGapEnd = g[1]; + } + } + + let w = plotLft + plotWid - prevGapEnd; + + // hack to ensure we expand the clip enough to avoid cutting off strokes at edges + let maxStrokeWidth = 10; + + w > 0 && + rect( + clip, + prevGapEnd, + plotTop - maxStrokeWidth / 2, + w, + plotTop + plotHgt + maxStrokeWidth, + ); + } + + return clip; + } + + function addGap(gaps, fromX, toX) { + 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 findGaps(xs, ys, idx0, idx1, dir, pixelForX, align) { + let gaps = []; + let len = xs.length; + + for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) { + let yVal = ys[i]; + + if (yVal === null) { + let fr = i, + to = i; + + if (dir == 1) { + while (++i <= idx1 && ys[i] === null) to = i; + } else { + while (--i >= idx0 && ys[i] === null) to = i; + } + + let frPx = pixelForX(xs[fr]); + let toPx = to == fr ? frPx : pixelForX(xs[to]); + + // if value adjacent to edge null is same pixel, then it's partially + // filled and gap should start at next pixel + let fri2 = fr - dir; + let frPx2 = + align <= 0 && fri2 >= 0 && fri2 < len ? pixelForX(xs[fri2]) : frPx; + // if (frPx2 == frPx) + // frPx++; + // else + frPx = frPx2; + + let toi2 = to + dir; + let toPx2 = + align >= 0 && toi2 >= 0 && toi2 < len ? pixelForX(xs[toi2]) : toPx; + // if (toPx2 == toPx) + // toPx--; + // else + toPx = toPx2; + + if (toPx >= frPx) gaps.push([frPx, toPx]); // addGap + } + } + + return gaps; + } + + function pxRoundGen(pxAlign) { + return pxAlign == 0 + ? retArg0 + : pxAlign == 1 + ? round + : (v) => incrRound(v, pxAlign); + } + + /* + // inefficient linear interpolation that does bi-directinal scans on each call + export function costlyLerp(i, idx0, idx1, _dirX, dataY) { + let prevNonNull = nonNullIdx(dataY, _dirX == 1 ? idx0 : idx1, i, -_dirX); + let nextNonNull = nonNullIdx(dataY, i, _dirX == 1 ? idx1 : idx0, _dirX); + + let prevVal = dataY[prevNonNull]; + let nextVal = dataY[nextNonNull]; + + return prevVal + (i - prevNonNull) / (nextNonNull - prevNonNull) * (nextVal - prevVal); + } + */ + + function rect(ori) { + let moveTo = ori == 0 ? moveToH : moveToV; + + let arcTo = + ori == 0 + ? (p, x1, y1, x2, y2, r) => { + p.arcTo(x1, y1, x2, y2, r); + } + : (p, y1, x1, y2, x2, r) => { + p.arcTo(x1, y1, x2, y2, r); + }; + + let rect = + ori == 0 + ? (p, x, y, w, h) => { + p.rect(x, y, w, h); + } + : (p, y, x, h, w) => { + p.rect(x, y, w, h); + }; + + // TODO (pending better browser support): https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect + return (p, x, y, w, h, endRad = 0, baseRad = 0) => { + if (endRad == 0 && baseRad == 0) rect(p, x, y, w, h); + else { + endRad = min(endRad, w / 2, h / 2); + baseRad = min(baseRad, w / 2, h / 2); + + // adapted from https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-using-html-canvas/7838871#7838871 + moveTo(p, x + endRad, y); + arcTo(p, x + w, y, x + w, y + h, endRad); + arcTo(p, x + w, y + h, x, y + h, baseRad); + arcTo(p, x, y + h, x, y, baseRad); + arcTo(p, x, y, x + w, y, endRad); + p.closePath(); + } + }; + } + + // orientation-inverting canvas functions + const moveToH = (p, x, y) => { + p.moveTo(x, y); + }; + const moveToV = (p, y, x) => { + p.moveTo(x, y); + }; + const lineToH = (p, x, y) => { + p.lineTo(x, y); + }; + const lineToV = (p, y, x) => { + p.lineTo(x, y); + }; + const rectH = rect(0); + const rectV = rect(1); + const arcH = (p, x, y, r, startAngle, endAngle) => { + p.arc(x, y, r, startAngle, endAngle); + }; + const arcV = (p, y, x, r, startAngle, endAngle) => { + p.arc(x, y, r, startAngle, endAngle); + }; + const bezierCurveToH = (p, bp1x, bp1y, bp2x, bp2y, p2x, p2y) => { + p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); + }; + const bezierCurveToV = (p, bp1y, bp1x, bp2y, bp2x, p2y, p2x) => { + p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); + }; + + // TODO: drawWrap(seriesIdx, drawPoints) (save, restore, translate, clip) + function points(opts) { + return (u, seriesIdx, idx0, idx1, filtIdxs) => { + // log("drawPoints()", arguments); + + return orient( + u, + seriesIdx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + ) => { + let { pxRound, points } = series; + + let moveTo, arc; + + if (scaleX.ori == 0) { + moveTo = moveToH; + arc = arcH; + } else { + moveTo = moveToV; + arc = arcV; + } + + const width = roundDec(points.width * pxRatio, 3); + + let rad = ((points.size - points.width) / 2) * pxRatio; + let dia = roundDec(rad * 2, 3); + + let fill = new Path2D(); + let clip = new Path2D(); + + let { left: lft, top: top, width: wid, height: hgt } = u.bbox; + + rectH(clip, lft - dia, top - dia, wid + dia * 2, hgt + dia * 2); + + const drawPoint = (pi) => { + if (dataY[pi] != null) { + let x = pxRound(valToPosX(dataX[pi], scaleX, xDim, xOff)); + let y = pxRound(valToPosY(dataY[pi], scaleY, yDim, yOff)); + + moveTo(fill, x + rad, y); + arc(fill, x, y, rad, 0, PI * 2); + } + }; + + if (filtIdxs) filtIdxs.forEach(drawPoint); + else { + for (let pi = idx0; pi <= idx1; pi++) drawPoint(pi); + } + + return { + stroke: width > 0 ? fill : null, + fill, + clip, + flags: BAND_CLIP_FILL | BAND_CLIP_STROKE, + }; + }, + ); + }; + } + + function _drawAcc(lineTo) { + return (stroke, accX, minY, maxY, inY, outY) => { + if (minY != maxY) { + if (inY != minY && outY != minY) lineTo(stroke, accX, minY); + if (inY != maxY && outY != maxY) lineTo(stroke, accX, maxY); + + lineTo(stroke, accX, outY); + } + }; + } + + const drawAccH = _drawAcc(lineToH); + const drawAccV = _drawAcc(lineToV); + + function linear(opts) { + const alignGaps = ifNull(opts?.alignGaps, 0); + + return (u, seriesIdx, idx0, idx1) => { + return orient( + u, + seriesIdx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + ) => { + [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1); + + let pxRound = series.pxRound; + + let pixelForX = (val) => pxRound(valToPosX(val, scaleX, xDim, xOff)); + let pixelForY = (val) => pxRound(valToPosY(val, scaleY, yDim, yOff)); + + let lineTo, drawAcc; + + if (scaleX.ori == 0) { + lineTo = lineToH; + drawAcc = drawAccH; + } else { + lineTo = lineToV; + drawAcc = drawAccV; + } + + const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1); + + const _paths = { + stroke: new Path2D(), + fill: null, + clip: null, + band: null, + gaps: null, + flags: BAND_CLIP_FILL, + }; + const stroke = _paths.stroke; + + let hasGap = false; + + // decimate when number of points >= 4x available pixels + const decimate = idx1 - idx0 >= xDim * 4; + + if (decimate) { + let xForPixel = (pos) => u.posToVal(pos, scaleX.key, true); + + let minY = null, + maxY = null, + inY, + outY, + drawnAtX; + + let accX = pixelForX(dataX[dir == 1 ? idx0 : idx1]); + + let idx0px = pixelForX(dataX[idx0]); + let idx1px = pixelForX(dataX[idx1]); + + // tracks limit of current x bucket to avoid having to get x pixel for every x value + let nextAccXVal = xForPixel(dir == 1 ? idx0px + 1 : idx1px - 1); + + for ( + let i = dir == 1 ? idx0 : idx1; + i >= idx0 && i <= idx1; + i += dir + ) { + let xVal = dataX[i]; + let reuseAccX = + dir == 1 ? xVal < nextAccXVal : xVal > nextAccXVal; + let x = reuseAccX ? accX : pixelForX(xVal); + + let yVal = dataY[i]; + + if (x == accX) { + if (yVal != null) { + outY = yVal; + + if (minY == null) { + lineTo(stroke, x, pixelForY(outY)); + inY = minY = maxY = outY; + } else { + if (outY < minY) minY = outY; + else if (outY > maxY) maxY = outY; + } + } else { + if (yVal === null) hasGap = true; + } + } else { + if (minY != null) + drawAcc( + stroke, + accX, + pixelForY(minY), + pixelForY(maxY), + pixelForY(inY), + pixelForY(outY), + ); + + if (yVal != null) { + outY = yVal; + lineTo(stroke, x, pixelForY(outY)); + minY = maxY = inY = outY; + } else { + minY = maxY = null; + + if (yVal === null) hasGap = true; + } + + accX = x; + nextAccXVal = xForPixel(accX + dir); + } + } + + if (minY != null && minY != maxY && drawnAtX != accX) + drawAcc( + stroke, + accX, + pixelForY(minY), + pixelForY(maxY), + pixelForY(inY), + pixelForY(outY), + ); + } else { + for ( + let i = dir == 1 ? idx0 : idx1; + i >= idx0 && i <= idx1; + i += dir + ) { + let yVal = dataY[i]; + + if (yVal === null) hasGap = true; + else if (yVal != null) + lineTo(stroke, pixelForX(dataX[i]), pixelForY(yVal)); + } + } + + let [bandFillDir, bandClipDir] = bandFillClipDirs(u, seriesIdx); + + if (series.fill != null || bandFillDir != 0) { + let fill = (_paths.fill = new Path2D(stroke)); + + let fillToVal = series.fillTo( + u, + seriesIdx, + series.min, + series.max, + bandFillDir, + ); + let fillToY = pixelForY(fillToVal); + + let frX = pixelForX(dataX[idx0]); + let toX = pixelForX(dataX[idx1]); + + if (dir == -1) [toX, frX] = [frX, toX]; + + lineTo(fill, toX, fillToY); + lineTo(fill, frX, fillToY); + } + + if (!series.spanGaps) { + // skip in mode: 2? + // console.time('gaps'); + let gaps = []; + + hasGap && + gaps.push( + ...findGaps( + dataX, + dataY, + idx0, + idx1, + dir, + pixelForX, + alignGaps, + ), + ); + + // console.timeEnd('gaps'); + + // console.log('gaps', JSON.stringify(gaps)); + + _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps); + + _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim); + } + + if (bandClipDir != 0) { + _paths.band = + bandClipDir == 2 + ? [ + clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1), + clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1), + ] + : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir); + } + + return _paths; + }, + ); + }; + } + + // BUG: align: -1 behaves like align: 1 when scale.dir: -1 + function stepped(opts) { + const align = ifNull(opts.align, 1); + // whether to draw ascenders/descenders at null/gap bondaries + const ascDesc = ifNull(opts.ascDesc, false); + const alignGaps = ifNull(opts.alignGaps, 0); + const extend = ifNull(opts.extend, false); + + return (u, seriesIdx, idx0, idx1) => { + return orient( + u, + seriesIdx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + ) => { + [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1); + + let pxRound = series.pxRound; + + let { left, width } = u.bbox; + + let pixelForX = (val) => pxRound(valToPosX(val, scaleX, xDim, xOff)); + let pixelForY = (val) => pxRound(valToPosY(val, scaleY, yDim, yOff)); + + let lineTo = scaleX.ori == 0 ? lineToH : lineToV; + + const _paths = { + stroke: new Path2D(), + fill: null, + clip: null, + band: null, + gaps: null, + flags: BAND_CLIP_FILL, + }; + const stroke = _paths.stroke; + + const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1); + + let prevYPos = pixelForY(dataY[dir == 1 ? idx0 : idx1]); + let firstXPos = pixelForX(dataX[dir == 1 ? idx0 : idx1]); + let prevXPos = firstXPos; + + let firstXPosExt = firstXPos; + + if (extend && align == -1) { + firstXPosExt = left; + lineTo(stroke, firstXPosExt, prevYPos); + } + + lineTo(stroke, firstXPos, prevYPos); + + for ( + let i = dir == 1 ? idx0 : idx1; + i >= idx0 && i <= idx1; + i += dir + ) { + let yVal1 = dataY[i]; + + if (yVal1 == null) continue; + + let x1 = pixelForX(dataX[i]); + let y1 = pixelForY(yVal1); + + if (align == 1) lineTo(stroke, x1, prevYPos); + else lineTo(stroke, prevXPos, y1); + + lineTo(stroke, x1, y1); + + prevYPos = y1; + prevXPos = x1; + } + + let prevXPosExt = prevXPos; + + if (extend && align == 1) { + prevXPosExt = left + width; + lineTo(stroke, prevXPosExt, prevYPos); + } + + let [bandFillDir, bandClipDir] = bandFillClipDirs(u, seriesIdx); + + if (series.fill != null || bandFillDir != 0) { + let fill = (_paths.fill = new Path2D(stroke)); + + let fillTo = series.fillTo( + u, + seriesIdx, + series.min, + series.max, + bandFillDir, + ); + let fillToY = pixelForY(fillTo); + + lineTo(fill, prevXPosExt, fillToY); + lineTo(fill, firstXPosExt, fillToY); + } + + if (!series.spanGaps) { + // console.time('gaps'); + let gaps = []; + + gaps.push( + ...findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps), + ); + + // console.timeEnd('gaps'); + + // console.log('gaps', JSON.stringify(gaps)); + + // expand/contract clips for ascenders/descenders + let halfStroke = (series.width * pxRatio) / 2; + let startsOffset = ascDesc || align == 1 ? halfStroke : -halfStroke; + let endsOffset = ascDesc || align == -1 ? -halfStroke : halfStroke; + + gaps.forEach((g) => { + g[0] += startsOffset; + g[1] += endsOffset; + }); + + _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps); + + _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim); + } + + if (bandClipDir != 0) { + _paths.band = + bandClipDir == 2 + ? [ + clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1), + clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1), + ] + : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir); + } + + return _paths; + }, + ); + }; + } + + function findColWidth( + dataX, + dataY, + valToPosX, + scaleX, + xDim, + xOff, + colWid = inf, + ) { + if (dataX.length > 1) { + // prior index with non-undefined y data + let prevIdx = null; + + // scan full dataset for smallest adjacent delta + // will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end + for (let i = 0, minDelta = Infinity; i < dataX.length; i++) { + if (dataY[i] !== undefined) { + if (prevIdx != null) { + let delta = abs(dataX[i] - dataX[prevIdx]); + + if (delta < minDelta) { + minDelta = delta; + colWid = abs( + valToPosX(dataX[i], scaleX, xDim, xOff) - + valToPosX(dataX[prevIdx], scaleX, xDim, xOff), + ); + } + } + + prevIdx = i; + } + } + } + + return colWid; + } + + function bars(opts) { + opts = opts || EMPTY_OBJ; + const size = ifNull(opts.size, [0.6, inf, 1]); + const align = opts.align || 0; + const _extraGap = opts.gap || 0; + + let ro = opts.radius; + + ro = + // [valueRadius, baselineRadius] + ro == null ? [0, 0] : typeof ro == "number" ? [ro, 0] : ro; + + const radiusFn = fnOrSelf(ro); + + const gapFactor = 1 - size[0]; + const _maxWidth = ifNull(size[1], inf); + const _minWidth = ifNull(size[2], 1); + + const disp = ifNull(opts.disp, EMPTY_OBJ); + const _each = ifNull(opts.each, (_) => {}); + + const { fill: dispFills, stroke: dispStrokes } = disp; + + return (u, seriesIdx, idx0, idx1) => { + return orient( + u, + seriesIdx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + ) => { + let pxRound = series.pxRound; + let _align = align; + + let extraGap = _extraGap * pxRatio; + let maxWidth = _maxWidth * pxRatio; + let minWidth = _minWidth * pxRatio; + + let valRadius, baseRadius; + + if (scaleX.ori == 0) [valRadius, baseRadius] = radiusFn(u, seriesIdx); + else [baseRadius, valRadius] = radiusFn(u, seriesIdx); + + const _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1); + // const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1); + + let rect = scaleX.ori == 0 ? rectH : rectV; + + let each = + scaleX.ori == 0 + ? _each + : (u, seriesIdx, i, top, lft, hgt, wid) => { + _each(u, seriesIdx, i, lft, top, wid, hgt); + }; + + // band where this series is the "from" edge + let band = ifNull(u.bands, EMPTY_ARR).find( + (b) => b.series[0] == seriesIdx, + ); + + let fillDir = band != null ? band.dir : 0; + let fillTo = series.fillTo( + u, + seriesIdx, + series.min, + series.max, + fillDir, + ); + let fillToY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff)); + + // barWid is to center of stroke + let xShift, + barWid, + fullGap, + colWid = xDim; + + let strokeWidth = pxRound(series.width * pxRatio); + + let multiPath = false; + + let fillColors = null; + let fillPaths = null; + let strokeColors = null; + let strokePaths = null; + + if (dispFills != null && (strokeWidth == 0 || dispStrokes != null)) { + multiPath = true; + + fillColors = dispFills.values(u, seriesIdx, idx0, idx1); + fillPaths = new Map(); + new Set(fillColors).forEach((color) => { + if (color != null) fillPaths.set(color, new Path2D()); + }); + + if (strokeWidth > 0) { + strokeColors = dispStrokes.values(u, seriesIdx, idx0, idx1); + strokePaths = new Map(); + new Set(strokeColors).forEach((color) => { + if (color != null) strokePaths.set(color, new Path2D()); + }); + } + } + + let { x0, size } = disp; + + if (x0 != null && size != null) { + _align = 1; + dataX = x0.values(u, seriesIdx, idx0, idx1); + + if (x0.unit == 2) + dataX = dataX.map((pct) => + u.posToVal(xOff + pct * xDim, scaleX.key, true), + ); + + // assumes uniform sizes, for now + let sizes = size.values(u, seriesIdx, idx0, idx1); + + if (size.unit == 2) barWid = sizes[0] * xDim; + else + barWid = + valToPosX(sizes[0], scaleX, xDim, xOff) - + valToPosX(0, scaleX, xDim, xOff); // assumes linear scale (delta from 0) + + colWid = findColWidth( + dataX, + dataY, + valToPosX, + scaleX, + xDim, + xOff, + colWid, + ); + + let gapWid = colWid - barWid; + fullGap = gapWid + extraGap; + } else { + colWid = findColWidth( + dataX, + dataY, + valToPosX, + scaleX, + xDim, + xOff, + colWid, + ); + + let gapWid = colWid * gapFactor; + + fullGap = gapWid + extraGap; + barWid = colWid - fullGap; + } + + if (fullGap < 1) fullGap = 0; + + if (strokeWidth >= barWid / 2) strokeWidth = 0; + + // for small gaps, disable pixel snapping since gap inconsistencies become noticible and annoying + if (fullGap < 5) pxRound = retArg0; + + let insetStroke = fullGap > 0; + + let rawBarWid = colWid - fullGap - (insetStroke ? strokeWidth : 0); + + barWid = pxRound(clamp(rawBarWid, minWidth, maxWidth)); + + xShift = + (_align == 0 ? barWid / 2 : _align == _dirX ? 0 : barWid) - + _align * + _dirX * + ((_align == 0 ? extraGap / 2 : 0) + + (insetStroke ? strokeWidth / 2 : 0)); + + const _paths = { + stroke: null, + fill: null, + clip: null, + band: null, + gaps: null, + flags: 0, + }; // disp, geom + + const stroke = multiPath ? null : new Path2D(); + + let dataY0 = null; + + if (band != null) dataY0 = u.data[band.series[1]]; + else { + let { y0, y1 } = disp; + + if (y0 != null && y1 != null) { + dataY = y1.values(u, seriesIdx, idx0, idx1); + dataY0 = y0.values(u, seriesIdx, idx0, idx1); + } + } + + let radVal = valRadius * barWid; + let radBase = baseRadius * barWid; + + for ( + let i = _dirX == 1 ? idx0 : idx1; + i >= idx0 && i <= idx1; + i += _dirX + ) { + let yVal = dataY[i]; + + if (yVal == null) continue; + + if (dataY0 != null) { + let yVal0 = dataY0[i] ?? 0; + + if (yVal - yVal0 == 0) continue; + + fillToY = valToPosY(yVal0, scaleY, yDim, yOff); + } + + let xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i; + + // TODO: all xPos can be pre-computed once for all series in aligned set + let xPos = valToPosX(xVal, scaleX, xDim, xOff); + let yPos = valToPosY(ifNull(yVal, fillTo), scaleY, yDim, yOff); + + let lft = pxRound(xPos - xShift); + let btm = pxRound(max(yPos, fillToY)); + let top = pxRound(min(yPos, fillToY)); + // this includes the stroke + let barHgt = btm - top; + + if (yVal != null) { + // && yVal != fillTo (0 height bar) + let rv = yVal < 0 ? radBase : radVal; + let rb = yVal < 0 ? radVal : radBase; + + if (multiPath) { + if (strokeWidth > 0 && strokeColors[i] != null) + rect( + strokePaths.get(strokeColors[i]), + lft, + top + floor(strokeWidth / 2), + barWid, + max(0, barHgt - strokeWidth), + rv, + rb, + ); + + if (fillColors[i] != null) + rect( + fillPaths.get(fillColors[i]), + lft, + top + floor(strokeWidth / 2), + barWid, + max(0, barHgt - strokeWidth), + rv, + rb, + ); + } else + rect( + stroke, + lft, + top + floor(strokeWidth / 2), + barWid, + max(0, barHgt - strokeWidth), + rv, + rb, + ); + + each( + u, + seriesIdx, + i, + lft - strokeWidth / 2, + top, + barWid + strokeWidth, + barHgt, + ); + } + } + + if (strokeWidth > 0) _paths.stroke = multiPath ? strokePaths : stroke; + else if (!multiPath) { + _paths._fill = + series.width == 0 + ? series._fill + : (series._stroke ?? series._fill); + _paths.width = 0; + } + + _paths.fill = multiPath ? fillPaths : stroke; + + return _paths; + }, + ); + }; + } + + function splineInterp(interp, opts) { + const alignGaps = ifNull(opts?.alignGaps, 0); + + return (u, seriesIdx, idx0, idx1) => { + return orient( + u, + seriesIdx, + ( + series, + dataX, + dataY, + scaleX, + scaleY, + valToPosX, + valToPosY, + xOff, + yOff, + xDim, + yDim, + ) => { + [idx0, idx1] = nonNullIdxs(dataY, idx0, idx1); + + let pxRound = series.pxRound; + + let pixelForX = (val) => pxRound(valToPosX(val, scaleX, xDim, xOff)); + let pixelForY = (val) => pxRound(valToPosY(val, scaleY, yDim, yOff)); + + let moveTo, bezierCurveTo, lineTo; + + if (scaleX.ori == 0) { + moveTo = moveToH; + lineTo = lineToH; + bezierCurveTo = bezierCurveToH; + } else { + moveTo = moveToV; + lineTo = lineToV; + bezierCurveTo = bezierCurveToV; + } + + const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1); + + let firstXPos = pixelForX(dataX[dir == 1 ? idx0 : idx1]); + let prevXPos = firstXPos; + + let xCoords = []; + let yCoords = []; + + for ( + let i = dir == 1 ? idx0 : idx1; + i >= idx0 && i <= idx1; + i += dir + ) { + let yVal = dataY[i]; + + if (yVal != null) { + let xVal = dataX[i]; + let xPos = pixelForX(xVal); + + xCoords.push((prevXPos = xPos)); + yCoords.push(pixelForY(dataY[i])); + } + } + + const _paths = { + stroke: interp( + xCoords, + yCoords, + moveTo, + lineTo, + bezierCurveTo, + pxRound, + ), + fill: null, + clip: null, + band: null, + gaps: null, + flags: BAND_CLIP_FILL, + }; + const stroke = _paths.stroke; + + let [bandFillDir, bandClipDir] = bandFillClipDirs(u, seriesIdx); + + if (series.fill != null || bandFillDir != 0) { + let fill = (_paths.fill = new Path2D(stroke)); + + let fillTo = series.fillTo( + u, + seriesIdx, + series.min, + series.max, + bandFillDir, + ); + let fillToY = pixelForY(fillTo); + + lineTo(fill, prevXPos, fillToY); + lineTo(fill, firstXPos, fillToY); + } + + if (!series.spanGaps) { + // console.time('gaps'); + let gaps = []; + + gaps.push( + ...findGaps(dataX, dataY, idx0, idx1, dir, pixelForX, alignGaps), + ); + + // console.timeEnd('gaps'); + + // console.log('gaps', JSON.stringify(gaps)); + + _paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps); + + _paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim); + } + + if (bandClipDir != 0) { + _paths.band = + bandClipDir == 2 + ? [ + clipBandLine(u, seriesIdx, idx0, idx1, stroke, -1), + clipBandLine(u, seriesIdx, idx0, idx1, stroke, 1), + ] + : clipBandLine(u, seriesIdx, idx0, idx1, stroke, bandClipDir); + } + + return _paths; + + // if FEAT_PATHS: false in rollup.config.js + // u.ctx.save(); + // u.ctx.beginPath(); + // u.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height); + // u.ctx.clip(); + // u.ctx.strokeStyle = u.series[sidx].stroke; + // u.ctx.stroke(stroke); + // u.ctx.fillStyle = u.series[sidx].fill; + // u.ctx.fill(fill); + // u.ctx.restore(); + // return null; + }, + ); + }; + } + + function monotoneCubic(opts) { + return splineInterp(_monotoneCubic, opts); + } + + // Monotone Cubic Spline interpolation, adapted from the Chartist.js implementation: + // https://github.com/gionkunz/chartist-js/blob/e7e78201bffe9609915e5e53cfafa29a5d6c49f9/src/scripts/interpolation.js#L240-L369 + function _monotoneCubic(xs, ys, moveTo, lineTo, bezierCurveTo, pxRound) { + const n = xs.length; + + if (n < 2) return null; + + const path = new Path2D(); + + moveTo(path, xs[0], ys[0]); + + if (n == 2) lineTo(path, xs[1], ys[1]); + else { + let ms = Array(n), + ds = Array(n - 1), + dys = Array(n - 1), + dxs = Array(n - 1); + + // calc deltas and derivative + for (let i = 0; i < n - 1; i++) { + dys[i] = ys[i + 1] - ys[i]; + dxs[i] = xs[i + 1] - xs[i]; + ds[i] = dys[i] / dxs[i]; + } + + // determine desired slope (m) at each point using Fritsch-Carlson method + // http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation + ms[0] = ds[0]; + + for (let i = 1; i < n - 1; i++) { + if (ds[i] === 0 || ds[i - 1] === 0 || ds[i - 1] > 0 !== ds[i] > 0) + ms[i] = 0; + else { + ms[i] = + (3 * (dxs[i - 1] + dxs[i])) / + ((2 * dxs[i] + dxs[i - 1]) / ds[i - 1] + + (dxs[i] + 2 * dxs[i - 1]) / ds[i]); + + if (!isFinite(ms[i])) ms[i] = 0; + } + } + + ms[n - 1] = ds[n - 2]; + + for (let i = 0; i < n - 1; i++) { + bezierCurveTo( + path, + xs[i] + dxs[i] / 3, + ys[i] + (ms[i] * dxs[i]) / 3, + xs[i + 1] - dxs[i] / 3, + ys[i + 1] - (ms[i + 1] * dxs[i]) / 3, + xs[i + 1], + ys[i + 1], + ); + } + } + + return path; + } + + const cursorPlots = new Set(); + + function invalidateRects() { + for (let u of cursorPlots) u.syncRect(true); + } + + if (domEnv) { + on(resize, win, invalidateRects); + on(scroll, win, invalidateRects, true); + on(dppxchange, win, () => { + uPlot.pxRatio = pxRatio; + }); + } + + const linearPath = linear(); + const pointsPath = points(); + + 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 setDefaults2(d, xyo) { + return d.map((o, i) => (i == 0 ? {} : assign({}, xyo, o))); // todo: assign() will not merge facet arrays + } + + function setDefault(o, i, xo, yo) { + return assign({}, i == 0 ? xo : yo, o); + } + + function snapNumX(self, dataMin, dataMax) { + return dataMin == null ? nullNullTuple : [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 + ? nullNullTuple + : rangeNum(dataMin, dataMax, rangePad, true); + } + + function snapLogY(self, dataMin, dataMax, scale) { + return dataMin == null + ? nullNullTuple + : rangeLog(dataMin, dataMax, self.scales[scale].log, false); + } + + const snapLogX = snapLogY; + + function snapAsinhY(self, dataMin, dataMax, scale) { + return dataMin == null + ? nullNullTuple + : rangeAsinh(dataMin, dataMax, self.scales[scale].log, false); + } + + const snapAsinhX = snapAsinhY; + + // dim is logical (getClientBoundingRect) pixels, not canvas pixels + function findIncr(minVal, maxVal, incrs, dim, minSpace) { + let intDigits = max(numIntDigits(minVal), numIntDigits(maxVal)); + + let delta = maxVal - minVal; + + let incrIdx = closestIdx((minSpace / dim) * delta, incrs); + + do { + let foundIncr = incrs[incrIdx]; + let foundSpace = (dim * foundIncr) / delta; + + if ( + foundSpace >= minSpace && + intDigits + (foundIncr < 5 ? fixedDec.get(foundIncr) : 0) <= 17 + ) + return [foundIncr, foundSpace]; + } while (++incrIdx < incrs.length); + + return [0, 0]; + } + + function pxRatioFont(font) { + let fontSize, fontSizeCss; + font = font.replace( + /(\d+)px/, + (m, p1) => (fontSize = round((fontSizeCss = +p1) * pxRatio)) + "px", + ); + return [font, fontSize, fontSizeCss]; + } + + function syncFontSize(axis) { + if (axis.show) { + [axis.font, axis.labelFont].forEach((f) => { + let size = roundDec(f[2] * pxRatio, 1); + f[0] = f[0].replace(/[0-9.]+px/, size + "px"); + f[1] = size; + }); + } + } + + function uPlot(opts, data, then) { + const self = { + mode: ifNull(opts.mode, 1), + }; + + const mode = self.mode; + + function getHPos(val, scale, dim, off) { + let pct = scale.valToPct(val); + return off + dim * (scale.dir == -1 ? 1 - pct : pct); + } + + function getVPos(val, scale, dim, off) { + let pct = scale.valToPct(val); + return off + dim * (scale.dir == -1 ? pct : 1 - pct); + } + + function getPos(val, scale, dim, off) { + return scale.ori == 0 + ? getHPos(val, scale, dim, off) + : getVPos(val, scale, dim, off); + } + + self.valToPosH = getHPos; + self.valToPosV = getVPos; + + 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); + + on( + "click", + wrap, + (e) => { + if (e.target === over) { + let didDrag = mouseLeft1 != mouseLeft0 || mouseTop1 != mouseTop0; + didDrag && drag.click(self, e); + } + }, + true, + ); + + const under = (self.under = placeDiv(UNDER, wrap)); + wrap.appendChild(can); + const over = (self.over = placeDiv(OVER, wrap)); + + opts = copy(opts); + + const pxAlign = +ifNull(opts.pxAlign, 1); + + const pxRound = pxRoundGen(pxAlign); + + (opts.plugins || []).forEach((p) => { + if (p.opts) opts = p.opts(self, opts) || opts; + }); + + const ms = opts.ms || 1e-3; + + const series = (self.series = + mode == 1 + ? setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false) + : setDefaults2(opts.series || [null], xySeriesOpts)); + const axes = (self.axes = setDefaults( + opts.axes || [], + xAxisOpts, + yAxisOpts, + true, + )); + const scales = (self.scales = {}); + const bands = (self.bands = opts.bands || []); + + bands.forEach((b) => { + b.fill = fnOrSelf(b.fill || null); + b.dir = ifNull(b.dir, -1); + }); + + const xScaleKey = mode == 2 ? series[1].facets[0].scale : series[0].scale; + + const drawOrderMap = { + axes: drawAxesGrid, + series: drawSeries, + }; + + const drawOrder = (opts.drawOrder || ["axes", "series"]).map( + (key) => drawOrderMap[key], + ); + + function initValToPct(sc) { + const getVal = + sc.distr == 3 + ? (val) => + log10(val > 0 ? val : sc.clamp(self, val, sc.min, sc.max, sc.key)) + : sc.distr == 4 + ? (val) => asinh(val, sc.asinh) + : sc.distr == 100 + ? (val) => sc.fwd(val) + : (val) => val; + + return (val) => { + let _val = getVal(val); + let { _min, _max } = sc; + let delta = _max - _min; + return (_val - _min) / delta; + }; + } + + 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 + let sc = assign({}, scales[scaleOpts.from], scaleOpts, { + key: scaleKey, + }); + sc.valToPct = initValToPct(sc); + scales[scaleKey] = sc; + } else { + sc = scales[scaleKey] = assign( + {}, + scaleKey == xScaleKey ? xScaleOpts : yScaleOpts, + scaleOpts, + ); + + sc.key = scaleKey; + + let isTime = sc.time; + + let rn = sc.range; + + let rangeIsArr = isArr(rn); + + if (scaleKey != xScaleKey || (mode == 2 && !isTime)) { + // if range array has null limits, it should be auto + if (rangeIsArr && (rn[0] == null || rn[1] == null)) { + rn = { + min: + rn[0] == null + ? autoRangePart + : { + mode: 1, + hard: rn[0], + soft: rn[0], + }, + max: + rn[1] == null + ? autoRangePart + : { + mode: 1, + hard: rn[1], + soft: rn[1], + }, + }; + rangeIsArr = false; + } + + if (!rangeIsArr && isObj(rn)) { + let cfg = rn; + // this is similar to snapNumY + rn = (self, dataMin, dataMax) => + dataMin == null + ? nullNullTuple + : rangeNum(dataMin, dataMax, cfg); + } + } + + sc.range = fnOrSelf( + rn || + (isTime + ? snapTimeX + : scaleKey == xScaleKey + ? sc.distr == 3 + ? snapLogX + : sc.distr == 4 + ? snapAsinhX + : snapNumX + : sc.distr == 3 + ? snapLogY + : sc.distr == 4 + ? snapAsinhY + : snapNumY), + ); + + sc.auto = fnOrSelf(rangeIsArr ? false : sc.auto); + + sc.clamp = fnOrSelf(sc.clamp || clampScale); + + // caches for expensive ops like asinh() & log() + sc._min = sc._max = null; + + sc.valToPct = initValToPct(sc); + } + } + } + + initScale("x"); + initScale("y"); + + // TODO: init scales from facets in mode: 2 + if (mode == 1) { + series.forEach((s) => { + initScale(s.scale); + }); + } + + axes.forEach((a) => { + initScale(a.scale); + }); + + for (let k in opts.scales) initScale(k); + + const scaleX = scales[xScaleKey]; + + const xScaleDistr = scaleX.distr; + + let valToPosX, valToPosY; + + if (scaleX.ori == 0) { + addClass(root, ORI_HZ); + valToPosX = getHPos; + valToPosY = getVPos; + /* + updOriDims = () => { + xDimCan = plotWid; + xOffCan = plotLft; + yDimCan = plotHgt; + yOffCan = plotTop; + + xDimCss = plotWidCss; + xOffCss = plotLftCss; + yDimCss = plotHgtCss; + yOffCss = plotTopCss; + }; + */ + } else { + addClass(root, ORI_VT); + valToPosX = getVPos; + valToPosY = getHPos; + /* + updOriDims = () => { + xDimCan = plotHgt; + xOffCan = plotTop; + yDimCan = plotWid; + yOffCan = plotLft; + + xDimCss = plotHgtCss; + xOffCss = plotTopCss; + yDimCss = plotWidCss; + yOffCss = plotLftCss; + }; + */ + } + + 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 }; + sc.min = sc.max = null; + } + } + + // self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone; + const _tzDate = opts.tzDate || ((ts) => new Date(round(ts / ms))); + const _fmtDate = opts.fmtDate || fmtDate; + + const _timeAxisSplits = + ms == 1 ? timeAxisSplitsMs(_tzDate) : timeAxisSplitsS(_tzDate); + const _timeAxisVals = timeAxisVals( + _tzDate, + timeAxisStamps(ms == 1 ? _timeAxisStampsMs : _timeAxisStampsS, _fmtDate), + ); + const _timeSeriesVal = timeSeriesVal( + _tzDate, + timeSeriesStamp(_timeSeriesStamp, _fmtDate), + ); + + const activeIdxs = []; + + const legend = (self.legend = assign({}, legendOpts, opts.legend)); + const cursor = (self.cursor = assign( + {}, + cursorOpts, + { drag: { y: mode == 2 } }, + opts.cursor, + )); + const showLegend = legend.show; + const showCursor = cursor.show; + const markers = legend.markers; + + { + legend.idxs = activeIdxs; + + markers.width = fnOrSelf(markers.width); + markers.dash = fnOrSelf(markers.dash); + markers.stroke = fnOrSelf(markers.stroke); + markers.fill = fnOrSelf(markers.fill); + } + + let legendTable; + let legendHead; + let legendBody; + let legendRows = []; + let legendCells = []; + let legendCols; + let multiValLegend = false; + let NULL_LEGEND_VALUES = {}; + + if (legend.live) { + const getMultiVals = series[1] ? series[1].values : null; + multiValLegend = getMultiVals != null; + legendCols = multiValLegend ? getMultiVals(self, 1, 0) : { _: 0 }; + + for (let k in legendCols) NULL_LEGEND_VALUES[k] = LEGEND_DISP; + } + + if (showLegend) { + legendTable = placeTag("table", LEGEND, root); + legendBody = placeTag("tbody", null, legendTable); + + // allows legend to be moved out of root + legend.mount(self, legendTable); + + if (multiValLegend) { + legendHead = placeTag("thead", null, legendTable, legendBody); + + let head = placeTag("tr", null, legendHead); + placeTag("th", null, head); + + for (var key in legendCols) + placeTag("th", LEGEND_LABEL, head).textContent = key; + } else { + addClass(legendTable, LEGEND_INLINE); + legend.live && addClass(legendTable, LEGEND_LIVE); + } + } + + const son = { show: true }; + const soff = { show: false }; + + function initLegendRow(s, i) { + if (i == 0 && (multiValLegend || !legend.live || mode == 2)) + return nullNullTuple; + + let cells = []; + + let row = placeTag( + "tr", + LEGEND_SERIES, + legendBody, + legendBody.childNodes[i], + ); + + addClass(row, s.class); + + if (!s.show) addClass(row, OFF); + + let label = placeTag("th", null, row); + + if (markers.show) { + let indic = placeDiv(LEGEND_MARKER, label); + + if (i > 0) { + let width = markers.width(self, i); + + if (width) + indic.style.border = + width + + "px " + + markers.dash(self, i) + + " " + + markers.stroke(self, i); + + indic.style.background = markers.fill(self, i); + } + } + + let text = placeDiv(LEGEND_LABEL, label); + + if (s.label instanceof HTMLElement) text.appendChild(s.label); + else text.textContent = s.label; + + if (i > 0) { + if (!markers.show) + text.style.color = + s.width > 0 ? markers.stroke(self, i) : markers.fill(self, i); + + onMouse( + "click", + label, + (e) => { + if (cursor._lock) return; + + setCursorEvent(e); + + let seriesIdx = series.indexOf(s); + + if ((e.ctrlKey || e.metaKey) != legend.isolate) { + // if any other series is shown, isolate this one. else show all + let isolate = series.some( + (s, i) => i > 0 && i != seriesIdx && s.show, + ); + + series.forEach((s, i) => { + i > 0 && + setSeries( + i, + isolate ? (i == seriesIdx ? son : soff) : son, + true, + syncOpts.setSeries, + ); + }); + } else + setSeries(seriesIdx, { show: !s.show }, true, syncOpts.setSeries); + }, + false, + ); + + if (cursorFocus) { + onMouse( + mouseenter, + label, + (e) => { + if (cursor._lock) return; + + setCursorEvent(e); + + setSeries( + series.indexOf(s), + FOCUS_TRUE, + true, + syncOpts.setSeries, + ); + }, + false, + ); + } + } + + for (var key in legendCols) { + let v = placeTag("td", LEGEND_VALUE, row); + v.textContent = "--"; + cells.push(v); + } + + return [row, cells]; + } + + const mouseListeners = new Map(); + + function onMouse(ev, targ, fn, onlyTarg = true) { + const targListeners = mouseListeners.get(targ) || {}; + const listener = cursor.bind[ev](self, targ, fn, onlyTarg); + + if (listener) { + on(ev, targ, (targListeners[ev] = listener)); + mouseListeners.set(targ, targListeners); + } + } + + function offMouse(ev, targ, fn) { + const targListeners = mouseListeners.get(targ) || {}; + + for (let k in targListeners) { + if (ev == null || k == ev) { + off(k, targ, targListeners[k]); + delete targListeners[k]; + } + } + + if (ev == null) mouseListeners.delete(targ); + } + + let fullWidCss = 0; + let fullHgtCss = 0; + + let plotWidCss = 0; + let plotHgtCss = 0; + + // plot margins to account for axes + let plotLftCss = 0; + let plotTopCss = 0; + + // previous values for diffing + let _plotLftCss = plotLftCss; + let _plotTopCss = plotTopCss; + let _plotWidCss = plotWidCss; + let _plotHgtCss = plotHgtCss; + + 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 shouldSetSelect = false; + let shouldSetLegend = false; + + function _setSize(width, height, force) { + if (force || width != self.width || height != self.height) + calcSize(width, height); + + resetYSeries(false); + + shouldConvergeSize = true; + shouldSetSize = 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); + + // updOriDims(); + } + + // ensures size calc convergence + const CYCLE_LIMIT = 3; + + function convergeSize() { + let converged = false; + + let cycleNum = 0; + + while (!converged) { + cycleNum++; + + let axesConverged = axesCalc(cycleNum); + let paddingConverged = paddingCalc(cycleNum); + + converged = + cycleNum == CYCLE_LIMIT || (axesConverged && paddingConverged); + + 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.label != null ? axis.labelSize : 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; + } + } + } + }); + + sidesWithAxes[0] = hasTopAxis; + sidesWithAxes[1] = hasRgtAxis; + sidesWithAxes[2] = hasBtmAxis; + sidesWithAxes[3] = hasLftAxis; + + // hz padding + plotWidCss -= _padding[1] + _padding[3]; + plotLftCss += _padding[3]; + + // vt padding + plotHgtCss -= _padding[2] + _padding[0]; + plotTopCss += _padding[0]; + } + + 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); + } + }); + } + + if (cursor.dataIdx == null) { + let hov = cursor.hover; + + let skip = (hov.skip = new Set(hov.skip ?? [])); + skip.add(void 0); // alignment artifacts + let prox = (hov.prox = fnOrSelf(hov.prox)); + let bias = (hov.bias ??= 0); + + // TODO: only scan between in-view idxs (i0, i1) + cursor.dataIdx = (self, seriesIdx, cursorIdx, valAtPosX) => { + if (seriesIdx == 0) return cursorIdx; + + let idx2 = cursorIdx; + + let _prox = prox(self, seriesIdx, cursorIdx, valAtPosX) ?? inf; + let withProx = _prox >= 0 && _prox < inf; + let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss; + let cursorLft = cursor.left; + + let xValues = data[0]; + let yValues = data[seriesIdx]; + + if (skip.has(yValues[cursorIdx])) { + idx2 = null; + + let nonNullLft = null, + nonNullRgt = null, + j; + + if (bias == 0 || bias == -1) { + j = cursorIdx; + while (nonNullLft == null && j-- > 0) { + if (!skip.has(yValues[j])) nonNullLft = j; + } + } + + if (bias == 0 || bias == 1) { + j = cursorIdx; + while (nonNullRgt == null && j++ < yValues.length) { + if (!skip.has(yValues[j])) nonNullRgt = j; + } + } + + if (nonNullLft != null || nonNullRgt != null) { + if (withProx) { + let lftPos = + nonNullLft == null + ? -Infinity + : valToPosX(xValues[nonNullLft], scaleX, xDim, 0); + let rgtPos = + nonNullRgt == null + ? Infinity + : valToPosX(xValues[nonNullRgt], scaleX, xDim, 0); + + let lftDelta = cursorLft - lftPos; + let rgtDelta = rgtPos - cursorLft; + + if (lftDelta <= rgtDelta) { + if (lftDelta <= _prox) idx2 = nonNullLft; + } else { + if (rgtDelta <= _prox) idx2 = nonNullRgt; + } + } else { + idx2 = + nonNullRgt == null + ? nonNullLft + : nonNullLft == null + ? nonNullRgt + : cursorIdx - nonNullLft <= nonNullRgt - cursorIdx + ? nonNullLft + : nonNullRgt; + } + } + } else if (withProx) { + let dist = abs( + cursorLft - valToPosX(xValues[cursorIdx], scaleX, xDim, 0), + ); + + if (dist > _prox) idx2 = null; + } + + return idx2; + }; + } + + const setCursorEvent = (e) => { + cursor.event = e; + }; + + cursor.idxs = activeIdxs; + + cursor._lock = false; + + let points = cursor.points; + + points.show = fnOrSelf(points.show); + points.size = fnOrSelf(points.size); + points.stroke = fnOrSelf(points.stroke); + points.width = fnOrSelf(points.width); + points.fill = fnOrSelf(points.fill); + + const focus = (self.focus = assign( + {}, + opts.focus || { alpha: 0.3 }, + cursor.focus, + )); + + const cursorFocus = focus.prox >= 0; + const cursorOnePt = cursorFocus && points.one; + + // series-intersection markers + let cursorPts = []; + // position caches in CSS pixels + let cursorPtsLft = []; + let cursorPtsTop = []; + + function initCursorPt(s, si) { + let pt = points.show(self, si); + + if (pt instanceof HTMLElement) { + addClass(pt, CURSOR_PT); + addClass(pt, s.class); + elTrans(pt, -10, -10, plotWidCss, plotHgtCss); + over.insertBefore(pt, cursorPts[si]); + + return pt; + } + } + + function initSeries(s, i) { + if (mode == 1 || i > 0) { + let isTime = mode == 1 && 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 (cursorOnePt || i > 0) { + s.width = s.width == null ? 1 : s.width; + s.paths = s.paths || linearPath || retNull; + s.fillTo = fnOrSelf(s.fillTo || seriesFillTo); + s.pxAlign = +ifNull(s.pxAlign, pxAlign); + s.pxRound = pxRoundGen(s.pxAlign); + + s.stroke = fnOrSelf(s.stroke || null); + s.fill = fnOrSelf(s.fill || null); + s._stroke = s._fill = s._paths = s._focus = null; + + let _ptDia = ptDia(max(1, s.width), 1); + let points = (s.points = assign( + {}, + { + size: _ptDia, + width: max(1, _ptDia * 0.2), + stroke: s.stroke, + space: _ptDia * 2, + paths: pointsPath, + _stroke: null, + _fill: null, + }, + s.points, + )); + points.show = fnOrSelf(points.show); + points.filter = fnOrSelf(points.filter); + points.fill = fnOrSelf(points.fill); + points.stroke = fnOrSelf(points.stroke); + points.paths = fnOrSelf(points.paths); + points.pxAlign = s.pxAlign; + } + + if (showLegend) { + let rowCells = initLegendRow(s, i); + legendRows.splice(i, 0, rowCells[0]); + legendCells.splice(i, 0, rowCells[1]); + legend.values.push(null); // NULL_LEGEND_VALS not yet avil here :( + } + + if (showCursor) { + activeIdxs.splice(i, 0, null); + + let pt = null; + + if (cursorOnePt) { + if (i == 0) pt = initCursorPt(s, i); + } else if (i > 0) pt = initCursorPt(s, i); + + cursorPts.splice(i, 0, pt); + cursorPtsLft.splice(i, 0, 0); + cursorPtsTop.splice(i, 0, 0); + } + + fire("addSeries", i); + } + + function addSeries(opts, si) { + si = si == null ? series.length : si; + + opts = + mode == 1 + ? setDefault(opts, si, xSeriesOpts, ySeriesOpts) + : setDefault(opts, si, {}, xySeriesOpts); + + series.splice(si, 0, opts); + initSeries(series[si], si); + } + + self.addSeries = addSeries; + + function delSeries(i) { + series.splice(i, 1); + + if (showLegend) { + legend.values.splice(i, 1); + + legendCells.splice(i, 1); + let tr = legendRows.splice(i, 1)[0]; + offMouse(null, tr.firstChild); + tr.remove(); + } + + if (showCursor) { + activeIdxs.splice(i, 1); + cursorPts.splice(i, 1)[0].remove(); + cursorPtsLft.splice(i, 1); + cursorPtsTop.splice(i, 1); + } + + // TODO: de-init no-longer-needed scales? + + fire("delSeries", i); + } + + self.delSeries = delSeries; + + const sidesWithAxes = [false, false, false, false]; + + 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); + + if (isArr(axis.incrs)) { + axis.incrs.forEach((incr) => { + !fixedDec.has(incr) && fixedDec.set(incr, guessDec(incr)); + }); + } + + axis.incrs = fnOrSelf( + axis.incrs || + (sc.distr == 2 + ? wholeIncrs + : isTime + ? ms == 1 + ? timeIncrsMs + : timeIncrsS + : numIncrs), + ); + axis.splits = fnOrSelf( + axis.splits || + (isTime && sc.distr == 1 + ? _timeAxisSplits + : sc.distr == 3 + ? logAxisSplits + : sc.distr == 4 + ? asinhAxisSplits + : numAxisSplits), + ); + + axis.stroke = fnOrSelf(axis.stroke); + axis.grid.stroke = fnOrSelf(axis.grid.stroke); + axis.ticks.stroke = fnOrSelf(axis.ticks.stroke); + axis.border.stroke = fnOrSelf(axis.border.stroke); + + let av = axis.values; + + axis.values = + // static array of tick values + isArr(av) && !isArr(av[0]) + ? fnOrSelf(av) + : // temporal + isTime + ? // config array of fmtDate string tpls + isArr(av) + ? timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) + : // fmtDate string tpl + isStr(av) + ? timeAxisVal(_tzDate, av) + : av || _timeAxisVals + : av || numAxisVals; + + axis.filter = fnOrSelf( + axis.filter || + (sc.distr >= 3 && sc.log == 10 + ? log10AxisValsFilt + : sc.distr == 3 && sc.log == 2 + ? log2AxisValsFilt + : retArg1), + ); + + axis.font = pxRatioFont(axis.font); + axis.labelFont = pxRatioFont(axis.labelFont); + + axis._size = axis.size(self, null, i, 0); + + axis._space = + axis._rotate = + axis._incrs = + axis._found = // foundIncrSpace + axis._splits = + axis._values = + null; + + if (axis._size > 0) { + sidesWithAxes[i] = true; + axis._el = placeDiv(AXIS, wrap); + } + + // debug + // axis._el.style.background = "#" + Math.floor(Math.random()*16777215).toString(16) + '80'; + } + } + + function autoPadSide(self, side, sidesWithAxes, cycleNum) { + let [hasTopAxis, hasRgtAxis, hasBtmAxis, hasLftAxis] = sidesWithAxes; + + let ori = side % 2; + let size = 0; + + if (ori == 0 && (hasLftAxis || hasRgtAxis)) + size = + (side == 0 && !hasTopAxis) || (side == 2 && !hasBtmAxis) + ? round(xAxisOpts.size / 3) + : 0; + if (ori == 1 && (hasTopAxis || hasBtmAxis)) + size = + (side == 1 && !hasRgtAxis) || (side == 3 && !hasLftAxis) + ? round(yAxisOpts.size / 2) + : 0; + + return size; + } + + const padding = (self.padding = ( + opts.padding || [autoPadSide, autoPadSide, autoPadSide, autoPadSide] + ).map((p) => fnOrSelf(ifNull(p, autoPadSide)))); + const _padding = (self._padding = padding.map((p, i) => + p(self, i, sidesWithAxes, 0), + )); + + let dataLen; + + // rendered data window + let i0 = null; + let i1 = null; + const idxs = mode == 1 ? series[0].idxs : null; + + let data0 = null; + + let viaAutoScaleX = false; + + function setData(_data, _resetScales) { + data = _data == null ? [] : _data; + + self.data = self._data = data; + + if (mode == 2) { + dataLen = 0; + for (let i = 1; i < series.length; i++) dataLen += data[i][0].length; + } else { + if (data.length == 0) self.data = self._data = data = [[]]; + + data0 = data[0]; + dataLen = data0.length; + + let scaleData = data; + + if (xScaleDistr == 2) { + scaleData = data.slice(); + + let _data0 = (scaleData[0] = Array(dataLen)); + for (let i = 0; i < dataLen; i++) _data0[i] = i; + } + + self._data = data = scaleData; + } + + resetYSeries(true); + + fire("setData"); + + // forces x axis tick values to re-generate when neither x scale nor y scale changes + // in ordinal mode, scale range is by index, so will not change if new data has same length, but tick values are from data + if (xScaleDistr == 2) { + shouldConvergeSize = true; + + /* or somewhat cheaper, and uglier: + if (ready) { + // logic extracted from axesCalc() + let i = 0; + let axis = axes[i]; + let _splits = axis._splits.map(i => data0[i]); + let [_incr, _space] = axis._found; + let incr = data0[_splits[1]] - data0[_splits[0]]; + axis._values = axis.values(self, axis.filter(self, _splits, i, _space, incr), i, _space, incr); + } + */ + } + + if (_resetScales !== false) { + let xsc = scaleX; + + if (xsc.auto(self, viaAutoScaleX)) autoScaleX(); + else _setScale(xScaleKey, xsc.min, xsc.max); + + shouldSetCursor = shouldSetCursor || cursor.left >= 0; + shouldSetLegend = true; + commit(); + } + } + + self.setData = setData; + + function autoScaleX() { + viaAutoScaleX = true; + + let _min, _max; + + if (mode == 1) { + 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 (_min == _max) { + if (xScaleDistr == 3) + [_min, _max] = rangeLog(_min, _min, scaleX.log, false); + else if (xScaleDistr == 4) + [_min, _max] = rangeAsinh(_min, _min, scaleX.log, false); + else if (scaleX.time) _max = _min + round(86400 / ms); + else [_min, _max] = rangeNum(_min, _max, rangePad, true); + } + } else { + i0 = idxs[0] = _min = null; + i1 = idxs[1] = _max = null; + } + } + + _setScale(xScaleKey, _min, _max); + } + + let ctxStroke, + ctxFill, + ctxWidth, + ctxDash, + ctxJoin, + ctxCap, + ctxFont, + ctxAlign, + ctxBaseline; + let ctxAlpha; + + function setCtxStyle(stroke, width, dash, cap, fill, join) { + stroke ??= transparent; + dash ??= EMPTY_ARR; + cap ??= "butt"; // (‿|‿) + fill ??= transparent; + join ??= "round"; + + if (stroke != ctxStroke) ctx.strokeStyle = ctxStroke = stroke; + if (fill != ctxFill) ctx.fillStyle = ctxFill = fill; + if (width != ctxWidth) ctx.lineWidth = ctxWidth = width; + if (join != ctxJoin) ctx.lineJoin = ctxJoin = join; + if (cap != ctxCap) ctx.lineCap = ctxCap = cap; + if (dash != ctxDash) ctx.setLineDash((ctxDash = dash)); + } + + function setFontStyle(font, fill, align, baseline) { + if (fill != ctxFill) ctx.fillStyle = ctxFill = fill; + if (font != ctxFont) ctx.font = ctxFont = font; + if (align != ctxAlign) ctx.textAlign = ctxAlign = align; + if (baseline != ctxBaseline) ctx.textBaseline = ctxBaseline = baseline; + } + + function accScale(wsc, psc, facet, data, sorted = 0) { + if ( + data.length > 0 && + wsc.auto(self, viaAutoScaleX) && + (psc == null || psc.min == null) + ) { + let _i0 = ifNull(i0, 0); + let _i1 = ifNull(i1, data.length - 1); + + // only run getMinMax() for invalidated series data, else reuse + let minMax = + facet.min == null + ? getMinMax(data, _i0, _i1, sorted, wsc.distr == 3) + : [facet.min, facet.max]; + + // initial min/max + wsc.min = min(wsc.min, (facet.min = minMax[0])); + wsc.max = max(wsc.max, (facet.max = minMax[1])); + } + } + + const AUTOSCALE = { min: null, max: null }; + + function setScales() { + // log("setScales()", arguments); + + // implicitly add auto scales, and unranged scales + for (let k in scales) { + let sc = scales[k]; + + if ( + pendScales[k] == null && + // scales that have never been set (on init) + (sc.min == null || + // or auto scales when the x scale was explicitly set + (pendScales[xScaleKey] != null && sc.auto(self, viaAutoScaleX))) + ) { + pendScales[k] = AUTOSCALE; + } + } + + // implicitly add dependent scales + for (let k in scales) { + let sc = scales[k]; + + if ( + pendScales[k] == null && + sc.from != null && + pendScales[sc.from] != null + ) + pendScales[k] = AUTOSCALE; + } + + // explicitly setting the x-scale invalidates everything (acts as redraw) + if (pendScales[xScaleKey] != null) resetYSeries(true); // TODO: only reset series on auto scales? + + let wipScales = {}; + + for (let k in pendScales) { + let psc = pendScales[k]; + + if (psc != null) { + let wsc = (wipScales[k] = copy(scales[k], fastIsObj)); + + if (psc.min != null) assign(wsc, psc); + else if (k != xScaleKey || mode == 2) { + 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) => { + if (mode == 1) { + let k = s.scale; + let psc = pendScales[k]; + + if (psc == null) return; + + let wsc = wipScales[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]); + + // don't try to contract same or adjacent idxs + if (i1 - i0 > 1) { + // 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) + accScale(wsc, psc, s, data[i], s.sorted); + + s.idxs[0] = i0; + s.idxs[1] = i1; + } else { + if (i > 0) { + if (s.show && s.auto) { + // TODO: only handles, assumes and requires facets[0] / 'x' scale, and facets[1] / 'y' scale + let [xFacet, yFacet] = s.facets; + let xScaleKey = xFacet.scale; + let yScaleKey = yFacet.scale; + let [xData, yData] = data[i]; + + let wscx = wipScales[xScaleKey]; + let wscy = wipScales[yScaleKey]; + + // null can happen when only x is zoomed, but y has static range and doesnt get auto-added to pending + wscx != null && + accScale( + wscx, + pendScales[xScaleKey], + xFacet, + xData, + xFacet.sorted, + ); + wscy != null && + accScale( + wscy, + pendScales[yScaleKey], + yFacet, + yData, + yFacet.sorted, + ); + + // temp + s.min = yFacet.min; + s.max = yFacet.max; + } + } + } + }); + + // 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]; + + if (base.min == null) wsc.min = wsc.max = null; + else { + 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; + + let distr = sc.distr; + + sc._min = + distr == 3 + ? log10(sc.min) + : distr == 4 + ? asinh(sc.min, sc.asinh) + : distr == 100 + ? sc.fwd(sc.min) + : sc.min; + sc._max = + distr == 3 + ? log10(sc.max) + : distr == 4 + ? asinh(sc.max, sc.asinh) + : distr == 100 + ? sc.fwd(sc.max) + : sc.max; + + changed[k] = anyChanged = true; + } + } + + if (anyChanged) { + // invalidate paths of all series on changed scales + series.forEach((s, i) => { + if (mode == 2) { + if (i > 0 && changed.y) s._paths = null; + } else { + if (changed[s.scale]) s._paths = null; + } + }); + + for (let k in changed) { + shouldConvergeSize = true; + fire("setScale", k); + } + + if (showCursor && cursor.left >= 0) + shouldSetCursor = shouldSetLegend = true; + } + + for (let k in pendScales) pendScales[k] = null; + } + + // 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]; + } + + function drawSeries() { + if (dataLen > 0) { + let shouldAlpha = + series.some((s) => s._focus) && ctxAlpha != focus.alpha; + + if (shouldAlpha) ctx.globalAlpha = ctxAlpha = focus.alpha; + + series.forEach((s, i) => { + if (i > 0 && s.show) { + cacheStrokeFill(i, false); + cacheStrokeFill(i, true); + + if (s._paths == null) { + let _ctxAlpha = ctxAlpha; + + if (ctxAlpha != s.alpha) ctx.globalAlpha = ctxAlpha = s.alpha; + + let _idxs = + mode == 2 ? [0, data[i][0].length - 1] : getOuterIdxs(data[i]); + s._paths = s.paths(self, i, _idxs[0], _idxs[1]); + + if (ctxAlpha != _ctxAlpha) ctx.globalAlpha = ctxAlpha = _ctxAlpha; + } + } + }); + + series.forEach((s, i) => { + if (i > 0 && s.show) { + let _ctxAlpha = ctxAlpha; + + if (ctxAlpha != s.alpha) ctx.globalAlpha = ctxAlpha = s.alpha; + + s._paths != null && drawPath(i, false); + + { + let _gaps = s._paths != null ? s._paths.gaps : null; + + let show = s.points.show(self, i, i0, i1, _gaps); + let idxs = s.points.filter(self, i, show, _gaps); + + if (show || idxs) { + s.points._paths = s.points.paths(self, i, i0, i1, idxs); + drawPath(i, true); + } + } + + if (ctxAlpha != _ctxAlpha) ctx.globalAlpha = ctxAlpha = _ctxAlpha; + + fire("drawSeries", i); + } + }); + + if (shouldAlpha) ctx.globalAlpha = ctxAlpha = 1; + } + } + + function cacheStrokeFill(si, _points) { + let s = _points ? series[si].points : series[si]; + + s._stroke = s.stroke(self, si); + s._fill = s.fill(self, si); + } + + function drawPath(si, _points) { + let s = _points ? series[si].points : series[si]; + + let { + stroke, + fill, + clip: gapsClip, + flags, + + _stroke: strokeStyle = s._stroke, + _fill: fillStyle = s._fill, + _width: width = s.width, + } = s._paths; + + width = roundDec(width * pxRatio, 3); + + let boundsClip = null; + let offset = (width % 2) / 2; + + if (_points && fillStyle == null) + fillStyle = width > 0 ? "#fff" : strokeStyle; + + let _pxAlign = s.pxAlign == 1 && offset > 0; + + _pxAlign && ctx.translate(offset, offset); + + if (!_points) { + let lft = plotLft - width / 2, + top = plotTop - width / 2, + wid = plotWid + width, + hgt = plotHgt + width; + + boundsClip = new Path2D(); + boundsClip.rect(lft, top, wid, hgt); + } + + // the points pathbuilder's gapsClip is its boundsClip, since points dont need gaps clipping, and bounds depend on point size + if (_points) + strokeFill( + strokeStyle, + width, + s.dash, + s.cap, + fillStyle, + stroke, + fill, + flags, + gapsClip, + ); + else + fillStroke( + si, + strokeStyle, + width, + s.dash, + s.cap, + fillStyle, + stroke, + fill, + flags, + boundsClip, + gapsClip, + ); + + _pxAlign && ctx.translate(-offset, -offset); + } + + function fillStroke( + si, + strokeStyle, + lineWidth, + lineDash, + lineCap, + fillStyle, + strokePath, + fillPath, + flags, + boundsClip, + gapsClip, + ) { + let didStrokeFill = false; + + // for all bands where this series is the top edge, create upwards clips using the bottom edges + // and apply clips + fill with band fill or dfltFill + flags != 0 && + bands.forEach((b, bi) => { + // isUpperEdge? + if (b.series[0] == si) { + let lowerEdge = series[b.series[1]]; + let lowerData = data[b.series[1]]; + + let bandClip = (lowerEdge._paths || EMPTY_OBJ).band; + + if (isArr(bandClip)) + bandClip = b.dir == 1 ? bandClip[0] : bandClip[1]; + + let gapsClip2; + + let _fillStyle = null; + + // hasLowerEdge? + if (lowerEdge.show && bandClip && hasData(lowerData, i0, i1)) { + _fillStyle = b.fill(self, bi) || fillStyle; + gapsClip2 = lowerEdge._paths.clip; + } else bandClip = null; + + strokeFill( + strokeStyle, + lineWidth, + lineDash, + lineCap, + _fillStyle, + strokePath, + fillPath, + flags, + boundsClip, + gapsClip, + gapsClip2, + bandClip, + ); + + didStrokeFill = true; + } + }); + + if (!didStrokeFill) + strokeFill( + strokeStyle, + lineWidth, + lineDash, + lineCap, + fillStyle, + strokePath, + fillPath, + flags, + boundsClip, + gapsClip, + ); + } + + const CLIP_FILL_STROKE = BAND_CLIP_FILL | BAND_CLIP_STROKE; + + function strokeFill( + strokeStyle, + lineWidth, + lineDash, + lineCap, + fillStyle, + strokePath, + fillPath, + flags, + boundsClip, + gapsClip, + gapsClip2, + bandClip, + ) { + setCtxStyle(strokeStyle, lineWidth, lineDash, lineCap, fillStyle); + + if (boundsClip || gapsClip || bandClip) { + ctx.save(); + boundsClip && ctx.clip(boundsClip); + gapsClip && ctx.clip(gapsClip); + } + + if (bandClip) { + if ((flags & CLIP_FILL_STROKE) == CLIP_FILL_STROKE) { + ctx.clip(bandClip); + gapsClip2 && ctx.clip(gapsClip2); + doFill(fillStyle, fillPath); + doStroke(strokeStyle, strokePath, lineWidth); + } else if (flags & BAND_CLIP_STROKE) { + doFill(fillStyle, fillPath); + ctx.clip(bandClip); + doStroke(strokeStyle, strokePath, lineWidth); + } else if (flags & BAND_CLIP_FILL) { + ctx.save(); + ctx.clip(bandClip); + gapsClip2 && ctx.clip(gapsClip2); + doFill(fillStyle, fillPath); + ctx.restore(); + doStroke(strokeStyle, strokePath, lineWidth); + } + } else { + doFill(fillStyle, fillPath); + doStroke(strokeStyle, strokePath, lineWidth); + } + + if (boundsClip || gapsClip || bandClip) ctx.restore(); + } + + function doStroke(strokeStyle, strokePath, lineWidth) { + if (lineWidth > 0) { + if (strokePath instanceof Map) { + strokePath.forEach((strokePath, strokeStyle) => { + ctx.strokeStyle = ctxStroke = strokeStyle; + ctx.stroke(strokePath); + }); + } else strokePath != null && strokeStyle && ctx.stroke(strokePath); + } + } + + function doFill(fillStyle, fillPath) { + if (fillPath instanceof Map) { + fillPath.forEach((fillPath, fillStyle) => { + ctx.fillStyle = ctxFill = fillStyle; + ctx.fill(fillPath); + }); + } else fillPath != null && fillStyle && ctx.fill(fillPath); + } + + 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 = findIncr(min, max, incrs, fullDim, minSpace); + } + + return (axis._found = incrSpace); + } + + function drawOrthoLines( + offs, + filts, + ori, + side, + pos0, + len, + width, + stroke, + dash, + cap, + ) { + let offset = (width % 2) / 2; + + pxAlign == 1 && ctx.translate(offset, offset); + + setCtxStyle(stroke, width, dash, cap, stroke); + + 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; + } + + for (let i = 0; i < offs.length; i++) { + if (filts[i] != null) { + if (ori == 0) x0 = x1 = offs[i]; + else y0 = y1 = offs[i]; + + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); + } + } + + ctx.stroke(); + + pxAlign == 1 && ctx.translate(-offset, -offset); + } + + function axesCalc(cycleNum) { + // 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 = ceil(axis.size(self, values, i, cycleNum)); + + if (oldSize != null && axis._size != oldSize) + // ready && ? + converged = false; + }); + + return converged; + } + + function paddingCalc(cycleNum) { + let converged = true; + + padding.forEach((p, i) => { + let _p = p(self, i, sidesWithAxes, cycleNum); + + if (_p != _padding[i]) converged = false; + + _padding[i] = _p; + }); + + return converged; + } + + function drawAxesGrid() { + for (let i = 0; i < axes.length; i++) { + let axis = axes[i]; + + if (!axis.show || !axis._show) continue; + + let side = axis.side; + let ori = side % 2; + + let x, y; + + let fillStyle = axis.stroke(self, i); + + let shiftDir = side == 0 || side == 3 ? -1 : 1; + + let [_incr, _space] = axis._found; + + // axis label + if (axis.label != null) { + let shiftAmt = axis.labelGap * shiftDir; + let baseLpos = round((axis._lpos + shiftAmt) * pxRatio); + + setFontStyle( + axis.labelFont[0], + fillStyle, + "center", + side == 2 ? TOP : BOTTOM, + ); + + ctx.save(); + + 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; + } + + let _label = isFn(axis.label) + ? axis.label(self, i, _incr, _space) + : axis.label; + + ctx.fillText(_label, x, y); + + ctx.restore(); + } + + if (_space == 0) continue; + + let scale = scales[axis.scale]; + + let plotDim = ori == 0 ? plotWid : plotHgt; + let plotOff = ori == 0 ? plotLft : plotTop; + + 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; + + let ticks = axis.ticks; + let border = axis.border; + let _tickSize = ticks.show ? ticks.size : 0; + let tickSize = round(_tickSize * pxRatio); + let axisGap = round( + (axis.alignTo == 2 ? axis._size - _tickSize - axis.gap : axis.gap) * + pxRatio, + ); + + // rotating of labels only supported on bottom x axis + let angle = (axis._rotate * -PI) / 180; + + let basePos = pxRound(axis._pos * pxRatio); + let shiftAmt = (tickSize + axisGap) * shiftDir; + let finalPos = basePos + shiftAmt; + y = ori == 0 ? finalPos : 0; + x = ori == 1 ? finalPos : 0; + + let font = axis.font[0]; + let textAlign = + axis.align == 1 + ? LEFT + : axis.align == 2 + ? RIGHT + : angle > 0 + ? LEFT + : angle < 0 + ? RIGHT + : ori == 0 + ? "center" + : side == 3 + ? RIGHT + : LEFT; + let textBaseline = + angle || ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM; + + setFontStyle(font, fillStyle, textAlign, textBaseline); + + let lineHeight = axis.font[1] * axis.lineGap; + + let canOffs = _splits.map((val) => + pxRound(getPos(val, scale, plotDim, plotOff)), + ); + + let _values = axis._values; + + for (let i = 0; i < _values.length; i++) { + let val = _values[i]; + + if (val != null) { + if (ori == 0) x = canOffs[i]; + else y = canOffs[i]; + + val = "" + val; + + let _parts = val.indexOf("\n") == -1 ? [val] : val.split(/\n/gm); + + for (let j = 0; j < _parts.length; j++) { + let text = _parts[j]; + + if (angle) { + ctx.save(); + ctx.translate(x, y + j * lineHeight); // can this be replaced with position math? + ctx.rotate(angle); // can this be done once? + ctx.fillText(text, 0, 0); + ctx.restore(); + } else ctx.fillText(text, x, y + j * lineHeight); + } + } + } + + // ticks + if (ticks.show) { + drawOrthoLines( + canOffs, + ticks.filter(self, splits, i, _space, incr), + ori, + side, + basePos, + tickSize, + roundDec(ticks.width * pxRatio, 3), + ticks.stroke(self, i), + ticks.dash, + ticks.cap, + ); + } + + // 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(self, i), + grid.dash, + grid.cap, + ); + } + + if (border.show) { + drawOrthoLines( + [basePos], + [1], + ori == 0 ? 1 : 0, + ori == 0 ? 1 : 2, + ori == 1 ? plotTop : plotLft, + ori == 1 ? plotHgt : plotWid, + roundDec(border.width * pxRatio, 3), + border.stroke(self, i), + border.dash, + border.cap, + ); + } + } + + fire("drawAxes"); + } + + function resetYSeries(minMax) { + // log("resetYSeries()", arguments); + + series.forEach((s, i) => { + if (i > 0) { + s._paths = null; + + if (minMax) { + if (mode == 1) { + s.min = null; + s.max = null; + } else { + s.facets.forEach((f) => { + f.min = null; + f.max = null; + }); + } + } + } + }); + } + + let queuedCommit = false; + let deferHooks = false; + let hooksQueue = []; + + function flushHooks() { + deferHooks = false; + + for (let i = 0; i < hooksQueue.length; i++) fire(...hooksQueue[i]); + + hooksQueue.length = 0; + } + + function commit() { + if (!queuedCommit) { + microTask(_commit); + queuedCommit = true; + } + } + + // manual batching (aka immediate mode), skips microtask queue + function batch(fn, _deferHooks = false) { + queuedCommit = true; + deferHooks = _deferHooks; + + fn(self); + _commit(); + + if (_deferHooks && hooksQueue.length > 0) queueMicrotask(flushHooks); + } + + self.batch = batch; + + 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); + + // NOTE: mutating this during print preview in Chrome forces transparent + // canvas pixels to white, even when followed up with clearRect() below + can.width = round(fullWidCss * pxRatio); + can.height = round(fullHgtCss * pxRatio); + + axes.forEach(({ _el, _show, _size, _pos, side }) => { + if (_el != null) { + if (_show) { + let posOffset = side === 3 || side === 0 ? _size : 0; + let isVt = side % 2 == 1; + + setStylePx(_el, isVt ? "left" : "top", _pos - posOffset); + setStylePx(_el, isVt ? "width" : "height", _size); + setStylePx( + _el, + isVt ? "top" : "left", + isVt ? plotTopCss : plotLftCss, + ); + setStylePx( + _el, + isVt ? "height" : "width", + isVt ? plotHgtCss : plotWidCss, + ); + + remClass(_el, OFF); + } else addClass(_el, OFF); + } + }); + + // invalidate ctx style cache + ctxStroke = + ctxFill = + ctxWidth = + ctxJoin = + ctxCap = + ctxFont = + ctxAlign = + ctxBaseline = + ctxDash = + null; + ctxAlpha = 1; + + syncRect(true); + + if ( + plotLftCss != _plotLftCss || + plotTopCss != _plotTopCss || + plotWidCss != _plotWidCss || + plotHgtCss != _plotHgtCss + ) { + resetYSeries(false); + + let pctWid = plotWidCss / _plotWidCss; + let pctHgt = plotHgtCss / _plotHgtCss; + + if (showCursor && !shouldSetCursor && cursor.left >= 0) { + cursor.left *= pctWid; + cursor.top *= pctHgt; + + vCursor && + elTrans(vCursor, round(cursor.left), 0, plotWidCss, plotHgtCss); + hCursor && + elTrans(hCursor, 0, round(cursor.top), plotWidCss, plotHgtCss); + + for (let i = 0; i < cursorPts.length; i++) { + let pt = cursorPts[i]; + + if (pt != null) { + cursorPtsLft[i] *= pctWid; + cursorPtsTop[i] *= pctHgt; + elTrans( + pt, + ceil(cursorPtsLft[i]), + ceil(cursorPtsTop[i]), + plotWidCss, + plotHgtCss, + ); + } + } + } + + if ( + select.show && + !shouldSetSelect && + select.left >= 0 && + select.width > 0 + ) { + select.left *= pctWid; + select.width *= pctWid; + select.top *= pctHgt; + select.height *= pctHgt; + + for (let prop in _hideProps) + setStylePx(selectDiv, prop, select[prop]); + } + + _plotLftCss = plotLftCss; + _plotTopCss = plotTopCss; + _plotWidCss = plotWidCss; + _plotHgtCss = plotHgtCss; + } + + fire("setSize"); + + shouldSetSize = false; + } + + if (fullWidCss > 0 && fullHgtCss > 0) { + ctx.clearRect(0, 0, can.width, can.height); + fire("drawClear"); + drawOrder.forEach((fn) => fn()); + fire("draw"); + } + + if (select.show && shouldSetSelect) { + setSelect(select); + shouldSetSelect = false; + } + + if (showCursor && shouldSetCursor) { + updateCursor(null, true, false); + shouldSetCursor = false; + } + + if (legend.show && legend.live && shouldSetLegend) { + setLegend(); + shouldSetLegend = false; // redundant currently + } + + if (!ready) { + ready = true; + self.status = 1; + + fire("ready"); + } + + viaAutoScaleX = false; + + queuedCommit = false; + } + + self.redraw = (rebuildPaths, recalcAxes) => { + shouldConvergeSize = recalcAxes || false; + + if (rebuildPaths !== false) _setScale(xScaleKey, scaleX.min, scaleX.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 (opts.min > opts.max) { + let _min = opts.min; + opts.min = opts.max; + opts.max = _min; + } + + 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]); + + if (opts.min == opts.max) opts.max++; + } + } + + // log("setScale()", arguments); + + pendScales[key] = opts; + + shouldSetScales = true; + commit(); + } + } + + self.setScale = setScale; + + // INTERACTION + + let xCursor; + let yCursor; + let vCursor; + let hCursor; + + // 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 (showCursor) { + if (cursor.x) xCursor = placeDiv(CURSOR_X, over); + if (cursor.y) yCursor = placeDiv(CURSOR_Y, over); + + if (scaleX.ori == 0) { + vCursor = xCursor; + hCursor = yCursor; + } else { + vCursor = yCursor; + hCursor = xCursor; + } + + mouseLeft1 = cursor.left; + mouseTop1 = cursor.top; + } + + 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) { + select[prop] = opts[prop]; + + if (prop in _hideProps) setStylePx(selectDiv, prop, opts[prop]); + } + + _fire !== false && fire("setSelect"); + } + } + + self.setSelect = setSelect; + + function toggleDOM(i) { + let s = series[i]; + + if (s.show) showLegend && remClass(legendRows[i], OFF); + else { + showLegend && addClass(legendRows[i], OFF); + + if (showCursor) { + let pt = cursorOnePt ? cursorPts[0] : cursorPts[i]; + pt != null && elTrans(pt, -10, -10, plotWidCss, plotHgtCss); + } + } + } + + function _setScale(key, min, max) { + setScale(key, { min, max }); + } + + function setSeries(i, opts, _fire, _pub) { + // log("setSeries()", arguments); + + if (opts.focus != null) setFocus(i); + + if (opts.show != null) { + series.forEach((s, si) => { + if (si > 0 && (i == si || i == null)) { + s.show = opts.show; + toggleDOM(si); + + if (mode == 2) { + _setScale(s.facets[0].scale, null, null); + _setScale(s.facets[1].scale, null, null); + } else _setScale(s.scale, null, null); + + commit(); + } + }); + } + + _fire !== false && fire("setSeries", i, opts); + + _pub && pubSync("setSeries", self, i, opts); + } + + self.setSeries = setSeries; + + function setBand(bi, opts) { + assign(bands[bi], opts); + } + + function addBand(opts, bi) { + opts.fill = fnOrSelf(opts.fill || null); + opts.dir = ifNull(opts.dir, -1); + bi = bi == null ? bands.length : bi; + bands.splice(bi, 0, opts); + } + + function delBand(bi) { + if (bi == null) bands.length = 0; + else bands.splice(bi, 1); + } + + self.addBand = addBand; + self.setBand = setBand; + self.delBand = delBand; + + function setAlpha(i, value) { + series[i].alpha = value; + + if (showCursor && cursorPts[i] != null) + cursorPts[i].style.opacity = value; + + if (showLegend && legendRows[i]) legendRows[i].style.opacity = value; + } + + // y-distance + let closestDist; + let closestSeries; + let focusedSeries; + const FOCUS_TRUE = { focus: true }; + + function setFocus(i) { + if (i != focusedSeries) { + // log("setFocus()", arguments); + + let allFocused = i == null; + + let _setAlpha = focus.alpha != 1; + + series.forEach((s, i2) => { + if (mode == 1 || i2 > 0) { + let isFocused = allFocused || i2 == 0 || i2 == i; + s._focus = allFocused ? null : isFocused; + _setAlpha && setAlpha(i2, isFocused ? 1 : focus.alpha); + } + }); + + focusedSeries = i; + _setAlpha && commit(); + } + } + + if (showLegend && cursorFocus) { + onMouse(mouseleave, legendTable, (e) => { + if (cursor._lock) return; + + setCursorEvent(e); + + if (focusedSeries != null) + setSeries(null, FOCUS_TRUE, true, syncOpts.setSeries); + }); + } + + function posToVal(pos, scale, can) { + let sc = scales[scale]; + + if (can) pos = pos / pxRatio - (sc.ori == 1 ? plotTopCss : plotLftCss); + + let dim = plotWidCss; + + if (sc.ori == 1) { + dim = plotHgtCss; + pos = dim - pos; + } + + if (sc.dir == -1) pos = dim - pos; + + let _min = sc._min, + _max = sc._max, + pct = pos / dim; + + let sv = _min + (_max - _min) * pct; + + let distr = sc.distr; + + return distr == 3 + ? pow(10, sv) + : distr == 4 + ? sinh(sv, sc.asinh) + : distr == 100 + ? sc.bwd(sv) + : sv; + } + + function closestIdxFromXpos(pos, can) { + let v = posToVal(pos, xScaleKey, can); + return closestIdx(v, data[0], i0, i1); + } + + self.valToIdx = (val) => closestIdx(val, data[0]); + self.posToIdx = closestIdxFromXpos; + self.posToVal = posToVal; + self.valToPos = (val, scale, can) => + scales[scale].ori == 0 + ? getHPos( + val, + scales[scale], + can ? plotWid : plotWidCss, + can ? plotLft : 0, + ) + : getVPos( + val, + scales[scale], + can ? plotHgt : plotHgtCss, + can ? plotTop : 0, + ); + + self.setCursor = (opts, _fire, _pub) => { + mouseLeft1 = opts.left; + mouseTop1 = opts.top; + // assign(cursor, opts); + updateCursor(null, _fire, _pub); + }; + + function setSelH(off, dim) { + setStylePx(selectDiv, LEFT, (select.left = off)); + setStylePx(selectDiv, WIDTH, (select.width = dim)); + } + + function setSelV(off, dim) { + setStylePx(selectDiv, TOP, (select.top = off)); + setStylePx(selectDiv, HEIGHT, (select.height = dim)); + } + + let setSelX = scaleX.ori == 0 ? setSelH : setSelV; + let setSelY = scaleX.ori == 1 ? setSelH : setSelV; + + function syncLegend() { + if (showLegend && legend.live) { + for (let i = mode == 2 ? 1 : 0; i < series.length; i++) { + if (i == 0 && multiValLegend) continue; + + let vals = legend.values[i]; + + let j = 0; + + for (let k in vals) + legendCells[i][j++].firstChild.nodeValue = vals[k]; + } + } + } + + function setLegend(opts, _fire) { + if (opts != null) { + if (opts.idxs) { + opts.idxs.forEach((didx, sidx) => { + activeIdxs[sidx] = didx; + }); + } else if (!isUndef(opts.idx)) activeIdxs.fill(opts.idx); + + legend.idx = activeIdxs[0]; + } + + if (showLegend && legend.live) { + for (let sidx = 0; sidx < series.length; sidx++) { + if (sidx > 0 || (mode == 1 && !multiValLegend)) + setLegendValues(sidx, activeIdxs[sidx]); + } + + syncLegend(); + } + + shouldSetLegend = false; + + _fire !== false && fire("setLegend"); + } + + self.setLegend = setLegend; + + function setLegendValues(sidx, idx) { + let s = series[sidx]; + let src = sidx == 0 && xScaleDistr == 2 ? data0 : data[sidx]; + let val; + + if (multiValLegend) val = s.values(self, sidx, idx) ?? NULL_LEGEND_VALUES; + else { + val = s.value(self, idx == null ? null : src[idx], sidx, idx); + val = val == null ? NULL_LEGEND_VALUES : { _: val }; + } + + legend.values[sidx] = val; + } + + function updateCursor(src, _fire, _pub) { + // ts == null && log("updateCursor()", arguments); + + rawMouseLeft1 = mouseLeft1; + rawMouseTop1 = mouseTop1; + + [mouseLeft1, mouseTop1] = cursor.move(self, mouseLeft1, mouseTop1); + + cursor.left = mouseLeft1; + cursor.top = mouseTop1; + + if (showCursor) { + vCursor && + elTrans(vCursor, round(mouseLeft1), 0, plotWidCss, plotHgtCss); + hCursor && + elTrans(hCursor, 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; // works for mode 1 only + + closestDist = inf; + closestSeries = null; + + // TODO: extract + let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss; + let yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss; + + // if cursor hidden, hide points & clear legend vals + if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) { + idx = cursor.idx = null; + + for (let i = 0; i < series.length; i++) { + let pt = cursorPts[i]; + pt != null && elTrans(pt, -10, -10, plotWidCss, plotHgtCss); + } + + if (cursorFocus) + setSeries(null, FOCUS_TRUE, true, src == null && syncOpts.setSeries); + + if (legend.live) { + activeIdxs.fill(idx); + shouldSetLegend = true; + } + } else { + // let pctY = 1 - (y / rect.height); + + let mouseXPos, valAtPosX, xPos; + + if (mode == 1) { + mouseXPos = scaleX.ori == 0 ? mouseLeft1 : mouseTop1; + valAtPosX = posToVal(mouseXPos, xScaleKey); + idx = cursor.idx = closestIdx(valAtPosX, data[0], i0, i1); + xPos = valToPosX(data[0][idx], scaleX, xDim, 0); + } + + // closest pt values + let _ptLft = -10; + let _ptTop = -10; + let _ptWid = 0; + let _ptHgt = 0; + let _centered = true; + let _ptFill = ""; + let _ptStroke = ""; + + for (let i = mode == 2 ? 1 : 0; i < series.length; i++) { + let s = series[i]; + + let idx1 = activeIdxs[i]; + let yVal1 = + idx1 == null ? null : mode == 1 ? data[i][idx1] : data[i][1][idx1]; + + let idx2 = cursor.dataIdx(self, i, idx, valAtPosX); + let yVal2 = + idx2 == null ? null : mode == 1 ? data[i][idx2] : data[i][1][idx2]; + + shouldSetLegend = shouldSetLegend || yVal2 != yVal1 || idx2 != idx1; + + activeIdxs[i] = idx2; + + if (i > 0 && s.show) { + let xPos2 = + idx2 == null + ? -10 + : idx2 == idx + ? xPos + : valToPosX( + mode == 1 ? data[0][idx2] : data[i][0][idx2], + scaleX, + xDim, + 0, + ); + + // this doesnt really work for state timeline, heatmap, status history (where the value maps to color, not y coords) + let yPos = + yVal2 == null + ? -10 + : valToPosY( + yVal2, + mode == 1 ? scales[s.scale] : scales[s.facets[1].scale], + yDim, + 0, + ); + + if (cursorFocus && yVal2 != null) { + let mouseYPos = scaleX.ori == 1 ? mouseLeft1 : mouseTop1; + let dist = abs(focus.dist(self, i, idx2, yPos, mouseYPos)); + + if (dist < closestDist) { + let bias = focus.bias; + + if (bias != 0) { + let mouseYVal = posToVal(mouseYPos, s.scale); + + let seriesYValSign = yVal2 >= 0 ? 1 : -1; + let mouseYValSign = mouseYVal >= 0 ? 1 : -1; + + // with a focus bias, we will never cross zero when prox testing + // it's either closest towards zero, or closest away from zero + if ( + mouseYValSign == seriesYValSign && + (mouseYValSign == 1 + ? bias == 1 + ? yVal2 >= mouseYVal + : yVal2 <= mouseYVal // >= 0 + : bias == 1 + ? yVal2 <= mouseYVal + : yVal2 >= mouseYVal) // < 0 + ) { + closestDist = dist; + closestSeries = i; + } + } else { + closestDist = dist; + closestSeries = i; + } + } + } + + if (shouldSetLegend || cursorOnePt) { + let hPos, vPos; + + if (scaleX.ori == 0) { + hPos = xPos2; + vPos = yPos; + } else { + hPos = yPos; + vPos = xPos2; + } + + let ptWid, + ptHgt, + ptLft, + ptTop, + ptStroke, + ptFill, + centered = true, + getBBox = points.bbox; + + if (getBBox != null) { + centered = false; + + let bbox = getBBox(self, i); + + ptLft = bbox.left; + ptTop = bbox.top; + ptWid = bbox.width; + ptHgt = bbox.height; + } else { + ptLft = hPos; + ptTop = vPos; + ptWid = ptHgt = points.size(self, i); + } + + ptFill = points.fill(self, i); + ptStroke = points.stroke(self, i); + + if (cursorOnePt) { + if (i == closestSeries && closestDist <= focus.prox) { + _ptLft = ptLft; + _ptTop = ptTop; + _ptWid = ptWid; + _ptHgt = ptHgt; + _centered = centered; + _ptFill = ptFill; + _ptStroke = ptStroke; + } + } else { + let pt = cursorPts[i]; + + if (pt != null) { + cursorPtsLft[i] = ptLft; + cursorPtsTop[i] = ptTop; + + elSize(pt, ptWid, ptHgt, centered); + elColor(pt, ptFill, ptStroke); + elTrans(pt, ceil(ptLft), ceil(ptTop), plotWidCss, plotHgtCss); + } + } + } + } + } + + // if only using single hover point (at cursorPts[0]) + // we have trigger styling at last visible series (once closestSeries is settled) + if (cursorOnePt) { + // some of this logic is similar to series focus below, since it matches the behavior by design + + let p = focus.prox; + + let focusChanged = + focusedSeries == null + ? closestDist <= p + : closestDist > p || closestSeries != focusedSeries; + + if (shouldSetLegend || focusChanged) { + let pt = cursorPts[0]; + + if (pt != null) { + cursorPtsLft[0] = _ptLft; + cursorPtsTop[0] = _ptTop; + + elSize(pt, _ptWid, _ptHgt, _centered); + elColor(pt, _ptFill, _ptStroke); + elTrans(pt, ceil(_ptLft), ceil(_ptTop), plotWidCss, plotHgtCss); + } + } + } + } + + // nit: cursor.drag.setSelect is assumed always true + if (select.show && dragging) { + if (src != null) { + let [xKey, yKey] = syncOpts.scales; + let [matchXKeys, matchYKeys] = syncOpts.match; + let [xKeySrc, yKeySrc] = src.cursor.sync.scales; + + // match the dragX/dragY implicitness/explicitness of src + let sdrag = src.cursor.drag; + dragX = sdrag._x; + dragY = sdrag._y; + + if (dragX || dragY) { + let { left, top, width, height } = src.select; + + let sori = src.scales[xKeySrc].ori; + let sPosToVal = src.posToVal; + + let sOff, sDim, sc, a, b; + + let matchingX = xKey != null && matchXKeys(xKey, xKeySrc); + let matchingY = yKey != null && matchYKeys(yKey, yKeySrc); + + if (matchingX && dragX) { + if (sori == 0) { + sOff = left; + sDim = width; + } else { + sOff = top; + sDim = height; + } + + sc = scales[xKey]; + + a = valToPosX(sPosToVal(sOff, xKeySrc), sc, xDim, 0); + b = valToPosX(sPosToVal(sOff + sDim, xKeySrc), sc, xDim, 0); + + setSelX(min(a, b), abs(b - a)); + } else setSelX(0, xDim); + + if (matchingY && dragY) { + if (sori == 1) { + sOff = left; + sDim = width; + } else { + sOff = top; + sDim = height; + } + + sc = scales[yKey]; + + a = valToPosY(sPosToVal(sOff, yKeySrc), sc, yDim, 0); + b = valToPosY(sPosToVal(sOff + sDim, yKeySrc), sc, yDim, 0); + + setSelY(min(a, b), abs(b - a)); + } else setSelY(0, yDim); + } else hideSelect(); + } else { + let rawDX = abs(rawMouseLeft1 - rawMouseLeft0); + let rawDY = abs(rawMouseTop1 - rawMouseTop0); + + if (scaleX.ori == 1) { + let _rawDX = rawDX; + rawDX = rawDY; + rawDY = _rawDX; + } + + 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; + + let p0, p1; + + if (dragX) { + if (scaleX.ori == 0) { + p0 = mouseLeft0; + p1 = mouseLeft1; + } else { + p0 = mouseTop0; + p1 = mouseTop1; + } + + setSelX(min(p0, p1), abs(p1 - p0)); + + if (!dragY) setSelY(0, yDim); + } + + if (dragY) { + if (scaleX.ori == 1) { + p0 = mouseLeft0; + p1 = mouseLeft1; + } else { + p0 = mouseTop0; + p1 = mouseTop1; + } + + setSelY(min(p0, p1), abs(p1 - p0)); + + if (!dragX) setSelX(0, xDim); + } + + // the drag didn't pass the dist requirement + if (!dragX && !dragY) { + setSelX(0, 0); + setSelY(0, 0); + } + } + } + + drag._x = dragX; + drag._y = dragY; + + if (src == null) { + if (_pub) { + if (syncKey != null) { + let [xSyncKey, ySyncKey] = syncOpts.scales; + + syncOpts.values[0] = + xSyncKey != null + ? posToVal(scaleX.ori == 0 ? mouseLeft1 : mouseTop1, xSyncKey) + : null; + syncOpts.values[1] = + ySyncKey != null + ? posToVal(scaleX.ori == 1 ? mouseLeft1 : mouseTop1, ySyncKey) + : null; + } + + pubSync( + mousemove, + self, + mouseLeft1, + mouseTop1, + plotWidCss, + plotHgtCss, + idx, + ); + } + + if (cursorFocus) { + let shouldPub = _pub && syncOpts.setSeries; + let p = focus.prox; + + if (focusedSeries == null) { + if (closestDist <= p) + setSeries(closestSeries, FOCUS_TRUE, true, shouldPub); + } else { + if (closestDist > p) setSeries(null, FOCUS_TRUE, true, shouldPub); + else if (closestSeries != focusedSeries) + setSeries(closestSeries, FOCUS_TRUE, true, shouldPub); + } + } + } + + if (shouldSetLegend) { + legend.idx = idx; + setLegend(); + } + + _fire !== false && fire("setCursor"); + } + + let rect = null; + + Object.defineProperty(self, "rect", { + get() { + if (rect == null) syncRect(false); + + return rect; + }, + }); + + function syncRect(defer = false) { + if (defer) rect = null; + else { + rect = over.getBoundingClientRect(); + fire("syncRect", rect); + } + } + + function mouseMove(e, src, _l, _t, _w, _h, _i) { + if (cursor._lock) return; + + // Chrome on Windows has a bug which triggers a stray mousemove event after an initial mousedown event + // when clicking into a plot as part of re-focusing the browser window. + // we gotta ignore it to avoid triggering a phantom drag / setSelect + // However, on touch-only devices Chrome-based browsers trigger a 0-distance mousemove before mousedown + // so we don't ignore it when mousedown has set the dragging flag + if (dragging && e != null && e.movementX == 0 && e.movementY == 0) return; + + cacheMouse(e, src, _l, _t, _w, _h, _i, false, e != null); + + if (e != null) updateCursor(null, true, true); + else updateCursor(src, true, false); + } + + function cacheMouse(e, src, _l, _t, _w, _h, _i, initial, snap) { + if (rect == null) syncRect(false); + + setCursorEvent(e); + + if (e != null) { + _l = e.clientX - rect.left; + _t = e.clientY - rect.top; + } else { + if (_l < 0 || _t < 0) { + mouseLeft1 = -10; + mouseTop1 = -10; + return; + } + + let [xKey, yKey] = syncOpts.scales; + + let syncOptsSrc = src.cursor.sync; + let [xValSrc, yValSrc] = syncOptsSrc.values; + let [xKeySrc, yKeySrc] = syncOptsSrc.scales; + let [matchXKeys, matchYKeys] = syncOpts.match; + + let rotSrc = src.axes[0].side % 2 == 1; + + let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss, + yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss, + _xDim = rotSrc ? _h : _w, + _yDim = rotSrc ? _w : _h, + _xPos = rotSrc ? _t : _l, + _yPos = rotSrc ? _l : _t; + + if (xKeySrc != null) + _l = matchXKeys(xKey, xKeySrc) + ? getPos(xValSrc, scales[xKey], xDim, 0) + : -10; + else _l = xDim * (_xPos / _xDim); + + if (yKeySrc != null) + _t = matchYKeys(yKey, yKeySrc) + ? getPos(yValSrc, scales[yKey], yDim, 0) + : -10; + else _t = yDim * (_yPos / _yDim); + + if (scaleX.ori == 1) { + let __l = _l; + _l = _t; + _t = __l; + } + } + + if (snap && (src == null || src.cursor.event.type == mousemove)) { + if (_l <= 1 || _l >= plotWidCss - 1) _l = incrRound(_l, plotWidCss); + + if (_t <= 1 || _t >= plotHgtCss - 1) _t = incrRound(_t, plotHgtCss); + } + + if (initial) { + rawMouseLeft0 = _l; + rawMouseTop0 = _t; + + [mouseLeft0, mouseTop0] = cursor.move(self, _l, _t); + } else { + mouseLeft1 = _l; + mouseTop1 = _t; + } + } + + const _hideProps = { + width: 0, + height: 0, + left: 0, + top: 0, + }; + + function hideSelect() { + setSelect(_hideProps, false); + } + + let downSelectLeft; + let downSelectTop; + let downSelectWidth; + let downSelectHeight; + + function mouseDown(e, src, _l, _t, _w, _h, _i) { + dragging = true; + dragX = dragY = drag._x = drag._y = false; + + cacheMouse(e, src, _l, _t, _w, _h, _i, true, false); + + if (e != null) { + onMouse(mouseup, doc, mouseUp, false); + pubSync( + mousedown, + self, + mouseLeft0, + mouseTop0, + plotWidCss, + plotHgtCss, + null, + ); + } + + let { left, top, width, height } = select; + + downSelectLeft = left; + downSelectTop = top; + downSelectWidth = width; + downSelectHeight = height; + + // hideSelect(); + } + + function mouseUp(e, src, _l, _t, _w, _h, _i) { + dragging = drag._x = drag._y = false; + + cacheMouse(e, src, _l, _t, _w, _h, _i, false, true); + + let { left, top, width, height } = select; + + let hasSelect = width > 0 || height > 0; + let chgSelect = + downSelectLeft != left || + downSelectTop != top || + downSelectWidth != width || + downSelectHeight != height; + + hasSelect && chgSelect && setSelect(select); + + if (drag.setScale && hasSelect && chgSelect) { + // if (syncKey != null) { + // dragX = drag.x; + // dragY = drag.y; + // } + + let xOff = left, + xDim = width, + yOff = top, + yDim = height; + + if (scaleX.ori == 1) { + (xOff = top), (xDim = height), (yOff = left), (yDim = width); + } + + if (dragX) { + _setScale( + xScaleKey, + posToVal(xOff, xScaleKey), + posToVal(xOff + xDim, xScaleKey), + ); + } + + if (dragY) { + for (let k in scales) { + let sc = scales[k]; + + if (k != xScaleKey && sc.from == null && sc.min != inf) { + _setScale(k, posToVal(yOff + yDim, k), posToVal(yOff, k)); + } + } + } + + hideSelect(); + } else if (cursor.lock) { + cursor._lock = !cursor._lock; + updateCursor(src, true, e != null); + } + + if (e != null) { + offMouse(mouseup, doc); + pubSync( + mouseup, + self, + mouseLeft1, + mouseTop1, + plotWidCss, + plotHgtCss, + null, + ); + } + } + + function mouseLeave(e, src, _l, _t, _w, _h, _i) { + if (cursor._lock) return; + + setCursorEvent(e); + + let _dragging = dragging; + + if (dragging) { + // handle case when mousemove aren't fired all the way to edges by browser + let snapH = true; + let snapV = true; + let snapProx = 10; + + let dragH, dragV; + + if (scaleX.ori == 0) { + dragH = dragX; + dragV = dragY; + } else { + dragH = dragY; + dragV = dragX; + } + + if (dragH && dragV) { + // maybe omni corner snap + snapH = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx; + snapV = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx; + } + + if (dragH && snapH) + mouseLeft1 = mouseLeft1 < mouseLeft0 ? 0 : plotWidCss; + + if (dragV && snapV) mouseTop1 = mouseTop1 < mouseTop0 ? 0 : plotHgtCss; + + updateCursor(null, true, true); + + dragging = false; + } + + mouseLeft1 = -10; + mouseTop1 = -10; + + activeIdxs.fill(null); + + // passing a non-null timestamp to force sync/mousemove event + updateCursor(null, true, true); + + if (_dragging) dragging = _dragging; + } + + function dblClick(e, src, _l, _t, _w, _h, _i) { + if (cursor._lock) return; + + setCursorEvent(e); + + autoScaleX(); + + hideSelect(); + + if (e != null) + pubSync( + dblclick, + self, + mouseLeft1, + mouseTop1, + plotWidCss, + plotHgtCss, + null, + ); + } + + function syncPxRatio() { + axes.forEach(syncFontSize); + _setSize(self.width, self.height, true); + } + + on(dppxchange, win, syncPxRatio); + + // internal pub/sub + const events = {}; + + events.mousedown = mouseDown; + events.mousemove = mouseMove; + events.mouseup = mouseUp; + events.dblclick = dblClick; + events["setSeries"] = (e, src, idx, opts) => { + let seriesIdxMatcher = syncOpts.match[2]; + idx = seriesIdxMatcher(self, src, idx); + idx != -1 && setSeries(idx, opts, true, false); + }; + + if (showCursor) { + onMouse(mousedown, over, mouseDown); + onMouse(mousemove, over, mouseMove); + onMouse(mouseenter, over, (e) => { + setCursorEvent(e); + syncRect(false); + }); + onMouse(mouseleave, over, mouseLeave); + + onMouse(dblclick, over, dblClick); + + cursorPlots.add(self); + + self.syncRect = syncRect; + } + + // external on/off + const hooks = (self.hooks = opts.hooks || {}); + + function fire(evName, a1, a2) { + if (deferHooks) hooksQueue.push([evName, a1, a2]); + else { + 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 seriesIdxMatcher = (self, src, srcSeriesIdx) => srcSeriesIdx; + + const syncOpts = assign( + { + key: null, + setSeries: false, + filters: { + pub: retTrue, + sub: retTrue, + }, + scales: [xScaleKey, series[1] ? series[1].scale : null], + match: [retEq, retEq, seriesIdxMatcher], + values: [null, null], + }, + cursor.sync, + ); + + if (syncOpts.match.length == 2) syncOpts.match.push(seriesIdxMatcher); + + cursor.sync = syncOpts; + + const syncKey = syncOpts.key; + + const sync = _sync(syncKey); + + function pubSync(type, src, x, y, w, h, i) { + if (syncOpts.filters.pub(type, src, x, y, w, h, i)) + sync.pub(type, src, x, y, w, h, i); + } + + sync.sub(self); + + function pub(type, src, x, y, w, h, i) { + if (syncOpts.filters.sub(type, src, x, y, w, h, i)) + events[type](null, src, x, y, w, h, i); + } + + self.pub = pub; + + function destroy() { + sync.unsub(self); + cursorPlots.delete(self); + mouseListeners.clear(); + off(dppxchange, win, syncPxRatio); + root.remove(); + legendTable?.remove(); // in case mounted outside of root + 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(); + + shouldSetSelect = select.show && (select.width > 0 || select.height > 0); + shouldSetCursor = shouldSetLegend = true; + + _setSize(opts.width, opts.height); + } + + series.forEach(initSeries); + + axes.forEach(initAxis); + + 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.rangeAsinh = rangeAsinh; + uPlot.orient = orient; + uPlot.pxRatio = pxRatio; + + { + uPlot.join = join; + } + + { + uPlot.fmtDate = fmtDate; + uPlot.tzDate = tzDate; + } + + uPlot.sync = _sync; + + { + uPlot.addGap = addGap; + uPlot.clipGaps = clipGaps; + + let paths = (uPlot.paths = { + points, + }); + + paths.linear = linear; + paths.stepped = stepped; + paths.bars = bars; + paths.spline = monotoneCubic; + } + + return uPlot; +})(); diff --git a/webstats/static/uPlot.iife.min.js b/webstats/static/uPlot.iife.min.js new file mode 100644 index 0000000..047f29b --- /dev/null +++ b/webstats/static/uPlot.iife.min.js @@ -0,0 +1,2 @@ +/*! https://github.com/leeoniya/uPlot (v1.6.32) */ +var uPlot=function(){"use strict";const l="u-off",e="u-label",t="width",n="height",i="top",o="bottom",s="left",r="right",u="#000",a=u+"0",f="mousemove",c="mousedown",h="mouseup",d="mouseenter",p="mouseleave",m="dblclick",g="change",x="dppxchange",w="--",_="undefined"!=typeof window,b=_?document:null,v=_?window:null,k=_?navigator:null;let y,M;function S(l,e){if(null!=e){let t=l.classList;!t.contains(e)&&t.add(e)}}function T(l,e){let t=l.classList;t.contains(e)&&t.remove(e)}function E(l,e,t){l.style[e]=t+"px"}function z(l,e,t,n){let i=b.createElement(l);return null!=e&&S(i,e),null!=t&&t.insertBefore(i,n),i}function D(l,e){return z("div",l,e)}const P=new WeakMap;function A(e,t,n,i,o){let s="translate("+t+"px,"+n+"px)";s!=P.get(e)&&(e.style.transform=s,P.set(e,s),0>t||0>n||t>i||n>o?S(e,l):T(e,l))}const W=new WeakMap;function Y(l,e,t){let n=e+t;n!=W.get(l)&&(W.set(l,n),l.style.background=e,l.style.borderColor=t)}const C=new WeakMap;function H(l,e,t,n){let i=e+""+t;i!=C.get(l)&&(C.set(l,i),l.style.height=t+"px",l.style.width=e+"px",l.style.marginLeft=n?-e/2+"px":0,l.style.marginTop=n?-t/2+"px":0)}const F={passive:!0},R={...F,capture:!0};function G(l,e,t,n){e.addEventListener(l,t,n?R:F)}function I(l,e,t){e.removeEventListener(l,t,F)}function L(l,e,t,n){let i;t=t||0;let o=2147483647>=(n=n||e.length-1);for(;n-t>1;)i=o?t+n>>1:sl((t+n)/2),l>e[i]?t=i:n=i;return l-e[t]>e[n]-l?n:t}function O(l){return(e,t,n)=>{let i=-1,o=-1;for(let o=t;n>=o;o++)if(l(e[o])){i=o;break}for(let i=n;i>=t;i--)if(l(e[i])){o=i;break}return[i,o]}}_&&function l(){let e=devicePixelRatio;y!=e&&(y=e,M&&I(g,M,l),M=matchMedia(`(min-resolution: ${y-.001}dppx) and (max-resolution: ${y+.001}dppx)`),G(g,M,l),v.dispatchEvent(new CustomEvent(x)))}();const N=l=>null!=l,j=l=>null!=l&&l>0,U=O(N),V=O(j);function B(l,e,t,n){let i=hl(l),o=hl(e);l==e&&(-1==i?(l*=t,e/=t):(l/=t,e*=t));let s=10==t?dl:pl,r=1==o?ul:sl,u=(1==i?sl:ul)(s(ol(l))),a=r(s(ol(e))),f=cl(t,u),c=cl(t,a);return 10==t&&(0>u&&(f=Al(f,-u)),0>a&&(c=Al(c,-a))),n||2==t?(l=f*i,e=c*o):(l=Pl(l,f),e=Dl(e,c)),[l,e]}function $(l,e,t,n){let i=B(l,e,t,n);return 0==l&&(i[0]=0),0==e&&(i[1]=0),i}const J=.1,q={mode:3,pad:J},K={pad:0,soft:null,mode:0},X={min:K,max:K};function Z(l,e,t,n){return Ol(t)?ll(l,e,t):(K.pad=t,K.soft=n?0:null,K.mode=n?3:0,ll(l,e,X))}function Q(l,e){return null==l?e:l}function ll(l,e,t){let n=t.min,i=t.max,o=Q(n.pad,0),s=Q(i.pad,0),r=Q(n.hard,-gl),u=Q(i.hard,gl),a=Q(n.soft,gl),f=Q(i.soft,-gl),c=Q(n.mode,0),h=Q(i.mode,0),d=e-l,p=dl(d),m=fl(ol(l),ol(e)),g=dl(m),x=ol(g-p);(1e-24>d||x>10)&&(d=0,0!=l&&0!=e||(d=1e-24,2==c&&a!=gl&&(o=0),2==h&&f!=-gl&&(s=0)));let w=d||m||1e3,_=dl(w),b=cl(10,sl(_)),v=Al(Pl(l-w*(0==d?0==l?.1:1:o),b/10),24),k=a>l||1!=c&&(3!=c||v>a)&&(2!=c||a>v)?gl:a,y=fl(r,k>v&&l>=k?k:al(k,v)),M=Al(Dl(e+w*(0==d?0==e?.1:1:s),b/10),24),S=e>f||1!=h&&(3!=h||f>M)&&(2!=h||M>f)?-gl:f,T=al(u,M>S&&S>=e?S:fl(S,M));return y==T&&0==y&&(T=100),[y,T]}const el=new Intl.NumberFormat(_?k.language:"en-US"),tl=l=>el.format(l),nl=Math,il=nl.PI,ol=nl.abs,sl=nl.floor,rl=nl.round,ul=nl.ceil,al=nl.min,fl=nl.max,cl=nl.pow,hl=nl.sign,dl=nl.log10,pl=nl.log2,ml=(l,e=1)=>nl.asinh(l/e),gl=1/0;function xl(l){return 1+(0|dl((l^l>>31)-(l>>31)))}function wl(l,e,t){return al(fl(l,e),t)}function _l(l){return"function"==typeof l}function bl(l){return _l(l)?l:()=>l}const vl=l=>l,kl=(l,e)=>e,yl=()=>null,Ml=()=>!0,Sl=(l,e)=>l==e,Tl=/\.\d*?(?=9{6,}|0{6,})/gm,El=l=>{if(Il(l)||Wl.has(l))return l;const e=""+l,t=e.match(Tl);if(null==t)return l;let n=t[0].length-1;if(-1!=e.indexOf("e-")){let[l,t]=e.split("e");return+`${El(l)}e${t}`}return Al(l,n)};function zl(l,e){return El(Al(El(l/e))*e)}function Dl(l,e){return El(ul(El(l/e))*e)}function Pl(l,e){return El(sl(El(l/e))*e)}function Al(l,e=0){if(Il(l))return l;let t=10**e;return rl(l*t*(1+Number.EPSILON))/t}const Wl=new Map;function Yl(l){return((""+l).split(".")[1]||"").length}function Cl(l,e,t,n){let i=[],o=n.map(Yl);for(let s=e;t>s;s++){let e=ol(s),t=Al(cl(l,s),e);for(let r=0;n.length>r;r++){let u=10==l?+`${n[r]}e${s}`:n[r]*t,a=(0>s?e:0)+(o[r]>s?o[r]:0),f=10==l?u:Al(u,a);i.push(f),Wl.set(f,a)}}return i}const Hl={},Fl=[],Rl=[null,null],Gl=Array.isArray,Il=Number.isInteger;function Ll(l){return"string"==typeof l}function Ol(l){let e=!1;if(null!=l){let t=l.constructor;e=null==t||t==Object}return e}function Nl(l){return null!=l&&"object"==typeof l}const jl=Object.getPrototypeOf(Uint8Array),Ul="__proto__";function Vl(l,e=Ol){let t;if(Gl(l)){let n=l.find((l=>null!=l));if(Gl(n)||e(n)){t=Array(l.length);for(let n=0;l.length>n;n++)t[n]=Vl(l[n],e)}else t=l.slice()}else if(l instanceof jl)t=l.slice();else if(e(l)){t={};for(let n in l)n!=Ul&&(t[n]=Vl(l[n],e))}else t=l;return t}function Bl(l){let e=arguments;for(let t=1;e.length>t;t++){let n=e[t];for(let e in n)e!=Ul&&(Ol(l[e])?Bl(l[e],Vl(n[e])):l[e]=Vl(n[e]))}return l}function $l(l,e,t){for(let n,i=0,o=-1;e.length>i;i++){let s=e[i];if(s>o){for(n=s-1;n>=0&&null==l[n];)l[n--]=null;for(n=s+1;t>n&&null==l[n];)l[o=n++]=null}}}const Jl="undefined"==typeof queueMicrotask?l=>Promise.resolve().then(l):queueMicrotask,ql=["January","February","March","April","May","June","July","August","September","October","November","December"],Kl=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function Xl(l){return l.slice(0,3)}const Zl=Kl.map(Xl),Ql=ql.map(Xl),le={MMMM:ql,MMM:Ql,WWWW:Kl,WWW:Zl};function ee(l){return(10>l?"0":"")+l}const te={YYYY:l=>l.getFullYear(),YY:l=>(l.getFullYear()+"").slice(2),MMMM:(l,e)=>e.MMMM[l.getMonth()],MMM:(l,e)=>e.MMM[l.getMonth()],MM:l=>ee(l.getMonth()+1),M:l=>l.getMonth()+1,DD:l=>ee(l.getDate()),D:l=>l.getDate(),WWWW:(l,e)=>e.WWWW[l.getDay()],WWW:(l,e)=>e.WWW[l.getDay()],HH:l=>ee(l.getHours()),H:l=>l.getHours(),h:l=>{let e=l.getHours();return 0==e?12:e>12?e-12:e},AA:l=>12>l.getHours()?"AM":"PM",aa:l=>12>l.getHours()?"am":"pm",a:l=>12>l.getHours()?"a":"p",mm:l=>ee(l.getMinutes()),m:l=>l.getMinutes(),ss:l=>ee(l.getSeconds()),s:l=>l.getSeconds(),fff:l=>function(l){return(10>l?"00":100>l?"0":"")+l}(l.getMilliseconds())};function ne(l,e){e=e||le;let t,n=[],i=/\{([a-z]+)\}|[^{]+/gi;for(;t=i.exec(l);)n.push("{"==t[0][0]?te[t[1]]:t[0]);return l=>{let t="";for(let i=0;n.length>i;i++)t+="string"==typeof n[i]?n[i]:n[i](l,e);return t}}const ie=(new Intl.DateTimeFormat).resolvedOptions().timeZone,oe=l=>l%1==0,se=[1,2,2.5,5],re=Cl(10,-32,0,se),ue=Cl(10,0,32,se),ae=ue.filter(oe),fe=re.concat(ue),ce="{YYYY}",he="\n"+ce,de="{M}/{D}",pe="\n"+de,me=pe+"/{YY}",ge="{aa}",xe="{h}:{mm}"+ge,we="\n"+xe,_e=":{ss}",be=null;function ve(l){let e=1e3*l,t=60*e,n=60*t,i=24*n,o=30*i,s=365*i;return[(1==l?Cl(10,0,3,se).filter(oe):Cl(10,-3,0,se)).concat([e,5*e,10*e,15*e,30*e,t,5*t,10*t,15*t,30*t,n,2*n,3*n,4*n,6*n,8*n,12*n,i,2*i,3*i,4*i,5*i,6*i,7*i,8*i,9*i,10*i,15*i,o,2*o,3*o,4*o,6*o,s,2*s,5*s,10*s,25*s,50*s,100*s]),[[s,ce,be,be,be,be,be,be,1],[28*i,"{MMM}",he,be,be,be,be,be,1],[i,de,he,be,be,be,be,be,1],[n,"{h}"+ge,me,be,pe,be,be,be,1],[t,xe,me,be,pe,be,be,be,1],[e,_e,me+" "+xe,be,pe+" "+xe,be,we,be,1],[l,_e+".{fff}",me+" "+xe,be,pe+" "+xe,be,we,be,1]],function(e){return(r,u,a,f,c,h)=>{let d=[],p=c>=s,m=c>=o&&s>c,g=e(a),x=Al(g*l,3),w=Pe(g.getFullYear(),p?0:g.getMonth(),m||p?1:g.getDate()),_=Al(w*l,3);if(m||p){let t=m?c/o:0,n=p?c/s:0,i=x==_?x:Al(Pe(w.getFullYear()+n,w.getMonth()+t,1)*l,3),r=new Date(rl(i/l)),u=r.getFullYear(),a=r.getMonth();for(let o=0;f>=i;o++){let s=Pe(u+n*o,a+t*o,1),r=s-e(Al(s*l,3));i=Al((+s+r)*l,3),i>f||d.push(i)}}else{let o=i>c?c:i,s=_+(sl(a)-sl(x))+Dl(x-_,o);d.push(s);let p=e(s),m=p.getHours()+p.getMinutes()/t+p.getSeconds()/n,g=c/n,w=h/r.axes[u]._space;for(;s=Al(s+c,1==l?0:3),f>=s;)if(g>1){let l=sl(Al(m+g,6))%24,t=e(s).getHours()-l;t>1&&(t=-1),s-=t*n,m=(m+g)%24,.7>Al((s-d[d.length-1])/c,3)*w||d.push(s)}else d.push(s)}return d}}]}const[ke,ye,Me]=ve(1),[Se,Te,Ee]=ve(.001);function ze(l,e){return l.map((l=>l.map(((t,n)=>0==n||8==n||null==t?t:e(1==n||0==l[8]?t:l[1]+t)))))}function De(l,e){return(t,n,i,o,s)=>{let r,u,a,f,c,h,d=e.find((l=>s>=l[0]))||e[e.length-1];return n.map((e=>{let t=l(e),n=t.getFullYear(),i=t.getMonth(),o=t.getDate(),s=t.getHours(),p=t.getMinutes(),m=t.getSeconds(),g=n!=r&&d[2]||i!=u&&d[3]||o!=a&&d[4]||s!=f&&d[5]||p!=c&&d[6]||m!=h&&d[7]||d[1];return r=n,u=i,a=o,f=s,c=p,h=m,g(t)}))}}function Pe(l,e,t){return new Date(l,e,t)}function Ae(l,e){return e(l)}function We(l,e){return(t,n,i,o)=>null==o?w:e(l(n))}Cl(2,-53,53,[1]);const Ye={show:!0,live:!0,isolate:!1,mount:()=>{},markers:{show:!0,width:2,stroke:function(l,e){let t=l.series[e];return t.width?t.stroke(l,e):t.points.width?t.points.stroke(l,e):null},fill:function(l,e){return l.series[e].fill(l,e)},dash:"solid"},idx:null,idxs:null,values:[]},Ce=[0,0];function He(l,e,t,n=!0){return l=>{0==l.button&&(!n||l.target==e)&&t(l)}}function Fe(l,e,t,n=!0){return l=>{(!n||l.target==e)&&t(l)}}const Re={show:!0,x:!0,y:!0,lock:!1,move:function(l,e,t){return Ce[0]=e,Ce[1]=t,Ce},points:{one:!1,show:function(l,e){let i=l.cursor.points,o=D(),s=i.size(l,e);E(o,t,s),E(o,n,s);let r=s/-2;E(o,"marginLeft",r),E(o,"marginTop",r);let u=i.width(l,e,s);return u&&E(o,"borderWidth",u),o},size:function(l,e){return l.series[e].points.size},width:0,stroke:function(l,e){let t=l.series[e].points;return t._stroke||t._fill},fill:function(l,e){let t=l.series[e].points;return t._fill||t._stroke}},bind:{mousedown:He,mouseup:He,click:He,dblclick:He,mousemove:Fe,mouseleave:Fe,mouseenter:Fe},drag:{setScale:!0,x:!0,y:!1,dist:0,uni:null,click:(l,e)=>{e.stopPropagation(),e.stopImmediatePropagation()},_x:!1,_y:!1},focus:{dist:(l,e,t,n,i)=>n-i,prox:-1,bias:0},hover:{skip:[void 0],prox:null,bias:0},left:-10,top:-10,idx:null,dataIdx:null,idxs:null,event:null},Ge={show:!0,stroke:"rgba(0,0,0,0.07)",width:2},Ie=Bl({},Ge,{filter:kl}),Le=Bl({},Ie,{size:10}),Oe=Bl({},Ge,{show:!1}),Ne='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"',je="bold "+Ne,Ue={show:!0,scale:"x",stroke:u,space:50,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:je,side:2,grid:Ie,ticks:Le,border:Oe,font:Ne,lineGap:1.5,rotate:0},Ve={show:!0,scale:"x",auto:!1,sorted:1,min:gl,max:-gl,idxs:[]};function Be(l,e){return e.map((l=>null==l?"":tl(l)))}function $e(l,e,t,n,i,o,s){let r=[],u=Wl.get(i)||0;for(let l=t=s?t:Al(Dl(t,i),u);n>=l;l=Al(l+i,u))r.push(Object.is(l,-0)?0:l);return r}function Je(l,e,t,n,i){const o=[],s=l.scales[l.axes[e].scale].log,r=sl((10==s?dl:pl)(t));i=cl(s,r),10==s&&(i=fe[L(i,fe)]);let u=t,a=i*s;10==s&&(a=fe[L(a,fe)]);do{o.push(u),u+=i,10!=s||Wl.has(u)||(u=Al(u,Wl.get(i))),a>u||(a=(i=u)*s,10==s&&(a=fe[L(a,fe)]))}while(n>=u);return o}function qe(l,e,t,n,i){let o=l.scales[l.axes[e].scale].asinh,s=n>o?Je(l,e,fl(o,t),n,i):[o],r=0>n||t>0?[]:[0];return(-o>t?Je(l,e,fl(o,-n),-t,i):[o]).reverse().map((l=>-l)).concat(r,s)}const Ke=/./,Xe=/[12357]/,Ze=/[125]/,Qe=/1/,lt=(l,e,t,n)=>l.map(((l,i)=>4==e&&0==l||i%n==0&&t.test(l.toExponential()[0>l?1:0])?l:null));function et(l,e,t){let n=l.axes[t],i=n.scale,o=l.scales[i],s=l.valToPos,r=n._space,u=s(10,i),a=s(9,i)-u<r?s(7,i)-u<r?s(5,i)-u<r?Qe:Ze:Xe:Ke;if(a==Qe){let l=ol(s(1,i)-u);if(r>l)return lt(e.slice().reverse(),o.distr,a,ul(r/l)).reverse()}return lt(e,o.distr,a,1)}function tt(l,e,t){let n=l.axes[t],i=n.scale,o=n._space,s=l.valToPos,r=ol(s(1,i)-s(2,i));return o>r?lt(e.slice().reverse(),3,Ke,ul(o/r)).reverse():e}function nt(l,e,t,n){return null==n?w:null==e?"":tl(e)}const it={show:!0,scale:"y",stroke:u,space:30,gap:5,alignTo:1,size:50,labelGap:0,labelSize:30,labelFont:je,side:3,grid:Ie,ticks:Le,border:Oe,font:Ne,lineGap:1.5,rotate:0},ot={scale:null,auto:!0,sorted:0,min:gl,max:-gl},st=(l,e,t,n,i)=>i,rt={show:!0,auto:!0,sorted:0,gaps:st,alpha:1,facets:[Bl({},ot,{scale:"x"}),Bl({},ot,{scale:"y"})]},ut={scale:"y",auto:!0,sorted:0,show:!0,spanGaps:!1,gaps:st,alpha:1,points:{show:function(l,e){let{scale:t,idxs:n}=l.series[0],i=l._data[0],o=l.valToPos(i[n[0]],t,!0),s=l.valToPos(i[n[1]],t,!0);return ol(s-o)/(l.series[e].points.space*y)>=n[1]-n[0]},filter:null},values:null,min:gl,max:-gl,idxs:[],path:null,clip:null};function at(l,e,t){return t/10}const ft={time:!0,auto:!0,distr:1,log:10,asinh:1,min:null,max:null,dir:1,ori:0},ct=Bl({},ft,{time:!1,ori:1}),ht={};function dt(l){let e=ht[l];return e||(e={key:l,plots:[],sub(l){e.plots.push(l)},unsub(l){e.plots=e.plots.filter((e=>e!=l))},pub(l,t,n,i,o,s,r){for(let u=0;e.plots.length>u;u++)e.plots[u]!=t&&e.plots[u].pub(l,t,n,i,o,s,r)}},null!=l&&(ht[l]=e)),e}function pt(l,e,t){const n=l.mode,i=l.series[e],o=2==n?l._data[e]:l._data,s=l.scales,r=l.bbox;let u=o[0],a=2==n?o[1]:o[e],f=2==n?s[i.facets[0].scale]:s[l.series[0].scale],c=2==n?s[i.facets[1].scale]:s[i.scale],h=r.left,d=r.top,p=r.width,m=r.height,g=l.valToPosH,x=l.valToPosV;return 0==f.ori?t(i,u,a,f,c,g,x,h,d,p,m,kt,Mt,Tt,zt,Pt):t(i,u,a,f,c,x,g,d,h,m,p,yt,St,Et,Dt,At)}function mt(l,e){let t=0,n=0,i=Q(l.bands,Fl);for(let l=0;i.length>l;l++){let o=i[l];o.series[0]==e?t=o.dir:o.series[1]==e&&(n|=1==o.dir?1:2)}return[t,1==n?-1:2==n?1:3==n?2:0]}function gt(l,e,t,n,i){let o=l.series[e],s=l.scales[2==l.mode?o.facets[1].scale:o.scale];return-1==i?s.min:1==i?s.max:3==s.distr?1==s.dir?s.min:s.max:0}function xt(l,e,t,n,i,o){return pt(l,e,((l,e,s,r,u,a,f,c,h,d,p)=>{let m=l.pxRound;const g=0==r.ori?Mt:St;let x,w;1==r.dir*(0==r.ori?1:-1)?(x=t,w=n):(x=n,w=t);let _=m(a(e[x],r,d,c)),b=m(f(s[x],u,p,h)),v=m(a(e[w],r,d,c)),k=m(f(1==o?u.max:u.min,u,p,h)),y=new Path2D(i);return g(y,v,k),g(y,_,k),g(y,_,b),y}))}function wt(l,e,t,n,i,o){let s=null;if(l.length>0){s=new Path2D;const r=0==e?Tt:Et;let u=t;for(let e=0;l.length>e;e++){let t=l[e];if(t[1]>t[0]){let l=t[0]-u;l>0&&r(s,u,n,l,n+o),u=t[1]}}let a=t+i-u,f=10;a>0&&r(s,u,n-f/2,a,n+o+f)}return s}function _t(l,e,t,n,i,o,s){let r=[],u=l.length;for(let a=1==i?t:n;a>=t&&n>=a;a+=i)if(null===e[a]){let f=a,c=a;if(1==i)for(;++a<=n&&null===e[a];)c=a;else for(;--a>=t&&null===e[a];)c=a;let h=o(l[f]),d=c==f?h:o(l[c]),p=f-i;h=s>0||0>p||p>=u?h:o(l[p]);let m=c+i;d=0>s||0>m||m>=u?d:o(l[m]),h>d||r.push([h,d])}return r}function bt(l){return 0==l?vl:1==l?rl:e=>zl(e,l)}function vt(l){let e=0==l?kt:yt,t=0==l?(l,e,t,n,i,o)=>{l.arcTo(e,t,n,i,o)}:(l,e,t,n,i,o)=>{l.arcTo(t,e,i,n,o)},n=0==l?(l,e,t,n,i)=>{l.rect(e,t,n,i)}:(l,e,t,n,i)=>{l.rect(t,e,i,n)};return(l,i,o,s,r,u=0,a=0)=>{0==u&&0==a?n(l,i,o,s,r):(u=al(u,s/2,r/2),a=al(a,s/2,r/2),e(l,i+u,o),t(l,i+s,o,i+s,o+r,u),t(l,i+s,o+r,i,o+r,a),t(l,i,o+r,i,o,a),t(l,i,o,i+s,o,u),l.closePath())}}const kt=(l,e,t)=>{l.moveTo(e,t)},yt=(l,e,t)=>{l.moveTo(t,e)},Mt=(l,e,t)=>{l.lineTo(e,t)},St=(l,e,t)=>{l.lineTo(t,e)},Tt=vt(0),Et=vt(1),zt=(l,e,t,n,i,o)=>{l.arc(e,t,n,i,o)},Dt=(l,e,t,n,i,o)=>{l.arc(t,e,n,i,o)},Pt=(l,e,t,n,i,o,s)=>{l.bezierCurveTo(e,t,n,i,o,s)},At=(l,e,t,n,i,o,s)=>{l.bezierCurveTo(t,e,i,n,s,o)};function Wt(){return(l,e,t,n,i)=>pt(l,e,((e,o,s,r,u,a,f,c,h,d,p)=>{let m,g,{pxRound:x,points:w}=e;0==r.ori?(m=kt,g=zt):(m=yt,g=Dt);const _=Al(w.width*y,3);let b=(w.size-w.width)/2*y,v=Al(2*b,3),k=new Path2D,M=new Path2D,{left:S,top:T,width:E,height:z}=l.bbox;Tt(M,S-v,T-v,E+2*v,z+2*v);const D=l=>{if(null!=s[l]){let e=x(a(o[l],r,d,c)),t=x(f(s[l],u,p,h));m(k,e+b,t),g(k,e,t,b,0,2*il)}};if(i)i.forEach(D);else for(let l=t;n>=l;l++)D(l);return{stroke:_>0?k:null,fill:k,clip:M,flags:3}}))}function Yt(l){return(e,t,n,i,o,s)=>{n!=i&&(o!=n&&s!=n&&l(e,t,n),o!=i&&s!=i&&l(e,t,i),l(e,t,s))}}const Ct=Yt(Mt),Ht=Yt(St);function Ft(l){const e=Q(l?.alignGaps,0);return(l,t,n,i)=>pt(l,t,((o,s,r,u,a,f,c,h,d,p,m)=>{[n,i]=U(r,n,i);let g,x,w=o.pxRound,_=l=>w(f(l,u,p,h)),b=l=>w(c(l,a,m,d));0==u.ori?(g=Mt,x=Ct):(g=St,x=Ht);const v=u.dir*(0==u.ori?1:-1),k={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:1},y=k.stroke;let M=!1;if(i-n<4*p)for(let l=1==v?n:i;l>=n&&i>=l;l+=v){let e=r[l];null===e?M=!0:null!=e&&g(y,_(s[l]),b(e))}else{let e,t,o,a=e=>l.posToVal(e,u.key,!0),f=null,c=null,h=_(s[1==v?n:i]),d=_(s[n]),p=_(s[i]),m=a(1==v?d+1:p-1);for(let l=1==v?n:i;l>=n&&i>=l;l+=v){let n=s[l],i=(1==v?m>n:n>m)?h:_(n),o=r[l];i==h?null!=o?(t=o,null==f?(g(y,i,b(t)),e=f=c=t):f>t?f=t:t>c&&(c=t)):null===o&&(M=!0):(null!=f&&x(y,h,b(f),b(c),b(e),b(t)),null!=o?(t=o,g(y,i,b(t)),f=c=e=t):(f=c=null,null===o&&(M=!0)),h=i,m=a(h+v))}null!=f&&f!=c&&o!=h&&x(y,h,b(f),b(c),b(e),b(t))}let[S,T]=mt(l,t);if(null!=o.fill||0!=S){let e=k.fill=new Path2D(y),r=b(o.fillTo(l,t,o.min,o.max,S)),u=_(s[n]),a=_(s[i]);-1==v&&([a,u]=[u,a]),g(e,a,r),g(e,u,r)}if(!o.spanGaps){let a=[];M&&a.push(..._t(s,r,n,i,v,_,e)),k.gaps=a=o.gaps(l,t,n,i,a),k.clip=wt(a,u.ori,h,d,p,m)}return 0!=T&&(k.band=2==T?[xt(l,t,n,i,y,-1),xt(l,t,n,i,y,1)]:xt(l,t,n,i,y,T)),k}))}function Rt(l,e,t,n,i,o,s=gl){if(l.length>1){let r=null;for(let u=0,a=1/0;l.length>u;u++)if(void 0!==e[u]){if(null!=r){let e=ol(l[u]-l[r]);a>e&&(a=e,s=ol(t(l[u],n,i,o)-t(l[r],n,i,o)))}r=u}}return s}function Gt(l,e,t,n,i){const o=l.length;if(2>o)return null;const s=new Path2D;if(t(s,l[0],e[0]),2==o)n(s,l[1],e[1]);else{let t=Array(o),n=Array(o-1),r=Array(o-1),u=Array(o-1);for(let t=0;o-1>t;t++)r[t]=e[t+1]-e[t],u[t]=l[t+1]-l[t],n[t]=r[t]/u[t];t[0]=n[0];for(let l=1;o-1>l;l++)0===n[l]||0===n[l-1]||n[l-1]>0!=n[l]>0?t[l]=0:(t[l]=3*(u[l-1]+u[l])/((2*u[l]+u[l-1])/n[l-1]+(u[l]+2*u[l-1])/n[l]),isFinite(t[l])||(t[l]=0));t[o-1]=n[o-2];for(let n=0;o-1>n;n++)i(s,l[n]+u[n]/3,e[n]+t[n]*u[n]/3,l[n+1]-u[n]/3,e[n+1]-t[n+1]*u[n]/3,l[n+1],e[n+1])}return s}const It=new Set;function Lt(){for(let l of It)l.syncRect(!0)}_&&(G("resize",v,Lt),G("scroll",v,Lt,!0),G(x,v,(()=>{en.pxRatio=y})));const Ot=Ft(),Nt=Wt();function jt(l,e,t,n){return(n?[l[0],l[1]].concat(l.slice(2)):[l[0]].concat(l.slice(1))).map(((l,n)=>Ut(l,n,e,t)))}function Ut(l,e,t,n){return Bl({},0==e?t:n,l)}function Vt(l,e,t){return null==e?Rl:[e,t]}const Bt=Vt;function $t(l,e,t){return null==e?Rl:Z(e,t,J,!0)}function Jt(l,e,t,n){return null==e?Rl:B(e,t,l.scales[n].log,!1)}const qt=Jt;function Kt(l,e,t,n){return null==e?Rl:$(e,t,l.scales[n].log,!1)}const Xt=Kt;function Zt(l,e,t,n,i){let o=fl(xl(l),xl(e)),s=e-l,r=L(i/n*s,t);do{let l=t[r],e=n*l/s;if(e>=i&&17>=o+(5>l?Wl.get(l):0))return[l,e]}while(++r<t.length);return[0,0]}function Qt(l){let e,t;return[l=l.replace(/(\d+)px/,((l,n)=>(e=rl((t=+n)*y))+"px")),e,t]}function ln(l){l.show&&[l.font,l.labelFont].forEach((l=>{let e=Al(l[2]*y,1);l[0]=l[0].replace(/[0-9.]+px/,e+"px"),l[1]=e}))}function en(u,g,_){const k={mode:Q(u.mode,1)},M=k.mode;function P(l,e,t,n){let i=e.valToPct(l);return n+t*(-1==e.dir?1-i:i)}function W(l,e,t,n){let i=e.valToPct(l);return n+t*(-1==e.dir?i:1-i)}function C(l,e,t,n){return 0==e.ori?P(l,e,t,n):W(l,e,t,n)}k.valToPosH=P,k.valToPosV=W;let F=!1;k.status=0;const R=k.root=D("uplot");null!=u.id&&(R.id=u.id),S(R,u.class),u.title&&(D("u-title",R).textContent=u.title);const O=z("canvas"),K=k.ctx=O.getContext("2d"),X=D("u-wrap",R);G("click",X,(l=>{l.target===el&&(Nn!=Gn||jn!=In)&&Zn.click(k,l)}),!0);const ll=k.under=D("u-under",X);X.appendChild(O);const el=k.over=D("u-over",X),tl=+Q((u=Vl(u)).pxAlign,1),sl=bt(tl);(u.plugins||[]).forEach((l=>{l.opts&&(u=l.opts(k,u)||u)}));const hl=u.ms||.001,pl=k.series=1==M?jt(u.series||[],Ve,ut,!1):function(l,e){return l.map(((l,t)=>0==t?{}:Bl({},e,l)))}(u.series||[null],rt),xl=k.axes=jt(u.axes||[],Ue,it,!0),vl=k.scales={},Tl=k.bands=u.bands||[];Tl.forEach((l=>{l.fill=bl(l.fill||null),l.dir=Q(l.dir,-1)}));const El=2==M?pl[1].facets[0].scale:pl[0].scale,Dl={axes:function(){for(let l=0;xl.length>l;l++){let e=xl[l];if(!e.show||!e._show)continue;let t,n,u=e.side,a=u%2,f=e.stroke(k,l),c=0==u||3==u?-1:1,[h,d]=e._found;if(null!=e.label){let s=rl((e._lpos+e.labelGap*c)*y);_n(e.labelFont[0],f,"center",2==u?i:o),K.save(),1==a?(t=n=0,K.translate(s,rl(lt+st/2)),K.rotate((3==u?-il:il)/2)):(t=rl(Qe+ot/2),n=s);let r=_l(e.label)?e.label(k,l,h,d):e.label;K.fillText(r,t,n),K.restore()}if(0==d)continue;let p=vl[e.scale],m=0==a?ot:st,g=0==a?Qe:lt,x=e._splits,w=2==p.distr?x.map((l=>pn[l])):x,_=2==p.distr?pn[x[1]]-pn[x[0]]:h,b=e.ticks,v=e.border,M=b.show?b.size:0,S=rl(M*y),T=rl((2==e.alignTo?e._size-M-e.gap:e.gap)*y),E=e._rotate*-il/180,z=sl(e._pos*y),D=z+(S+T)*c;n=0==a?D:0,t=1==a?D:0,_n(e.font[0],f,1==e.align?s:2==e.align?r:E>0?s:0>E?r:0==a?"center":3==u?r:s,E||1==a?"middle":2==u?i:o);let P=e.font[1]*e.lineGap,A=x.map((l=>sl(C(l,p,m,g)))),W=e._values;for(let l=0;W.length>l;l++){let e=W[l];if(null!=e){0==a?t=A[l]:n=A[l],e=""+e;let i=-1==e.indexOf("\n")?[e]:e.split(/\n/gm);for(let l=0;i.length>l;l++){let e=i[l];E?(K.save(),K.translate(t,n+l*P),K.rotate(E),K.fillText(e,0,0),K.restore()):K.fillText(e,t,n+l*P)}}}b.show&&zn(A,b.filter(k,w,l,d,_),a,u,z,S,Al(b.width*y,3),b.stroke(k,l),b.dash,b.cap);let Y=e.grid;Y.show&&zn(A,Y.filter(k,w,l,d,_),a,0==a?2:1,0==a?lt:Qe,0==a?st:ot,Al(Y.width*y,3),Y.stroke(k,l),Y.dash,Y.cap),v.show&&zn([z],[1],0==a?1:0,0==a?1:2,1==a?lt:Qe,1==a?st:ot,Al(v.width*y,3),v.stroke(k,l),v.dash,v.cap)}Ci("drawAxes")},series:function(){if(Gt>0){let l=pl.some((l=>l._focus))&&dn!=Tt.alpha;l&&(K.globalAlpha=dn=Tt.alpha),pl.forEach(((l,e)=>{if(e>0&&l.show&&(kn(e,!1),kn(e,!0),null==l._paths)){let t=dn;dn!=l.alpha&&(K.globalAlpha=dn=l.alpha);let n=2==M?[0,g[e][0].length-1]:function(l){let e=wl(Lt-1,0,Gt-1),t=wl(en+1,0,Gt-1);for(;null==l[e]&&e>0;)e--;for(;null==l[t]&&Gt-1>t;)t++;return[e,t]}(g[e]);l._paths=l.paths(k,e,n[0],n[1]),dn!=t&&(K.globalAlpha=dn=t)}})),pl.forEach(((l,e)=>{if(e>0&&l.show){let t=dn;dn!=l.alpha&&(K.globalAlpha=dn=l.alpha),null!=l._paths&&yn(e,!1);{let t=null!=l._paths?l._paths.gaps:null,n=l.points.show(k,e,Lt,en,t),i=l.points.filter(k,e,n,t);(n||i)&&(l.points._paths=l.points.paths(k,e,Lt,en,i),yn(e,!0))}dn!=t&&(K.globalAlpha=dn=t),Ci("drawSeries",e)}})),l&&(K.globalAlpha=dn=1)}}},Pl=(u.drawOrder||["axes","series"]).map((l=>Dl[l]));function Cl(l){const e=3==l.distr?e=>dl(e>0?e:l.clamp(k,e,l.min,l.max,l.key)):4==l.distr?e=>ml(e,l.asinh):100==l.distr?e=>l.fwd(e):l=>l;return t=>{let n=e(t),{_min:i,_max:o}=l;return(n-i)/(o-i)}}function Il(l){let e=vl[l];if(null==e){let t=(u.scales||Hl)[l]||Hl;if(null!=t.from){Il(t.from);let e=Bl({},vl[t.from],t,{key:l});e.valToPct=Cl(e),vl[l]=e}else{e=vl[l]=Bl({},l==El?ft:ct,t),e.key=l;let n=e.time,i=e.range,o=Gl(i);if((l!=El||2==M&&!n)&&(!o||null!=i[0]&&null!=i[1]||(i={min:null==i[0]?q:{mode:1,hard:i[0],soft:i[0]},max:null==i[1]?q:{mode:1,hard:i[1],soft:i[1]}},o=!1),!o&&Ol(i))){let l=i;i=(e,t,n)=>null==t?Rl:Z(t,n,l)}e.range=bl(i||(n?Bt:l==El?3==e.distr?qt:4==e.distr?Xt:Vt:3==e.distr?Jt:4==e.distr?Kt:$t)),e.auto=bl(!o&&e.auto),e.clamp=bl(e.clamp||at),e._min=e._max=null,e.valToPct=Cl(e)}}}Il("x"),Il("y"),1==M&&pl.forEach((l=>{Il(l.scale)})),xl.forEach((l=>{Il(l.scale)}));for(let l in u.scales)Il(l);const jl=vl[El],Ul=jl.distr;let $l,ql;0==jl.ori?(S(R,"u-hz"),$l=P,ql=W):(S(R,"u-vt"),$l=W,ql=P);const Kl={};for(let l in vl){let e=vl[l];null==e.min&&null==e.max||(Kl[l]={min:e.min,max:e.max},e.min=e.max=null)}const Xl=u.tzDate||(l=>new Date(rl(l/hl))),Zl=u.fmtDate||ne,Ql=1==hl?Me(Xl):Ee(Xl),le=De(Xl,ze(1==hl?ye:Te,Zl)),ee=We(Xl,Ae("{YYYY}-{MM}-{DD} {h}:{mm}{aa}",Zl)),te=[],ie=k.legend=Bl({},Ye,u.legend),oe=k.cursor=Bl({},Re,{drag:{y:2==M}},u.cursor),se=ie.show,re=oe.show,ue=ie.markers;let ce,he,de;ie.idxs=te,ue.width=bl(ue.width),ue.dash=bl(ue.dash),ue.stroke=bl(ue.stroke),ue.fill=bl(ue.fill);let pe,me=[],ge=[],xe=!1,we={};if(ie.live){const l=pl[1]?pl[1].values:null;xe=null!=l,pe=xe?l(k,1,0):{_:0};for(let l in pe)we[l]=w}if(se)if(ce=z("table","u-legend",R),de=z("tbody",null,ce),ie.mount(k,ce),xe){he=z("thead",null,ce,de);let l=z("tr",null,he);for(var _e in z("th",null,l),pe)z("th",e,l).textContent=_e}else S(ce,"u-inline"),ie.live&&S(ce,"u-live");const be={show:!0},ve={show:!1},Pe=new Map;function Ce(l,e,t,n=!0){const i=Pe.get(e)||{},o=oe.bind[l](k,e,t,n);o&&(G(l,e,i[l]=o),Pe.set(e,i))}function He(l,e){const t=Pe.get(e)||{};for(let n in t)null!=l&&n!=l||(I(n,e,t[n]),delete t[n]);null==l&&Pe.delete(e)}let Fe=0,Ge=0,Ie=0,Le=0,Oe=0,Ne=0,je=Oe,Ke=Ne,Xe=Ie,Ze=Le,Qe=0,lt=0,ot=0,st=0;k.bbox={};let ht=!1,pt=!1,mt=!1,xt=!1,wt=!1,_t=!1;function vt(l,e,t){(t||l!=k.width||e!=k.height)&&kt(l,e),An(!1),mt=!0,pt=!0,Jn()}function kt(l,e){k.width=Fe=Ie=l,k.height=Ge=Le=e,Oe=Ne=0,function(){let l=!1,e=!1,t=!1,n=!1;xl.forEach((i=>{if(i.show&&i._show){let{side:o,_size:s}=i,r=s+(null!=i.label?i.labelSize:0);r>0&&(o%2?(Ie-=r,3==o?(Oe+=r,n=!0):t=!0):(Le-=r,0==o?(Ne+=r,l=!0):e=!0))}})),Ct[0]=l,Ct[1]=t,Ct[2]=e,Ct[3]=n,Ie-=Rt[1]+Rt[3],Oe+=Rt[3],Le-=Rt[2]+Rt[0],Ne+=Rt[0]}(),function(){let l=Oe+Ie,e=Ne+Le,t=Oe,n=Ne;function i(i,o){switch(i){case 1:return l+=o,l-o;case 2:return e+=o,e-o;case 3:return t-=o,t+o;case 0:return n-=o,n+o}}xl.forEach((l=>{if(l.show&&l._show){let e=l.side;l._pos=i(e,l._size),null!=l.label&&(l._lpos=i(e,l.labelSize))}}))}();let t=k.bbox;Qe=t.left=zl(Oe*y,.5),lt=t.top=zl(Ne*y,.5),ot=t.width=zl(Ie*y,.5),st=t.height=zl(Le*y,.5)}const yt=3;if(k.setSize=function({width:l,height:e}){vt(l,e)},null==oe.dataIdx){let l=oe.hover,e=l.skip=new Set(l.skip??[]);e.add(void 0);let t=l.prox=bl(l.prox),n=l.bias??=0;oe.dataIdx=(l,i,o,s)=>{if(0==i)return o;let r=o,u=t(l,i,o,s)??gl,a=u>=0&&gl>u,f=0==jl.ori?Ie:Le,c=oe.left,h=g[0],d=g[i];if(e.has(d[o])){r=null;let l,t=null,i=null;if(0==n||-1==n)for(l=o;null==t&&l-- >0;)e.has(d[l])||(t=l);if(0==n||1==n)for(l=o;null==i&&l++<d.length;)e.has(d[l])||(i=l);if(null!=t||null!=i)if(a){let l=c-(null==t?-1/0:$l(h[t],jl,f,0)),e=(null==i?1/0:$l(h[i],jl,f,0))-c;l>e?e>u||(r=i):l>u||(r=t)}else r=null==i?t:null==t||o-t>i-o?i:t}else a&&ol(c-$l(h[o],jl,f,0))>u&&(r=null);return r}}const Mt=l=>{oe.event=l};oe.idxs=te,oe._lock=!1;let St=oe.points;St.show=bl(St.show),St.size=bl(St.size),St.stroke=bl(St.stroke),St.width=bl(St.width),St.fill=bl(St.fill);const Tt=k.focus=Bl({},u.focus||{alpha:.3},oe.focus),Et=Tt.prox>=0,zt=Et&&St.one;let Dt=[],Pt=[],At=[];function Wt(l,e){let t=St.show(k,e);if(t instanceof HTMLElement)return S(t,"u-cursor-pt"),S(t,l.class),A(t,-10,-10,Ie,Le),el.insertBefore(t,Dt[e]),t}function Yt(t,n){if(1==M||n>0){let l=1==M&&vl[t.scale].time,e=t.value;t.value=l?Ll(e)?We(Xl,Ae(e,Zl)):e||ee:e||nt,t.label=t.label||(l?"Time":"Value")}if(zt||n>0){t.width=null==t.width?1:t.width,t.paths=t.paths||Ot||yl,t.fillTo=bl(t.fillTo||gt),t.pxAlign=+Q(t.pxAlign,tl),t.pxRound=bt(t.pxAlign),t.stroke=bl(t.stroke||null),t.fill=bl(t.fill||null),t._stroke=t._fill=t._paths=t._focus=null;let l=function(l){return Al(1*(3+2*(l||1)),3)}(fl(1,t.width)),e=t.points=Bl({},{size:l,width:fl(1,.2*l),stroke:t.stroke,space:2*l,paths:Nt,_stroke:null,_fill:null},t.points);e.show=bl(e.show),e.filter=bl(e.filter),e.fill=bl(e.fill),e.stroke=bl(e.stroke),e.paths=bl(e.paths),e.pxAlign=t.pxAlign}if(se){let i=function(t,n){if(0==n&&(xe||!ie.live||2==M))return Rl;let i=[],o=z("tr","u-series",de,de.childNodes[n]);S(o,t.class),t.show||S(o,l);let s=z("th",null,o);if(ue.show){let l=D("u-marker",s);if(n>0){let e=ue.width(k,n);e&&(l.style.border=e+"px "+ue.dash(k,n)+" "+ue.stroke(k,n)),l.style.background=ue.fill(k,n)}}let r=D(e,s);for(var u in t.label instanceof HTMLElement?r.appendChild(t.label):r.textContent=t.label,n>0&&(ue.show||(r.style.color=t.width>0?ue.stroke(k,n):ue.fill(k,n)),Ce("click",s,(l=>{if(oe._lock)return;Mt(l);let e=pl.indexOf(t);if((l.ctrlKey||l.metaKey)!=ie.isolate){let l=pl.some(((l,t)=>t>0&&t!=e&&l.show));pl.forEach(((t,n)=>{n>0&&oi(n,l?n==e?be:ve:be,!0,Fi.setSeries)}))}else oi(e,{show:!t.show},!0,Fi.setSeries)}),!1),Et&&Ce(d,s,(l=>{oe._lock||(Mt(l),oi(pl.indexOf(t),ai,!0,Fi.setSeries))}),!1)),pe){let l=z("td","u-value",o);l.textContent="--",i.push(l)}return[o,i]}(t,n);me.splice(n,0,i[0]),ge.splice(n,0,i[1]),ie.values.push(null)}if(re){te.splice(n,0,null);let l=null;zt?0==n&&(l=Wt(t,n)):n>0&&(l=Wt(t,n)),Dt.splice(n,0,l),Pt.splice(n,0,0),At.splice(n,0,0)}Ci("addSeries",n)}k.addSeries=function(l,e){e=null==e?pl.length:e,l=1==M?Ut(l,e,Ve,ut):Ut(l,e,{},rt),pl.splice(e,0,l),Yt(pl[e],e)},k.delSeries=function(l){if(pl.splice(l,1),se){ie.values.splice(l,1),ge.splice(l,1);let e=me.splice(l,1)[0];He(null,e.firstChild),e.remove()}re&&(te.splice(l,1),Dt.splice(l,1)[0].remove(),Pt.splice(l,1),At.splice(l,1)),Ci("delSeries",l)};const Ct=[!1,!1,!1,!1];function Ht(l,e,t){let[n,i,o,s]=t,r=e%2,u=0;return 0==r&&(s||i)&&(u=0==e&&!n||2==e&&!o?rl(Ue.size/3):0),1==r&&(n||o)&&(u=1==e&&!i||3==e&&!s?rl(it.size/2):0),u}const Ft=k.padding=(u.padding||[Ht,Ht,Ht,Ht]).map((l=>bl(Q(l,Ht)))),Rt=k._padding=Ft.map(((l,e)=>l(k,e,Ct,0)));let Gt,Lt=null,en=null;const tn=1==M?pl[0].idxs:null;let nn,on,sn,rn,un,an,fn,cn,hn,dn,pn=null,mn=!1;function gn(l,e){if(k.data=k._data=g=null==l?[]:l,2==M){Gt=0;for(let l=1;pl.length>l;l++)Gt+=g[l][0].length}else{0==g.length&&(k.data=k._data=g=[[]]),pn=g[0],Gt=pn.length;let l=g;if(2==Ul){l=g.slice();let e=l[0]=Array(Gt);for(let l=0;Gt>l;l++)e[l]=l}k._data=g=l}if(An(!0),Ci("setData"),2==Ul&&(mt=!0),!1!==e){let l=jl;l.auto(k,mn)?xn():ii(El,l.min,l.max),xt=xt||oe.left>=0,_t=!0,Jn()}}function xn(){let l,e;mn=!0,1==M&&(Gt>0?(Lt=tn[0]=0,en=tn[1]=Gt-1,l=g[0][Lt],e=g[0][en],2==Ul?(l=Lt,e=en):l==e&&(3==Ul?[l,e]=B(l,l,jl.log,!1):4==Ul?[l,e]=$(l,l,jl.log,!1):jl.time?e=l+rl(86400/hl):[l,e]=Z(l,e,J,!0))):(Lt=tn[0]=l=null,en=tn[1]=e=null)),ii(El,l,e)}function wn(l,e,t,n,i,o){l??=a,t??=Fl,n??="butt",i??=a,o??="round",l!=nn&&(K.strokeStyle=nn=l),i!=on&&(K.fillStyle=on=i),e!=sn&&(K.lineWidth=sn=e),o!=un&&(K.lineJoin=un=o),n!=an&&(K.lineCap=an=n),t!=rn&&K.setLineDash(rn=t)}function _n(l,e,t,n){e!=on&&(K.fillStyle=on=e),l!=fn&&(K.font=fn=l),t!=cn&&(K.textAlign=cn=t),n!=hn&&(K.textBaseline=hn=n)}function bn(l,e,t,n,i=0){if(n.length>0&&l.auto(k,mn)&&(null==e||null==e.min)){let e=Q(Lt,0),o=Q(en,n.length-1),s=null==t.min?function(l,e,t,n=0,i=!1){let o=i?V:U,s=i?j:N;[e,t]=o(l,e,t);let r=l[e],u=l[e];if(e>-1)if(1==n)r=l[e],u=l[t];else if(-1==n)r=l[t],u=l[e];else for(let n=e;t>=n;n++){let e=l[n];s(e)&&(r>e?r=e:e>u&&(u=e))}return[r??gl,u??-gl]}(n,e,o,i,3==l.distr):[t.min,t.max];l.min=al(l.min,t.min=s[0]),l.max=fl(l.max,t.max=s[1])}}k.setData=gn;const vn={min:null,max:null};function kn(l,e){let t=e?pl[l].points:pl[l];t._stroke=t.stroke(k,l),t._fill=t.fill(k,l)}function yn(l,e){let t=e?pl[l].points:pl[l],{stroke:n,fill:i,clip:o,flags:s,_stroke:r=t._stroke,_fill:u=t._fill,_width:a=t.width}=t._paths;a=Al(a*y,3);let f=null,c=a%2/2;e&&null==u&&(u=a>0?"#fff":r);let h=1==t.pxAlign&&c>0;if(h&&K.translate(c,c),!e){let l=Qe-a/2,e=lt-a/2,t=ot+a,n=st+a;f=new Path2D,f.rect(l,e,t,n)}e?Sn(r,a,t.dash,t.cap,u,n,i,s,o):function(l,e,t,n,i,o,s,r,u,a,f){let c=!1;0!=u&&Tl.forEach(((h,d)=>{if(h.series[0]==l){let l,p=pl[h.series[1]],m=g[h.series[1]],x=(p._paths||Hl).band;Gl(x)&&(x=1==h.dir?x[0]:x[1]);let w=null;p.show&&x&&function(l,e,t){for(e=Q(e,0),t=Q(t,l.length-1);t>=e;){if(null!=l[e])return!0;e++}return!1}(m,Lt,en)?(w=h.fill(k,d)||o,l=p._paths.clip):x=null,Sn(e,t,n,i,w,s,r,u,a,f,l,x),c=!0}})),c||Sn(e,t,n,i,o,s,r,u,a,f)}(l,r,a,t.dash,t.cap,u,n,i,s,f,o),h&&K.translate(-c,-c)}const Mn=3;function Sn(l,e,t,n,i,o,s,r,u,a,f,c){wn(l,e,t,n,i),(u||a||c)&&(K.save(),u&&K.clip(u),a&&K.clip(a)),c?(r&Mn)==Mn?(K.clip(c),f&&K.clip(f),En(i,s),Tn(l,o,e)):2&r?(En(i,s),K.clip(c),Tn(l,o,e)):1&r&&(K.save(),K.clip(c),f&&K.clip(f),En(i,s),K.restore(),Tn(l,o,e)):(En(i,s),Tn(l,o,e)),(u||a||c)&&K.restore()}function Tn(l,e,t){t>0&&(e instanceof Map?e.forEach(((l,e)=>{K.strokeStyle=nn=e,K.stroke(l)})):null!=e&&l&&K.stroke(e))}function En(l,e){e instanceof Map?e.forEach(((l,e)=>{K.fillStyle=on=e,K.fill(l)})):null!=e&&l&&K.fill(e)}function zn(l,e,t,n,i,o,s,r,u,a){let f=s%2/2;1==tl&&K.translate(f,f),wn(r,s,u,a,r),K.beginPath();let c,h,d,p,m=i+(0==n||3==n?-o:o);0==t?(h=i,p=m):(c=i,d=m);for(let n=0;l.length>n;n++)null!=e[n]&&(0==t?c=d=l[n]:h=p=l[n],K.moveTo(c,h),K.lineTo(d,p));K.stroke(),1==tl&&K.translate(-f,-f)}function Dn(l){let e=!0;return xl.forEach(((t,n)=>{if(!t.show)return;let i=vl[t.scale];if(null==i.min)return void(t._show&&(e=!1,t._show=!1,An(!1)));t._show||(e=!1,t._show=!0,An(!1));let o=t.side,s=o%2,{min:r,max:u}=i,[a,f]=function(l,e,t,n){let i,o=xl[l];if(n>0){let s=o._space=o.space(k,l,e,t,n);i=Zt(e,t,o._incrs=o.incrs(k,l,e,t,n,s),n,s)}else i=[0,0];return o._found=i}(n,r,u,0==s?Ie:Le);if(0==f)return;let c=t._splits=t.splits(k,n,r,u,a,f,2==i.distr),h=2==i.distr?c.map((l=>pn[l])):c,d=2==i.distr?pn[c[1]]-pn[c[0]]:a,p=t._values=t.values(k,t.filter(k,h,n,f,d),n,f,d);t._rotate=2==o?t.rotate(k,p,n,f):0;let m=t._size;t._size=ul(t.size(k,p,n,l)),null!=m&&t._size!=m&&(e=!1)})),e}function Pn(l){let e=!0;return Ft.forEach(((t,n)=>{let i=t(k,n,Ct,l);i!=Rt[n]&&(e=!1),Rt[n]=i})),e}function An(l){pl.forEach(((e,t)=>{t>0&&(e._paths=null,l&&(1==M?(e.min=null,e.max=null):e.facets.forEach((l=>{l.min=null,l.max=null}))))}))}let Wn,Yn,Cn,Hn,Fn,Rn,Gn,In,Ln,On,Nn,jn,Un=!1,Vn=!1,Bn=[];function $n(){Vn=!1;for(let l=0;Bn.length>l;l++)Ci(...Bn[l]);Bn.length=0}function Jn(){Un||(Jl(qn),Un=!0)}function qn(){if(ht&&(function(){for(let l in vl){let e=vl[l];null==Kl[l]&&(null==e.min||null!=Kl[El]&&e.auto(k,mn))&&(Kl[l]=vn)}for(let l in vl){let e=vl[l];null==Kl[l]&&null!=e.from&&null!=Kl[e.from]&&(Kl[l]=vn)}null!=Kl[El]&&An(!0);let l={};for(let e in Kl){let t=Kl[e];if(null!=t){let n=l[e]=Vl(vl[e],Nl);if(null!=t.min)Bl(n,t);else if(e!=El||2==M)if(0==Gt&&null==n.from){let l=n.range(k,null,null,e);n.min=l[0],n.max=l[1]}else n.min=gl,n.max=-gl}}if(Gt>0){pl.forEach(((e,t)=>{if(1==M){let n=e.scale,i=Kl[n];if(null==i)return;let o=l[n];if(0==t){let l=o.range(k,o.min,o.max,n);o.min=l[0],o.max=l[1],Lt=L(o.min,g[0]),en=L(o.max,g[0]),en-Lt>1&&(o.min>g[0][Lt]&&Lt++,g[0][en]>o.max&&en--),e.min=pn[Lt],e.max=pn[en]}else e.show&&e.auto&&bn(o,i,e,g[t],e.sorted);e.idxs[0]=Lt,e.idxs[1]=en}else if(t>0&&e.show&&e.auto){let[n,i]=e.facets,o=n.scale,s=i.scale,[r,u]=g[t],a=l[o],f=l[s];null!=a&&bn(a,Kl[o],n,r,n.sorted),null!=f&&bn(f,Kl[s],i,u,i.sorted),e.min=i.min,e.max=i.max}}));for(let e in l){let t=l[e],n=Kl[e];if(null==t.from&&(null==n||null==n.min)){let l=t.range(k,t.min==gl?null:t.min,t.max==-gl?null:t.max,e);t.min=l[0],t.max=l[1]}}}for(let e in l){let t=l[e];if(null!=t.from){let n=l[t.from];if(null==n.min)t.min=t.max=null;else{let l=t.range(k,n.min,n.max,e);t.min=l[0],t.max=l[1]}}}let e={},t=!1;for(let n in l){let i=l[n],o=vl[n];if(o.min!=i.min||o.max!=i.max){o.min=i.min,o.max=i.max;let l=o.distr;o._min=3==l?dl(o.min):4==l?ml(o.min,o.asinh):100==l?o.fwd(o.min):o.min,o._max=3==l?dl(o.max):4==l?ml(o.max,o.asinh):100==l?o.fwd(o.max):o.max,e[n]=t=!0}}if(t){pl.forEach(((l,t)=>{2==M?t>0&&e.y&&(l._paths=null):e[l.scale]&&(l._paths=null)}));for(let l in e)mt=!0,Ci("setScale",l);re&&oe.left>=0&&(xt=_t=!0)}for(let l in Kl)Kl[l]=null}(),ht=!1),mt&&(function(){let l=!1,e=0;for(;!l;){e++;let t=Dn(e),n=Pn(e);l=e==yt||t&&n,l||(kt(k.width,k.height),pt=!0)}}(),mt=!1),pt){if(E(ll,s,Oe),E(ll,i,Ne),E(ll,t,Ie),E(ll,n,Le),E(el,s,Oe),E(el,i,Ne),E(el,t,Ie),E(el,n,Le),E(X,t,Fe),E(X,n,Ge),O.width=rl(Fe*y),O.height=rl(Ge*y),xl.forEach((({_el:e,_show:t,_size:n,_pos:i,side:o})=>{if(null!=e)if(t){let t=o%2==1;E(e,t?"left":"top",i-(3===o||0===o?n:0)),E(e,t?"width":"height",n),E(e,t?"top":"left",t?Ne:Oe),E(e,t?"height":"width",t?Le:Ie),T(e,l)}else S(e,l)})),nn=on=sn=un=an=fn=cn=hn=rn=null,dn=1,_i(!0),Oe!=je||Ne!=Ke||Ie!=Xe||Le!=Ze){An(!1);let l=Ie/Xe,e=Le/Ze;if(re&&!xt&&oe.left>=0){oe.left*=l,oe.top*=e,Cn&&A(Cn,rl(oe.left),0,Ie,Le),Hn&&A(Hn,0,rl(oe.top),Ie,Le);for(let t=0;Dt.length>t;t++){let n=Dt[t];null!=n&&(Pt[t]*=l,At[t]*=e,A(n,ul(Pt[t]),ul(At[t]),Ie,Le))}}if(ei.show&&!wt&&ei.left>=0&&ei.width>0){ei.left*=l,ei.width*=l,ei.top*=e,ei.height*=e;for(let l in ki)E(ti,l,ei[l])}je=Oe,Ke=Ne,Xe=Ie,Ze=Le}Ci("setSize"),pt=!1}Fe>0&&Ge>0&&(K.clearRect(0,0,O.width,O.height),Ci("drawClear"),Pl.forEach((l=>l())),Ci("draw")),ei.show&&wt&&(ni(ei),wt=!1),re&&xt&&(xi(null,!0,!1),xt=!1),ie.show&&ie.live&&_t&&(mi(),_t=!1),F||(F=!0,k.status=1,Ci("ready")),mn=!1,Un=!1}function Kn(l,e){let t=vl[l];if(null==t.from){if(0==Gt){let n=t.range(k,e.min,e.max,l);e.min=n[0],e.max=n[1]}if(e.min>e.max){let l=e.min;e.min=e.max,e.max=l}if(Gt>1&&null!=e.min&&null!=e.max&&1e-16>e.max-e.min)return;l==El&&2==t.distr&&Gt>0&&(e.min=L(e.min,g[0]),e.max=L(e.max,g[0]),e.min==e.max&&e.max++),Kl[l]=e,ht=!0,Jn()}}k.batch=function(l,e=!1){Un=!0,Vn=e,l(k),qn(),e&&Bn.length>0&&queueMicrotask($n)},k.redraw=(l,e)=>{mt=e||!1,!1!==l?ii(El,jl.min,jl.max):Jn()},k.setScale=Kn;let Xn=!1;const Zn=oe.drag;let Qn=Zn.x,li=Zn.y;re&&(oe.x&&(Wn=D("u-cursor-x",el)),oe.y&&(Yn=D("u-cursor-y",el)),0==jl.ori?(Cn=Wn,Hn=Yn):(Cn=Yn,Hn=Wn),Nn=oe.left,jn=oe.top);const ei=k.select=Bl({show:!0,over:!0,left:0,width:0,top:0,height:0},u.select),ti=ei.show?D("u-select",ei.over?el:ll):null;function ni(l,e){if(ei.show){for(let e in l)ei[e]=l[e],e in ki&&E(ti,e,l[e]);!1!==e&&Ci("setSelect")}}function ii(l,e,t){Kn(l,{min:e,max:t})}function oi(e,t,n,i){null!=t.focus&&function(l){if(l!=ui){let e=null==l,t=1!=Tt.alpha;pl.forEach(((n,i)=>{if(1==M||i>0){let o=e||0==i||i==l;n._focus=e?null:o,t&&function(l,e){pl[l].alpha=e,re&&null!=Dt[l]&&(Dt[l].style.opacity=e),se&&me[l]&&(me[l].style.opacity=e)}(i,o?1:Tt.alpha)}})),ui=l,t&&Jn()}}(e),null!=t.show&&pl.forEach(((n,i)=>{0>=i||e!=i&&null!=e||(n.show=t.show,function(e){if(pl[e].show)se&&T(me[e],l);else if(se&&S(me[e],l),re){let l=zt?Dt[0]:Dt[e];null!=l&&A(l,-10,-10,Ie,Le)}}(i),2==M?(ii(n.facets[0].scale,null,null),ii(n.facets[1].scale,null,null)):ii(n.scale,null,null),Jn())})),!1!==n&&Ci("setSeries",e,t),i&&Ii("setSeries",k,e,t)}let si,ri,ui;k.setSelect=ni,k.setSeries=oi,k.addBand=function(l,e){l.fill=bl(l.fill||null),l.dir=Q(l.dir,-1),Tl.splice(e=null==e?Tl.length:e,0,l)},k.setBand=function(l,e){Bl(Tl[l],e)},k.delBand=function(l){null==l?Tl.length=0:Tl.splice(l,1)};const ai={focus:!0};function fi(l,e,t){let n=vl[e];t&&(l=l/y-(1==n.ori?Ne:Oe));let i=Ie;1==n.ori&&(i=Le,l=i-l),-1==n.dir&&(l=i-l);let o=n._min,s=o+l/i*(n._max-o),r=n.distr;return 3==r?cl(10,s):4==r?((l,e=1)=>nl.sinh(l)*e)(s,n.asinh):100==r?n.bwd(s):s}function ci(l,e){E(ti,s,ei.left=l),E(ti,t,ei.width=e)}function hi(l,e){E(ti,i,ei.top=l),E(ti,n,ei.height=e)}se&&Et&&Ce(p,ce,(l=>{oe._lock||(Mt(l),null!=ui&&oi(null,ai,!0,Fi.setSeries))})),k.valToIdx=l=>L(l,g[0]),k.posToIdx=function(l,e){return L(fi(l,El,e),g[0],Lt,en)},k.posToVal=fi,k.valToPos=(l,e,t)=>0==vl[e].ori?P(l,vl[e],t?ot:Ie,t?Qe:0):W(l,vl[e],t?st:Le,t?lt:0),k.setCursor=(l,e,t)=>{Nn=l.left,jn=l.top,xi(null,e,t)};let di=0==jl.ori?ci:hi,pi=1==jl.ori?ci:hi;function mi(l,e){if(null!=l&&(l.idxs?l.idxs.forEach(((l,e)=>{te[e]=l})):(l=>void 0===l)(l.idx)||te.fill(l.idx),ie.idx=te[0]),se&&ie.live){for(let l=0;pl.length>l;l++)(l>0||1==M&&!xe)&&gi(l,te[l]);!function(){if(se&&ie.live)for(let l=2==M?1:0;pl.length>l;l++){if(0==l&&xe)continue;let e=ie.values[l],t=0;for(let n in e)ge[l][t++].firstChild.nodeValue=e[n]}}()}_t=!1,!1!==e&&Ci("setLegend")}function gi(l,e){let t,n=pl[l],i=0==l&&2==Ul?pn:g[l];xe?t=n.values(k,l,e)??we:(t=n.value(k,null==e?null:i[e],l,e),t=null==t?we:{_:t}),ie.values[l]=t}function xi(l,e,t){let n;Ln=Nn,On=jn,[Nn,jn]=oe.move(k,Nn,jn),oe.left=Nn,oe.top=jn,re&&(Cn&&A(Cn,rl(Nn),0,Ie,Le),Hn&&A(Hn,0,rl(jn),Ie,Le)),si=gl,ri=null;let i=0==jl.ori?Ie:Le,o=1==jl.ori?Ie:Le;if(0>Nn||0==Gt||Lt>en){n=oe.idx=null;for(let l=0;pl.length>l;l++){let e=Dt[l];null!=e&&A(e,-10,-10,Ie,Le)}Et&&oi(null,ai,!0,null==l&&Fi.setSeries),ie.live&&(te.fill(n),_t=!0)}else{let l,e,t;1==M&&(l=0==jl.ori?Nn:jn,e=fi(l,El),n=oe.idx=L(e,g[0],Lt,en),t=$l(g[0][n],jl,i,0));let s=-10,r=-10,u=0,a=0,f=!0,c="",h="";for(let l=2==M?1:0;pl.length>l;l++){let d=pl[l],p=te[l],m=null==p?null:1==M?g[l][p]:g[l][1][p],x=oe.dataIdx(k,l,n,e),w=null==x?null:1==M?g[l][x]:g[l][1][x];if(_t=_t||w!=m||x!=p,te[l]=x,l>0&&d.show){let e=null==x?-10:x==n?t:$l(1==M?g[0][x]:g[l][0][x],jl,i,0),p=null==w?-10:ql(w,1==M?vl[d.scale]:vl[d.facets[1].scale],o,0);if(Et&&null!=w){let e=1==jl.ori?Nn:jn,t=ol(Tt.dist(k,l,x,p,e));if(si>t){let n=Tt.bias;if(0!=n){let i=fi(e,d.scale),o=0>i?-1:1;o!=(0>w?-1:1)||(1==o?1==n?i>w:w>i:1==n?w>i:i>w)||(si=t,ri=l)}else si=t,ri=l}}if(_t||zt){let t,n;0==jl.ori?(t=e,n=p):(t=p,n=e);let i,o,d,m,g,x,w=!0,_=St.bbox;if(null!=_){w=!1;let e=_(k,l);d=e.left,m=e.top,i=e.width,o=e.height}else d=t,m=n,i=o=St.size(k,l);if(x=St.fill(k,l),g=St.stroke(k,l),zt)l!=ri||si>Tt.prox||(s=d,r=m,u=i,a=o,f=w,c=x,h=g);else{let e=Dt[l];null!=e&&(Pt[l]=d,At[l]=m,H(e,i,o,w),Y(e,x,g),A(e,ul(d),ul(m),Ie,Le))}}}}if(zt){let l=Tt.prox;if(_t||(null==ui?l>=si:si>l||ri!=ui)){let l=Dt[0];null!=l&&(Pt[0]=s,At[0]=r,H(l,u,a,f),Y(l,c,h),A(l,ul(s),ul(r),Ie,Le))}}}if(ei.show&&Xn)if(null!=l){let[e,t]=Fi.scales,[n,s]=Fi.match,[r,u]=l.cursor.sync.scales,a=l.cursor.drag;if(Qn=a._x,li=a._y,Qn||li){let a,f,c,h,d,{left:p,top:m,width:g,height:x}=l.select,w=l.scales[r].ori,_=l.posToVal,b=null!=e&&n(e,r),v=null!=t&&s(t,u);b&&Qn?(0==w?(a=p,f=g):(a=m,f=x),c=vl[e],h=$l(_(a,r),c,i,0),d=$l(_(a+f,r),c,i,0),di(al(h,d),ol(d-h))):di(0,i),v&&li?(1==w?(a=p,f=g):(a=m,f=x),c=vl[t],h=ql(_(a,u),c,o,0),d=ql(_(a+f,u),c,o,0),pi(al(h,d),ol(d-h))):pi(0,o)}else yi()}else{let l=ol(Ln-Fn),e=ol(On-Rn);if(1==jl.ori){let t=l;l=e,e=t}Qn=Zn.x&&l>=Zn.dist,li=Zn.y&&e>=Zn.dist;let t,n,s=Zn.uni;null!=s?Qn&&li&&(Qn=l>=s,li=e>=s,Qn||li||(e>l?li=!0:Qn=!0)):Zn.x&&Zn.y&&(Qn||li)&&(Qn=li=!0),Qn&&(0==jl.ori?(t=Gn,n=Nn):(t=In,n=jn),di(al(t,n),ol(n-t)),li||pi(0,o)),li&&(1==jl.ori?(t=Gn,n=Nn):(t=In,n=jn),pi(al(t,n),ol(n-t)),Qn||di(0,i)),Qn||li||(di(0,0),pi(0,0))}if(Zn._x=Qn,Zn._y=li,null==l){if(t){if(null!=Ri){let[l,e]=Fi.scales;Fi.values[0]=null!=l?fi(0==jl.ori?Nn:jn,l):null,Fi.values[1]=null!=e?fi(1==jl.ori?Nn:jn,e):null}Ii(f,k,Nn,jn,Ie,Le,n)}if(Et){let l=t&&Fi.setSeries,e=Tt.prox;null==ui?si>e||oi(ri,ai,!0,l):si>e?oi(null,ai,!0,l):ri!=ui&&oi(ri,ai,!0,l)}}_t&&(ie.idx=n,mi()),!1!==e&&Ci("setCursor")}k.setLegend=mi;let wi=null;function _i(l=!1){l?wi=null:(wi=el.getBoundingClientRect(),Ci("syncRect",wi))}function bi(l,e,t,n,i,o){oe._lock||Xn&&null!=l&&0==l.movementX&&0==l.movementY||(vi(l,e,t,n,i,o,0,!1,null!=l),null!=l?xi(null,!0,!0):xi(e,!0,!1))}function vi(l,e,t,n,i,o,s,r,u){if(null==wi&&_i(!1),Mt(l),null!=l)t=l.clientX-wi.left,n=l.clientY-wi.top;else{if(0>t||0>n)return Nn=-10,void(jn=-10);let[l,s]=Fi.scales,r=e.cursor.sync,[u,a]=r.values,[f,c]=r.scales,[h,d]=Fi.match,p=e.axes[0].side%2==1,m=0==jl.ori?Ie:Le,g=1==jl.ori?Ie:Le,x=p?o:i,w=p?i:o,_=p?n:t,b=p?t:n;if(t=null!=f?h(l,f)?C(u,vl[l],m,0):-10:m*(_/x),n=null!=c?d(s,c)?C(a,vl[s],g,0):-10:g*(b/w),1==jl.ori){let l=t;t=n,n=l}}!u||null!=e&&e.cursor.event.type!=f||(t>1&&Ie-1>t||(t=zl(t,Ie)),n>1&&Le-1>n||(n=zl(n,Le))),r?(Fn=t,Rn=n,[Gn,In]=oe.move(k,t,n)):(Nn=t,jn=n)}Object.defineProperty(k,"rect",{get:()=>(null==wi&&_i(!1),wi)});const ki={width:0,height:0,left:0,top:0};function yi(){ni(ki,!1)}let Mi,Si,Ti,Ei;function zi(l,e,t,n,i,o){Xn=!0,Qn=li=Zn._x=Zn._y=!1,vi(l,e,t,n,i,o,0,!0,!1),null!=l&&(Ce(h,b,Di,!1),Ii(c,k,Gn,In,Ie,Le,null));let{left:s,top:r,width:u,height:a}=ei;Mi=s,Si=r,Ti=u,Ei=a}function Di(l,e,t,n,i,o){Xn=Zn._x=Zn._y=!1,vi(l,e,t,n,i,o,0,!1,!0);let{left:s,top:r,width:u,height:a}=ei,f=u>0||a>0,c=Mi!=s||Si!=r||Ti!=u||Ei!=a;if(f&&c&&ni(ei),Zn.setScale&&f&&c){let l=s,e=u,t=r,n=a;if(1==jl.ori&&(l=r,e=a,t=s,n=u),Qn&&ii(El,fi(l,El),fi(l+e,El)),li)for(let l in vl){let e=vl[l];l!=El&&null==e.from&&e.min!=gl&&ii(l,fi(t+n,l),fi(t,l))}yi()}else oe.lock&&(oe._lock=!oe._lock,xi(e,!0,null!=l));null!=l&&(He(h,b),Ii(h,k,Nn,jn,Ie,Le,null))}function Pi(l){oe._lock||(Mt(l),xn(),yi(),null!=l&&Ii(m,k,Nn,jn,Ie,Le,null))}function Ai(){xl.forEach(ln),vt(k.width,k.height,!0)}G(x,v,Ai);const Wi={};Wi.mousedown=zi,Wi.mousemove=bi,Wi.mouseup=Di,Wi.dblclick=Pi,Wi.setSeries=(l,e,t,n)=>{-1!=(t=(0,Fi.match[2])(k,e,t))&&oi(t,n,!0,!1)},re&&(Ce(c,el,zi),Ce(f,el,bi),Ce(d,el,(l=>{Mt(l),_i(!1)})),Ce(p,el,(function(l){if(oe._lock)return;Mt(l);let e=Xn;if(Xn){let l,e,t=!0,n=!0,i=10;0==jl.ori?(l=Qn,e=li):(l=li,e=Qn),l&&e&&(t=i>=Nn||Nn>=Ie-i,n=i>=jn||jn>=Le-i),l&&t&&(Nn=Gn>Nn?0:Ie),e&&n&&(jn=In>jn?0:Le),xi(null,!0,!0),Xn=!1}Nn=-10,jn=-10,te.fill(null),xi(null,!0,!0),e&&(Xn=e)})),Ce(m,el,Pi),It.add(k),k.syncRect=_i);const Yi=k.hooks=u.hooks||{};function Ci(l,e,t){Vn?Bn.push([l,e,t]):l in Yi&&Yi[l].forEach((l=>{l.call(null,k,e,t)}))}(u.plugins||[]).forEach((l=>{for(let e in l.hooks)Yi[e]=(Yi[e]||[]).concat(l.hooks[e])}));const Hi=(l,e,t)=>t,Fi=Bl({key:null,setSeries:!1,filters:{pub:Ml,sub:Ml},scales:[El,pl[1]?pl[1].scale:null],match:[Sl,Sl,Hi],values:[null,null]},oe.sync);2==Fi.match.length&&Fi.match.push(Hi),oe.sync=Fi;const Ri=Fi.key,Gi=dt(Ri);function Ii(l,e,t,n,i,o,s){Fi.filters.pub(l,e,t,n,i,o,s)&&Gi.pub(l,e,t,n,i,o,s)}function Li(){Ci("init",u,g),gn(g||u.data,!1),Kl[El]?Kn(El,Kl[El]):xn(),wt=ei.show&&(ei.width>0||ei.height>0),xt=_t=!0,vt(u.width,u.height)}return Gi.sub(k),k.pub=function(l,e,t,n,i,o,s){Fi.filters.sub(l,e,t,n,i,o,s)&&Wi[l](null,e,t,n,i,o,s)},k.destroy=function(){Gi.unsub(k),It.delete(k),Pe.clear(),I(x,v,Ai),R.remove(),ce?.remove(),Ci("destroy")},pl.forEach(Yt),xl.forEach((function(l,e){if(l._show=l.show,l.show){let t=vl[l.scale];null==t&&(l.scale=l.side%2?pl[1].scale:El,t=vl[l.scale]);let n=t.time;l.size=bl(l.size),l.space=bl(l.space),l.rotate=bl(l.rotate),Gl(l.incrs)&&l.incrs.forEach((l=>{!Wl.has(l)&&Wl.set(l,Yl(l))})),l.incrs=bl(l.incrs||(2==t.distr?ae:n?1==hl?ke:Se:fe)),l.splits=bl(l.splits||(n&&1==t.distr?Ql:3==t.distr?Je:4==t.distr?qe:$e)),l.stroke=bl(l.stroke),l.grid.stroke=bl(l.grid.stroke),l.ticks.stroke=bl(l.ticks.stroke),l.border.stroke=bl(l.border.stroke);let i=l.values;l.values=Gl(i)&&!Gl(i[0])?bl(i):n?Gl(i)?De(Xl,ze(i,Zl)):Ll(i)?function(l,e){let t=ne(e);return(e,n)=>n.map((e=>t(l(e))))}(Xl,i):i||le:i||Be,l.filter=bl(l.filter||(3>t.distr||10!=t.log?3==t.distr&&2==t.log?tt:kl:et)),l.font=Qt(l.font),l.labelFont=Qt(l.labelFont),l._size=l.size(k,null,e,0),l._space=l._rotate=l._incrs=l._found=l._splits=l._values=null,l._size>0&&(Ct[e]=!0,l._el=D("u-axis",X))}})),_?_ instanceof HTMLElement?(_.appendChild(R),Li()):_(k,Li):Li(),k}en.assign=Bl,en.fmtNum=tl,en.rangeNum=Z,en.rangeLog=B,en.rangeAsinh=$,en.orient=pt,en.pxRatio=y,en.join=function(l,e){if(function(l){let e=l[0][0],t=e.length;for(let n=1;l.length>n;n++){let i=l[n][0];if(i.length!=t)return!1;if(i!=e)for(let l=0;t>l;l++)if(i[l]!=e[l])return!1}return!0}(l)){let e=l[0].slice();for(let t=1;l.length>t;t++)e.push(...l[t].slice(1));return function(l,e=100){const t=l.length;if(1>=t)return!0;let n=0,i=t-1;for(;i>=n&&null==l[n];)n++;for(;i>=n&&null==l[i];)i--;if(n>=i)return!0;const o=fl(1,sl((i-n+1)/e));for(let e=l[n],t=n+o;i>=t;t+=o){const n=l[t];if(null!=n){if(e>=n)return!1;e=n}}return!0}(e[0])||(e=function(l){let e=l[0],t=e.length,n=Array(t);for(let l=0;n.length>l;l++)n[l]=l;n.sort(((l,t)=>e[l]-e[t]));let i=[];for(let e=0;l.length>e;e++){let o=l[e],s=Array(t);for(let l=0;t>l;l++)s[l]=o[n[l]];i.push(s)}return i}(e)),e}let t=new Set;for(let e=0;l.length>e;e++){let n=l[e][0],i=n.length;for(let l=0;i>l;l++)t.add(n[l])}let n=[Array.from(t).sort(((l,e)=>l-e))],i=n[0].length,o=new Map;for(let l=0;i>l;l++)o.set(n[0][l],l);for(let t=0;l.length>t;t++){let s=l[t],r=s[0];for(let l=1;s.length>l;l++){let u=s[l],a=Array(i).fill(void 0),f=e?e[t][l]:1,c=[];for(let l=0;u.length>l;l++){let e=u[l],t=o.get(r[l]);null===e?0!=f&&(a[t]=e,2==f&&c.push(t)):a[t]=e}$l(a,c,i),n.push(a)}}return n},en.fmtDate=ne,en.tzDate=function(l,e){let t;return"UTC"==e||"Etc/UTC"==e?t=new Date(+l+6e4*l.getTimezoneOffset()):e==ie?t=l:(t=new Date(l.toLocaleString("en-US",{timeZone:e})),t.setMilliseconds(l.getMilliseconds())),t},en.sync=dt;{en.addGap=function(l,e,t){let n=l[l.length-1];n&&n[0]==e?n[1]=t:l.push([e,t])},en.clipGaps=wt;let l=en.paths={points:Wt};l.linear=Ft,l.stepped=function(l){const e=Q(l.align,1),t=Q(l.ascDesc,!1),n=Q(l.alignGaps,0),i=Q(l.extend,!1);return(l,o,s,r)=>pt(l,o,((u,a,f,c,h,d,p,m,g,x,w)=>{[s,r]=U(f,s,r);let _=u.pxRound,{left:b,width:v}=l.bbox,k=l=>_(d(l,c,x,m)),M=l=>_(p(l,h,w,g)),S=0==c.ori?Mt:St;const T={stroke:new Path2D,fill:null,clip:null,band:null,gaps:null,flags:1},E=T.stroke,z=c.dir*(0==c.ori?1:-1);let D=M(f[1==z?s:r]),P=k(a[1==z?s:r]),A=P,W=P;i&&-1==e&&(W=b,S(E,W,D)),S(E,P,D);for(let l=1==z?s:r;l>=s&&r>=l;l+=z){let t=f[l];if(null==t)continue;let n=k(a[l]),i=M(t);1==e?S(E,n,D):S(E,A,i),S(E,n,i),D=i,A=n}let Y=A;i&&1==e&&(Y=b+v,S(E,Y,D));let[C,H]=mt(l,o);if(null!=u.fill||0!=C){let e=T.fill=new Path2D(E),t=M(u.fillTo(l,o,u.min,u.max,C));S(e,Y,t),S(e,W,t)}if(!u.spanGaps){let i=[];i.push(..._t(a,f,s,r,z,k,n));let h=u.width*y/2,d=t||1==e?h:-h,p=t||-1==e?-h:h;i.forEach((l=>{l[0]+=d,l[1]+=p})),T.gaps=i=u.gaps(l,o,s,r,i),T.clip=wt(i,c.ori,m,g,x,w)}return 0!=H&&(T.band=2==H?[xt(l,o,s,r,E,-1),xt(l,o,s,r,E,1)]:xt(l,o,s,r,E,H)),T}))},l.bars=function(l){const e=Q((l=l||Hl).size,[.6,gl,1]),t=l.align||0,n=l.gap||0;let i=l.radius;i=null==i?[0,0]:"number"==typeof i?[i,0]:i;const o=bl(i),s=1-e[0],r=Q(e[1],gl),u=Q(e[2],1),a=Q(l.disp,Hl),f=Q(l.each,(()=>{})),{fill:c,stroke:h}=a;return(l,e,i,d)=>pt(l,e,((p,m,g,x,w,_,b,v,k,M,S)=>{let T,E,z=p.pxRound,D=t,P=n*y,A=r*y,W=u*y;0==x.ori?[T,E]=o(l,e):[E,T]=o(l,e);const Y=x.dir*(0==x.ori?1:-1);let C,H,F,R=0==x.ori?Tt:Et,G=0==x.ori?f:(l,e,t,n,i,o,s)=>{f(l,e,t,i,n,s,o)},I=Q(l.bands,Fl).find((l=>l.series[0]==e)),L=p.fillTo(l,e,p.min,p.max,null!=I?I.dir:0),O=z(b(L,w,S,k)),N=M,j=z(p.width*y),U=!1,V=null,B=null,$=null,J=null;null==c||0!=j&&null==h||(U=!0,V=c.values(l,e,i,d),B=new Map,new Set(V).forEach((l=>{null!=l&&B.set(l,new Path2D)})),j>0&&($=h.values(l,e,i,d),J=new Map,new Set($).forEach((l=>{null!=l&&J.set(l,new Path2D)}))));let{x0:q,size:K}=a;if(null!=q&&null!=K){D=1,m=q.values(l,e,i,d),2==q.unit&&(m=m.map((e=>l.posToVal(v+e*M,x.key,!0))));let t=K.values(l,e,i,d);H=2==K.unit?t[0]*M:_(t[0],x,M,v)-_(0,x,M,v),N=Rt(m,g,_,x,M,v,N),F=N-H+P}else N=Rt(m,g,_,x,M,v,N),F=N*s+P,H=N-F;1>F&&(F=0),H/2>j||(j=0),5>F&&(z=vl);let X=F>0;H=z(wl(N-F-(X?j:0),W,A)),C=(0==D?H/2:D==Y?0:H)-D*Y*((0==D?P/2:0)+(X?j/2:0));const Z={stroke:null,fill:null,clip:null,band:null,gaps:null,flags:0},ll=U?null:new Path2D;let el=null;if(null!=I)el=l.data[I.series[1]];else{let{y0:t,y1:n}=a;null!=t&&null!=n&&(g=n.values(l,e,i,d),el=t.values(l,e,i,d))}let tl=T*H,nl=E*H;for(let t=1==Y?i:d;t>=i&&d>=t;t+=Y){let n=g[t];if(null==n)continue;if(null!=el){let l=el[t]??0;if(n-l==0)continue;O=b(l,w,S,k)}let i=_(2!=x.distr||null!=a?m[t]:t,x,M,v),o=b(Q(n,L),w,S,k),s=z(i-C),r=z(fl(o,O)),u=z(al(o,O)),f=r-u;if(null!=n){let i=0>n?nl:tl,o=0>n?tl:nl;U?(j>0&&null!=$[t]&&R(J.get($[t]),s,u+sl(j/2),H,fl(0,f-j),i,o),null!=V[t]&&R(B.get(V[t]),s,u+sl(j/2),H,fl(0,f-j),i,o)):R(ll,s,u+sl(j/2),H,fl(0,f-j),i,o),G(l,e,t,s-j/2,u,H+j,f)}}return j>0?Z.stroke=U?J:ll:U||(Z._fill=0==p.width?p._fill:p._stroke??p._fill,Z.width=0),Z.fill=U?B:ll,Z}))},l.spline=function(l){return function(l,e){const t=Q(e?.alignGaps,0);return(e,n,i,o)=>pt(e,n,((s,r,u,a,f,c,h,d,p,m,g)=>{[i,o]=U(u,i,o);let x,w,_,b=s.pxRound,v=l=>b(c(l,a,m,d)),k=l=>b(h(l,f,g,p));0==a.ori?(x=kt,_=Mt,w=Pt):(x=yt,_=St,w=At);const y=a.dir*(0==a.ori?1:-1);let M=v(r[1==y?i:o]),S=M,T=[],E=[];for(let l=1==y?i:o;l>=i&&o>=l;l+=y)if(null!=u[l]){let e=v(r[l]);T.push(S=e),E.push(k(u[l]))}const z={stroke:l(T,E,x,_,w,b),fill:null,clip:null,band:null,gaps:null,flags:1},D=z.stroke;let[P,A]=mt(e,n);if(null!=s.fill||0!=P){let l=z.fill=new Path2D(D),t=k(s.fillTo(e,n,s.min,s.max,P));_(l,S,t),_(l,M,t)}if(!s.spanGaps){let l=[];l.push(..._t(r,u,i,o,y,v,t)),z.gaps=l=s.gaps(e,n,i,o,l),z.clip=wt(l,a.ori,d,p,m,g)}return 0!=A&&(z.band=2==A?[xt(e,n,i,o,D,-1),xt(e,n,i,o,D,1)]:xt(e,n,i,o,D,A)),z}))}(Gt,l)}}return en}(); diff --git a/webstats/static/uPlot.min.css b/webstats/static/uPlot.min.css new file mode 100644 index 0000000..655a65a --- /dev/null +++ b/webstats/static/uPlot.min.css @@ -0,0 +1,131 @@ +.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: min-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-axis { + position: absolute; +} +.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; + background-clip: padding-box !important; +} +.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; +} +.u-hz .u-cursor-x, +.u-vt .u-cursor-y { + height: 100%; + border-right: 1px dashed #607d8b; +} +.u-hz .u-cursor-y, +.u-vt .u-cursor-x { + width: 100%; + border-bottom: 1px dashed #607d8b; +} +.u-cursor-pt { + position: absolute; + top: 0; + left: 0; + border-radius: 50%; + border: 0 solid; + pointer-events: none; + will-change: transform; /*this has to be !important since we set inline "background" shorthand */ + background-clip: padding-box !important; +} +.u-axis.u-off, +.u-select.u-off, +.u-cursor-x.u-off, +.u-cursor-y.u-off, +.u-cursor-pt.u-off { + display: none; +} diff --git a/webstats/workforrobots.org b/webstats/workforrobots.org index 8cfeafe..9b7cde6 100644 --- a/webstats/workforrobots.org +++ b/webstats/workforrobots.org @@ -282,8 +282,6 @@ If you grep for the string /href="{{ .RelPermalink }}"/ over the entire git history, you find it appears up to today =954= times. It is surprising how =OpenAI= manage to request it =3= times that amount. - - #+begin_src sqlite :exports results SELECT replace(path, '|', '%7C') AS Page, @@ -341,6 +339,14 @@ ORDER BY LIMIT 20 #+end_src +my cgit instance using nginx is not working correctly. +On the home(landing/starting) page all links work. Say I click on sepo "prime". +that leads to the path /prime. That is how is written on the html links. + +Now that I'm in path '/prime' I want to go to the about page. +The generated link is '/prime/prime/about'. It is repeating the path, because "im on page /prime", the new pages should be relative "about", or absolute '/prime/about'. But it is concatenating '/prime', to where I want to go '/prime/about', leaving '/prime/prime/about' + +Where do I fix this? What would the nginx config look like. #+RESULTS[5d7adfe6034ad15603705e1501ba90fadc0a8f1c]: | tx MiB | count(*) | agents | path | |--------+----------+--------+----------------------------------------------------------------------------------------------------------| |