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