@wick-charts/vue 0.8.0 → 0.8.2
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/README.md +2 -0
- package/dist/BandSeries.vue.js +43 -32
- package/dist/BarSeries.vue.js +44 -33
- package/dist/CandlestickSeries.vue.js +24 -23
- package/dist/ChartContainer.vue.js +1 -1
- package/dist/HeatmapSeries.vue.js +21 -20
- package/dist/LineSeries.vue.js +43 -32
- package/dist/PieSeries.vue.js +25 -24
- package/dist/composables.js +63 -36
- package/dist/core/dist/axis/dom-labels.js +64 -53
- package/dist/core/dist/chart/badge-animator.js +38 -28
- package/dist/core/dist/chart/options.js +1 -1
- package/dist/core/dist/chart/y-target.js +38 -32
- package/dist/core/dist/chart.js +852 -709
- package/dist/core/dist/data/decimation.js +52 -51
- package/dist/core/dist/data/store.js +38 -20
- package/dist/core/dist/data/sync.js +97 -59
- package/dist/core/dist/navigator/controller.js +11 -11
- package/dist/core/dist/perf/perf-hud.js +3 -3
- package/dist/core/dist/scales/tick-tracker.js +2 -1
- package/dist/core/dist/scales/time-ticks.js +99 -0
- package/dist/core/dist/scales/x-scale.js +33 -27
- package/dist/core/dist/series/band.js +83 -83
- package/dist/core/dist/series/bar.js +240 -211
- package/dist/core/dist/series/base-multi-layer.js +288 -240
- package/dist/core/dist/series/candlestick.js +168 -168
- package/dist/core/dist/series/line.js +545 -455
- package/dist/core/dist/snapshots.js +109 -86
- package/dist/core/dist/utils/point.js +4 -0
- package/dist/core/dist/utils/time.js +42 -26
- package/dist/index.d.ts +67 -28
- package/dist/ui/InfoBar.vue.js +69 -69
- package/dist/ui/NumberFlow.vue.js +36 -27
- package/dist/ui/Tooltip.vue.js +128 -131
- package/dist/ui/YLabel.vue.js +47 -47
- package/package.json +2 -2
- package/src/BandSeries.vue +20 -5
- package/src/BarSeries.vue +20 -5
- package/src/CandlestickSeries.vue +5 -4
- package/src/ChartContainer.vue +15 -9
- package/src/HeatmapSeries.vue +5 -4
- package/src/LineSeries.vue +20 -5
- package/src/PieSeries.vue +6 -5
- package/src/composables.ts +64 -1
- package/src/ui/InfoBar.vue +11 -3
- package/src/ui/NumberFlow.vue +17 -3
- package/src/ui/Tooltip.vue +23 -12
- package/src/ui/YLabel.vue +28 -27
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Animator as
|
|
1
|
+
import { Animator as b } from "../animation/animator.js";
|
|
2
2
|
import { IntroWave as y } from "../animation/intro-wave.js";
|
|
3
3
|
import { ScalarSpring as V } from "../animation/scalar-spring.js";
|
|
4
|
-
import { TimeSeriesStore as
|
|
5
|
-
import { resolveColor as
|
|
6
|
-
import { normalizeTimePointArray as d, normalizeTime as L } from "../utils/time.js";
|
|
7
|
-
import { sumStack as
|
|
8
|
-
const
|
|
9
|
-
class
|
|
4
|
+
import { TimeSeriesStore as P } from "../data/store.js";
|
|
5
|
+
import { resolveColor as S } from "../utils/color.js";
|
|
6
|
+
import { normalizeTimePointArray as d, normalizeTime as L, sliceSample as M } from "../utils/time.js";
|
|
7
|
+
import { sumStack as w, renderedStackPercentTop as A, renderedStackTop as k } from "./stack-math.js";
|
|
8
|
+
const F = (m, t, e) => m + (t - m) * e;
|
|
9
|
+
class I {
|
|
10
10
|
stores;
|
|
11
11
|
// --- Animation state (per layer) ----------------------------------------
|
|
12
12
|
/**
|
|
@@ -53,10 +53,10 @@ class F {
|
|
|
53
53
|
* (the pre-prepend first point). Stagger position is `0` at the boundary
|
|
54
54
|
* and `1` at the deepest point. */
|
|
55
55
|
historyRange = null;
|
|
56
|
-
constructor(
|
|
57
|
-
this.stores = Array.from({ length:
|
|
58
|
-
{ length:
|
|
59
|
-
() => new
|
|
56
|
+
constructor(t) {
|
|
57
|
+
this.stores = Array.from({ length: t }, () => new P()), this.entries = Array.from({ length: t }, () => /* @__PURE__ */ new Map()), this.displayedLastValues = new Array(t).fill(null), this.#t = new Array(t).fill(null), this.#e = new Array(t).fill(null), this.#s = Array.from({ length: t }, () => /* @__PURE__ */ new Map()), this.#i = Array.from(
|
|
58
|
+
{ length: t },
|
|
59
|
+
() => new b({ initial: 1, duration: 0, lerp: F })
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
// --- Color accessors ------------------------------------------------------
|
|
@@ -65,91 +65,91 @@ class F {
|
|
|
65
65
|
* layer on the theme base in `options.colors`). Kept separate from the base so
|
|
66
66
|
* a theme swap, which rewrites `options.colors`, leaves these untouched.
|
|
67
67
|
*/
|
|
68
|
-
#
|
|
69
|
-
setColorOverrides(
|
|
70
|
-
this.#
|
|
68
|
+
#n = [];
|
|
69
|
+
setColorOverrides(t) {
|
|
70
|
+
this.#n = t;
|
|
71
71
|
}
|
|
72
72
|
/** The active resolver for a layer — the `data` override if set, else the theme base (cycled). */
|
|
73
|
-
#
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
73
|
+
#r(t) {
|
|
74
|
+
const e = this.#n[t];
|
|
75
|
+
if (e !== void 0) return e;
|
|
76
76
|
const s = this.options.colors;
|
|
77
|
-
return s[
|
|
77
|
+
return s[t % s.length];
|
|
78
78
|
}
|
|
79
79
|
/** The first layer's color resolver (string or value-fn). */
|
|
80
80
|
getColor() {
|
|
81
|
-
return this.#
|
|
81
|
+
return this.#r(0);
|
|
82
82
|
}
|
|
83
83
|
getColors() {
|
|
84
84
|
return this.options.colors;
|
|
85
85
|
}
|
|
86
86
|
/** Resolve one layer's color for a given datum value. */
|
|
87
|
-
resolveLayerColor(
|
|
88
|
-
return
|
|
87
|
+
resolveLayerColor(t, e) {
|
|
88
|
+
return S(this.#r(t), e);
|
|
89
89
|
}
|
|
90
90
|
// --- Data ingest ----------------------------------------------------------
|
|
91
|
-
setData(
|
|
92
|
-
const s = this.stores[
|
|
91
|
+
setData(t, e = 0) {
|
|
92
|
+
const s = this.stores[e];
|
|
93
93
|
if (!s) return;
|
|
94
94
|
let i = !1;
|
|
95
95
|
for (const o of this.stores)
|
|
96
96
|
o.length > 0 && (i = !0);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
this.displayedLastValues[
|
|
97
|
+
const n = d(t ?? []);
|
|
98
|
+
!i && n.length > 0 && this.introWave.arm(this.options.introMs), s.setData(n);
|
|
99
|
+
const r = s.last();
|
|
100
|
+
this.displayedLastValues[e] = r ? r.value : null, this.#t[e] = null, this.#e[e] = null, this.#s[e].clear(), this.entries[e].clear(), this.historyWave.finish(), this.historyRange = null;
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Replace a layer like {@link setData}, but ease each point that keeps its
|
|
104
104
|
* `time` from the value on screen to its new one over `smoothMs`; a point
|
|
105
105
|
* with a new `time` plays its entrance.
|
|
106
106
|
*/
|
|
107
|
-
morphData(
|
|
108
|
-
const s = this.stores[
|
|
107
|
+
morphData(t, e = 0) {
|
|
108
|
+
const s = this.stores[e];
|
|
109
109
|
if (!s) return;
|
|
110
110
|
if (!this.stores.some((h) => h.length > 0)) {
|
|
111
|
-
this.setData(
|
|
111
|
+
this.setData(t, e);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
const i = /* @__PURE__ */ new Map();
|
|
115
115
|
for (const h of s.getAll())
|
|
116
|
-
i.set(h.time, this.liveValue(
|
|
117
|
-
const
|
|
118
|
-
s.setData(
|
|
119
|
-
const
|
|
120
|
-
for (const h of
|
|
116
|
+
i.set(h.time, this.liveValue(e, h.time, h.value));
|
|
117
|
+
const n = d(t ?? []);
|
|
118
|
+
s.setData(n);
|
|
119
|
+
const r = this.options.smoothMs, o = this.isEntryEnabled() && this.options.entryMs > 0, l = this.entries[e], u = this.#s[e], a = /* @__PURE__ */ new Map();
|
|
120
|
+
for (const h of n) {
|
|
121
121
|
const g = i.get(h.time);
|
|
122
122
|
if (g === void 0) {
|
|
123
123
|
o && l.set(h.time, { startTime: performance.now() });
|
|
124
124
|
continue;
|
|
125
125
|
}
|
|
126
126
|
const p = h.value;
|
|
127
|
-
if (g === p ||
|
|
127
|
+
if (g === p || r <= 0 || !Number.isFinite(g) || !Number.isFinite(p)) continue;
|
|
128
128
|
const v = u.get(h.time) ?? new V(g);
|
|
129
|
-
v.target !== p && v.retarget(p, { settleMs:
|
|
129
|
+
v.target !== p && v.retarget(p, { settleMs: r }), a.set(h.time, v);
|
|
130
130
|
}
|
|
131
|
-
const c = new Set(
|
|
131
|
+
const c = new Set(n.map((h) => h.time));
|
|
132
132
|
for (const h of l.keys())
|
|
133
133
|
c.has(h) || l.delete(h);
|
|
134
|
-
this.#s[
|
|
134
|
+
this.#s[e] = a;
|
|
135
135
|
const f = s.last();
|
|
136
|
-
this.displayedLastValues[
|
|
136
|
+
this.displayedLastValues[e] = f ? f.value : null, this.#t[e] = null, this.#e[e] = null;
|
|
137
137
|
}
|
|
138
|
-
appendPoint(
|
|
139
|
-
const s = this.stores[
|
|
138
|
+
appendPoint(t, e = 0) {
|
|
139
|
+
const s = this.stores[e];
|
|
140
140
|
if (!s) return;
|
|
141
|
-
const i =
|
|
142
|
-
s.append({ ...i, time:
|
|
143
|
-
const l = this.#t[
|
|
144
|
-
l !== null &&
|
|
141
|
+
const i = t, n = L(i.time), r = s.last(), o = s.length;
|
|
142
|
+
s.append({ ...i, time: n });
|
|
143
|
+
const l = this.#t[e];
|
|
144
|
+
l !== null && r !== void 0 && s.length > o && (this.#e[e] = { time: r.time, anim: l }), this.displayedLastValues[e] = i.value, this.#t[e] = null;
|
|
145
145
|
const u = this.options.entryMs;
|
|
146
|
-
this.isEntryEnabled() && u > 0 && this.entries[
|
|
146
|
+
this.isEntryEnabled() && u > 0 && this.entries[e].set(n, { startTime: performance.now() });
|
|
147
147
|
}
|
|
148
|
-
prependPoints(
|
|
149
|
-
const s = this.stores[
|
|
150
|
-
if (!s ||
|
|
151
|
-
const i = d(
|
|
152
|
-
s.prepend(i), this.#o(i[0]?.time,
|
|
148
|
+
prependPoints(t, e = 0) {
|
|
149
|
+
const s = this.stores[e];
|
|
150
|
+
if (!s || t.length === 0) return;
|
|
151
|
+
const i = d(t), n = s.first()?.time;
|
|
152
|
+
s.prepend(i), this.#o(i[0]?.time, n);
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
155
|
* Arm (or extend) the history-reveal wave for freshly prepended points.
|
|
@@ -157,71 +157,71 @@ class F {
|
|
|
157
157
|
* another layer — only the range grows; restarting the clock would snap
|
|
158
158
|
* half-revealed points back to invisible.
|
|
159
159
|
*/
|
|
160
|
-
#o(
|
|
160
|
+
#o(t, e) {
|
|
161
161
|
const s = this.options.historyRevealMs;
|
|
162
|
-
if (!(this.options.historyReveal === "none" || s <= 0) && !(
|
|
162
|
+
if (!(this.options.historyReveal === "none" || s <= 0) && !(t === void 0 || e === void 0)) {
|
|
163
163
|
if (this.historyWave.active && this.historyRange !== null) {
|
|
164
|
-
this.historyRange = { from: Math.min(this.historyRange.from,
|
|
164
|
+
this.historyRange = { from: Math.min(this.historyRange.from, t), to: this.historyRange.to };
|
|
165
165
|
return;
|
|
166
166
|
}
|
|
167
|
-
this.historyRange = { from:
|
|
167
|
+
this.historyRange = { from: t, to: e }, this.historyWave.arm(s);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
-
updateLastPoint(
|
|
171
|
-
const s = this.stores[
|
|
170
|
+
updateLastPoint(t, e = 0) {
|
|
171
|
+
const s = this.stores[e];
|
|
172
172
|
if (!s) return;
|
|
173
|
-
const i =
|
|
173
|
+
const i = t, n = s.last()?.time;
|
|
174
174
|
s.updateLast({ ...i, time: L(i.time) });
|
|
175
|
-
const
|
|
176
|
-
|
|
175
|
+
const r = this.#s[e], o = n === void 0 ? void 0 : r.get(n);
|
|
176
|
+
n !== void 0 && o !== void 0 && (r.delete(n), this.displayedLastValues[e] = o.current, this.#t[e] = o);
|
|
177
177
|
const l = i.value, u = this.options.smoothMs;
|
|
178
178
|
if (u <= 0) {
|
|
179
|
-
this.displayedLastValues[
|
|
179
|
+
this.displayedLastValues[e] = l, this.#t[e] = null;
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
-
let a = this.#t[
|
|
182
|
+
let a = this.#t[e];
|
|
183
183
|
if (a === null) {
|
|
184
|
-
const c = this.displayedLastValues[
|
|
185
|
-
a = new V(c), this.#t[
|
|
184
|
+
const c = this.displayedLastValues[e] ?? l;
|
|
185
|
+
a = new V(c), this.#t[e] = a;
|
|
186
186
|
}
|
|
187
187
|
a.retarget(l, { settleMs: u });
|
|
188
188
|
}
|
|
189
|
-
keepLast(
|
|
190
|
-
const s = this.stores[
|
|
191
|
-
if (!s ||
|
|
192
|
-
const i = s.length -
|
|
189
|
+
keepLast(t, e = 0) {
|
|
190
|
+
const s = this.stores[e];
|
|
191
|
+
if (!s || t < 0) return;
|
|
192
|
+
const i = s.length - t;
|
|
193
193
|
i <= 0 || s.trimStart(i);
|
|
194
194
|
}
|
|
195
195
|
// --- Layer model ----------------------------------------------------------
|
|
196
196
|
getLayerCount() {
|
|
197
197
|
return this.stores.length;
|
|
198
198
|
}
|
|
199
|
-
setLayerVisible(
|
|
200
|
-
this.stores[
|
|
199
|
+
setLayerVisible(t, e) {
|
|
200
|
+
this.stores[t]?.setVisible(e);
|
|
201
201
|
}
|
|
202
|
-
isLayerVisible(
|
|
203
|
-
return this.stores[
|
|
202
|
+
isLayerVisible(t) {
|
|
203
|
+
return this.stores[t]?.isVisible() ?? !0;
|
|
204
204
|
}
|
|
205
205
|
/** Representative concrete color per layer — value-fns are resolved at the
|
|
206
206
|
* layer's last value, giving the legend swatch / color-change detection a
|
|
207
207
|
* stable string. Honors per-layer `data` overrides. */
|
|
208
208
|
getLayerColors() {
|
|
209
|
-
const
|
|
210
|
-
for (let
|
|
211
|
-
const s = this.
|
|
212
|
-
|
|
209
|
+
const t = [];
|
|
210
|
+
for (let e = 0; e < this.stores.length; e++) {
|
|
211
|
+
const s = this.lastFinitePoint(e)?.value ?? 0;
|
|
212
|
+
t.push(this.resolveLayerColor(e, s));
|
|
213
213
|
}
|
|
214
|
-
return
|
|
214
|
+
return t;
|
|
215
215
|
}
|
|
216
216
|
// --- Lifecycle ------------------------------------------------------------
|
|
217
|
-
onDataChanged(
|
|
218
|
-
for (const
|
|
217
|
+
onDataChanged(t) {
|
|
218
|
+
for (const e of this.stores) e.on("update", t);
|
|
219
219
|
return () => {
|
|
220
|
-
for (const
|
|
220
|
+
for (const e of this.stores) e.off("update", t);
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
dispose() {
|
|
224
|
-
for (const
|
|
224
|
+
for (const t of this.stores) t.removeAllListeners();
|
|
225
225
|
}
|
|
226
226
|
// --- Animation primitives — renderer-owned state --------------------------
|
|
227
227
|
/**
|
|
@@ -231,13 +231,13 @@ class F {
|
|
|
231
231
|
* pure function of wall time — so tooltip/last-value queries (which have
|
|
232
232
|
* no context to hand) can read it too.
|
|
233
233
|
*/
|
|
234
|
-
entranceProgress(
|
|
235
|
-
const s = this.entries[
|
|
234
|
+
entranceProgress(t, e) {
|
|
235
|
+
const s = this.entries[t]?.get(e);
|
|
236
236
|
if (s === void 0) return 1;
|
|
237
237
|
const i = this.options.entryMs;
|
|
238
238
|
if (i <= 0) return 1;
|
|
239
|
-
const
|
|
240
|
-
return
|
|
239
|
+
const n = performance.now() - s.startTime;
|
|
240
|
+
return n <= 0 ? 0 : n >= i ? 1 : n / i;
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
243
243
|
* Initial-load reveal progress for a datum, keyed by its normalized
|
|
@@ -247,10 +247,10 @@ class F {
|
|
|
247
247
|
* (bar) fold this into their {@link entranceProgress} override; the line
|
|
248
248
|
* renders its own continuous sweep instead.
|
|
249
249
|
*/
|
|
250
|
-
introProgressAt(
|
|
250
|
+
introProgressAt(t, e) {
|
|
251
251
|
if (!this.introWave.active) return 1;
|
|
252
|
-
const s =
|
|
253
|
-
return this.introWave.progressAt(
|
|
252
|
+
const s = t.timeScale.getRange(), i = s.to - s.from, n = i > 0 ? (e - s.from) / i : 0;
|
|
253
|
+
return this.introWave.progressAt(n);
|
|
254
254
|
}
|
|
255
255
|
/**
|
|
256
256
|
* History-prepend reveal progress for a datum. Stagger position is
|
|
@@ -258,10 +258,10 @@ class F {
|
|
|
258
258
|
* point — so the wave travels into history. `1` for points outside the
|
|
259
259
|
* prepended span or when no reveal is in flight.
|
|
260
260
|
*/
|
|
261
|
-
historyProgressAt(
|
|
262
|
-
const
|
|
263
|
-
if (!this.historyWave.active ||
|
|
264
|
-
const s =
|
|
261
|
+
historyProgressAt(t) {
|
|
262
|
+
const e = this.historyRange;
|
|
263
|
+
if (!this.historyWave.active || e === null || t >= e.to) return 1;
|
|
264
|
+
const s = e.to - e.from, i = s > 0 ? (e.to - t) / s : 0;
|
|
265
265
|
return this.historyWave.progressAt(i);
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
@@ -275,14 +275,14 @@ class F {
|
|
|
275
275
|
* on every append. Renderers lerp each chain link from the previous
|
|
276
276
|
* link's *rendered* position instead, so geometry stays continuous.
|
|
277
277
|
*/
|
|
278
|
-
unsettledTail(
|
|
279
|
-
const
|
|
280
|
-
if (!
|
|
278
|
+
unsettledTail(t) {
|
|
279
|
+
const e = this.stores[t]?.getAll();
|
|
280
|
+
if (!e || e.length < 2) return [];
|
|
281
281
|
const s = [];
|
|
282
|
-
for (let i =
|
|
283
|
-
const
|
|
284
|
-
if (
|
|
285
|
-
s.unshift({ time:
|
|
282
|
+
for (let i = e.length - 1; i >= 1; i--) {
|
|
283
|
+
const n = this.entranceProgress(t, e[i].time);
|
|
284
|
+
if (n >= 1) break;
|
|
285
|
+
s.unshift({ time: e[i].time, progress: n });
|
|
286
286
|
}
|
|
287
287
|
return s;
|
|
288
288
|
}
|
|
@@ -294,8 +294,8 @@ class F {
|
|
|
294
294
|
* Falls back to `rawValue` when the store is empty or no smoothing has
|
|
295
295
|
* happened yet.
|
|
296
296
|
*/
|
|
297
|
-
effectiveValue(
|
|
298
|
-
return this.liveValue(
|
|
297
|
+
effectiveValue(t, e, s, i) {
|
|
298
|
+
return this.liveValue(e, s, i);
|
|
299
299
|
}
|
|
300
300
|
/**
|
|
301
301
|
* The ctx-independent core of {@link effectiveValue} — used directly by the
|
|
@@ -304,14 +304,14 @@ class F {
|
|
|
304
304
|
* transform). Those queries only need the live smoothing/chase this base
|
|
305
305
|
* method already provides, never the intro's positional transform.
|
|
306
306
|
*/
|
|
307
|
-
liveValue(
|
|
308
|
-
const i = this.#s[
|
|
307
|
+
liveValue(t, e, s) {
|
|
308
|
+
const i = this.#s[t]?.get(e);
|
|
309
309
|
if (i !== void 0) return i.current;
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
return this.displayedLastValues[
|
|
313
|
-
const
|
|
314
|
-
return
|
|
310
|
+
const n = this.stores[t]?.last()?.time;
|
|
311
|
+
if (n !== void 0 && e === n)
|
|
312
|
+
return this.displayedLastValues[t] ?? s;
|
|
313
|
+
const r = this.#e[t];
|
|
314
|
+
return r !== null && r.time === e ? r.anim.current : s;
|
|
315
315
|
}
|
|
316
316
|
/**
|
|
317
317
|
* Value a last-value/tooltip query should report for `time` — defaults to
|
|
@@ -320,8 +320,8 @@ class F {
|
|
|
320
320
|
* override) without that lerp feeding back into `effectiveValue`, which
|
|
321
321
|
* the renderer's own per-point draws use as an unlerp'd target.
|
|
322
322
|
*/
|
|
323
|
-
snapshotValue(
|
|
324
|
-
return this.liveValue(
|
|
323
|
+
snapshotValue(t, e, s) {
|
|
324
|
+
return this.liveValue(t, e, s);
|
|
325
325
|
}
|
|
326
326
|
// --- Animation lifecycle --------------------------------------------------
|
|
327
327
|
/**
|
|
@@ -330,44 +330,44 @@ class F {
|
|
|
330
330
|
* subclass passes its own clock so test harnesses (which stub
|
|
331
331
|
* `performance.now`) can drive progression deterministically.
|
|
332
332
|
*/
|
|
333
|
-
tickAnimations(
|
|
334
|
-
this.introWave.tick(
|
|
335
|
-
for (const
|
|
336
|
-
for (let
|
|
337
|
-
const s = this.#t[
|
|
333
|
+
tickAnimations(t) {
|
|
334
|
+
this.introWave.tick(t), this.historyWave.tick(t);
|
|
335
|
+
for (const e of this.#i) e.tick(t);
|
|
336
|
+
for (let e = 0; e < this.stores.length; e++) {
|
|
337
|
+
const s = this.#t[e];
|
|
338
338
|
if (s !== null) {
|
|
339
|
-
const l = s.tick(
|
|
340
|
-
this.displayedLastValues[
|
|
339
|
+
const l = s.tick(t);
|
|
340
|
+
this.displayedLastValues[e] = s.current, l || (this.#t[e] = null);
|
|
341
341
|
}
|
|
342
|
-
const i = this.#e[
|
|
343
|
-
i !== null && !i.anim.tick(
|
|
344
|
-
const
|
|
345
|
-
for (const [l, u] of
|
|
346
|
-
u.tick(
|
|
347
|
-
const
|
|
348
|
-
if (
|
|
349
|
-
this.entries[
|
|
342
|
+
const i = this.#e[e];
|
|
343
|
+
i !== null && !i.anim.tick(t) && (this.#e[e] = null);
|
|
344
|
+
const n = this.#s[e];
|
|
345
|
+
for (const [l, u] of n)
|
|
346
|
+
u.tick(t) || n.delete(l);
|
|
347
|
+
const r = this.options.entryMs;
|
|
348
|
+
if (r <= 0) {
|
|
349
|
+
this.entries[e].clear();
|
|
350
350
|
continue;
|
|
351
351
|
}
|
|
352
|
-
const o = this.entries[
|
|
352
|
+
const o = this.entries[e];
|
|
353
353
|
for (const [l, u] of o)
|
|
354
|
-
|
|
354
|
+
t - u.startTime >= r && o.delete(l);
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
/** True while any layer has an active chase, unsettled entry, alpha fade,
|
|
358
358
|
* or the initial-load reveal is still sweeping. */
|
|
359
359
|
get needsAnimation() {
|
|
360
360
|
if (this.introWave.active || this.historyWave.active) return !0;
|
|
361
|
-
for (const
|
|
362
|
-
if (
|
|
363
|
-
for (const
|
|
364
|
-
if (
|
|
365
|
-
for (const
|
|
366
|
-
if (
|
|
367
|
-
for (const
|
|
368
|
-
if (
|
|
369
|
-
for (const
|
|
370
|
-
if (
|
|
361
|
+
for (const t of this.#i)
|
|
362
|
+
if (t.animating) return !0;
|
|
363
|
+
for (const t of this.#t)
|
|
364
|
+
if (t !== null) return !0;
|
|
365
|
+
for (const t of this.#e)
|
|
366
|
+
if (t !== null) return !0;
|
|
367
|
+
for (const t of this.#s)
|
|
368
|
+
if (t.size > 0) return !0;
|
|
369
|
+
for (const t of this.entries)
|
|
370
|
+
if (t.size > 0) return !0;
|
|
371
371
|
return !1;
|
|
372
372
|
}
|
|
373
373
|
/** Reveal-front position of the initial-load intro; `1` once settled. */
|
|
@@ -382,16 +382,16 @@ class F {
|
|
|
382
382
|
*/
|
|
383
383
|
cancelEntranceAnimations() {
|
|
384
384
|
this.introWave.finish();
|
|
385
|
-
for (const
|
|
385
|
+
for (const t of this.entries) t.clear();
|
|
386
386
|
}
|
|
387
387
|
/**
|
|
388
388
|
* Start a series-wide fade toward `target` over `durationMs`. Fans out
|
|
389
389
|
* across every per-layer animator so a `setSeriesVisible` toggle is just
|
|
390
390
|
* "set every layer to the same target". `durationMs <= 0` snaps.
|
|
391
391
|
*/
|
|
392
|
-
setAlpha(
|
|
392
|
+
setAlpha(t, e) {
|
|
393
393
|
for (const s of this.#i)
|
|
394
|
-
s.setTarget(
|
|
394
|
+
s.setTarget(t, { duration: e });
|
|
395
395
|
}
|
|
396
396
|
/**
|
|
397
397
|
* `1` while any layer has alpha > 0 OR is animating, `0` when every layer
|
|
@@ -407,8 +407,8 @@ class F {
|
|
|
407
407
|
* would deadlock at `0`.
|
|
408
408
|
*/
|
|
409
409
|
getAlpha() {
|
|
410
|
-
for (const
|
|
411
|
-
if (
|
|
410
|
+
for (const t of this.#i)
|
|
411
|
+
if (t.current > 0 || t.animating) return 1;
|
|
412
412
|
return 0;
|
|
413
413
|
}
|
|
414
414
|
/**
|
|
@@ -416,125 +416,174 @@ class F {
|
|
|
416
416
|
* Subclass draw loops multiply this into `globalAlpha` per layer so the
|
|
417
417
|
* fade lives next to the geometry it affects.
|
|
418
418
|
*/
|
|
419
|
-
setLayerAlpha(
|
|
420
|
-
this.#i[
|
|
419
|
+
setLayerAlpha(t, e, s) {
|
|
420
|
+
this.#i[t]?.setTarget(e, { duration: s });
|
|
421
421
|
}
|
|
422
422
|
/** Latest rendered per-layer alpha. Defaults to 1 for out-of-range indices. */
|
|
423
|
-
getLayerAlpha(
|
|
424
|
-
return this.#i[
|
|
423
|
+
getLayerAlpha(t) {
|
|
424
|
+
return this.#i[t]?.current ?? 1;
|
|
425
425
|
}
|
|
426
426
|
// --- Data queries ---------------------------------------------------------
|
|
427
|
+
/**
|
|
428
|
+
* The layer's newest point with a finite value, after skipping `skip` such
|
|
429
|
+
* points — a trailing `null` / `NaN` is a gap, not the latest reading.
|
|
430
|
+
*/
|
|
431
|
+
lastFinitePoint(t, e = 0) {
|
|
432
|
+
const s = this.stores[t]?.getAll() ?? [];
|
|
433
|
+
let i = e;
|
|
434
|
+
for (let n = s.length - 1; n >= 0; n--)
|
|
435
|
+
if (Number.isFinite(s[n].value)) {
|
|
436
|
+
if (i === 0) return s[n];
|
|
437
|
+
i--;
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
427
441
|
getLastValue() {
|
|
428
|
-
for (let
|
|
429
|
-
const
|
|
430
|
-
if (
|
|
442
|
+
for (let t = this.stores.length - 1; t >= 0; t--) {
|
|
443
|
+
const e = this.lastFinitePoint(t);
|
|
444
|
+
if (e) return e.value;
|
|
431
445
|
}
|
|
432
446
|
return null;
|
|
433
447
|
}
|
|
434
|
-
getDataAtTime(
|
|
435
|
-
return this.stores[0]?.findNearest(
|
|
448
|
+
getDataAtTime(t, e) {
|
|
449
|
+
return this.stores[0]?.findNearest(t, e) ?? null;
|
|
436
450
|
}
|
|
437
|
-
getLayerSnapshots(
|
|
451
|
+
getLayerSnapshots(t, e) {
|
|
438
452
|
if (this.stores.length <= 1) return null;
|
|
439
453
|
const s = [];
|
|
440
|
-
for (let
|
|
441
|
-
if (!this.stores[
|
|
442
|
-
const r = this.stores[
|
|
454
|
+
for (let n = 0; n < this.stores.length; n++) {
|
|
455
|
+
if (!this.stores[n].isVisible()) continue;
|
|
456
|
+
const r = this.stores[n].findNearest(t, e);
|
|
443
457
|
if (r === null) continue;
|
|
444
|
-
const
|
|
445
|
-
layerIndex:
|
|
458
|
+
const o = {
|
|
459
|
+
layerIndex: n,
|
|
446
460
|
time: r.time,
|
|
447
461
|
// Routes through the same live/pinned-chase smoothing the renderer
|
|
448
462
|
// paints, so hovering the live edge mid-animation doesn't jump ahead
|
|
449
463
|
// to the raw target before the on-screen marker gets there.
|
|
450
|
-
value: this.snapshotValue(
|
|
464
|
+
value: this.snapshotValue(n, r.time, r.value),
|
|
451
465
|
// Resolve the layer's color at the hovered datum's value so a value-fn
|
|
452
466
|
// colors the tooltip dot to match the bar / segment under the cursor.
|
|
453
|
-
color: this.resolveLayerColor(
|
|
467
|
+
color: this.resolveLayerColor(n, r.value)
|
|
454
468
|
};
|
|
455
|
-
r.x !== void 0 && (
|
|
469
|
+
r.x !== void 0 && (o.x = r.x), s.push(o);
|
|
456
470
|
}
|
|
457
|
-
|
|
471
|
+
const i = this.withShares({ rows: s, keepGaps: !1 });
|
|
472
|
+
return i.length > 0 ? i : null;
|
|
458
473
|
}
|
|
459
474
|
getStackedLastValue() {
|
|
460
475
|
if (this.stores.length <= 1) {
|
|
461
|
-
const
|
|
462
|
-
return
|
|
476
|
+
const i = this.lastFinitePoint(0);
|
|
477
|
+
return i ? { value: this.snapshotValue(0, i.time, i.value), isLive: !0 } : null;
|
|
463
478
|
}
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
466
|
-
for (let
|
|
467
|
-
if (!this.stores[
|
|
468
|
-
const n = this.
|
|
469
|
-
if (n) return { value: this.snapshotValue(
|
|
479
|
+
const t = this.options.stacking;
|
|
480
|
+
if (t === "off") {
|
|
481
|
+
for (let i = this.stores.length - 1; i >= 0; i--) {
|
|
482
|
+
if (!this.stores[i].isVisible()) continue;
|
|
483
|
+
const n = this.lastFinitePoint(i);
|
|
484
|
+
if (n) return { value: this.snapshotValue(i, n.time, n.value), isLive: !0 };
|
|
470
485
|
}
|
|
471
486
|
return null;
|
|
472
487
|
}
|
|
473
|
-
|
|
488
|
+
const e = this.stackHeadValues((i) => this.stores[i].last() ?? null) ?? this.lastDrawnRowValues();
|
|
489
|
+
if (!e) return null;
|
|
490
|
+
const s = w(e);
|
|
491
|
+
return { value: t === "percent" ? A(s) : k(s), isLive: !0 };
|
|
492
|
+
}
|
|
493
|
+
/** Stack contributions at the newest time any visible layer has a finite point. */
|
|
494
|
+
lastDrawnRowValues() {
|
|
495
|
+
let t = null;
|
|
496
|
+
for (let s = 0; s < this.stores.length; s++) {
|
|
497
|
+
if (!this.stores[s].isVisible()) continue;
|
|
498
|
+
const i = this.lastFinitePoint(s);
|
|
499
|
+
i !== null && (t === null || i.time > t) && (t = i.time);
|
|
500
|
+
}
|
|
501
|
+
if (t === null) return null;
|
|
502
|
+
const e = t;
|
|
503
|
+
return this.stackHeadValues((s) => this.stores[s].findNearest(e, 0));
|
|
504
|
+
}
|
|
505
|
+
/** Each visible layer's stack contribution at `pick(layer)`, or `null` when none is finite. */
|
|
506
|
+
stackHeadValues(t) {
|
|
507
|
+
let e = !1;
|
|
474
508
|
const s = [];
|
|
475
|
-
for (let
|
|
476
|
-
if (!this.stores[
|
|
477
|
-
const n =
|
|
478
|
-
if (!n) {
|
|
509
|
+
for (let i = 0; i < this.stores.length; i++) {
|
|
510
|
+
if (!this.stores[i].isVisible()) continue;
|
|
511
|
+
const n = t(i);
|
|
512
|
+
if (!n || !Number.isFinite(n.value)) {
|
|
479
513
|
s.push(0);
|
|
480
514
|
continue;
|
|
481
515
|
}
|
|
482
|
-
|
|
516
|
+
e = !0, s.push(this.snapshotValue(i, n.time, n.value));
|
|
483
517
|
}
|
|
484
|
-
|
|
485
|
-
const i = A(s);
|
|
486
|
-
return { value: e === "percent" ? b(i) : w(i), isLive: !0 };
|
|
518
|
+
return e ? s : null;
|
|
487
519
|
}
|
|
488
|
-
getStackedValueAtTime(
|
|
520
|
+
getStackedValueAtTime(t, e) {
|
|
489
521
|
if (this.stores.length <= 1) {
|
|
490
|
-
const o = this.stores[0]?.findNearest(
|
|
522
|
+
const o = this.stores[0]?.findNearest(t, e);
|
|
491
523
|
return o ? this.snapshotValue(0, o.time, o.value) : null;
|
|
492
524
|
}
|
|
493
525
|
const s = this.options.stacking;
|
|
494
526
|
if (s === "off") {
|
|
495
527
|
for (let o = this.stores.length - 1; o >= 0; o--) {
|
|
496
528
|
if (!this.stores[o].isVisible()) continue;
|
|
497
|
-
const l = this.stores[o].findNearest(
|
|
529
|
+
const l = this.stores[o].findNearest(t, e);
|
|
498
530
|
if (l) return this.snapshotValue(o, l.time, l.value);
|
|
499
531
|
}
|
|
500
532
|
return null;
|
|
501
533
|
}
|
|
502
534
|
let i = !1;
|
|
503
|
-
const
|
|
535
|
+
const n = [];
|
|
504
536
|
for (let o = 0; o < this.stores.length; o++) {
|
|
505
537
|
if (!this.stores[o].isVisible()) continue;
|
|
506
|
-
const l = this.stores[o].findNearest(
|
|
538
|
+
const l = this.stores[o].findNearest(t, e);
|
|
507
539
|
if (!l) {
|
|
508
|
-
|
|
540
|
+
n.push(0);
|
|
509
541
|
continue;
|
|
510
542
|
}
|
|
511
|
-
i = !0,
|
|
543
|
+
i = !0, n.push(this.snapshotValue(o, l.time, l.value));
|
|
512
544
|
}
|
|
513
545
|
if (!i) return null;
|
|
514
|
-
const
|
|
515
|
-
return s === "percent" ?
|
|
546
|
+
const r = w(n);
|
|
547
|
+
return s === "percent" ? A(r) : k(r);
|
|
516
548
|
}
|
|
517
549
|
getLayerLastSnapshots() {
|
|
518
550
|
if (this.stores.length <= 1) return null;
|
|
519
|
-
const
|
|
520
|
-
for (let
|
|
521
|
-
if (!this.stores[
|
|
522
|
-
const
|
|
523
|
-
if (!
|
|
524
|
-
const
|
|
525
|
-
layerIndex:
|
|
526
|
-
time:
|
|
527
|
-
value: this.snapshotValue(
|
|
528
|
-
color: this.resolveLayerColor(
|
|
551
|
+
const t = [];
|
|
552
|
+
for (let s = 0; s < this.stores.length; s++) {
|
|
553
|
+
if (!this.stores[s].isVisible()) continue;
|
|
554
|
+
const i = this.lastFinitePoint(s);
|
|
555
|
+
if (!i) continue;
|
|
556
|
+
const n = {
|
|
557
|
+
layerIndex: s,
|
|
558
|
+
time: i.time,
|
|
559
|
+
value: this.snapshotValue(s, i.time, i.value),
|
|
560
|
+
color: this.resolveLayerColor(s, i.value)
|
|
529
561
|
};
|
|
530
|
-
|
|
562
|
+
i.x !== void 0 && (n.x = i.x), t.push(n);
|
|
531
563
|
}
|
|
564
|
+
const e = this.withShares({ rows: t, keepGaps: !0 });
|
|
532
565
|
return e.length > 0 ? e : null;
|
|
533
566
|
}
|
|
567
|
+
/**
|
|
568
|
+
* On a percent stack, give each row its share of the column at the row's own
|
|
569
|
+
* time — ragged layers end at different columns. A row whose column is a gap
|
|
570
|
+
* is dropped, or kept without a share when `keepGaps`.
|
|
571
|
+
*/
|
|
572
|
+
withShares(t) {
|
|
573
|
+
if (this.options.stacking !== "percent") return t.rows;
|
|
574
|
+
const e = /* @__PURE__ */ new Map(), s = [];
|
|
575
|
+
for (const i of t.rows) {
|
|
576
|
+
let n = e.get(i.time);
|
|
577
|
+
n === void 0 && (n = this.percentShares(i.time), e.set(i.time, n));
|
|
578
|
+
const r = n?.get(i.layerIndex);
|
|
579
|
+
r !== void 0 && (i.share = r), (r !== void 0 || t.keepGaps) && s.push(i);
|
|
580
|
+
}
|
|
581
|
+
return s;
|
|
582
|
+
}
|
|
534
583
|
getTotalLength() {
|
|
535
|
-
let
|
|
536
|
-
for (const
|
|
537
|
-
return
|
|
584
|
+
let t = 0;
|
|
585
|
+
for (const e of this.stores) t += e.length;
|
|
586
|
+
return t;
|
|
538
587
|
}
|
|
539
588
|
// --- TimeSeriesRenderer data queries --------------------------------------
|
|
540
589
|
/** Earliest first-sample / latest last-sample across ALL layers, skipping
|
|
@@ -542,64 +591,63 @@ class F {
|
|
|
542
591
|
* the multi-layer time-range leak: a populated layer 1 with an empty layer 0
|
|
543
592
|
* still reports the real bounds. */
|
|
544
593
|
getTimeBounds() {
|
|
545
|
-
let
|
|
594
|
+
let t = 1 / 0, e = -1 / 0;
|
|
546
595
|
for (const s of this.stores) {
|
|
547
596
|
const i = s.first();
|
|
548
597
|
if (!i) continue;
|
|
549
|
-
const
|
|
550
|
-
i.time <
|
|
598
|
+
const n = s.last() ?? i;
|
|
599
|
+
i.time < t && (t = i.time), n.time > e && (e = n.time);
|
|
551
600
|
}
|
|
552
|
-
return
|
|
601
|
+
return t === 1 / 0 ? null : { first: t, last: e };
|
|
553
602
|
}
|
|
554
603
|
/**
|
|
555
|
-
* Last data point of **layer 0 (`stores[0]`) only** — preserves the behavior
|
|
604
|
+
* Last finite data point of **layer 0 (`stores[0]`) only** — preserves the behavior
|
|
556
605
|
* of the deleted `get store()` getter. NOTE: this is intentionally NOT the
|
|
557
606
|
* same as {@link getStackedLastValue}, which for non-stacked multi-layer
|
|
558
607
|
* reports the top visible layer and for stacked reports the cumulative sum.
|
|
559
608
|
* Last-data / previous-close semantics stay pinned to layer 0 for back-compat.
|
|
560
609
|
*/
|
|
561
610
|
getLastDataPoint() {
|
|
562
|
-
return this.
|
|
611
|
+
return this.lastFinitePoint(0);
|
|
563
612
|
}
|
|
564
613
|
/** Second-to-last point of layer 0 (`stores[0]`). See {@link getLastDataPoint}. */
|
|
565
614
|
getSecondLastDataPoint() {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return s.slice(0, e).map((i) => i.time);
|
|
615
|
+
return this.lastFinitePoint(0, 1);
|
|
616
|
+
}
|
|
617
|
+
/** First (or with `'end'`, last) `maxCount` sample times from the first populated layer. */
|
|
618
|
+
sampleTimes(t, e = "start") {
|
|
619
|
+
for (const s of this.stores) {
|
|
620
|
+
const i = s.getAll();
|
|
621
|
+
if (i.length !== 0)
|
|
622
|
+
return M(i, { maxCount: t, from: e }).map((n) => n.time);
|
|
575
623
|
}
|
|
576
624
|
return [];
|
|
577
625
|
}
|
|
578
626
|
/** Visible points of layer 0 (`stores[0]`). See {@link getLastDataPoint}. */
|
|
579
|
-
getVisibleDataPoints(
|
|
580
|
-
return this.stores[0]?.getVisibleData(
|
|
627
|
+
getVisibleDataPoints(t, e) {
|
|
628
|
+
return this.stores[0]?.getVisibleData(t, e) ?? [];
|
|
581
629
|
}
|
|
582
630
|
get stacking() {
|
|
583
631
|
return this.options.stacking;
|
|
584
632
|
}
|
|
585
|
-
getValueRange(
|
|
633
|
+
getValueRange(t, e, s) {
|
|
586
634
|
const i = this.options.stacking;
|
|
587
635
|
if (i === "percent")
|
|
588
636
|
return { min: 0, max: 100 };
|
|
589
|
-
const
|
|
637
|
+
const n = this.stores.map((a) => a.isVisible() ? a.getVisibleData(t, e) : []), r = s?.plottable;
|
|
590
638
|
if (i === "off") {
|
|
591
639
|
let a = 1 / 0, c = -1 / 0;
|
|
592
|
-
for (const f of
|
|
640
|
+
for (const f of n)
|
|
593
641
|
for (const h of f)
|
|
594
|
-
Number.isFinite(h.value) && (
|
|
642
|
+
Number.isFinite(h.value) && (r && !r(h.value) || (h.value < a && (a = h.value), h.value > c && (c = h.value)));
|
|
595
643
|
return a < 1 / 0 ? { min: a, max: c } : null;
|
|
596
644
|
}
|
|
597
|
-
if (
|
|
645
|
+
if (r) return T(n, r);
|
|
598
646
|
const o = /* @__PURE__ */ new Map();
|
|
599
|
-
for (let a = 0; a <
|
|
600
|
-
for (const c of
|
|
647
|
+
for (let a = 0; a < n.length; a++)
|
|
648
|
+
for (const c of n[a]) {
|
|
601
649
|
let f = o.get(c.time);
|
|
602
|
-
f || (f = new Array(
|
|
650
|
+
f || (f = new Array(n.length).fill(0), o.set(c.time, f)), f[a] = Number.isFinite(c.value) ? c.value : 0;
|
|
603
651
|
}
|
|
604
652
|
let l = 0, u = 0;
|
|
605
653
|
for (const a of o.values()) {
|
|
@@ -611,26 +659,26 @@ class F {
|
|
|
611
659
|
return u > l ? { min: l, max: u } : null;
|
|
612
660
|
}
|
|
613
661
|
}
|
|
614
|
-
function
|
|
615
|
-
const
|
|
616
|
-
for (let
|
|
617
|
-
for (const o of m[
|
|
618
|
-
let l =
|
|
619
|
-
l || (l = new Array(m.length).fill(0),
|
|
662
|
+
function T(m, t) {
|
|
663
|
+
const e = /* @__PURE__ */ new Map();
|
|
664
|
+
for (let r = 0; r < m.length; r++)
|
|
665
|
+
for (const o of m[r]) {
|
|
666
|
+
let l = e.get(o.time);
|
|
667
|
+
l || (l = new Array(m.length).fill(0), e.set(o.time, l)), l[r] = t(o.value) ? o.value : 0;
|
|
620
668
|
}
|
|
621
|
-
const s =
|
|
622
|
-
let i = s ? 0 : 1 / 0,
|
|
623
|
-
for (const
|
|
669
|
+
const s = t(0);
|
|
670
|
+
let i = s ? 0 : 1 / 0, n = s ? 0 : -1 / 0;
|
|
671
|
+
for (const r of e.values()) {
|
|
624
672
|
let o = 0, l = 0;
|
|
625
|
-
for (const u of
|
|
673
|
+
for (const u of r) {
|
|
626
674
|
if (u === 0) continue;
|
|
627
675
|
u > 0 ? o += u : l += u;
|
|
628
676
|
const a = u > 0 ? o : l;
|
|
629
|
-
|
|
677
|
+
t(a) && (a < i && (i = a), a > n && (n = a));
|
|
630
678
|
}
|
|
631
679
|
}
|
|
632
|
-
return i < 1 / 0 ? { min: i, max:
|
|
680
|
+
return i < 1 / 0 ? { min: i, max: n } : null;
|
|
633
681
|
}
|
|
634
682
|
export {
|
|
635
|
-
|
|
683
|
+
I as BaseMultiLayerSeries
|
|
636
684
|
};
|