uplot-webgpu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CANVAS_PROXY.md +602 -0
- package/README.md +854 -0
- package/favicon.ico +0 -0
- package/index.html +14 -0
- package/index.js +21 -0
- package/original/paths.canvas2d/bars.js +252 -0
- package/original/paths.canvas2d/catmullRomCentrip.js +125 -0
- package/original/paths.canvas2d/linear.js +170 -0
- package/original/paths.canvas2d/monotoneCubic.js +68 -0
- package/original/paths.canvas2d/points.js +66 -0
- package/original/paths.canvas2d/spline.js +103 -0
- package/original/paths.canvas2d/stepped.js +124 -0
- package/original/paths.canvas2d/utils.js +301 -0
- package/original/uPlot.canvas2d.js +3548 -0
- package/package.json +110 -0
- package/paths/bars.js +253 -0
- package/paths/catmullRomCentrip.js +126 -0
- package/paths/linear.js +171 -0
- package/paths/monotoneCubic.js +69 -0
- package/paths/points.js +67 -0
- package/paths/spline.js +104 -0
- package/paths/stepped.js +125 -0
- package/paths/utils.js +301 -0
- package/scripts/uPlot.css +168 -0
- package/scripts/uPlot.d.ts +26 -0
- package/scripts/uPlot.js +3687 -0
- package/scripts/utils/dom.js +124 -0
- package/scripts/utils/domClasses.js +22 -0
- package/scripts/utils/feats.js +13 -0
- package/scripts/utils/fmtDate.js +398 -0
- package/scripts/utils/opts.js +844 -0
- package/scripts/utils/strings.js +22 -0
- package/scripts/utils/sync.js +27 -0
- package/scripts/utils/utils.js +692 -0
- package/scripts/webgpu/GPUPath.d.ts +46 -0
- package/scripts/webgpu/GPUPath.js +633 -0
- package/scripts/webgpu/GPUPath.ts +634 -0
- package/scripts/webgpu/WebGPURenderer.d.ts +176 -0
- package/scripts/webgpu/WebGPURenderer.js +4256 -0
- package/scripts/webgpu/WebGPURenderer.ts +4257 -0
- package/scripts/webgpu/browserSmokeHarness.js +105 -0
- package/scripts/webgpu/exporters.d.ts +8 -0
- package/scripts/webgpu/exporters.js +212 -0
- package/scripts/webgpu/shaders.d.ts +2 -0
- package/scripts/webgpu/shaders.js +76 -0
- package/scripts/webgpu/shaders.ts +77 -0
- package/scripts/webgpu/smokeTest.d.ts +2 -0
- package/scripts/webgpu/smokeTest.js +144 -0
- package/scripts/webgpu/webgpu-ambient.d.ts +41 -0
- package/tinybuild.config.js +109 -0
|
@@ -0,0 +1,3548 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FEAT_TIME,
|
|
3
|
+
FEAT_LEGEND,
|
|
4
|
+
|
|
5
|
+
FEAT_POINTS,
|
|
6
|
+
|
|
7
|
+
FEAT_PATHS,
|
|
8
|
+
FEAT_PATHS_LINEAR,
|
|
9
|
+
FEAT_PATHS_SPLINE,
|
|
10
|
+
FEAT_PATHS_SPLINE2,
|
|
11
|
+
FEAT_PATHS_STEPPED,
|
|
12
|
+
FEAT_PATHS_BARS,
|
|
13
|
+
|
|
14
|
+
FEAT_JOIN,
|
|
15
|
+
} from '../scripts/utils/feats.js';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
copy,
|
|
19
|
+
assign,
|
|
20
|
+
PI,
|
|
21
|
+
inf,
|
|
22
|
+
abs,
|
|
23
|
+
floor,
|
|
24
|
+
round,
|
|
25
|
+
roundDec,
|
|
26
|
+
ceil,
|
|
27
|
+
min,
|
|
28
|
+
max,
|
|
29
|
+
clamp,
|
|
30
|
+
pow,
|
|
31
|
+
asinh,
|
|
32
|
+
sinh,
|
|
33
|
+
log10,
|
|
34
|
+
closestIdx,
|
|
35
|
+
getMinMax,
|
|
36
|
+
rangeNum,
|
|
37
|
+
rangeLog,
|
|
38
|
+
rangeAsinh,
|
|
39
|
+
incrRound,
|
|
40
|
+
incrRoundUp,
|
|
41
|
+
isArr,
|
|
42
|
+
isObj,
|
|
43
|
+
fastIsObj,
|
|
44
|
+
isStr,
|
|
45
|
+
fnOrSelf,
|
|
46
|
+
fmtNum,
|
|
47
|
+
fixedDec,
|
|
48
|
+
ifNull,
|
|
49
|
+
join,
|
|
50
|
+
microTask,
|
|
51
|
+
retArg0,
|
|
52
|
+
retArg1,
|
|
53
|
+
retNull,
|
|
54
|
+
retTrue,
|
|
55
|
+
EMPTY_OBJ,
|
|
56
|
+
EMPTY_ARR,
|
|
57
|
+
nullNullTuple,
|
|
58
|
+
retEq,
|
|
59
|
+
autoRangePart,
|
|
60
|
+
rangePad,
|
|
61
|
+
hasData,
|
|
62
|
+
numIntDigits,
|
|
63
|
+
isUndef,
|
|
64
|
+
guessDec,
|
|
65
|
+
cmpObj,
|
|
66
|
+
isFn,
|
|
67
|
+
} from '../scripts/utils/utils.js';
|
|
68
|
+
|
|
69
|
+
import {
|
|
70
|
+
WIDTH,
|
|
71
|
+
HEIGHT,
|
|
72
|
+
TOP,
|
|
73
|
+
BOTTOM,
|
|
74
|
+
LEFT,
|
|
75
|
+
RIGHT,
|
|
76
|
+
transparent,
|
|
77
|
+
|
|
78
|
+
mousemove,
|
|
79
|
+
mousedown,
|
|
80
|
+
mouseup,
|
|
81
|
+
mouseleave,
|
|
82
|
+
mouseenter,
|
|
83
|
+
dblclick,
|
|
84
|
+
resize,
|
|
85
|
+
scroll,
|
|
86
|
+
|
|
87
|
+
dppxchange,
|
|
88
|
+
LEGEND_DISP
|
|
89
|
+
} from '../scripts/utils/strings.js';
|
|
90
|
+
|
|
91
|
+
import {
|
|
92
|
+
UPLOT,
|
|
93
|
+
ORI_HZ,
|
|
94
|
+
ORI_VT,
|
|
95
|
+
TITLE,
|
|
96
|
+
WRAP,
|
|
97
|
+
UNDER,
|
|
98
|
+
OVER,
|
|
99
|
+
AXIS,
|
|
100
|
+
OFF,
|
|
101
|
+
SELECT,
|
|
102
|
+
CURSOR_X,
|
|
103
|
+
CURSOR_Y,
|
|
104
|
+
CURSOR_PT,
|
|
105
|
+
LEGEND,
|
|
106
|
+
LEGEND_LIVE,
|
|
107
|
+
LEGEND_INLINE,
|
|
108
|
+
LEGEND_SERIES,
|
|
109
|
+
LEGEND_MARKER,
|
|
110
|
+
LEGEND_LABEL,
|
|
111
|
+
LEGEND_VALUE,
|
|
112
|
+
} from '../scripts/utils/domClasses.js';
|
|
113
|
+
|
|
114
|
+
import {
|
|
115
|
+
domEnv,
|
|
116
|
+
doc,
|
|
117
|
+
win,
|
|
118
|
+
pxRatio as pxRatioGlobal,
|
|
119
|
+
|
|
120
|
+
addClass,
|
|
121
|
+
remClass,
|
|
122
|
+
setStylePx,
|
|
123
|
+
placeTag,
|
|
124
|
+
placeDiv,
|
|
125
|
+
elTrans,
|
|
126
|
+
elColor,
|
|
127
|
+
elSize,
|
|
128
|
+
on,
|
|
129
|
+
off,
|
|
130
|
+
} from '../scripts/utils/dom.js';
|
|
131
|
+
|
|
132
|
+
import {
|
|
133
|
+
fmtDate,
|
|
134
|
+
tzDate,
|
|
135
|
+
} from '../scripts/utils/fmtDate.js';
|
|
136
|
+
|
|
137
|
+
import {
|
|
138
|
+
ptDia,
|
|
139
|
+
cursorOpts,
|
|
140
|
+
|
|
141
|
+
xAxisOpts,
|
|
142
|
+
yAxisOpts,
|
|
143
|
+
xSeriesOpts,
|
|
144
|
+
ySeriesOpts,
|
|
145
|
+
xScaleOpts,
|
|
146
|
+
yScaleOpts,
|
|
147
|
+
|
|
148
|
+
xySeriesOpts,
|
|
149
|
+
|
|
150
|
+
clampScale,
|
|
151
|
+
|
|
152
|
+
timeIncrsMs,
|
|
153
|
+
timeIncrsS,
|
|
154
|
+
|
|
155
|
+
wholeIncrs,
|
|
156
|
+
numIncrs,
|
|
157
|
+
timeAxisVal,
|
|
158
|
+
timeAxisVals,
|
|
159
|
+
numAxisVals,
|
|
160
|
+
|
|
161
|
+
log2AxisValsFilt,
|
|
162
|
+
log10AxisValsFilt,
|
|
163
|
+
|
|
164
|
+
timeSeriesVal,
|
|
165
|
+
numSeriesVal,
|
|
166
|
+
|
|
167
|
+
timeSeriesLabel,
|
|
168
|
+
numSeriesLabel,
|
|
169
|
+
|
|
170
|
+
timeAxisSplitsMs,
|
|
171
|
+
timeAxisSplitsS,
|
|
172
|
+
|
|
173
|
+
numAxisSplits,
|
|
174
|
+
logAxisSplits,
|
|
175
|
+
asinhAxisSplits,
|
|
176
|
+
|
|
177
|
+
timeAxisStamps,
|
|
178
|
+
|
|
179
|
+
_timeAxisStampsMs,
|
|
180
|
+
_timeAxisStampsS,
|
|
181
|
+
|
|
182
|
+
timeSeriesStamp,
|
|
183
|
+
_timeSeriesStamp,
|
|
184
|
+
|
|
185
|
+
legendOpts,
|
|
186
|
+
} from '../scripts/utils/opts.js';
|
|
187
|
+
|
|
188
|
+
import { _sync } from '../scripts/utils/sync.js';
|
|
189
|
+
|
|
190
|
+
import { points } from './paths.canvas2d/points.js';
|
|
191
|
+
import { linear } from './paths.canvas2d/linear.js';
|
|
192
|
+
import { stepped } from './paths.canvas2d/stepped.js';
|
|
193
|
+
import { bars } from './paths.canvas2d/bars.js';
|
|
194
|
+
import { monotoneCubic as spline } from './paths.canvas2d/monotoneCubic.js';
|
|
195
|
+
import { catmullRomCentrip as spline2 } from './paths.canvas2d/catmullRomCentrip.js';
|
|
196
|
+
|
|
197
|
+
import { addGap, clipGaps, moveToH, moveToV, arcH, arcV, orient, pxRoundGen, seriesFillTo, BAND_CLIP_FILL, BAND_CLIP_STROKE } from './paths.canvas2d/utils.js';
|
|
198
|
+
|
|
199
|
+
function log(name, args) {
|
|
200
|
+
console.log.apply(console, [name].concat(Array.prototype.slice.call(args)));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const cursorPlots = new Set();
|
|
204
|
+
|
|
205
|
+
function invalidateRects() {
|
|
206
|
+
for (let u of cursorPlots)
|
|
207
|
+
u.syncRect(true);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (domEnv) {
|
|
211
|
+
on(resize, win, invalidateRects);
|
|
212
|
+
on(scroll, win, invalidateRects, true);
|
|
213
|
+
on(dppxchange, win, () => { uPlot.pxRatio = pxRatioGlobal; });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const linearPath = FEAT_PATHS && FEAT_PATHS_LINEAR ? linear() : null;
|
|
217
|
+
const pointsPath = FEAT_POINTS ? points() : null;
|
|
218
|
+
|
|
219
|
+
function setDefaults(d, xo, yo, initY) {
|
|
220
|
+
let d2 = initY ? [d[0], d[1]].concat(d.slice(2)) : [d[0]].concat(d.slice(1));
|
|
221
|
+
return d2.map((o, i) => setDefault(o, i, xo, yo));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function setDefaults2(d, xyo) {
|
|
225
|
+
return d.map((o, i) => i == 0 ? {} : assign({}, xyo, o)); // todo: assign() will not merge facet arrays
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function setDefault(o, i, xo, yo) {
|
|
229
|
+
return assign({}, (i == 0 ? xo : yo), o);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function snapNumX(self, dataMin, dataMax) {
|
|
233
|
+
return dataMin == null ? nullNullTuple : [dataMin, dataMax];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const snapTimeX = snapNumX;
|
|
237
|
+
|
|
238
|
+
// this ensures that non-temporal/numeric y-axes get multiple-snapped padding added above/below
|
|
239
|
+
// TODO: also account for incrs when snapping to ensure top of axis gets a tick & value
|
|
240
|
+
function snapNumY(self, dataMin, dataMax) {
|
|
241
|
+
return dataMin == null ? nullNullTuple : rangeNum(dataMin, dataMax, rangePad, true);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function snapLogY(self, dataMin, dataMax, scale) {
|
|
245
|
+
return dataMin == null ? nullNullTuple : rangeLog(dataMin, dataMax, self.scales[scale].log, true);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const snapLogX = snapLogY;
|
|
249
|
+
|
|
250
|
+
function snapAsinhY(self, dataMin, dataMax, scale) {
|
|
251
|
+
return dataMin == null ? nullNullTuple : rangeAsinh(dataMin, dataMax, self.scales[scale].log, true);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const snapAsinhX = snapAsinhY;
|
|
255
|
+
|
|
256
|
+
// dim is logical (getClientBoundingRect) pixels, not canvas pixels
|
|
257
|
+
function findIncr(minVal, maxVal, incrs, dim, minSpace) {
|
|
258
|
+
let intDigits = max(numIntDigits(minVal), numIntDigits(maxVal));
|
|
259
|
+
|
|
260
|
+
let delta = maxVal - minVal;
|
|
261
|
+
|
|
262
|
+
let incrIdx = closestIdx((minSpace / dim) * delta, incrs);
|
|
263
|
+
|
|
264
|
+
do {
|
|
265
|
+
let foundIncr = incrs[incrIdx];
|
|
266
|
+
let foundSpace = dim * foundIncr / delta;
|
|
267
|
+
|
|
268
|
+
if (foundSpace >= minSpace && intDigits + (foundIncr < 5 ? fixedDec.get(foundIncr) : 0) <= 17)
|
|
269
|
+
return [foundIncr, foundSpace];
|
|
270
|
+
} while (++incrIdx < incrs.length);
|
|
271
|
+
|
|
272
|
+
return [0, 0];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function pxRatioFont(font, pxRatio) {
|
|
276
|
+
let fontSize, fontSizeCss;
|
|
277
|
+
font = font.replace(/(\d+)px/, (m, p1) => (fontSize = round((fontSizeCss = +p1) * pxRatio)) + 'px');
|
|
278
|
+
return [font, fontSize, fontSizeCss];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function syncFontSize(axis, pxRatio) {
|
|
282
|
+
if (axis.show) {
|
|
283
|
+
[axis.font, axis.labelFont].forEach(f => {
|
|
284
|
+
let size = roundDec(f[2] * pxRatio, 1);
|
|
285
|
+
f[0] = f[0].replace(/[0-9.]+px/, size + 'px');
|
|
286
|
+
f[1] = size;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default function uPlot(opts, data, then) {
|
|
292
|
+
let pxRatio = opts.pxRatio ?? pxRatioGlobal;
|
|
293
|
+
|
|
294
|
+
function setPxRatio(_pxRatio) {
|
|
295
|
+
pxRatio = self.pxRatio = (_pxRatio ?? pxRatioGlobal);
|
|
296
|
+
axes.forEach(axis => syncFontSize(axis, pxRatio));
|
|
297
|
+
_setSize(self.width, self.height, true);
|
|
298
|
+
}
|
|
299
|
+
const self = {
|
|
300
|
+
mode: ifNull(opts.mode, 1),
|
|
301
|
+
pxRatio,
|
|
302
|
+
setPxRatio,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
self.setPxRatio = setPxRatio;
|
|
306
|
+
const mode = self.mode;
|
|
307
|
+
|
|
308
|
+
function getHPos(val, scale, dim, off) {
|
|
309
|
+
let pct = scale.valToPct(val);
|
|
310
|
+
return off + dim * (scale.dir == -1 ? (1 - pct) : pct);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function getVPos(val, scale, dim, off) {
|
|
314
|
+
let pct = scale.valToPct(val);
|
|
315
|
+
return off + dim * (scale.dir == -1 ? pct : (1 - pct));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function getPos(val, scale, dim, off) {
|
|
319
|
+
return scale.ori == 0 ? getHPos(val, scale, dim, off) : getVPos(val, scale, dim, off);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
self.valToPosH = getHPos;
|
|
323
|
+
self.valToPosV = getVPos;
|
|
324
|
+
|
|
325
|
+
let ready = false;
|
|
326
|
+
self.status = 0;
|
|
327
|
+
|
|
328
|
+
const root = self.root = placeDiv(UPLOT);
|
|
329
|
+
|
|
330
|
+
if (opts.id != null)
|
|
331
|
+
root.id = opts.id;
|
|
332
|
+
|
|
333
|
+
addClass(root, opts.class);
|
|
334
|
+
|
|
335
|
+
if (opts.title) {
|
|
336
|
+
let title = placeDiv(TITLE, root);
|
|
337
|
+
title.textContent = opts.title;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const can = placeTag("canvas");
|
|
341
|
+
const ctx = self.ctx = can.getContext("2d");
|
|
342
|
+
|
|
343
|
+
const wrap = placeDiv(WRAP, root);
|
|
344
|
+
|
|
345
|
+
on("click", wrap, e => {
|
|
346
|
+
if (e.target === over) {
|
|
347
|
+
let didDrag = mouseLeft1 != mouseLeft0 || mouseTop1 != mouseTop0;
|
|
348
|
+
didDrag && drag.click(self, e);
|
|
349
|
+
}
|
|
350
|
+
}, true);
|
|
351
|
+
|
|
352
|
+
const under = self.under = placeDiv(UNDER, wrap);
|
|
353
|
+
wrap.appendChild(can);
|
|
354
|
+
const over = self.over = placeDiv(OVER, wrap);
|
|
355
|
+
|
|
356
|
+
opts = copy(opts);
|
|
357
|
+
|
|
358
|
+
const usePathCache = opts.cache ?? true;
|
|
359
|
+
|
|
360
|
+
const pxAlign = +ifNull(opts.pxAlign, 1);
|
|
361
|
+
|
|
362
|
+
const pxRound = pxRoundGen(pxAlign);
|
|
363
|
+
|
|
364
|
+
(opts.plugins || []).forEach(p => {
|
|
365
|
+
if (p.opts)
|
|
366
|
+
opts = p.opts(self, opts) || opts;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const ms = opts.ms || 1e-3;
|
|
370
|
+
|
|
371
|
+
const series = self.series = mode == 1 ?
|
|
372
|
+
setDefaults(opts.series || [], xSeriesOpts, ySeriesOpts, false) :
|
|
373
|
+
setDefaults2(opts.series || [null], xySeriesOpts);
|
|
374
|
+
const axes = self.axes = setDefaults(opts.axes || [], xAxisOpts, yAxisOpts, true);
|
|
375
|
+
const scales = self.scales = {};
|
|
376
|
+
const bands = self.bands = opts.bands || [];
|
|
377
|
+
|
|
378
|
+
bands.forEach(b => {
|
|
379
|
+
b.fill = fnOrSelf(b.fill || null);
|
|
380
|
+
b.dir = ifNull(b.dir, -1);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const xScaleKey = mode == 2 ? series[1].facets[0].scale : series[0].scale;
|
|
384
|
+
|
|
385
|
+
const drawOrderMap = {
|
|
386
|
+
axes: drawAxesGrid,
|
|
387
|
+
series: drawSeries,
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const drawOrder = (opts.drawOrder || ["axes", "series"]).map(key => drawOrderMap[key]);
|
|
391
|
+
|
|
392
|
+
function initValToPct(sc) {
|
|
393
|
+
const getVal = (
|
|
394
|
+
sc.distr == 3 ? val => log10(val > 0 ? val : sc.clamp(self, val, sc.min, sc.max, sc.key)) :
|
|
395
|
+
sc.distr == 4 ? val => asinh(val, sc.asinh) :
|
|
396
|
+
sc.distr == 100 ? val => sc.fwd(val) :
|
|
397
|
+
val => val
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
return val => {
|
|
401
|
+
let _val = getVal(val);
|
|
402
|
+
let { _min, _max } = sc;
|
|
403
|
+
let delta = _max - _min;
|
|
404
|
+
return (_val - _min) / delta;
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function initScale(scaleKey) {
|
|
409
|
+
let sc = scales[scaleKey];
|
|
410
|
+
|
|
411
|
+
if (sc == null) {
|
|
412
|
+
let scaleOpts = (opts.scales || EMPTY_OBJ)[scaleKey] || EMPTY_OBJ;
|
|
413
|
+
|
|
414
|
+
if (scaleOpts.from != null) {
|
|
415
|
+
// ensure parent is initialized
|
|
416
|
+
initScale(scaleOpts.from);
|
|
417
|
+
// dependent scales inherit
|
|
418
|
+
let sc = assign({}, scales[scaleOpts.from], scaleOpts, {key: scaleKey});
|
|
419
|
+
sc.valToPct = initValToPct(sc);
|
|
420
|
+
scales[scaleKey] = sc;
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
sc = scales[scaleKey] = assign({}, (scaleKey == xScaleKey ? xScaleOpts : yScaleOpts), scaleOpts);
|
|
424
|
+
|
|
425
|
+
sc.key = scaleKey;
|
|
426
|
+
|
|
427
|
+
let isTime = FEAT_TIME && sc.time;
|
|
428
|
+
|
|
429
|
+
let rn = sc.range;
|
|
430
|
+
|
|
431
|
+
let rangeIsArr = isArr(rn);
|
|
432
|
+
|
|
433
|
+
if (scaleKey != xScaleKey || (mode == 2 && !isTime)) {
|
|
434
|
+
// if range array has null limits, it should be auto
|
|
435
|
+
if (rangeIsArr && (rn[0] == null || rn[1] == null)) {
|
|
436
|
+
rn = {
|
|
437
|
+
min: rn[0] == null ? autoRangePart : {
|
|
438
|
+
mode: 1,
|
|
439
|
+
hard: rn[0],
|
|
440
|
+
soft: rn[0],
|
|
441
|
+
},
|
|
442
|
+
max: rn[1] == null ? autoRangePart : {
|
|
443
|
+
mode: 1,
|
|
444
|
+
hard: rn[1],
|
|
445
|
+
soft: rn[1],
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
rangeIsArr = false;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!rangeIsArr && isObj(rn)) {
|
|
452
|
+
let cfg = rn;
|
|
453
|
+
// this is similar to snapNumY
|
|
454
|
+
rn = (self, dataMin, dataMax) => dataMin == null ? nullNullTuple : rangeNum(dataMin, dataMax, cfg);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
sc.range = fnOrSelf(rn || (isTime ? snapTimeX : scaleKey == xScaleKey ?
|
|
459
|
+
(sc.distr == 3 ? snapLogX : sc.distr == 4 ? snapAsinhX : snapNumX) :
|
|
460
|
+
(sc.distr == 3 ? snapLogY : sc.distr == 4 ? snapAsinhY : snapNumY)
|
|
461
|
+
));
|
|
462
|
+
|
|
463
|
+
sc.auto = fnOrSelf(rangeIsArr ? false : sc.auto);
|
|
464
|
+
|
|
465
|
+
sc.clamp = fnOrSelf(sc.clamp || clampScale);
|
|
466
|
+
|
|
467
|
+
// caches for expensive ops like asinh() & log()
|
|
468
|
+
sc._min = sc._max = null;
|
|
469
|
+
|
|
470
|
+
sc.valToPct = initValToPct(sc);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
initScale("x");
|
|
476
|
+
initScale("y");
|
|
477
|
+
|
|
478
|
+
// TODO: init scales from facets in mode: 2
|
|
479
|
+
if (mode == 1) {
|
|
480
|
+
series.forEach(s => {
|
|
481
|
+
initScale(s.scale);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
axes.forEach(a => {
|
|
486
|
+
initScale(a.scale);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
for (let k in opts.scales)
|
|
490
|
+
initScale(k);
|
|
491
|
+
|
|
492
|
+
const scaleX = scales[xScaleKey];
|
|
493
|
+
|
|
494
|
+
const xScaleDistr = scaleX.distr;
|
|
495
|
+
|
|
496
|
+
let valToPosX, valToPosY, moveTo, arc, xDimCan, xOffCan, yDimCan, yOffCan, xDimCss, xOffCss, yDimCss, yOffCss, updOriDims;
|
|
497
|
+
|
|
498
|
+
if (scaleX.ori == 0) {
|
|
499
|
+
addClass(root, ORI_HZ);
|
|
500
|
+
valToPosX = getHPos;
|
|
501
|
+
valToPosY = getVPos;
|
|
502
|
+
moveTo = moveToH;
|
|
503
|
+
arc = arcH;
|
|
504
|
+
/*
|
|
505
|
+
updOriDims = () => {
|
|
506
|
+
xDimCan = plotWid;
|
|
507
|
+
xOffCan = plotLft;
|
|
508
|
+
yDimCan = plotHgt;
|
|
509
|
+
yOffCan = plotTop;
|
|
510
|
+
|
|
511
|
+
xDimCss = plotWidCss;
|
|
512
|
+
xOffCss = plotLftCss;
|
|
513
|
+
yDimCss = plotHgtCss;
|
|
514
|
+
yOffCss = plotTopCss;
|
|
515
|
+
};
|
|
516
|
+
*/
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
addClass(root, ORI_VT);
|
|
520
|
+
valToPosX = getVPos;
|
|
521
|
+
valToPosY = getHPos;
|
|
522
|
+
moveTo = moveToV;
|
|
523
|
+
arc = arcV;
|
|
524
|
+
/*
|
|
525
|
+
updOriDims = () => {
|
|
526
|
+
xDimCan = plotHgt;
|
|
527
|
+
xOffCan = plotTop;
|
|
528
|
+
yDimCan = plotWid;
|
|
529
|
+
yOffCan = plotLft;
|
|
530
|
+
|
|
531
|
+
xDimCss = plotHgtCss;
|
|
532
|
+
xOffCss = plotTopCss;
|
|
533
|
+
yDimCss = plotWidCss;
|
|
534
|
+
yOffCss = plotLftCss;
|
|
535
|
+
};
|
|
536
|
+
*/
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const pendScales = {};
|
|
540
|
+
|
|
541
|
+
// explicitly-set initial scales
|
|
542
|
+
for (let k in scales) {
|
|
543
|
+
let sc = scales[k];
|
|
544
|
+
|
|
545
|
+
if (sc.min != null || sc.max != null) {
|
|
546
|
+
pendScales[k] = {min: sc.min, max: sc.max};
|
|
547
|
+
sc.min = sc.max = null;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// self.tz = opts.tz || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
552
|
+
const _tzDate = FEAT_TIME && (opts.tzDate || (ts => new Date(round(ts / ms))));
|
|
553
|
+
const _fmtDate = FEAT_TIME && (opts.fmtDate || fmtDate);
|
|
554
|
+
|
|
555
|
+
const _timeAxisSplits = FEAT_TIME && (ms == 1 ? timeAxisSplitsMs(_tzDate) : timeAxisSplitsS(_tzDate));
|
|
556
|
+
const _timeAxisVals = FEAT_TIME && timeAxisVals(_tzDate, timeAxisStamps((ms == 1 ? _timeAxisStampsMs : _timeAxisStampsS), _fmtDate));
|
|
557
|
+
const _timeSeriesVal = FEAT_TIME && timeSeriesVal(_tzDate, timeSeriesStamp(_timeSeriesStamp, _fmtDate));
|
|
558
|
+
|
|
559
|
+
const activeIdxs = [];
|
|
560
|
+
|
|
561
|
+
const legend = FEAT_LEGEND && (self.legend = assign({}, legendOpts, opts.legend));
|
|
562
|
+
const cursor = (self.cursor = assign({}, cursorOpts, {drag: {y: mode == 2}}, opts.cursor));
|
|
563
|
+
const showLegend = FEAT_LEGEND && legend.show;
|
|
564
|
+
const showCursor = cursor.show;
|
|
565
|
+
const markers = FEAT_LEGEND && legend.markers;
|
|
566
|
+
|
|
567
|
+
if (FEAT_LEGEND) {
|
|
568
|
+
legend.idxs = activeIdxs;
|
|
569
|
+
|
|
570
|
+
markers.width = fnOrSelf(markers.width);
|
|
571
|
+
markers.dash = fnOrSelf(markers.dash);
|
|
572
|
+
markers.stroke = fnOrSelf(markers.stroke);
|
|
573
|
+
markers.fill = fnOrSelf(markers.fill);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
let legendTable;
|
|
577
|
+
let legendHead;
|
|
578
|
+
let legendBody;
|
|
579
|
+
let legendRows = [];
|
|
580
|
+
let legendCells = [];
|
|
581
|
+
let legendCols;
|
|
582
|
+
let multiValLegend = false;
|
|
583
|
+
let NULL_LEGEND_VALUES = {};
|
|
584
|
+
|
|
585
|
+
if (FEAT_LEGEND && legend.live) {
|
|
586
|
+
const getMultiVals = series[1] ? series[1].values : null;
|
|
587
|
+
multiValLegend = getMultiVals != null;
|
|
588
|
+
legendCols = multiValLegend ? getMultiVals(self, 1, 0) : {_: 0};
|
|
589
|
+
|
|
590
|
+
for (let k in legendCols)
|
|
591
|
+
NULL_LEGEND_VALUES[k] = LEGEND_DISP;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (showLegend) {
|
|
595
|
+
legendTable = placeTag("table", LEGEND, root);
|
|
596
|
+
legendBody = placeTag("tbody", null, legendTable);
|
|
597
|
+
|
|
598
|
+
// allows legend to be moved out of root
|
|
599
|
+
legend.mount(self, legendTable);
|
|
600
|
+
|
|
601
|
+
if (multiValLegend) {
|
|
602
|
+
legendHead = placeTag("thead", null, legendTable, legendBody);
|
|
603
|
+
|
|
604
|
+
let head = placeTag("tr", null, legendHead);
|
|
605
|
+
placeTag("th", null, head);
|
|
606
|
+
|
|
607
|
+
for (var key in legendCols)
|
|
608
|
+
placeTag("th", LEGEND_LABEL, head).textContent = key;
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
addClass(legendTable, LEGEND_INLINE);
|
|
612
|
+
legend.live && addClass(legendTable, LEGEND_LIVE);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const son = {show: true};
|
|
617
|
+
const soff = {show: false};
|
|
618
|
+
|
|
619
|
+
function initLegendRow(s, i) {
|
|
620
|
+
if (i == 0 && (multiValLegend || !legend.live || mode == 2))
|
|
621
|
+
return nullNullTuple;
|
|
622
|
+
|
|
623
|
+
let cells = [];
|
|
624
|
+
|
|
625
|
+
let row = placeTag("tr", LEGEND_SERIES, legendBody, legendBody.childNodes[i]);
|
|
626
|
+
|
|
627
|
+
addClass(row, s.class);
|
|
628
|
+
|
|
629
|
+
if (!s.show)
|
|
630
|
+
addClass(row, OFF);
|
|
631
|
+
|
|
632
|
+
let label = placeTag("th", null, row);
|
|
633
|
+
|
|
634
|
+
if (markers.show) {
|
|
635
|
+
let indic = placeDiv(LEGEND_MARKER, label);
|
|
636
|
+
|
|
637
|
+
if (i > 0) {
|
|
638
|
+
let width = markers.width(self, i);
|
|
639
|
+
|
|
640
|
+
if (width)
|
|
641
|
+
indic.style.border = width + "px " + markers.dash(self, i) + " " + markers.stroke(self, i);
|
|
642
|
+
|
|
643
|
+
indic.style.background = markers.fill(self, i);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
let text = placeDiv(LEGEND_LABEL, label);
|
|
648
|
+
|
|
649
|
+
if (s.label instanceof HTMLElement)
|
|
650
|
+
text.appendChild(s.label);
|
|
651
|
+
else
|
|
652
|
+
text.textContent = s.label;
|
|
653
|
+
|
|
654
|
+
if (i > 0) {
|
|
655
|
+
if (!markers.show)
|
|
656
|
+
text.style.color = s.width > 0 ? markers.stroke(self, i) : markers.fill(self, i);
|
|
657
|
+
|
|
658
|
+
onMouse("click", label, e => {
|
|
659
|
+
if (cursor._lock)
|
|
660
|
+
return;
|
|
661
|
+
|
|
662
|
+
setCursorEvent(e);
|
|
663
|
+
|
|
664
|
+
let seriesIdx = series.indexOf(s);
|
|
665
|
+
|
|
666
|
+
if ((e.ctrlKey || e.metaKey) != legend.isolate) {
|
|
667
|
+
// if any other series is shown, isolate this one. else show all
|
|
668
|
+
let isolate = series.some((s, i) => i > 0 && i != seriesIdx && s.show);
|
|
669
|
+
|
|
670
|
+
series.forEach((s, i) => {
|
|
671
|
+
i > 0 && setSeries(i, isolate ? (i == seriesIdx ? son : soff) : son, true, syncOpts.setSeries);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
else
|
|
675
|
+
setSeries(seriesIdx, {show: !s.show}, true, syncOpts.setSeries);
|
|
676
|
+
}, false);
|
|
677
|
+
|
|
678
|
+
if (cursorFocus) {
|
|
679
|
+
onMouse(mouseenter, label, e => {
|
|
680
|
+
if (cursor._lock)
|
|
681
|
+
return;
|
|
682
|
+
|
|
683
|
+
setCursorEvent(e);
|
|
684
|
+
|
|
685
|
+
setSeries(series.indexOf(s), FOCUS_TRUE, true, syncOpts.setSeries);
|
|
686
|
+
}, false);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
for (var key in legendCols) {
|
|
691
|
+
let v = placeTag("td", LEGEND_VALUE, row);
|
|
692
|
+
v.textContent = "--";
|
|
693
|
+
cells.push(v);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return [row, cells];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const mouseListeners = new Map();
|
|
700
|
+
|
|
701
|
+
function onMouse(ev, targ, fn, onlyTarg = true) {
|
|
702
|
+
const targListeners = mouseListeners.get(targ) || {};
|
|
703
|
+
const listener = cursor.bind[ev](self, targ, fn, onlyTarg);
|
|
704
|
+
|
|
705
|
+
if (listener) {
|
|
706
|
+
on(ev, targ, targListeners[ev] = listener);
|
|
707
|
+
mouseListeners.set(targ, targListeners);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function offMouse(ev, targ, fn) {
|
|
712
|
+
const targListeners = mouseListeners.get(targ) || {};
|
|
713
|
+
|
|
714
|
+
for (let k in targListeners) {
|
|
715
|
+
if (ev == null || k == ev) {
|
|
716
|
+
off(k, targ, targListeners[k]);
|
|
717
|
+
delete targListeners[k];
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (ev == null)
|
|
722
|
+
mouseListeners.delete(targ);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
let fullWidCss = 0;
|
|
726
|
+
let fullHgtCss = 0;
|
|
727
|
+
|
|
728
|
+
let plotWidCss = 0;
|
|
729
|
+
let plotHgtCss = 0;
|
|
730
|
+
|
|
731
|
+
// plot margins to account for axes
|
|
732
|
+
let plotLftCss = 0;
|
|
733
|
+
let plotTopCss = 0;
|
|
734
|
+
|
|
735
|
+
// previous values for diffing
|
|
736
|
+
let _plotLftCss = plotLftCss;
|
|
737
|
+
let _plotTopCss = plotTopCss;
|
|
738
|
+
let _plotWidCss = plotWidCss;
|
|
739
|
+
let _plotHgtCss = plotHgtCss;
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
let plotLft = 0;
|
|
743
|
+
let plotTop = 0;
|
|
744
|
+
let plotWid = 0;
|
|
745
|
+
let plotHgt = 0;
|
|
746
|
+
|
|
747
|
+
self.bbox = {};
|
|
748
|
+
|
|
749
|
+
let shouldSetScales = false;
|
|
750
|
+
let shouldSetSize = false;
|
|
751
|
+
let shouldConvergeSize = false;
|
|
752
|
+
let shouldSetCursor = false;
|
|
753
|
+
let shouldSetSelect = false;
|
|
754
|
+
let shouldSetLegend = false;
|
|
755
|
+
|
|
756
|
+
function _setSize(width, height, force) {
|
|
757
|
+
if (force || (width != self.width || height != self.height))
|
|
758
|
+
calcSize(width, height);
|
|
759
|
+
|
|
760
|
+
resetYSeries(false);
|
|
761
|
+
|
|
762
|
+
shouldConvergeSize = true;
|
|
763
|
+
shouldSetSize = true;
|
|
764
|
+
|
|
765
|
+
commit();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function calcSize(width, height) {
|
|
769
|
+
// log("calcSize()", arguments);
|
|
770
|
+
|
|
771
|
+
self.width = fullWidCss = plotWidCss = width;
|
|
772
|
+
self.height = fullHgtCss = plotHgtCss = height;
|
|
773
|
+
plotLftCss = plotTopCss = 0;
|
|
774
|
+
|
|
775
|
+
calcPlotRect();
|
|
776
|
+
calcAxesRects();
|
|
777
|
+
|
|
778
|
+
let bb = self.bbox;
|
|
779
|
+
|
|
780
|
+
plotLft = bb.left = incrRound(plotLftCss * pxRatio, 0.5);
|
|
781
|
+
plotTop = bb.top = incrRound(plotTopCss * pxRatio, 0.5);
|
|
782
|
+
plotWid = bb.width = incrRound(plotWidCss * pxRatio, 0.5);
|
|
783
|
+
plotHgt = bb.height = incrRound(plotHgtCss * pxRatio, 0.5);
|
|
784
|
+
|
|
785
|
+
// updOriDims();
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// ensures size calc convergence
|
|
789
|
+
const CYCLE_LIMIT = 3;
|
|
790
|
+
|
|
791
|
+
function convergeSize() {
|
|
792
|
+
let converged = false;
|
|
793
|
+
|
|
794
|
+
let cycleNum = 0;
|
|
795
|
+
|
|
796
|
+
while (!converged) {
|
|
797
|
+
cycleNum++;
|
|
798
|
+
|
|
799
|
+
let axesConverged = axesCalc(cycleNum);
|
|
800
|
+
let paddingConverged = paddingCalc(cycleNum);
|
|
801
|
+
|
|
802
|
+
converged = cycleNum == CYCLE_LIMIT || (axesConverged && paddingConverged);
|
|
803
|
+
|
|
804
|
+
if (!converged) {
|
|
805
|
+
calcSize(self.width, self.height);
|
|
806
|
+
shouldSetSize = true;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function setSize({width, height}) {
|
|
812
|
+
_setSize(width, height);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
self.setSize = setSize;
|
|
816
|
+
|
|
817
|
+
// accumulate axis offsets, reduce canvas width
|
|
818
|
+
function calcPlotRect() {
|
|
819
|
+
// easements for edge labels
|
|
820
|
+
let hasTopAxis = false;
|
|
821
|
+
let hasBtmAxis = false;
|
|
822
|
+
let hasRgtAxis = false;
|
|
823
|
+
let hasLftAxis = false;
|
|
824
|
+
|
|
825
|
+
axes.forEach((axis, i) => {
|
|
826
|
+
if (axis.show && axis._show) {
|
|
827
|
+
let {side, _size} = axis;
|
|
828
|
+
let isVt = side % 2;
|
|
829
|
+
let labelSize = axis.label != null ? axis.labelSize : 0;
|
|
830
|
+
|
|
831
|
+
let fullSize = _size + labelSize;
|
|
832
|
+
|
|
833
|
+
if (fullSize > 0) {
|
|
834
|
+
if (isVt) {
|
|
835
|
+
plotWidCss -= fullSize;
|
|
836
|
+
|
|
837
|
+
if (side == 3) {
|
|
838
|
+
plotLftCss += fullSize;
|
|
839
|
+
hasLftAxis = true;
|
|
840
|
+
}
|
|
841
|
+
else
|
|
842
|
+
hasRgtAxis = true;
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
plotHgtCss -= fullSize;
|
|
846
|
+
|
|
847
|
+
if (side == 0) {
|
|
848
|
+
plotTopCss += fullSize;
|
|
849
|
+
hasTopAxis = true;
|
|
850
|
+
}
|
|
851
|
+
else
|
|
852
|
+
hasBtmAxis = true;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
sidesWithAxes[0] = hasTopAxis;
|
|
859
|
+
sidesWithAxes[1] = hasRgtAxis;
|
|
860
|
+
sidesWithAxes[2] = hasBtmAxis;
|
|
861
|
+
sidesWithAxes[3] = hasLftAxis;
|
|
862
|
+
|
|
863
|
+
// hz padding
|
|
864
|
+
plotWidCss -= _padding[1] + _padding[3];
|
|
865
|
+
plotLftCss += _padding[3];
|
|
866
|
+
|
|
867
|
+
// vt padding
|
|
868
|
+
plotHgtCss -= _padding[2] + _padding[0];
|
|
869
|
+
plotTopCss += _padding[0];
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function calcAxesRects() {
|
|
873
|
+
// will accum +
|
|
874
|
+
let off1 = plotLftCss + plotWidCss;
|
|
875
|
+
let off2 = plotTopCss + plotHgtCss;
|
|
876
|
+
// will accum -
|
|
877
|
+
let off3 = plotLftCss;
|
|
878
|
+
let off0 = plotTopCss;
|
|
879
|
+
|
|
880
|
+
function incrOffset(side, size) {
|
|
881
|
+
switch (side) {
|
|
882
|
+
case 1: off1 += size; return off1 - size;
|
|
883
|
+
case 2: off2 += size; return off2 - size;
|
|
884
|
+
case 3: off3 -= size; return off3 + size;
|
|
885
|
+
case 0: off0 -= size; return off0 + size;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
axes.forEach((axis, i) => {
|
|
890
|
+
if (axis.show && axis._show) {
|
|
891
|
+
let side = axis.side;
|
|
892
|
+
|
|
893
|
+
axis._pos = incrOffset(side, axis._size);
|
|
894
|
+
|
|
895
|
+
if (axis.label != null)
|
|
896
|
+
axis._lpos = incrOffset(side, axis.labelSize);
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (cursor.dataIdx == null) {
|
|
902
|
+
let hov = cursor.hover;
|
|
903
|
+
|
|
904
|
+
let skip = hov.skip = new Set(hov.skip ?? []);
|
|
905
|
+
skip.add(void 0); // alignment artifacts
|
|
906
|
+
let prox = hov.prox = fnOrSelf(hov.prox);
|
|
907
|
+
let bias = hov.bias ??= 0;
|
|
908
|
+
|
|
909
|
+
cursor.dataIdx = (self, seriesIdx, cursorIdx, valAtPosX) => {
|
|
910
|
+
if (seriesIdx == 0)
|
|
911
|
+
return cursorIdx;
|
|
912
|
+
|
|
913
|
+
let idx2 = cursorIdx;
|
|
914
|
+
|
|
915
|
+
let _prox = prox(self, seriesIdx, cursorIdx, valAtPosX) ?? inf;
|
|
916
|
+
let withProx = _prox >= 0 && _prox < inf;
|
|
917
|
+
let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss;
|
|
918
|
+
let cursorLft = cursor.left;
|
|
919
|
+
|
|
920
|
+
let xValues = data[0];
|
|
921
|
+
let yValues = data[seriesIdx];
|
|
922
|
+
|
|
923
|
+
if (skip.has(yValues[cursorIdx])) {
|
|
924
|
+
idx2 = null;
|
|
925
|
+
|
|
926
|
+
let nonNullLft = null,
|
|
927
|
+
nonNullRgt = null,
|
|
928
|
+
j;
|
|
929
|
+
|
|
930
|
+
if (bias == 0 || bias == -1) {
|
|
931
|
+
j = cursorIdx;
|
|
932
|
+
while (nonNullLft == null && --j >= i0) {
|
|
933
|
+
if (!skip.has(yValues[j]))
|
|
934
|
+
nonNullLft = j;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (bias == 0 || bias == 1) {
|
|
939
|
+
j = cursorIdx;
|
|
940
|
+
while (nonNullRgt == null && ++j <= i1) {
|
|
941
|
+
if (!skip.has(yValues[j]))
|
|
942
|
+
nonNullRgt = j;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (nonNullLft != null || nonNullRgt != null) {
|
|
947
|
+
if (withProx) {
|
|
948
|
+
let lftPos = nonNullLft == null ? -Infinity : valToPosX(xValues[nonNullLft], scaleX, xDim, 0);
|
|
949
|
+
let rgtPos = nonNullRgt == null ? Infinity : valToPosX(xValues[nonNullRgt], scaleX, xDim, 0);
|
|
950
|
+
|
|
951
|
+
let lftDelta = cursorLft - lftPos;
|
|
952
|
+
let rgtDelta = rgtPos - cursorLft;
|
|
953
|
+
|
|
954
|
+
if (lftDelta <= rgtDelta) {
|
|
955
|
+
if (lftDelta <= _prox)
|
|
956
|
+
idx2 = nonNullLft;
|
|
957
|
+
} else {
|
|
958
|
+
if (rgtDelta <= _prox)
|
|
959
|
+
idx2 = nonNullRgt;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
idx2 =
|
|
964
|
+
nonNullRgt == null ? nonNullLft :
|
|
965
|
+
nonNullLft == null ? nonNullRgt :
|
|
966
|
+
cursorIdx - nonNullLft <= nonNullRgt - cursorIdx ? nonNullLft : nonNullRgt;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else if (withProx) {
|
|
971
|
+
let dist = abs(cursorLft - valToPosX(xValues[cursorIdx], scaleX, xDim, 0));
|
|
972
|
+
|
|
973
|
+
if (dist > _prox)
|
|
974
|
+
idx2 = null;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
return idx2;
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const setCursorEvent = e => { cursor.event = e; };
|
|
982
|
+
|
|
983
|
+
cursor.idxs = activeIdxs;
|
|
984
|
+
|
|
985
|
+
cursor._lock = false;
|
|
986
|
+
|
|
987
|
+
let points = cursor.points;
|
|
988
|
+
|
|
989
|
+
points.show = fnOrSelf(points.show);
|
|
990
|
+
points.size = fnOrSelf(points.size);
|
|
991
|
+
points.stroke = fnOrSelf(points.stroke);
|
|
992
|
+
points.width = fnOrSelf(points.width);
|
|
993
|
+
points.fill = fnOrSelf(points.fill);
|
|
994
|
+
|
|
995
|
+
const focus = self.focus = assign({}, opts.focus || {alpha: 0.3}, cursor.focus);
|
|
996
|
+
|
|
997
|
+
const cursorFocus = focus.prox >= 0;
|
|
998
|
+
const cursorOnePt = cursorFocus && points.one;
|
|
999
|
+
|
|
1000
|
+
// series-intersection markers
|
|
1001
|
+
let cursorPts = [];
|
|
1002
|
+
// position caches in CSS pixels
|
|
1003
|
+
let cursorPtsLft = [];
|
|
1004
|
+
let cursorPtsTop = [];
|
|
1005
|
+
|
|
1006
|
+
function initCursorPt(s, si) {
|
|
1007
|
+
let pt = points.show(self, si);
|
|
1008
|
+
|
|
1009
|
+
if (pt instanceof HTMLElement) {
|
|
1010
|
+
addClass(pt, CURSOR_PT);
|
|
1011
|
+
addClass(pt, s.class);
|
|
1012
|
+
elTrans(pt, -10, -10, plotWidCss, plotHgtCss);
|
|
1013
|
+
over.insertBefore(pt, cursorPts[si]);
|
|
1014
|
+
|
|
1015
|
+
return pt;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function initSeries(s, i) {
|
|
1020
|
+
if (mode == 1 || i > 0) {
|
|
1021
|
+
let isTime = FEAT_TIME && mode == 1 && scales[s.scale].time;
|
|
1022
|
+
|
|
1023
|
+
let sv = s.value;
|
|
1024
|
+
s.value = isTime ? (isStr(sv) ? timeSeriesVal(_tzDate, timeSeriesStamp(sv, _fmtDate)) : sv || _timeSeriesVal) : sv || numSeriesVal;
|
|
1025
|
+
s.label = s.label || (isTime ? timeSeriesLabel : numSeriesLabel);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
if (cursorOnePt || i > 0) {
|
|
1029
|
+
s.width = s.width == null ? 1 : s.width;
|
|
1030
|
+
s.paths = s.paths || linearPath || retNull;
|
|
1031
|
+
s.fillTo = fnOrSelf(s.fillTo || seriesFillTo);
|
|
1032
|
+
s.pxAlign = +ifNull(s.pxAlign, pxAlign);
|
|
1033
|
+
s.pxRound = pxRoundGen(s.pxAlign);
|
|
1034
|
+
|
|
1035
|
+
s.stroke = fnOrSelf(s.stroke || null);
|
|
1036
|
+
s.fill = fnOrSelf(s.fill || null);
|
|
1037
|
+
s._stroke = s._fill = s._paths = s._focus = null;
|
|
1038
|
+
|
|
1039
|
+
let _ptDia = ptDia(max(1, s.width), 1);
|
|
1040
|
+
let points = s.points = assign({}, {
|
|
1041
|
+
size: _ptDia,
|
|
1042
|
+
width: max(1, _ptDia * .2),
|
|
1043
|
+
stroke: s.stroke,
|
|
1044
|
+
space: _ptDia * 2,
|
|
1045
|
+
paths: pointsPath,
|
|
1046
|
+
_stroke: null,
|
|
1047
|
+
_fill: null,
|
|
1048
|
+
}, s.points);
|
|
1049
|
+
points.show = fnOrSelf(points.show);
|
|
1050
|
+
points.filter = fnOrSelf(points.filter);
|
|
1051
|
+
points.fill = fnOrSelf(points.fill);
|
|
1052
|
+
points.stroke = fnOrSelf(points.stroke);
|
|
1053
|
+
points.paths = fnOrSelf(points.paths);
|
|
1054
|
+
points.pxAlign = s.pxAlign;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (showLegend) {
|
|
1058
|
+
let rowCells = initLegendRow(s, i);
|
|
1059
|
+
legendRows.splice(i, 0, rowCells[0]);
|
|
1060
|
+
legendCells.splice(i, 0, rowCells[1]);
|
|
1061
|
+
legend.values.push(null); // NULL_LEGEND_VALS not yet avil here :(
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if (showCursor) {
|
|
1065
|
+
activeIdxs.splice(i, 0, null);
|
|
1066
|
+
|
|
1067
|
+
let pt = null;
|
|
1068
|
+
|
|
1069
|
+
if (cursorOnePt) {
|
|
1070
|
+
if (i == 0)
|
|
1071
|
+
pt = initCursorPt(s, i);
|
|
1072
|
+
}
|
|
1073
|
+
else if (i > 0)
|
|
1074
|
+
pt = initCursorPt(s, i);
|
|
1075
|
+
|
|
1076
|
+
cursorPts.splice(i, 0, pt);
|
|
1077
|
+
cursorPtsLft.splice(i, 0, 0);
|
|
1078
|
+
cursorPtsTop.splice(i, 0, 0);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
fire("addSeries", i);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function addSeries(opts, si) {
|
|
1085
|
+
si = si == null ? series.length : si;
|
|
1086
|
+
|
|
1087
|
+
opts = mode == 1 ? setDefault(opts, si, xSeriesOpts, ySeriesOpts) : setDefault(opts, si, {}, xySeriesOpts);
|
|
1088
|
+
|
|
1089
|
+
series.splice(si, 0, opts);
|
|
1090
|
+
initSeries(series[si], si);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
self.addSeries = addSeries;
|
|
1094
|
+
|
|
1095
|
+
function delSeries(i) {
|
|
1096
|
+
series.splice(i, 1);
|
|
1097
|
+
|
|
1098
|
+
if (showLegend) {
|
|
1099
|
+
legend.values.splice(i, 1);
|
|
1100
|
+
|
|
1101
|
+
legendCells.splice(i, 1);
|
|
1102
|
+
let tr = legendRows.splice(i, 1)[0];
|
|
1103
|
+
offMouse(null, tr.firstChild);
|
|
1104
|
+
tr.remove();
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (showCursor) {
|
|
1108
|
+
activeIdxs.splice(i, 1);
|
|
1109
|
+
cursorPts.splice(i, 1)[0].remove();
|
|
1110
|
+
cursorPtsLft.splice(i, 1);
|
|
1111
|
+
cursorPtsTop.splice(i, 1);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// TODO: de-init no-longer-needed scales?
|
|
1115
|
+
|
|
1116
|
+
fire("delSeries", i);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
self.delSeries = delSeries;
|
|
1120
|
+
|
|
1121
|
+
const sidesWithAxes = [false, false, false, false];
|
|
1122
|
+
|
|
1123
|
+
function initAxis(axis, i) {
|
|
1124
|
+
axis._show = axis.show;
|
|
1125
|
+
|
|
1126
|
+
if (axis.show) {
|
|
1127
|
+
let isVt = axis.side % 2;
|
|
1128
|
+
|
|
1129
|
+
let sc = scales[axis.scale];
|
|
1130
|
+
|
|
1131
|
+
// this can occur if all series specify non-default scales
|
|
1132
|
+
if (sc == null) {
|
|
1133
|
+
axis.scale = isVt ? series[1].scale : xScaleKey;
|
|
1134
|
+
sc = scales[axis.scale];
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// also set defaults for incrs & values based on axis distr
|
|
1138
|
+
let isTime = FEAT_TIME && sc.time;
|
|
1139
|
+
|
|
1140
|
+
axis.size = fnOrSelf(axis.size);
|
|
1141
|
+
axis.space = fnOrSelf(axis.space);
|
|
1142
|
+
axis.rotate = fnOrSelf(axis.rotate);
|
|
1143
|
+
|
|
1144
|
+
if (isArr(axis.incrs)) {
|
|
1145
|
+
axis.incrs.forEach(incr => {
|
|
1146
|
+
!fixedDec.has(incr) && fixedDec.set(incr, guessDec(incr));
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
axis.incrs = fnOrSelf(axis.incrs || ( sc.distr == 2 ? wholeIncrs : (isTime ? (ms == 1 ? timeIncrsMs : timeIncrsS) : numIncrs)));
|
|
1151
|
+
axis.splits = fnOrSelf(axis.splits || (isTime && sc.distr == 1 ? _timeAxisSplits : sc.distr == 3 ? logAxisSplits : sc.distr == 4 ? asinhAxisSplits : numAxisSplits));
|
|
1152
|
+
|
|
1153
|
+
axis.stroke = fnOrSelf(axis.stroke);
|
|
1154
|
+
axis.grid.stroke = fnOrSelf(axis.grid.stroke);
|
|
1155
|
+
axis.ticks.stroke = fnOrSelf(axis.ticks.stroke);
|
|
1156
|
+
axis.border.stroke = fnOrSelf(axis.border.stroke);
|
|
1157
|
+
|
|
1158
|
+
let av = axis.values;
|
|
1159
|
+
|
|
1160
|
+
axis.values = (
|
|
1161
|
+
// static array of tick values
|
|
1162
|
+
isArr(av) && !isArr(av[0]) ? fnOrSelf(av) :
|
|
1163
|
+
// temporal
|
|
1164
|
+
isTime ? (
|
|
1165
|
+
// config array of fmtDate string tpls
|
|
1166
|
+
isArr(av) ?
|
|
1167
|
+
timeAxisVals(_tzDate, timeAxisStamps(av, _fmtDate)) :
|
|
1168
|
+
// fmtDate string tpl
|
|
1169
|
+
isStr(av) ?
|
|
1170
|
+
timeAxisVal(_tzDate, av) :
|
|
1171
|
+
av || _timeAxisVals
|
|
1172
|
+
) : av || numAxisVals
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
axis.filter = fnOrSelf(axis.filter || ( sc.distr >= 3 && sc.log == 10 ? log10AxisValsFilt : sc.distr == 3 && sc.log == 2 ? log2AxisValsFilt : retArg1));
|
|
1176
|
+
|
|
1177
|
+
axis.font = pxRatioFont(axis.font, pxRatio);
|
|
1178
|
+
axis.labelFont = pxRatioFont(axis.labelFont, pxRatio);
|
|
1179
|
+
|
|
1180
|
+
axis._size = axis.size(self, null, i, 0);
|
|
1181
|
+
|
|
1182
|
+
axis._space =
|
|
1183
|
+
axis._rotate =
|
|
1184
|
+
axis._incrs =
|
|
1185
|
+
axis._found = // foundIncrSpace
|
|
1186
|
+
axis._splits =
|
|
1187
|
+
axis._values = null;
|
|
1188
|
+
|
|
1189
|
+
if (axis._size > 0) {
|
|
1190
|
+
sidesWithAxes[i] = true;
|
|
1191
|
+
axis._el = placeDiv(AXIS, wrap);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// debug
|
|
1195
|
+
// axis._el.style.background = "#" + Math.floor(Math.random()*16777215).toString(16) + '80';
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function autoPadSide(self, side, sidesWithAxes, cycleNum) {
|
|
1200
|
+
let [hasTopAxis, hasRgtAxis, hasBtmAxis, hasLftAxis] = sidesWithAxes;
|
|
1201
|
+
|
|
1202
|
+
let ori = side % 2;
|
|
1203
|
+
let size = 0;
|
|
1204
|
+
|
|
1205
|
+
if (ori == 0 && (hasLftAxis || hasRgtAxis))
|
|
1206
|
+
size = (side == 0 && !hasTopAxis || side == 2 && !hasBtmAxis ? round(xAxisOpts.size / 3) : 0);
|
|
1207
|
+
if (ori == 1 && (hasTopAxis || hasBtmAxis))
|
|
1208
|
+
size = (side == 1 && !hasRgtAxis || side == 3 && !hasLftAxis ? round(yAxisOpts.size / 2) : 0);
|
|
1209
|
+
|
|
1210
|
+
return size;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const padding = self.padding = (opts.padding || [autoPadSide,autoPadSide,autoPadSide,autoPadSide]).map(p => fnOrSelf(ifNull(p, autoPadSide)));
|
|
1214
|
+
const _padding = self._padding = padding.map((p, i) => p(self, i, sidesWithAxes, 0));
|
|
1215
|
+
|
|
1216
|
+
let dataLen;
|
|
1217
|
+
|
|
1218
|
+
// rendered data window
|
|
1219
|
+
let i0 = null;
|
|
1220
|
+
let i1 = null;
|
|
1221
|
+
const idxs = mode == 1 ? series[0].idxs : null;
|
|
1222
|
+
|
|
1223
|
+
let data0 = null;
|
|
1224
|
+
|
|
1225
|
+
let viaAutoScaleX = false;
|
|
1226
|
+
|
|
1227
|
+
function setData(_data, _resetScales) {
|
|
1228
|
+
data = _data == null ? [] : _data;
|
|
1229
|
+
|
|
1230
|
+
self.data = self._data = data;
|
|
1231
|
+
|
|
1232
|
+
if (mode == 2) {
|
|
1233
|
+
dataLen = 0;
|
|
1234
|
+
for (let i = 1; i < series.length; i++)
|
|
1235
|
+
dataLen += data[i][0].length;
|
|
1236
|
+
}
|
|
1237
|
+
else {
|
|
1238
|
+
if (data.length == 0)
|
|
1239
|
+
self.data = self._data = data = [[]];
|
|
1240
|
+
|
|
1241
|
+
data0 = data[0];
|
|
1242
|
+
dataLen = data0.length;
|
|
1243
|
+
|
|
1244
|
+
let scaleData = data;
|
|
1245
|
+
|
|
1246
|
+
if (xScaleDistr == 2) {
|
|
1247
|
+
scaleData = data.slice();
|
|
1248
|
+
|
|
1249
|
+
let _data0 = scaleData[0] = Array(dataLen);
|
|
1250
|
+
for (let i = 0; i < dataLen; i++)
|
|
1251
|
+
_data0[i] = i;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
self._data = data = scaleData;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
resetYSeries(true);
|
|
1258
|
+
|
|
1259
|
+
fire("setData");
|
|
1260
|
+
|
|
1261
|
+
// forces x axis tick values to re-generate when neither x scale nor y scale changes
|
|
1262
|
+
// in ordinal mode, scale range is by index, so will not change if new data has same length, but tick values are from data
|
|
1263
|
+
if (xScaleDistr == 2) {
|
|
1264
|
+
shouldConvergeSize = true;
|
|
1265
|
+
|
|
1266
|
+
/* or somewhat cheaper, and uglier:
|
|
1267
|
+
if (ready) {
|
|
1268
|
+
// logic extracted from axesCalc()
|
|
1269
|
+
let i = 0;
|
|
1270
|
+
let axis = axes[i];
|
|
1271
|
+
let _splits = axis._splits.map(i => data0[i]);
|
|
1272
|
+
let [_incr, _space] = axis._found;
|
|
1273
|
+
let incr = data0[_splits[1]] - data0[_splits[0]];
|
|
1274
|
+
axis._values = axis.values(self, axis.filter(self, _splits, i, _space, incr), i, _space, incr);
|
|
1275
|
+
}
|
|
1276
|
+
*/
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (_resetScales !== false) {
|
|
1280
|
+
let xsc = scaleX;
|
|
1281
|
+
|
|
1282
|
+
if (xsc.auto(self, viaAutoScaleX))
|
|
1283
|
+
autoScaleX();
|
|
1284
|
+
else
|
|
1285
|
+
_setScale(xScaleKey, xsc.min, xsc.max);
|
|
1286
|
+
|
|
1287
|
+
shouldSetCursor = shouldSetCursor || cursor.left >= 0;
|
|
1288
|
+
shouldSetLegend = true;
|
|
1289
|
+
commit();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
self.setData = setData;
|
|
1294
|
+
|
|
1295
|
+
function autoScaleX() {
|
|
1296
|
+
viaAutoScaleX = true;
|
|
1297
|
+
|
|
1298
|
+
let _min, _max;
|
|
1299
|
+
|
|
1300
|
+
if (mode == 1) {
|
|
1301
|
+
if (dataLen > 0) {
|
|
1302
|
+
i0 = idxs[0] = 0;
|
|
1303
|
+
i1 = idxs[1] = dataLen - 1;
|
|
1304
|
+
|
|
1305
|
+
_min = data[0][i0];
|
|
1306
|
+
_max = data[0][i1];
|
|
1307
|
+
|
|
1308
|
+
if (xScaleDistr == 2) {
|
|
1309
|
+
_min = i0;
|
|
1310
|
+
_max = i1;
|
|
1311
|
+
}
|
|
1312
|
+
else if (_min == _max) {
|
|
1313
|
+
if (xScaleDistr == 3)
|
|
1314
|
+
[_min, _max] = rangeLog(_min, _min, scaleX.log, false);
|
|
1315
|
+
else if (xScaleDistr == 4)
|
|
1316
|
+
[_min, _max] = rangeAsinh(_min, _min, scaleX.log, false);
|
|
1317
|
+
else if (scaleX.time)
|
|
1318
|
+
_max = _min + round(86400 / ms);
|
|
1319
|
+
else
|
|
1320
|
+
[_min, _max] = rangeNum(_min, _max, rangePad, true);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
i0 = idxs[0] = _min = null;
|
|
1325
|
+
i1 = idxs[1] = _max = null;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
_setScale(xScaleKey, _min, _max);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
let ctxStroke, ctxFill, ctxWidth, ctxDash, ctxJoin, ctxCap, ctxFont, ctxAlign, ctxBaseline;
|
|
1333
|
+
let ctxAlpha;
|
|
1334
|
+
|
|
1335
|
+
function setCtxStyle(stroke, width, dash, cap, fill, join) {
|
|
1336
|
+
stroke ??= transparent;
|
|
1337
|
+
dash ??= EMPTY_ARR;
|
|
1338
|
+
cap ??= "butt"; // (‿|‿)
|
|
1339
|
+
fill ??= transparent;
|
|
1340
|
+
join ??= "round";
|
|
1341
|
+
|
|
1342
|
+
if (stroke != ctxStroke)
|
|
1343
|
+
ctx.strokeStyle = ctxStroke = stroke;
|
|
1344
|
+
if (fill != ctxFill)
|
|
1345
|
+
ctx.fillStyle = ctxFill = fill;
|
|
1346
|
+
if (width != ctxWidth)
|
|
1347
|
+
ctx.lineWidth = ctxWidth = width;
|
|
1348
|
+
if (join != ctxJoin)
|
|
1349
|
+
ctx.lineJoin = ctxJoin = join;
|
|
1350
|
+
if (cap != ctxCap)
|
|
1351
|
+
ctx.lineCap = ctxCap = cap;
|
|
1352
|
+
if (dash != ctxDash)
|
|
1353
|
+
ctx.setLineDash(ctxDash = dash);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function setFontStyle(font, fill, align, baseline) {
|
|
1357
|
+
if (fill != ctxFill)
|
|
1358
|
+
ctx.fillStyle = ctxFill = fill;
|
|
1359
|
+
if (font != ctxFont)
|
|
1360
|
+
ctx.font = ctxFont = font;
|
|
1361
|
+
if (align != ctxAlign)
|
|
1362
|
+
ctx.textAlign = ctxAlign = align;
|
|
1363
|
+
if (baseline != ctxBaseline)
|
|
1364
|
+
ctx.textBaseline = ctxBaseline = baseline;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
function accScale(wsc, psc, facet, data, sorted = 0) {
|
|
1368
|
+
if (data.length > 0 && wsc.auto(self, viaAutoScaleX) && (psc == null || psc.min == null)) {
|
|
1369
|
+
let _i0 = ifNull(i0, 0);
|
|
1370
|
+
let _i1 = ifNull(i1, data.length - 1);
|
|
1371
|
+
|
|
1372
|
+
// only run getMinMax() for invalidated series data, else reuse
|
|
1373
|
+
let minMax = facet.min == null ? getMinMax(data, _i0, _i1, sorted, wsc.distr == 3) : [facet.min, facet.max];
|
|
1374
|
+
|
|
1375
|
+
// initial min/max
|
|
1376
|
+
wsc.min = min(wsc.min, facet.min = minMax[0]);
|
|
1377
|
+
wsc.max = max(wsc.max, facet.max = minMax[1]);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const AUTOSCALE = {min: null, max: null};
|
|
1382
|
+
|
|
1383
|
+
function setScales() {
|
|
1384
|
+
// log("setScales()", arguments);
|
|
1385
|
+
|
|
1386
|
+
// implicitly add auto scales, and unranged scales
|
|
1387
|
+
for (let k in scales) {
|
|
1388
|
+
let sc = scales[k];
|
|
1389
|
+
|
|
1390
|
+
if (pendScales[k] == null &&
|
|
1391
|
+
(
|
|
1392
|
+
// scales that have never been set (on init)
|
|
1393
|
+
sc.min == null ||
|
|
1394
|
+
// or auto scales when the x scale was explicitly set
|
|
1395
|
+
pendScales[xScaleKey] != null && sc.auto(self, viaAutoScaleX)
|
|
1396
|
+
)
|
|
1397
|
+
) {
|
|
1398
|
+
pendScales[k] = AUTOSCALE;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// implicitly add dependent scales
|
|
1403
|
+
for (let k in scales) {
|
|
1404
|
+
let sc = scales[k];
|
|
1405
|
+
|
|
1406
|
+
if (pendScales[k] == null && sc.from != null && pendScales[sc.from] != null)
|
|
1407
|
+
pendScales[k] = AUTOSCALE;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// explicitly setting the x-scale invalidates everything (acts as redraw)
|
|
1411
|
+
if (pendScales[xScaleKey] != null)
|
|
1412
|
+
resetYSeries(true); // TODO: only reset series on auto scales?
|
|
1413
|
+
|
|
1414
|
+
let wipScales = {};
|
|
1415
|
+
|
|
1416
|
+
for (let k in pendScales) {
|
|
1417
|
+
let psc = pendScales[k];
|
|
1418
|
+
|
|
1419
|
+
if (psc != null) {
|
|
1420
|
+
let wsc = wipScales[k] = copy(scales[k], fastIsObj);
|
|
1421
|
+
|
|
1422
|
+
if (psc.min != null)
|
|
1423
|
+
assign(wsc, psc);
|
|
1424
|
+
else if (k != xScaleKey || mode == 2) {
|
|
1425
|
+
if (dataLen == 0 && wsc.from == null) {
|
|
1426
|
+
let minMax = wsc.range(self, null, null, k);
|
|
1427
|
+
wsc.min = minMax[0];
|
|
1428
|
+
wsc.max = minMax[1];
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
wsc.min = inf;
|
|
1432
|
+
wsc.max = -inf;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (dataLen > 0) {
|
|
1439
|
+
// pre-range y-scales from y series' data values
|
|
1440
|
+
series.forEach((s, i) => {
|
|
1441
|
+
if (mode == 1) {
|
|
1442
|
+
let k = s.scale;
|
|
1443
|
+
let psc = pendScales[k];
|
|
1444
|
+
|
|
1445
|
+
if (psc == null)
|
|
1446
|
+
return;
|
|
1447
|
+
|
|
1448
|
+
let wsc = wipScales[k];
|
|
1449
|
+
|
|
1450
|
+
if (i == 0) {
|
|
1451
|
+
let minMax = wsc.range(self, wsc.min, wsc.max, k);
|
|
1452
|
+
|
|
1453
|
+
wsc.min = minMax[0];
|
|
1454
|
+
wsc.max = minMax[1];
|
|
1455
|
+
|
|
1456
|
+
i0 = closestIdx(wsc.min, data[0]);
|
|
1457
|
+
i1 = closestIdx(wsc.max, data[0]);
|
|
1458
|
+
|
|
1459
|
+
// don't try to contract same or adjacent idxs
|
|
1460
|
+
if (i1 - i0 > 1) {
|
|
1461
|
+
// closest indices can be outside of view
|
|
1462
|
+
if (data[0][i0] < wsc.min)
|
|
1463
|
+
i0++;
|
|
1464
|
+
if (data[0][i1] > wsc.max)
|
|
1465
|
+
i1--;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
s.min = data0[i0];
|
|
1469
|
+
s.max = data0[i1];
|
|
1470
|
+
}
|
|
1471
|
+
else if (s.show && s.auto)
|
|
1472
|
+
accScale(wsc, psc, s, data[i], s.sorted);
|
|
1473
|
+
|
|
1474
|
+
s.idxs[0] = i0;
|
|
1475
|
+
s.idxs[1] = i1;
|
|
1476
|
+
}
|
|
1477
|
+
else {
|
|
1478
|
+
if (i > 0) {
|
|
1479
|
+
if (s.show && s.auto) {
|
|
1480
|
+
// TODO: only handles, assumes and requires facets[0] / 'x' scale, and facets[1] / 'y' scale
|
|
1481
|
+
let [ xFacet, yFacet ] = s.facets;
|
|
1482
|
+
let xScaleKey = xFacet.scale;
|
|
1483
|
+
let yScaleKey = yFacet.scale;
|
|
1484
|
+
let [ xData, yData ] = data[i];
|
|
1485
|
+
|
|
1486
|
+
let wscx = wipScales[xScaleKey];
|
|
1487
|
+
let wscy = wipScales[yScaleKey];
|
|
1488
|
+
|
|
1489
|
+
// null can happen when only x is zoomed, but y has static range and doesnt get auto-added to pending
|
|
1490
|
+
wscx != null && accScale(wscx, pendScales[xScaleKey], xFacet, xData, xFacet.sorted);
|
|
1491
|
+
wscy != null && accScale(wscy, pendScales[yScaleKey], yFacet, yData, yFacet.sorted);
|
|
1492
|
+
|
|
1493
|
+
// temp
|
|
1494
|
+
s.min = yFacet.min;
|
|
1495
|
+
s.max = yFacet.max;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
// range independent scales
|
|
1502
|
+
for (let k in wipScales) {
|
|
1503
|
+
let wsc = wipScales[k];
|
|
1504
|
+
let psc = pendScales[k];
|
|
1505
|
+
|
|
1506
|
+
if (wsc.from == null && (psc == null || psc.min == null)) {
|
|
1507
|
+
let minMax = wsc.range(
|
|
1508
|
+
self,
|
|
1509
|
+
wsc.min == inf ? null : wsc.min,
|
|
1510
|
+
wsc.max == -inf ? null : wsc.max,
|
|
1511
|
+
k
|
|
1512
|
+
);
|
|
1513
|
+
wsc.min = minMax[0];
|
|
1514
|
+
wsc.max = minMax[1];
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// range dependent scales
|
|
1520
|
+
for (let k in wipScales) {
|
|
1521
|
+
let wsc = wipScales[k];
|
|
1522
|
+
|
|
1523
|
+
if (wsc.from != null) {
|
|
1524
|
+
let base = wipScales[wsc.from];
|
|
1525
|
+
|
|
1526
|
+
if (base.min == null)
|
|
1527
|
+
wsc.min = wsc.max = null;
|
|
1528
|
+
else {
|
|
1529
|
+
let minMax = wsc.range(self, base.min, base.max, k);
|
|
1530
|
+
wsc.min = minMax[0];
|
|
1531
|
+
wsc.max = minMax[1];
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
let changed = {};
|
|
1537
|
+
let anyChanged = false;
|
|
1538
|
+
|
|
1539
|
+
for (let k in wipScales) {
|
|
1540
|
+
let wsc = wipScales[k];
|
|
1541
|
+
let sc = scales[k];
|
|
1542
|
+
|
|
1543
|
+
if (sc.min != wsc.min || sc.max != wsc.max) {
|
|
1544
|
+
sc.min = wsc.min;
|
|
1545
|
+
sc.max = wsc.max;
|
|
1546
|
+
|
|
1547
|
+
let distr = sc.distr;
|
|
1548
|
+
|
|
1549
|
+
sc._min = distr == 3 ? log10(sc.min) : distr == 4 ? asinh(sc.min, sc.asinh) : distr == 100 ? sc.fwd(sc.min) : sc.min;
|
|
1550
|
+
sc._max = distr == 3 ? log10(sc.max) : distr == 4 ? asinh(sc.max, sc.asinh) : distr == 100 ? sc.fwd(sc.max) : sc.max;
|
|
1551
|
+
|
|
1552
|
+
changed[k] = anyChanged = true;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
if (anyChanged) {
|
|
1557
|
+
// invalidate paths of all series on changed scales
|
|
1558
|
+
series.forEach((s, i) => {
|
|
1559
|
+
if (mode == 2) {
|
|
1560
|
+
if (i > 0 && changed.y)
|
|
1561
|
+
s._paths = null;
|
|
1562
|
+
}
|
|
1563
|
+
else {
|
|
1564
|
+
if (changed[s.scale])
|
|
1565
|
+
s._paths = null;
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
for (let k in changed) {
|
|
1570
|
+
shouldConvergeSize = true;
|
|
1571
|
+
fire("setScale", k);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if (showCursor && cursor.left >= 0)
|
|
1575
|
+
shouldSetCursor = shouldSetLegend = true;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
for (let k in pendScales)
|
|
1579
|
+
pendScales[k] = null;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// grabs the nearest indices with y data outside of x-scale limits
|
|
1583
|
+
function getOuterIdxs(ydata) {
|
|
1584
|
+
let _i0 = clamp(i0 - 1, 0, dataLen - 1);
|
|
1585
|
+
let _i1 = clamp(i1 + 1, 0, dataLen - 1);
|
|
1586
|
+
|
|
1587
|
+
while (ydata[_i0] == null && _i0 > 0)
|
|
1588
|
+
_i0--;
|
|
1589
|
+
|
|
1590
|
+
while (ydata[_i1] == null && _i1 < dataLen - 1)
|
|
1591
|
+
_i1++;
|
|
1592
|
+
|
|
1593
|
+
return [_i0, _i1];
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function drawSeries() {
|
|
1597
|
+
if (dataLen > 0) {
|
|
1598
|
+
let shouldAlpha = series.some(s => s._focus) && ctxAlpha != focus.alpha;
|
|
1599
|
+
|
|
1600
|
+
if (shouldAlpha)
|
|
1601
|
+
ctx.globalAlpha = ctxAlpha = focus.alpha;
|
|
1602
|
+
|
|
1603
|
+
series.forEach((s, i) => {
|
|
1604
|
+
if (i > 0 && s.show) {
|
|
1605
|
+
FEAT_PATHS && cacheStrokeFill(i, false);
|
|
1606
|
+
FEAT_POINTS && cacheStrokeFill(i, true);
|
|
1607
|
+
|
|
1608
|
+
if (s._paths == null) {
|
|
1609
|
+
let _ctxAlpha = ctxAlpha;
|
|
1610
|
+
|
|
1611
|
+
if (ctxAlpha != s.alpha)
|
|
1612
|
+
ctx.globalAlpha = ctxAlpha = s.alpha;
|
|
1613
|
+
|
|
1614
|
+
let _idxs = mode == 2 ? [0, data[i][0].length - 1] : getOuterIdxs(data[i]);
|
|
1615
|
+
s._paths = s.paths(self, i, _idxs[0], _idxs[1]);
|
|
1616
|
+
|
|
1617
|
+
if (ctxAlpha != _ctxAlpha)
|
|
1618
|
+
ctx.globalAlpha = ctxAlpha = _ctxAlpha;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1623
|
+
series.forEach((s, i) => {
|
|
1624
|
+
if (i > 0 && s.show) {
|
|
1625
|
+
let _ctxAlpha = ctxAlpha;
|
|
1626
|
+
|
|
1627
|
+
if (ctxAlpha != s.alpha)
|
|
1628
|
+
ctx.globalAlpha = ctxAlpha = s.alpha;
|
|
1629
|
+
|
|
1630
|
+
FEAT_PATHS && s._paths != null && drawPath(i, false);
|
|
1631
|
+
|
|
1632
|
+
if (FEAT_POINTS) {
|
|
1633
|
+
let _gaps = s._paths != null ? s._paths.gaps : null;
|
|
1634
|
+
|
|
1635
|
+
let show = s.points.show(self, i, i0, i1, _gaps);
|
|
1636
|
+
let idxs = s.points.filter(self, i, show, _gaps);
|
|
1637
|
+
|
|
1638
|
+
if (show || idxs) {
|
|
1639
|
+
s.points._paths = s.points.paths(self, i, i0, i1, idxs);
|
|
1640
|
+
drawPath(i, true);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (ctxAlpha != _ctxAlpha)
|
|
1645
|
+
ctx.globalAlpha = ctxAlpha = _ctxAlpha;
|
|
1646
|
+
|
|
1647
|
+
fire("drawSeries", i);
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
if (shouldAlpha)
|
|
1652
|
+
ctx.globalAlpha = ctxAlpha = 1;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
function cacheStrokeFill(si, _points) {
|
|
1657
|
+
let s = _points ? series[si].points : series[si];
|
|
1658
|
+
|
|
1659
|
+
s._stroke = s.stroke(self, si);
|
|
1660
|
+
s._fill = s.fill(self, si);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function drawPath(si, _points) {
|
|
1664
|
+
let s = _points ? series[si].points : series[si];
|
|
1665
|
+
|
|
1666
|
+
let {
|
|
1667
|
+
stroke,
|
|
1668
|
+
fill,
|
|
1669
|
+
clip: gapsClip,
|
|
1670
|
+
flags,
|
|
1671
|
+
|
|
1672
|
+
_stroke: strokeStyle = s._stroke,
|
|
1673
|
+
_fill: fillStyle = s._fill,
|
|
1674
|
+
_width: width = s.width,
|
|
1675
|
+
} = s._paths;
|
|
1676
|
+
|
|
1677
|
+
width = roundDec(width * pxRatio, 3);
|
|
1678
|
+
|
|
1679
|
+
let boundsClip = null;
|
|
1680
|
+
let offset = (width % 2) / 2;
|
|
1681
|
+
|
|
1682
|
+
if (_points && fillStyle == null)
|
|
1683
|
+
fillStyle = width > 0 ? "#fff" : strokeStyle;
|
|
1684
|
+
|
|
1685
|
+
let _pxAlign = s.pxAlign == 1 && offset > 0;
|
|
1686
|
+
|
|
1687
|
+
_pxAlign && offset != 0 && ctx.translate(offset, offset);
|
|
1688
|
+
|
|
1689
|
+
if (!_points) {
|
|
1690
|
+
let lft = plotLft - width / 2,
|
|
1691
|
+
top = plotTop - width / 2,
|
|
1692
|
+
wid = plotWid + width,
|
|
1693
|
+
hgt = plotHgt + width;
|
|
1694
|
+
|
|
1695
|
+
boundsClip = new Path2D();
|
|
1696
|
+
boundsClip.rect(lft, top, wid, hgt);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// the points pathbuilder's gapsClip is its boundsClip, since points dont need gaps clipping, and bounds depend on point size
|
|
1700
|
+
if (_points)
|
|
1701
|
+
strokeFill(strokeStyle, width, s.dash, s.cap, fillStyle, stroke, fill, flags, gapsClip);
|
|
1702
|
+
else
|
|
1703
|
+
fillStroke(si, strokeStyle, width, s.dash, s.cap, fillStyle, stroke, fill, flags, boundsClip, gapsClip);
|
|
1704
|
+
|
|
1705
|
+
_pxAlign && offset != 0 && ctx.translate(-offset, -offset);
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
function fillStroke(si, strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip) {
|
|
1709
|
+
let didStrokeFill = false;
|
|
1710
|
+
|
|
1711
|
+
// for all bands where this series is the top edge, create upwards clips using the bottom edges
|
|
1712
|
+
// and apply clips + fill with band fill or dfltFill
|
|
1713
|
+
flags != 0 && bands.forEach((b, bi) => {
|
|
1714
|
+
// isUpperEdge?
|
|
1715
|
+
if (b.series[0] == si) {
|
|
1716
|
+
let lowerEdge = series[b.series[1]];
|
|
1717
|
+
let lowerData = data[b.series[1]];
|
|
1718
|
+
|
|
1719
|
+
let bandClip = (lowerEdge._paths || EMPTY_OBJ).band;
|
|
1720
|
+
|
|
1721
|
+
if (isArr(bandClip))
|
|
1722
|
+
bandClip = b.dir == 1 ? bandClip[0] : bandClip[1];
|
|
1723
|
+
|
|
1724
|
+
let gapsClip2;
|
|
1725
|
+
|
|
1726
|
+
let _fillStyle = null;
|
|
1727
|
+
|
|
1728
|
+
// hasLowerEdge?
|
|
1729
|
+
if (lowerEdge.show && bandClip && hasData(lowerData, i0, i1)) {
|
|
1730
|
+
_fillStyle = b.fill(self, bi) || fillStyle;
|
|
1731
|
+
gapsClip2 = lowerEdge._paths.clip;
|
|
1732
|
+
}
|
|
1733
|
+
else
|
|
1734
|
+
bandClip = null;
|
|
1735
|
+
|
|
1736
|
+
strokeFill(strokeStyle, lineWidth, lineDash, lineCap, _fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip, gapsClip2, bandClip);
|
|
1737
|
+
|
|
1738
|
+
didStrokeFill = true;
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
if (!didStrokeFill)
|
|
1743
|
+
strokeFill(strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
const CLIP_FILL_STROKE = BAND_CLIP_FILL | BAND_CLIP_STROKE;
|
|
1747
|
+
|
|
1748
|
+
function strokeFill(strokeStyle, lineWidth, lineDash, lineCap, fillStyle, strokePath, fillPath, flags, boundsClip, gapsClip, gapsClip2, bandClip) {
|
|
1749
|
+
setCtxStyle(strokeStyle, lineWidth, lineDash, lineCap, fillStyle);
|
|
1750
|
+
|
|
1751
|
+
if (boundsClip || gapsClip || bandClip) {
|
|
1752
|
+
ctx.save();
|
|
1753
|
+
boundsClip && ctx.clip(boundsClip);
|
|
1754
|
+
gapsClip && ctx.clip(gapsClip);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
if (bandClip) {
|
|
1758
|
+
if ((flags & CLIP_FILL_STROKE) == CLIP_FILL_STROKE) {
|
|
1759
|
+
ctx.clip(bandClip);
|
|
1760
|
+
gapsClip2 && ctx.clip(gapsClip2);
|
|
1761
|
+
doFill(fillStyle, fillPath);
|
|
1762
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
1763
|
+
}
|
|
1764
|
+
else if (flags & BAND_CLIP_STROKE) {
|
|
1765
|
+
doFill(fillStyle, fillPath);
|
|
1766
|
+
ctx.clip(bandClip);
|
|
1767
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
1768
|
+
}
|
|
1769
|
+
else if (flags & BAND_CLIP_FILL) {
|
|
1770
|
+
ctx.save();
|
|
1771
|
+
ctx.clip(bandClip);
|
|
1772
|
+
gapsClip2 && ctx.clip(gapsClip2);
|
|
1773
|
+
doFill(fillStyle, fillPath);
|
|
1774
|
+
ctx.restore();
|
|
1775
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
else {
|
|
1779
|
+
doFill(fillStyle, fillPath);
|
|
1780
|
+
doStroke(strokeStyle, strokePath, lineWidth);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
if (boundsClip || gapsClip || bandClip)
|
|
1784
|
+
ctx.restore();
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
function doStroke(strokeStyle, strokePath, lineWidth) {
|
|
1788
|
+
if (lineWidth > 0) {
|
|
1789
|
+
if (strokePath instanceof Map) {
|
|
1790
|
+
strokePath.forEach((strokePath, strokeStyle) => {
|
|
1791
|
+
ctx.strokeStyle = ctxStroke = strokeStyle;
|
|
1792
|
+
ctx.stroke(strokePath);
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
else
|
|
1796
|
+
strokePath != null && strokeStyle && ctx.stroke(strokePath);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function doFill(fillStyle, fillPath) {
|
|
1801
|
+
if (fillPath instanceof Map) {
|
|
1802
|
+
fillPath.forEach((fillPath, fillStyle) => {
|
|
1803
|
+
ctx.fillStyle = ctxFill = fillStyle;
|
|
1804
|
+
ctx.fill(fillPath);
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
else
|
|
1808
|
+
fillPath != null && fillStyle && ctx.fill(fillPath);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function getIncrSpace(axisIdx, min, max, fullDim) {
|
|
1812
|
+
let axis = axes[axisIdx];
|
|
1813
|
+
|
|
1814
|
+
let incrSpace;
|
|
1815
|
+
|
|
1816
|
+
if (fullDim <= 0)
|
|
1817
|
+
incrSpace = [0, 0];
|
|
1818
|
+
else {
|
|
1819
|
+
let minSpace = axis._space = axis.space(self, axisIdx, min, max, fullDim);
|
|
1820
|
+
let incrs = axis._incrs = axis.incrs(self, axisIdx, min, max, fullDim, minSpace);
|
|
1821
|
+
incrSpace = findIncr(min, max, incrs, fullDim, minSpace);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
return (axis._found = incrSpace);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
function drawOrthoLines(offs, filts, ori, side, pos0, len, width, stroke, dash, cap) {
|
|
1828
|
+
let offset = (width % 2) / 2;
|
|
1829
|
+
|
|
1830
|
+
pxAlign == 1 && offset != 0 && ctx.translate(offset, offset);
|
|
1831
|
+
|
|
1832
|
+
setCtxStyle(stroke, width, dash, cap, stroke);
|
|
1833
|
+
|
|
1834
|
+
ctx.beginPath();
|
|
1835
|
+
|
|
1836
|
+
let x0, y0, x1, y1, pos1 = pos0 + (side == 0 || side == 3 ? -len : len);
|
|
1837
|
+
|
|
1838
|
+
if (ori == 0) {
|
|
1839
|
+
y0 = pos0;
|
|
1840
|
+
y1 = pos1;
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
x0 = pos0;
|
|
1844
|
+
x1 = pos1;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
for (let i = 0; i < offs.length; i++) {
|
|
1848
|
+
if (filts[i] != null) {
|
|
1849
|
+
if (ori == 0)
|
|
1850
|
+
x0 = x1 = offs[i];
|
|
1851
|
+
else
|
|
1852
|
+
y0 = y1 = offs[i];
|
|
1853
|
+
|
|
1854
|
+
ctx.moveTo(x0, y0);
|
|
1855
|
+
ctx.lineTo(x1, y1);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
ctx.stroke();
|
|
1860
|
+
|
|
1861
|
+
pxAlign == 1 && offset != 0 && ctx.translate(-offset, -offset);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function axesCalc(cycleNum) {
|
|
1865
|
+
// log("axesCalc()", arguments);
|
|
1866
|
+
|
|
1867
|
+
let converged = true;
|
|
1868
|
+
|
|
1869
|
+
axes.forEach((axis, i) => {
|
|
1870
|
+
if (!axis.show)
|
|
1871
|
+
return;
|
|
1872
|
+
|
|
1873
|
+
let scale = scales[axis.scale];
|
|
1874
|
+
|
|
1875
|
+
if (scale.min == null) {
|
|
1876
|
+
if (axis._show) {
|
|
1877
|
+
converged = false;
|
|
1878
|
+
axis._show = false;
|
|
1879
|
+
resetYSeries(false);
|
|
1880
|
+
}
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
else {
|
|
1884
|
+
if (!axis._show) {
|
|
1885
|
+
converged = false;
|
|
1886
|
+
axis._show = true;
|
|
1887
|
+
resetYSeries(false);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
let side = axis.side;
|
|
1892
|
+
let ori = side % 2;
|
|
1893
|
+
|
|
1894
|
+
let {min, max} = scale; // // should this toggle them ._show = false
|
|
1895
|
+
|
|
1896
|
+
let [_incr, _space] = getIncrSpace(i, min, max, ori == 0 ? plotWidCss : plotHgtCss);
|
|
1897
|
+
|
|
1898
|
+
if (_space == 0)
|
|
1899
|
+
return;
|
|
1900
|
+
|
|
1901
|
+
// if we're using index positions, force first tick to match passed index
|
|
1902
|
+
let forceMin = scale.distr == 2;
|
|
1903
|
+
|
|
1904
|
+
let _splits = axis._splits = axis.splits(self, i, min, max, _incr, _space, forceMin);
|
|
1905
|
+
|
|
1906
|
+
// tick labels
|
|
1907
|
+
// BOO this assumes a specific data/series
|
|
1908
|
+
let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
|
|
1909
|
+
let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
|
|
1910
|
+
|
|
1911
|
+
let values = axis._values = axis.values(self, axis.filter(self, splits, i, _space, incr), i, _space, incr);
|
|
1912
|
+
|
|
1913
|
+
// rotating of labels only supported on bottom x axis
|
|
1914
|
+
axis._rotate = side == 2 ? axis.rotate(self, values, i, _space) : 0;
|
|
1915
|
+
|
|
1916
|
+
let oldSize = axis._size;
|
|
1917
|
+
|
|
1918
|
+
axis._size = ceil(axis.size(self, values, i, cycleNum));
|
|
1919
|
+
|
|
1920
|
+
if (oldSize != null && axis._size != oldSize) // ready && ?
|
|
1921
|
+
converged = false;
|
|
1922
|
+
});
|
|
1923
|
+
|
|
1924
|
+
return converged;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
function paddingCalc(cycleNum) {
|
|
1928
|
+
let converged = true;
|
|
1929
|
+
|
|
1930
|
+
padding.forEach((p, i) => {
|
|
1931
|
+
let _p = p(self, i, sidesWithAxes, cycleNum);
|
|
1932
|
+
|
|
1933
|
+
if (_p != _padding[i])
|
|
1934
|
+
converged = false;
|
|
1935
|
+
|
|
1936
|
+
_padding[i] = _p;
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
return converged;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
function drawAxesGrid() {
|
|
1943
|
+
for (let i = 0; i < axes.length; i++) {
|
|
1944
|
+
let axis = axes[i];
|
|
1945
|
+
|
|
1946
|
+
if (!axis.show || !axis._show)
|
|
1947
|
+
continue;
|
|
1948
|
+
|
|
1949
|
+
let side = axis.side;
|
|
1950
|
+
let ori = side % 2;
|
|
1951
|
+
|
|
1952
|
+
let x, y;
|
|
1953
|
+
|
|
1954
|
+
let fillStyle = axis.stroke(self, i);
|
|
1955
|
+
|
|
1956
|
+
let shiftDir = side == 0 || side == 3 ? -1 : 1;
|
|
1957
|
+
|
|
1958
|
+
let [_incr, _space] = axis._found;
|
|
1959
|
+
|
|
1960
|
+
// axis label
|
|
1961
|
+
if (axis.label != null) {
|
|
1962
|
+
let shiftAmt = axis.labelGap * shiftDir;
|
|
1963
|
+
let baseLpos = round((axis._lpos + shiftAmt) * pxRatio);
|
|
1964
|
+
|
|
1965
|
+
setFontStyle(axis.labelFont[0], fillStyle, "center", side == 2 ? TOP : BOTTOM);
|
|
1966
|
+
|
|
1967
|
+
ctx.save();
|
|
1968
|
+
|
|
1969
|
+
if (ori == 1) {
|
|
1970
|
+
x = y = 0;
|
|
1971
|
+
|
|
1972
|
+
ctx.translate(
|
|
1973
|
+
baseLpos,
|
|
1974
|
+
round(plotTop + plotHgt / 2),
|
|
1975
|
+
);
|
|
1976
|
+
ctx.rotate((side == 3 ? -PI : PI) / 2);
|
|
1977
|
+
|
|
1978
|
+
}
|
|
1979
|
+
else {
|
|
1980
|
+
x = round(plotLft + plotWid / 2);
|
|
1981
|
+
y = baseLpos;
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
let _label = isFn(axis.label) ? axis.label(self, i, _incr, _space) : axis.label;
|
|
1985
|
+
|
|
1986
|
+
ctx.fillText(_label, x, y);
|
|
1987
|
+
|
|
1988
|
+
ctx.restore();
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
if (_space == 0)
|
|
1992
|
+
continue;
|
|
1993
|
+
|
|
1994
|
+
let scale = scales[axis.scale];
|
|
1995
|
+
|
|
1996
|
+
let plotDim = ori == 0 ? plotWid : plotHgt;
|
|
1997
|
+
let plotOff = ori == 0 ? plotLft : plotTop;
|
|
1998
|
+
|
|
1999
|
+
let _splits = axis._splits;
|
|
2000
|
+
|
|
2001
|
+
// tick labels
|
|
2002
|
+
// BOO this assumes a specific data/series
|
|
2003
|
+
let splits = scale.distr == 2 ? _splits.map(i => data0[i]) : _splits;
|
|
2004
|
+
let incr = scale.distr == 2 ? data0[_splits[1]] - data0[_splits[0]] : _incr;
|
|
2005
|
+
|
|
2006
|
+
let ticks = axis.ticks;
|
|
2007
|
+
let border = axis.border;
|
|
2008
|
+
let _tickSize = ticks.show ? ticks.size : 0;
|
|
2009
|
+
let tickSize = round(_tickSize * pxRatio);
|
|
2010
|
+
let axisGap = round((axis.alignTo == 2 ? axis._size - _tickSize - axis.gap : axis.gap) * pxRatio);
|
|
2011
|
+
|
|
2012
|
+
// rotating of labels only supported on bottom x axis
|
|
2013
|
+
let angle = axis._rotate * -PI/180;
|
|
2014
|
+
|
|
2015
|
+
let basePos = pxRound(axis._pos * pxRatio);
|
|
2016
|
+
let shiftAmt = (tickSize + axisGap) * shiftDir;
|
|
2017
|
+
let finalPos = basePos + shiftAmt;
|
|
2018
|
+
y = ori == 0 ? finalPos : 0;
|
|
2019
|
+
x = ori == 1 ? finalPos : 0;
|
|
2020
|
+
|
|
2021
|
+
let font = axis.font[0];
|
|
2022
|
+
let textAlign = axis.align == 1 ? LEFT :
|
|
2023
|
+
axis.align == 2 ? RIGHT :
|
|
2024
|
+
angle > 0 ? LEFT :
|
|
2025
|
+
angle < 0 ? RIGHT :
|
|
2026
|
+
ori == 0 ? "center" : side == 3 ? RIGHT : LEFT;
|
|
2027
|
+
let textBaseline = angle ||
|
|
2028
|
+
ori == 1 ? "middle" : side == 2 ? TOP : BOTTOM;
|
|
2029
|
+
|
|
2030
|
+
setFontStyle(font, fillStyle, textAlign, textBaseline);
|
|
2031
|
+
|
|
2032
|
+
let lineHeight = axis.font[1] * axis.lineGap;
|
|
2033
|
+
|
|
2034
|
+
let canOffs = _splits.map(val => pxRound(getPos(val, scale, plotDim, plotOff)));
|
|
2035
|
+
|
|
2036
|
+
let _values = axis._values;
|
|
2037
|
+
|
|
2038
|
+
for (let i = 0; i < _values.length; i++) {
|
|
2039
|
+
let val = _values[i];
|
|
2040
|
+
|
|
2041
|
+
if (val != null) {
|
|
2042
|
+
if (ori == 0)
|
|
2043
|
+
x = canOffs[i];
|
|
2044
|
+
else
|
|
2045
|
+
y = canOffs[i];
|
|
2046
|
+
|
|
2047
|
+
val = "" + val;
|
|
2048
|
+
|
|
2049
|
+
let _parts = val.indexOf("\n") == -1 ? [val] : val.split(/\n/gm);
|
|
2050
|
+
|
|
2051
|
+
for (let j = 0; j < _parts.length; j++) {
|
|
2052
|
+
let text = _parts[j];
|
|
2053
|
+
|
|
2054
|
+
if (angle) {
|
|
2055
|
+
ctx.save();
|
|
2056
|
+
ctx.translate(x, y + j * lineHeight); // can this be replaced with position math?
|
|
2057
|
+
ctx.rotate(angle); // can this be done once?
|
|
2058
|
+
ctx.fillText(text, 0, 0);
|
|
2059
|
+
ctx.restore();
|
|
2060
|
+
}
|
|
2061
|
+
else
|
|
2062
|
+
ctx.fillText(text, x, y + j * lineHeight);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// ticks
|
|
2068
|
+
if (ticks.show) {
|
|
2069
|
+
drawOrthoLines(
|
|
2070
|
+
canOffs,
|
|
2071
|
+
ticks.filter(self, splits, i, _space, incr),
|
|
2072
|
+
ori,
|
|
2073
|
+
side,
|
|
2074
|
+
basePos,
|
|
2075
|
+
tickSize,
|
|
2076
|
+
roundDec(ticks.width * pxRatio, 3),
|
|
2077
|
+
ticks.stroke(self, i),
|
|
2078
|
+
ticks.dash,
|
|
2079
|
+
ticks.cap,
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
// grid
|
|
2084
|
+
let grid = axis.grid;
|
|
2085
|
+
|
|
2086
|
+
if (grid.show) {
|
|
2087
|
+
drawOrthoLines(
|
|
2088
|
+
canOffs,
|
|
2089
|
+
grid.filter(self, splits, i, _space, incr),
|
|
2090
|
+
ori,
|
|
2091
|
+
ori == 0 ? 2 : 1,
|
|
2092
|
+
ori == 0 ? plotTop : plotLft,
|
|
2093
|
+
ori == 0 ? plotHgt : plotWid,
|
|
2094
|
+
roundDec(grid.width * pxRatio, 3),
|
|
2095
|
+
grid.stroke(self, i),
|
|
2096
|
+
grid.dash,
|
|
2097
|
+
grid.cap,
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (border.show) {
|
|
2102
|
+
drawOrthoLines(
|
|
2103
|
+
[basePos],
|
|
2104
|
+
[1],
|
|
2105
|
+
ori == 0 ? 1 : 0,
|
|
2106
|
+
ori == 0 ? 1 : 2,
|
|
2107
|
+
ori == 1 ? plotTop : plotLft,
|
|
2108
|
+
ori == 1 ? plotHgt : plotWid,
|
|
2109
|
+
roundDec(border.width * pxRatio, 3),
|
|
2110
|
+
border.stroke(self, i),
|
|
2111
|
+
border.dash,
|
|
2112
|
+
border.cap,
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
fire("drawAxes");
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
function resetYSeries(minMax) {
|
|
2121
|
+
// log("resetYSeries()", arguments);
|
|
2122
|
+
|
|
2123
|
+
series.forEach((s, i) => {
|
|
2124
|
+
if (i > 0) {
|
|
2125
|
+
s._paths = null;
|
|
2126
|
+
|
|
2127
|
+
if (minMax) {
|
|
2128
|
+
if (mode == 1) {
|
|
2129
|
+
s.min = null;
|
|
2130
|
+
s.max = null;
|
|
2131
|
+
}
|
|
2132
|
+
else {
|
|
2133
|
+
s.facets.forEach(f => {
|
|
2134
|
+
f.min = null;
|
|
2135
|
+
f.max = null;
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
let queuedCommit = false;
|
|
2144
|
+
let deferHooks = false;
|
|
2145
|
+
let hooksQueue = [];
|
|
2146
|
+
|
|
2147
|
+
function flushHooks() {
|
|
2148
|
+
deferHooks = false;
|
|
2149
|
+
|
|
2150
|
+
for (let i = 0; i < hooksQueue.length; i++)
|
|
2151
|
+
fire(...hooksQueue[i])
|
|
2152
|
+
|
|
2153
|
+
hooksQueue.length = 0;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
function commit() {
|
|
2157
|
+
if (!queuedCommit) {
|
|
2158
|
+
microTask(_commit);
|
|
2159
|
+
queuedCommit = true;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
// manual batching (aka immediate mode), skips microtask queue
|
|
2164
|
+
function batch(fn, _deferHooks = false) {
|
|
2165
|
+
queuedCommit = true;
|
|
2166
|
+
deferHooks = _deferHooks;
|
|
2167
|
+
|
|
2168
|
+
fn(self);
|
|
2169
|
+
_commit();
|
|
2170
|
+
|
|
2171
|
+
if (_deferHooks && hooksQueue.length > 0)
|
|
2172
|
+
queueMicrotask(flushHooks);
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
self.batch = batch;
|
|
2176
|
+
|
|
2177
|
+
function _commit() {
|
|
2178
|
+
// log("_commit()", arguments);
|
|
2179
|
+
|
|
2180
|
+
if (shouldSetScales) {
|
|
2181
|
+
setScales();
|
|
2182
|
+
shouldSetScales = false;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
if (shouldConvergeSize) {
|
|
2186
|
+
convergeSize();
|
|
2187
|
+
shouldConvergeSize = false;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
if (shouldSetSize) {
|
|
2191
|
+
setStylePx(under, LEFT, plotLftCss);
|
|
2192
|
+
setStylePx(under, TOP, plotTopCss);
|
|
2193
|
+
setStylePx(under, WIDTH, plotWidCss);
|
|
2194
|
+
setStylePx(under, HEIGHT, plotHgtCss);
|
|
2195
|
+
|
|
2196
|
+
setStylePx(over, LEFT, plotLftCss);
|
|
2197
|
+
setStylePx(over, TOP, plotTopCss);
|
|
2198
|
+
setStylePx(over, WIDTH, plotWidCss);
|
|
2199
|
+
setStylePx(over, HEIGHT, plotHgtCss);
|
|
2200
|
+
|
|
2201
|
+
setStylePx(wrap, WIDTH, fullWidCss);
|
|
2202
|
+
setStylePx(wrap, HEIGHT, fullHgtCss);
|
|
2203
|
+
|
|
2204
|
+
// NOTE: mutating this during print preview in Chrome forces transparent
|
|
2205
|
+
// canvas pixels to white, even when followed up with clearRect() below
|
|
2206
|
+
can.width = round(fullWidCss * pxRatio);
|
|
2207
|
+
can.height = round(fullHgtCss * pxRatio);
|
|
2208
|
+
|
|
2209
|
+
axes.forEach(({ _el, _show, _size, _pos, side }) => {
|
|
2210
|
+
if (_el != null) {
|
|
2211
|
+
if (_show) {
|
|
2212
|
+
let posOffset = (side === 3 || side === 0 ? _size : 0);
|
|
2213
|
+
let isVt = side % 2 == 1;
|
|
2214
|
+
|
|
2215
|
+
setStylePx(_el, isVt ? "left" : "top", _pos - posOffset);
|
|
2216
|
+
setStylePx(_el, isVt ? "width" : "height", _size);
|
|
2217
|
+
setStylePx(_el, isVt ? "top" : "left", isVt ? plotTopCss : plotLftCss);
|
|
2218
|
+
setStylePx(_el, isVt ? "height" : "width", isVt ? plotHgtCss : plotWidCss);
|
|
2219
|
+
|
|
2220
|
+
remClass(_el, OFF);
|
|
2221
|
+
}
|
|
2222
|
+
else
|
|
2223
|
+
addClass(_el, OFF);
|
|
2224
|
+
}
|
|
2225
|
+
});
|
|
2226
|
+
|
|
2227
|
+
// invalidate ctx style cache
|
|
2228
|
+
ctxStroke = ctxFill = ctxWidth = ctxJoin = ctxCap = ctxFont = ctxAlign = ctxBaseline = ctxDash = null;
|
|
2229
|
+
ctxAlpha = 1;
|
|
2230
|
+
|
|
2231
|
+
syncRect(true);
|
|
2232
|
+
|
|
2233
|
+
if (
|
|
2234
|
+
plotLftCss != _plotLftCss ||
|
|
2235
|
+
plotTopCss != _plotTopCss ||
|
|
2236
|
+
plotWidCss != _plotWidCss ||
|
|
2237
|
+
plotHgtCss != _plotHgtCss
|
|
2238
|
+
) {
|
|
2239
|
+
resetYSeries(false);
|
|
2240
|
+
|
|
2241
|
+
let pctWid = plotWidCss / _plotWidCss;
|
|
2242
|
+
let pctHgt = plotHgtCss / _plotHgtCss;
|
|
2243
|
+
|
|
2244
|
+
if (showCursor && !shouldSetCursor && cursor.left >= 0) {
|
|
2245
|
+
cursor.left *= pctWid;
|
|
2246
|
+
cursor.top *= pctHgt;
|
|
2247
|
+
|
|
2248
|
+
vCursor && elTrans(vCursor, round(cursor.left), 0, plotWidCss, plotHgtCss);
|
|
2249
|
+
hCursor && elTrans(hCursor, 0, round(cursor.top), plotWidCss, plotHgtCss);
|
|
2250
|
+
|
|
2251
|
+
for (let i = 0; i < cursorPts.length; i++) {
|
|
2252
|
+
let pt = cursorPts[i];
|
|
2253
|
+
|
|
2254
|
+
if (pt != null) {
|
|
2255
|
+
cursorPtsLft[i] *= pctWid;
|
|
2256
|
+
cursorPtsTop[i] *= pctHgt;
|
|
2257
|
+
elTrans(pt, ceil(cursorPtsLft[i]), ceil(cursorPtsTop[i]), plotWidCss, plotHgtCss);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
if (select.show && !shouldSetSelect && select.left >= 0 && select.width > 0) {
|
|
2263
|
+
select.left *= pctWid;
|
|
2264
|
+
select.width *= pctWid;
|
|
2265
|
+
select.top *= pctHgt;
|
|
2266
|
+
select.height *= pctHgt;
|
|
2267
|
+
|
|
2268
|
+
for (let prop in _hideProps)
|
|
2269
|
+
setStylePx(selectDiv, prop, select[prop]);
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
_plotLftCss = plotLftCss;
|
|
2273
|
+
_plotTopCss = plotTopCss;
|
|
2274
|
+
_plotWidCss = plotWidCss;
|
|
2275
|
+
_plotHgtCss = plotHgtCss;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
fire("setSize");
|
|
2279
|
+
|
|
2280
|
+
shouldSetSize = false;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
if (fullWidCss > 0 && fullHgtCss > 0) {
|
|
2284
|
+
ctx.clearRect(0, 0, can.width, can.height);
|
|
2285
|
+
fire("drawClear");
|
|
2286
|
+
drawOrder.forEach(fn => fn());
|
|
2287
|
+
fire("draw");
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
if (select.show && shouldSetSelect) {
|
|
2291
|
+
setSelect(select);
|
|
2292
|
+
shouldSetSelect = false;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
if (showCursor && shouldSetCursor) {
|
|
2296
|
+
updateCursor(null, true, false);
|
|
2297
|
+
shouldSetCursor = false;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
if (FEAT_LEGEND && legend.show && legend.live && shouldSetLegend) {
|
|
2301
|
+
setLegend();
|
|
2302
|
+
shouldSetLegend = false; // redundant currently
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
if (!ready) {
|
|
2306
|
+
ready = true;
|
|
2307
|
+
self.status = 1;
|
|
2308
|
+
|
|
2309
|
+
fire("ready");
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
viaAutoScaleX = false;
|
|
2313
|
+
|
|
2314
|
+
queuedCommit = false;
|
|
2315
|
+
|
|
2316
|
+
if (!usePathCache)
|
|
2317
|
+
clearPathCache();
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
function clearPathCache() {
|
|
2321
|
+
series.forEach((s, i) => {
|
|
2322
|
+
if (i > 0)
|
|
2323
|
+
s._paths = null;
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
self.clearCache = clearPathCache;
|
|
2328
|
+
|
|
2329
|
+
self.redraw = (rebuildPaths, recalcAxes) => {
|
|
2330
|
+
shouldConvergeSize = recalcAxes || false;
|
|
2331
|
+
|
|
2332
|
+
if (rebuildPaths !== false)
|
|
2333
|
+
_setScale(xScaleKey, scaleX.min, scaleX.max);
|
|
2334
|
+
else
|
|
2335
|
+
commit();
|
|
2336
|
+
};
|
|
2337
|
+
|
|
2338
|
+
// redraw() => setScale('x', scales.x.min, scales.x.max);
|
|
2339
|
+
|
|
2340
|
+
// explicit, never re-ranged (is this actually true? for x and y)
|
|
2341
|
+
function setScale(key, opts) {
|
|
2342
|
+
let sc = scales[key];
|
|
2343
|
+
|
|
2344
|
+
if (sc.from == null) {
|
|
2345
|
+
if (dataLen == 0) {
|
|
2346
|
+
let minMax = sc.range(self, opts.min, opts.max, key);
|
|
2347
|
+
opts.min = minMax[0];
|
|
2348
|
+
opts.max = minMax[1];
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
if (opts.min > opts.max) {
|
|
2352
|
+
let _min = opts.min;
|
|
2353
|
+
opts.min = opts.max;
|
|
2354
|
+
opts.max = _min;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
if (dataLen > 1 && opts.min != null && opts.max != null && opts.max - opts.min < 1e-16)
|
|
2358
|
+
return;
|
|
2359
|
+
|
|
2360
|
+
if (key == xScaleKey) {
|
|
2361
|
+
if (sc.distr == 2 && dataLen > 0) {
|
|
2362
|
+
opts.min = closestIdx(opts.min, data[0]);
|
|
2363
|
+
opts.max = closestIdx(opts.max, data[0]);
|
|
2364
|
+
|
|
2365
|
+
if (opts.min == opts.max)
|
|
2366
|
+
opts.max++;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// log("setScale()", arguments);
|
|
2371
|
+
|
|
2372
|
+
pendScales[key] = opts;
|
|
2373
|
+
|
|
2374
|
+
shouldSetScales = true;
|
|
2375
|
+
commit();
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
self.setScale = setScale;
|
|
2380
|
+
|
|
2381
|
+
// INTERACTION
|
|
2382
|
+
|
|
2383
|
+
let xCursor;
|
|
2384
|
+
let yCursor;
|
|
2385
|
+
let vCursor;
|
|
2386
|
+
let hCursor;
|
|
2387
|
+
|
|
2388
|
+
// starting position before cursor.move
|
|
2389
|
+
let rawMouseLeft0;
|
|
2390
|
+
let rawMouseTop0;
|
|
2391
|
+
|
|
2392
|
+
// starting position
|
|
2393
|
+
let mouseLeft0;
|
|
2394
|
+
let mouseTop0;
|
|
2395
|
+
|
|
2396
|
+
// current position before cursor.move
|
|
2397
|
+
let rawMouseLeft1;
|
|
2398
|
+
let rawMouseTop1;
|
|
2399
|
+
|
|
2400
|
+
// current position
|
|
2401
|
+
let mouseLeft1;
|
|
2402
|
+
let mouseTop1;
|
|
2403
|
+
|
|
2404
|
+
let dragging = false;
|
|
2405
|
+
|
|
2406
|
+
const drag = cursor.drag;
|
|
2407
|
+
|
|
2408
|
+
let dragX = drag.x;
|
|
2409
|
+
let dragY = drag.y;
|
|
2410
|
+
|
|
2411
|
+
if (showCursor) {
|
|
2412
|
+
if (cursor.x)
|
|
2413
|
+
xCursor = placeDiv(CURSOR_X, over);
|
|
2414
|
+
if (cursor.y)
|
|
2415
|
+
yCursor = placeDiv(CURSOR_Y, over);
|
|
2416
|
+
|
|
2417
|
+
if (scaleX.ori == 0) {
|
|
2418
|
+
vCursor = xCursor;
|
|
2419
|
+
hCursor = yCursor;
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
vCursor = yCursor;
|
|
2423
|
+
hCursor = xCursor;
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
mouseLeft1 = cursor.left;
|
|
2427
|
+
mouseTop1 = cursor.top;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
const select = self.select = assign({
|
|
2431
|
+
show: true,
|
|
2432
|
+
over: true,
|
|
2433
|
+
left: 0,
|
|
2434
|
+
width: 0,
|
|
2435
|
+
top: 0,
|
|
2436
|
+
height: 0,
|
|
2437
|
+
}, opts.select);
|
|
2438
|
+
|
|
2439
|
+
const selectDiv = select.show ? placeDiv(SELECT, select.over ? over : under) : null;
|
|
2440
|
+
|
|
2441
|
+
function setSelect(opts, _fire) {
|
|
2442
|
+
if (select.show) {
|
|
2443
|
+
for (let prop in opts) {
|
|
2444
|
+
select[prop] = opts[prop];
|
|
2445
|
+
|
|
2446
|
+
if (prop in _hideProps)
|
|
2447
|
+
setStylePx(selectDiv, prop, opts[prop]);
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
_fire !== false && fire("setSelect");
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
self.setSelect = setSelect;
|
|
2455
|
+
|
|
2456
|
+
function toggleDOM(i) {
|
|
2457
|
+
let s = series[i];
|
|
2458
|
+
|
|
2459
|
+
if (s.show)
|
|
2460
|
+
showLegend && remClass(legendRows[i], OFF);
|
|
2461
|
+
else {
|
|
2462
|
+
showLegend && addClass(legendRows[i], OFF);
|
|
2463
|
+
|
|
2464
|
+
if (showCursor) {
|
|
2465
|
+
let pt = cursorOnePt ? cursorPts[0] : cursorPts[i];
|
|
2466
|
+
pt != null && elTrans(pt, -10, -10, plotWidCss, plotHgtCss);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
function _setScale(key, min, max) {
|
|
2472
|
+
setScale(key, {min, max});
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
function setSeries(i, opts, _fire, _pub) {
|
|
2476
|
+
// log("setSeries()", arguments);
|
|
2477
|
+
|
|
2478
|
+
if (opts.focus != null)
|
|
2479
|
+
setFocus(i);
|
|
2480
|
+
|
|
2481
|
+
if (opts.show != null) {
|
|
2482
|
+
series.forEach((s, si) => {
|
|
2483
|
+
if (si > 0 && (i == si || i == null)) {
|
|
2484
|
+
s.show = opts.show;
|
|
2485
|
+
FEAT_LEGEND && toggleDOM(si);
|
|
2486
|
+
|
|
2487
|
+
if (mode == 2) {
|
|
2488
|
+
_setScale(s.facets[0].scale, null, null);
|
|
2489
|
+
_setScale(s.facets[1].scale, null, null);
|
|
2490
|
+
}
|
|
2491
|
+
else
|
|
2492
|
+
_setScale(s.scale, null, null);
|
|
2493
|
+
|
|
2494
|
+
commit();
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
_fire !== false && fire("setSeries", i, opts);
|
|
2500
|
+
|
|
2501
|
+
_pub && pubSync("setSeries", self, i, opts);
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
self.setSeries = setSeries;
|
|
2505
|
+
|
|
2506
|
+
function setBand(bi, opts) {
|
|
2507
|
+
assign(bands[bi], opts);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
function addBand(opts, bi) {
|
|
2511
|
+
opts.fill = fnOrSelf(opts.fill || null);
|
|
2512
|
+
opts.dir = ifNull(opts.dir, -1);
|
|
2513
|
+
bi = bi == null ? bands.length : bi;
|
|
2514
|
+
bands.splice(bi, 0, opts);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
function delBand(bi) {
|
|
2518
|
+
if (bi == null)
|
|
2519
|
+
bands.length = 0;
|
|
2520
|
+
else
|
|
2521
|
+
bands.splice(bi, 1);
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
self.addBand = addBand;
|
|
2525
|
+
self.setBand = setBand;
|
|
2526
|
+
self.delBand = delBand;
|
|
2527
|
+
|
|
2528
|
+
function setAlpha(i, value) {
|
|
2529
|
+
series[i].alpha = value;
|
|
2530
|
+
|
|
2531
|
+
if (showCursor && cursorPts[i] != null)
|
|
2532
|
+
cursorPts[i].style.opacity = value;
|
|
2533
|
+
|
|
2534
|
+
if (FEAT_LEGEND && showLegend && legendRows[i])
|
|
2535
|
+
legendRows[i].style.opacity = value;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// y-distance
|
|
2539
|
+
let closestDist;
|
|
2540
|
+
let closestSeries;
|
|
2541
|
+
let focusedSeries;
|
|
2542
|
+
const FOCUS_TRUE = {focus: true};
|
|
2543
|
+
|
|
2544
|
+
function setFocus(i) {
|
|
2545
|
+
if (i != focusedSeries) {
|
|
2546
|
+
// log("setFocus()", arguments);
|
|
2547
|
+
|
|
2548
|
+
let allFocused = i == null;
|
|
2549
|
+
|
|
2550
|
+
let _setAlpha = focus.alpha != 1;
|
|
2551
|
+
|
|
2552
|
+
series.forEach((s, i2) => {
|
|
2553
|
+
if (mode == 1 || i2 > 0) {
|
|
2554
|
+
let isFocused = allFocused || i2 == 0 || i2 == i;
|
|
2555
|
+
s._focus = allFocused ? null : isFocused;
|
|
2556
|
+
_setAlpha && setAlpha(i2, isFocused ? 1 : focus.alpha);
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
focusedSeries = i;
|
|
2561
|
+
_setAlpha && commit();
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
if (showLegend && cursorFocus) {
|
|
2566
|
+
onMouse(mouseleave, legendTable, e => {
|
|
2567
|
+
if (cursor._lock)
|
|
2568
|
+
return;
|
|
2569
|
+
|
|
2570
|
+
setCursorEvent(e);
|
|
2571
|
+
|
|
2572
|
+
if (focusedSeries != null)
|
|
2573
|
+
setSeries(null, FOCUS_TRUE, true, syncOpts.setSeries);
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
function posToVal(pos, scale, can) {
|
|
2578
|
+
let sc = scales[scale];
|
|
2579
|
+
|
|
2580
|
+
if (can)
|
|
2581
|
+
pos = pos / pxRatio - (sc.ori == 1 ? plotTopCss : plotLftCss);
|
|
2582
|
+
|
|
2583
|
+
let dim = plotWidCss;
|
|
2584
|
+
|
|
2585
|
+
if (sc.ori == 1) {
|
|
2586
|
+
dim = plotHgtCss;
|
|
2587
|
+
pos = dim - pos;
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
if (sc.dir == -1)
|
|
2591
|
+
pos = dim - pos;
|
|
2592
|
+
|
|
2593
|
+
let _min = sc._min,
|
|
2594
|
+
_max = sc._max,
|
|
2595
|
+
pct = pos / dim;
|
|
2596
|
+
|
|
2597
|
+
let sv = _min + (_max - _min) * pct;
|
|
2598
|
+
|
|
2599
|
+
let distr = sc.distr;
|
|
2600
|
+
|
|
2601
|
+
return (
|
|
2602
|
+
distr == 3 ? pow(10, sv) :
|
|
2603
|
+
distr == 4 ? sinh(sv, sc.asinh) :
|
|
2604
|
+
distr == 100 ? sc.bwd(sv) :
|
|
2605
|
+
sv
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
function closestIdxFromXpos(pos, can) {
|
|
2610
|
+
let v = posToVal(pos, xScaleKey, can);
|
|
2611
|
+
return closestIdx(v, data[0], i0, i1);
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
self.valToIdx = val => closestIdx(val, data[0]);
|
|
2615
|
+
self.posToIdx = closestIdxFromXpos;
|
|
2616
|
+
self.posToVal = posToVal;
|
|
2617
|
+
self.valToPos = (val, scale, can) => (
|
|
2618
|
+
scales[scale].ori == 0 ?
|
|
2619
|
+
getHPos(val, scales[scale],
|
|
2620
|
+
can ? plotWid : plotWidCss,
|
|
2621
|
+
can ? plotLft : 0,
|
|
2622
|
+
) :
|
|
2623
|
+
getVPos(val, scales[scale],
|
|
2624
|
+
can ? plotHgt : plotHgtCss,
|
|
2625
|
+
can ? plotTop : 0,
|
|
2626
|
+
)
|
|
2627
|
+
);
|
|
2628
|
+
|
|
2629
|
+
self.setCursor = (opts, _fire, _pub) => {
|
|
2630
|
+
mouseLeft1 = opts.left;
|
|
2631
|
+
mouseTop1 = opts.top;
|
|
2632
|
+
// assign(cursor, opts);
|
|
2633
|
+
updateCursor(null, _fire, _pub);
|
|
2634
|
+
};
|
|
2635
|
+
|
|
2636
|
+
function setSelH(off, dim) {
|
|
2637
|
+
setStylePx(selectDiv, LEFT, select.left = off);
|
|
2638
|
+
setStylePx(selectDiv, WIDTH, select.width = dim);
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
function setSelV(off, dim) {
|
|
2642
|
+
setStylePx(selectDiv, TOP, select.top = off);
|
|
2643
|
+
setStylePx(selectDiv, HEIGHT, select.height = dim);
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
let setSelX = scaleX.ori == 0 ? setSelH : setSelV;
|
|
2647
|
+
let setSelY = scaleX.ori == 1 ? setSelH : setSelV;
|
|
2648
|
+
|
|
2649
|
+
function syncLegend() {
|
|
2650
|
+
if (showLegend && legend.live) {
|
|
2651
|
+
for (let i = mode == 2 ? 1 : 0; i < series.length; i++) {
|
|
2652
|
+
if (i == 0 && multiValLegend)
|
|
2653
|
+
continue;
|
|
2654
|
+
|
|
2655
|
+
let vals = legend.values[i];
|
|
2656
|
+
|
|
2657
|
+
let j = 0;
|
|
2658
|
+
|
|
2659
|
+
for (let k in vals)
|
|
2660
|
+
legendCells[i][j++].firstChild.nodeValue = vals[k];
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
function setLegend(opts, _fire) {
|
|
2666
|
+
if (opts != null) {
|
|
2667
|
+
if (opts.idxs) {
|
|
2668
|
+
opts.idxs.forEach((didx, sidx) => {
|
|
2669
|
+
activeIdxs[sidx] = didx;
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
else if (!isUndef(opts.idx))
|
|
2673
|
+
activeIdxs.fill(opts.idx);
|
|
2674
|
+
|
|
2675
|
+
legend.idx = activeIdxs[0];
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
if (showLegend && legend.live) {
|
|
2679
|
+
for (let sidx = 0; sidx < series.length; sidx++) {
|
|
2680
|
+
if (sidx > 0 || mode == 1 && !multiValLegend)
|
|
2681
|
+
setLegendValues(sidx, activeIdxs[sidx]);
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
syncLegend();
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
shouldSetLegend = false;
|
|
2688
|
+
|
|
2689
|
+
_fire !== false && fire("setLegend");
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
self.setLegend = setLegend;
|
|
2693
|
+
|
|
2694
|
+
function setLegendValues(sidx, idx) {
|
|
2695
|
+
let s = series[sidx];
|
|
2696
|
+
let src = sidx == 0 && xScaleDistr == 2 ? data0 : data[sidx];
|
|
2697
|
+
let val;
|
|
2698
|
+
|
|
2699
|
+
if (multiValLegend)
|
|
2700
|
+
val = s.values(self, sidx, idx) ?? NULL_LEGEND_VALUES;
|
|
2701
|
+
else {
|
|
2702
|
+
val = s.value(self, idx == null ? null : src[idx], sidx, idx);
|
|
2703
|
+
val = val == null ? NULL_LEGEND_VALUES : {_: val};
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
legend.values[sidx] = val;
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
function updateCursor(src, _fire, _pub) {
|
|
2710
|
+
// ts == null && log("updateCursor()", arguments);
|
|
2711
|
+
|
|
2712
|
+
rawMouseLeft1 = mouseLeft1;
|
|
2713
|
+
rawMouseTop1 = mouseTop1;
|
|
2714
|
+
|
|
2715
|
+
[mouseLeft1, mouseTop1] = cursor.move(self, mouseLeft1, mouseTop1);
|
|
2716
|
+
|
|
2717
|
+
cursor.left = mouseLeft1;
|
|
2718
|
+
cursor.top = mouseTop1;
|
|
2719
|
+
|
|
2720
|
+
if (showCursor) {
|
|
2721
|
+
vCursor && elTrans(vCursor, round(mouseLeft1), 0, plotWidCss, plotHgtCss);
|
|
2722
|
+
hCursor && elTrans(hCursor, 0, round(mouseTop1), plotWidCss, plotHgtCss);
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
let idx;
|
|
2726
|
+
|
|
2727
|
+
// when zooming to an x scale range between datapoints the binary search
|
|
2728
|
+
// for nearest min/max indices results in this condition. cheap hack :D
|
|
2729
|
+
let noDataInRange = i0 > i1; // works for mode 1 only
|
|
2730
|
+
|
|
2731
|
+
closestDist = inf;
|
|
2732
|
+
closestSeries = null;
|
|
2733
|
+
|
|
2734
|
+
// TODO: extract
|
|
2735
|
+
let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss;
|
|
2736
|
+
let yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss;
|
|
2737
|
+
|
|
2738
|
+
// if cursor hidden, hide points & clear legend vals
|
|
2739
|
+
if (mouseLeft1 < 0 || dataLen == 0 || noDataInRange) {
|
|
2740
|
+
idx = cursor.idx = null;
|
|
2741
|
+
|
|
2742
|
+
for (let i = 0; i < series.length; i++) {
|
|
2743
|
+
let pt = cursorPts[i];
|
|
2744
|
+
pt != null && elTrans(pt, -10, -10, plotWidCss, plotHgtCss);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
if (cursorFocus)
|
|
2748
|
+
setSeries(null, FOCUS_TRUE, true, src == null && syncOpts.setSeries);
|
|
2749
|
+
|
|
2750
|
+
if (FEAT_LEGEND && legend.live) {
|
|
2751
|
+
activeIdxs.fill(idx);
|
|
2752
|
+
shouldSetLegend = true;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
else {
|
|
2756
|
+
// let pctY = 1 - (y / rect.height);
|
|
2757
|
+
|
|
2758
|
+
let mouseXPos, valAtPosX, xPos;
|
|
2759
|
+
|
|
2760
|
+
if (mode == 1) {
|
|
2761
|
+
mouseXPos = scaleX.ori == 0 ? mouseLeft1 : mouseTop1;
|
|
2762
|
+
valAtPosX = posToVal(mouseXPos, xScaleKey);
|
|
2763
|
+
idx = cursor.idx = closestIdx(valAtPosX, data[0], i0, i1);
|
|
2764
|
+
xPos = valToPosX(data[0][idx], scaleX, xDim, 0);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// closest pt values
|
|
2768
|
+
let _ptLft = -10;
|
|
2769
|
+
let _ptTop = -10;
|
|
2770
|
+
let _ptWid = 0;
|
|
2771
|
+
let _ptHgt = 0;
|
|
2772
|
+
let _centered = true;
|
|
2773
|
+
let _ptFill = '';
|
|
2774
|
+
let _ptStroke = '';
|
|
2775
|
+
|
|
2776
|
+
for (let i = mode == 2 ? 1 : 0; i < series.length; i++) {
|
|
2777
|
+
let s = series[i];
|
|
2778
|
+
|
|
2779
|
+
let idx1 = activeIdxs[i];
|
|
2780
|
+
let yVal1 = idx1 == null ? null : (mode == 1 ? data[i][idx1] : data[i][1][idx1]);
|
|
2781
|
+
|
|
2782
|
+
let idx2 = cursor.dataIdx(self, i, idx, valAtPosX);
|
|
2783
|
+
let yVal2 = idx2 == null ? null : (mode == 1 ? data[i][idx2] : data[i][1][idx2]);
|
|
2784
|
+
|
|
2785
|
+
shouldSetLegend = shouldSetLegend || yVal2 != yVal1 || idx2 != idx1;
|
|
2786
|
+
|
|
2787
|
+
activeIdxs[i] = idx2;
|
|
2788
|
+
|
|
2789
|
+
if (i > 0 && s.show) {
|
|
2790
|
+
let xPos2 = idx2 == null ? -10 : idx2 == idx ? xPos : valToPosX(mode == 1 ? data[0][idx2] : data[i][0][idx2], scaleX, xDim, 0);
|
|
2791
|
+
|
|
2792
|
+
// this doesnt really work for state timeline, heatmap, status history (where the value maps to color, not y coords)
|
|
2793
|
+
let yPos = yVal2 == null ? -10 : valToPosY(yVal2, mode == 1 ? scales[s.scale] : scales[s.facets[1].scale], yDim, 0);
|
|
2794
|
+
|
|
2795
|
+
if (cursorFocus && yVal2 != null) {
|
|
2796
|
+
let mouseYPos = scaleX.ori == 1 ? mouseLeft1 : mouseTop1;
|
|
2797
|
+
let dist = abs(focus.dist(self, i, idx2, yPos, mouseYPos));
|
|
2798
|
+
|
|
2799
|
+
if (dist < closestDist) {
|
|
2800
|
+
let bias = focus.bias;
|
|
2801
|
+
|
|
2802
|
+
if (bias != 0) {
|
|
2803
|
+
let mouseYVal = posToVal(mouseYPos, s.scale);
|
|
2804
|
+
|
|
2805
|
+
let seriesYValSign = yVal2 >= 0 ? 1 : -1;
|
|
2806
|
+
let mouseYValSign = mouseYVal >= 0 ? 1 : -1;
|
|
2807
|
+
|
|
2808
|
+
// with a focus bias, we will never cross zero when prox testing
|
|
2809
|
+
// it's either closest towards zero, or closest away from zero
|
|
2810
|
+
if (mouseYValSign == seriesYValSign && (
|
|
2811
|
+
mouseYValSign == 1 ?
|
|
2812
|
+
(bias == 1 ? yVal2 >= mouseYVal : yVal2 <= mouseYVal) : // >= 0
|
|
2813
|
+
(bias == 1 ? yVal2 <= mouseYVal : yVal2 >= mouseYVal) // < 0
|
|
2814
|
+
)) {
|
|
2815
|
+
closestDist = dist;
|
|
2816
|
+
closestSeries = i;
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
else {
|
|
2820
|
+
closestDist = dist;
|
|
2821
|
+
closestSeries = i;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
if (shouldSetLegend || cursorOnePt) {
|
|
2827
|
+
let hPos, vPos;
|
|
2828
|
+
|
|
2829
|
+
if (scaleX.ori == 0) {
|
|
2830
|
+
hPos = xPos2;
|
|
2831
|
+
vPos = yPos;
|
|
2832
|
+
}
|
|
2833
|
+
else {
|
|
2834
|
+
hPos = yPos;
|
|
2835
|
+
vPos = xPos2;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
let ptWid, ptHgt, ptLft, ptTop,
|
|
2839
|
+
ptStroke, ptFill,
|
|
2840
|
+
centered = true,
|
|
2841
|
+
getBBox = points.bbox;
|
|
2842
|
+
|
|
2843
|
+
if (getBBox != null) {
|
|
2844
|
+
centered = false;
|
|
2845
|
+
|
|
2846
|
+
let bbox = getBBox(self, i);
|
|
2847
|
+
|
|
2848
|
+
ptLft = bbox.left;
|
|
2849
|
+
ptTop = bbox.top;
|
|
2850
|
+
ptWid = bbox.width;
|
|
2851
|
+
ptHgt = bbox.height;
|
|
2852
|
+
}
|
|
2853
|
+
else {
|
|
2854
|
+
ptLft = hPos;
|
|
2855
|
+
ptTop = vPos;
|
|
2856
|
+
ptWid = ptHgt = points.size(self, i);
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
ptFill = points.fill(self, i);
|
|
2860
|
+
ptStroke = points.stroke(self, i);
|
|
2861
|
+
|
|
2862
|
+
if (cursorOnePt) {
|
|
2863
|
+
if (i == closestSeries && closestDist <= focus.prox) {
|
|
2864
|
+
_ptLft = ptLft;
|
|
2865
|
+
_ptTop = ptTop;
|
|
2866
|
+
_ptWid = ptWid;
|
|
2867
|
+
_ptHgt = ptHgt;
|
|
2868
|
+
_centered = centered;
|
|
2869
|
+
_ptFill = ptFill;
|
|
2870
|
+
_ptStroke = ptStroke;
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
else {
|
|
2874
|
+
let pt = cursorPts[i];
|
|
2875
|
+
|
|
2876
|
+
if (pt != null) {
|
|
2877
|
+
cursorPtsLft[i] = ptLft;
|
|
2878
|
+
cursorPtsTop[i] = ptTop;
|
|
2879
|
+
|
|
2880
|
+
elSize(pt, ptWid, ptHgt, centered);
|
|
2881
|
+
elColor(pt, ptFill, ptStroke);
|
|
2882
|
+
elTrans(pt, ceil(ptLft), ceil(ptTop), plotWidCss, plotHgtCss);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// if only using single hover point (at cursorPts[0])
|
|
2890
|
+
// we have trigger styling at last visible series (once closestSeries is settled)
|
|
2891
|
+
if (cursorOnePt) {
|
|
2892
|
+
// some of this logic is similar to series focus below, since it matches the behavior by design
|
|
2893
|
+
|
|
2894
|
+
let p = focus.prox;
|
|
2895
|
+
|
|
2896
|
+
let focusChanged = focusedSeries == null ? closestDist <= p : (closestDist > p || closestSeries != focusedSeries);
|
|
2897
|
+
|
|
2898
|
+
if (shouldSetLegend || focusChanged) {
|
|
2899
|
+
let pt = cursorPts[0];
|
|
2900
|
+
|
|
2901
|
+
if (pt != null) {
|
|
2902
|
+
cursorPtsLft[0] = _ptLft;
|
|
2903
|
+
cursorPtsTop[0] = _ptTop;
|
|
2904
|
+
|
|
2905
|
+
elSize(pt, _ptWid, _ptHgt, _centered);
|
|
2906
|
+
elColor(pt, _ptFill, _ptStroke);
|
|
2907
|
+
elTrans(pt, ceil(_ptLft), ceil(_ptTop), plotWidCss, plotHgtCss);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// nit: cursor.drag.setSelect is assumed always true
|
|
2914
|
+
if (select.show && dragging) {
|
|
2915
|
+
if (src != null) {
|
|
2916
|
+
let [xKey, yKey] = syncOpts.scales;
|
|
2917
|
+
let [matchXKeys, matchYKeys] = syncOpts.match;
|
|
2918
|
+
let [xKeySrc, yKeySrc] = src.cursor.sync.scales;
|
|
2919
|
+
|
|
2920
|
+
// match the dragX/dragY implicitness/explicitness of src
|
|
2921
|
+
let sdrag = src.cursor.drag;
|
|
2922
|
+
dragX = sdrag._x;
|
|
2923
|
+
dragY = sdrag._y;
|
|
2924
|
+
|
|
2925
|
+
if (dragX || dragY) {
|
|
2926
|
+
let { left, top, width, height } = src.select;
|
|
2927
|
+
|
|
2928
|
+
let sori = src.scales[xKeySrc].ori;
|
|
2929
|
+
let sPosToVal = src.posToVal;
|
|
2930
|
+
|
|
2931
|
+
let sOff, sDim, sc, a, b;
|
|
2932
|
+
|
|
2933
|
+
let matchingX = xKey != null && matchXKeys(xKey, xKeySrc);
|
|
2934
|
+
let matchingY = yKey != null && matchYKeys(yKey, yKeySrc);
|
|
2935
|
+
|
|
2936
|
+
if (matchingX && dragX) {
|
|
2937
|
+
if (sori == 0) {
|
|
2938
|
+
sOff = left;
|
|
2939
|
+
sDim = width;
|
|
2940
|
+
}
|
|
2941
|
+
else {
|
|
2942
|
+
sOff = top;
|
|
2943
|
+
sDim = height;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
sc = scales[xKey];
|
|
2947
|
+
|
|
2948
|
+
a = valToPosX(sPosToVal(sOff, xKeySrc), sc, xDim, 0);
|
|
2949
|
+
b = valToPosX(sPosToVal(sOff + sDim, xKeySrc), sc, xDim, 0);
|
|
2950
|
+
|
|
2951
|
+
setSelX(min(a,b), abs(b-a));
|
|
2952
|
+
}
|
|
2953
|
+
else
|
|
2954
|
+
setSelX(0, xDim);
|
|
2955
|
+
|
|
2956
|
+
if (matchingY && dragY) {
|
|
2957
|
+
if (sori == 1) {
|
|
2958
|
+
sOff = left;
|
|
2959
|
+
sDim = width;
|
|
2960
|
+
}
|
|
2961
|
+
else {
|
|
2962
|
+
sOff = top;
|
|
2963
|
+
sDim = height;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2966
|
+
sc = scales[yKey];
|
|
2967
|
+
|
|
2968
|
+
a = valToPosY(sPosToVal(sOff, yKeySrc), sc, yDim, 0);
|
|
2969
|
+
b = valToPosY(sPosToVal(sOff + sDim, yKeySrc), sc, yDim, 0);
|
|
2970
|
+
|
|
2971
|
+
setSelY(min(a,b), abs(b-a));
|
|
2972
|
+
}
|
|
2973
|
+
else
|
|
2974
|
+
setSelY(0, yDim);
|
|
2975
|
+
}
|
|
2976
|
+
else
|
|
2977
|
+
hideSelect();
|
|
2978
|
+
}
|
|
2979
|
+
else {
|
|
2980
|
+
let rawDX = abs(rawMouseLeft1 - rawMouseLeft0);
|
|
2981
|
+
let rawDY = abs(rawMouseTop1 - rawMouseTop0);
|
|
2982
|
+
|
|
2983
|
+
if (scaleX.ori == 1) {
|
|
2984
|
+
let _rawDX = rawDX;
|
|
2985
|
+
rawDX = rawDY;
|
|
2986
|
+
rawDY = _rawDX;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
dragX = drag.x && rawDX >= drag.dist;
|
|
2990
|
+
dragY = drag.y && rawDY >= drag.dist;
|
|
2991
|
+
|
|
2992
|
+
let uni = drag.uni;
|
|
2993
|
+
|
|
2994
|
+
if (uni != null) {
|
|
2995
|
+
// only calc drag status if they pass the dist thresh
|
|
2996
|
+
if (dragX && dragY) {
|
|
2997
|
+
dragX = rawDX >= uni;
|
|
2998
|
+
dragY = rawDY >= uni;
|
|
2999
|
+
|
|
3000
|
+
// force unidirectionality when both are under uni limit
|
|
3001
|
+
if (!dragX && !dragY) {
|
|
3002
|
+
if (rawDY > rawDX)
|
|
3003
|
+
dragY = true;
|
|
3004
|
+
else
|
|
3005
|
+
dragX = true;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
else if (drag.x && drag.y && (dragX || dragY))
|
|
3010
|
+
// if omni with no uni then both dragX / dragY should be true if either is true
|
|
3011
|
+
dragX = dragY = true;
|
|
3012
|
+
|
|
3013
|
+
let p0, p1;
|
|
3014
|
+
|
|
3015
|
+
if (dragX) {
|
|
3016
|
+
if (scaleX.ori == 0) {
|
|
3017
|
+
p0 = mouseLeft0;
|
|
3018
|
+
p1 = mouseLeft1;
|
|
3019
|
+
}
|
|
3020
|
+
else {
|
|
3021
|
+
p0 = mouseTop0;
|
|
3022
|
+
p1 = mouseTop1;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
setSelX(min(p0, p1), abs(p1 - p0));
|
|
3026
|
+
|
|
3027
|
+
if (!dragY)
|
|
3028
|
+
setSelY(0, yDim);
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
if (dragY) {
|
|
3032
|
+
if (scaleX.ori == 1) {
|
|
3033
|
+
p0 = mouseLeft0;
|
|
3034
|
+
p1 = mouseLeft1;
|
|
3035
|
+
}
|
|
3036
|
+
else {
|
|
3037
|
+
p0 = mouseTop0;
|
|
3038
|
+
p1 = mouseTop1;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
setSelY(min(p0, p1), abs(p1 - p0));
|
|
3042
|
+
|
|
3043
|
+
if (!dragX)
|
|
3044
|
+
setSelX(0, xDim);
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// the drag didn't pass the dist requirement
|
|
3048
|
+
if (!dragX && !dragY) {
|
|
3049
|
+
setSelX(0, 0);
|
|
3050
|
+
setSelY(0, 0);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
drag._x = dragX;
|
|
3056
|
+
drag._y = dragY;
|
|
3057
|
+
|
|
3058
|
+
if (src == null) {
|
|
3059
|
+
if (_pub) {
|
|
3060
|
+
if (syncKey != null) {
|
|
3061
|
+
let [xSyncKey, ySyncKey] = syncOpts.scales;
|
|
3062
|
+
|
|
3063
|
+
syncOpts.values[0] = xSyncKey != null ? posToVal(scaleX.ori == 0 ? mouseLeft1 : mouseTop1, xSyncKey) : null;
|
|
3064
|
+
syncOpts.values[1] = ySyncKey != null ? posToVal(scaleX.ori == 1 ? mouseLeft1 : mouseTop1, ySyncKey) : null;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
pubSync(mousemove, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, idx);
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
if (cursorFocus) {
|
|
3071
|
+
let shouldPub = _pub && syncOpts.setSeries;
|
|
3072
|
+
let p = focus.prox;
|
|
3073
|
+
|
|
3074
|
+
if (focusedSeries == null) {
|
|
3075
|
+
if (closestDist <= p)
|
|
3076
|
+
setSeries(closestSeries, FOCUS_TRUE, true, shouldPub);
|
|
3077
|
+
}
|
|
3078
|
+
else {
|
|
3079
|
+
if (closestDist > p)
|
|
3080
|
+
setSeries(null, FOCUS_TRUE, true, shouldPub);
|
|
3081
|
+
else if (closestSeries != focusedSeries)
|
|
3082
|
+
setSeries(closestSeries, FOCUS_TRUE, true, shouldPub);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
if (shouldSetLegend) {
|
|
3088
|
+
legend.idx = idx;
|
|
3089
|
+
setLegend();
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
_fire !== false && fire("setCursor");
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
let rect = null;
|
|
3096
|
+
|
|
3097
|
+
Object.defineProperty(self, 'rect', {
|
|
3098
|
+
get() {
|
|
3099
|
+
if (rect == null)
|
|
3100
|
+
syncRect(false);
|
|
3101
|
+
|
|
3102
|
+
return rect;
|
|
3103
|
+
},
|
|
3104
|
+
});
|
|
3105
|
+
|
|
3106
|
+
function syncRect(defer = false) {
|
|
3107
|
+
if (defer)
|
|
3108
|
+
rect = null;
|
|
3109
|
+
else {
|
|
3110
|
+
rect = over.getBoundingClientRect();
|
|
3111
|
+
fire("syncRect", rect);
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
function mouseMove(e, src, _l, _t, _w, _h, _i) {
|
|
3116
|
+
if (cursor._lock)
|
|
3117
|
+
return;
|
|
3118
|
+
|
|
3119
|
+
// Chrome on Windows has a bug which triggers a stray mousemove event after an initial mousedown event
|
|
3120
|
+
// when clicking into a plot as part of re-focusing the browser window.
|
|
3121
|
+
// we gotta ignore it to avoid triggering a phantom drag / setSelect
|
|
3122
|
+
// However, on touch-only devices Chrome-based browsers trigger a 0-distance mousemove before mousedown
|
|
3123
|
+
// so we don't ignore it when mousedown has set the dragging flag
|
|
3124
|
+
if (dragging && e != null && e.movementX == 0 && e.movementY == 0)
|
|
3125
|
+
return;
|
|
3126
|
+
|
|
3127
|
+
cacheMouse(e, src, _l, _t, _w, _h, _i, false, e != null);
|
|
3128
|
+
|
|
3129
|
+
if (e != null)
|
|
3130
|
+
updateCursor(null, true, true);
|
|
3131
|
+
else
|
|
3132
|
+
updateCursor(src, true, false);
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
function cacheMouse(e, src, _l, _t, _w, _h, _i, initial, snap) {
|
|
3136
|
+
if (rect == null)
|
|
3137
|
+
syncRect(false);
|
|
3138
|
+
|
|
3139
|
+
setCursorEvent(e);
|
|
3140
|
+
|
|
3141
|
+
if (e != null) {
|
|
3142
|
+
_l = e.clientX - rect.left;
|
|
3143
|
+
_t = e.clientY - rect.top;
|
|
3144
|
+
}
|
|
3145
|
+
else {
|
|
3146
|
+
if (_l < 0 || _t < 0) {
|
|
3147
|
+
mouseLeft1 = -10;
|
|
3148
|
+
mouseTop1 = -10;
|
|
3149
|
+
return;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
let [xKey, yKey] = syncOpts.scales;
|
|
3153
|
+
|
|
3154
|
+
let syncOptsSrc = src.cursor.sync;
|
|
3155
|
+
let [xValSrc, yValSrc] = syncOptsSrc.values;
|
|
3156
|
+
let [xKeySrc, yKeySrc] = syncOptsSrc.scales;
|
|
3157
|
+
let [matchXKeys, matchYKeys] = syncOpts.match;
|
|
3158
|
+
|
|
3159
|
+
let rotSrc = src.axes[0].side % 2 == 1;
|
|
3160
|
+
|
|
3161
|
+
let xDim = scaleX.ori == 0 ? plotWidCss : plotHgtCss,
|
|
3162
|
+
yDim = scaleX.ori == 1 ? plotWidCss : plotHgtCss,
|
|
3163
|
+
_xDim = rotSrc ? _h : _w,
|
|
3164
|
+
_yDim = rotSrc ? _w : _h,
|
|
3165
|
+
_xPos = rotSrc ? _t : _l,
|
|
3166
|
+
_yPos = rotSrc ? _l : _t;
|
|
3167
|
+
|
|
3168
|
+
if (xKeySrc != null)
|
|
3169
|
+
_l = matchXKeys(xKey, xKeySrc) ? getPos(xValSrc, scales[xKey], xDim, 0) : -10;
|
|
3170
|
+
else
|
|
3171
|
+
_l = xDim * (_xPos/_xDim);
|
|
3172
|
+
|
|
3173
|
+
if (yKeySrc != null)
|
|
3174
|
+
_t = matchYKeys(yKey, yKeySrc) ? getPos(yValSrc, scales[yKey], yDim, 0) : -10;
|
|
3175
|
+
else
|
|
3176
|
+
_t = yDim * (_yPos/_yDim);
|
|
3177
|
+
|
|
3178
|
+
if (scaleX.ori == 1) {
|
|
3179
|
+
let __l = _l;
|
|
3180
|
+
_l = _t;
|
|
3181
|
+
_t = __l;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
if (snap && (src == null || src.cursor.event.type == mousemove)) {
|
|
3186
|
+
if (_l <= 1 || _l >= plotWidCss - 1)
|
|
3187
|
+
_l = incrRound(_l, plotWidCss);
|
|
3188
|
+
|
|
3189
|
+
if (_t <= 1 || _t >= plotHgtCss - 1)
|
|
3190
|
+
_t = incrRound(_t, plotHgtCss);
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
if (initial) {
|
|
3194
|
+
rawMouseLeft0 = _l;
|
|
3195
|
+
rawMouseTop0 = _t;
|
|
3196
|
+
|
|
3197
|
+
[mouseLeft0, mouseTop0] = cursor.move(self, _l, _t);
|
|
3198
|
+
}
|
|
3199
|
+
else {
|
|
3200
|
+
mouseLeft1 = _l;
|
|
3201
|
+
mouseTop1 = _t;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
const _hideProps = {
|
|
3206
|
+
width: 0,
|
|
3207
|
+
height: 0,
|
|
3208
|
+
left: 0,
|
|
3209
|
+
top: 0,
|
|
3210
|
+
};
|
|
3211
|
+
|
|
3212
|
+
function hideSelect() {
|
|
3213
|
+
setSelect(_hideProps, false);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
let downSelectLeft;
|
|
3217
|
+
let downSelectTop;
|
|
3218
|
+
let downSelectWidth;
|
|
3219
|
+
let downSelectHeight;
|
|
3220
|
+
|
|
3221
|
+
function mouseDown(e, src, _l, _t, _w, _h, _i) {
|
|
3222
|
+
dragging = true;
|
|
3223
|
+
dragX = dragY = drag._x = drag._y = false;
|
|
3224
|
+
|
|
3225
|
+
cacheMouse(e, src, _l, _t, _w, _h, _i, true, false);
|
|
3226
|
+
|
|
3227
|
+
if (e != null) {
|
|
3228
|
+
onMouse(mouseup, doc, mouseUp, false);
|
|
3229
|
+
pubSync(mousedown, self, mouseLeft0, mouseTop0, plotWidCss, plotHgtCss, null);
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
let { left, top, width, height } = select;
|
|
3233
|
+
|
|
3234
|
+
downSelectLeft = left;
|
|
3235
|
+
downSelectTop = top;
|
|
3236
|
+
downSelectWidth = width;
|
|
3237
|
+
downSelectHeight = height;
|
|
3238
|
+
|
|
3239
|
+
// hideSelect();
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
function mouseUp(e, src, _l, _t, _w, _h, _i) {
|
|
3243
|
+
dragging = drag._x = drag._y = false;
|
|
3244
|
+
|
|
3245
|
+
cacheMouse(e, src, _l, _t, _w, _h, _i, false, true);
|
|
3246
|
+
|
|
3247
|
+
let { left, top, width, height } = select;
|
|
3248
|
+
|
|
3249
|
+
let hasSelect = width > 0 || height > 0;
|
|
3250
|
+
let chgSelect = (
|
|
3251
|
+
downSelectLeft != left ||
|
|
3252
|
+
downSelectTop != top ||
|
|
3253
|
+
downSelectWidth != width ||
|
|
3254
|
+
downSelectHeight != height
|
|
3255
|
+
);
|
|
3256
|
+
|
|
3257
|
+
hasSelect && chgSelect && setSelect(select);
|
|
3258
|
+
|
|
3259
|
+
if (drag.setScale && hasSelect && chgSelect) {
|
|
3260
|
+
// if (syncKey != null) {
|
|
3261
|
+
// dragX = drag.x;
|
|
3262
|
+
// dragY = drag.y;
|
|
3263
|
+
// }
|
|
3264
|
+
|
|
3265
|
+
let xOff = left,
|
|
3266
|
+
xDim = width,
|
|
3267
|
+
yOff = top,
|
|
3268
|
+
yDim = height;
|
|
3269
|
+
|
|
3270
|
+
if (scaleX.ori == 1) {
|
|
3271
|
+
xOff = top,
|
|
3272
|
+
xDim = height,
|
|
3273
|
+
yOff = left,
|
|
3274
|
+
yDim = width;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
if (dragX) {
|
|
3278
|
+
_setScale(xScaleKey,
|
|
3279
|
+
posToVal(xOff, xScaleKey),
|
|
3280
|
+
posToVal(xOff + xDim, xScaleKey)
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3284
|
+
if (dragY) {
|
|
3285
|
+
for (let k in scales) {
|
|
3286
|
+
let sc = scales[k];
|
|
3287
|
+
|
|
3288
|
+
if (k != xScaleKey && sc.from == null && sc.min != inf) {
|
|
3289
|
+
_setScale(k,
|
|
3290
|
+
posToVal(yOff + yDim, k),
|
|
3291
|
+
posToVal(yOff, k)
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
hideSelect();
|
|
3298
|
+
}
|
|
3299
|
+
else if (cursor.lock) {
|
|
3300
|
+
cursor._lock = !cursor._lock;
|
|
3301
|
+
updateCursor(src, true, e != null);
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
if (e != null) {
|
|
3305
|
+
offMouse(mouseup, doc, mouseUp);
|
|
3306
|
+
pubSync(mouseup, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
function mouseLeave(e, src, _l, _t, _w, _h, _i) {
|
|
3311
|
+
if (cursor._lock)
|
|
3312
|
+
return;
|
|
3313
|
+
|
|
3314
|
+
setCursorEvent(e);
|
|
3315
|
+
|
|
3316
|
+
let _dragging = dragging;
|
|
3317
|
+
|
|
3318
|
+
if (dragging) {
|
|
3319
|
+
// handle case when mousemove aren't fired all the way to edges by browser
|
|
3320
|
+
let snapH = true;
|
|
3321
|
+
let snapV = true;
|
|
3322
|
+
let snapProx = 10;
|
|
3323
|
+
|
|
3324
|
+
let dragH, dragV;
|
|
3325
|
+
|
|
3326
|
+
if (scaleX.ori == 0) {
|
|
3327
|
+
dragH = dragX;
|
|
3328
|
+
dragV = dragY;
|
|
3329
|
+
}
|
|
3330
|
+
else {
|
|
3331
|
+
dragH = dragY;
|
|
3332
|
+
dragV = dragX;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
if (dragH && dragV) {
|
|
3336
|
+
// maybe omni corner snap
|
|
3337
|
+
snapH = mouseLeft1 <= snapProx || mouseLeft1 >= plotWidCss - snapProx;
|
|
3338
|
+
snapV = mouseTop1 <= snapProx || mouseTop1 >= plotHgtCss - snapProx;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
if (dragH && snapH)
|
|
3342
|
+
mouseLeft1 = mouseLeft1 < mouseLeft0 ? 0 : plotWidCss;
|
|
3343
|
+
|
|
3344
|
+
if (dragV && snapV)
|
|
3345
|
+
mouseTop1 = mouseTop1 < mouseTop0 ? 0 : plotHgtCss;
|
|
3346
|
+
|
|
3347
|
+
updateCursor(null, true, true);
|
|
3348
|
+
|
|
3349
|
+
dragging = false;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
mouseLeft1 = -10;
|
|
3353
|
+
mouseTop1 = -10;
|
|
3354
|
+
|
|
3355
|
+
activeIdxs.fill(null);
|
|
3356
|
+
|
|
3357
|
+
// passing a non-null timestamp to force sync/mousemove event
|
|
3358
|
+
updateCursor(null, true, true);
|
|
3359
|
+
|
|
3360
|
+
if (_dragging)
|
|
3361
|
+
dragging = _dragging;
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
function dblClick(e, src, _l, _t, _w, _h, _i) {
|
|
3365
|
+
if (cursor._lock)
|
|
3366
|
+
return;
|
|
3367
|
+
|
|
3368
|
+
setCursorEvent(e);
|
|
3369
|
+
|
|
3370
|
+
autoScaleX();
|
|
3371
|
+
|
|
3372
|
+
hideSelect();
|
|
3373
|
+
|
|
3374
|
+
if (e != null)
|
|
3375
|
+
pubSync(dblclick, self, mouseLeft1, mouseTop1, plotWidCss, plotHgtCss, null);
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
function onDppxChange() {
|
|
3379
|
+
setPxRatio();
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
on(dppxchange, win, onDppxChange);
|
|
3383
|
+
|
|
3384
|
+
// internal pub/sub
|
|
3385
|
+
const events = {};
|
|
3386
|
+
|
|
3387
|
+
events.mousedown = mouseDown;
|
|
3388
|
+
events.mousemove = mouseMove;
|
|
3389
|
+
events.mouseup = mouseUp;
|
|
3390
|
+
events.dblclick = dblClick;
|
|
3391
|
+
events["setSeries"] = (e, src, idx, opts) => {
|
|
3392
|
+
let seriesIdxMatcher = syncOpts.match[2];
|
|
3393
|
+
idx = seriesIdxMatcher(self, src, idx);
|
|
3394
|
+
idx != -1 && setSeries(idx, opts, true, false);
|
|
3395
|
+
};
|
|
3396
|
+
|
|
3397
|
+
if (showCursor) {
|
|
3398
|
+
onMouse(mousedown, over, mouseDown);
|
|
3399
|
+
onMouse(mousemove, over, mouseMove);
|
|
3400
|
+
onMouse(mouseenter, over, e => {
|
|
3401
|
+
setCursorEvent(e);
|
|
3402
|
+
syncRect(false);
|
|
3403
|
+
});
|
|
3404
|
+
onMouse(mouseleave, over, mouseLeave);
|
|
3405
|
+
|
|
3406
|
+
onMouse(dblclick, over, dblClick);
|
|
3407
|
+
|
|
3408
|
+
cursorPlots.add(self);
|
|
3409
|
+
|
|
3410
|
+
self.syncRect = syncRect;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
// external on/off
|
|
3414
|
+
const hooks = self.hooks = opts.hooks || {};
|
|
3415
|
+
|
|
3416
|
+
function fire(evName, a1, a2) {
|
|
3417
|
+
if (deferHooks)
|
|
3418
|
+
hooksQueue.push([evName, a1, a2]);
|
|
3419
|
+
else {
|
|
3420
|
+
if (evName in hooks) {
|
|
3421
|
+
hooks[evName].forEach(fn => {
|
|
3422
|
+
fn.call(null, self, a1, a2);
|
|
3423
|
+
});
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
|
|
3428
|
+
(opts.plugins || []).forEach(p => {
|
|
3429
|
+
for (let evName in p.hooks)
|
|
3430
|
+
hooks[evName] = (hooks[evName] || []).concat(p.hooks[evName]);
|
|
3431
|
+
});
|
|
3432
|
+
|
|
3433
|
+
const seriesIdxMatcher = (self, src, srcSeriesIdx) => srcSeriesIdx;
|
|
3434
|
+
|
|
3435
|
+
const syncOpts = assign({
|
|
3436
|
+
key: null,
|
|
3437
|
+
setSeries: false,
|
|
3438
|
+
filters: {
|
|
3439
|
+
pub: retTrue,
|
|
3440
|
+
sub: retTrue,
|
|
3441
|
+
},
|
|
3442
|
+
scales: [xScaleKey, series[1] ? series[1].scale : null],
|
|
3443
|
+
match: [retEq, retEq, seriesIdxMatcher],
|
|
3444
|
+
values: [null, null],
|
|
3445
|
+
}, cursor.sync);
|
|
3446
|
+
|
|
3447
|
+
if (syncOpts.match.length == 2)
|
|
3448
|
+
syncOpts.match.push(seriesIdxMatcher);
|
|
3449
|
+
|
|
3450
|
+
cursor.sync = syncOpts;
|
|
3451
|
+
|
|
3452
|
+
const syncKey = syncOpts.key;
|
|
3453
|
+
|
|
3454
|
+
const sync = _sync(syncKey);
|
|
3455
|
+
|
|
3456
|
+
function pubSync(type, src, x, y, w, h, i) {
|
|
3457
|
+
if (syncOpts.filters.pub(type, src, x, y, w, h, i))
|
|
3458
|
+
sync.pub(type, src, x, y, w, h, i);
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
sync.sub(self);
|
|
3462
|
+
|
|
3463
|
+
function pub(type, src, x, y, w, h, i) {
|
|
3464
|
+
if (syncOpts.filters.sub(type, src, x, y, w, h, i))
|
|
3465
|
+
events[type](null, src, x, y, w, h, i);
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
self.pub = pub;
|
|
3469
|
+
|
|
3470
|
+
function destroy() {
|
|
3471
|
+
sync.unsub(self);
|
|
3472
|
+
cursorPlots.delete(self);
|
|
3473
|
+
mouseListeners.clear();
|
|
3474
|
+
off(dppxchange, win, onDppxChange);
|
|
3475
|
+
root.remove();
|
|
3476
|
+
FEAT_LEGEND && legendTable?.remove(); // in case mounted outside of root
|
|
3477
|
+
fire("destroy");
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
self.destroy = destroy;
|
|
3481
|
+
|
|
3482
|
+
function _init() {
|
|
3483
|
+
fire("init", opts, data);
|
|
3484
|
+
|
|
3485
|
+
setData(data || opts.data, false);
|
|
3486
|
+
|
|
3487
|
+
if (pendScales[xScaleKey])
|
|
3488
|
+
setScale(xScaleKey, pendScales[xScaleKey]);
|
|
3489
|
+
else
|
|
3490
|
+
autoScaleX();
|
|
3491
|
+
|
|
3492
|
+
shouldSetSelect = select.show && (select.width > 0 || select.height > 0);
|
|
3493
|
+
shouldSetCursor = shouldSetLegend = true;
|
|
3494
|
+
|
|
3495
|
+
_setSize(opts.width, opts.height);
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
series.forEach(initSeries);
|
|
3499
|
+
|
|
3500
|
+
axes.forEach(initAxis);
|
|
3501
|
+
|
|
3502
|
+
if (then) {
|
|
3503
|
+
if (then instanceof HTMLElement) {
|
|
3504
|
+
then.appendChild(root);
|
|
3505
|
+
_init();
|
|
3506
|
+
}
|
|
3507
|
+
else
|
|
3508
|
+
then(self, _init);
|
|
3509
|
+
}
|
|
3510
|
+
else
|
|
3511
|
+
_init();
|
|
3512
|
+
|
|
3513
|
+
return self;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
uPlot.assign = assign;
|
|
3517
|
+
uPlot.fmtNum = fmtNum;
|
|
3518
|
+
uPlot.rangeNum = rangeNum;
|
|
3519
|
+
uPlot.rangeLog = rangeLog;
|
|
3520
|
+
uPlot.rangeAsinh = rangeAsinh;
|
|
3521
|
+
uPlot.orient = orient;
|
|
3522
|
+
uPlot.pxRatio = pxRatioGlobal;
|
|
3523
|
+
|
|
3524
|
+
if (FEAT_JOIN) {
|
|
3525
|
+
uPlot.join = join;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
if (FEAT_TIME) {
|
|
3529
|
+
uPlot.fmtDate = fmtDate;
|
|
3530
|
+
uPlot.tzDate = tzDate;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
uPlot.sync = _sync;
|
|
3534
|
+
|
|
3535
|
+
if (FEAT_PATHS) {
|
|
3536
|
+
uPlot.addGap = addGap;
|
|
3537
|
+
uPlot.clipGaps = clipGaps;
|
|
3538
|
+
|
|
3539
|
+
let paths = uPlot.paths = {
|
|
3540
|
+
points,
|
|
3541
|
+
};
|
|
3542
|
+
|
|
3543
|
+
FEAT_PATHS_LINEAR && (paths.linear = linear);
|
|
3544
|
+
FEAT_PATHS_STEPPED && (paths.stepped = stepped);
|
|
3545
|
+
FEAT_PATHS_BARS && (paths.bars = bars);
|
|
3546
|
+
FEAT_PATHS_SPLINE && (paths.spline = spline);
|
|
3547
|
+
FEAT_PATHS_SPLINE2 && (paths.spline2 = spline2);
|
|
3548
|
+
}
|