uplot-python 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uplot/__init__.py +14 -0
- uplot/color_picker.py +47 -0
- uplot/exceptions.py +12 -0
- uplot/generate_html.py +228 -0
- uplot/plot.py +36 -0
- uplot/uplot/__init__.py +1 -0
- uplot/uplot/uPlot.iife.js +5029 -0
- uplot/uplot/uPlot.min.css +1 -0
- uplot/uplot/uPlot.mousewheel.js +126 -0
- uplot/write_tmpfile.py +32 -0
- uplot_python-0.0.1.dist-info/LICENSE +201 -0
- uplot_python-0.0.1.dist-info/METADATA +31 -0
- uplot_python-0.0.1.dist-info/RECORD +14 -0
- uplot_python-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,5029 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2021, Leon Sorokin
|
|
3
|
+
* All rights reserved. (MIT Licensed)
|
|
4
|
+
*
|
|
5
|
+
* uPlot.js (μPlot)
|
|
6
|
+
* A small, fast chart for time series, lines, areas, ohlc & bars
|
|
7
|
+
* https://github.com/leeoniya/uPlot (v1.6.16)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var uPlot = (function () {
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const FEAT_TIME = true;
|
|
14
|
+
|
|
15
|
+
// binary search for index of closest value
|
|
16
|
+
function closestIdx(num, arr, lo, hi) {
|
|
17
|
+
let mid;
|
|
18
|
+
lo = lo || 0;
|
|
19
|
+
hi = hi || arr.length - 1;
|
|
20
|
+
let bitwise = hi <= 2147483647;
|
|
21
|
+
|
|
22
|
+
while (hi - lo > 1) {
|
|
23
|
+
mid = bitwise ? (lo + hi) >> 1 : floor((lo + hi) / 2);
|
|
24
|
+
|
|
25
|
+
if (arr[mid] < num)
|
|
26
|
+
lo = mid;
|
|
27
|
+
else
|
|
28
|
+
hi = mid;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (num - arr[lo] <= arr[hi] - num)
|
|
32
|
+
return lo;
|
|
33
|
+
|
|
34
|
+
return hi;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function nonNullIdx(data, _i0, _i1, dir) {
|
|
38
|
+
for (let i = dir == 1 ? _i0 : _i1; i >= _i0 && i <= _i1; i += dir) {
|
|
39
|
+
if (data[i] != null)
|
|
40
|
+
return i;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getMinMax(data, _i0, _i1, sorted) {
|
|
47
|
+
// console.log("getMinMax()");
|
|
48
|
+
|
|
49
|
+
let _min = inf;
|
|
50
|
+
let _max = -inf;
|
|
51
|
+
|
|
52
|
+
if (sorted == 1) {
|
|
53
|
+
_min = data[_i0];
|
|
54
|
+
_max = data[_i1];
|
|
55
|
+
}
|
|
56
|
+
else if (sorted == -1) {
|
|
57
|
+
_min = data[_i1];
|
|
58
|
+
_max = data[_i0];
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
for (let i = _i0; i <= _i1; i++) {
|
|
62
|
+
if (data[i] != null) {
|
|
63
|
+
_min = min(_min, data[i]);
|
|
64
|
+
_max = max(_max, data[i]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return [_min, _max];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getMinMaxLog(data, _i0, _i1) {
|
|
73
|
+
// console.log("getMinMax()");
|
|
74
|
+
|
|
75
|
+
let _min = inf;
|
|
76
|
+
let _max = -inf;
|
|
77
|
+
|
|
78
|
+
for (let i = _i0; i <= _i1; i++) {
|
|
79
|
+
if (data[i] > 0) {
|
|
80
|
+
_min = min(_min, data[i]);
|
|
81
|
+
_max = max(_max, data[i]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
_min == inf ? 1 : _min,
|
|
87
|
+
_max == -inf ? 10 : _max,
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const _fixedTuple = [0, 0];
|
|
92
|
+
|
|
93
|
+
function fixIncr(minIncr, maxIncr, minExp, maxExp) {
|
|
94
|
+
_fixedTuple[0] = minExp < 0 ? roundDec(minIncr, -minExp) : minIncr;
|
|
95
|
+
_fixedTuple[1] = maxExp < 0 ? roundDec(maxIncr, -maxExp) : maxIncr;
|
|
96
|
+
return _fixedTuple;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function rangeLog(min, max, base, fullMags) {
|
|
100
|
+
let minSign = sign(min);
|
|
101
|
+
|
|
102
|
+
let logFn = base == 10 ? log10 : log2;
|
|
103
|
+
|
|
104
|
+
if (min == max) {
|
|
105
|
+
if (minSign == -1) {
|
|
106
|
+
min *= base;
|
|
107
|
+
max /= base;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
min /= base;
|
|
111
|
+
max *= base;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let minExp, maxExp, minMaxIncrs;
|
|
116
|
+
|
|
117
|
+
if (fullMags) {
|
|
118
|
+
minExp = floor(logFn(min));
|
|
119
|
+
maxExp = ceil(logFn(max));
|
|
120
|
+
|
|
121
|
+
minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
|
|
122
|
+
|
|
123
|
+
min = minMaxIncrs[0];
|
|
124
|
+
max = minMaxIncrs[1];
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
minExp = floor(logFn(abs(min)));
|
|
128
|
+
maxExp = floor(logFn(abs(max)));
|
|
129
|
+
|
|
130
|
+
minMaxIncrs = fixIncr(pow(base, minExp), pow(base, maxExp), minExp, maxExp);
|
|
131
|
+
|
|
132
|
+
min = incrRoundDn(min, minMaxIncrs[0]);
|
|
133
|
+
max = incrRoundUp(max, minMaxIncrs[1]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return [min, max];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function rangeAsinh(min, max, base, fullMags) {
|
|
140
|
+
let minMax = rangeLog(min, max, base, fullMags);
|
|
141
|
+
|
|
142
|
+
if (min == 0)
|
|
143
|
+
minMax[0] = 0;
|
|
144
|
+
|
|
145
|
+
if (max == 0)
|
|
146
|
+
minMax[1] = 0;
|
|
147
|
+
|
|
148
|
+
return minMax;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const rangePad = 0.1;
|
|
152
|
+
|
|
153
|
+
const autoRangePart = {
|
|
154
|
+
mode: 3,
|
|
155
|
+
pad: rangePad,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const _eqRangePart = {
|
|
159
|
+
pad: 0,
|
|
160
|
+
soft: null,
|
|
161
|
+
mode: 0,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const _eqRange = {
|
|
165
|
+
min: _eqRangePart,
|
|
166
|
+
max: _eqRangePart,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
|
|
170
|
+
// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
|
|
171
|
+
function rangeNum(_min, _max, mult, extra) {
|
|
172
|
+
if (isObj(mult))
|
|
173
|
+
return _rangeNum(_min, _max, mult);
|
|
174
|
+
|
|
175
|
+
_eqRangePart.pad = mult;
|
|
176
|
+
_eqRangePart.soft = extra ? 0 : null;
|
|
177
|
+
_eqRangePart.mode = extra ? 3 : 0;
|
|
178
|
+
|
|
179
|
+
return _rangeNum(_min, _max, _eqRange);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// nullish coalesce
|
|
183
|
+
function ifNull(lh, rh) {
|
|
184
|
+
return lh == null ? rh : lh;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function _rangeNum(_min, _max, cfg) {
|
|
188
|
+
let cmin = cfg.min;
|
|
189
|
+
let cmax = cfg.max;
|
|
190
|
+
|
|
191
|
+
let padMin = ifNull(cmin.pad, 0);
|
|
192
|
+
let padMax = ifNull(cmax.pad, 0);
|
|
193
|
+
|
|
194
|
+
let hardMin = ifNull(cmin.hard, -inf);
|
|
195
|
+
let hardMax = ifNull(cmax.hard, inf);
|
|
196
|
+
|
|
197
|
+
let softMin = ifNull(cmin.soft, inf);
|
|
198
|
+
let softMax = ifNull(cmax.soft, -inf);
|
|
199
|
+
|
|
200
|
+
let softMinMode = ifNull(cmin.mode, 0);
|
|
201
|
+
let softMaxMode = ifNull(cmax.mode, 0);
|
|
202
|
+
|
|
203
|
+
let delta = _max - _min;
|
|
204
|
+
|
|
205
|
+
// this handles situations like 89.7, 89.69999999999999
|
|
206
|
+
// by assuming 0.001x deltas are precision errors
|
|
207
|
+
// if (delta > 0 && delta < abs(_max) / 1e3)
|
|
208
|
+
// delta = 0;
|
|
209
|
+
|
|
210
|
+
// treat data as flat if delta is less than 1 billionth
|
|
211
|
+
if (delta < 1e-9) {
|
|
212
|
+
delta = 0;
|
|
213
|
+
|
|
214
|
+
// if soft mode is 2 and all vals are flat at 0, avoid the 0.1 * 1e3 fallback
|
|
215
|
+
// this prevents 0,0,0 from ranging to -100,100 when softMin/softMax are -1,1
|
|
216
|
+
if (_min == 0 || _max == 0) {
|
|
217
|
+
delta = 1e-9;
|
|
218
|
+
|
|
219
|
+
if (softMinMode == 2 && softMin != inf)
|
|
220
|
+
padMin = 0;
|
|
221
|
+
|
|
222
|
+
if (softMaxMode == 2 && softMax != -inf)
|
|
223
|
+
padMax = 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let nonZeroDelta = delta || abs(_max) || 1e3;
|
|
228
|
+
let mag = log10(nonZeroDelta);
|
|
229
|
+
let base = pow(10, floor(mag));
|
|
230
|
+
|
|
231
|
+
let _padMin = nonZeroDelta * (delta == 0 ? (_min == 0 ? .1 : 1) : padMin);
|
|
232
|
+
let _newMin = roundDec(incrRoundDn(_min - _padMin, base/10), 9);
|
|
233
|
+
let _softMin = _min >= softMin && (softMinMode == 1 || softMinMode == 3 && _newMin <= softMin || softMinMode == 2 && _newMin >= softMin) ? softMin : inf;
|
|
234
|
+
let minLim = max(hardMin, _newMin < _softMin && _min >= _softMin ? _softMin : min(_softMin, _newMin));
|
|
235
|
+
|
|
236
|
+
let _padMax = nonZeroDelta * (delta == 0 ? (_max == 0 ? .1 : 1) : padMax);
|
|
237
|
+
let _newMax = roundDec(incrRoundUp(_max + _padMax, base/10), 9);
|
|
238
|
+
let _softMax = _max <= softMax && (softMaxMode == 1 || softMaxMode == 3 && _newMax >= softMax || softMaxMode == 2 && _newMax <= softMax) ? softMax : -inf;
|
|
239
|
+
let maxLim = min(hardMax, _newMax > _softMax && _max <= _softMax ? _softMax : max(_softMax, _newMax));
|
|
240
|
+
|
|
241
|
+
if (minLim == maxLim && minLim == 0)
|
|
242
|
+
maxLim = 100;
|
|
243
|
+
|
|
244
|
+
return [minLim, maxLim];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// alternative: https://stackoverflow.com/a/2254896
|
|
248
|
+
const fmtNum = new Intl.NumberFormat(navigator.language).format;
|
|
249
|
+
|
|
250
|
+
const M = Math;
|
|
251
|
+
|
|
252
|
+
const PI = M.PI;
|
|
253
|
+
const abs = M.abs;
|
|
254
|
+
const floor = M.floor;
|
|
255
|
+
const round = M.round;
|
|
256
|
+
const ceil = M.ceil;
|
|
257
|
+
const min = M.min;
|
|
258
|
+
const max = M.max;
|
|
259
|
+
const pow = M.pow;
|
|
260
|
+
const sign = M.sign;
|
|
261
|
+
const log10 = M.log10;
|
|
262
|
+
const log2 = M.log2;
|
|
263
|
+
const sinh = (v, linthresh = 1) => M.sinh(v / linthresh);
|
|
264
|
+
const asinh = (v, linthresh = 1) => M.asinh(v / linthresh);
|
|
265
|
+
|
|
266
|
+
const inf = Infinity;
|
|
267
|
+
|
|
268
|
+
function incrRound(num, incr) {
|
|
269
|
+
return round(num/incr)*incr;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function clamp(num, _min, _max) {
|
|
273
|
+
return min(max(num, _min), _max);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function fnOrSelf(v) {
|
|
277
|
+
return typeof v == "function" ? v : () => v;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const retArg0 = _0 => _0;
|
|
281
|
+
|
|
282
|
+
const retArg1 = (_0, _1) => _1;
|
|
283
|
+
|
|
284
|
+
const retNull = _ => null;
|
|
285
|
+
|
|
286
|
+
const retTrue = _ => true;
|
|
287
|
+
|
|
288
|
+
const retEq = (a, b) => a == b;
|
|
289
|
+
|
|
290
|
+
function incrRoundUp(num, incr) {
|
|
291
|
+
return ceil(num/incr)*incr;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function incrRoundDn(num, incr) {
|
|
295
|
+
return floor(num/incr)*incr;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function roundDec(val, dec) {
|
|
299
|
+
return round(val * (dec = 10**dec)) / dec;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const fixedDec = new Map();
|
|
303
|
+
|
|
304
|
+
function guessDec(num) {
|
|
305
|
+
return ((""+num).split(".")[1] || "").length;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function genIncrs(base, minExp, maxExp, mults) {
|
|
309
|
+
let incrs = [];
|
|
310
|
+
|
|
311
|
+
let multDec = mults.map(guessDec);
|
|
312
|
+
|
|
313
|
+
for (let exp = minExp; exp < maxExp; exp++) {
|
|
314
|
+
let expa = abs(exp);
|
|
315
|
+
let mag = roundDec(pow(base, exp), expa);
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < mults.length; i++) {
|
|
318
|
+
let _incr = mults[i] * mag;
|
|
319
|
+
let dec = (_incr >= 0 && exp >= 0 ? 0 : expa) + (exp >= multDec[i] ? 0 : multDec[i]);
|
|
320
|
+
let incr = roundDec(_incr, dec);
|
|
321
|
+
incrs.push(incr);
|
|
322
|
+
fixedDec.set(incr, dec);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return incrs;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//export const assign = Object.assign;
|
|
330
|
+
|
|
331
|
+
const EMPTY_OBJ = {};
|
|
332
|
+
const EMPTY_ARR = [];
|
|
333
|
+
|
|
334
|
+
const nullNullTuple = [null, null];
|
|
335
|
+
|
|
336
|
+
const isArr = Array.isArray;
|
|
337
|
+
|
|
338
|
+
function isStr(v) {
|
|
339
|
+
return typeof v == 'string';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function isObj(v) {
|
|
343
|
+
let is = false;
|
|
344
|
+
|
|
345
|
+
if (v != null) {
|
|
346
|
+
let c = v.constructor;
|
|
347
|
+
is = c == null || c == Object;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return is;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function fastIsObj(v) {
|
|
354
|
+
return v != null && typeof v == 'object';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function copy(o, _isObj = isObj) {
|
|
358
|
+
let out;
|
|
359
|
+
|
|
360
|
+
if (isArr(o)) {
|
|
361
|
+
let val = o.find(v => v != null);
|
|
362
|
+
|
|
363
|
+
if (isArr(val) || _isObj(val)) {
|
|
364
|
+
out = Array(o.length);
|
|
365
|
+
for (let i = 0; i < o.length; i++)
|
|
366
|
+
out[i] = copy(o[i], _isObj);
|
|
367
|
+
}
|
|
368
|
+
else
|
|
369
|
+
out = o.slice();
|
|
370
|
+
}
|
|
371
|
+
else if (_isObj(o)) {
|
|
372
|
+
out = {};
|
|
373
|
+
for (let k in o)
|
|
374
|
+
out[k] = copy(o[k], _isObj);
|
|
375
|
+
}
|
|
376
|
+
else
|
|
377
|
+
out = o;
|
|
378
|
+
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function assign(targ) {
|
|
383
|
+
let args = arguments;
|
|
384
|
+
|
|
385
|
+
for (let i = 1; i < args.length; i++) {
|
|
386
|
+
let src = args[i];
|
|
387
|
+
|
|
388
|
+
for (let key in src) {
|
|
389
|
+
if (isObj(targ[key]))
|
|
390
|
+
assign(targ[key], copy(src[key]));
|
|
391
|
+
else
|
|
392
|
+
targ[key] = copy(src[key]);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return targ;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// nullModes
|
|
400
|
+
const NULL_REMOVE = 0; // nulls are converted to undefined (e.g. for spanGaps: true)
|
|
401
|
+
const NULL_RETAIN = 1; // nulls are retained, with alignment artifacts set to undefined (default)
|
|
402
|
+
const NULL_EXPAND = 2; // nulls are expanded to include any adjacent alignment artifacts
|
|
403
|
+
|
|
404
|
+
// sets undefined values to nulls when adjacent to existing nulls (minesweeper)
|
|
405
|
+
function nullExpand(yVals, nullIdxs, alignedLen) {
|
|
406
|
+
for (let i = 0, xi, lastNullIdx = -1; i < nullIdxs.length; i++) {
|
|
407
|
+
let nullIdx = nullIdxs[i];
|
|
408
|
+
|
|
409
|
+
if (nullIdx > lastNullIdx) {
|
|
410
|
+
xi = nullIdx - 1;
|
|
411
|
+
while (xi >= 0 && yVals[xi] == null)
|
|
412
|
+
yVals[xi--] = null;
|
|
413
|
+
|
|
414
|
+
xi = nullIdx + 1;
|
|
415
|
+
while (xi < alignedLen && yVals[xi] == null)
|
|
416
|
+
yVals[lastNullIdx = xi++] = null;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// nullModes is a tables-matched array indicating how to treat nulls in each series
|
|
422
|
+
// output is sorted ASC on the joined field (table[0]) and duplicate join values are collapsed
|
|
423
|
+
function join(tables, nullModes) {
|
|
424
|
+
let xVals = new Set();
|
|
425
|
+
|
|
426
|
+
for (let ti = 0; ti < tables.length; ti++) {
|
|
427
|
+
let t = tables[ti];
|
|
428
|
+
let xs = t[0];
|
|
429
|
+
let len = xs.length;
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < len; i++)
|
|
432
|
+
xVals.add(xs[i]);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
let data = [Array.from(xVals).sort((a, b) => a - b)];
|
|
436
|
+
|
|
437
|
+
let alignedLen = data[0].length;
|
|
438
|
+
|
|
439
|
+
let xIdxs = new Map();
|
|
440
|
+
|
|
441
|
+
for (let i = 0; i < alignedLen; i++)
|
|
442
|
+
xIdxs.set(data[0][i], i);
|
|
443
|
+
|
|
444
|
+
for (let ti = 0; ti < tables.length; ti++) {
|
|
445
|
+
let t = tables[ti];
|
|
446
|
+
let xs = t[0];
|
|
447
|
+
|
|
448
|
+
for (let si = 1; si < t.length; si++) {
|
|
449
|
+
let ys = t[si];
|
|
450
|
+
|
|
451
|
+
let yVals = Array(alignedLen).fill(undefined);
|
|
452
|
+
|
|
453
|
+
let nullMode = nullModes ? nullModes[ti][si] : NULL_RETAIN;
|
|
454
|
+
|
|
455
|
+
let nullIdxs = [];
|
|
456
|
+
|
|
457
|
+
for (let i = 0; i < ys.length; i++) {
|
|
458
|
+
let yVal = ys[i];
|
|
459
|
+
let alignedIdx = xIdxs.get(xs[i]);
|
|
460
|
+
|
|
461
|
+
if (yVal === null) {
|
|
462
|
+
if (nullMode != NULL_REMOVE) {
|
|
463
|
+
yVals[alignedIdx] = yVal;
|
|
464
|
+
|
|
465
|
+
if (nullMode == NULL_EXPAND)
|
|
466
|
+
nullIdxs.push(alignedIdx);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
else
|
|
470
|
+
yVals[alignedIdx] = yVal;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
nullExpand(yVals, nullIdxs, alignedLen);
|
|
474
|
+
|
|
475
|
+
data.push(yVals);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return data;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const microTask = typeof queueMicrotask == "undefined" ? fn => Promise.resolve().then(fn) : queueMicrotask;
|
|
483
|
+
|
|
484
|
+
const WIDTH = "width";
|
|
485
|
+
const HEIGHT = "height";
|
|
486
|
+
const TOP = "top";
|
|
487
|
+
const BOTTOM = "bottom";
|
|
488
|
+
const LEFT = "left";
|
|
489
|
+
const RIGHT = "right";
|
|
490
|
+
const hexBlack = "#000";
|
|
491
|
+
const transparent = hexBlack + "0";
|
|
492
|
+
|
|
493
|
+
const mousemove = "mousemove";
|
|
494
|
+
const mousedown = "mousedown";
|
|
495
|
+
const mouseup = "mouseup";
|
|
496
|
+
const mouseenter = "mouseenter";
|
|
497
|
+
const mouseleave = "mouseleave";
|
|
498
|
+
const dblclick = "dblclick";
|
|
499
|
+
const resize = "resize";
|
|
500
|
+
const scroll = "scroll";
|
|
501
|
+
|
|
502
|
+
const change = "change";
|
|
503
|
+
const dppxchange = "dppxchange";
|
|
504
|
+
|
|
505
|
+
const pre = "u-";
|
|
506
|
+
|
|
507
|
+
const UPLOT = "uplot";
|
|
508
|
+
const ORI_HZ = pre + "hz";
|
|
509
|
+
const ORI_VT = pre + "vt";
|
|
510
|
+
const TITLE = pre + "title";
|
|
511
|
+
const WRAP = pre + "wrap";
|
|
512
|
+
const UNDER = pre + "under";
|
|
513
|
+
const OVER = pre + "over";
|
|
514
|
+
const OFF = pre + "off";
|
|
515
|
+
const SELECT = pre + "select";
|
|
516
|
+
const CURSOR_X = pre + "cursor-x";
|
|
517
|
+
const CURSOR_Y = pre + "cursor-y";
|
|
518
|
+
const CURSOR_PT = pre + "cursor-pt";
|
|
519
|
+
const LEGEND = pre + "legend";
|
|
520
|
+
const LEGEND_LIVE = pre + "live";
|
|
521
|
+
const LEGEND_INLINE = pre + "inline";
|
|
522
|
+
const LEGEND_THEAD = pre + "thead";
|
|
523
|
+
const LEGEND_SERIES = pre + "series";
|
|
524
|
+
const LEGEND_MARKER = pre + "marker";
|
|
525
|
+
const LEGEND_LABEL = pre + "label";
|
|
526
|
+
const LEGEND_VALUE = pre + "value";
|
|
527
|
+
|
|
528
|
+
const doc = document;
|
|
529
|
+
const win = window;
|
|
530
|
+
let pxRatio;
|
|
531
|
+
|
|
532
|
+
let query;
|
|
533
|
+
|
|
534
|
+
function setPxRatio() {
|
|
535
|
+
let _pxRatio = devicePixelRatio;
|
|
536
|
+
|
|
537
|
+
// during print preview, Chrome fires off these dppx queries even without changes
|
|
538
|
+
if (pxRatio != _pxRatio) {
|
|
539
|
+
pxRatio = _pxRatio;
|
|
540
|
+
|
|
541
|
+
query && off(change, query, setPxRatio);
|
|
542
|
+
query = matchMedia(`(min-resolution: ${pxRatio - 0.001}dppx) and (max-resolution: ${pxRatio + 0.001}dppx)`);
|
|
543
|
+
on(change, query, setPxRatio);
|
|
544
|
+
|
|
545
|
+
win.dispatchEvent(new CustomEvent(dppxchange));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function addClass(el, c) {
|
|
550
|
+
if (c != null) {
|
|
551
|
+
let cl = el.classList;
|
|
552
|
+
!cl.contains(c) && cl.add(c);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function remClass(el, c) {
|
|
557
|
+
let cl = el.classList;
|
|
558
|
+
cl.contains(c) && cl.remove(c);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function setStylePx(el, name, value) {
|
|
562
|
+
el.style[name] = value + "px";
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function placeTag(tag, cls, targ, refEl) {
|
|
566
|
+
let el = doc.createElement(tag);
|
|
567
|
+
|
|
568
|
+
if (cls != null)
|
|
569
|
+
addClass(el, cls);
|
|
570
|
+
|
|
571
|
+
if (targ != null)
|
|
572
|
+
targ.insertBefore(el, refEl);
|
|
573
|
+
|
|
574
|
+
return el;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function placeDiv(cls, targ) {
|
|
578
|
+
return placeTag("div", cls, targ);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const xformCache = new WeakMap();
|
|
582
|
+
|
|
583
|
+
function elTrans(el, xPos, yPos, xMax, yMax) {
|
|
584
|
+
let xform = "translate(" + xPos + "px," + yPos + "px)";
|
|
585
|
+
let xformOld = xformCache.get(el);
|
|
586
|
+
|
|
587
|
+
if (xform != xformOld) {
|
|
588
|
+
el.style.transform = xform;
|
|
589
|
+
xformCache.set(el, xform);
|
|
590
|
+
|
|
591
|
+
if (xPos < 0 || yPos < 0 || xPos > xMax || yPos > yMax)
|
|
592
|
+
addClass(el, OFF);
|
|
593
|
+
else
|
|
594
|
+
remClass(el, OFF);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const colorCache = new WeakMap();
|
|
599
|
+
|
|
600
|
+
function elColor(el, background, borderColor) {
|
|
601
|
+
let newColor = background + borderColor;
|
|
602
|
+
let oldColor = colorCache.get(el);
|
|
603
|
+
|
|
604
|
+
if (newColor != oldColor) {
|
|
605
|
+
colorCache.set(el, newColor);
|
|
606
|
+
el.style.background = background;
|
|
607
|
+
el.style.borderColor = borderColor;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const sizeCache = new WeakMap();
|
|
612
|
+
|
|
613
|
+
function elSize(el, newSize) {
|
|
614
|
+
let oldSize = sizeCache.get(el);
|
|
615
|
+
|
|
616
|
+
if (newSize != oldSize) {
|
|
617
|
+
sizeCache.set(el, newSize);
|
|
618
|
+
el.style.height = el.style.width = newSize + "px";
|
|
619
|
+
el.style.marginLeft = el.style.marginTop = -newSize/2 + "px";
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const evOpts = {passive: true};
|
|
624
|
+
const evOpts2 = assign({capture: true}, evOpts);
|
|
625
|
+
|
|
626
|
+
function on(ev, el, cb, capt) {
|
|
627
|
+
el.addEventListener(ev, cb, capt ? evOpts2 : evOpts);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function off(ev, el, cb, capt) {
|
|
631
|
+
el.removeEventListener(ev, cb, capt ? evOpts2 : evOpts);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
setPxRatio();
|
|
635
|
+
|
|
636
|
+
const months = [
|
|
637
|
+
"January",
|
|
638
|
+
"February",
|
|
639
|
+
"March",
|
|
640
|
+
"April",
|
|
641
|
+
"May",
|
|
642
|
+
"June",
|
|
643
|
+
"July",
|
|
644
|
+
"August",
|
|
645
|
+
"September",
|
|
646
|
+
"October",
|
|
647
|
+
"November",
|
|
648
|
+
"December",
|
|
649
|
+
];
|
|
650
|
+
|
|
651
|
+
const days = [
|
|
652
|
+
"Sunday",
|
|
653
|
+
"Monday",
|
|
654
|
+
"Tuesday",
|
|
655
|
+
"Wednesday",
|
|
656
|
+
"Thursday",
|
|
657
|
+
"Friday",
|
|
658
|
+
"Saturday",
|
|
659
|
+
];
|
|
660
|
+
|
|
661
|
+
function slice3(str) {
|
|
662
|
+
return str.slice(0, 3);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const days3 = days.map(slice3);
|
|
666
|
+
|
|
667
|
+
const months3 = months.map(slice3);
|
|
668
|
+
|
|
669
|
+
const engNames = {
|
|
670
|
+
MMMM: months,
|
|
671
|
+
MMM: months3,
|
|
672
|
+
WWWW: days,
|
|
673
|
+
WWW: days3,
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
function zeroPad2(int) {
|
|
677
|
+
return (int < 10 ? '0' : '') + int;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function zeroPad3(int) {
|
|
681
|
+
return (int < 10 ? '00' : int < 100 ? '0' : '') + int;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/*
|
|
685
|
+
function suffix(int) {
|
|
686
|
+
let mod10 = int % 10;
|
|
687
|
+
|
|
688
|
+
return int + (
|
|
689
|
+
mod10 == 1 && int != 11 ? "st" :
|
|
690
|
+
mod10 == 2 && int != 12 ? "nd" :
|
|
691
|
+
mod10 == 3 && int != 13 ? "rd" : "th"
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
*/
|
|
695
|
+
|
|
696
|
+
const subs = {
|
|
697
|
+
// 2019
|
|
698
|
+
YYYY: d => d.getFullYear(),
|
|
699
|
+
// 19
|
|
700
|
+
YY: d => (d.getFullYear()+'').slice(2),
|
|
701
|
+
// July
|
|
702
|
+
MMMM: (d, names) => names.MMMM[d.getMonth()],
|
|
703
|
+
// Jul
|
|
704
|
+
MMM: (d, names) => names.MMM[d.getMonth()],
|
|
705
|
+
// 07
|
|
706
|
+
MM: d => zeroPad2(d.getMonth()+1),
|
|
707
|
+
// 7
|
|
708
|
+
M: d => d.getMonth()+1,
|
|
709
|
+
// 09
|
|
710
|
+
DD: d => zeroPad2(d.getDate()),
|
|
711
|
+
// 9
|
|
712
|
+
D: d => d.getDate(),
|
|
713
|
+
// Monday
|
|
714
|
+
WWWW: (d, names) => names.WWWW[d.getDay()],
|
|
715
|
+
// Mon
|
|
716
|
+
WWW: (d, names) => names.WWW[d.getDay()],
|
|
717
|
+
// 03
|
|
718
|
+
HH: d => zeroPad2(d.getHours()),
|
|
719
|
+
// 3
|
|
720
|
+
H: d => d.getHours(),
|
|
721
|
+
// 9 (12hr, unpadded)
|
|
722
|
+
h: d => {let h = d.getHours(); return h == 0 ? 12 : h > 12 ? h - 12 : h;},
|
|
723
|
+
// AM
|
|
724
|
+
AA: d => d.getHours() >= 12 ? 'PM' : 'AM',
|
|
725
|
+
// am
|
|
726
|
+
aa: d => d.getHours() >= 12 ? 'pm' : 'am',
|
|
727
|
+
// a
|
|
728
|
+
a: d => d.getHours() >= 12 ? 'p' : 'a',
|
|
729
|
+
// 09
|
|
730
|
+
mm: d => zeroPad2(d.getMinutes()),
|
|
731
|
+
// 9
|
|
732
|
+
m: d => d.getMinutes(),
|
|
733
|
+
// 09
|
|
734
|
+
ss: d => zeroPad2(d.getSeconds()),
|
|
735
|
+
// 9
|
|
736
|
+
s: d => d.getSeconds(),
|
|
737
|
+
// 374
|
|
738
|
+
fff: d => zeroPad3(d.getMilliseconds()),
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
function fmtDate(tpl, names) {
|
|
742
|
+
names = names || engNames;
|
|
743
|
+
let parts = [];
|
|
744
|
+
|
|
745
|
+
let R = /\{([a-z]+)\}|[^{]+/gi, m;
|
|
746
|
+
|
|
747
|
+
while (m = R.exec(tpl))
|
|
748
|
+
parts.push(m[0][0] == '{' ? subs[m[1]] : m[0]);
|
|
749
|
+
|
|
750
|
+
return d => {
|
|
751
|
+
let out = '';
|
|
752
|
+
|
|
753
|
+
for (let i = 0; i < parts.length; i++)
|
|
754
|
+
out += typeof parts[i] == "string" ? parts[i] : parts[i](d, names);
|
|
755
|
+
|
|
756
|
+
return out;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const localTz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
761
|
+
|
|
762
|
+
// https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone/53652131#53652131
|
|
763
|
+
function tzDate(date, tz) {
|
|
764
|
+
let date2;
|
|
765
|
+
|
|
766
|
+
// perf optimization
|
|
767
|
+
if (tz == 'UTC' || tz == 'Etc/UTC')
|
|
768
|
+
date2 = new Date(+date + date.getTimezoneOffset() * 6e4);
|
|
769
|
+
else if (tz == localTz)
|
|
770
|
+
date2 = date;
|
|
771
|
+
else {
|
|
772
|
+
date2 = new Date(date.toLocaleString('en-US', {timeZone: tz}));
|
|
773
|
+
date2.setMilliseconds(date.getMilliseconds());
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return date2;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
//export const series = [];
|
|
780
|
+
|
|
781
|
+
// default formatters:
|
|
782
|
+
|
|
783
|
+
const onlyWhole = v => v % 1 == 0;
|
|
784
|
+
|
|
785
|
+
const allMults = [1,2,2.5,5];
|
|
786
|
+
|
|
787
|
+
// ...0.01, 0.02, 0.025, 0.05, 0.1, 0.2, 0.25, 0.5
|
|
788
|
+
const decIncrs = genIncrs(10, -16, 0, allMults);
|
|
789
|
+
|
|
790
|
+
// 1, 2, 2.5, 5, 10, 20, 25, 50...
|
|
791
|
+
const oneIncrs = genIncrs(10, 0, 16, allMults);
|
|
792
|
+
|
|
793
|
+
// 1, 2, 5, 10, 20, 25, 50...
|
|
794
|
+
const wholeIncrs = oneIncrs.filter(onlyWhole);
|
|
795
|
+
|
|
796
|
+
const numIncrs = decIncrs.concat(oneIncrs);
|
|
797
|
+
|
|
798
|
+
const NL = "\n";
|
|
799
|
+
|
|
800
|
+
const yyyy = "{YYYY}";
|
|
801
|
+
const NLyyyy = NL + yyyy;
|
|
802
|
+
const md = "{M}/{D}";
|
|
803
|
+
const NLmd = NL + md;
|
|
804
|
+
const NLmdyy = NLmd + "/{YY}";
|
|
805
|
+
|
|
806
|
+
const aa = "{aa}";
|
|
807
|
+
const hmm = "{h}:{mm}";
|
|
808
|
+
const hmmaa = hmm + aa;
|
|
809
|
+
const NLhmmaa = NL + hmmaa;
|
|
810
|
+
const ss = ":{ss}";
|
|
811
|
+
|
|
812
|
+
const _ = null;
|
|
813
|
+
|
|
814
|
+
function genTimeStuffs(ms) {
|
|
815
|
+
let s = ms * 1e3,
|
|
816
|
+
m = s * 60,
|
|
817
|
+
h = m * 60,
|
|
818
|
+
d = h * 24,
|
|
819
|
+
mo = d * 30,
|
|
820
|
+
y = d * 365;
|
|
821
|
+
|
|
822
|
+
// min of 1e-3 prevents setting a temporal x ticks too small since Date objects cannot advance ticks smaller than 1ms
|
|
823
|
+
let subSecIncrs = ms == 1 ? genIncrs(10, 0, 3, allMults).filter(onlyWhole) : genIncrs(10, -3, 0, allMults);
|
|
824
|
+
|
|
825
|
+
let timeIncrs = subSecIncrs.concat([
|
|
826
|
+
// minute divisors (# of secs)
|
|
827
|
+
s,
|
|
828
|
+
s * 5,
|
|
829
|
+
s * 10,
|
|
830
|
+
s * 15,
|
|
831
|
+
s * 30,
|
|
832
|
+
// hour divisors (# of mins)
|
|
833
|
+
m,
|
|
834
|
+
m * 5,
|
|
835
|
+
m * 10,
|
|
836
|
+
m * 15,
|
|
837
|
+
m * 30,
|
|
838
|
+
// day divisors (# of hrs)
|
|
839
|
+
h,
|
|
840
|
+
h * 2,
|
|
841
|
+
h * 3,
|
|
842
|
+
h * 4,
|
|
843
|
+
h * 6,
|
|
844
|
+
h * 8,
|
|
845
|
+
h * 12,
|
|
846
|
+
// month divisors TODO: need more?
|
|
847
|
+
d,
|
|
848
|
+
d * 2,
|
|
849
|
+
d * 3,
|
|
850
|
+
d * 4,
|
|
851
|
+
d * 5,
|
|
852
|
+
d * 6,
|
|
853
|
+
d * 7,
|
|
854
|
+
d * 8,
|
|
855
|
+
d * 9,
|
|
856
|
+
d * 10,
|
|
857
|
+
d * 15,
|
|
858
|
+
// year divisors (# months, approx)
|
|
859
|
+
mo,
|
|
860
|
+
mo * 2,
|
|
861
|
+
mo * 3,
|
|
862
|
+
mo * 4,
|
|
863
|
+
mo * 6,
|
|
864
|
+
// century divisors
|
|
865
|
+
y,
|
|
866
|
+
y * 2,
|
|
867
|
+
y * 5,
|
|
868
|
+
y * 10,
|
|
869
|
+
y * 25,
|
|
870
|
+
y * 50,
|
|
871
|
+
y * 100,
|
|
872
|
+
]);
|
|
873
|
+
|
|
874
|
+
// [0]: minimum num secs in the tick incr
|
|
875
|
+
// [1]: default tick format
|
|
876
|
+
// [2-7]: rollover tick formats
|
|
877
|
+
// [8]: mode: 0: replace [1] -> [2-7], 1: concat [1] + [2-7]
|
|
878
|
+
const _timeAxisStamps = [
|
|
879
|
+
// tick incr default year month day hour min sec mode
|
|
880
|
+
[y, yyyy, _, _, _, _, _, _, 1],
|
|
881
|
+
[d * 28, "{MMM}", NLyyyy, _, _, _, _, _, 1],
|
|
882
|
+
[d, md, NLyyyy, _, _, _, _, _, 1],
|
|
883
|
+
[h, "{h}" + aa, NLmdyy, _, NLmd, _, _, _, 1],
|
|
884
|
+
[m, hmmaa, NLmdyy, _, NLmd, _, _, _, 1],
|
|
885
|
+
[s, ss, NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
|
|
886
|
+
[ms, ss + ".{fff}", NLmdyy + " " + hmmaa, _, NLmd + " " + hmmaa, _, NLhmmaa, _, 1],
|
|
887
|
+
];
|
|
888
|
+
|
|
889
|
+
// the ensures that axis ticks, values & grid are aligned to logical temporal breakpoints and not an arbitrary timestamp
|
|
890
|
+
// https://www.timeanddate.com/time/dst/
|
|
891
|
+
// https://www.timeanddate.com/time/dst/2019.html
|
|
892
|
+
// https://www.epochconverter.com/timezones
|
|
893
|
+
function timeAxisSplits(tzDate) {
|
|
894
|
+
return (self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace) => {
|
|
895
|
+
let splits = [];
|
|
896
|
+
let isYr = foundIncr >= y;
|
|
897
|
+
let isMo = foundIncr >= mo && foundIncr < y;
|
|
898
|
+
|
|
899
|
+
// get the timezone-adjusted date
|
|
900
|
+
let minDate = tzDate(scaleMin);
|
|
901
|
+
let minDateTs = roundDec(minDate * ms, 3);
|
|
902
|
+
|
|
903
|
+
// get ts of 12am (this lands us at or before the original scaleMin)
|
|
904
|
+
let minMin = mkDate(minDate.getFullYear(), isYr ? 0 : minDate.getMonth(), isMo || isYr ? 1 : minDate.getDate());
|
|
905
|
+
let minMinTs = roundDec(minMin * ms, 3);
|
|
906
|
+
|
|
907
|
+
if (isMo || isYr) {
|
|
908
|
+
let moIncr = isMo ? foundIncr / mo : 0;
|
|
909
|
+
let yrIncr = isYr ? foundIncr / y : 0;
|
|
910
|
+
// let tzOffset = scaleMin - minDateTs; // needed?
|
|
911
|
+
let split = minDateTs == minMinTs ? minDateTs : roundDec(mkDate(minMin.getFullYear() + yrIncr, minMin.getMonth() + moIncr, 1) * ms, 3);
|
|
912
|
+
let splitDate = new Date(round(split / ms));
|
|
913
|
+
let baseYear = splitDate.getFullYear();
|
|
914
|
+
let baseMonth = splitDate.getMonth();
|
|
915
|
+
|
|
916
|
+
for (let i = 0; split <= scaleMax; i++) {
|
|
917
|
+
let next = mkDate(baseYear + yrIncr * i, baseMonth + moIncr * i, 1);
|
|
918
|
+
let offs = next - tzDate(roundDec(next * ms, 3));
|
|
919
|
+
|
|
920
|
+
split = roundDec((+next + offs) * ms, 3);
|
|
921
|
+
|
|
922
|
+
if (split <= scaleMax)
|
|
923
|
+
splits.push(split);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
let incr0 = foundIncr >= d ? d : foundIncr;
|
|
928
|
+
let tzOffset = floor(scaleMin) - floor(minDateTs);
|
|
929
|
+
let split = minMinTs + tzOffset + incrRoundUp(minDateTs - minMinTs, incr0);
|
|
930
|
+
splits.push(split);
|
|
931
|
+
|
|
932
|
+
let date0 = tzDate(split);
|
|
933
|
+
|
|
934
|
+
let prevHour = date0.getHours() + (date0.getMinutes() / m) + (date0.getSeconds() / h);
|
|
935
|
+
let incrHours = foundIncr / h;
|
|
936
|
+
|
|
937
|
+
let minSpace = self.axes[axisIdx]._space;
|
|
938
|
+
let pctSpace = foundSpace / minSpace;
|
|
939
|
+
|
|
940
|
+
while (1) {
|
|
941
|
+
split = roundDec(split + foundIncr, ms == 1 ? 0 : 3);
|
|
942
|
+
|
|
943
|
+
if (split > scaleMax)
|
|
944
|
+
break;
|
|
945
|
+
|
|
946
|
+
if (incrHours > 1) {
|
|
947
|
+
let expectedHour = floor(roundDec(prevHour + incrHours, 6)) % 24;
|
|
948
|
+
let splitDate = tzDate(split);
|
|
949
|
+
let actualHour = splitDate.getHours();
|
|
950
|
+
|
|
951
|
+
let dstShift = actualHour - expectedHour;
|
|
952
|
+
|
|
953
|
+
if (dstShift > 1)
|
|
954
|
+
dstShift = -1;
|
|
955
|
+
|
|
956
|
+
split -= dstShift * h;
|
|
957
|
+
|
|
958
|
+
prevHour = (prevHour + incrHours) % 24;
|
|
959
|
+
|
|
960
|
+
// add a tick only if it's further than 70% of the min allowed label spacing
|
|
961
|
+
let prevSplit = splits[splits.length - 1];
|
|
962
|
+
let pctIncr = roundDec((split - prevSplit) / foundIncr, 3);
|
|
963
|
+
|
|
964
|
+
if (pctIncr * pctSpace >= .7)
|
|
965
|
+
splits.push(split);
|
|
966
|
+
}
|
|
967
|
+
else
|
|
968
|
+
splits.push(split);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return splits;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
return [
|
|
977
|
+
timeIncrs,
|
|
978
|
+
_timeAxisStamps,
|
|
979
|
+
timeAxisSplits,
|
|
980
|
+
];
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const [ timeIncrsMs, _timeAxisStampsMs, timeAxisSplitsMs ] = genTimeStuffs(1);
|
|
984
|
+
const [ timeIncrsS, _timeAxisStampsS, timeAxisSplitsS ] = genTimeStuffs(1e-3);
|
|
985
|
+
|
|
986
|
+
// base 2
|
|
987
|
+
genIncrs(2, -53, 53, [1]);
|
|
988
|
+
|
|
989
|
+
/*
|
|
990
|
+
console.log({
|
|
991
|
+
decIncrs,
|
|
992
|
+
oneIncrs,
|
|
993
|
+
wholeIncrs,
|
|
994
|
+
numIncrs,
|
|
995
|
+
timeIncrs,
|
|
996
|
+
fixedDec,
|
|
997
|
+
});
|
|
998
|
+
*/
|
|
999
|
+
|
|
1000
|
+
function timeAxisStamps(stampCfg, fmtDate) {
|
|
1001
|
+
return stampCfg.map(s => s.map((v, i) =>
|
|
1002
|
+
i == 0 || i == 8 || v == null ? v : fmtDate(i == 1 || s[8] == 0 ? v : s[1] + v)
|
|
1003
|
+
));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// TODO: will need to accept spaces[] and pull incr into the loop when grid will be non-uniform, eg for log scales.
|
|
1007
|
+
// currently we ignore this for months since they're *nearly* uniform and the added complexity is not worth it
|
|
1008
|
+
function timeAxisVals(tzDate, stamps) {
|
|
1009
|
+
return (self, splits, axisIdx, foundSpace, foundIncr) => {
|
|
1010
|
+
let s = stamps.find(s => foundIncr >= s[0]) || stamps[stamps.length - 1];
|
|
1011
|
+
|
|
1012
|
+
// these track boundaries when a full label is needed again
|
|
1013
|
+
let prevYear;
|
|
1014
|
+
let prevMnth;
|
|
1015
|
+
let prevDate;
|
|
1016
|
+
let prevHour;
|
|
1017
|
+
let prevMins;
|
|
1018
|
+
let prevSecs;
|
|
1019
|
+
|
|
1020
|
+
return splits.map(split => {
|
|
1021
|
+
let date = tzDate(split);
|
|
1022
|
+
|
|
1023
|
+
let newYear = date.getFullYear();
|
|
1024
|
+
let newMnth = date.getMonth();
|
|
1025
|
+
let newDate = date.getDate();
|
|
1026
|
+
let newHour = date.getHours();
|
|
1027
|
+
let newMins = date.getMinutes();
|
|
1028
|
+
let newSecs = date.getSeconds();
|
|
1029
|
+
|
|
1030
|
+
let stamp = (
|
|
1031
|
+
newYear != prevYear && s[2] ||
|
|
1032
|
+
newMnth != prevMnth && s[3] ||
|
|
1033
|
+
newDate != prevDate && s[4] ||
|
|
1034
|
+
newHour != prevHour && s[5] ||
|
|
1035
|
+
newMins != prevMins && s[6] ||
|
|
1036
|
+
newSecs != prevSecs && s[7] ||
|
|
1037
|
+
s[1]
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
prevYear = newYear;
|
|
1041
|
+
prevMnth = newMnth;
|
|
1042
|
+
prevDate = newDate;
|
|
1043
|
+
prevHour = newHour;
|
|
1044
|
+
prevMins = newMins;
|
|
1045
|
+
prevSecs = newSecs;
|
|
1046
|
+
|
|
1047
|
+
return stamp(date);
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// for when axis.values is defined as a static fmtDate template string
|
|
1053
|
+
function timeAxisVal(tzDate, dateTpl) {
|
|
1054
|
+
let stamp = fmtDate(dateTpl);
|
|
1055
|
+
return (self, splits, axisIdx, foundSpace, foundIncr) => splits.map(split => stamp(tzDate(split)));
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function mkDate(y, m, d) {
|
|
1059
|
+
return new Date(y, m, d);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function timeSeriesStamp(stampCfg, fmtDate) {
|
|
1063
|
+
return fmtDate(stampCfg);
|
|
1064
|
+
}
|
|
1065
|
+
const _timeSeriesStamp = '{YYYY}-{MM}-{DD} {h}:{mm}{aa}';
|
|
1066
|
+
|
|
1067
|
+
function timeSeriesVal(tzDate, stamp) {
|
|
1068
|
+
return (self, val) => stamp(tzDate(val));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function legendStroke(self, seriesIdx) {
|
|
1072
|
+
let s = self.series[seriesIdx];
|
|
1073
|
+
return s.width ? s.stroke(self, seriesIdx) : s.points.width ? s.points.stroke(self, seriesIdx) : null;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function legendFill(self, seriesIdx) {
|
|
1077
|
+
return self.series[seriesIdx].fill(self, seriesIdx);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const legendOpts = {
|
|
1081
|
+
show: true,
|
|
1082
|
+
live: true,
|
|
1083
|
+
isolate: false,
|
|
1084
|
+
markers: {
|
|
1085
|
+
show: true,
|
|
1086
|
+
width: 2,
|
|
1087
|
+
stroke: legendStroke,
|
|
1088
|
+
fill: legendFill,
|
|
1089
|
+
dash: "solid",
|
|
1090
|
+
},
|
|
1091
|
+
idx: null,
|
|
1092
|
+
idxs: null,
|
|
1093
|
+
values: [],
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
function cursorPointShow(self, si) {
|
|
1097
|
+
let o = self.cursor.points;
|
|
1098
|
+
|
|
1099
|
+
let pt = placeDiv();
|
|
1100
|
+
|
|
1101
|
+
let size = o.size(self, si);
|
|
1102
|
+
setStylePx(pt, WIDTH, size);
|
|
1103
|
+
setStylePx(pt, HEIGHT, size);
|
|
1104
|
+
|
|
1105
|
+
let mar = size / -2;
|
|
1106
|
+
setStylePx(pt, "marginLeft", mar);
|
|
1107
|
+
setStylePx(pt, "marginTop", mar);
|
|
1108
|
+
|
|
1109
|
+
let width = o.width(self, si, size);
|
|
1110
|
+
width && setStylePx(pt, "borderWidth", width);
|
|
1111
|
+
|
|
1112
|
+
return pt;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function cursorPointFill(self, si) {
|
|
1116
|
+
let sp = self.series[si].points;
|
|
1117
|
+
return sp._fill || sp._stroke;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
function cursorPointStroke(self, si) {
|
|
1121
|
+
let sp = self.series[si].points;
|
|
1122
|
+
return sp._stroke || sp._fill;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
function cursorPointSize(self, si) {
|
|
1126
|
+
let sp = self.series[si].points;
|
|
1127
|
+
return ptDia(sp.width, 1);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function dataIdx(self, seriesIdx, cursorIdx) {
|
|
1131
|
+
return cursorIdx;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const moveTuple = [0,0];
|
|
1135
|
+
|
|
1136
|
+
function cursorMove(self, mouseLeft1, mouseTop1) {
|
|
1137
|
+
moveTuple[0] = mouseLeft1;
|
|
1138
|
+
moveTuple[1] = mouseTop1;
|
|
1139
|
+
return moveTuple;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function filtBtn0(self, targ, handle) {
|
|
1143
|
+
return e => {
|
|
1144
|
+
e.button == 0 && handle(e);
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function passThru(self, targ, handle) {
|
|
1149
|
+
return handle;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const cursorOpts = {
|
|
1153
|
+
show: true,
|
|
1154
|
+
x: true,
|
|
1155
|
+
y: true,
|
|
1156
|
+
lock: false,
|
|
1157
|
+
move: cursorMove,
|
|
1158
|
+
points: {
|
|
1159
|
+
show: cursorPointShow,
|
|
1160
|
+
size: cursorPointSize,
|
|
1161
|
+
width: 0,
|
|
1162
|
+
stroke: cursorPointStroke,
|
|
1163
|
+
fill: cursorPointFill,
|
|
1164
|
+
},
|
|
1165
|
+
|
|
1166
|
+
bind: {
|
|
1167
|
+
mousedown: filtBtn0,
|
|
1168
|
+
mouseup: filtBtn0,
|
|
1169
|
+
click: filtBtn0,
|
|
1170
|
+
dblclick: filtBtn0,
|
|
1171
|
+
|
|
1172
|
+
mousemove: passThru,
|
|
1173
|
+
mouseleave: passThru,
|
|
1174
|
+
mouseenter: passThru,
|
|
1175
|
+
},
|
|
1176
|
+
|
|
1177
|
+
drag: {
|
|
1178
|
+
setScale: true,
|
|
1179
|
+
x: true,
|
|
1180
|
+
y: false,
|
|
1181
|
+
dist: 0,
|
|
1182
|
+
uni: null,
|
|
1183
|
+
_x: false,
|
|
1184
|
+
_y: false,
|
|
1185
|
+
},
|
|
1186
|
+
|
|
1187
|
+
focus: {
|
|
1188
|
+
prox: -1,
|
|
1189
|
+
},
|
|
1190
|
+
|
|
1191
|
+
left: -10,
|
|
1192
|
+
top: -10,
|
|
1193
|
+
idx: null,
|
|
1194
|
+
dataIdx,
|
|
1195
|
+
idxs: null,
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
const grid = {
|
|
1199
|
+
show: true,
|
|
1200
|
+
stroke: "rgba(0,0,0,0.07)",
|
|
1201
|
+
width: 2,
|
|
1202
|
+
// dash: [],
|
|
1203
|
+
filter: retArg1,
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
const ticks = assign({}, grid, {size: 10});
|
|
1207
|
+
|
|
1208
|
+
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"';
|
|
1209
|
+
const labelFont = "bold " + font;
|
|
1210
|
+
const lineMult = 1.5; // font-size multiplier
|
|
1211
|
+
|
|
1212
|
+
const xAxisOpts = {
|
|
1213
|
+
show: true,
|
|
1214
|
+
scale: "x",
|
|
1215
|
+
stroke: hexBlack,
|
|
1216
|
+
space: 50,
|
|
1217
|
+
gap: 5,
|
|
1218
|
+
size: 50,
|
|
1219
|
+
labelGap: 0,
|
|
1220
|
+
labelSize: 30,
|
|
1221
|
+
labelFont,
|
|
1222
|
+
side: 2,
|
|
1223
|
+
// class: "x-vals",
|
|
1224
|
+
// incrs: timeIncrs,
|
|
1225
|
+
// values: timeVals,
|
|
1226
|
+
// filter: retArg1,
|
|
1227
|
+
grid,
|
|
1228
|
+
ticks,
|
|
1229
|
+
font,
|
|
1230
|
+
rotate: 0,
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1233
|
+
const numSeriesLabel = "Value";
|
|
1234
|
+
const timeSeriesLabel = "Time";
|
|
1235
|
+
|
|
1236
|
+
const xSeriesOpts = {
|
|
1237
|
+
show: true,
|
|
1238
|
+
scale: "x",
|
|
1239
|
+
auto: false,
|
|
1240
|
+
sorted: 1,
|
|
1241
|
+
// label: "Time",
|
|
1242
|
+
// value: v => stamp(new Date(v * 1e3)),
|
|
1243
|
+
|
|
1244
|
+
// internal caches
|
|
1245
|
+
min: inf,
|
|
1246
|
+
max: -inf,
|
|
1247
|
+
idxs: [],
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
function numAxisVals(self, splits, axisIdx, foundSpace, foundIncr) {
|
|
1251
|
+
return splits.map(v => v == null ? "" : fmtNum(v));
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function numAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
|
|
1255
|
+
let splits = [];
|
|
1256
|
+
|
|
1257
|
+
let numDec = fixedDec.get(foundIncr) || 0;
|
|
1258
|
+
|
|
1259
|
+
scaleMin = forceMin ? scaleMin : roundDec(incrRoundUp(scaleMin, foundIncr), numDec);
|
|
1260
|
+
|
|
1261
|
+
for (let val = scaleMin; val <= scaleMax; val = roundDec(val + foundIncr, numDec))
|
|
1262
|
+
splits.push(Object.is(val, -0) ? 0 : val); // coalesces -0
|
|
1263
|
+
|
|
1264
|
+
return splits;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// this doesnt work for sin, which needs to come off from 0 independently in pos and neg dirs
|
|
1268
|
+
function logAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
|
|
1269
|
+
const splits = [];
|
|
1270
|
+
|
|
1271
|
+
const logBase = self.scales[self.axes[axisIdx].scale].log;
|
|
1272
|
+
|
|
1273
|
+
const logFn = logBase == 10 ? log10 : log2;
|
|
1274
|
+
|
|
1275
|
+
const exp = floor(logFn(scaleMin));
|
|
1276
|
+
|
|
1277
|
+
foundIncr = pow(logBase, exp);
|
|
1278
|
+
|
|
1279
|
+
if (exp < 0)
|
|
1280
|
+
foundIncr = roundDec(foundIncr, -exp);
|
|
1281
|
+
|
|
1282
|
+
let split = scaleMin;
|
|
1283
|
+
|
|
1284
|
+
do {
|
|
1285
|
+
splits.push(split);
|
|
1286
|
+
split = roundDec(split + foundIncr, fixedDec.get(foundIncr));
|
|
1287
|
+
|
|
1288
|
+
if (split >= foundIncr * logBase)
|
|
1289
|
+
foundIncr = split;
|
|
1290
|
+
|
|
1291
|
+
} while (split <= scaleMax);
|
|
1292
|
+
|
|
1293
|
+
return splits;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function asinhAxisSplits(self, axisIdx, scaleMin, scaleMax, foundIncr, foundSpace, forceMin) {
|
|
1297
|
+
let sc = self.scales[self.axes[axisIdx].scale];
|
|
1298
|
+
|
|
1299
|
+
let linthresh = sc.asinh;
|
|
1300
|
+
|
|
1301
|
+
let posSplits = scaleMax > linthresh ? logAxisSplits(self, axisIdx, max(linthresh, scaleMin), scaleMax, foundIncr) : [linthresh];
|
|
1302
|
+
let zero = scaleMax >= 0 && scaleMin <= 0 ? [0] : [];
|
|
1303
|
+
let negSplits = scaleMin < -linthresh ? logAxisSplits(self, axisIdx, max(linthresh, -scaleMax), -scaleMin, foundIncr): [linthresh];
|
|
1304
|
+
|
|
1305
|
+
return negSplits.reverse().map(v => -v).concat(zero, posSplits);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
const RE_ALL = /./;
|
|
1309
|
+
const RE_12357 = /[12357]/;
|
|
1310
|
+
const RE_125 = /[125]/;
|
|
1311
|
+
const RE_1 = /1/;
|
|
1312
|
+
|
|
1313
|
+
function logAxisValsFilt(self, splits, axisIdx, foundSpace, foundIncr) {
|
|
1314
|
+
let axis = self.axes[axisIdx];
|
|
1315
|
+
let scaleKey = axis.scale;
|
|
1316
|
+
let sc = self.scales[scaleKey];
|
|
1317
|
+
|
|
1318
|
+
if (sc.distr == 3 && sc.log == 2)
|
|
1319
|
+
return splits;
|
|
1320
|
+
|
|
1321
|
+
let valToPos = self.valToPos;
|
|
1322
|
+
|
|
1323
|
+
let minSpace = axis._space;
|
|
1324
|
+
|
|
1325
|
+
let _10 = valToPos(10, scaleKey);
|
|
1326
|
+
|
|
1327
|
+
let re = (
|
|
1328
|
+
valToPos(9, scaleKey) - _10 >= minSpace ? RE_ALL :
|
|
1329
|
+
valToPos(7, scaleKey) - _10 >= minSpace ? RE_12357 :
|
|
1330
|
+
valToPos(5, scaleKey) - _10 >= minSpace ? RE_125 :
|
|
1331
|
+
RE_1
|
|
1332
|
+
);
|
|
1333
|
+
|
|
1334
|
+
return splits.map(v => ((sc.distr == 4 && v == 0) || re.test(v)) ? v : null);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function numSeriesVal(self, val) {
|
|
1338
|
+
return val == null ? "" : fmtNum(val);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const yAxisOpts = {
|
|
1342
|
+
show: true,
|
|
1343
|
+
scale: "y",
|
|
1344
|
+
stroke: hexBlack,
|
|
1345
|
+
space: 30,
|
|
1346
|
+
gap: 5,
|
|
1347
|
+
size: 50,
|
|
1348
|
+
labelGap: 0,
|
|
1349
|
+
labelSize: 30,
|
|
1350
|
+
labelFont,
|
|
1351
|
+
side: 3,
|
|
1352
|
+
// class: "y-vals",
|
|
1353
|
+
// incrs: numIncrs,
|
|
1354
|
+
// values: (vals, space) => vals,
|
|
1355
|
+
// filter: retArg1,
|
|
1356
|
+
grid,
|
|
1357
|
+
ticks,
|
|
1358
|
+
font,
|
|
1359
|
+
rotate: 0,
|
|
1360
|
+
};
|
|
1361
|
+
|
|
1362
|
+
// takes stroke width
|
|
1363
|
+
function ptDia(width, mult) {
|
|
1364
|
+
let dia = 3 + (width || 1) * 2;
|
|
1365
|
+
return roundDec(dia * mult, 3);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function seriesPointsShow(self, si) {
|
|
1369
|
+
let { scale, idxs } = self.series[0];
|
|
1370
|
+
let xData = self._data[0];
|
|
1371
|
+
let p0 = self.valToPos(xData[idxs[0]], scale, true);
|
|
1372
|
+
let p1 = self.valToPos(xData[idxs[1]], scale, true);
|
|
1373
|
+
let dim = abs(p1 - p0);
|
|
1374
|
+
|
|
1375
|
+
let s = self.series[si];
|
|
1376
|
+
// const dia = ptDia(s.width, pxRatio);
|
|
1377
|
+
let maxPts = dim / (s.points.space * pxRatio);
|
|
1378
|
+
return idxs[1] - idxs[0] <= maxPts;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function seriesFillTo(self, seriesIdx, dataMin, dataMax) {
|
|
1382
|
+
let scale = self.scales[self.series[seriesIdx].scale];
|
|
1383
|
+
let isUpperBandEdge = self.bands && self.bands.some(b => b.series[0] == seriesIdx);
|
|
1384
|
+
return scale.distr == 3 || isUpperBandEdge ? scale.min : 0;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
const facet = {
|
|
1388
|
+
scale: null,
|
|
1389
|
+
auto: true,
|
|
1390
|
+
|
|
1391
|
+
// internal caches
|
|
1392
|
+
min: inf,
|
|
1393
|
+
max: -inf,
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
const xySeriesOpts = {
|
|
1397
|
+
show: true,
|
|
1398
|
+
auto: true,
|
|
1399
|
+
sorted: 0,
|
|
1400
|
+
alpha: 1,
|
|
1401
|
+
facets: [
|
|
1402
|
+
assign({}, facet, {scale: 'x'}),
|
|
1403
|
+
assign({}, facet, {scale: 'y'}),
|
|
1404
|
+
],
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
const ySeriesOpts = {
|
|
1408
|
+
scale: "y",
|
|
1409
|
+
auto: true,
|
|
1410
|
+
sorted: 0,
|
|
1411
|
+
show: true,
|
|
1412
|
+
spanGaps: false,
|
|
1413
|
+
gaps: (self, seriesIdx, idx0, idx1, nullGaps) => nullGaps,
|
|
1414
|
+
alpha: 1,
|
|
1415
|
+
points: {
|
|
1416
|
+
show: seriesPointsShow,
|
|
1417
|
+
filter: null,
|
|
1418
|
+
// paths:
|
|
1419
|
+
// stroke: "#000",
|
|
1420
|
+
// fill: "#fff",
|
|
1421
|
+
// width: 1,
|
|
1422
|
+
// size: 10,
|
|
1423
|
+
},
|
|
1424
|
+
// label: "Value",
|
|
1425
|
+
// value: v => v,
|
|
1426
|
+
values: null,
|
|
1427
|
+
|
|
1428
|
+
// internal caches
|
|
1429
|
+
min: inf,
|
|
1430
|
+
max: -inf,
|
|
1431
|
+
idxs: [],
|
|
1432
|
+
|
|
1433
|
+
path: null,
|
|
1434
|
+
clip: null,
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
function clampScale(self, val, scaleMin, scaleMax, scaleKey) {
|
|
1438
|
+
/*
|
|
1439
|
+
if (val < 0) {
|
|
1440
|
+
let cssHgt = self.bbox.height / pxRatio;
|
|
1441
|
+
let absPos = self.valToPos(abs(val), scaleKey);
|
|
1442
|
+
let fromBtm = cssHgt - absPos;
|
|
1443
|
+
return self.posToVal(cssHgt + fromBtm, scaleKey);
|
|
1444
|
+
}
|
|
1445
|
+
*/
|
|
1446
|
+
return scaleMin / 10;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const xScaleOpts = {
|
|
1450
|
+
time: FEAT_TIME,
|
|
1451
|
+
auto: true,
|
|
1452
|
+
distr: 1,
|
|
1453
|
+
log: 10,
|
|
1454
|
+
asinh: 1,
|
|
1455
|
+
min: null,
|
|
1456
|
+
max: null,
|
|
1457
|
+
dir: 1,
|
|
1458
|
+
ori: 0,
|
|
1459
|
+
};
|
|
1460
|
+
|
|
1461
|
+
const yScaleOpts = assign({}, xScaleOpts, {
|
|
1462
|
+
time: false,
|
|
1463
|
+
ori: 1,
|
|
1464
|
+
});
|
|
1465
|
+
|
|
1466
|
+
const syncs = {};
|
|
1467
|
+
|
|
1468
|
+
function _sync(key, opts) {
|
|
1469
|
+
let s = syncs[key];
|
|
1470
|
+
|
|
1471
|
+
if (!s) {
|
|
1472
|
+
s = {
|
|
1473
|
+
key,
|
|
1474
|
+
plots: [],
|
|
1475
|
+
sub(plot) {
|
|
1476
|
+
s.plots.push(plot);
|
|
1477
|
+
},
|
|
1478
|
+
unsub(plot) {
|
|
1479
|
+
s.plots = s.plots.filter(c => c != plot);
|
|
1480
|
+
},
|
|
1481
|
+
pub(type, self, x, y, w, h, i) {
|
|
1482
|
+
for (let j = 0; j < s.plots.length; j++)
|
|
1483
|
+
s.plots[j] != self && s.plots[j].pub(type, self, x, y, w, h, i);
|
|
1484
|
+
},
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
if (key != null)
|
|
1488
|
+
syncs[key] = s;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
return s;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const BAND_CLIP_FILL = 1 << 0;
|
|
1495
|
+
const BAND_CLIP_STROKE = 1 << 1;
|
|
1496
|
+
|
|
1497
|
+
function orient(u, seriesIdx, cb) {
|
|
1498
|
+
const series = u.series[seriesIdx];
|
|
1499
|
+
const scales = u.scales;
|
|
1500
|
+
const bbox = u.bbox;
|
|
1501
|
+
const scaleX = u.mode == 2 ? scales[series.facets[0].scale] : scales[u.series[0].scale];
|
|
1502
|
+
|
|
1503
|
+
let dx = u._data[0],
|
|
1504
|
+
dy = u._data[seriesIdx],
|
|
1505
|
+
sx = scaleX,
|
|
1506
|
+
sy = u.mode == 2 ? scales[series.facets[1].scale] : scales[series.scale],
|
|
1507
|
+
l = bbox.left,
|
|
1508
|
+
t = bbox.top,
|
|
1509
|
+
w = bbox.width,
|
|
1510
|
+
h = bbox.height,
|
|
1511
|
+
H = u.valToPosH,
|
|
1512
|
+
V = u.valToPosV;
|
|
1513
|
+
|
|
1514
|
+
return (sx.ori == 0
|
|
1515
|
+
? cb(
|
|
1516
|
+
series,
|
|
1517
|
+
dx,
|
|
1518
|
+
dy,
|
|
1519
|
+
sx,
|
|
1520
|
+
sy,
|
|
1521
|
+
H,
|
|
1522
|
+
V,
|
|
1523
|
+
l,
|
|
1524
|
+
t,
|
|
1525
|
+
w,
|
|
1526
|
+
h,
|
|
1527
|
+
moveToH,
|
|
1528
|
+
lineToH,
|
|
1529
|
+
rectH,
|
|
1530
|
+
arcH,
|
|
1531
|
+
bezierCurveToH,
|
|
1532
|
+
)
|
|
1533
|
+
: cb(
|
|
1534
|
+
series,
|
|
1535
|
+
dx,
|
|
1536
|
+
dy,
|
|
1537
|
+
sx,
|
|
1538
|
+
sy,
|
|
1539
|
+
V,
|
|
1540
|
+
H,
|
|
1541
|
+
t,
|
|
1542
|
+
l,
|
|
1543
|
+
h,
|
|
1544
|
+
w,
|
|
1545
|
+
moveToV,
|
|
1546
|
+
lineToV,
|
|
1547
|
+
rectV,
|
|
1548
|
+
arcV,
|
|
1549
|
+
bezierCurveToV,
|
|
1550
|
+
)
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// creates inverted band clip path (towards from stroke path -> yMax)
|
|
1555
|
+
function clipBandLine(self, seriesIdx, idx0, idx1, strokePath) {
|
|
1556
|
+
return orient(self, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
1557
|
+
const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
|
|
1558
|
+
const lineTo = scaleX.ori == 0 ? lineToH : lineToV;
|
|
1559
|
+
|
|
1560
|
+
let frIdx, toIdx;
|
|
1561
|
+
|
|
1562
|
+
if (dir == 1) {
|
|
1563
|
+
frIdx = idx0;
|
|
1564
|
+
toIdx = idx1;
|
|
1565
|
+
}
|
|
1566
|
+
else {
|
|
1567
|
+
frIdx = idx1;
|
|
1568
|
+
toIdx = idx0;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// path start
|
|
1572
|
+
let x0 = incrRound(valToPosX(dataX[frIdx], scaleX, xDim, xOff), 0.5);
|
|
1573
|
+
let y0 = incrRound(valToPosY(dataY[frIdx], scaleY, yDim, yOff), 0.5);
|
|
1574
|
+
// path end x
|
|
1575
|
+
let x1 = incrRound(valToPosX(dataX[toIdx], scaleX, xDim, xOff), 0.5);
|
|
1576
|
+
// upper y limit
|
|
1577
|
+
let yLimit = incrRound(valToPosY(scaleY.max, scaleY, yDim, yOff), 0.5);
|
|
1578
|
+
|
|
1579
|
+
let clip = new Path2D(strokePath);
|
|
1580
|
+
|
|
1581
|
+
lineTo(clip, x1, yLimit);
|
|
1582
|
+
lineTo(clip, x0, yLimit);
|
|
1583
|
+
lineTo(clip, x0, y0);
|
|
1584
|
+
|
|
1585
|
+
return clip;
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function clipGaps(gaps, ori, plotLft, plotTop, plotWid, plotHgt) {
|
|
1590
|
+
let clip = null;
|
|
1591
|
+
|
|
1592
|
+
// create clip path (invert gaps and non-gaps)
|
|
1593
|
+
if (gaps.length > 0) {
|
|
1594
|
+
clip = new Path2D();
|
|
1595
|
+
|
|
1596
|
+
const rect = ori == 0 ? rectH : rectV;
|
|
1597
|
+
|
|
1598
|
+
let prevGapEnd = plotLft;
|
|
1599
|
+
|
|
1600
|
+
for (let i = 0; i < gaps.length; i++) {
|
|
1601
|
+
let g = gaps[i];
|
|
1602
|
+
|
|
1603
|
+
if (g[1] > g[0]) {
|
|
1604
|
+
rect(clip, prevGapEnd, plotTop, g[0] - prevGapEnd, plotTop + plotHgt);
|
|
1605
|
+
|
|
1606
|
+
prevGapEnd = g[1];
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
rect(clip, prevGapEnd, plotTop, plotLft + plotWid - prevGapEnd, plotTop + plotHgt);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
return clip;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function addGap(gaps, fromX, toX) {
|
|
1617
|
+
let prevGap = gaps[gaps.length - 1];
|
|
1618
|
+
|
|
1619
|
+
if (prevGap && prevGap[0] == fromX) // TODO: gaps must be encoded at stroke widths?
|
|
1620
|
+
prevGap[1] = toX;
|
|
1621
|
+
else
|
|
1622
|
+
gaps.push([fromX, toX]);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function pxRoundGen(pxAlign) {
|
|
1626
|
+
return pxAlign == 0 ? retArg0 : pxAlign == 1 ? round : v => incrRound(v, pxAlign);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// orientation-inverting canvas functions
|
|
1630
|
+
function moveToH(p, x, y) { p.moveTo(x, y); }
|
|
1631
|
+
function moveToV(p, y, x) { p.moveTo(x, y); }
|
|
1632
|
+
function lineToH(p, x, y) { p.lineTo(x, y); }
|
|
1633
|
+
function lineToV(p, y, x) { p.lineTo(x, y); }
|
|
1634
|
+
function rectH(p, x, y, w, h) { p.rect(x, y, w, h); }
|
|
1635
|
+
function rectV(p, y, x, h, w) { p.rect(x, y, w, h); }
|
|
1636
|
+
function arcH(p, x, y, r, startAngle, endAngle) { p.arc(x, y, r, startAngle, endAngle); }
|
|
1637
|
+
function arcV(p, y, x, r, startAngle, endAngle) { p.arc(x, y, r, startAngle, endAngle); }
|
|
1638
|
+
function bezierCurveToH(p, bp1x, bp1y, bp2x, bp2y, p2x, p2y) { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); }function bezierCurveToV(p, bp1y, bp1x, bp2y, bp2x, p2y, p2x) { p.bezierCurveTo(bp1x, bp1y, bp2x, bp2y, p2x, p2y); }
|
|
1639
|
+
|
|
1640
|
+
// TODO: drawWrap(seriesIdx, drawPoints) (save, restore, translate, clip)
|
|
1641
|
+
function points(opts) {
|
|
1642
|
+
return (u, seriesIdx, idx0, idx1, filtIdxs) => {
|
|
1643
|
+
// log("drawPoints()", arguments);
|
|
1644
|
+
|
|
1645
|
+
return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
1646
|
+
let { pxRound, points } = series;
|
|
1647
|
+
|
|
1648
|
+
let moveTo, arc;
|
|
1649
|
+
|
|
1650
|
+
if (scaleX.ori == 0) {
|
|
1651
|
+
moveTo = moveToH;
|
|
1652
|
+
arc = arcH;
|
|
1653
|
+
}
|
|
1654
|
+
else {
|
|
1655
|
+
moveTo = moveToV;
|
|
1656
|
+
arc = arcV;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
const width = roundDec(points.width * pxRatio, 3);
|
|
1660
|
+
|
|
1661
|
+
let rad = (points.size - points.width) / 2 * pxRatio;
|
|
1662
|
+
let dia = roundDec(rad * 2, 3);
|
|
1663
|
+
|
|
1664
|
+
let fill = new Path2D();
|
|
1665
|
+
let clip = new Path2D();
|
|
1666
|
+
|
|
1667
|
+
let { left: lft, top: top, width: wid, height: hgt } = u.bbox;
|
|
1668
|
+
|
|
1669
|
+
rectH(clip,
|
|
1670
|
+
lft - dia,
|
|
1671
|
+
top - dia,
|
|
1672
|
+
wid + dia * 2,
|
|
1673
|
+
hgt + dia * 2,
|
|
1674
|
+
);
|
|
1675
|
+
|
|
1676
|
+
const drawPoint = pi => {
|
|
1677
|
+
if (dataY[pi] != null) {
|
|
1678
|
+
let x = pxRound(valToPosX(dataX[pi], scaleX, xDim, xOff));
|
|
1679
|
+
let y = pxRound(valToPosY(dataY[pi], scaleY, yDim, yOff));
|
|
1680
|
+
|
|
1681
|
+
moveTo(fill, x + rad, y);
|
|
1682
|
+
arc(fill, x, y, rad, 0, PI * 2);
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
if (filtIdxs)
|
|
1687
|
+
filtIdxs.forEach(drawPoint);
|
|
1688
|
+
else {
|
|
1689
|
+
for (let pi = idx0; pi <= idx1; pi++)
|
|
1690
|
+
drawPoint(pi);
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
return {
|
|
1694
|
+
stroke: width > 0 ? fill : null,
|
|
1695
|
+
fill,
|
|
1696
|
+
clip,
|
|
1697
|
+
flags: BAND_CLIP_FILL | BAND_CLIP_STROKE,
|
|
1698
|
+
};
|
|
1699
|
+
});
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function _drawAcc(lineTo) {
|
|
1704
|
+
return (stroke, accX, minY, maxY, inY, outY) => {
|
|
1705
|
+
if (minY != maxY) {
|
|
1706
|
+
if (inY != minY && outY != minY)
|
|
1707
|
+
lineTo(stroke, accX, minY);
|
|
1708
|
+
if (inY != maxY && outY != maxY)
|
|
1709
|
+
lineTo(stroke, accX, maxY);
|
|
1710
|
+
|
|
1711
|
+
lineTo(stroke, accX, outY);
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
const drawAccH = _drawAcc(lineToH);
|
|
1717
|
+
const drawAccV = _drawAcc(lineToV);
|
|
1718
|
+
|
|
1719
|
+
function linear() {
|
|
1720
|
+
return (u, seriesIdx, idx0, idx1) => {
|
|
1721
|
+
return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
1722
|
+
let pxRound = series.pxRound;
|
|
1723
|
+
|
|
1724
|
+
let lineTo, drawAcc;
|
|
1725
|
+
|
|
1726
|
+
if (scaleX.ori == 0) {
|
|
1727
|
+
lineTo = lineToH;
|
|
1728
|
+
drawAcc = drawAccH;
|
|
1729
|
+
}
|
|
1730
|
+
else {
|
|
1731
|
+
lineTo = lineToV;
|
|
1732
|
+
drawAcc = drawAccV;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
const dir = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
|
|
1736
|
+
|
|
1737
|
+
const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
|
|
1738
|
+
const stroke = _paths.stroke;
|
|
1739
|
+
|
|
1740
|
+
let minY = inf,
|
|
1741
|
+
maxY = -inf,
|
|
1742
|
+
inY, outY, outX, drawnAtX;
|
|
1743
|
+
|
|
1744
|
+
let gaps = [];
|
|
1745
|
+
|
|
1746
|
+
let accX = pxRound(valToPosX(dataX[dir == 1 ? idx0 : idx1], scaleX, xDim, xOff));
|
|
1747
|
+
let accGaps = false;
|
|
1748
|
+
let prevYNull = false;
|
|
1749
|
+
|
|
1750
|
+
// data edges
|
|
1751
|
+
let lftIdx = nonNullIdx(dataY, idx0, idx1, 1 * dir);
|
|
1752
|
+
let rgtIdx = nonNullIdx(dataY, idx0, idx1, -1 * dir);
|
|
1753
|
+
let lftX = incrRound(valToPosX(dataX[lftIdx], scaleX, xDim, xOff), 0.5);
|
|
1754
|
+
let rgtX = incrRound(valToPosX(dataX[rgtIdx], scaleX, xDim, xOff), 0.5);
|
|
1755
|
+
|
|
1756
|
+
if (lftX > xOff)
|
|
1757
|
+
addGap(gaps, xOff, lftX);
|
|
1758
|
+
|
|
1759
|
+
for (let i = dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += dir) {
|
|
1760
|
+
let x = pxRound(valToPosX(dataX[i], scaleX, xDim, xOff));
|
|
1761
|
+
|
|
1762
|
+
if (x == accX) {
|
|
1763
|
+
if (dataY[i] != null) {
|
|
1764
|
+
outY = pxRound(valToPosY(dataY[i], scaleY, yDim, yOff));
|
|
1765
|
+
|
|
1766
|
+
if (minY == inf) {
|
|
1767
|
+
lineTo(stroke, x, outY);
|
|
1768
|
+
inY = outY;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
minY = min(outY, minY);
|
|
1772
|
+
maxY = max(outY, maxY);
|
|
1773
|
+
}
|
|
1774
|
+
else if (dataY[i] === null)
|
|
1775
|
+
accGaps = prevYNull = true;
|
|
1776
|
+
}
|
|
1777
|
+
else {
|
|
1778
|
+
let _addGap = false;
|
|
1779
|
+
|
|
1780
|
+
if (minY != inf) {
|
|
1781
|
+
drawAcc(stroke, accX, minY, maxY, inY, outY);
|
|
1782
|
+
outX = drawnAtX = accX;
|
|
1783
|
+
}
|
|
1784
|
+
else if (accGaps) {
|
|
1785
|
+
_addGap = true;
|
|
1786
|
+
accGaps = false;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
if (dataY[i] != null) {
|
|
1790
|
+
outY = pxRound(valToPosY(dataY[i], scaleY, yDim, yOff));
|
|
1791
|
+
lineTo(stroke, x, outY);
|
|
1792
|
+
minY = maxY = inY = outY;
|
|
1793
|
+
|
|
1794
|
+
// prior pixel can have data but still start a gap if ends with null
|
|
1795
|
+
if (prevYNull && x - accX > 1)
|
|
1796
|
+
_addGap = true;
|
|
1797
|
+
|
|
1798
|
+
prevYNull = false;
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1801
|
+
minY = inf;
|
|
1802
|
+
maxY = -inf;
|
|
1803
|
+
|
|
1804
|
+
if (dataY[i] === null) {
|
|
1805
|
+
accGaps = true;
|
|
1806
|
+
|
|
1807
|
+
if (x - accX > 1)
|
|
1808
|
+
_addGap = true;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
_addGap && addGap(gaps, outX, x);
|
|
1813
|
+
|
|
1814
|
+
accX = x;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
if (minY != inf && minY != maxY && drawnAtX != accX)
|
|
1819
|
+
drawAcc(stroke, accX, minY, maxY, inY, outY);
|
|
1820
|
+
|
|
1821
|
+
if (rgtX < xOff + xDim)
|
|
1822
|
+
addGap(gaps, rgtX, xOff + xDim);
|
|
1823
|
+
|
|
1824
|
+
if (series.fill != null) {
|
|
1825
|
+
let fill = _paths.fill = new Path2D(stroke);
|
|
1826
|
+
|
|
1827
|
+
let fillTo = pxRound(valToPosY(series.fillTo(u, seriesIdx, series.min, series.max), scaleY, yDim, yOff));
|
|
1828
|
+
|
|
1829
|
+
lineTo(fill, rgtX, fillTo);
|
|
1830
|
+
lineTo(fill, lftX, fillTo);
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
|
|
1834
|
+
|
|
1835
|
+
if (!series.spanGaps)
|
|
1836
|
+
_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
|
|
1837
|
+
|
|
1838
|
+
if (u.bands.length > 0) {
|
|
1839
|
+
// ADDL OPT: only create band clips for series that are band lower edges
|
|
1840
|
+
// if (b.series[1] == i && _paths.band == null)
|
|
1841
|
+
_paths.band = clipBandLine(u, seriesIdx, idx0, idx1, stroke);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
return _paths;
|
|
1845
|
+
});
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function stepped(opts) {
|
|
1850
|
+
const align = ifNull(opts.align, 1);
|
|
1851
|
+
// whether to draw ascenders/descenders at null/gap bondaries
|
|
1852
|
+
const ascDesc = ifNull(opts.ascDesc, false);
|
|
1853
|
+
|
|
1854
|
+
return (u, seriesIdx, idx0, idx1) => {
|
|
1855
|
+
return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
1856
|
+
let pxRound = series.pxRound;
|
|
1857
|
+
|
|
1858
|
+
let lineTo = scaleX.ori == 0 ? lineToH : lineToV;
|
|
1859
|
+
|
|
1860
|
+
const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
|
|
1861
|
+
const stroke = _paths.stroke;
|
|
1862
|
+
|
|
1863
|
+
const _dir = 1 * scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
|
|
1864
|
+
|
|
1865
|
+
idx0 = nonNullIdx(dataY, idx0, idx1, 1);
|
|
1866
|
+
idx1 = nonNullIdx(dataY, idx0, idx1, -1);
|
|
1867
|
+
|
|
1868
|
+
let gaps = [];
|
|
1869
|
+
let inGap = false;
|
|
1870
|
+
let prevYPos = pxRound(valToPosY(dataY[_dir == 1 ? idx0 : idx1], scaleY, yDim, yOff));
|
|
1871
|
+
let firstXPos = pxRound(valToPosX(dataX[_dir == 1 ? idx0 : idx1], scaleX, xDim, xOff));
|
|
1872
|
+
let prevXPos = firstXPos;
|
|
1873
|
+
|
|
1874
|
+
lineTo(stroke, firstXPos, prevYPos);
|
|
1875
|
+
|
|
1876
|
+
for (let i = _dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dir) {
|
|
1877
|
+
let yVal1 = dataY[i];
|
|
1878
|
+
|
|
1879
|
+
let x1 = pxRound(valToPosX(dataX[i], scaleX, xDim, xOff));
|
|
1880
|
+
|
|
1881
|
+
if (yVal1 == null) {
|
|
1882
|
+
if (yVal1 === null) {
|
|
1883
|
+
addGap(gaps, prevXPos, x1);
|
|
1884
|
+
inGap = true;
|
|
1885
|
+
}
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
let y1 = pxRound(valToPosY(yVal1, scaleY, yDim, yOff));
|
|
1890
|
+
|
|
1891
|
+
if (inGap) {
|
|
1892
|
+
addGap(gaps, prevXPos, x1);
|
|
1893
|
+
|
|
1894
|
+
// don't clip vertical extenders
|
|
1895
|
+
if (prevYPos != y1) {
|
|
1896
|
+
let halfStroke = (series.width * pxRatio) / 2;
|
|
1897
|
+
|
|
1898
|
+
let lastGap = gaps[gaps.length - 1];
|
|
1899
|
+
|
|
1900
|
+
lastGap[0] += (ascDesc || align == 1) ? halfStroke : -halfStroke;
|
|
1901
|
+
lastGap[1] -= (ascDesc || align == -1) ? halfStroke : -halfStroke;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
inGap = false;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
if (align == 1)
|
|
1908
|
+
lineTo(stroke, x1, prevYPos);
|
|
1909
|
+
else
|
|
1910
|
+
lineTo(stroke, prevXPos, y1);
|
|
1911
|
+
|
|
1912
|
+
lineTo(stroke, x1, y1);
|
|
1913
|
+
|
|
1914
|
+
prevYPos = y1;
|
|
1915
|
+
prevXPos = x1;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
if (series.fill != null) {
|
|
1919
|
+
let fill = _paths.fill = new Path2D(stroke);
|
|
1920
|
+
|
|
1921
|
+
let fillTo = series.fillTo(u, seriesIdx, series.min, series.max);
|
|
1922
|
+
let minY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff));
|
|
1923
|
+
|
|
1924
|
+
lineTo(fill, prevXPos, minY);
|
|
1925
|
+
lineTo(fill, firstXPos, minY);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
|
|
1929
|
+
|
|
1930
|
+
if (!series.spanGaps)
|
|
1931
|
+
_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
|
|
1932
|
+
|
|
1933
|
+
if (u.bands.length > 0) {
|
|
1934
|
+
// ADDL OPT: only create band clips for series that are band lower edges
|
|
1935
|
+
// if (b.series[1] == i && _paths.band == null)
|
|
1936
|
+
_paths.band = clipBandLine(u, seriesIdx, idx0, idx1, stroke);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
return _paths;
|
|
1940
|
+
});
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
function bars(opts) {
|
|
1945
|
+
opts = opts || EMPTY_OBJ;
|
|
1946
|
+
const size = ifNull(opts.size, [0.6, inf, 1]);
|
|
1947
|
+
const align = opts.align || 0;
|
|
1948
|
+
const extraGap = (opts.gap || 0) * pxRatio;
|
|
1949
|
+
|
|
1950
|
+
const gapFactor = 1 - size[0];
|
|
1951
|
+
const maxWidth = ifNull(size[1], inf) * pxRatio;
|
|
1952
|
+
const minWidth = ifNull(size[2], 1) * pxRatio;
|
|
1953
|
+
|
|
1954
|
+
const disp = opts.disp;
|
|
1955
|
+
const _each = ifNull(opts.each, _ => {});
|
|
1956
|
+
|
|
1957
|
+
return (u, seriesIdx, idx0, idx1) => {
|
|
1958
|
+
return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
1959
|
+
let pxRound = series.pxRound;
|
|
1960
|
+
|
|
1961
|
+
const _dirX = scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
|
|
1962
|
+
const _dirY = scaleY.dir * (scaleY.ori == 1 ? 1 : -1);
|
|
1963
|
+
|
|
1964
|
+
let rect = scaleX.ori == 0 ? rectH : rectV;
|
|
1965
|
+
|
|
1966
|
+
let each = scaleX.ori == 0 ? _each : (u, seriesIdx, i, top, lft, hgt, wid) => {
|
|
1967
|
+
_each(u, seriesIdx, i, lft, top, wid, hgt);
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
let fillToY = series.fillTo(u, seriesIdx, series.min, series.max);
|
|
1971
|
+
|
|
1972
|
+
let y0Pos = valToPosY(fillToY, scaleY, yDim, yOff);
|
|
1973
|
+
|
|
1974
|
+
let xShift, barWid;
|
|
1975
|
+
|
|
1976
|
+
let strokeWidth = pxRound(series.width * pxRatio);
|
|
1977
|
+
|
|
1978
|
+
if (disp != null) {
|
|
1979
|
+
dataX = disp.x0.values(u, seriesIdx, idx0, idx1);
|
|
1980
|
+
|
|
1981
|
+
if (disp.x0.unit == 2)
|
|
1982
|
+
dataX = dataX.map(pct => u.posToVal(xOff + pct * xDim, scaleX.key, true));
|
|
1983
|
+
|
|
1984
|
+
// assumes uniform sizes, for now
|
|
1985
|
+
let sizes = disp.size.values(u, seriesIdx, idx0, idx1);
|
|
1986
|
+
|
|
1987
|
+
if (disp.size.unit == 2)
|
|
1988
|
+
barWid = sizes[0] * xDim;
|
|
1989
|
+
else
|
|
1990
|
+
barWid = valToPosX(sizes[0], scaleX, xDim, xOff) - valToPosX(0, scaleX, xDim, xOff); // assumes linear scale (delta from 0)
|
|
1991
|
+
|
|
1992
|
+
barWid = pxRound(barWid - strokeWidth);
|
|
1993
|
+
|
|
1994
|
+
xShift = (_dirX == 1 ? -strokeWidth / 2 : barWid + strokeWidth / 2);
|
|
1995
|
+
}
|
|
1996
|
+
else {
|
|
1997
|
+
let colWid = xDim;
|
|
1998
|
+
|
|
1999
|
+
if (dataX.length > 1) {
|
|
2000
|
+
// scan full dataset for smallest adjacent delta
|
|
2001
|
+
// will not work properly for non-linear x scales, since does not do expensive valToPosX calcs till end
|
|
2002
|
+
for (let i = 1, minDelta = Infinity; i < dataX.length; i++) {
|
|
2003
|
+
let delta = abs(dataX[i] - dataX[i-1]);
|
|
2004
|
+
|
|
2005
|
+
if (delta < minDelta) {
|
|
2006
|
+
minDelta = delta;
|
|
2007
|
+
colWid = abs(valToPosX(dataX[i], scaleX, xDim, xOff) - valToPosX(dataX[i-1], scaleX, xDim, xOff));
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
let gapWid = colWid * gapFactor;
|
|
2013
|
+
|
|
2014
|
+
barWid = pxRound(min(maxWidth, max(minWidth, colWid - gapWid)) - strokeWidth - extraGap);
|
|
2015
|
+
|
|
2016
|
+
xShift = (align == 0 ? barWid / 2 : align == _dirX ? 0 : barWid) - align * _dirX * extraGap / 2;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
const _paths = {stroke: new Path2D(), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL | BAND_CLIP_STROKE}; // disp, geom
|
|
2020
|
+
|
|
2021
|
+
const hasBands = u.bands.length > 0;
|
|
2022
|
+
let yLimit;
|
|
2023
|
+
|
|
2024
|
+
if (hasBands) {
|
|
2025
|
+
// ADDL OPT: only create band clips for series that are band lower edges
|
|
2026
|
+
// if (b.series[1] == i && _paths.band == null)
|
|
2027
|
+
_paths.band = new Path2D();
|
|
2028
|
+
yLimit = incrRound(valToPosY(scaleY.max, scaleY, yDim, yOff), 0.5);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
const stroke = _paths.stroke;
|
|
2032
|
+
const band = _paths.band;
|
|
2033
|
+
|
|
2034
|
+
for (let i = _dirX == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dirX) {
|
|
2035
|
+
let yVal = dataY[i];
|
|
2036
|
+
|
|
2037
|
+
/*
|
|
2038
|
+
// interpolate upwards band clips
|
|
2039
|
+
if (yVal == null) {
|
|
2040
|
+
// if (hasBands)
|
|
2041
|
+
// yVal = costlyLerp(i, idx0, idx1, _dirX, dataY);
|
|
2042
|
+
// else
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
*/
|
|
2046
|
+
|
|
2047
|
+
let xVal = scaleX.distr != 2 || disp != null ? dataX[i] : i;
|
|
2048
|
+
|
|
2049
|
+
// TODO: all xPos can be pre-computed once for all series in aligned set
|
|
2050
|
+
let xPos = valToPosX(xVal, scaleX, xDim, xOff);
|
|
2051
|
+
let yPos = valToPosY(yVal, scaleY, yDim, yOff);
|
|
2052
|
+
|
|
2053
|
+
let lft = pxRound(xPos - xShift);
|
|
2054
|
+
let btm = pxRound(max(yPos, y0Pos));
|
|
2055
|
+
let top = pxRound(min(yPos, y0Pos));
|
|
2056
|
+
let barHgt = btm - top;
|
|
2057
|
+
|
|
2058
|
+
if (dataY[i] != null) {
|
|
2059
|
+
rect(stroke, lft, top, barWid, barHgt);
|
|
2060
|
+
|
|
2061
|
+
each(u, seriesIdx, i,
|
|
2062
|
+
lft - strokeWidth / 2,
|
|
2063
|
+
top - strokeWidth / 2,
|
|
2064
|
+
barWid + strokeWidth,
|
|
2065
|
+
barHgt + strokeWidth,
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
if (hasBands) {
|
|
2070
|
+
if (_dirY == 1) {
|
|
2071
|
+
btm = top;
|
|
2072
|
+
top = yLimit;
|
|
2073
|
+
}
|
|
2074
|
+
else {
|
|
2075
|
+
top = btm;
|
|
2076
|
+
btm = yLimit;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
barHgt = btm - top;
|
|
2080
|
+
|
|
2081
|
+
rect(band, lft - strokeWidth / 2, top + strokeWidth / 2, barWid + strokeWidth, barHgt - strokeWidth);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
if (series.fill != null)
|
|
2086
|
+
_paths.fill = new Path2D(stroke);
|
|
2087
|
+
|
|
2088
|
+
return _paths;
|
|
2089
|
+
});
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
function splineInterp(interp, opts) {
|
|
2094
|
+
return (u, seriesIdx, idx0, idx1) => {
|
|
2095
|
+
return orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
2096
|
+
let pxRound = series.pxRound;
|
|
2097
|
+
|
|
2098
|
+
let moveTo, bezierCurveTo, lineTo;
|
|
2099
|
+
|
|
2100
|
+
if (scaleX.ori == 0) {
|
|
2101
|
+
moveTo = moveToH;
|
|
2102
|
+
lineTo = lineToH;
|
|
2103
|
+
bezierCurveTo = bezierCurveToH;
|
|
2104
|
+
}
|
|
2105
|
+
else {
|
|
2106
|
+
moveTo = moveToV;
|
|
2107
|
+
lineTo = lineToV;
|
|
2108
|
+
bezierCurveTo = bezierCurveToV;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
const _dir = 1 * scaleX.dir * (scaleX.ori == 0 ? 1 : -1);
|
|
2112
|
+
|
|
2113
|
+
idx0 = nonNullIdx(dataY, idx0, idx1, 1);
|
|
2114
|
+
idx1 = nonNullIdx(dataY, idx0, idx1, -1);
|
|
2115
|
+
|
|
2116
|
+
let gaps = [];
|
|
2117
|
+
let inGap = false;
|
|
2118
|
+
let firstXPos = pxRound(valToPosX(dataX[_dir == 1 ? idx0 : idx1], scaleX, xDim, xOff));
|
|
2119
|
+
let prevXPos = firstXPos;
|
|
2120
|
+
|
|
2121
|
+
let xCoords = [];
|
|
2122
|
+
let yCoords = [];
|
|
2123
|
+
|
|
2124
|
+
for (let i = _dir == 1 ? idx0 : idx1; i >= idx0 && i <= idx1; i += _dir) {
|
|
2125
|
+
let yVal = dataY[i];
|
|
2126
|
+
let xVal = dataX[i];
|
|
2127
|
+
let xPos = valToPosX(xVal, scaleX, xDim, xOff);
|
|
2128
|
+
|
|
2129
|
+
if (yVal == null) {
|
|
2130
|
+
if (yVal === null) {
|
|
2131
|
+
addGap(gaps, prevXPos, xPos);
|
|
2132
|
+
inGap = true;
|
|
2133
|
+
}
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
else {
|
|
2137
|
+
if (inGap) {
|
|
2138
|
+
addGap(gaps, prevXPos, xPos);
|
|
2139
|
+
inGap = false;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
xCoords.push((prevXPos = xPos));
|
|
2143
|
+
yCoords.push(valToPosY(dataY[i], scaleY, yDim, yOff));
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
const _paths = {stroke: interp(xCoords, yCoords, moveTo, lineTo, bezierCurveTo, pxRound), fill: null, clip: null, band: null, gaps: null, flags: BAND_CLIP_FILL};
|
|
2148
|
+
const stroke = _paths.stroke;
|
|
2149
|
+
|
|
2150
|
+
if (series.fill != null && stroke != null) {
|
|
2151
|
+
let fill = _paths.fill = new Path2D(stroke);
|
|
2152
|
+
|
|
2153
|
+
let fillTo = series.fillTo(u, seriesIdx, series.min, series.max);
|
|
2154
|
+
let minY = pxRound(valToPosY(fillTo, scaleY, yDim, yOff));
|
|
2155
|
+
|
|
2156
|
+
lineTo(fill, prevXPos, minY);
|
|
2157
|
+
lineTo(fill, firstXPos, minY);
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
_paths.gaps = gaps = series.gaps(u, seriesIdx, idx0, idx1, gaps);
|
|
2161
|
+
|
|
2162
|
+
if (!series.spanGaps)
|
|
2163
|
+
_paths.clip = clipGaps(gaps, scaleX.ori, xOff, yOff, xDim, yDim);
|
|
2164
|
+
|
|
2165
|
+
if (u.bands.length > 0) {
|
|
2166
|
+
// ADDL OPT: only create band clips for series that are band lower edges
|
|
2167
|
+
// if (b.series[1] == i && _paths.band == null)
|
|
2168
|
+
_paths.band = clipBandLine(u, seriesIdx, idx0, idx1, stroke);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
return _paths;
|
|
2172
|
+
|
|
2173
|
+
// if FEAT_PATHS: false in rollup.config.js
|
|
2174
|
+
// u.ctx.save();
|
|
2175
|
+
// u.ctx.beginPath();
|
|
2176
|
+
// u.ctx.rect(u.bbox.left, u.bbox.top, u.bbox.width, u.bbox.height);
|
|
2177
|
+
// u.ctx.clip();
|
|
2178
|
+
// u.ctx.strokeStyle = u.series[sidx].stroke;
|
|
2179
|
+
// u.ctx.stroke(stroke);
|
|
2180
|
+
// u.ctx.fillStyle = u.series[sidx].fill;
|
|
2181
|
+
// u.ctx.fill(fill);
|
|
2182
|
+
// u.ctx.restore();
|
|
2183
|
+
// return null;
|
|
2184
|
+
});
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
function monotoneCubic(opts) {
|
|
2189
|
+
return splineInterp(_monotoneCubic);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
// Monotone Cubic Spline interpolation, adapted from the Chartist.js implementation:
|
|
2193
|
+
// https://github.com/gionkunz/chartist-js/blob/e7e78201bffe9609915e5e53cfafa29a5d6c49f9/src/scripts/interpolation.js#L240-L369
|
|
2194
|
+
function _monotoneCubic(xs, ys, moveTo, lineTo, bezierCurveTo, pxRound) {
|
|
2195
|
+
const n = xs.length;
|
|
2196
|
+
|
|
2197
|
+
if (n < 2)
|
|
2198
|
+
return null;
|
|
2199
|
+
|
|
2200
|
+
const path = new Path2D();
|
|
2201
|
+
|
|
2202
|
+
moveTo(path, xs[0], ys[0]);
|
|
2203
|
+
|
|
2204
|
+
if (n == 2)
|
|
2205
|
+
lineTo(path, xs[1], ys[1]);
|
|
2206
|
+
else {
|
|
2207
|
+
let ms = Array(n),
|
|
2208
|
+
ds = Array(n - 1),
|
|
2209
|
+
dys = Array(n - 1),
|
|
2210
|
+
dxs = Array(n - 1);
|
|
2211
|
+
|
|
2212
|
+
// calc deltas and derivative
|
|
2213
|
+
for (let i = 0; i < n - 1; i++) {
|
|
2214
|
+
dys[i] = ys[i + 1] - ys[i];
|
|
2215
|
+
dxs[i] = xs[i + 1] - xs[i];
|
|
2216
|
+
ds[i] = dys[i] / dxs[i];
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// determine desired slope (m) at each point using Fritsch-Carlson method
|
|
2220
|
+
// http://math.stackexchange.com/questions/45218/implementation-of-monotone-cubic-interpolation
|
|
2221
|
+
ms[0] = ds[0];
|
|
2222
|
+
|
|
2223
|
+
for (let i = 1; i < n - 1; i++) {
|
|
2224
|
+
if (ds[i] === 0 || ds[i - 1] === 0 || (ds[i - 1] > 0) !== (ds[i] > 0))
|
|
2225
|
+
ms[i] = 0;
|
|
2226
|
+
else {
|
|
2227
|
+
ms[i] = 3 * (dxs[i - 1] + dxs[i]) / (
|
|
2228
|
+
(2 * dxs[i] + dxs[i - 1]) / ds[i - 1] +
|
|
2229
|
+
(dxs[i] + 2 * dxs[i - 1]) / ds[i]
|
|
2230
|
+
);
|
|
2231
|
+
|
|
2232
|
+
if (!isFinite(ms[i]))
|
|
2233
|
+
ms[i] = 0;
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
ms[n - 1] = ds[n - 2];
|
|
2238
|
+
|
|
2239
|
+
for (let i = 0; i < n - 1; i++) {
|
|
2240
|
+
bezierCurveTo(
|
|
2241
|
+
path,
|
|
2242
|
+
xs[i] + dxs[i] / 3,
|
|
2243
|
+
ys[i] + ms[i] * dxs[i] / 3,
|
|
2244
|
+
xs[i + 1] - dxs[i] / 3,
|
|
2245
|
+
ys[i + 1] - ms[i + 1] * dxs[i] / 3,
|
|
2246
|
+
xs[i + 1],
|
|
2247
|
+
ys[i + 1],
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
return path;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
const cursorPlots = new Set();
|
|
2256
|
+
|
|
2257
|
+
function invalidateRects() {
|
|
2258
|
+
cursorPlots.forEach(u => {
|
|
2259
|
+
u.syncRect(true);
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
on(resize, win, invalidateRects);
|
|
2264
|
+
on(scroll, win, invalidateRects, true);
|
|
2265
|
+
|
|
2266
|
+
const linearPath = linear() ;
|
|
2267
|
+
const pointsPath = points() ;
|
|
2268
|
+
|
|
2269
|
+
function setDefaults(d, xo, yo, initY) {
|
|
2270
|
+
let d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));
|
|
2271
|
+
return d2.map((o, i) => setDefault(o, i, xo, yo));
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
function setDefaults2(d, xyo) {
|
|
2275
|
+
return d.map((o, i) => i == 0 ? null : assign({}, xyo, o)); // todo: assign() will not merge facet arrays
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
function setDefault(o, i, xo, yo) {
|
|
2279
|
+
return assign({}, (i == 0 ? xo : yo), o);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
function snapNumX(self, dataMin, dataMax) {
|
|
2283
|
+
return dataMin == null ? nullNullTuple : [dataMin, dataMax];
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
const snapTimeX = snapNumX;
|
|
2287
|
+
|
|
2288
|
+
// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
|
|
2289
|
+
// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
|
|
2290
|
+
function snapNumY(self, dataMin, dataMax) {
|
|
2291
|
+
return dataMin == null ? nullNullTuple : rangeNum(dataMin, dataMax, rangePad, true);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
function snapLogY(self, dataMin, dataMax, scale) {
|
|
2295
|
+
return dataMin == null ? nullNullTuple : rangeLog(dataMin, dataMax, self.scales[scale].log, false);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
const snapLogX = snapLogY;
|
|
2299
|
+
|
|
2300
|
+
function snapAsinhY(self, dataMin, dataMax, scale) {
|
|
2301
|
+
return dataMin == null ? nullNullTuple : rangeAsinh(dataMin, dataMax, self.scales[scale].log, false);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
const snapAsinhX = snapAsinhY;
|
|
2305
|
+
|
|
2306
|
+
// dim is logical (getClientBoundingRect) pixels, not canvas pixels
|
|
2307
|
+
function findIncr(min, max, incrs, dim, minSpace) {
|
|
2308
|
+
let pxPerUnit = dim / (max - min);
|
|
2309
|
+
|
|
2310
|
+
let minDec = (""+floor(min)).length;
|
|
2311
|
+
|
|
2312
|
+
for (var i = 0; i < incrs.length; i++) {
|
|
2313
|
+
let space = incrs[i] * pxPerUnit;
|
|
2314
|
+
|
|
2315
|
+
let incrDec = incrs[i] < 10 ? fixedDec.get(incrs[i]) : 0;
|
|
2316
|
+
|
|
2317
|
+
if (space >= minSpace && minDec + incrDec < 17)
|
|
2318
|
+
return [incrs[i], space];
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
return [0, 0];
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
function pxRatioFont(font) {
|
|
2325
|
+
let fontSize, fontSizeCss;
|
|
2326
|
+
font = font.replace(/(\d+)px/, (m, p1) => (fontSize = round((fontSizeCss = +p1) * pxRatio)) + 'px');
|
|
2327
|
+
return [font, fontSize, fontSizeCss];
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
function syncFontSize(axis) {
|
|
2331
|
+
if (axis.show) {
|
|
2332
|
+
[axis.font, axis.labelFont].forEach(f => {
|
|
2333
|
+
let size = roundDec(f[2] * pxRatio, 1);
|
|
2334
|
+
f[0] = f[0].replace(/[0-9.]+px/, size + 'px');
|
|
2335
|
+
f[1] = size;
|
|
2336
|
+
});
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
function uPlot(opts, data, then) {
|
|
2341
|
+
const self = {
|
|
2342
|
+
mode: opts.mode ?? 1,
|
|
2343
|
+
};
|
|
2344
|
+
|
|
2345
|
+
const mode = self.mode;
|
|
2346
|
+
|
|
2347
|
+
// TODO: cache denoms & mins scale.cache = {r, min, }
|
|
2348
|
+
function getValPct(val, scale) {
|
|
2349
|
+
let _val = (
|
|
2350
|
+
scale.distr == 3 ? log10(val > 0 ? val : scale.clamp(self, val, scale.min, scale.max, scale.key)) :
|
|
2351
|
+
scale.distr == 4 ? asinh(val, scale.asinh) :
|
|
2352
|
+
val
|
|
2353
|
+
);
|
|
2354
|
+
|
|
2355
|
+
return (_val - scale._min) / (scale._max - scale._min);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
function getHPos(val, scale, dim, off) {
|
|
2359
|
+
let pct = getValPct(val, scale);
|
|
2360
|
+
return off + dim * (scale.dir == -1 ? (1 - pct) : pct);
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
function getVPos(val, scale, dim, off) {
|
|
2364
|
+
let pct = getValPct(val, scale);
|
|
2365
|
+
return off + dim * (scale.dir == -1 ? pct : (1 - pct));
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function getPos(val, scale, dim, off) {
|
|
2369
|
+
return scale.ori == 0 ? getHPos(val, scale, dim, off) : getVPos(val, scale, dim, off);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
self.valToPosH = getHPos;
|
|
2373
|
+
self.valToPosV = getVPos;
|
|
2374
|
+
|
|
2375
|
+
let ready = false;
|
|
2376
|
+
self.status = 0;
|
|
2377
|
+
|
|
2378
|
+
const root = self.root = placeDiv(UPLOT);
|
|
2379
|
+
|
|
2380
|
+
if (opts.id != null)
|
|
2381
|
+
root.id = opts.id;
|
|
2382
|
+
|
|
2383
|
+
addClass(root, opts.class);
|
|
2384
|
+
|
|
2385
|
+
if (opts.title) {
|
|
2386
|
+
let title = placeDiv(TITLE, root);
|
|
2387
|
+
title.textContent = opts.title;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
const can = placeTag("canvas");
|
|
2391
|
+
const ctx = self.ctx = can.getContext("2d");
|
|
2392
|
+
|
|
2393
|
+
const wrap = placeDiv(WRAP, root);
|
|
2394
|
+
const under = self.under = placeDiv(UNDER, wrap);
|
|
2395
|
+
wrap.appendChild(can);
|
|
2396
|
+
const over = self.over = placeDiv(OVER, wrap);
|
|
2397
|
+
|
|
2398
|
+
opts = copy(opts);
|
|
2399
|
+
|
|
2400
|
+
const pxAlign = +ifNull(opts.pxAlign, 1);
|
|
2401
|
+
|
|
2402
|
+
const pxRound = pxRoundGen(pxAlign);
|
|
2403
|
+
|
|
2404
|
+
(opts.plugins || []).forEach(p => {
|
|
2405
|
+
if (p.opts)
|
|
2406
|
+
opts = p.opts(self, opts) || opts;
|
|
2407
|
+
});
|
|
2408
|
+
|
|
2409
|
+
const ms = opts.ms || 1e-3;
|
|
2410
|
+
|
|
2411
|
+
const series = self.series = mode == 1 ?
|
|
2412
|
+
setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false) :
|
|
2413
|
+
setDefaults2(opts.series || [null], xySeriesOpts);
|
|
2414
|
+
const axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);
|
|
2415
|
+
const scales = self.scales = {};
|
|
2416
|
+
const bands = self.bands = opts.bands || [];
|
|
2417
|
+
|
|
2418
|
+
bands.forEach(b => {
|
|
2419
|
+
b.fill = fnOrSelf(b.fill || null);
|
|
2420
|
+
});
|
|
2421
|
+
|
|
2422
|
+
const xScaleKey = mode == 2 ? series[1].facets[0].scale : series[0].scale;
|
|
2423
|
+
|
|
2424
|
+
const drawOrderMap = {
|
|
2425
|
+
axes: drawAxesGrid,
|
|
2426
|
+
series: drawSeries,
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
const drawOrder = (opts.drawOrder || ["axes", "series"]).map(key => drawOrderMap[key]);
|
|
2430
|
+
|
|
2431
|
+
function initScale(scaleKey) {
|
|
2432
|
+
let sc = scales[scaleKey];
|
|
2433
|
+
|
|
2434
|
+
if (sc == null) {
|
|
2435
|
+
let scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;
|
|
2436
|
+
|
|
2437
|
+
if (scaleOpts.from != null) {
|
|
2438
|
+
// ensure parent is initialized
|
|
2439
|
+
initScale(scaleOpts.from);
|
|
2440
|
+
// dependent scales inherit
|
|
2441
|
+
scales[scaleKey] = assign({}, scales[scaleOpts.from], scaleOpts);
|
|
2442
|
+
}
|
|
2443
|
+
else {
|
|
2444
|
+
sc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);
|
|
2445
|
+
|
|
2446
|
+
if (mode == 2)
|
|
2447
|
+
sc.time = false;
|
|
2448
|
+
|
|
2449
|
+
sc.key = scaleKey;
|
|
2450
|
+
|
|
2451
|
+
let isTime = sc.time;
|
|
2452
|
+
|
|
2453
|
+
let rn = sc.range;
|
|
2454
|
+
|
|
2455
|
+
let rangeIsArr = isArr(rn);
|
|
2456
|
+
|
|
2457
|
+
if (scaleKey != xScaleKey || mode == 2) {
|
|
2458
|
+
// if range array has null limits, it should be auto
|
|
2459
|
+
if (rangeIsArr && (rn[0] == null || rn[1] == null)) {
|
|
2460
|
+
rn = {
|
|
2461
|
+
min: rn[0] == null ? autoRangePart : {
|
|
2462
|
+
mode: 1,
|
|
2463
|
+
hard: rn[0],
|
|
2464
|
+
soft: rn[0],
|
|
2465
|
+
},
|
|
2466
|
+
max: rn[1] == null ? autoRangePart : {
|
|
2467
|
+
mode: 1,
|
|
2468
|
+
hard: rn[1],
|
|
2469
|
+
soft: rn[1],
|
|
2470
|
+
},
|
|
2471
|
+
};
|
|
2472
|
+
rangeIsArr = false;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
if (!rangeIsArr && isObj(rn)) {
|
|
2476
|
+
let cfg = rn;
|
|
2477
|
+
// this is similar to snapNumY
|
|
2478
|
+
rn = (self, dataMin, dataMax) => dataMin == null ? nullNullTuple : rangeNum(dataMin, dataMax, cfg);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
sc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ?
|
|
2483
|
+
(sc.distr == 3 ? snapLogX : sc.distr == 4 ? snapAsinhX : snapNumX) :
|
|
2484
|
+
(sc.distr == 3 ? snapLogY : sc.distr == 4 ? snapAsinhY : snapNumY)
|
|
2485
|
+
));
|
|
2486
|
+
|
|
2487
|
+
sc.auto = fnOrSelf(rangeIsArr ? false : sc.auto);
|
|
2488
|
+
|
|
2489
|
+
sc.clamp = fnOrSelf(sc.clamp || clampScale);
|
|
2490
|
+
|
|
2491
|
+
// caches for expensive ops like asinh() & log()
|
|
2492
|
+
sc._min = sc._max = null;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
initScale("x");
|
|
2498
|
+
initScale("y");
|
|
2499
|
+
|
|
2500
|
+
// TODO: init scales from facets in mode: 2
|
|
2501
|
+
if (mode == 1) {
|
|
2502
|
+
series.forEach(s => {
|
|
2503
|
+
initScale(s.scale);
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
axes.forEach(a => {
|
|
2508
|
+
initScale(a.scale);
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2511
|
+
for (let k in opts.scales)
|
|
2512
|
+
initScale(k);
|
|
2513
|
+
|
|
2514
|
+
const scaleX = scales[xScaleKey];
|
|
2515
|
+
|
|
2516
|
+
const xScaleDistr = scaleX.distr;
|
|
2517
|
+
|
|
2518
|
+
let valToPosX, valToPosY;
|
|
2519
|
+
|
|
2520
|
+
if (scaleX.ori == 0) {
|
|
2521
|
+
addClass(root, ORI_HZ);
|
|
2522
|
+
valToPosX = getHPos;
|
|
2523
|
+
valToPosY = getVPos;
|
|
2524
|
+
/*
|
|
2525
|
+
updOriDims = () => {
|
|
2526
|
+
xDimCan = plotWid;
|
|
2527
|
+
xOffCan = plotLft;
|
|
2528
|
+
yDimCan = plotHgt;
|
|
2529
|
+
yOffCan = plotTop;
|
|
2530
|
+
|
|
2531
|
+
xDimCss = plotWidCss;
|
|
2532
|
+
xOffCss = plotLftCss;
|
|
2533
|
+
yDimCss = plotHgtCss;
|
|
2534
|
+
yOffCss = plotTopCss;
|
|
2535
|
+
};
|
|
2536
|
+
*/
|
|
2537
|
+
}
|
|
2538
|
+
else {
|
|
2539
|
+
addClass(root, ORI_VT);
|
|
2540
|
+
valToPosX = getVPos;
|
|
2541
|
+
valToPosY = getHPos;
|
|
2542
|
+
/*
|
|
2543
|
+
updOriDims = () => {
|
|
2544
|
+
xDimCan = plotHgt;
|
|
2545
|
+
xOffCan = plotTop;
|
|
2546
|
+
yDimCan = plotWid;
|
|
2547
|
+
yOffCan = plotLft;
|
|
2548
|
+
|
|
2549
|
+
xDimCss = plotHgtCss;
|
|
2550
|
+
xOffCss = plotTopCss;
|
|
2551
|
+
yDimCss = plotWidCss;
|
|
2552
|
+
yOffCss = plotLftCss;
|
|
2553
|
+
};
|
|
2554
|
+
*/
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
const pendScales = {};
|
|
2558
|
+
|
|
2559
|
+
// explicitly-set initial scales
|
|
2560
|
+
for (let k in scales) {
|
|
2561
|
+
let sc = scales[k];
|
|
2562
|
+
|
|
2563
|
+
if (sc.min != null || sc.max != null) {
|
|
2564
|
+
pendScales[k] = {min: sc.min, max: sc.max};
|
|
2565
|
+
sc.min = sc.max = null;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
// self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
2570
|
+
const _tzDate = (opts.tzDate || (ts => new Date(round(ts / ms))));
|
|
2571
|
+
const _fmtDate = (opts.fmtDate || fmtDate);
|
|
2572
|
+
|
|
2573
|
+
const _timeAxisSplits = (ms == 1 ? timeAxisSplitsMs(_tzDate) : timeAxisSplitsS(_tzDate));
|
|
2574
|
+
const _timeAxisVals = timeAxisVals(_tzDate, timeAxisStamps((ms == 1 ? _timeAxisStampsMs : _timeAxisStampsS), _fmtDate));
|
|
2575
|
+
const _timeSeriesVal = timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));
|
|
2576
|
+
|
|
2577
|
+
const activeIdxs = [];
|
|
2578
|
+
|
|
2579
|
+
const legend = (self.legend = assign({}, legendOpts, opts.legend));
|
|
2580
|
+
const showLegend = legend.show;
|
|
2581
|
+
const markers = legend.markers;
|
|
2582
|
+
|
|
2583
|
+
{
|
|
2584
|
+
legend.idxs = activeIdxs;
|
|
2585
|
+
|
|
2586
|
+
markers.width = fnOrSelf(markers.width);
|
|
2587
|
+
markers.dash = fnOrSelf(markers.dash);
|
|
2588
|
+
markers.stroke = fnOrSelf(markers.stroke);
|
|
2589
|
+
markers.fill = fnOrSelf(markers.fill);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
let legendEl;
|
|
2593
|
+
let legendRows = [];
|
|
2594
|
+
let legendCells = [];
|
|
2595
|
+
let legendCols;
|
|
2596
|
+
let multiValLegend = false;
|
|
2597
|
+
let NULL_LEGEND_VALUES = {};
|
|
2598
|
+
|
|
2599
|
+
if (legend.live) {
|
|
2600
|
+
const getMultiVals = series[1] ? series[1].values : null;
|
|
2601
|
+
multiValLegend = getMultiVals != null;
|
|
2602
|
+
legendCols = multiValLegend ? getMultiVals(self, 1, 0) : {_: 0};
|
|
2603
|
+
|
|
2604
|
+
for (let k in legendCols)
|
|
2605
|
+
NULL_LEGEND_VALUES[k] = "--";
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
if (showLegend) {
|
|
2609
|
+
legendEl = placeTag("table", LEGEND, root);
|
|
2610
|
+
|
|
2611
|
+
if (multiValLegend) {
|
|
2612
|
+
let head = placeTag("tr", LEGEND_THEAD, legendEl);
|
|
2613
|
+
placeTag("th", null, head);
|
|
2614
|
+
|
|
2615
|
+
for (var key in legendCols)
|
|
2616
|
+
placeTag("th", LEGEND_LABEL, head).textContent = key;
|
|
2617
|
+
}
|
|
2618
|
+
else {
|
|
2619
|
+
addClass(legendEl, LEGEND_INLINE);
|
|
2620
|
+
legend.live && addClass(legendEl, LEGEND_LIVE);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
const son = {show: true};
|
|
2625
|
+
const soff = {show: false};
|
|
2626
|
+
|
|
2627
|
+
function initLegendRow(s, i) {
|
|
2628
|
+
if (i == 0 && (multiValLegend || !legend.live || mode == 2))
|
|
2629
|
+
return nullNullTuple;
|
|
2630
|
+
|
|
2631
|
+
let cells = [];
|
|
2632
|
+
|
|
2633
|
+
let row = placeTag("tr", LEGEND_SERIES, legendEl, legendEl.childNodes[i]);
|
|
2634
|
+
|
|
2635
|
+
addClass(row, s.class);
|
|
2636
|
+
|
|
2637
|
+
if (!s.show)
|
|
2638
|
+
addClass(row, OFF);
|
|
2639
|
+
|
|
2640
|
+
let label = placeTag("th", null, row);
|
|
2641
|
+
|
|
2642
|
+
if (markers.show) {
|
|
2643
|
+
let indic = placeDiv(LEGEND_MARKER, label);
|
|
2644
|
+
|
|
2645
|
+
if (i > 0) {
|
|
2646
|
+
let width = markers.width(self, i);
|
|
2647
|
+
|
|
2648
|
+
if (width)
|
|
2649
|
+
indic.style.border = width + "px " + markers.dash(self, i) + " " + markers.stroke(self, i);
|
|
2650
|
+
|
|
2651
|
+
indic.style.background = markers.fill(self, i);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
let text = placeDiv(LEGEND_LABEL, label);
|
|
2656
|
+
text.textContent = s.label;
|
|
2657
|
+
|
|
2658
|
+
if (i > 0) {
|
|
2659
|
+
if (!markers.show)
|
|
2660
|
+
text.style.color = s.width > 0 ? markers.stroke(self, i) : markers.fill(self, i);
|
|
2661
|
+
|
|
2662
|
+
onMouse("click", label, e => {
|
|
2663
|
+
if (cursor._lock)
|
|
2664
|
+
return;
|
|
2665
|
+
|
|
2666
|
+
let seriesIdx = series.indexOf(s);
|
|
2667
|
+
|
|
2668
|
+
if (e.ctrlKey != legend.isolate) {
|
|
2669
|
+
// if any other series is shown, isolate this one. else show all
|
|
2670
|
+
let isolate = series.some((s, i) => i > 0 && i != seriesIdx && s.show);
|
|
2671
|
+
|
|
2672
|
+
series.forEach((s, i) => {
|
|
2673
|
+
i > 0 && setSeries(i, isolate ? (i == seriesIdx ? son : soff) : son, true, syncOpts.setSeries);
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
else
|
|
2677
|
+
setSeries(seriesIdx, {show: !s.show}, true, syncOpts.setSeries);
|
|
2678
|
+
});
|
|
2679
|
+
|
|
2680
|
+
if (cursorFocus) {
|
|
2681
|
+
onMouse(mouseenter, label, e => {
|
|
2682
|
+
if (cursor._lock)
|
|
2683
|
+
return;
|
|
2684
|
+
|
|
2685
|
+
setSeries(series.indexOf(s), FOCUS_TRUE, true, syncOpts.setSeries);
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
for (var key in legendCols) {
|
|
2691
|
+
let v = placeTag("td", LEGEND_VALUE, row);
|
|
2692
|
+
v.textContent = "--";
|
|
2693
|
+
cells.push(v);
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
return [row, cells];
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
const mouseListeners = new Map();
|
|
2700
|
+
|
|
2701
|
+
function onMouse(ev, targ, fn) {
|
|
2702
|
+
const targListeners = mouseListeners.get(targ) || {};
|
|
2703
|
+
const listener = cursor.bind[ev](self, targ, fn);
|
|
2704
|
+
|
|
2705
|
+
if (listener) {
|
|
2706
|
+
on(ev, targ, targListeners[ev] = listener);
|
|
2707
|
+
mouseListeners.set(targ, targListeners);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
function offMouse(ev, targ, fn) {
|
|
2712
|
+
const targListeners = mouseListeners.get(targ) || {};
|
|
2713
|
+
|
|
2714
|
+
for (let k in targListeners) {
|
|
2715
|
+
if (ev == null || k == ev) {
|
|
2716
|
+
off(k, targ, targListeners[k]);
|
|
2717
|
+
delete targListeners[k];
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
if (ev == null)
|
|
2722
|
+
mouseListeners.delete(targ);
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
let fullWidCss = 0;
|
|
2726
|
+
let fullHgtCss = 0;
|
|
2727
|
+
|
|
2728
|
+
let plotWidCss = 0;
|
|
2729
|
+
let plotHgtCss = 0;
|
|
2730
|
+
|
|
2731
|
+
// plot margins to account for axes
|
|
2732
|
+
let plotLftCss = 0;
|
|
2733
|
+
let plotTopCss = 0;
|
|
2734
|
+
|
|
2735
|
+
let plotLft = 0;
|
|
2736
|
+
let plotTop = 0;
|
|
2737
|
+
let plotWid = 0;
|
|
2738
|
+
let plotHgt = 0;
|
|
2739
|
+
|
|
2740
|
+
self.bbox = {};
|
|
2741
|
+
|
|
2742
|
+
let shouldSetScales = false;
|
|
2743
|
+
let shouldSetSize = false;
|
|
2744
|
+
let shouldConvergeSize = false;
|
|
2745
|
+
let shouldSetCursor = false;
|
|
2746
|
+
let shouldSetLegend = false;
|
|
2747
|
+
|
|
2748
|
+
function _setSize(width, height, force) {
|
|
2749
|
+
if (force || (width != self.width || height != self.height))
|
|
2750
|
+
calcSize(width, height);
|
|
2751
|
+
|
|
2752
|
+
resetYSeries(false);
|
|
2753
|
+
|
|
2754
|
+
shouldConvergeSize = true;
|
|
2755
|
+
shouldSetSize = true;
|
|
2756
|
+
shouldSetCursor = shouldSetLegend = cursor.left >= 0;
|
|
2757
|
+
commit();
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
function calcSize(width, height) {
|
|
2761
|
+
// log("calcSize()", arguments);
|
|
2762
|
+
|
|
2763
|
+
self.width = fullWidCss = plotWidCss = width;
|
|
2764
|
+
self.height = fullHgtCss = plotHgtCss = height;
|
|
2765
|
+
plotLftCss = plotTopCss = 0;
|
|
2766
|
+
|
|
2767
|
+
calcPlotRect();
|
|
2768
|
+
calcAxesRects();
|
|
2769
|
+
|
|
2770
|
+
let bb = self.bbox;
|
|
2771
|
+
|
|
2772
|
+
plotLft = bb.left = incrRound(plotLftCss * pxRatio, 0.5);
|
|
2773
|
+
plotTop = bb.top = incrRound(plotTopCss * pxRatio, 0.5);
|
|
2774
|
+
plotWid = bb.width = incrRound(plotWidCss * pxRatio, 0.5);
|
|
2775
|
+
plotHgt = bb.height = incrRound(plotHgtCss * pxRatio, 0.5);
|
|
2776
|
+
|
|
2777
|
+
// updOriDims();
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
// ensures size calc convergence
|
|
2781
|
+
const CYCLE_LIMIT = 3;
|
|
2782
|
+
|
|
2783
|
+
function convergeSize() {
|
|
2784
|
+
let converged = false;
|
|
2785
|
+
|
|
2786
|
+
let cycleNum = 0;
|
|
2787
|
+
|
|
2788
|
+
while (!converged) {
|
|
2789
|
+
cycleNum++;
|
|
2790
|
+
|
|
2791
|
+
let axesConverged = axesCalc(cycleNum);
|
|
2792
|
+
let paddingConverged = paddingCalc(cycleNum);
|
|
2793
|
+
|
|
2794
|
+
converged = cycleNum == CYCLE_LIMIT || (axesConverged && paddingConverged);
|
|
2795
|
+
|
|
2796
|
+
if (!converged) {
|
|
2797
|
+
calcSize(self.width, self.height);
|
|
2798
|
+
shouldSetSize = true;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
function setSize({width, height}) {
|
|
2804
|
+
_setSize(width, height);
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
self.setSize = setSize;
|
|
2808
|
+
|
|
2809
|
+
// accumulate axis offsets, reduce canvas width
|
|
2810
|
+
function calcPlotRect() {
|
|
2811
|
+
// easements for edge labels
|
|
2812
|
+
let hasTopAxis = false;
|
|
2813
|
+
let hasBtmAxis = false;
|
|
2814
|
+
let hasRgtAxis = false;
|
|
2815
|
+
let hasLftAxis = false;
|
|
2816
|
+
|
|
2817
|
+
axes.forEach((axis, i) => {
|
|
2818
|
+
if (axis.show && axis._show) {
|
|
2819
|
+
let {side, _size} = axis;
|
|
2820
|
+
let isVt = side % 2;
|
|
2821
|
+
let labelSize = axis.labelSize = (axis.label != null ? (axis.labelSize || 30) : 0);
|
|
2822
|
+
|
|
2823
|
+
let fullSize = _size + labelSize;
|
|
2824
|
+
|
|
2825
|
+
if (fullSize > 0) {
|
|
2826
|
+
if (isVt) {
|
|
2827
|
+
plotWidCss -= fullSize;
|
|
2828
|
+
|
|
2829
|
+
if (side == 3) {
|
|
2830
|
+
plotLftCss += fullSize;
|
|
2831
|
+
hasLftAxis = true;
|
|
2832
|
+
}
|
|
2833
|
+
else
|
|
2834
|
+
hasRgtAxis = true;
|
|
2835
|
+
}
|
|
2836
|
+
else {
|
|
2837
|
+
plotHgtCss -= fullSize;
|
|
2838
|
+
|
|
2839
|
+
if (side == 0) {
|
|
2840
|
+
plotTopCss += fullSize;
|
|
2841
|
+
hasTopAxis = true;
|
|
2842
|
+
}
|
|
2843
|
+
else
|
|
2844
|
+
hasBtmAxis = true;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
|
|
2850
|
+
sidesWithAxes[0] = hasTopAxis;
|
|
2851
|
+
sidesWithAxes[1] = hasRgtAxis;
|
|
2852
|
+
sidesWithAxes[2] = hasBtmAxis;
|
|
2853
|
+
sidesWithAxes[3] = hasLftAxis;
|
|
2854
|
+
|
|
2855
|
+
// hz padding
|
|
2856
|
+
plotWidCss -= _padding[1] + _padding[3];
|
|
2857
|
+
plotLftCss += _padding[3];
|
|
2858
|
+
|
|
2859
|
+
// vt padding
|
|
2860
|
+
plotHgtCss -= _padding[2] + _padding[0];
|
|
2861
|
+
plotTopCss += _padding[0];
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
function calcAxesRects() {
|
|
2865
|
+
// will accum +
|
|
2866
|
+
let off1 = plotLftCss + plotWidCss;
|
|
2867
|
+
let off2 = plotTopCss + plotHgtCss;
|
|
2868
|
+
// will accum -
|
|
2869
|
+
let off3 = plotLftCss;
|
|
2870
|
+
let off0 = plotTopCss;
|
|
2871
|
+
|
|
2872
|
+
function incrOffset(side, size) {
|
|
2873
|
+
switch (side) {
|
|
2874
|
+
case 1: off1 += size; return off1 - size;
|
|
2875
|
+
case 2: off2 += size; return off2 - size;
|
|
2876
|
+
case 3: off3 -= size; return off3 + size;
|
|
2877
|
+
case 0: off0 -= size; return off0 + size;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
axes.forEach((axis, i) => {
|
|
2882
|
+
if (axis.show && axis._show) {
|
|
2883
|
+
let side = axis.side;
|
|
2884
|
+
|
|
2885
|
+
axis._pos = incrOffset(side, axis._size);
|
|
2886
|
+
|
|
2887
|
+
if (axis.label != null)
|
|
2888
|
+
axis._lpos = incrOffset(side, axis.labelSize);
|
|
2889
|
+
}
|
|
2890
|
+
});
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
const cursor = (self.cursor = assign({}, cursorOpts, {drag: {y: mode == 2}}, opts.cursor));
|
|
2894
|
+
|
|
2895
|
+
{
|
|
2896
|
+
cursor.idxs = activeIdxs;
|
|
2897
|
+
|
|
2898
|
+
cursor._lock = false;
|
|
2899
|
+
|
|
2900
|
+
let points = cursor.points;
|
|
2901
|
+
|
|
2902
|
+
points.show = fnOrSelf(points.show);
|
|
2903
|
+
points.size = fnOrSelf(points.size);
|
|
2904
|
+
points.stroke = fnOrSelf(points.stroke);
|
|
2905
|
+
points.width = fnOrSelf(points.width);
|
|
2906
|
+
points.fill = fnOrSelf(points.fill);
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
const focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);
|
|
2910
|
+
const cursorFocus = focus.prox >= 0;
|
|
2911
|
+
|
|
2912
|
+
// series-intersection markers
|
|
2913
|
+
let cursorPts = [null];
|
|
2914
|
+
|
|
2915
|
+
function initCursorPt(s, si) {
|
|
2916
|
+
if (si > 0) {
|
|
2917
|
+
let pt = cursor.points.show(self, si);
|
|
2918
|
+
|
|
2919
|
+
if (pt) {
|
|
2920
|
+
addClass(pt, CURSOR_PT);
|
|
2921
|
+
addClass(pt, s.class);
|
|
2922
|
+
elTrans(pt, -10, -10, plotWidCss, plotHgtCss);
|
|
2923
|
+
over.insertBefore(pt, cursorPts[si]);
|
|
2924
|
+
|
|
2925
|
+
return pt;
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
function initSeries(s, i) {
|
|
2931
|
+
if (mode == 1 || i > 0) {
|
|
2932
|
+
let isTime = mode == 1 && scales[s.scale].time;
|
|
2933
|
+
|
|
2934
|
+
let sv = s.value;
|
|
2935
|
+
s.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;
|
|
2936
|
+
s.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
if (i > 0) {
|
|
2940
|
+
s.width = s.width == null ? 1 : s.width;
|
|
2941
|
+
s.paths = s.paths || linearPath || retNull;
|
|
2942
|
+
s.fillTo = fnOrSelf(s.fillTo || seriesFillTo);
|
|
2943
|
+
s.pxAlign = +ifNull(s.pxAlign, pxAlign);
|
|
2944
|
+
s.pxRound = pxRoundGen(s.pxAlign);
|
|
2945
|
+
|
|
2946
|
+
s.stroke = fnOrSelf(s.stroke || null);
|
|
2947
|
+
s.fill = fnOrSelf(s.fill || null);
|
|
2948
|
+
s._stroke = s._fill = s._paths = s._focus = null;
|
|
2949
|
+
|
|
2950
|
+
let _ptDia = ptDia(s.width, 1);
|
|
2951
|
+
let points = s.points = assign({}, {
|
|
2952
|
+
size: _ptDia,
|
|
2953
|
+
width: max(1, _ptDia * .2),
|
|
2954
|
+
stroke: s.stroke,
|
|
2955
|
+
space: _ptDia * 2,
|
|
2956
|
+
paths: pointsPath,
|
|
2957
|
+
_stroke: null,
|
|
2958
|
+
_fill: null,
|
|
2959
|
+
}, s.points);
|
|
2960
|
+
points.show = fnOrSelf(points.show);
|
|
2961
|
+
points.filter = fnOrSelf(points.filter);
|
|
2962
|
+
points.fill = fnOrSelf(points.fill);
|
|
2963
|
+
points.stroke = fnOrSelf(points.stroke);
|
|
2964
|
+
points.paths = fnOrSelf(points.paths);
|
|
2965
|
+
points.pxAlign = s.pxAlign;
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
if (showLegend) {
|
|
2969
|
+
let rowCells = initLegendRow(s, i);
|
|
2970
|
+
legendRows.splice(i, 0, rowCells[0]);
|
|
2971
|
+
legendCells.splice(i, 0, rowCells[1]);
|
|
2972
|
+
legend.values.push(null); // NULL_LEGEND_VALS not yet avil here :(
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
if (cursor.show) {
|
|
2976
|
+
activeIdxs.splice(i, 0, null);
|
|
2977
|
+
|
|
2978
|
+
let pt = initCursorPt(s, i);
|
|
2979
|
+
pt && cursorPts.splice(i, 0, pt);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
function addSeries(opts, si) {
|
|
2984
|
+
si = si == null ? series.length : si;
|
|
2985
|
+
|
|
2986
|
+
opts = setDefault(opts, si, xSeriesOpts, ySeriesOpts);
|
|
2987
|
+
series.splice(si, 0, opts);
|
|
2988
|
+
initSeries(series[si], si);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
self.addSeries = addSeries;
|
|
2992
|
+
|
|
2993
|
+
function delSeries(i) {
|
|
2994
|
+
series.splice(i, 1);
|
|
2995
|
+
|
|
2996
|
+
if (showLegend) {
|
|
2997
|
+
legend.values.splice(i, 1);
|
|
2998
|
+
|
|
2999
|
+
legendCells.splice(i, 1);
|
|
3000
|
+
let tr = legendRows.splice(i, 1)[0];
|
|
3001
|
+
offMouse(null, tr.firstChild);
|
|
3002
|
+
tr.remove();
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
if (cursor.show) {
|
|
3006
|
+
activeIdxs.splice(i, 1);
|
|
3007
|
+
|
|
3008
|
+
cursorPts.length > 1 && cursorPts.splice(i, 1)[0].remove();
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
// TODO: de-init no-longer-needed scales?
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
self.delSeries = delSeries;
|
|
3015
|
+
|
|
3016
|
+
const sidesWithAxes = [false, false, false, false];
|
|
3017
|
+
|
|
3018
|
+
function initAxis(axis, i) {
|
|
3019
|
+
axis._show = axis.show;
|
|
3020
|
+
|
|
3021
|
+
if (axis.show) {
|
|
3022
|
+
let isVt = axis.side % 2;
|
|
3023
|
+
|
|
3024
|
+
let sc = scales[axis.scale];
|
|
3025
|
+
|
|
3026
|
+
// this can occur if all series specify non-default scales
|
|
3027
|
+
if (sc == null) {
|
|
3028
|
+
axis.scale = isVt ? series[1].scale : xScaleKey;
|
|
3029
|
+
sc = scales[axis.scale];
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
// also set defaults for incrs & values based on axis distr
|
|
3033
|
+
let isTime = sc.time;
|
|
3034
|
+
|
|
3035
|
+
axis.size = fnOrSelf(axis.size);
|
|
3036
|
+
axis.space = fnOrSelf(axis.space);
|
|
3037
|
+
axis.rotate = fnOrSelf(axis.rotate);
|
|
3038
|
+
axis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? (ms == 1 ? timeIncrsMs : timeIncrsS) : numIncrs)));
|
|
3039
|
+
axis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : sc.distr == 4 ? asinhAxisSplits : numAxisSplits));
|
|
3040
|
+
|
|
3041
|
+
axis.stroke = fnOrSelf(axis.stroke);
|
|
3042
|
+
axis.grid.stroke = fnOrSelf(axis.grid.stroke);
|
|
3043
|
+
axis.ticks.stroke = fnOrSelf(axis.ticks.stroke);
|
|
3044
|
+
|
|
3045
|
+
let av = axis.values;
|
|
3046
|
+
|
|
3047
|
+
axis.values = (
|
|
3048
|
+
// static array of tick values
|
|
3049
|
+
isArr(av) && !isArr(av[0]) ? fnOrSelf(av) :
|
|
3050
|
+
// temporal
|
|
3051
|
+
isTime ? (
|
|
3052
|
+
// config array of fmtDate string tpls
|
|
3053
|
+
isArr(av) ?
|
|
3054
|
+
timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :
|
|
3055
|
+
// fmtDate string tpl
|
|
3056
|
+
isStr(av) ?
|
|
3057
|
+
timeAxisVal(_tzDate, av) :
|
|
3058
|
+
av || _timeAxisVals
|
|
3059
|
+
) : av || numAxisVals
|
|
3060
|
+
);
|
|
3061
|
+
|
|
3062
|
+
axis.filter = fnOrSelf(axis.filter || ( sc.distr >= 3 ? logAxisValsFilt : retArg1));
|
|
3063
|
+
|
|
3064
|
+
axis.font = pxRatioFont(axis.font);
|
|
3065
|
+
axis.labelFont = pxRatioFont(axis.labelFont);
|
|
3066
|
+
|
|
3067
|
+
axis._size = axis.size(self, null, i, 0);
|
|
3068
|
+
|
|
3069
|
+
axis._space =
|
|
3070
|
+
axis._rotate =
|
|
3071
|
+
axis._incrs =
|
|
3072
|
+
axis._found = // foundIncrSpace
|
|
3073
|
+
axis._splits =
|
|
3074
|
+
axis._values = null;
|
|
3075
|
+
|
|
3076
|
+
if (axis._size > 0)
|
|
3077
|
+
sidesWithAxes[i] = true;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
function autoPadSide(self, side, sidesWithAxes, cycleNum) {
|
|
3082
|
+
let [hasTopAxis, hasRgtAxis, hasBtmAxis, hasLftAxis] = sidesWithAxes;
|
|
3083
|
+
|
|
3084
|
+
let ori = side % 2;
|
|
3085
|
+
let size = 0;
|
|
3086
|
+
|
|
3087
|
+
if (ori == 0 && (hasLftAxis || hasRgtAxis))
|
|
3088
|
+
size = (side == 0 && !hasTopAxis || side == 2 && !hasBtmAxis ? round(xAxisOpts.size / 3) : 0);
|
|
3089
|
+
if (ori == 1 && (hasTopAxis || hasBtmAxis))
|
|
3090
|
+
size = (side == 1 && !hasRgtAxis || side == 3 && !hasLftAxis ? round(yAxisOpts.size / 2) : 0);
|
|
3091
|
+
|
|
3092
|
+
return size;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
const padding = self.padding = (opts.padding || [autoPadSide,autoPadSide,autoPadSide,autoPadSide]).map(p => fnOrSelf(ifNull(p, autoPadSide)));
|
|
3096
|
+
const _padding = self._padding = padding.map((p, i) => p(self, i, sidesWithAxes, 0));
|
|
3097
|
+
|
|
3098
|
+
let dataLen;
|
|
3099
|
+
|
|
3100
|
+
// rendered data window
|
|
3101
|
+
let i0 = null;
|
|
3102
|
+
let i1 = null;
|
|
3103
|
+
const idxs = mode == 1 ? series[0].idxs : null;
|
|
3104
|
+
|
|
3105
|
+
let data0 = null;
|
|
3106
|
+
|
|
3107
|
+
let viaAutoScaleX = false;
|
|
3108
|
+
|
|
3109
|
+
function setData(_data, _resetScales) {
|
|
3110
|
+
if (mode == 2) {
|
|
3111
|
+
dataLen = 0;
|
|
3112
|
+
for (let i = 1; i < series.length; i++)
|
|
3113
|
+
dataLen += data[i][0].length;
|
|
3114
|
+
self.data = data = _data;
|
|
3115
|
+
}
|
|
3116
|
+
else {
|
|
3117
|
+
data = (_data || []).slice();
|
|
3118
|
+
data[0] = data[0] || [];
|
|
3119
|
+
|
|
3120
|
+
self.data = data.slice();
|
|
3121
|
+
data0 = data[0];
|
|
3122
|
+
dataLen = data0.length;
|
|
3123
|
+
|
|
3124
|
+
if (xScaleDistr == 2)
|
|
3125
|
+
data[0] = data0.map((v, i) => i);
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
self._data = data;
|
|
3129
|
+
|
|
3130
|
+
resetYSeries(true);
|
|
3131
|
+
|
|
3132
|
+
fire("setData");
|
|
3133
|
+
|
|
3134
|
+
if (_resetScales !== false) {
|
|
3135
|
+
let xsc = scaleX;
|
|
3136
|
+
|
|
3137
|
+
if (xsc.auto(self, viaAutoScaleX))
|
|
3138
|
+
autoScaleX();
|
|
3139
|
+
else
|
|
3140
|
+
_setScale(xScaleKey, xsc.min, xsc.max);
|
|
3141
|
+
|
|
3142
|
+
shouldSetCursor = cursor.left >= 0;
|
|
3143
|
+
shouldSetLegend = true;
|
|
3144
|
+
commit();
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
self.setData = setData;
|
|
3149
|
+
|
|
3150
|
+
function autoScaleX() {
|
|
3151
|
+
viaAutoScaleX = true;
|
|
3152
|
+
|
|
3153
|
+
let _min, _max;
|
|
3154
|
+
|
|
3155
|
+
if (mode == 1) {
|
|
3156
|
+
if (dataLen > 0) {
|
|
3157
|
+
i0 = idxs[0] = 0;
|
|
3158
|
+
i1 = idxs[1] = dataLen - 1;
|
|
3159
|
+
|
|
3160
|
+
_min = data[0][i0];
|
|
3161
|
+
_max = data[0][i1];
|
|
3162
|
+
|
|
3163
|
+
if (xScaleDistr == 2) {
|
|
3164
|
+
_min = i0;
|
|
3165
|
+
_max = i1;
|
|
3166
|
+
}
|
|
3167
|
+
else if (dataLen == 1) {
|
|
3168
|
+
if (xScaleDistr == 3)
|
|
3169
|
+
[_min, _max] = rangeLog(_min, _min, scaleX.log, false);
|
|
3170
|
+
else if (xScaleDistr == 4)
|
|
3171
|
+
[_min, _max] = rangeAsinh(_min, _min, scaleX.log, false);
|
|
3172
|
+
else if (scaleX.time)
|
|
3173
|
+
_max = _min + round(86400 / ms);
|
|
3174
|
+
else
|
|
3175
|
+
[_min, _max] = rangeNum(_min, _max, rangePad, true);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
else {
|
|
3179
|
+
i0 = idxs[0] = _min = null;
|
|
3180
|
+
i1 = idxs[1] = _max = null;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
_setScale(xScaleKey, _min, _max);
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
let ctxStroke, ctxFill, ctxWidth, ctxDash, ctxJoin, ctxCap, ctxFont, ctxAlign, ctxBaseline;
|
|
3188
|
+
let ctxAlpha;
|
|
3189
|
+
|
|
3190
|
+
function setCtxStyle(stroke = transparent, width, dash = EMPTY_ARR, cap = "butt", fill = transparent, join = "round") {
|
|
3191
|
+
if (stroke != ctxStroke)
|
|
3192
|
+
ctx.strokeStyle = ctxStroke = stroke;
|
|
3193
|
+
if (fill != ctxFill)
|
|
3194
|
+
ctx.fillStyle = ctxFill = fill;
|
|
3195
|
+
if (width != ctxWidth)
|
|
3196
|
+
ctx.lineWidth = ctxWidth = width;
|
|
3197
|
+
if (join != ctxJoin)
|
|
3198
|
+
ctx.lineJoin = ctxJoin = join;
|
|
3199
|
+
if (cap != ctxCap)
|
|
3200
|
+
ctx.lineCap = ctxCap = cap; // (‿|‿)
|
|
3201
|
+
if (dash != ctxDash)
|
|
3202
|
+
ctx.setLineDash(ctxDash = dash);
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
function setFontStyle(font, fill, align, baseline) {
|
|
3206
|
+
if (fill != ctxFill)
|
|
3207
|
+
ctx.fillStyle = ctxFill = fill;
|
|
3208
|
+
if (font != ctxFont)
|
|
3209
|
+
ctx.font = ctxFont = font;
|
|
3210
|
+
if (align != ctxAlign)
|
|
3211
|
+
ctx.textAlign = ctxAlign = align;
|
|
3212
|
+
if (baseline != ctxBaseline)
|
|
3213
|
+
ctx.textBaseline = ctxBaseline = baseline;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
function accScale(wsc, psc, facet, data) {
|
|
3217
|
+
if (wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {
|
|
3218
|
+
let _i0 = i0 ?? 0;
|
|
3219
|
+
let _i1 = i1 ?? data.length - 1;
|
|
3220
|
+
|
|
3221
|
+
// only run getMinMax() for invalidated series data, else reuse
|
|
3222
|
+
let minMax = facet.min == null ? (wsc.distr == 3 ? getMinMaxLog(data, _i0, _i1) : getMinMax(data, _i0, _i1)) : [facet.min, facet.max];
|
|
3223
|
+
|
|
3224
|
+
// initial min/max
|
|
3225
|
+
wsc.min = min(wsc.min, facet.min = minMax[0]);
|
|
3226
|
+
wsc.max = max(wsc.max, facet.max = minMax[1]);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
function setScales() {
|
|
3231
|
+
// log("setScales()", arguments);
|
|
3232
|
+
|
|
3233
|
+
// wip scales
|
|
3234
|
+
let wipScales = copy(scales, fastIsObj);
|
|
3235
|
+
|
|
3236
|
+
for (let k in wipScales) {
|
|
3237
|
+
let wsc = wipScales[k];
|
|
3238
|
+
let psc = pendScales[k];
|
|
3239
|
+
|
|
3240
|
+
if (psc != null && psc.min != null) {
|
|
3241
|
+
assign(wsc, psc);
|
|
3242
|
+
|
|
3243
|
+
// explicitly setting the x-scale invalidates everything (acts as redraw)
|
|
3244
|
+
if (k == xScaleKey)
|
|
3245
|
+
resetYSeries(true);
|
|
3246
|
+
}
|
|
3247
|
+
else if (k != xScaleKey || mode == 2) {
|
|
3248
|
+
if (dataLen == 0 && wsc.from == null) {
|
|
3249
|
+
let minMax = wsc.range(self, null, null, k);
|
|
3250
|
+
wsc.min = minMax[0];
|
|
3251
|
+
wsc.max = minMax[1];
|
|
3252
|
+
}
|
|
3253
|
+
else {
|
|
3254
|
+
wsc.min = inf;
|
|
3255
|
+
wsc.max = -inf;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
if (dataLen > 0) {
|
|
3261
|
+
// pre-range y-scales from y series' data values
|
|
3262
|
+
series.forEach((s, i) => {
|
|
3263
|
+
if (mode == 1) {
|
|
3264
|
+
let k = s.scale;
|
|
3265
|
+
let wsc = wipScales[k];
|
|
3266
|
+
let psc = pendScales[k];
|
|
3267
|
+
|
|
3268
|
+
if (i == 0) {
|
|
3269
|
+
let minMax = wsc.range(self, wsc.min, wsc.max, k);
|
|
3270
|
+
|
|
3271
|
+
wsc.min = minMax[0];
|
|
3272
|
+
wsc.max = minMax[1];
|
|
3273
|
+
|
|
3274
|
+
i0 = closestIdx(wsc.min, data[0]);
|
|
3275
|
+
i1 = closestIdx(wsc.max, data[0]);
|
|
3276
|
+
|
|
3277
|
+
// closest indices can be outside of view
|
|
3278
|
+
if (data[0][i0] < wsc.min)
|
|
3279
|
+
i0++;
|
|
3280
|
+
if (data[0][i1] > wsc.max)
|
|
3281
|
+
i1--;
|
|
3282
|
+
|
|
3283
|
+
s.min = data0[i0];
|
|
3284
|
+
s.max = data0[i1];
|
|
3285
|
+
}
|
|
3286
|
+
else if (s.show && s.auto)
|
|
3287
|
+
accScale(wsc, psc, s, data[i]);
|
|
3288
|
+
|
|
3289
|
+
s.idxs[0] = i0;
|
|
3290
|
+
s.idxs[1] = i1;
|
|
3291
|
+
}
|
|
3292
|
+
else {
|
|
3293
|
+
if (i > 0) {
|
|
3294
|
+
if (s.show && s.auto) {
|
|
3295
|
+
// TODO: only handles, assumes and requires facets[0] / 'x' scale, and facets[1] / 'y' scale
|
|
3296
|
+
let [ xFacet, yFacet ] = s.facets;
|
|
3297
|
+
let xScaleKey = xFacet.scale;
|
|
3298
|
+
let yScaleKey = yFacet.scale;
|
|
3299
|
+
let [ xData, yData ] = data[i];
|
|
3300
|
+
|
|
3301
|
+
accScale(wipScales[xScaleKey], pendScales[xScaleKey], xFacet, xData);
|
|
3302
|
+
accScale(wipScales[yScaleKey], pendScales[yScaleKey], yFacet, yData);
|
|
3303
|
+
|
|
3304
|
+
// temp
|
|
3305
|
+
s.min = yFacet.min;
|
|
3306
|
+
s.max = yFacet.max;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
});
|
|
3311
|
+
|
|
3312
|
+
// range independent scales
|
|
3313
|
+
for (let k in wipScales) {
|
|
3314
|
+
let wsc = wipScales[k];
|
|
3315
|
+
let psc = pendScales[k];
|
|
3316
|
+
|
|
3317
|
+
if (wsc.from == null && (psc == null || psc.min == null)) {
|
|
3318
|
+
let minMax = wsc.range(
|
|
3319
|
+
self,
|
|
3320
|
+
wsc.min == inf ? null : wsc.min,
|
|
3321
|
+
wsc.max == -inf ? null : wsc.max,
|
|
3322
|
+
k
|
|
3323
|
+
);
|
|
3324
|
+
wsc.min = minMax[0];
|
|
3325
|
+
wsc.max = minMax[1];
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
// range dependent scales
|
|
3331
|
+
for (let k in wipScales) {
|
|
3332
|
+
let wsc = wipScales[k];
|
|
3333
|
+
|
|
3334
|
+
if (wsc.from != null) {
|
|
3335
|
+
let base = wipScales[wsc.from];
|
|
3336
|
+
let minMax = wsc.range(self, base.min, base.max, k);
|
|
3337
|
+
wsc.min = minMax[0];
|
|
3338
|
+
wsc.max = minMax[1];
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
let changed = {};
|
|
3343
|
+
let anyChanged = false;
|
|
3344
|
+
|
|
3345
|
+
for (let k in wipScales) {
|
|
3346
|
+
let wsc = wipScales[k];
|
|
3347
|
+
let sc = scales[k];
|
|
3348
|
+
|
|
3349
|
+
if (sc.min != wsc.min || sc.max != wsc.max) {
|
|
3350
|
+
sc.min = wsc.min;
|
|
3351
|
+
sc.max = wsc.max;
|
|
3352
|
+
|
|
3353
|
+
let distr = sc.distr;
|
|
3354
|
+
|
|
3355
|
+
sc._min = distr == 3 ? log10(sc.min) : distr == 4 ? asinh(sc.min, sc.asinh) : sc.min;
|
|
3356
|
+
sc._max = distr == 3 ? log10(sc.max) : distr == 4 ? asinh(sc.max, sc.asinh) : sc.max;
|
|
3357
|
+
|
|
3358
|
+
changed[k] = anyChanged = true;
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
if (anyChanged) {
|
|
3363
|
+
// invalidate paths of all series on changed scales
|
|
3364
|
+
series.forEach((s, i) => {
|
|
3365
|
+
if (mode == 2) {
|
|
3366
|
+
if (i > 0 && changed.y)
|
|
3367
|
+
s._paths = null;
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
if (changed[s.scale])
|
|
3371
|
+
s._paths = null;
|
|
3372
|
+
}
|
|
3373
|
+
});
|
|
3374
|
+
|
|
3375
|
+
for (let k in changed) {
|
|
3376
|
+
shouldConvergeSize = true;
|
|
3377
|
+
fire("setScale", k);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
if (cursor.show)
|
|
3381
|
+
shouldSetCursor = shouldSetLegend = cursor.left >= 0;
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
for (let k in pendScales)
|
|
3385
|
+
pendScales[k] = null;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
// grabs the nearest indices with y data outside of x-scale limits
|
|
3389
|
+
function getOuterIdxs(ydata) {
|
|
3390
|
+
let _i0 = clamp(i0 - 1, 0, dataLen - 1);
|
|
3391
|
+
let _i1 = clamp(i1 + 1, 0, dataLen - 1);
|
|
3392
|
+
|
|
3393
|
+
while (ydata[_i0] == null && _i0 > 0)
|
|
3394
|
+
_i0--;
|
|
3395
|
+
|
|
3396
|
+
while (ydata[_i1] == null && _i1 < dataLen - 1)
|
|
3397
|
+
_i1++;
|
|
3398
|
+
|
|
3399
|
+
return [_i0, _i1];
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
function drawSeries() {
|
|
3403
|
+
if (dataLen > 0) {
|
|
3404
|
+
series.forEach((s, i) => {
|
|
3405
|
+
if (i > 0 && s.show && s._paths == null) {
|
|
3406
|
+
let _idxs = getOuterIdxs(data[i]);
|
|
3407
|
+
s._paths = s.paths(self, i, _idxs[0], _idxs[1]);
|
|
3408
|
+
}
|
|
3409
|
+
});
|
|
3410
|
+
|
|
3411
|
+
series.forEach((s, i) => {
|
|
3412
|
+
if (i > 0 && s.show) {
|
|
3413
|
+
if (ctxAlpha != s.alpha)
|
|
3414
|
+
ctx.globalAlpha = ctxAlpha = s.alpha;
|
|
3415
|
+
|
|
3416
|
+
{
|
|
3417
|
+
cacheStrokeFill(i, false);
|
|
3418
|
+
s._paths && drawPath(i, false);
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
{
|
|
3422
|
+
cacheStrokeFill(i, true);
|
|
3423
|
+
|
|
3424
|
+
let show = s.points.show(self, i, i0, i1);
|
|
3425
|
+
let idxs = s.points.filter(self, i, show, s._paths ? s._paths.gaps : null);
|
|
3426
|
+
|
|
3427
|
+
if (show || idxs) {
|
|
3428
|
+
s.points._paths = s.points.paths(self, i, i0, i1, idxs);
|
|
3429
|
+
drawPath(i, true);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
if (ctxAlpha != 1)
|
|
3434
|
+
ctx.globalAlpha = ctxAlpha = 1;
|
|
3435
|
+
|
|
3436
|
+
fire("drawSeries", i);
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
function cacheStrokeFill(si, _points) {
|
|
3443
|
+
let s = _points ? series[si].points : series[si];
|
|
3444
|
+
|
|
3445
|
+
s._stroke = s.stroke(self, si);
|
|
3446
|
+
s._fill = s.fill(self, si);
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
function drawPath(si, _points) {
|
|
3450
|
+
let s = _points ? series[si].points : series[si];
|
|
3451
|
+
|
|
3452
|
+
let strokeStyle = s._stroke;
|
|
3453
|
+
let fillStyle = s._fill;
|
|
3454
|
+
|
|
3455
|
+
let { stroke, fill, clip: gapsClip, flags } = s._paths;
|
|
3456
|
+
let boundsClip = null;
|
|
3457
|
+
let width = roundDec(s.width * pxRatio, 3);
|
|
3458
|
+
let offset = (width % 2) / 2;
|
|
3459
|
+
|
|
3460
|
+
if (_points && fillStyle == null)
|
|
3461
|
+
fillStyle = width > 0 ? "#fff" : strokeStyle;
|
|
3462
|
+
|
|
3463
|
+
let _pxAlign = s.pxAlign == 1;
|
|
3464
|
+
|
|
3465
|
+
_pxAlign && ctx.translate(offset, offset);
|
|
3466
|
+
|
|
3467
|
+
if (!_points) {
|
|
3468
|
+
let lft = plotLft,
|
|
3469
|
+
top = plotTop,
|
|
3470
|
+
wid = plotWid,
|
|
3471
|
+
hgt = plotHgt;
|
|
3472
|
+
|
|
3473
|
+
let halfWid = width * pxRatio / 2;
|
|
3474
|
+
|
|
3475
|
+
if (s.min == 0)
|
|
3476
|
+
hgt += halfWid;
|
|
3477
|
+
|
|
3478
|
+
if (s.max == 0) {
|
|
3479
|
+
top -= halfWid;
|
|
3480
|
+
hgt += halfWid;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
boundsClip = new Path2D();
|
|
3484
|
+
boundsClip.rect(lft, top, wid, hgt);
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// the points pathbuilder's gapsClip is its boundsClip, since points dont need gaps clipping, and bounds depend on point size
|
|
3488
|
+
if (_points)
|
|
3489
|
+
strokeFill(strokeStyle, width, s.dash, s.cap, fillStyle, stroke, fill, flags, gapsClip);
|
|
3490
|
+
else
|
|
3491
|
+
fillStroke(si, strokeStyle, width, s.dash, s.cap, fillStyle, stroke, fill, flags, boundsClip, gapsClip);
|
|
3492
|
+
|
|
3493
|
+
_pxAlign && ctx.translate(-offset, -offset);
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
function fillStroke(si, strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip) {
|
|
3497
|
+
let didStrokeFill = false;
|
|
3498
|
+
|
|
3499
|
+
// for all bands where this series is the top edge, create upwards clips using the bottom edges
|
|
3500
|
+
// and apply clips + fill with band fill or dfltFill
|
|
3501
|
+
bands.forEach((b, bi) => {
|
|
3502
|
+
// isUpperEdge?
|
|
3503
|
+
if (b.series[0] == si) {
|
|
3504
|
+
let lowerEdge = series[b.series[1]];
|
|
3505
|
+
|
|
3506
|
+
let bandClip = (lowerEdge._paths || EMPTY_OBJ).band;
|
|
3507
|
+
let gapsClip2;
|
|
3508
|
+
|
|
3509
|
+
let _fillStyle = null;
|
|
3510
|
+
|
|
3511
|
+
// hasLowerEdge?
|
|
3512
|
+
if (lowerEdge.show && bandClip) {
|
|
3513
|
+
_fillStyle = b.fill(self, bi) || fillStyle;
|
|
3514
|
+
gapsClip2 = lowerEdge._paths.clip;
|
|
3515
|
+
}
|
|
3516
|
+
else
|
|
3517
|
+
bandClip = null;
|
|
3518
|
+
|
|
3519
|
+
strokeFill(strokeStyle, lineWidth, lineDash, lineCap, _fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip, gapsClip2, bandClip);
|
|
3520
|
+
|
|
3521
|
+
didStrokeFill = true;
|
|
3522
|
+
}
|
|
3523
|
+
});
|
|
3524
|
+
|
|
3525
|
+
if (!didStrokeFill)
|
|
3526
|
+
strokeFill(strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
const CLIP_FILL_STROKE = BAND_CLIP_FILL | BAND_CLIP_STROKE;
|
|
3530
|
+
|
|
3531
|
+
function strokeFill(strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip, gapsClip2, bandClip) {
|
|
3532
|
+
setCtxStyle(strokeStyle, lineWidth, lineDash, lineCap, fillStyle);
|
|
3533
|
+
|
|
3534
|
+
if (boundsClip || gapsClip || bandClip) {
|
|
3535
|
+
ctx.save();
|
|
3536
|
+
boundsClip && ctx.clip(boundsClip);
|
|
3537
|
+
gapsClip && ctx.clip(gapsClip);
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
if (bandClip) {
|
|
3541
|
+
if ((flags & CLIP_FILL_STROKE) == CLIP_FILL_STROKE) {
|
|
3542
|
+
ctx.clip(bandClip);
|
|
3543
|
+
gapsClip2 && ctx.clip(gapsClip2);
|
|
3544
|
+
doFill(fillStyle, fillPath);
|
|
3545
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
3546
|
+
}
|
|
3547
|
+
else if (flags & BAND_CLIP_STROKE) {
|
|
3548
|
+
doFill(fillStyle, fillPath);
|
|
3549
|
+
ctx.clip(bandClip);
|
|
3550
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
3551
|
+
}
|
|
3552
|
+
else if (flags & BAND_CLIP_FILL) {
|
|
3553
|
+
ctx.save();
|
|
3554
|
+
ctx.clip(bandClip);
|
|
3555
|
+
gapsClip2 && ctx.clip(gapsClip2);
|
|
3556
|
+
doFill(fillStyle, fillPath);
|
|
3557
|
+
ctx.restore();
|
|
3558
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
else {
|
|
3562
|
+
doFill(fillStyle, fillPath);
|
|
3563
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
if (boundsClip || gapsClip || bandClip)
|
|
3567
|
+
ctx.restore();
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
function doStroke(strokeStyle, strokePath, lineWidth) {
|
|
3571
|
+
strokeStyle && strokePath && lineWidth && ctx.stroke(strokePath);
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
function doFill(fillStyle, fillPath) {
|
|
3575
|
+
fillStyle && fillPath && ctx.fill(fillPath);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
function getIncrSpace(axisIdx, min, max, fullDim) {
|
|
3579
|
+
let axis = axes[axisIdx];
|
|
3580
|
+
|
|
3581
|
+
let incrSpace;
|
|
3582
|
+
|
|
3583
|
+
if (fullDim <= 0)
|
|
3584
|
+
incrSpace = [0, 0];
|
|
3585
|
+
else {
|
|
3586
|
+
let minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);
|
|
3587
|
+
let incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);
|
|
3588
|
+
incrSpace = axis._found = findIncr(min, max, incrs, fullDim, minSpace);
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3591
|
+
return incrSpace;
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
function drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash, cap) {
|
|
3595
|
+
let offset = (width % 2) / 2;
|
|
3596
|
+
|
|
3597
|
+
pxAlign == 1 && ctx.translate(offset, offset);
|
|
3598
|
+
|
|
3599
|
+
setCtxStyle(stroke, width, dash, cap, stroke);
|
|
3600
|
+
|
|
3601
|
+
ctx.beginPath();
|
|
3602
|
+
|
|
3603
|
+
let x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);
|
|
3604
|
+
|
|
3605
|
+
if (ori == 0) {
|
|
3606
|
+
y0 = pos0;
|
|
3607
|
+
y1 = pos1;
|
|
3608
|
+
}
|
|
3609
|
+
else {
|
|
3610
|
+
x0 = pos0;
|
|
3611
|
+
x1 = pos1;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
for (let i = 0; i < offs.length; i++) {
|
|
3615
|
+
if (filts[i] != null) {
|
|
3616
|
+
if (ori == 0)
|
|
3617
|
+
x0 = x1 = offs[i];
|
|
3618
|
+
else
|
|
3619
|
+
y0 = y1 = offs[i];
|
|
3620
|
+
|
|
3621
|
+
ctx.moveTo(x0, y0);
|
|
3622
|
+
ctx.lineTo(x1, y1);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
ctx.stroke();
|
|
3627
|
+
|
|
3628
|
+
pxAlign == 1 && ctx.translate(-offset, -offset);
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
function axesCalc(cycleNum) {
|
|
3632
|
+
// log("axesCalc()", arguments);
|
|
3633
|
+
|
|
3634
|
+
let converged = true;
|
|
3635
|
+
|
|
3636
|
+
axes.forEach((axis, i) => {
|
|
3637
|
+
if (!axis.show)
|
|
3638
|
+
return;
|
|
3639
|
+
|
|
3640
|
+
let scale = scales[axis.scale];
|
|
3641
|
+
|
|
3642
|
+
if (scale.min == null) {
|
|
3643
|
+
if (axis._show) {
|
|
3644
|
+
converged = false;
|
|
3645
|
+
axis._show = false;
|
|
3646
|
+
resetYSeries(false);
|
|
3647
|
+
}
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
else {
|
|
3651
|
+
if (!axis._show) {
|
|
3652
|
+
converged = false;
|
|
3653
|
+
axis._show = true;
|
|
3654
|
+
resetYSeries(false);
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
|
|
3658
|
+
let side = axis.side;
|
|
3659
|
+
let ori = side % 2;
|
|
3660
|
+
|
|
3661
|
+
let {min, max} = scale; // // should this toggle them ._show = false
|
|
3662
|
+
|
|
3663
|
+
let [_incr, _space] = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);
|
|
3664
|
+
|
|
3665
|
+
if (_space == 0)
|
|
3666
|
+
return;
|
|
3667
|
+
|
|
3668
|
+
// if we're using index positions, force first tick to match passed index
|
|
3669
|
+
let forceMin = scale.distr == 2;
|
|
3670
|
+
|
|
3671
|
+
let _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);
|
|
3672
|
+
|
|
3673
|
+
// tick labels
|
|
3674
|
+
// BOO this assumes a specific data/series
|
|
3675
|
+
let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
|
|
3676
|
+
let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
|
|
3677
|
+
|
|
3678
|
+
let values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);
|
|
3679
|
+
|
|
3680
|
+
// rotating of labels only supported on bottom x axis
|
|
3681
|
+
axis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;
|
|
3682
|
+
|
|
3683
|
+
let oldSize = axis._size;
|
|
3684
|
+
|
|
3685
|
+
axis._size = ceil(axis.size(self, values, i, cycleNum));
|
|
3686
|
+
|
|
3687
|
+
if (oldSize != null && axis._size != oldSize) // ready && ?
|
|
3688
|
+
converged = false;
|
|
3689
|
+
});
|
|
3690
|
+
|
|
3691
|
+
return converged;
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
function paddingCalc(cycleNum) {
|
|
3695
|
+
let converged = true;
|
|
3696
|
+
|
|
3697
|
+
padding.forEach((p, i) => {
|
|
3698
|
+
let _p = p(self, i, sidesWithAxes, cycleNum);
|
|
3699
|
+
|
|
3700
|
+
if (_p != _padding[i])
|
|
3701
|
+
converged = false;
|
|
3702
|
+
|
|
3703
|
+
_padding[i] = _p;
|
|
3704
|
+
});
|
|
3705
|
+
|
|
3706
|
+
return converged;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
function drawAxesGrid() {
|
|
3710
|
+
for (let i = 0; i < axes.length; i++) {
|
|
3711
|
+
let axis = axes[i];
|
|
3712
|
+
|
|
3713
|
+
if (!axis.show || !axis._show)
|
|
3714
|
+
return;
|
|
3715
|
+
|
|
3716
|
+
let side = axis.side;
|
|
3717
|
+
let ori = side % 2;
|
|
3718
|
+
|
|
3719
|
+
let x, y;
|
|
3720
|
+
|
|
3721
|
+
let fillStyle = axis.stroke(self, i);
|
|
3722
|
+
|
|
3723
|
+
let shiftDir = side == 0 || side == 3 ? -1 : 1;
|
|
3724
|
+
|
|
3725
|
+
// axis label
|
|
3726
|
+
if (axis.label) {
|
|
3727
|
+
let shiftAmt = axis.labelGap * shiftDir;
|
|
3728
|
+
let baseLpos = round((axis._lpos + shiftAmt) * pxRatio);
|
|
3729
|
+
|
|
3730
|
+
setFontStyle(axis.labelFont[0], fillStyle, "center", side == 2 ? TOP : BOTTOM);
|
|
3731
|
+
|
|
3732
|
+
ctx.save();
|
|
3733
|
+
|
|
3734
|
+
if (ori == 1) {
|
|
3735
|
+
x = y = 0;
|
|
3736
|
+
|
|
3737
|
+
ctx.translate(
|
|
3738
|
+
baseLpos,
|
|
3739
|
+
round(plotTop + plotHgt / 2),
|
|
3740
|
+
);
|
|
3741
|
+
ctx.rotate((side == 3 ? -PI : PI) / 2);
|
|
3742
|
+
|
|
3743
|
+
}
|
|
3744
|
+
else {
|
|
3745
|
+
x = round(plotLft + plotWid / 2);
|
|
3746
|
+
y = baseLpos;
|
|
3747
|
+
}
|
|
3748
|
+
|
|
3749
|
+
ctx.fillText(axis.label, x, y);
|
|
3750
|
+
|
|
3751
|
+
ctx.restore();
|
|
3752
|
+
}
|
|
3753
|
+
|
|
3754
|
+
let [_incr, _space] = axis._found;
|
|
3755
|
+
|
|
3756
|
+
if (_space == 0)
|
|
3757
|
+
return;
|
|
3758
|
+
|
|
3759
|
+
let scale = scales[axis.scale];
|
|
3760
|
+
|
|
3761
|
+
let plotDim = ori == 0 ? plotWid : plotHgt;
|
|
3762
|
+
let plotOff = ori == 0 ? plotLft : plotTop;
|
|
3763
|
+
|
|
3764
|
+
let axisGap = round(axis.gap * pxRatio);
|
|
3765
|
+
|
|
3766
|
+
let _splits = axis._splits;
|
|
3767
|
+
|
|
3768
|
+
// tick labels
|
|
3769
|
+
// BOO this assumes a specific data/series
|
|
3770
|
+
let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
|
|
3771
|
+
let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
|
|
3772
|
+
|
|
3773
|
+
let ticks = axis.ticks;
|
|
3774
|
+
let tickSize = ticks.show ? round(ticks.size * pxRatio) : 0;
|
|
3775
|
+
|
|
3776
|
+
// rotating of labels only supported on bottom x axis
|
|
3777
|
+
let angle = axis._rotate * -PI/180;
|
|
3778
|
+
|
|
3779
|
+
let basePos = pxRound(axis._pos * pxRatio);
|
|
3780
|
+
let shiftAmt = (tickSize + axisGap) * shiftDir;
|
|
3781
|
+
let finalPos = basePos + shiftAmt;
|
|
3782
|
+
y = ori == 0 ? finalPos : 0;
|
|
3783
|
+
x = ori == 1 ? finalPos : 0;
|
|
3784
|
+
|
|
3785
|
+
let font = axis.font[0];
|
|
3786
|
+
let textAlign = axis.align == 1 ? LEFT :
|
|
3787
|
+
axis.align == 2 ? RIGHT :
|
|
3788
|
+
angle > 0 ? LEFT :
|
|
3789
|
+
angle < 0 ? RIGHT :
|
|
3790
|
+
ori == 0 ? "center" : side == 3 ? RIGHT : LEFT;
|
|
3791
|
+
let textBaseline = angle ||
|
|
3792
|
+
ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM;
|
|
3793
|
+
|
|
3794
|
+
setFontStyle(font, fillStyle, textAlign, textBaseline);
|
|
3795
|
+
|
|
3796
|
+
let lineHeight = axis.font[1] * lineMult;
|
|
3797
|
+
|
|
3798
|
+
let canOffs = _splits.map(val => pxRound(getPos(val, scale, plotDim, plotOff)));
|
|
3799
|
+
|
|
3800
|
+
let _values = axis._values;
|
|
3801
|
+
|
|
3802
|
+
for (let i = 0; i < _values.length; i++) {
|
|
3803
|
+
let val = _values[i];
|
|
3804
|
+
|
|
3805
|
+
if (val != null) {
|
|
3806
|
+
if (ori == 0)
|
|
3807
|
+
x = canOffs[i];
|
|
3808
|
+
else
|
|
3809
|
+
y = canOffs[i];
|
|
3810
|
+
|
|
3811
|
+
val = "" + val;
|
|
3812
|
+
|
|
3813
|
+
let _parts = val.indexOf("\n") == -1 ? [val] : val.split(/\n/gm);
|
|
3814
|
+
|
|
3815
|
+
for (let j = 0; j < _parts.length; j++) {
|
|
3816
|
+
let text = _parts[j];
|
|
3817
|
+
|
|
3818
|
+
if (angle) {
|
|
3819
|
+
ctx.save();
|
|
3820
|
+
ctx.translate(x, y + j * lineHeight); // can this be replaced with position math?
|
|
3821
|
+
ctx.rotate(angle); // can this be done once?
|
|
3822
|
+
ctx.fillText(text, 0, 0);
|
|
3823
|
+
ctx.restore();
|
|
3824
|
+
}
|
|
3825
|
+
else
|
|
3826
|
+
ctx.fillText(text, x, y + j * lineHeight);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
// ticks
|
|
3832
|
+
if (ticks.show) {
|
|
3833
|
+
drawOrthoLines(
|
|
3834
|
+
canOffs,
|
|
3835
|
+
ticks.filter(self, splits, i, _space, incr),
|
|
3836
|
+
ori,
|
|
3837
|
+
side,
|
|
3838
|
+
basePos,
|
|
3839
|
+
tickSize,
|
|
3840
|
+
roundDec(ticks.width * pxRatio, 3),
|
|
3841
|
+
ticks.stroke(self, i),
|
|
3842
|
+
ticks.dash,
|
|
3843
|
+
ticks.cap,
|
|
3844
|
+
);
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
// grid
|
|
3848
|
+
let grid = axis.grid;
|
|
3849
|
+
|
|
3850
|
+
if (grid.show) {
|
|
3851
|
+
drawOrthoLines(
|
|
3852
|
+
canOffs,
|
|
3853
|
+
grid.filter(self, splits, i, _space, incr),
|
|
3854
|
+
ori,
|
|
3855
|
+
ori == 0 ? 2 : 1,
|
|
3856
|
+
ori == 0 ? plotTop : plotLft,
|
|
3857
|
+
ori == 0 ? plotHgt : plotWid,
|
|
3858
|
+
roundDec(grid.width * pxRatio, 3),
|
|
3859
|
+
grid.stroke(self, i),
|
|
3860
|
+
grid.dash,
|
|
3861
|
+
grid.cap,
|
|
3862
|
+
);
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
fire("drawAxes");
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
function resetYSeries(minMax) {
|
|
3870
|
+
// log("resetYSeries()", arguments);
|
|
3871
|
+
|
|
3872
|
+
series.forEach((s, i) => {
|
|
3873
|
+
if (i > 0) {
|
|
3874
|
+
s._paths = null;
|
|
3875
|
+
|
|
3876
|
+
if (minMax) {
|
|
3877
|
+
if (mode == 1) {
|
|
3878
|
+
s.min = null;
|
|
3879
|
+
s.max = null;
|
|
3880
|
+
}
|
|
3881
|
+
else {
|
|
3882
|
+
s.facets.forEach(f => {
|
|
3883
|
+
f.min = null;
|
|
3884
|
+
f.max = null;
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
|
|
3892
|
+
let queuedCommit = false;
|
|
3893
|
+
|
|
3894
|
+
function commit() {
|
|
3895
|
+
if (!queuedCommit) {
|
|
3896
|
+
microTask(_commit);
|
|
3897
|
+
queuedCommit = true;
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
function _commit() {
|
|
3902
|
+
// log("_commit()", arguments);
|
|
3903
|
+
|
|
3904
|
+
if (shouldSetScales) {
|
|
3905
|
+
setScales();
|
|
3906
|
+
shouldSetScales = false;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
if (shouldConvergeSize) {
|
|
3910
|
+
convergeSize();
|
|
3911
|
+
shouldConvergeSize = false;
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
if (shouldSetSize) {
|
|
3915
|
+
setStylePx(under, LEFT, plotLftCss);
|
|
3916
|
+
setStylePx(under, TOP, plotTopCss);
|
|
3917
|
+
setStylePx(under, WIDTH, plotWidCss);
|
|
3918
|
+
setStylePx(under, HEIGHT, plotHgtCss);
|
|
3919
|
+
|
|
3920
|
+
setStylePx(over, LEFT, plotLftCss);
|
|
3921
|
+
setStylePx(over, TOP, plotTopCss);
|
|
3922
|
+
setStylePx(over, WIDTH, plotWidCss);
|
|
3923
|
+
setStylePx(over, HEIGHT, plotHgtCss);
|
|
3924
|
+
|
|
3925
|
+
setStylePx(wrap, WIDTH, fullWidCss);
|
|
3926
|
+
setStylePx(wrap, HEIGHT, fullHgtCss);
|
|
3927
|
+
|
|
3928
|
+
// NOTE: mutating this during print preview in Chrome forces transparent
|
|
3929
|
+
// canvas pixels to white, even when followed up with clearRect() below
|
|
3930
|
+
can.width = round(fullWidCss * pxRatio);
|
|
3931
|
+
can.height = round(fullHgtCss * pxRatio);
|
|
3932
|
+
|
|
3933
|
+
// invalidate ctx style cache
|
|
3934
|
+
ctxStroke = ctxFill = ctxWidth = ctxJoin = ctxCap = ctxFont = ctxAlign = ctxBaseline = ctxDash = null;
|
|
3935
|
+
ctxAlpha = 1;
|
|
3936
|
+
|
|
3937
|
+
syncRect(false);
|
|
3938
|
+
|
|
3939
|
+
fire("setSize");
|
|
3940
|
+
|
|
3941
|
+
shouldSetSize = false;
|
|
3942
|
+
}
|
|
3943
|
+
|
|
3944
|
+
if (fullWidCss > 0 && fullHgtCss > 0) {
|
|
3945
|
+
ctx.clearRect(0, 0, can.width, can.height);
|
|
3946
|
+
fire("drawClear");
|
|
3947
|
+
drawOrder.forEach(fn => fn());
|
|
3948
|
+
fire("draw");
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
// if (shouldSetSelect) {
|
|
3952
|
+
// TODO: update .u-select metrics (if visible)
|
|
3953
|
+
// setStylePx(selectDiv, TOP, select.top = 0);
|
|
3954
|
+
// setStylePx(selectDiv, LEFT, select.left = 0);
|
|
3955
|
+
// setStylePx(selectDiv, WIDTH, select.width = 0);
|
|
3956
|
+
// setStylePx(selectDiv, HEIGHT, select.height = 0);
|
|
3957
|
+
// shouldSetSelect = false;
|
|
3958
|
+
// }
|
|
3959
|
+
|
|
3960
|
+
if (cursor.show && shouldSetCursor) {
|
|
3961
|
+
updateCursor(null, true, false);
|
|
3962
|
+
shouldSetCursor = false;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
// if (FEAT_LEGEND && legend.show && legend.live && shouldSetLegend) {}
|
|
3966
|
+
|
|
3967
|
+
if (!ready) {
|
|
3968
|
+
ready = true;
|
|
3969
|
+
self.status = 1;
|
|
3970
|
+
|
|
3971
|
+
fire("ready");
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
viaAutoScaleX = false;
|
|
3975
|
+
|
|
3976
|
+
queuedCommit = false;
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
self.redraw = (rebuildPaths, recalcAxes) => {
|
|
3980
|
+
shouldConvergeSize = recalcAxes || false;
|
|
3981
|
+
|
|
3982
|
+
if (rebuildPaths !== false)
|
|
3983
|
+
_setScale(xScaleKey, scaleX.min, scaleX.max);
|
|
3984
|
+
else
|
|
3985
|
+
commit();
|
|
3986
|
+
};
|
|
3987
|
+
|
|
3988
|
+
// redraw() => setScale('x', scales.x.min, scales.x.max);
|
|
3989
|
+
|
|
3990
|
+
// explicit, never re-ranged (is this actually true? for x and y)
|
|
3991
|
+
function setScale(key, opts) {
|
|
3992
|
+
let sc = scales[key];
|
|
3993
|
+
|
|
3994
|
+
if (sc.from == null) {
|
|
3995
|
+
if (dataLen == 0) {
|
|
3996
|
+
let minMax = sc.range(self, opts.min, opts.max, key);
|
|
3997
|
+
opts.min = minMax[0];
|
|
3998
|
+
opts.max = minMax[1];
|
|
3999
|
+
}
|
|
4000
|
+
|
|
4001
|
+
if (opts.min > opts.max) {
|
|
4002
|
+
let _min = opts.min;
|
|
4003
|
+
opts.min = opts.max;
|
|
4004
|
+
opts.max = _min;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
4007
|
+
if (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)
|
|
4008
|
+
return;
|
|
4009
|
+
|
|
4010
|
+
if (key == xScaleKey) {
|
|
4011
|
+
if (sc.distr == 2 && dataLen > 0) {
|
|
4012
|
+
opts.min = closestIdx(opts.min, data[0]);
|
|
4013
|
+
opts.max = closestIdx(opts.max, data[0]);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
// log("setScale()", arguments);
|
|
4018
|
+
|
|
4019
|
+
pendScales[key] = opts;
|
|
4020
|
+
|
|
4021
|
+
shouldSetScales = true;
|
|
4022
|
+
commit();
|
|
4023
|
+
}
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
self.setScale = setScale;
|
|
4027
|
+
|
|
4028
|
+
// INTERACTION
|
|
4029
|
+
|
|
4030
|
+
let xCursor;
|
|
4031
|
+
let yCursor;
|
|
4032
|
+
let vCursor;
|
|
4033
|
+
let hCursor;
|
|
4034
|
+
|
|
4035
|
+
// starting position before cursor.move
|
|
4036
|
+
let rawMouseLeft0;
|
|
4037
|
+
let rawMouseTop0;
|
|
4038
|
+
|
|
4039
|
+
// starting position
|
|
4040
|
+
let mouseLeft0;
|
|
4041
|
+
let mouseTop0;
|
|
4042
|
+
|
|
4043
|
+
// current position before cursor.move
|
|
4044
|
+
let rawMouseLeft1;
|
|
4045
|
+
let rawMouseTop1;
|
|
4046
|
+
|
|
4047
|
+
// current position
|
|
4048
|
+
let mouseLeft1;
|
|
4049
|
+
let mouseTop1;
|
|
4050
|
+
|
|
4051
|
+
let dragging = false;
|
|
4052
|
+
|
|
4053
|
+
const drag = cursor.drag;
|
|
4054
|
+
|
|
4055
|
+
let dragX = drag.x;
|
|
4056
|
+
let dragY = drag.y;
|
|
4057
|
+
|
|
4058
|
+
if (cursor.show) {
|
|
4059
|
+
if (cursor.x)
|
|
4060
|
+
xCursor = placeDiv(CURSOR_X, over);
|
|
4061
|
+
if (cursor.y)
|
|
4062
|
+
yCursor = placeDiv(CURSOR_Y, over);
|
|
4063
|
+
|
|
4064
|
+
if (scaleX.ori == 0) {
|
|
4065
|
+
vCursor = xCursor;
|
|
4066
|
+
hCursor = yCursor;
|
|
4067
|
+
}
|
|
4068
|
+
else {
|
|
4069
|
+
vCursor = yCursor;
|
|
4070
|
+
hCursor = xCursor;
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4073
|
+
mouseLeft1 = cursor.left;
|
|
4074
|
+
mouseTop1 = cursor.top;
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
const select = self.select = assign({
|
|
4078
|
+
show: true,
|
|
4079
|
+
over: true,
|
|
4080
|
+
left: 0,
|
|
4081
|
+
width: 0,
|
|
4082
|
+
top: 0,
|
|
4083
|
+
height: 0,
|
|
4084
|
+
}, opts.select);
|
|
4085
|
+
|
|
4086
|
+
const selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;
|
|
4087
|
+
|
|
4088
|
+
function setSelect(opts, _fire) {
|
|
4089
|
+
if (select.show) {
|
|
4090
|
+
for (let prop in opts)
|
|
4091
|
+
setStylePx(selectDiv, prop, select[prop] = opts[prop]);
|
|
4092
|
+
|
|
4093
|
+
_fire !== false && fire("setSelect");
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
self.setSelect = setSelect;
|
|
4098
|
+
|
|
4099
|
+
function toggleDOM(i, onOff) {
|
|
4100
|
+
let s = series[i];
|
|
4101
|
+
let label = showLegend ? legendRows[i] : null;
|
|
4102
|
+
|
|
4103
|
+
if (s.show)
|
|
4104
|
+
label && remClass(label, OFF);
|
|
4105
|
+
else {
|
|
4106
|
+
label && addClass(label, OFF);
|
|
4107
|
+
cursorPts.length > 1 && elTrans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
function _setScale(key, min, max) {
|
|
4112
|
+
setScale(key, {min, max});
|
|
4113
|
+
}
|
|
4114
|
+
|
|
4115
|
+
function setSeries(i, opts, _fire, _pub) {
|
|
4116
|
+
// log("setSeries()", arguments);
|
|
4117
|
+
|
|
4118
|
+
let s = series[i];
|
|
4119
|
+
|
|
4120
|
+
if (opts.focus != null)
|
|
4121
|
+
setFocus(i);
|
|
4122
|
+
|
|
4123
|
+
if (opts.show != null) {
|
|
4124
|
+
s.show = opts.show;
|
|
4125
|
+
toggleDOM(i, opts.show);
|
|
4126
|
+
|
|
4127
|
+
_setScale(mode == 2 ? s.facets[1].scale : s.scale, null, null);
|
|
4128
|
+
commit();
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
_fire !== false && fire("setSeries", i, opts);
|
|
4132
|
+
|
|
4133
|
+
_pub && pubSync("setSeries", self, i, opts);
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
self.setSeries = setSeries;
|
|
4137
|
+
|
|
4138
|
+
function setBand(bi, opts) {
|
|
4139
|
+
assign(bands[bi], opts);
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
function addBand(opts, bi) {
|
|
4143
|
+
opts.fill = fnOrSelf(opts.fill || null);
|
|
4144
|
+
bi = bi == null ? bands.length : bi;
|
|
4145
|
+
bands.splice(bi, 0, opts);
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
function delBand(bi) {
|
|
4149
|
+
if (bi == null)
|
|
4150
|
+
bands.length = 0;
|
|
4151
|
+
else
|
|
4152
|
+
bands.splice(bi, 1);
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
self.addBand = addBand;
|
|
4156
|
+
self.setBand = setBand;
|
|
4157
|
+
self.delBand = delBand;
|
|
4158
|
+
|
|
4159
|
+
function setAlpha(i, value) {
|
|
4160
|
+
series[i].alpha = value;
|
|
4161
|
+
|
|
4162
|
+
if (cursor.show && cursorPts[i])
|
|
4163
|
+
cursorPts[i].style.opacity = value;
|
|
4164
|
+
|
|
4165
|
+
if (showLegend && legendRows[i])
|
|
4166
|
+
legendRows[i].style.opacity = value;
|
|
4167
|
+
}
|
|
4168
|
+
|
|
4169
|
+
// y-distance
|
|
4170
|
+
let closestDist;
|
|
4171
|
+
let closestSeries;
|
|
4172
|
+
let focusedSeries;
|
|
4173
|
+
const FOCUS_TRUE = {focus: true};
|
|
4174
|
+
const FOCUS_FALSE = {focus: false};
|
|
4175
|
+
|
|
4176
|
+
function setFocus(i) {
|
|
4177
|
+
if (i != focusedSeries) {
|
|
4178
|
+
// log("setFocus()", arguments);
|
|
4179
|
+
|
|
4180
|
+
let allFocused = i == null;
|
|
4181
|
+
|
|
4182
|
+
let _setAlpha = focus.alpha != 1;
|
|
4183
|
+
|
|
4184
|
+
series.forEach((s, i2) => {
|
|
4185
|
+
let isFocused = allFocused || i2 == 0 || i2 == i;
|
|
4186
|
+
s._focus = allFocused ? null : isFocused;
|
|
4187
|
+
_setAlpha && setAlpha(i2, isFocused ? 1 : focus.alpha);
|
|
4188
|
+
});
|
|
4189
|
+
|
|
4190
|
+
focusedSeries = i;
|
|
4191
|
+
_setAlpha && commit();
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
if (showLegend && cursorFocus) {
|
|
4196
|
+
on(mouseleave, legendEl, e => {
|
|
4197
|
+
if (cursor._lock)
|
|
4198
|
+
return;
|
|
4199
|
+
setSeries(null, FOCUS_FALSE, true, syncOpts.setSeries);
|
|
4200
|
+
updateCursor(null, true, false);
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
function posToVal(pos, scale, can) {
|
|
4205
|
+
let sc = scales[scale];
|
|
4206
|
+
|
|
4207
|
+
if (can)
|
|
4208
|
+
pos = pos / pxRatio - (sc.ori == 1 ? plotTopCss : plotLftCss);
|
|
4209
|
+
|
|
4210
|
+
let dim = plotWidCss;
|
|
4211
|
+
|
|
4212
|
+
if (sc.ori == 1) {
|
|
4213
|
+
dim = plotHgtCss;
|
|
4214
|
+
pos = dim - pos;
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
if (sc.dir == -1)
|
|
4218
|
+
pos = dim - pos;
|
|
4219
|
+
|
|
4220
|
+
let _min = sc._min,
|
|
4221
|
+
_max = sc._max,
|
|
4222
|
+
pct = pos / dim;
|
|
4223
|
+
|
|
4224
|
+
let sv = _min + (_max - _min) * pct;
|
|
4225
|
+
|
|
4226
|
+
let distr = sc.distr;
|
|
4227
|
+
|
|
4228
|
+
return (
|
|
4229
|
+
distr == 3 ? pow(10, sv) :
|
|
4230
|
+
distr == 4 ? sinh(sv, sc.asinh) :
|
|
4231
|
+
sv
|
|
4232
|
+
);
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
function closestIdxFromXpos(pos, can) {
|
|
4236
|
+
let v = posToVal(pos, xScaleKey, can);
|
|
4237
|
+
return closestIdx(v, data[0], i0, i1);
|
|
4238
|
+
}
|
|
4239
|
+
|
|
4240
|
+
self.valToIdx = val => closestIdx(val, data[0]);
|
|
4241
|
+
self.posToIdx = closestIdxFromXpos;
|
|
4242
|
+
self.posToVal = posToVal;
|
|
4243
|
+
self.valToPos = (val, scale, can) => (
|
|
4244
|
+
scales[scale].ori == 0 ?
|
|
4245
|
+
getHPos(val, scales[scale],
|
|
4246
|
+
can ? plotWid : plotWidCss,
|
|
4247
|
+
can ? plotLft : 0,
|
|
4248
|
+
) :
|
|
4249
|
+
getVPos(val, scales[scale],
|
|
4250
|
+
can ? plotHgt : plotHgtCss,
|
|
4251
|
+
can ? plotTop : 0,
|
|
4252
|
+
)
|
|
4253
|
+
);
|
|
4254
|
+
|
|
4255
|
+
// defers calling expensive functions
|
|
4256
|
+
function batch(fn) {
|
|
4257
|
+
fn(self);
|
|
4258
|
+
commit();
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
self.batch = batch;
|
|
4262
|
+
|
|
4263
|
+
(self.setCursor = (opts, _fire, _pub) => {
|
|
4264
|
+
mouseLeft1 = opts.left;
|
|
4265
|
+
mouseTop1 = opts.top;
|
|
4266
|
+
// assign(cursor, opts);
|
|
4267
|
+
updateCursor(null, _fire, _pub);
|
|
4268
|
+
});
|
|
4269
|
+
|
|
4270
|
+
function setSelH(off, dim) {
|
|
4271
|
+
setStylePx(selectDiv, LEFT, select.left = off);
|
|
4272
|
+
setStylePx(selectDiv, WIDTH, select.width = dim);
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
function setSelV(off, dim) {
|
|
4276
|
+
setStylePx(selectDiv, TOP, select.top = off);
|
|
4277
|
+
setStylePx(selectDiv, HEIGHT, select.height = dim);
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
let setSelX = scaleX.ori == 0 ? setSelH : setSelV;
|
|
4281
|
+
let setSelY = scaleX.ori == 1 ? setSelH : setSelV;
|
|
4282
|
+
|
|
4283
|
+
function syncLegend() {
|
|
4284
|
+
if (showLegend && legend.live) {
|
|
4285
|
+
for (let i = mode == 2 ? 1 : 0; i < series.length; i++) {
|
|
4286
|
+
if (i == 0 && multiValLegend)
|
|
4287
|
+
continue;
|
|
4288
|
+
|
|
4289
|
+
let vals = legend.values[i];
|
|
4290
|
+
|
|
4291
|
+
let j = 0;
|
|
4292
|
+
|
|
4293
|
+
for (let k in vals)
|
|
4294
|
+
legendCells[i][j++].firstChild.nodeValue = vals[k];
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4299
|
+
function setLegend(opts, _fire) {
|
|
4300
|
+
if (opts != null) {
|
|
4301
|
+
let idx = opts.idx;
|
|
4302
|
+
|
|
4303
|
+
legend.idx = idx;
|
|
4304
|
+
series.forEach((s, sidx) => {
|
|
4305
|
+
(sidx > 0 || !multiValLegend) && setLegendValues(sidx, idx);
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
if (showLegend && legend.live)
|
|
4310
|
+
syncLegend();
|
|
4311
|
+
|
|
4312
|
+
shouldSetLegend = false;
|
|
4313
|
+
|
|
4314
|
+
_fire !== false && fire("setLegend");
|
|
4315
|
+
}
|
|
4316
|
+
|
|
4317
|
+
self.setLegend = setLegend;
|
|
4318
|
+
|
|
4319
|
+
function setLegendValues(sidx, idx) {
|
|
4320
|
+
let val;
|
|
4321
|
+
|
|
4322
|
+
if (idx == null)
|
|
4323
|
+
val = NULL_LEGEND_VALUES;
|
|
4324
|
+
else {
|
|
4325
|
+
let s = series[sidx];
|
|
4326
|
+
let src = sidx == 0 && xScaleDistr == 2 ? data0 : data[sidx];
|
|
4327
|
+
val = multiValLegend ? s.values(self, sidx, idx) : {_: s.value(self, src[idx], sidx, idx)};
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4330
|
+
legend.values[sidx] = val;
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
function updateCursor(src, _fire, _pub) {
|
|
4334
|
+
// ts == null && log("updateCursor()", arguments);
|
|
4335
|
+
|
|
4336
|
+
rawMouseLeft1 = mouseLeft1;
|
|
4337
|
+
rawMouseTop1 = mouseTop1;
|
|
4338
|
+
|
|
4339
|
+
[mouseLeft1, mouseTop1] = cursor.move(self, mouseLeft1, mouseTop1);
|
|
4340
|
+
|
|
4341
|
+
if (cursor.show) {
|
|
4342
|
+
vCursor && elTrans(vCursor, round(mouseLeft1), 0, plotWidCss, plotHgtCss);
|
|
4343
|
+
hCursor && elTrans(hCursor, 0, round(mouseTop1), plotWidCss, plotHgtCss);
|
|
4344
|
+
}
|
|
4345
|
+
|
|
4346
|
+
let idx;
|
|
4347
|
+
|
|
4348
|
+
// when zooming to an x scale range between datapoints the binary search
|
|
4349
|
+
// for nearest min/max indices results in this condition. cheap hack :D
|
|
4350
|
+
let noDataInRange = i0 > i1; // works for mode 1 only
|
|
4351
|
+
|
|
4352
|
+
closestDist = inf;
|
|
4353
|
+
|
|
4354
|
+
// TODO: extract
|
|
4355
|
+
let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss;
|
|
4356
|
+
let yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss;
|
|
4357
|
+
|
|
4358
|
+
// if cursor hidden, hide points & clear legend vals
|
|
4359
|
+
if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {
|
|
4360
|
+
idx = null;
|
|
4361
|
+
|
|
4362
|
+
for (let i = 0; i < series.length; i++) {
|
|
4363
|
+
if (i > 0) {
|
|
4364
|
+
cursorPts.length > 1 && elTrans(cursorPts[i], -10, -10, plotWidCss, plotHgtCss);
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
if (cursorFocus)
|
|
4369
|
+
setSeries(null, FOCUS_TRUE, true, src == null && syncOpts.setSeries);
|
|
4370
|
+
|
|
4371
|
+
if (legend.live) {
|
|
4372
|
+
activeIdxs.fill(null);
|
|
4373
|
+
shouldSetLegend = true;
|
|
4374
|
+
|
|
4375
|
+
for (let i = 0; i < series.length; i++)
|
|
4376
|
+
legend.values[i] = NULL_LEGEND_VALUES;
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
else {
|
|
4380
|
+
// let pctY = 1 - (y / rect.height);
|
|
4381
|
+
|
|
4382
|
+
let mouseXPos, valAtPosX, xPos;
|
|
4383
|
+
|
|
4384
|
+
if (mode == 1) {
|
|
4385
|
+
mouseXPos = scaleX.ori == 0 ? mouseLeft1 : mouseTop1;
|
|
4386
|
+
valAtPosX = posToVal(mouseXPos, xScaleKey);
|
|
4387
|
+
idx = closestIdx(valAtPosX, data[0], i0, i1);
|
|
4388
|
+
xPos = incrRoundUp(valToPosX(data[0][idx], scaleX, xDim, 0), 0.5);
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4391
|
+
for (let i = mode == 2 ? 1 : 0; i < series.length; i++) {
|
|
4392
|
+
let s = series[i];
|
|
4393
|
+
|
|
4394
|
+
let idx1 = activeIdxs[i];
|
|
4395
|
+
let yVal1 = mode == 1 ? data[i][idx1] : data[i][1][idx1];
|
|
4396
|
+
|
|
4397
|
+
let idx2 = cursor.dataIdx(self, i, idx, valAtPosX);
|
|
4398
|
+
let yVal2 = mode == 1 ? data[i][idx2] : data[i][1][idx2];
|
|
4399
|
+
|
|
4400
|
+
shouldSetLegend = shouldSetLegend || yVal2 != yVal1 || idx2 != idx1;
|
|
4401
|
+
|
|
4402
|
+
activeIdxs[i] = idx2;
|
|
4403
|
+
|
|
4404
|
+
let xPos2 = idx2 == idx ? xPos : incrRoundUp(valToPosX(mode == 1 ? data[0][idx2] : data[i][0][idx2], scaleX, xDim, 0), 0.5);
|
|
4405
|
+
|
|
4406
|
+
if (i > 0 && s.show) {
|
|
4407
|
+
let yPos = yVal2 == null ? -10 : incrRoundUp(valToPosY(yVal2, mode == 1 ? scales[s.scale] : scales[s.facets[1].scale], yDim, 0), 0.5);
|
|
4408
|
+
|
|
4409
|
+
if (yPos > 0 && mode == 1) {
|
|
4410
|
+
let dist = abs(yPos - mouseTop1);
|
|
4411
|
+
|
|
4412
|
+
if (dist <= closestDist) {
|
|
4413
|
+
closestDist = dist;
|
|
4414
|
+
closestSeries = i;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
let hPos, vPos;
|
|
4419
|
+
|
|
4420
|
+
if (scaleX.ori == 0) {
|
|
4421
|
+
hPos = xPos2;
|
|
4422
|
+
vPos = yPos;
|
|
4423
|
+
}
|
|
4424
|
+
else {
|
|
4425
|
+
hPos = yPos;
|
|
4426
|
+
vPos = xPos2;
|
|
4427
|
+
}
|
|
4428
|
+
|
|
4429
|
+
if (shouldSetLegend && cursorPts.length > 1) {
|
|
4430
|
+
elTrans(cursorPts[i], hPos, vPos, plotWidCss, plotHgtCss);
|
|
4431
|
+
elColor(cursorPts[i], cursor.points.fill(self, i), cursor.points.stroke(self, i));
|
|
4432
|
+
mode == 2 && elSize(cursorPts[i], cursor.points.size(self, i));
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
|
|
4436
|
+
if (legend.live) {
|
|
4437
|
+
if (!shouldSetLegend || i == 0 && multiValLegend)
|
|
4438
|
+
continue;
|
|
4439
|
+
|
|
4440
|
+
setLegendValues(i, idx2);
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
|
|
4445
|
+
if (shouldSetLegend) {
|
|
4446
|
+
legend.idx = idx;
|
|
4447
|
+
setLegend();
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
// nit: cursor.drag.setSelect is assumed always true
|
|
4451
|
+
if (select.show && dragging) {
|
|
4452
|
+
if (src != null) {
|
|
4453
|
+
let [xKey, yKey] = syncOpts.scales;
|
|
4454
|
+
let [matchXKeys, matchYKeys] = syncOpts.match;
|
|
4455
|
+
let [xKeySrc, yKeySrc] = src.cursor.sync.scales;
|
|
4456
|
+
|
|
4457
|
+
// match the dragX/dragY implicitness/explicitness of src
|
|
4458
|
+
let sdrag = src.cursor.drag;
|
|
4459
|
+
dragX = sdrag._x;
|
|
4460
|
+
dragY = sdrag._y;
|
|
4461
|
+
|
|
4462
|
+
let { left, top, width, height } = src.select;
|
|
4463
|
+
|
|
4464
|
+
let sori = src.scales[xKey].ori;
|
|
4465
|
+
let sPosToVal = src.posToVal;
|
|
4466
|
+
|
|
4467
|
+
let sOff, sDim, sc, a, b;
|
|
4468
|
+
|
|
4469
|
+
let matchingX = xKey != null && matchXKeys(xKey, xKeySrc);
|
|
4470
|
+
let matchingY = yKey != null && matchYKeys(yKey, yKeySrc);
|
|
4471
|
+
|
|
4472
|
+
if (matchingX) {
|
|
4473
|
+
if (sori == 0) {
|
|
4474
|
+
sOff = left;
|
|
4475
|
+
sDim = width;
|
|
4476
|
+
}
|
|
4477
|
+
else {
|
|
4478
|
+
sOff = top;
|
|
4479
|
+
sDim = height;
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
if (dragX) {
|
|
4483
|
+
sc = scales[xKey];
|
|
4484
|
+
|
|
4485
|
+
a = valToPosX(sPosToVal(sOff, xKeySrc), sc, xDim, 0);
|
|
4486
|
+
b = valToPosX(sPosToVal(sOff + sDim, xKeySrc), sc, xDim, 0);
|
|
4487
|
+
|
|
4488
|
+
setSelX(min(a,b), abs(b-a));
|
|
4489
|
+
}
|
|
4490
|
+
else
|
|
4491
|
+
setSelX(0, xDim);
|
|
4492
|
+
|
|
4493
|
+
if (!matchingY)
|
|
4494
|
+
setSelY(0, yDim);
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4497
|
+
if (matchingY) {
|
|
4498
|
+
if (sori == 1) {
|
|
4499
|
+
sOff = left;
|
|
4500
|
+
sDim = width;
|
|
4501
|
+
}
|
|
4502
|
+
else {
|
|
4503
|
+
sOff = top;
|
|
4504
|
+
sDim = height;
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4507
|
+
if (dragY) {
|
|
4508
|
+
sc = scales[yKey];
|
|
4509
|
+
|
|
4510
|
+
a = valToPosY(sPosToVal(sOff, yKeySrc), sc, yDim, 0);
|
|
4511
|
+
b = valToPosY(sPosToVal(sOff + sDim, yKeySrc), sc, yDim, 0);
|
|
4512
|
+
|
|
4513
|
+
setSelY(min(a,b), abs(b-a));
|
|
4514
|
+
}
|
|
4515
|
+
else
|
|
4516
|
+
setSelY(0, yDim);
|
|
4517
|
+
|
|
4518
|
+
if (!matchingX)
|
|
4519
|
+
setSelX(0, xDim);
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
else {
|
|
4523
|
+
let rawDX = abs(rawMouseLeft1 - rawMouseLeft0);
|
|
4524
|
+
let rawDY = abs(rawMouseTop1 - rawMouseTop0);
|
|
4525
|
+
|
|
4526
|
+
if (scaleX.ori == 1) {
|
|
4527
|
+
let _rawDX = rawDX;
|
|
4528
|
+
rawDX = rawDY;
|
|
4529
|
+
rawDY = _rawDX;
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
dragX = drag.x && rawDX >= drag.dist;
|
|
4533
|
+
dragY = drag.y && rawDY >= drag.dist;
|
|
4534
|
+
|
|
4535
|
+
let uni = drag.uni;
|
|
4536
|
+
|
|
4537
|
+
if (uni != null) {
|
|
4538
|
+
// only calc drag status if they pass the dist thresh
|
|
4539
|
+
if (dragX && dragY) {
|
|
4540
|
+
dragX = rawDX >= uni;
|
|
4541
|
+
dragY = rawDY >= uni;
|
|
4542
|
+
|
|
4543
|
+
// force unidirectionality when both are under uni limit
|
|
4544
|
+
if (!dragX && !dragY) {
|
|
4545
|
+
if (rawDY > rawDX)
|
|
4546
|
+
dragY = true;
|
|
4547
|
+
else
|
|
4548
|
+
dragX = true;
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
else if (drag.x && drag.y && (dragX || dragY))
|
|
4553
|
+
// if omni with no uni then both dragX / dragY should be true if either is true
|
|
4554
|
+
dragX = dragY = true;
|
|
4555
|
+
|
|
4556
|
+
let p0, p1;
|
|
4557
|
+
|
|
4558
|
+
if (dragX) {
|
|
4559
|
+
if (scaleX.ori == 0) {
|
|
4560
|
+
p0 = mouseLeft0;
|
|
4561
|
+
p1 = mouseLeft1;
|
|
4562
|
+
}
|
|
4563
|
+
else {
|
|
4564
|
+
p0 = mouseTop0;
|
|
4565
|
+
p1 = mouseTop1;
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
setSelX(min(p0, p1), abs(p1 - p0));
|
|
4569
|
+
|
|
4570
|
+
if (!dragY)
|
|
4571
|
+
setSelY(0, yDim);
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4574
|
+
if (dragY) {
|
|
4575
|
+
if (scaleX.ori == 1) {
|
|
4576
|
+
p0 = mouseLeft0;
|
|
4577
|
+
p1 = mouseLeft1;
|
|
4578
|
+
}
|
|
4579
|
+
else {
|
|
4580
|
+
p0 = mouseTop0;
|
|
4581
|
+
p1 = mouseTop1;
|
|
4582
|
+
}
|
|
4583
|
+
|
|
4584
|
+
setSelY(min(p0, p1), abs(p1 - p0));
|
|
4585
|
+
|
|
4586
|
+
if (!dragX)
|
|
4587
|
+
setSelX(0, xDim);
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
// the drag didn't pass the dist requirement
|
|
4591
|
+
if (!dragX && !dragY) {
|
|
4592
|
+
setSelX(0, 0);
|
|
4593
|
+
setSelY(0, 0);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
|
|
4598
|
+
cursor.idx = idx;
|
|
4599
|
+
cursor.left = mouseLeft1;
|
|
4600
|
+
cursor.top = mouseTop1;
|
|
4601
|
+
drag._x = dragX;
|
|
4602
|
+
drag._y = dragY;
|
|
4603
|
+
|
|
4604
|
+
if (src == null) {
|
|
4605
|
+
if (_pub) {
|
|
4606
|
+
if (syncKey != null) {
|
|
4607
|
+
let [xSyncKey, ySyncKey] = syncOpts.scales;
|
|
4608
|
+
|
|
4609
|
+
syncOpts.values[0] = xSyncKey != null ? posToVal(scaleX.ori == 0 ? mouseLeft1 : mouseTop1, xSyncKey) : null;
|
|
4610
|
+
syncOpts.values[1] = ySyncKey != null ? posToVal(scaleX.ori == 1 ? mouseLeft1 : mouseTop1, ySyncKey) : null;
|
|
4611
|
+
}
|
|
4612
|
+
|
|
4613
|
+
pubSync(mousemove, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, idx);
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4616
|
+
if (cursorFocus) {
|
|
4617
|
+
let shouldPub = _pub && syncOpts.setSeries;
|
|
4618
|
+
let p = focus.prox;
|
|
4619
|
+
|
|
4620
|
+
if (focusedSeries == null) {
|
|
4621
|
+
if (closestDist <= p)
|
|
4622
|
+
setSeries(closestSeries, FOCUS_TRUE, true, shouldPub);
|
|
4623
|
+
}
|
|
4624
|
+
else {
|
|
4625
|
+
if (closestDist > p)
|
|
4626
|
+
setSeries(null, FOCUS_TRUE, true, shouldPub);
|
|
4627
|
+
else if (closestSeries != focusedSeries)
|
|
4628
|
+
setSeries(closestSeries, FOCUS_TRUE, true, shouldPub);
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
|
|
4633
|
+
ready && _fire !== false && fire("setCursor");
|
|
4634
|
+
}
|
|
4635
|
+
|
|
4636
|
+
let rect = null;
|
|
4637
|
+
|
|
4638
|
+
function syncRect(defer) {
|
|
4639
|
+
if (defer === true)
|
|
4640
|
+
rect = null;
|
|
4641
|
+
else {
|
|
4642
|
+
rect = over.getBoundingClientRect();
|
|
4643
|
+
fire("syncRect", rect);
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4647
|
+
function mouseMove(e, src, _l, _t, _w, _h, _i) {
|
|
4648
|
+
if (cursor._lock)
|
|
4649
|
+
return;
|
|
4650
|
+
|
|
4651
|
+
cacheMouse(e, src, _l, _t, _w, _h, _i, false, e != null);
|
|
4652
|
+
|
|
4653
|
+
if (e != null)
|
|
4654
|
+
updateCursor(null, true, true);
|
|
4655
|
+
else
|
|
4656
|
+
updateCursor(src, true, false);
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
function cacheMouse(e, src, _l, _t, _w, _h, _i, initial, snap) {
|
|
4660
|
+
if (rect == null)
|
|
4661
|
+
syncRect(false);
|
|
4662
|
+
|
|
4663
|
+
if (e != null) {
|
|
4664
|
+
_l = e.clientX - rect.left;
|
|
4665
|
+
_t = e.clientY - rect.top;
|
|
4666
|
+
}
|
|
4667
|
+
else {
|
|
4668
|
+
if (_l < 0 || _t < 0) {
|
|
4669
|
+
mouseLeft1 = -10;
|
|
4670
|
+
mouseTop1 = -10;
|
|
4671
|
+
return;
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
let [xKey, yKey] = syncOpts.scales;
|
|
4675
|
+
|
|
4676
|
+
let syncOptsSrc = src.cursor.sync;
|
|
4677
|
+
let [xValSrc, yValSrc] = syncOptsSrc.values;
|
|
4678
|
+
let [xKeySrc, yKeySrc] = syncOptsSrc.scales;
|
|
4679
|
+
let [matchXKeys, matchYKeys] = syncOpts.match;
|
|
4680
|
+
|
|
4681
|
+
let rotSrc = src.scales[xKeySrc].ori == 1;
|
|
4682
|
+
|
|
4683
|
+
let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss,
|
|
4684
|
+
yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss,
|
|
4685
|
+
_xDim = rotSrc ? _h : _w,
|
|
4686
|
+
_yDim = rotSrc ? _w : _h,
|
|
4687
|
+
_xPos = rotSrc ? _t : _l,
|
|
4688
|
+
_yPos = rotSrc ? _l : _t;
|
|
4689
|
+
|
|
4690
|
+
if (xKeySrc != null)
|
|
4691
|
+
_l = matchXKeys(xKey, xKeySrc) ? getPos(xValSrc, scales[xKey], xDim, 0) : -10;
|
|
4692
|
+
else
|
|
4693
|
+
_l = xDim * (_xPos/_xDim);
|
|
4694
|
+
|
|
4695
|
+
if (yKeySrc != null)
|
|
4696
|
+
_t = matchYKeys(yKey, yKeySrc) ? getPos(yValSrc, scales[yKey], yDim, 0) : -10;
|
|
4697
|
+
else
|
|
4698
|
+
_t = yDim * (_yPos/_yDim);
|
|
4699
|
+
|
|
4700
|
+
if (scaleX.ori == 1) {
|
|
4701
|
+
let __l = _l;
|
|
4702
|
+
_l = _t;
|
|
4703
|
+
_t = __l;
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
|
|
4707
|
+
if (snap) {
|
|
4708
|
+
if (_l <= 1 || _l >= plotWidCss - 1)
|
|
4709
|
+
_l = incrRound(_l, plotWidCss);
|
|
4710
|
+
|
|
4711
|
+
if (_t <= 1 || _t >= plotHgtCss - 1)
|
|
4712
|
+
_t = incrRound(_t, plotHgtCss);
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
if (initial) {
|
|
4716
|
+
rawMouseLeft0 = _l;
|
|
4717
|
+
rawMouseTop0 = _t;
|
|
4718
|
+
|
|
4719
|
+
[mouseLeft0, mouseTop0] = cursor.move(self, _l, _t);
|
|
4720
|
+
}
|
|
4721
|
+
else {
|
|
4722
|
+
mouseLeft1 = _l;
|
|
4723
|
+
mouseTop1 = _t;
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
4726
|
+
|
|
4727
|
+
function hideSelect() {
|
|
4728
|
+
setSelect({
|
|
4729
|
+
width: 0,
|
|
4730
|
+
height: 0,
|
|
4731
|
+
}, false);
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
function mouseDown(e, src, _l, _t, _w, _h, _i) {
|
|
4735
|
+
dragging = true;
|
|
4736
|
+
dragX = dragY = drag._x = drag._y = false;
|
|
4737
|
+
|
|
4738
|
+
cacheMouse(e, src, _l, _t, _w, _h, _i, true, false);
|
|
4739
|
+
|
|
4740
|
+
if (e != null) {
|
|
4741
|
+
onMouse(mouseup, doc, mouseUp);
|
|
4742
|
+
pubSync(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
|
|
4746
|
+
function mouseUp(e, src, _l, _t, _w, _h, _i) {
|
|
4747
|
+
dragging = drag._x = drag._y = false;
|
|
4748
|
+
|
|
4749
|
+
cacheMouse(e, src, _l, _t, _w, _h, _i, false, true);
|
|
4750
|
+
|
|
4751
|
+
let { left, top, width, height } = select;
|
|
4752
|
+
|
|
4753
|
+
let hasSelect = width > 0 || height > 0;
|
|
4754
|
+
|
|
4755
|
+
hasSelect && setSelect(select);
|
|
4756
|
+
|
|
4757
|
+
if (drag.setScale && hasSelect) {
|
|
4758
|
+
// if (syncKey != null) {
|
|
4759
|
+
// dragX = drag.x;
|
|
4760
|
+
// dragY = drag.y;
|
|
4761
|
+
// }
|
|
4762
|
+
|
|
4763
|
+
let xOff = left,
|
|
4764
|
+
xDim = width,
|
|
4765
|
+
yOff = top,
|
|
4766
|
+
yDim = height;
|
|
4767
|
+
|
|
4768
|
+
if (scaleX.ori == 1) {
|
|
4769
|
+
xOff = top,
|
|
4770
|
+
xDim = height,
|
|
4771
|
+
yOff = left,
|
|
4772
|
+
yDim = width;
|
|
4773
|
+
}
|
|
4774
|
+
|
|
4775
|
+
if (dragX) {
|
|
4776
|
+
_setScale(xScaleKey,
|
|
4777
|
+
posToVal(xOff, xScaleKey),
|
|
4778
|
+
posToVal(xOff + xDim, xScaleKey)
|
|
4779
|
+
);
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
if (dragY) {
|
|
4783
|
+
for (let k in scales) {
|
|
4784
|
+
let sc = scales[k];
|
|
4785
|
+
|
|
4786
|
+
if (k != xScaleKey && sc.from == null && sc.min != inf) {
|
|
4787
|
+
_setScale(k,
|
|
4788
|
+
posToVal(yOff + yDim, k),
|
|
4789
|
+
posToVal(yOff, k)
|
|
4790
|
+
);
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4795
|
+
hideSelect();
|
|
4796
|
+
}
|
|
4797
|
+
else if (cursor.lock) {
|
|
4798
|
+
cursor._lock = !cursor._lock;
|
|
4799
|
+
|
|
4800
|
+
if (!cursor._lock)
|
|
4801
|
+
updateCursor(null, true, false);
|
|
4802
|
+
}
|
|
4803
|
+
|
|
4804
|
+
if (e != null) {
|
|
4805
|
+
offMouse(mouseup, doc);
|
|
4806
|
+
pubSync(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4809
|
+
|
|
4810
|
+
function mouseLeave(e, src, _l, _t, _w, _h, _i) {
|
|
4811
|
+
if (!cursor._lock) {
|
|
4812
|
+
let _dragging = dragging;
|
|
4813
|
+
|
|
4814
|
+
if (dragging) {
|
|
4815
|
+
// handle case when mousemove aren't fired all the way to edges by browser
|
|
4816
|
+
let snapH = true;
|
|
4817
|
+
let snapV = true;
|
|
4818
|
+
let snapProx = 10;
|
|
4819
|
+
|
|
4820
|
+
let dragH, dragV;
|
|
4821
|
+
|
|
4822
|
+
if (scaleX.ori == 0) {
|
|
4823
|
+
dragH = dragX;
|
|
4824
|
+
dragV = dragY;
|
|
4825
|
+
}
|
|
4826
|
+
else {
|
|
4827
|
+
dragH = dragY;
|
|
4828
|
+
dragV = dragX;
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4831
|
+
if (dragH && dragV) {
|
|
4832
|
+
// maybe omni corner snap
|
|
4833
|
+
snapH = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;
|
|
4834
|
+
snapV = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
if (dragH && snapH)
|
|
4838
|
+
mouseLeft1 = mouseLeft1 < mouseLeft0 ? 0 : plotWidCss;
|
|
4839
|
+
|
|
4840
|
+
if (dragV && snapV)
|
|
4841
|
+
mouseTop1 = mouseTop1 < mouseTop0 ? 0 : plotHgtCss;
|
|
4842
|
+
|
|
4843
|
+
updateCursor(null, true, true);
|
|
4844
|
+
|
|
4845
|
+
dragging = false;
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
mouseLeft1 = -10;
|
|
4849
|
+
mouseTop1 = -10;
|
|
4850
|
+
|
|
4851
|
+
// passing a non-null timestamp to force sync/mousemove event
|
|
4852
|
+
updateCursor(null, true, true);
|
|
4853
|
+
|
|
4854
|
+
if (_dragging)
|
|
4855
|
+
dragging = _dragging;
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
function dblClick(e, src, _l, _t, _w, _h, _i) {
|
|
4860
|
+
autoScaleX();
|
|
4861
|
+
|
|
4862
|
+
hideSelect();
|
|
4863
|
+
|
|
4864
|
+
if (e != null)
|
|
4865
|
+
pubSync(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4868
|
+
function syncPxRatio() {
|
|
4869
|
+
axes.forEach(syncFontSize);
|
|
4870
|
+
_setSize(self.width, self.height, true);
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
on(dppxchange, win, syncPxRatio);
|
|
4874
|
+
|
|
4875
|
+
// internal pub/sub
|
|
4876
|
+
const events = {};
|
|
4877
|
+
|
|
4878
|
+
events.mousedown = mouseDown;
|
|
4879
|
+
events.mousemove = mouseMove;
|
|
4880
|
+
events.mouseup = mouseUp;
|
|
4881
|
+
events.dblclick = dblClick;
|
|
4882
|
+
events["setSeries"] = (e, src, idx, opts) => {
|
|
4883
|
+
setSeries(idx, opts, true, false);
|
|
4884
|
+
};
|
|
4885
|
+
|
|
4886
|
+
if (cursor.show) {
|
|
4887
|
+
onMouse(mousedown, over, mouseDown);
|
|
4888
|
+
onMouse(mousemove, over, mouseMove);
|
|
4889
|
+
onMouse(mouseenter, over, syncRect);
|
|
4890
|
+
onMouse(mouseleave, over, mouseLeave);
|
|
4891
|
+
|
|
4892
|
+
onMouse(dblclick, over, dblClick);
|
|
4893
|
+
|
|
4894
|
+
cursorPlots.add(self);
|
|
4895
|
+
|
|
4896
|
+
self.syncRect = syncRect;
|
|
4897
|
+
}
|
|
4898
|
+
|
|
4899
|
+
// external on/off
|
|
4900
|
+
const hooks = self.hooks = opts.hooks || {};
|
|
4901
|
+
|
|
4902
|
+
function fire(evName, a1, a2) {
|
|
4903
|
+
if (evName in hooks) {
|
|
4904
|
+
hooks[evName].forEach(fn => {
|
|
4905
|
+
fn.call(null, self, a1, a2);
|
|
4906
|
+
});
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
(opts.plugins || []).forEach(p => {
|
|
4911
|
+
for (let evName in p.hooks)
|
|
4912
|
+
hooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]);
|
|
4913
|
+
});
|
|
4914
|
+
|
|
4915
|
+
const syncOpts = assign({
|
|
4916
|
+
key: null,
|
|
4917
|
+
setSeries: false,
|
|
4918
|
+
filters: {
|
|
4919
|
+
pub: retTrue,
|
|
4920
|
+
sub: retTrue,
|
|
4921
|
+
},
|
|
4922
|
+
scales: [xScaleKey, series[1] ? series[1].scale : null],
|
|
4923
|
+
match: [retEq, retEq],
|
|
4924
|
+
values: [null, null],
|
|
4925
|
+
}, cursor.sync);
|
|
4926
|
+
|
|
4927
|
+
(cursor.sync = syncOpts);
|
|
4928
|
+
|
|
4929
|
+
const syncKey = syncOpts.key;
|
|
4930
|
+
|
|
4931
|
+
const sync = _sync(syncKey);
|
|
4932
|
+
|
|
4933
|
+
function pubSync(type, src, x, y, w, h, i) {
|
|
4934
|
+
if (syncOpts.filters.pub(type, src, x, y, w, h, i))
|
|
4935
|
+
sync.pub(type, src, x, y, w, h, i);
|
|
4936
|
+
}
|
|
4937
|
+
|
|
4938
|
+
sync.sub(self);
|
|
4939
|
+
|
|
4940
|
+
function pub(type, src, x, y, w, h, i) {
|
|
4941
|
+
if (syncOpts.filters.sub(type, src, x, y, w, h, i))
|
|
4942
|
+
events[type](null, src, x, y, w, h, i);
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4945
|
+
(self.pub = pub);
|
|
4946
|
+
|
|
4947
|
+
function destroy() {
|
|
4948
|
+
sync.unsub(self);
|
|
4949
|
+
cursorPlots.delete(self);
|
|
4950
|
+
mouseListeners.clear();
|
|
4951
|
+
off(dppxchange, win, syncPxRatio);
|
|
4952
|
+
root.remove();
|
|
4953
|
+
fire("destroy");
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4956
|
+
self.destroy = destroy;
|
|
4957
|
+
|
|
4958
|
+
function _init() {
|
|
4959
|
+
fire("init", opts, data);
|
|
4960
|
+
|
|
4961
|
+
setData(data || opts.data, false);
|
|
4962
|
+
|
|
4963
|
+
if (pendScales[xScaleKey])
|
|
4964
|
+
setScale(xScaleKey, pendScales[xScaleKey]);
|
|
4965
|
+
else
|
|
4966
|
+
autoScaleX();
|
|
4967
|
+
|
|
4968
|
+
_setSize(opts.width, opts.height);
|
|
4969
|
+
|
|
4970
|
+
updateCursor(null, true, false);
|
|
4971
|
+
|
|
4972
|
+
setSelect(select, false);
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4975
|
+
series.forEach(initSeries);
|
|
4976
|
+
|
|
4977
|
+
axes.forEach(initAxis);
|
|
4978
|
+
|
|
4979
|
+
if (then) {
|
|
4980
|
+
if (then instanceof HTMLElement) {
|
|
4981
|
+
then.appendChild(root);
|
|
4982
|
+
_init();
|
|
4983
|
+
}
|
|
4984
|
+
else
|
|
4985
|
+
then(self, _init);
|
|
4986
|
+
}
|
|
4987
|
+
else
|
|
4988
|
+
_init();
|
|
4989
|
+
|
|
4990
|
+
return self;
|
|
4991
|
+
}
|
|
4992
|
+
|
|
4993
|
+
uPlot.assign = assign;
|
|
4994
|
+
uPlot.fmtNum = fmtNum;
|
|
4995
|
+
uPlot.rangeNum = rangeNum;
|
|
4996
|
+
uPlot.rangeLog = rangeLog;
|
|
4997
|
+
uPlot.rangeAsinh = rangeAsinh;
|
|
4998
|
+
uPlot.orient = orient;
|
|
4999
|
+
|
|
5000
|
+
{
|
|
5001
|
+
uPlot.join = join;
|
|
5002
|
+
}
|
|
5003
|
+
|
|
5004
|
+
{
|
|
5005
|
+
uPlot.fmtDate = fmtDate;
|
|
5006
|
+
uPlot.tzDate = tzDate;
|
|
5007
|
+
}
|
|
5008
|
+
|
|
5009
|
+
{
|
|
5010
|
+
uPlot.sync = _sync;
|
|
5011
|
+
}
|
|
5012
|
+
|
|
5013
|
+
{
|
|
5014
|
+
uPlot.addGap = addGap;
|
|
5015
|
+
uPlot.clipGaps = clipGaps;
|
|
5016
|
+
|
|
5017
|
+
let paths = uPlot.paths = {
|
|
5018
|
+
points,
|
|
5019
|
+
};
|
|
5020
|
+
|
|
5021
|
+
(paths.linear = linear);
|
|
5022
|
+
(paths.stepped = stepped);
|
|
5023
|
+
(paths.bars = bars);
|
|
5024
|
+
(paths.spline = monotoneCubic);
|
|
5025
|
+
}
|
|
5026
|
+
|
|
5027
|
+
return uPlot;
|
|
5028
|
+
|
|
5029
|
+
}());
|