@wick-charts/vue 0.6.2 → 0.6.4
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 -2
- package/dist/BandSeries.vue.js +50 -0
- package/dist/BandSeries.vue2.js +4 -0
- package/dist/ChartContainer.vue.js +45 -39
- package/dist/composables.js +39 -26
- package/dist/core/dist/animation/config.js +92 -75
- package/dist/core/dist/axis/dom-labels.js +56 -43
- package/dist/core/dist/chart/badge-animator.js +30 -22
- package/dist/core/dist/chart/edge-indicators.js +22 -21
- package/dist/core/dist/chart/last-value.js +33 -26
- package/dist/core/dist/chart/options.js +5 -5
- package/dist/core/dist/chart/scale-guards.js +26 -0
- package/dist/core/dist/chart/y-target.js +46 -19
- package/dist/core/dist/chart.js +670 -518
- package/dist/core/dist/components/crosshair.js +8 -8
- package/dist/core/dist/components/grid.js +26 -17
- package/dist/core/dist/components/marker.js +28 -28
- package/dist/core/dist/components/reference-line.js +16 -16
- package/dist/core/dist/data/decimation.js +58 -39
- package/dist/core/dist/interactions/handler.js +3 -2
- package/dist/core/dist/interactions/zoom.js +6 -5
- package/dist/core/dist/navigator/controller.js +47 -44
- package/dist/core/dist/perf/perf-hud.js +3 -3
- package/dist/core/dist/scales/log-scale.js +76 -0
- package/dist/core/dist/scales/y-scale.js +134 -35
- package/dist/core/dist/series/band.js +670 -0
- package/dist/core/dist/series/bar-intro.js +11 -11
- package/dist/core/dist/series/bar.js +301 -251
- package/dist/core/dist/series/base-multi-layer.js +100 -76
- package/dist/core/dist/series/candlestick.js +238 -235
- package/dist/core/dist/series/heatmap.js +5 -5
- package/dist/core/dist/series/line-intro.js +50 -40
- package/dist/core/dist/series/line.js +561 -483
- package/dist/core/dist/series/painters/builtins.js +4 -4
- package/dist/core/dist/series/painters/canvas-path.js +64 -52
- package/dist/core/dist/series/register-builtins.js +9 -8
- package/dist/core/dist/series/scale-stack.js +25 -0
- package/dist/core/dist/snapshots.js +36 -35
- package/dist/core/dist/tooltip-position.js +5 -5
- package/dist/core/dist/utils/point.js +10 -0
- package/dist/index.d.ts +501 -128
- package/dist/index.js +195 -189
- package/dist/ui/Crosshair.vue.js +14 -14
- package/dist/ui/HeatmapTooltip.vue.js +6 -6
- package/dist/ui/InfoBar.vue.js +100 -81
- package/dist/ui/PieTooltip.vue.js +4 -4
- package/dist/ui/TimeAxis.vue.js +23 -22
- package/dist/ui/Tooltip.vue.js +140 -111
- package/dist/ui/YAxis.vue.js +25 -24
- package/dist/ui/YLabel.vue.js +63 -70
- package/package.json +2 -2
- package/src/BandSeries.vue +87 -0
- package/src/ChartContainer.vue +20 -8
- package/src/composables.ts +35 -1
- package/src/index.ts +13 -0
- package/src/ui/Crosshair.vue +2 -2
- package/src/ui/InfoBar.vue +20 -1
- package/src/ui/TimeAxis.vue +7 -4
- package/src/ui/Tooltip.vue +40 -3
- package/src/ui/YAxis.vue +5 -3
- package/src/ui/YLabel.vue +5 -16
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
import { Animator as I } from "../animation/animator.js";
|
|
2
|
+
import { DEFAULT_HISTORY_REVEAL as F, DEFAULT_BAND_INTRO as z, DEFAULT_BAND_SMOOTH as _, DEFAULT_BAND_ENTRY as O } from "../animation/config.js";
|
|
3
|
+
import { easeOutCubic as $ } from "../animation/easing.js";
|
|
4
|
+
import { IntroWave as D } from "../animation/intro-wave.js";
|
|
5
|
+
import { ScalarSpring as N } from "../animation/scalar-spring.js";
|
|
6
|
+
import { decimateBandData as Y } from "../data/decimation.js";
|
|
7
|
+
import { TimeSeriesStore as H } from "../data/store.js";
|
|
8
|
+
import { resolveColor as U, hexToRgba as A } from "../utils/color.js";
|
|
9
|
+
import { lerp as P } from "../utils/math.js";
|
|
10
|
+
import { normalizeTime as W } from "../utils/time.js";
|
|
11
|
+
import { unfoldIntro as J, scaleMean as j, backfillSweepIntro as G } from "./line-intro.js";
|
|
12
|
+
import { buildCurveSegments as B, replaySegmentsForward as R, replaySegmentsReversed as Q } from "./painters/canvas-path.js";
|
|
13
|
+
const Z = {
|
|
14
|
+
colors: ["#2962FF"],
|
|
15
|
+
fill: void 0,
|
|
16
|
+
stroke: { visible: !0, width: 1, color: void 0 },
|
|
17
|
+
curve: "straight",
|
|
18
|
+
entryAnimation: "grow",
|
|
19
|
+
entryMs: O,
|
|
20
|
+
smoothMs: _,
|
|
21
|
+
introMs: z,
|
|
22
|
+
historyRevealMs: F,
|
|
23
|
+
introAnimation: void 0,
|
|
24
|
+
historyReveal: void 0
|
|
25
|
+
}, q = 0.4, K = 0.04, E = 0.6, tt = G();
|
|
26
|
+
function S(l, t) {
|
|
27
|
+
return l === !1 ? 0 : l ?? t;
|
|
28
|
+
}
|
|
29
|
+
function V(l, t) {
|
|
30
|
+
const i = t.stroke;
|
|
31
|
+
return {
|
|
32
|
+
colors: t.colors ?? l.colors,
|
|
33
|
+
fill: "fill" in t ? t.fill : l.fill,
|
|
34
|
+
stroke: {
|
|
35
|
+
visible: i?.visible ?? l.stroke.visible,
|
|
36
|
+
width: i?.width ?? l.stroke.width,
|
|
37
|
+
color: i !== void 0 && "color" in i ? i.color : l.stroke.color
|
|
38
|
+
},
|
|
39
|
+
curve: t.curve ?? l.curve,
|
|
40
|
+
entryAnimation: t.entryAnimation ?? l.entryAnimation,
|
|
41
|
+
entryMs: S(t.entryMs, l.entryMs),
|
|
42
|
+
smoothMs: S(t.smoothMs, l.smoothMs),
|
|
43
|
+
introMs: S(t.introMs, l.introMs),
|
|
44
|
+
historyRevealMs: S(t.historyRevealMs, l.historyRevealMs),
|
|
45
|
+
introAnimation: "introAnimation" in t ? t.introAnimation : l.introAnimation,
|
|
46
|
+
historyReveal: "historyReveal" in t ? t.historyReveal : l.historyReveal
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const et = (l, t, i) => l + (t - l) * i;
|
|
50
|
+
function L(l) {
|
|
51
|
+
return l <= 0 ? 0 : l >= 1 ? 1 : l;
|
|
52
|
+
}
|
|
53
|
+
function T(l) {
|
|
54
|
+
const t = W(l.time), { low: i, high: e } = l;
|
|
55
|
+
return !Number.isFinite(i) || !Number.isFinite(e) ? { ...l, time: t, low: Number.NaN, high: Number.NaN } : { ...l, time: t, low: Math.min(i, e), high: Math.max(i, e) };
|
|
56
|
+
}
|
|
57
|
+
function b(l) {
|
|
58
|
+
return !Number.isFinite(l.low);
|
|
59
|
+
}
|
|
60
|
+
function x(l) {
|
|
61
|
+
return { low: l.low, high: l.high };
|
|
62
|
+
}
|
|
63
|
+
function it(l) {
|
|
64
|
+
const { upperSegs: t, lowerSegs: i } = l;
|
|
65
|
+
for (let e = 0; e < t.length; e++) {
|
|
66
|
+
const o = t[e], s = i[e];
|
|
67
|
+
if (o.kind !== "bezier" || s.kind !== "bezier") continue;
|
|
68
|
+
const n = C(o.cp1y, s.cp1y), a = C(o.cp2y, s.cp2y);
|
|
69
|
+
t[e] = { ...o, cp1y: n.top, cp2y: a.top }, i[e] = { ...s, cp1y: n.bottom, cp2y: a.bottom };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function C(l, t) {
|
|
73
|
+
if (l <= t) return { top: l, bottom: t };
|
|
74
|
+
const i = (l + t) / 2;
|
|
75
|
+
return { top: i, bottom: i };
|
|
76
|
+
}
|
|
77
|
+
class ot {
|
|
78
|
+
store = new H();
|
|
79
|
+
/** Entrance start wall-time per appended point, keyed by point time. */
|
|
80
|
+
entries = /* @__PURE__ */ new Map();
|
|
81
|
+
alpha = new I({ initial: 1, duration: 0, lerp: et });
|
|
82
|
+
/** Displayed edges of the last point; the chase moves them toward the stored ones. */
|
|
83
|
+
displayedLast = null;
|
|
84
|
+
chase = null;
|
|
85
|
+
/**
|
|
86
|
+
* A chase displaced from the last slot by an append, still settling on that
|
|
87
|
+
* (now penultimate) point. Dropping it would snap that vertex the frame the
|
|
88
|
+
* new point lands.
|
|
89
|
+
*/
|
|
90
|
+
pinned = null;
|
|
91
|
+
}
|
|
92
|
+
class st {
|
|
93
|
+
kind = "band";
|
|
94
|
+
#e;
|
|
95
|
+
#t;
|
|
96
|
+
/** Per-layer colors carried in `data` (sparse); kept apart so a theme swap leaves them alone. */
|
|
97
|
+
#p = [];
|
|
98
|
+
/** Initial-load reveal, armed once on the empty → non-empty seed and shared by every layer. */
|
|
99
|
+
#i = new D();
|
|
100
|
+
/** History-prepend reveal, armed by {@link prependPoints}. */
|
|
101
|
+
#o = new D();
|
|
102
|
+
/** Prepended span: `from` = deepest new point, `to` = the pre-prepend first point. */
|
|
103
|
+
#s = null;
|
|
104
|
+
#a = null;
|
|
105
|
+
#n = null;
|
|
106
|
+
#h = null;
|
|
107
|
+
/** True while a ghost pre-pass draws — edges only, no fill. */
|
|
108
|
+
#c = !1;
|
|
109
|
+
constructor(t, i) {
|
|
110
|
+
this.#t = Array.from({ length: Math.max(1, t) }, () => new ot()), this.#e = V(Z, i ?? {});
|
|
111
|
+
}
|
|
112
|
+
updateOptions(t) {
|
|
113
|
+
this.#e = V(this.#e, t);
|
|
114
|
+
}
|
|
115
|
+
applyTheme(t) {
|
|
116
|
+
this.updateOptions({ colors: X(t, this.#t.length) });
|
|
117
|
+
}
|
|
118
|
+
// --- Data ingest ----------------------------------------------------------
|
|
119
|
+
setData(t, i = 0) {
|
|
120
|
+
const e = this.#t[i];
|
|
121
|
+
if (!e) return;
|
|
122
|
+
const o = this.#t.some((a) => a.store.length > 0), s = (t ?? []).map(T);
|
|
123
|
+
e.store.setData(s), !o && s.length > 0 && this.#i.arm(this.#e.introMs);
|
|
124
|
+
const n = e.store.last();
|
|
125
|
+
e.displayedLast = n ? x(n) : null, e.chase = null, e.pinned = null, e.entries.clear(), this.#o.finish(), this.#s = null;
|
|
126
|
+
}
|
|
127
|
+
appendPoint(t, i = 0) {
|
|
128
|
+
const e = this.#t[i];
|
|
129
|
+
if (!e) return;
|
|
130
|
+
const o = T(t), s = e.store.last(), n = e.store.length;
|
|
131
|
+
e.store.append(o);
|
|
132
|
+
const a = e.store.length > n;
|
|
133
|
+
e.chase !== null && s !== void 0 && a && (e.pinned = { time: s.time, chase: e.chase }), e.displayedLast = x(o), e.chase = null, this.#e.entryAnimation !== "none" && this.#e.entryMs > 0 && e.entries.set(o.time, performance.now());
|
|
134
|
+
}
|
|
135
|
+
prependPoints(t, i = 0) {
|
|
136
|
+
const e = this.#t[i];
|
|
137
|
+
if (!e || t.length === 0) return;
|
|
138
|
+
const o = t.map(T), s = e.store.first()?.time;
|
|
139
|
+
e.store.prepend(o), this.#A(o[0]?.time, s);
|
|
140
|
+
}
|
|
141
|
+
/** While a reveal is in flight only the range grows — a restart would hide half-revealed points again. */
|
|
142
|
+
#A(t, i) {
|
|
143
|
+
const e = this.#e.historyRevealMs;
|
|
144
|
+
if (!(this.#e.historyReveal === "none" || e <= 0) && !(t === void 0 || i === void 0)) {
|
|
145
|
+
if (this.#o.active && this.#s !== null) {
|
|
146
|
+
this.#s = { from: Math.min(this.#s.from, t), to: this.#s.to };
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.#s = { from: t, to: i }, this.#o.arm(e);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
updateLastPoint(t, i = 0) {
|
|
153
|
+
const e = this.#t[i];
|
|
154
|
+
if (!e) return;
|
|
155
|
+
const o = T(t);
|
|
156
|
+
e.store.updateLast(o);
|
|
157
|
+
const s = this.#e.smoothMs;
|
|
158
|
+
if (s <= 0 || b(o)) {
|
|
159
|
+
e.displayedLast = x(o), e.chase = null;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (e.chase === null) {
|
|
163
|
+
const n = e.displayedLast, a = n !== null && Number.isFinite(n.low) ? n : x(o);
|
|
164
|
+
e.chase = { low: new N(a.low), high: new N(a.high) };
|
|
165
|
+
}
|
|
166
|
+
e.chase.low.retarget(o.low, { settleMs: s }), e.chase.high.retarget(o.high, { settleMs: s });
|
|
167
|
+
}
|
|
168
|
+
keepLast(t, i = 0) {
|
|
169
|
+
const e = this.#t[i];
|
|
170
|
+
if (!e || t < 0) return;
|
|
171
|
+
const o = e.store.length - t;
|
|
172
|
+
o <= 0 || e.store.trimStart(o);
|
|
173
|
+
}
|
|
174
|
+
// --- Layer model ----------------------------------------------------------
|
|
175
|
+
getLayerCount() {
|
|
176
|
+
return this.#t.length;
|
|
177
|
+
}
|
|
178
|
+
setLayerVisible(t, i) {
|
|
179
|
+
this.#t[t]?.store.setVisible(i);
|
|
180
|
+
}
|
|
181
|
+
isLayerVisible(t) {
|
|
182
|
+
return this.#t[t]?.store.isVisible() ?? !0;
|
|
183
|
+
}
|
|
184
|
+
setColorOverrides(t) {
|
|
185
|
+
this.#p = t;
|
|
186
|
+
}
|
|
187
|
+
/** Representative color per layer — a value-fn resolves at the layer's last midpoint. */
|
|
188
|
+
getLayerColors() {
|
|
189
|
+
return this.#t.map((t, i) => this.#r(i));
|
|
190
|
+
}
|
|
191
|
+
#r(t) {
|
|
192
|
+
const i = this.#p[t], e = this.#e.colors, o = i ?? e[t % e.length], s = this.#t[t]?.store.last(), n = s !== void 0 && !b(s) ? (s.low + s.high) / 2 : 0;
|
|
193
|
+
return U(o, n);
|
|
194
|
+
}
|
|
195
|
+
// --- Animation ------------------------------------------------------------
|
|
196
|
+
/** Advance owned animators. Called by both passes; every clock samples `now`, so a second tick is a no-op. */
|
|
197
|
+
#f(t) {
|
|
198
|
+
this.#i.tick(t), this.#o.tick(t);
|
|
199
|
+
for (const i of this.#t)
|
|
200
|
+
i.alpha.tick(t), this.#S(i, t), this.#T(i, t);
|
|
201
|
+
}
|
|
202
|
+
#S(t, i) {
|
|
203
|
+
const e = t.chase;
|
|
204
|
+
if (e !== null) {
|
|
205
|
+
const s = e.low.tick(i), n = e.high.tick(i);
|
|
206
|
+
t.displayedLast = { low: e.low.current, high: e.high.current }, !s && !n && (t.chase = null);
|
|
207
|
+
}
|
|
208
|
+
const o = t.pinned;
|
|
209
|
+
if (o !== null) {
|
|
210
|
+
const s = o.chase.low.tick(i), n = o.chase.high.tick(i);
|
|
211
|
+
!s && !n && (t.pinned = null);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
#T(t, i) {
|
|
215
|
+
const e = this.#e.entryMs;
|
|
216
|
+
if (e <= 0) {
|
|
217
|
+
t.entries.clear();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
for (const [o, s] of t.entries)
|
|
221
|
+
i - s >= e && t.entries.delete(o);
|
|
222
|
+
}
|
|
223
|
+
#g(t, i) {
|
|
224
|
+
const e = t.entries.get(i), o = this.#e.entryMs;
|
|
225
|
+
return e === void 0 || o <= 0 ? 1 : L((performance.now() - e) / o);
|
|
226
|
+
}
|
|
227
|
+
/** Trailing points whose entrance is unsettled, oldest first; stops at the first settled one. */
|
|
228
|
+
#P(t) {
|
|
229
|
+
const i = t.store.getAll(), e = [];
|
|
230
|
+
for (let o = i.length - 1; o >= 1; o--) {
|
|
231
|
+
const s = this.#g(t, i[o].time);
|
|
232
|
+
if (s >= 1) break;
|
|
233
|
+
e.unshift({ time: i[o].time, progress: s });
|
|
234
|
+
}
|
|
235
|
+
return e;
|
|
236
|
+
}
|
|
237
|
+
get needsAnimation() {
|
|
238
|
+
if (this.#i.active || this.#o.active) return !0;
|
|
239
|
+
for (const t of this.#t)
|
|
240
|
+
if (t.alpha.animating || t.chase !== null || t.pinned !== null || t.entries.size > 0) return !0;
|
|
241
|
+
return !1;
|
|
242
|
+
}
|
|
243
|
+
getIntroFront() {
|
|
244
|
+
return this.#i.sweep();
|
|
245
|
+
}
|
|
246
|
+
cancelEntranceAnimations() {
|
|
247
|
+
this.#i.finish();
|
|
248
|
+
for (const t of this.#t)
|
|
249
|
+
t.entries.clear();
|
|
250
|
+
}
|
|
251
|
+
setAlpha(t, i) {
|
|
252
|
+
for (const e of this.#t)
|
|
253
|
+
e.alpha.setTarget(t, { duration: i });
|
|
254
|
+
}
|
|
255
|
+
/** `1` while any layer is visible or still fading — a binary gate; per-layer alpha is applied at draw time. */
|
|
256
|
+
getAlpha() {
|
|
257
|
+
return this.#t.some((t) => t.alpha.current > 0 || t.alpha.animating) ? 1 : 0;
|
|
258
|
+
}
|
|
259
|
+
setLayerAlpha(t, i, e) {
|
|
260
|
+
this.#t[t]?.alpha.setTarget(i, { duration: e });
|
|
261
|
+
}
|
|
262
|
+
getLayerAlpha(t) {
|
|
263
|
+
return this.#t[t]?.alpha.current ?? 1;
|
|
264
|
+
}
|
|
265
|
+
// --- Geometry -------------------------------------------------------------
|
|
266
|
+
/** Stored edges with the live chase applied: the last point, or the pinned chase on the point an append displaced. */
|
|
267
|
+
#d(t, i) {
|
|
268
|
+
const e = t.store.last();
|
|
269
|
+
if (e !== void 0 && i.time === e.time) return t.displayedLast ?? x(i);
|
|
270
|
+
const o = t.pinned;
|
|
271
|
+
return o !== null && o.time === i.time ? { low: o.chase.low.current, high: o.chase.high.current } : x(i);
|
|
272
|
+
}
|
|
273
|
+
/** The active intro / history value transform for one sample, or `null` when none applies. */
|
|
274
|
+
#R(t, i, e) {
|
|
275
|
+
const o = this.#o.active ? this.#n?.value : void 0, s = this.#s;
|
|
276
|
+
if (o !== void 0 && s !== null && e < s.to) {
|
|
277
|
+
const m = s.to - s.from, f = m > 0 ? L((s.to - e) / m) : 0;
|
|
278
|
+
return (u) => o({ layerIndex: i, time: e, value: u, position: f });
|
|
279
|
+
}
|
|
280
|
+
const n = this.#i.active ? this.#a?.value : void 0;
|
|
281
|
+
if (n === void 0) return null;
|
|
282
|
+
const a = t.timeScale.getRange(), r = a.to - a.from, c = r > 0 ? L((e - a.from) / r) : 0;
|
|
283
|
+
return (m) => n({ layerIndex: i, time: e, value: m, position: c });
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Where a sample draws this frame, or `null` for a gap (non-finite, or an
|
|
287
|
+
* edge the Y scale can't place, stored or as drawn). Edges swap when an
|
|
288
|
+
* animation crosses them, so the polygon never twists.
|
|
289
|
+
*/
|
|
290
|
+
#l(t, i, e) {
|
|
291
|
+
const { timeScale: o, yScale: s } = t;
|
|
292
|
+
if (b(e) || !s.isPlottable(e.low) || !s.isPlottable(e.high)) return null;
|
|
293
|
+
const n = this.#t[i], a = this.#d(n, e), r = this.#R(t, i, e.time), c = r === null ? a.low : r(a.low), m = r === null ? a.high : r(a.high);
|
|
294
|
+
if (!s.isPlottable(c) || !s.isPlottable(m)) return null;
|
|
295
|
+
const f = s.valueToBitmapYExact(c), u = s.valueToBitmapYExact(m);
|
|
296
|
+
return { x: o.timeToBitmapXExact(e.time), top: Math.min(f, u), bottom: Math.max(f, u) };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Rendered vertices of the layer's still-entering tail, keyed by time. Each
|
|
300
|
+
* link grows from the previous link's rendered spot, so a feed faster than
|
|
301
|
+
* `entryMs` keeps every head unfurling instead of snapping older ones.
|
|
302
|
+
*/
|
|
303
|
+
#v(t, i) {
|
|
304
|
+
if (this.#e.entryAnimation !== "grow") return null;
|
|
305
|
+
const e = this.#t[i], o = this.#P(e);
|
|
306
|
+
if (o.length === 0) return null;
|
|
307
|
+
const s = e.store.getAll(), n = s.length - 1 - o.length;
|
|
308
|
+
if (n < 0) return null;
|
|
309
|
+
const a = this.#l(t, i, s[n]);
|
|
310
|
+
if (a === null) return null;
|
|
311
|
+
const r = /* @__PURE__ */ new Map();
|
|
312
|
+
let c = a;
|
|
313
|
+
for (let m = 0; m < o.length; m++) {
|
|
314
|
+
const f = s[n + 1 + m], u = this.#l(t, i, f);
|
|
315
|
+
if (u === null) return null;
|
|
316
|
+
const v = $(o[m].progress), y = {
|
|
317
|
+
x: P(c.x, u.x, v),
|
|
318
|
+
top: P(c.top, u.top, v),
|
|
319
|
+
bottom: P(c.bottom, u.bottom, v)
|
|
320
|
+
};
|
|
321
|
+
r.set(f.time, y), c = y;
|
|
322
|
+
}
|
|
323
|
+
return r;
|
|
324
|
+
}
|
|
325
|
+
// --- Rendering ------------------------------------------------------------
|
|
326
|
+
render(t) {
|
|
327
|
+
this.#f(performance.now());
|
|
328
|
+
const { context: i } = t.scope;
|
|
329
|
+
this.#n = null;
|
|
330
|
+
const e = this.#s;
|
|
331
|
+
if (this.#o.active && e !== null) {
|
|
332
|
+
const n = this.#e.historyReveal ?? tt;
|
|
333
|
+
n !== "none" && (this.#n = n(this.#C(t, e)));
|
|
334
|
+
}
|
|
335
|
+
const o = this.#n;
|
|
336
|
+
this.#y(t, o?.ghostAlpha ?? 0);
|
|
337
|
+
const s = this.#D(t, o, e);
|
|
338
|
+
this.#i.active ? this.#L(t) : (this.#a = null, this.#u(t)), s && i.restore();
|
|
339
|
+
}
|
|
340
|
+
#L(t) {
|
|
341
|
+
const i = (this.#e.introAnimation ?? this.#k())(this.#V(t));
|
|
342
|
+
this.#a = i, this.#y(t, i.ghostAlpha ?? 0);
|
|
343
|
+
const { context: e } = t.scope, { width: o, height: s } = t.scope.bitmapSize, n = i.clip, a = n !== void 0 && (n.fromX !== void 0 || n.toX !== void 0);
|
|
344
|
+
if (a) {
|
|
345
|
+
const r = n.fromX ?? 0, c = n.toX ?? o;
|
|
346
|
+
e.save(), e.beginPath(), e.rect(r, 0, Math.max(0, c - r), s), e.clip();
|
|
347
|
+
}
|
|
348
|
+
this.#u(t), a && e.restore();
|
|
349
|
+
}
|
|
350
|
+
#k() {
|
|
351
|
+
return this.#h === null && (this.#h = J()), this.#h;
|
|
352
|
+
}
|
|
353
|
+
/** Edges-only skeleton under the main pass, so a clip reveal never reads as an empty chart. */
|
|
354
|
+
#y(t, i) {
|
|
355
|
+
if (i <= 0) return;
|
|
356
|
+
const { context: e } = t.scope;
|
|
357
|
+
e.save(), e.globalAlpha *= i, this.#c = !0, this.#u(t), this.#c = !1, e.restore();
|
|
358
|
+
}
|
|
359
|
+
/** History clip, mapped from reveal space (`x = 0` at the data boundary) to bitmap space. */
|
|
360
|
+
#D(t, i, e) {
|
|
361
|
+
const o = i?.clip;
|
|
362
|
+
if (o === void 0 || e === null || o.fromX === void 0 && o.toX === void 0) return !1;
|
|
363
|
+
const { context: s } = t.scope, { width: n, height: a } = t.scope.bitmapSize, r = t.timeScale.timeToBitmapXExact(e.to), c = Math.abs(r - t.timeScale.timeToBitmapXExact(e.from)), m = Math.max(2, this.#e.stroke.width * t.scope.horizontalPixelRatio), f = o.fromX ?? 0, u = o.toX ?? c;
|
|
364
|
+
return s.save(), s.beginPath(), s.rect(r - u - m, 0, Math.max(0, u - f + m), a), s.rect(r, 0, Math.max(0, n - r), a), s.clip(), !0;
|
|
365
|
+
}
|
|
366
|
+
#u(t) {
|
|
367
|
+
for (let i = 0; i < this.#t.length; i++)
|
|
368
|
+
this.#N(t, i);
|
|
369
|
+
}
|
|
370
|
+
#N(t, i) {
|
|
371
|
+
const e = this.#t[i], o = e.alpha.current;
|
|
372
|
+
if (o <= 0) return;
|
|
373
|
+
const { scope: s, timeScale: n } = t, a = n.getRange();
|
|
374
|
+
let r = e.store.getVisibleData(a.from, a.to);
|
|
375
|
+
if (r.length > s.mediaSize.width * 2 && (r = Y(r, Math.round(s.mediaSize.width * 1.5))), r.length === 0) return;
|
|
376
|
+
const c = r[r.length - 1], m = this.#w(e, r), f = m ? this.#v(t, i) : null, u = this.#B(t, { layerIndex: i, data: r, grown: f });
|
|
377
|
+
if (u.length === 0) return;
|
|
378
|
+
const v = m ? this.#g(e, c.time) : 1, y = m && !b(c) && this.#e.entryAnimation === "fade" && v < 1 ? nt(u) : null, { context: h } = s;
|
|
379
|
+
h.save(), o < 1 && (h.globalAlpha *= o);
|
|
380
|
+
const p = this.#r(i), d = { ctx: t, runs: u, color: p, growing: f !== null };
|
|
381
|
+
if (y === null)
|
|
382
|
+
this.#m(d);
|
|
383
|
+
else {
|
|
384
|
+
const { width: g, height: w } = s.bitmapSize;
|
|
385
|
+
h.save(), h.beginPath(), h.rect(0, 0, y, w), h.clip(), this.#m(d), h.restore(), h.save(), h.beginPath(), h.rect(y, 0, Math.max(0, g - y), w), h.clip(), h.globalAlpha *= v, this.#m(d), h.restore();
|
|
386
|
+
}
|
|
387
|
+
h.restore();
|
|
388
|
+
}
|
|
389
|
+
/** Split the visible slice into runs of drawable vertices; a gap ends a run. */
|
|
390
|
+
/**
|
|
391
|
+
* Whether a visible slice ends at the live point. Panned into history, the
|
|
392
|
+
* slice's last point is an interior sample, and the live-tail entrance must not apply.
|
|
393
|
+
*/
|
|
394
|
+
#w(t, i) {
|
|
395
|
+
const e = t.store.last(), o = i[i.length - 1];
|
|
396
|
+
return e !== void 0 && o !== void 0 && o.time === e.time;
|
|
397
|
+
}
|
|
398
|
+
#B(t, i) {
|
|
399
|
+
const { layerIndex: e, data: o, grown: s } = i, n = [];
|
|
400
|
+
let a = null;
|
|
401
|
+
for (const r of o) {
|
|
402
|
+
const c = s?.get(r.time) ?? this.#l(t, e, r);
|
|
403
|
+
if (c === null) {
|
|
404
|
+
a = null;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
a === null && (a = [], n.push(a)), a.push(c);
|
|
408
|
+
}
|
|
409
|
+
return n;
|
|
410
|
+
}
|
|
411
|
+
#m(t) {
|
|
412
|
+
const { ctx: i, runs: e, color: o, growing: s } = t, { context: n, verticalPixelRatio: a } = i.scope, r = this.#e.curve, c = [], m = [];
|
|
413
|
+
for (let h = 0; h < e.length; h++) {
|
|
414
|
+
const p = e[h];
|
|
415
|
+
if (p.length < 2) {
|
|
416
|
+
m.push(p[0]);
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const d = s && h === e.length - 1, g = p.map((M) => ({ x: M.x, y: M.top })), w = p.map((M) => ({ x: M.x, y: M.bottom })), k = {
|
|
420
|
+
upper: g,
|
|
421
|
+
lower: w,
|
|
422
|
+
upperSegs: B(g, r, d),
|
|
423
|
+
lowerSegs: B(w, r, d)
|
|
424
|
+
};
|
|
425
|
+
it(k), c.push(k);
|
|
426
|
+
}
|
|
427
|
+
if (!this.#c && c.length > 0) {
|
|
428
|
+
n.fillStyle = this.#E({ context: n, paths: c, color: o });
|
|
429
|
+
for (const h of c) {
|
|
430
|
+
n.beginPath(), n.moveTo(h.upper[0].x, h.upper[0].y), R(n, h.upperSegs);
|
|
431
|
+
const p = h.lower[h.lower.length - 1];
|
|
432
|
+
n.lineTo(p.x, p.y), Q(n, h.lower, h.lowerSegs), n.closePath(), n.fill();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
const f = this.#e.stroke, u = f.color ?? A(o, E), v = Math.max(1, Math.round(f.width * a)), y = f.visible && f.width > 0;
|
|
436
|
+
if (y && c.length > 0) {
|
|
437
|
+
n.beginPath();
|
|
438
|
+
for (const h of c)
|
|
439
|
+
n.moveTo(h.upper[0].x, h.upper[0].y), R(n, h.upperSegs), n.moveTo(h.lower[0].x, h.lower[0].y), R(n, h.lowerSegs);
|
|
440
|
+
n.strokeStyle = u, n.lineWidth = v, n.lineJoin = "round", n.lineCap = "round", n.stroke();
|
|
441
|
+
}
|
|
442
|
+
if (m.length > 0) {
|
|
443
|
+
n.beginPath();
|
|
444
|
+
for (const h of m)
|
|
445
|
+
n.moveTo(h.x, h.top), n.lineTo(h.x, h.bottom);
|
|
446
|
+
n.strokeStyle = y ? u : A(o, E), n.lineWidth = v, n.lineCap = "round", n.stroke();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/** A gradient fill spans the layer's drawn extent this frame; a degenerate extent paints solid. */
|
|
450
|
+
#E(t) {
|
|
451
|
+
const { context: i, paths: e, color: o } = t, s = this.#e.fill;
|
|
452
|
+
if (typeof s == "string") return s;
|
|
453
|
+
const [n, a] = s ?? [
|
|
454
|
+
A(o, q),
|
|
455
|
+
A(o, K)
|
|
456
|
+
];
|
|
457
|
+
let r = 1 / 0, c = -1 / 0;
|
|
458
|
+
for (const f of e) {
|
|
459
|
+
for (const u of f.upper)
|
|
460
|
+
u.y < r && (r = u.y);
|
|
461
|
+
for (const u of f.lower)
|
|
462
|
+
u.y > c && (c = u.y);
|
|
463
|
+
}
|
|
464
|
+
if (!(c > r + 0.5)) return n;
|
|
465
|
+
const m = i.createLinearGradient(0, r, 0, c);
|
|
466
|
+
return m.addColorStop(0, n), m.addColorStop(1, a), m;
|
|
467
|
+
}
|
|
468
|
+
// --- Intro frames ---------------------------------------------------------
|
|
469
|
+
#V(t) {
|
|
470
|
+
return this.#b(t, {
|
|
471
|
+
progress: this.#i.linear(),
|
|
472
|
+
range: t.timeScale.getRange(),
|
|
473
|
+
// Plot-area extent — the canvas bitmap also carries the axis strips.
|
|
474
|
+
width: t.timeScale.getMediaWidth() * t.scope.horizontalPixelRatio
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
#C(t, i) {
|
|
478
|
+
const { timeScale: e } = t;
|
|
479
|
+
return this.#b(t, {
|
|
480
|
+
progress: this.#o.linear(),
|
|
481
|
+
range: i,
|
|
482
|
+
width: Math.abs(e.timeToBitmapXExact(i.to) - e.timeToBitmapXExact(i.from))
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* The line intro contract, fed each band's center: `layerData` maps every
|
|
487
|
+
* sample to the midpoint of its edges on the Y axis (geometric on log), so
|
|
488
|
+
* `layerMean` is the center the default unfold opens the band out of.
|
|
489
|
+
*/
|
|
490
|
+
#b(t, i) {
|
|
491
|
+
const { timeScale: e, yScale: o, scope: s } = t, { progress: n, range: a, width: r } = i, c = this.#t.length, m = new Array(c), f = new Array(c);
|
|
492
|
+
let u;
|
|
493
|
+
const v = (p) => {
|
|
494
|
+
const d = m[p];
|
|
495
|
+
if (d !== void 0) return d;
|
|
496
|
+
const g = (this.#t[p]?.store.getVisibleData(a.from, a.to) ?? []).map((w) => ({
|
|
497
|
+
time: w.time,
|
|
498
|
+
value: b(w) ? Number.NaN : o.lerpValue(w.low, w.high, 0.5)
|
|
499
|
+
}));
|
|
500
|
+
return m[p] = g, g;
|
|
501
|
+
}, y = (p) => {
|
|
502
|
+
const d = f[p];
|
|
503
|
+
if (d !== void 0) return d;
|
|
504
|
+
const g = j(v(p), o);
|
|
505
|
+
return f[p] = g, g;
|
|
506
|
+
}, h = () => {
|
|
507
|
+
if (u !== void 0) return u;
|
|
508
|
+
u = null;
|
|
509
|
+
for (let p = 0; p < c; p++) {
|
|
510
|
+
if (this.getLayerAlpha(p) <= 0 || !this.isLayerVisible(p)) continue;
|
|
511
|
+
const d = v(p).filter((g) => o.isPlottable(g.value));
|
|
512
|
+
if (!(d.length < 2)) {
|
|
513
|
+
u = d.map((g) => ({
|
|
514
|
+
x: e.timeToBitmapXExact(g.time),
|
|
515
|
+
y: o.valueToBitmapYExact(g.value),
|
|
516
|
+
time: g.time
|
|
517
|
+
}));
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return u;
|
|
522
|
+
};
|
|
523
|
+
return {
|
|
524
|
+
progress: n,
|
|
525
|
+
range: a,
|
|
526
|
+
width: r,
|
|
527
|
+
height: o.getMediaHeight() * s.verticalPixelRatio,
|
|
528
|
+
stacking: "off",
|
|
529
|
+
timeToX: (p) => e.timeToBitmapXExact(p),
|
|
530
|
+
xToTime: (p) => e.xToTime(p / s.horizontalPixelRatio),
|
|
531
|
+
valueToY: (p) => o.valueToBitmapYExact(p),
|
|
532
|
+
lerpValue: (p, d, g) => o.lerpValue(p, d, g),
|
|
533
|
+
layerCount: c,
|
|
534
|
+
layerData: v,
|
|
535
|
+
layerMean: y,
|
|
536
|
+
primaryPath: h
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
// --- Overlay --------------------------------------------------------------
|
|
540
|
+
/** Crosshair dots on both edges of every visible band at the hovered sample. */
|
|
541
|
+
drawOverlay(t) {
|
|
542
|
+
this.#f(performance.now());
|
|
543
|
+
const { crosshair: i, scope: e } = t;
|
|
544
|
+
if (i === null) return;
|
|
545
|
+
const { context: o, horizontalPixelRatio: s } = e, n = 3 * s, a = n + 3 * s;
|
|
546
|
+
for (let r = 0; r < this.#t.length; r++) {
|
|
547
|
+
const c = this.#t[r], m = c.alpha.current;
|
|
548
|
+
if (m <= 0) continue;
|
|
549
|
+
const f = c.store.findNearest(i.time, t.dataInterval);
|
|
550
|
+
if (f === null) continue;
|
|
551
|
+
const u = this.#X(t, r, f);
|
|
552
|
+
if (u === null) continue;
|
|
553
|
+
const v = this.#r(r);
|
|
554
|
+
o.save(), o.globalAlpha *= m, o.fillStyle = v;
|
|
555
|
+
for (const y of [u.top, u.bottom])
|
|
556
|
+
o.save(), o.globalAlpha *= 0.25, o.beginPath(), o.arc(u.x, y, a, 0, Math.PI * 2), o.fill(), o.restore(), o.beginPath(), o.arc(u.x, y, n, 0, Math.PI * 2), o.fill();
|
|
557
|
+
o.restore();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/** The hovered sample where the fill draws it: an entering tail point sits on its grown head. */
|
|
561
|
+
#X(t, i, e) {
|
|
562
|
+
const o = this.#t[i], s = t.timeScale.getRange(), n = o.store.getVisibleData(s.from, s.to);
|
|
563
|
+
return (this.#w(o, n) ? this.#v(t, i) : null)?.get(e.time) ?? this.#l(t, i, e);
|
|
564
|
+
}
|
|
565
|
+
// --- Data queries ---------------------------------------------------------
|
|
566
|
+
/** A sample as a query reports it: the drawn (live-chased) edges, `null` for a gap. */
|
|
567
|
+
#x(t, i) {
|
|
568
|
+
if (b(i)) return null;
|
|
569
|
+
const e = this.#d(t, i);
|
|
570
|
+
return { ...i, low: Math.min(e.low, e.high), high: Math.max(e.low, e.high) };
|
|
571
|
+
}
|
|
572
|
+
getDataAtTime(t, i) {
|
|
573
|
+
const e = this.#t[0], o = e.store.findNearest(t, i);
|
|
574
|
+
return o === null ? null : this.#x(e, o);
|
|
575
|
+
}
|
|
576
|
+
getLayerSnapshots(t, i) {
|
|
577
|
+
return this.#t.length <= 1 ? null : this.#M((e) => e.store.findNearest(t, i));
|
|
578
|
+
}
|
|
579
|
+
getLayerLastSnapshots() {
|
|
580
|
+
return this.#t.length <= 1 ? null : this.#M((t) => t.store.last() ?? null);
|
|
581
|
+
}
|
|
582
|
+
#M(t) {
|
|
583
|
+
const i = [];
|
|
584
|
+
for (let e = 0; e < this.#t.length; e++) {
|
|
585
|
+
const o = this.#t[e];
|
|
586
|
+
if (!o.store.isVisible()) continue;
|
|
587
|
+
const s = t(o), n = s === null ? null : this.#x(o, s);
|
|
588
|
+
n !== null && i.push({
|
|
589
|
+
layerIndex: e,
|
|
590
|
+
time: n.time,
|
|
591
|
+
value: (n.low + n.high) / 2,
|
|
592
|
+
color: this.#r(e),
|
|
593
|
+
low: n.low,
|
|
594
|
+
high: n.high
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
return i.length > 0 ? i : null;
|
|
598
|
+
}
|
|
599
|
+
getTotalLength() {
|
|
600
|
+
let t = 0;
|
|
601
|
+
for (const i of this.#t)
|
|
602
|
+
t += i.store.length;
|
|
603
|
+
return t;
|
|
604
|
+
}
|
|
605
|
+
getTimeBounds() {
|
|
606
|
+
let t = 1 / 0, i = -1 / 0;
|
|
607
|
+
for (const e of this.#t) {
|
|
608
|
+
const o = e.store.first(), s = e.store.last();
|
|
609
|
+
o === void 0 || s === void 0 || (o.time < t && (t = o.time), s.time > i && (i = s.time));
|
|
610
|
+
}
|
|
611
|
+
return t === 1 / 0 ? null : { first: t, last: i };
|
|
612
|
+
}
|
|
613
|
+
/** Last point of layer 0, like the line / bar renderers. */
|
|
614
|
+
getLastDataPoint() {
|
|
615
|
+
return this.#t[0].store.last() ?? null;
|
|
616
|
+
}
|
|
617
|
+
getSecondLastDataPoint() {
|
|
618
|
+
const t = this.#t[0].store.getAll();
|
|
619
|
+
return t.length < 2 ? null : t[t.length - 2];
|
|
620
|
+
}
|
|
621
|
+
sampleTimes(t) {
|
|
622
|
+
for (const i of this.#t) {
|
|
623
|
+
const e = i.store.getAll();
|
|
624
|
+
if (e.length !== 0)
|
|
625
|
+
return e.slice(0, t).map((o) => o.time);
|
|
626
|
+
}
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
getVisibleDataPoints(t, i) {
|
|
630
|
+
return this.#t[0].store.getVisibleData(t, i);
|
|
631
|
+
}
|
|
632
|
+
/** Union of every visible band's edges. A sample counts only when both edges are drawable. */
|
|
633
|
+
getValueRange(t, i, e) {
|
|
634
|
+
const o = e?.plottable;
|
|
635
|
+
let s = 1 / 0, n = -1 / 0;
|
|
636
|
+
for (const a of this.#t)
|
|
637
|
+
if (a.store.isVisible())
|
|
638
|
+
for (const r of a.store.getVisibleData(t, i))
|
|
639
|
+
b(r) || o && (!o(r.low) || !o(r.high)) || (r.low < s && (s = r.low), r.high > n && (n = r.high));
|
|
640
|
+
return s < 1 / 0 ? { min: s, max: n } : null;
|
|
641
|
+
}
|
|
642
|
+
// --- Lifecycle ------------------------------------------------------------
|
|
643
|
+
onDataChanged(t) {
|
|
644
|
+
for (const i of this.#t)
|
|
645
|
+
i.store.on("update", t);
|
|
646
|
+
return () => {
|
|
647
|
+
for (const i of this.#t)
|
|
648
|
+
i.store.off("update", t);
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
dispose() {
|
|
652
|
+
for (const t of this.#t)
|
|
653
|
+
t.store.removeAllListeners();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function X(l, t) {
|
|
657
|
+
return t === 1 ? [l.bands.upper] : l.seriesColors.slice(0, t);
|
|
658
|
+
}
|
|
659
|
+
function nt(l) {
|
|
660
|
+
const t = l[l.length - 1];
|
|
661
|
+
return t.length < 2 ? null : Math.round(t[t.length - 2].x);
|
|
662
|
+
}
|
|
663
|
+
const yt = {
|
|
664
|
+
type: "band",
|
|
665
|
+
create: ({ theme: l, layerCount: t }, i) => new st(t, { colors: X(l, t), ...i })
|
|
666
|
+
};
|
|
667
|
+
export {
|
|
668
|
+
st as BandRenderer,
|
|
669
|
+
yt as BandSeriesDef
|
|
670
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { easeOutBack as
|
|
2
|
-
function
|
|
3
|
-
return () => ({
|
|
4
|
-
value: ({ value: e, progress:
|
|
1
|
+
import { easeOutBack as s } from "../animation/easing.js";
|
|
2
|
+
function n() {
|
|
3
|
+
return (r) => ({
|
|
4
|
+
value: ({ value: e, progress: a }) => r.lerpValue(r.baseline, e, a),
|
|
5
5
|
element: (e) => ({ alpha: e.progress })
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
const
|
|
9
|
-
function
|
|
10
|
-
return () => ({
|
|
11
|
-
value: ({ value: e, progress:
|
|
12
|
-
element: (e) => ({ alpha: Math.min(1, e.progress /
|
|
8
|
+
const l = 0.3;
|
|
9
|
+
function t() {
|
|
10
|
+
return (r) => ({
|
|
11
|
+
value: ({ value: e, progress: a }) => r.lerpValue(r.baseline, e, s(a)),
|
|
12
|
+
element: (e) => ({ alpha: Math.min(1, e.progress / l) })
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
15
|
export {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
n as growIntro,
|
|
17
|
+
t as springIntro
|
|
18
18
|
};
|