@wick-charts/vue 0.6.4 → 0.8.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.
Files changed (55) hide show
  1. package/README.md +21 -7
  2. package/dist/BandSeries.vue.js +20 -18
  3. package/dist/BarSeries.vue.js +15 -13
  4. package/dist/CandlestickSeries.vue.js +31 -29
  5. package/dist/ChartContainer.vue.js +77 -70
  6. package/dist/LineSeries.vue.js +19 -17
  7. package/dist/composables.js +46 -36
  8. package/dist/core/dist/animation/viewport-engine.js +155 -96
  9. package/dist/core/dist/axis/dom-labels.js +56 -51
  10. package/dist/core/dist/chart/scale-guards.js +23 -5
  11. package/dist/core/dist/chart/x-guards.js +37 -0
  12. package/dist/core/dist/chart.js +1286 -843
  13. package/dist/core/dist/data/sync.js +37 -31
  14. package/dist/core/dist/navigator/controller.js +37 -37
  15. package/dist/core/dist/perf/perf-hud.js +3 -3
  16. package/dist/core/dist/scales/tick-tracker.js +2 -2
  17. package/dist/core/dist/scales/x-scale.js +167 -53
  18. package/dist/core/dist/scales/y-scale.js +36 -34
  19. package/dist/core/dist/series/band.js +266 -213
  20. package/dist/core/dist/series/bar.js +297 -291
  21. package/dist/core/dist/series/base-multi-layer.js +283 -237
  22. package/dist/core/dist/series/candlestick.js +1 -0
  23. package/dist/core/dist/series/line.js +21 -20
  24. package/dist/core/dist/snapshots.js +55 -43
  25. package/dist/index.d.ts +323 -58
  26. package/dist/index.js +9 -9
  27. package/dist/ui/Crosshair.vue.js +51 -27
  28. package/dist/ui/InfoBar.vue.js +37 -37
  29. package/dist/ui/Marker.vue.js +14 -11
  30. package/dist/ui/Navigator.vue.js +23 -23
  31. package/dist/ui/ReferenceLine.vue.js +19 -16
  32. package/dist/ui/Tooltip.vue.js +91 -86
  33. package/dist/ui/XAxis.vue.js +48 -0
  34. package/dist/ui/XAxis.vue2.js +4 -0
  35. package/dist/ui/YAxis.vue.js +44 -28
  36. package/dist/ui/YLabel.vue.js +78 -74
  37. package/package.json +9 -3
  38. package/src/BandSeries.vue +11 -6
  39. package/src/BarSeries.vue +13 -6
  40. package/src/CandlestickSeries.vue +9 -5
  41. package/src/ChartContainer.vue +20 -0
  42. package/src/LineSeries.vue +13 -6
  43. package/src/composables.ts +23 -4
  44. package/src/index.ts +8 -1
  45. package/src/ui/Crosshair.vue +41 -1
  46. package/src/ui/InfoBar.vue +2 -1
  47. package/src/ui/Marker.vue +5 -1
  48. package/src/ui/Navigator.vue +2 -1
  49. package/src/ui/ReferenceLine.vue +5 -1
  50. package/src/ui/Tooltip.vue +7 -4
  51. package/src/ui/{TimeAxis.vue → XAxis.vue} +18 -5
  52. package/src/ui/YAxis.vue +66 -12
  53. package/src/ui/YLabel.vue +35 -5
  54. package/dist/ui/TimeAxis.vue.js +0 -44
  55. package/dist/ui/TimeAxis.vue2.js +0 -4
@@ -1,65 +1,100 @@
1
- import { Animator as K } from "./animation/animator.js";
2
- import { AnimationConfig as j, DEFAULT_LINE_PULSE as J } from "./animation/config.js";
1
+ import { Animator as nt } from "./animation/animator.js";
2
+ import { AnimationConfig as at, DEFAULT_LINE_PULSE as ot } from "./animation/config.js";
3
3
  import { easeOutCubic as W } from "./animation/easing.js";
4
- import { prefersReducedMotion as Q } from "./animation/reduced-motion.js";
5
- import { createViewportEngine as tt } from "./animation/viewport-engine.js";
6
- import { CanvasManager as it } from "./canvas-manager.js";
7
- import { resolveEdgeAnchorValue as et, drawEdgeIndicators as st, resolveEdgeBoundary as rt } from "./chart/edge-indicators.js";
8
- import { computeFitToData as nt } from "./chart/fit-to-data.js";
9
- import { getLastValue as at, getPreviousClose as ot, getStackedLastValue as ht } from "./chart/last-value.js";
10
- import { resolveFade as O, resolveMaxVisibleBars as lt, resolvePadding as F, resolvePerfOptions as ct, isSameHorizontalPadding as $ } from "./chart/options.js";
11
- import { computePan as dt, computeZoom as ut } from "./chart/pan-zoom-math.js";
12
- import { warnUnplottableBounds as N, assertStackingFitsScale as M, warnUnplottableValues as mt } from "./chart/scale-guards.js";
13
- import { StreamingCadence as ft } from "./chart/streaming-cadence.js";
14
- import { computeStreamingTarget as gt } from "./chart/streaming-target.js";
15
- import { resolvePaddingTime as B } from "./chart/viewport-padding.js";
16
- import { fromScaleSpace as pt, computeTargetYRange as yt, resolveYTarget as vt } from "./chart/y-target.js";
17
- import { renderCrosshair as bt } from "./components/crosshair.js";
18
- import { renderGrid as E } from "./components/grid.js";
19
- import { renderMarker as xt } from "./components/marker.js";
20
- import { renderReferenceLine as St } from "./components/reference-line.js";
21
- import { resolveTimeRegionColors as kt, renderTimeRegion as At } from "./components/time-region.js";
22
- import { EventEmitter as wt } from "./events.js";
23
- import { InteractionHandler as Tt } from "./interactions/handler.js";
24
- import { RenderScheduler as Y } from "./render-scheduler.js";
25
- import { XScale as Lt } from "./scales/x-scale.js";
26
- import { YScale as Mt, isPlottableOn as Pt } from "./scales/y-scale.js";
27
- import { resolveSeriesDefinition as Dt } from "./series/definition.js";
28
- import { isTimeSeriesRenderer as b } from "./series/types.js";
29
- import { catppuccin as Rt } from "./theme/themes/catppuccin.js";
30
- import { clamp as Ct } from "./utils/math.js";
31
- import { crispLineWidth as It, crispCenterOffset as Ht } from "./utils/pixel-grid.js";
32
- import { pointValue as X, isBandPoint as zt } from "./utils/point.js";
33
- import { normalizeTime as x, detectInterval as Vt } from "./utils/time.js";
34
- const Wt = 200, Ot = 1500, Ft = 48, Z = 12, P = 9, $t = ["left", "right"], Nt = ["left"], Bt = ["right"];
35
- let Et = 0, Yt = 0, Xt = 0, Zt = 0;
36
- function _(y, t) {
37
- return y === t ? !0 : !y || !t || y.length !== t.length ? !1 : y.every((i, e) => i === t[e]);
4
+ import { prefersReducedMotion as ht } from "./animation/reduced-motion.js";
5
+ import { createViewportEngine as lt } from "./animation/viewport-engine.js";
6
+ import { CanvasManager as ct } from "./canvas-manager.js";
7
+ import { resolveEdgeAnchorValue as dt, drawEdgeIndicators as ut, resolveEdgeBoundary as mt } from "./chart/edge-indicators.js";
8
+ import { computeFitToData as ft } from "./chart/fit-to-data.js";
9
+ import { getLastValue as gt, getPreviousClose as pt, getStackedLastValue as yt } from "./chart/last-value.js";
10
+ import { resolveFade as Y, resolveMaxVisibleBars as xt, resolvePadding as E, resolvePerfOptions as vt, isSameHorizontalPadding as B } from "./chart/options.js";
11
+ import { computePan as St, computeZoom as bt } from "./chart/pan-zoom-math.js";
12
+ import { warnUnplottableBounds as C, assertStackingFitsScale as L, warnScaleSideTaken as kt, warnUnknownScale as wt, warnStackingOffScale as Tt, warnUnplottableValues as At } from "./chart/scale-guards.js";
13
+ import { StreamingCadence as Mt } from "./chart/streaming-cadence.js";
14
+ import { computeStreamingTarget as Pt } from "./chart/streaming-target.js";
15
+ import { resolvePaddingTime as N } from "./chart/viewport-padding.js";
16
+ import { assertSeriesFitsXAxis as Ct, warnXMisfit as Lt, warnXTimeMixed as Dt, warnTimeOnlyFeature as Rt, warnXTypeChange as It } from "./chart/x-guards.js";
17
+ import { fromScaleSpace as Ht, computeTargetYRange as Vt, resolveYTarget as zt } from "./chart/y-target.js";
18
+ import { renderCrosshair as Ft } from "./components/crosshair.js";
19
+ import { renderGrid as X } from "./components/grid.js";
20
+ import { renderMarker as Ot } from "./components/marker.js";
21
+ import { renderReferenceLine as $t } from "./components/reference-line.js";
22
+ import { resolveTimeRegionColors as Wt, renderTimeRegion as Yt } from "./components/time-region.js";
23
+ import { EventEmitter as Et } from "./events.js";
24
+ import { InteractionHandler as Bt } from "./interactions/handler.js";
25
+ import { RenderScheduler as Z } from "./render-scheduler.js";
26
+ import { XScale as Nt } from "./scales/x-scale.js";
27
+ import { YScale as U, isPlottableOn as _ } from "./scales/y-scale.js";
28
+ import { resolveSeriesDefinition as Xt } from "./series/definition.js";
29
+ import { isTimeSeriesRenderer as v } from "./series/types.js";
30
+ import { catppuccin as Zt } from "./theme/themes/catppuccin.js";
31
+ import { deepEqual as Ut } from "./utils/deep-equal.js";
32
+ import { clamp as _t } from "./utils/math.js";
33
+ import { crispLineWidth as Gt, crispCenterOffset as Kt } from "./utils/pixel-grid.js";
34
+ import { pointValue as G, isBandPoint as jt } from "./utils/point.js";
35
+ import { normalizeTime as k, detectInterval as qt } from "./utils/time.js";
36
+ const Jt = 200, Qt = 1500, te = 48, K = 12, D = 9, ee = ["left", "right"], ie = { entries: [], isAnimating: !1 }, se = [], re = ["left"], ne = ["right"], ae = 55;
37
+ function j(p, t) {
38
+ return {
39
+ yScale: p,
40
+ bounds: { min: t?.min, max: t?.max },
41
+ range: { min: 0, max: 0 },
42
+ prevMin: Number.NaN,
43
+ prevMax: Number.NaN
44
+ };
38
45
  }
39
- function _t(y, t) {
40
- return y === t ? !0 : !y || !t || y.length !== t.length ? !1 : y.every((i, e) => i === t[e]);
46
+ function q(p) {
47
+ return p === "left" ? "right" : "left";
41
48
  }
42
- class Li extends wt {
49
+ function J(p, t) {
50
+ return p === void 0 || p === "auto" ? !1 : !(typeof p == "number" && !t.isPlottable(p));
51
+ }
52
+ let oe = 0, he = 0, le = 0, ce = 0;
53
+ function Q(p, t) {
54
+ return p === t ? !0 : !p || !t || p.length !== t.length ? !1 : p.every((e, i) => e === t[i]);
55
+ }
56
+ function tt(p) {
57
+ return v(p) ? p.stacking : void 0;
58
+ }
59
+ function de(p, t) {
60
+ return p === t ? !0 : !p || !t || p.length !== t.length ? !1 : p.every((e, i) => e === t[i]);
61
+ }
62
+ class _e extends Et {
43
63
  /** Canvas lifecycle and DPR-aware sizing. */
44
- #a;
64
+ #l;
45
65
  /** Schedules main-layer redraws (background, grid, series). */
46
66
  #i;
47
67
  /** Schedules overlay redraws (crosshair). */
48
- #o;
68
+ #c;
49
69
  /** Maps time values to horizontal pixel coordinates. */
50
70
  timeScale;
51
- /** Maps price/value to vertical pixel coordinates. */
71
+ /** Maps price/value to vertical pixel coordinates — the default Y scale. */
52
72
  yScale;
53
- /** Zoom, pan, crosshair — null when interactive=false. */
73
+ /** Default-scale Y state; `yScale` is its scale. */
54
74
  #P;
75
+ /** Named Y scales from `options.scales`, in declaration order. */
76
+ #a = /* @__PURE__ */ new Map();
77
+ /** Copy of the `scales` option as last applied — `setScales` skips a deep-equal replay,
78
+ * and the copy keeps an in-place edit of the caller's object from comparing equal. */
79
+ #At = {};
80
+ /** Set when a named scale's range moved, so the crosshair re-emits for its axis label. */
81
+ #nt = !1;
82
+ /** Keys of dev warnings already printed, so each fires once. */
83
+ #Mt = /* @__PURE__ */ new Set();
84
+ /** What the X axis maps — read from `axis.x.type` once, at construction. */
85
+ #r;
86
+ /** Category axis: name → band index, in first-seen order across the series. */
87
+ #Y = /* @__PURE__ */ new Map();
88
+ /** Zoom, pan, crosshair — null when interactive=false. */
89
+ #V;
55
90
  /** All registered series (candlestick, line, bar, pie). */
56
91
  #t = [];
57
92
  /** Point annotations (event markers) drawn on the overlay layer, kept out of the series model. */
58
- #u = [];
93
+ #y = [];
59
94
  /** Time-region bands drawn on the main layer behind the series, kept out of the series model. */
60
- #x = [];
95
+ #T = [];
61
96
  /** Reference lines (horizontal threshold / vertical event) drawn on the overlay layer, kept out of the series model. */
62
- #y = [];
97
+ #b = [];
63
98
  /**
64
99
  * Overlay-intro latch. Annotations ride the *initial* reveal, then stay put.
65
100
  * `#overlayIntroSeen` flips once we've actually watched an intro in progress
@@ -68,31 +103,31 @@ class Li extends wt {
68
103
  * later — which re-arms `getIntroFront()`'s global min — can't blink settled
69
104
  * markers / reference lines back out.
70
105
  */
71
- #mt = !1;
72
- #ft = !1;
106
+ #Pt = !1;
107
+ #Ct = !1;
73
108
  /** Active visual theme (colors, fonts, grid style). */
74
109
  #n;
75
110
  /** Whether to render the background grid. */
76
- #F;
111
+ #G;
77
112
  /**
78
113
  * Gridline layer opacity, multiplied into every per-tick fade: `0 → 1` on
79
114
  * the opening reveal, `1 → 0` when {@link setGrid} hides the grid. The
80
115
  * per-tick fades stay snapped during mount (see the tracker's arming), so
81
116
  * this is what keeps `fitToData` tick churn from flickering the reveal.
82
117
  */
83
- #H;
118
+ #E;
84
119
  /** Resolved edge-fade zones — a `null` side is auto: the axis-column ramp on the Y-axis side, off on the other. */
85
- #A;
120
+ #C;
86
121
  /** See {@link headerHeight}. */
87
- #U = 0;
122
+ #at = 0;
88
123
  /** Memoized destination-out mask gradients, one per edge, keyed by the
89
124
  * zone's bitmap geometry so a resize / DPR change / `setFade` rebuilds
90
125
  * them and steady frames reuse them. */
91
- #v = { top: null, axis: null, edge: { left: null, right: null }, tail: null, overhang: null };
126
+ #k = { top: null, axis: { left: null, right: null }, edge: { left: null, right: null }, tail: null, overhang: null };
92
127
  /** Detected time interval between data points (milliseconds). */
93
- #r = 6e4;
128
+ #o = 6e4;
94
129
  /** Current crosshair position, null when cursor is outside the chart. */
95
- #c = null;
130
+ #f = null;
96
131
  /**
97
132
  * What the crosshair is anchored to, deciding which half of the position
98
133
  * survives a viewport move (see {@link #syncCrosshairToViewport}):
@@ -100,45 +135,43 @@ class Li extends wt {
100
135
  * `time`/`y`; `'time'` (programmatic {@link setCrosshair}) keeps
101
136
  * `time`/`y` and re-projects the pixel position.
102
137
  */
103
- #q = "pointer";
138
+ #ot = "pointer";
104
139
  /** Whether the last overlay pass painted anything — gates the full-bitmap
105
140
  * clear so an empty overlay isn't cleared again every main frame. */
106
- #gt = !1;
107
- /** User-specified Y-axis bounds (auto, fixed, percentage). */
108
- #p = {};
141
+ #Lt = !1;
109
142
  /** Cached series ID list — invalidated on add/remove. */
110
- #D = null;
143
+ #z = null;
111
144
  /** Axis visibility and sizing configuration. */
112
- #T = {};
145
+ #v = {};
113
146
  /** Host-declared state per edge — drives the edge-indicator overlay. */
114
- #S = { left: "idle", right: "idle" };
147
+ #A = { left: "idle", right: "idle" };
115
148
  /** Wall-clock start of a `loading` exit fade per side — set when the host
116
149
  * drops `loading` back to `idle`/`has-more`, so the indicator fades out
117
150
  * over {@link EDGE_EXIT_FADE_MS} instead of vanishing the same frame the
118
151
  * fetched data lands. `null` = no fade in flight. */
119
- #$ = { left: null, right: null };
152
+ #K = { left: null, right: null };
120
153
  /** Cached boundary timestamps from the last `edgeReached` emission, by side. */
121
- #pt = { left: null, right: null };
154
+ #Dt = { left: null, right: null };
122
155
  /** Latest placeholder shapes a `loading` indicator reported per side —
123
156
  * the morph hand-off candidate consumed by {@link prependData}. */
124
- #K = { left: null, right: null };
157
+ #ht = { left: null, right: null };
125
158
  /** True after a prepend consumed a side's placeholder snapshot: the series'
126
159
  * reveal owns those pixels now, so the still-`loading` indicator is hidden
127
160
  * and the upcoming `loading → idle` transition skips its exit fade. Reset
128
161
  * by the next {@link setEdgeState} call. */
129
- #z = { left: !1, right: !1 };
162
+ #B = { left: !1, right: !1 };
130
163
  /** Host-supplied callback fired when the user releases a pan/zoom past a data edge. */
131
- #yt;
164
+ #Rt;
132
165
  /** Per-side custom painter for the edge `loading` spinner. `null` falls back to the built-in dots. */
133
- #vt = { left: null, right: null };
166
+ #It = { left: null, right: null };
134
167
  /** Nesting depth for batch updates. Suppresses recomputes while > 0. */
135
- #L = 0;
168
+ #R = 0;
136
169
  /** True when batched operations include data changes (triggers full onDataChanged on end). */
137
- #j = !1;
170
+ #lt = !1;
138
171
  /** True when batched operations include visibility changes (triggers Y-range + redraw on end). */
139
- #V = !1;
172
+ #N = !1;
140
173
  /** True when batched operations bumped overlay version (emits a single overlayChange on flush). */
141
- #J = !1;
174
+ #ct = !1;
142
175
  /**
143
176
  * Monotonic counter bumped on any mutation that affects overlay output —
144
177
  * data, visibility, series options, theme. Used by snapshot helpers in
@@ -148,36 +181,59 @@ class Li extends wt {
148
181
  *
149
182
  * @internal
150
183
  */
151
- #bt = 0;
152
- /** CSS px of the Y-axis column. The default collapses to `0` on a spatial-only chart. */
184
+ #Ht = 0;
185
+ /** CSS px of the default Y-axis column. The default collapses to `0` on a spatial-only chart. */
153
186
  get yAxisWidth() {
154
- const t = this.#T.y;
155
- return t?.visible === !1 ? 0 : t?.width !== void 0 ? t.width : this.#X() ? 0 : 55;
187
+ return this.#j(this.#v.y);
156
188
  }
157
- /** Side of the plot area the Y-axis column sits on. */
189
+ /** Side of the plot area the default Y-axis column sits on. */
158
190
  get yAxisPosition() {
159
- return this.#T.y?.position ?? "right";
191
+ return this.#v.y?.position ?? "right";
160
192
  }
161
193
  /**
162
- * Left edge of the plot area in container CSS px — the Y-axis width when
163
- * the axis sits on the left, `0` otherwise. Scales map into the plot area,
164
- * so add this to `timeScale.timeToX()` to get a container coordinate.
194
+ * Left edge of the plot area in container CSS px — the width of the Y-axis
195
+ * column on the left, `0` without one. Scales map into the plot area, so
196
+ * add this to `timeScale.timeToX()` to get a container coordinate.
165
197
  */
166
198
  get plotLeft() {
167
- return this.yAxisPosition === "left" ? this.yAxisWidth : 0;
199
+ return this.getAxisColumnWidth("left");
200
+ }
201
+ /**
202
+ * CSS px of the Y-axis column on `side` — the default axis's or the named
203
+ * scale's that owns that side — `0` when no column sits there.
204
+ */
205
+ getAxisColumnWidth(t) {
206
+ if (t === this.yAxisPosition && this.yAxisWidth > 0) return this.yAxisWidth;
207
+ for (const e of this.#a.values())
208
+ if (e.column === t) return this.#j(e.config);
209
+ return 0;
210
+ }
211
+ /** The named scale whose axis column sits on `side`, `null` when the default axis's does or none. */
212
+ getAxisColumnScale(t) {
213
+ for (const [e, i] of this.#a)
214
+ if (i.column === t) return e;
215
+ return null;
216
+ }
217
+ /** Both Y-axis columns together — the plot area is the canvas width minus this. */
218
+ #w() {
219
+ return this.getAxisColumnWidth("left") + this.getAxisColumnWidth("right");
220
+ }
221
+ /** Column width a Y-axis config asks for; `0` when hidden, and by default on a spatial-only chart. */
222
+ #j(t) {
223
+ return t?.visible === !1 ? 0 : t?.width !== void 0 ? t.width : this.#Q() ? 0 : ae;
168
224
  }
169
225
  /** CSS px height of the floating Title / InfoBar header, `0` without one. A left
170
226
  * axis shares its column with it, so tick labels fade out underneath. */
171
227
  get headerHeight() {
172
- return this.#U;
228
+ return this.#at;
173
229
  }
174
230
  /** CSS px of the time-axis row. The default collapses to `0` on a spatial-only chart. */
175
231
  get xAxisHeight() {
176
- const t = this.#T.x;
177
- return t?.visible === !1 ? 0 : t?.height !== void 0 ? t.height : this.#X() ? 0 : 30;
232
+ const t = this.#v.x;
233
+ return t?.visible === !1 ? 0 : t?.height !== void 0 ? t.height : this.#Q() ? 0 : 30;
178
234
  }
179
235
  /** Resolved animation config derived from `options.animations` at construction. */
180
- #d;
236
+ #g;
181
237
  /**
182
238
  * Single source of truth for the chart's X / Y viewport animation.
183
239
  * Push-model API: chart emits intent signals (`onPointAppended`,
@@ -186,13 +242,13 @@ class Li extends wt {
186
242
  * matching targets back through `computeXTarget` / `computeYTarget`
187
243
  * closures supplied at construction.
188
244
  */
189
- #s;
245
+ #e;
190
246
  /** EMA-smoothed real-wall-clock cadence between streaming appends. The
191
247
  * X spring's `setSettleMs` is fed from this so the settle time stays just
192
248
  * longer than the producer's tick interval — the spring never quite
193
249
  * settles between ticks, so velocity carries continuously and the slide
194
250
  * doesn't pulse. */
195
- #R;
251
+ #F;
196
252
  /**
197
253
  * Mirrors `#dataReplaceSnapPending` for the engine signal that follows
198
254
  * inside the same `onDataChanged` call. `#dataReplaceSnapPending` is
@@ -202,31 +258,31 @@ class Li extends wt {
202
258
  * field carries the decision across that gap. The closure also reads
203
259
  * `#dataStart` / `#dataEnd` directly for the timestamps.
204
260
  */
205
- #xt = !1;
261
+ #Vt = !1;
206
262
  /** Earliest data timestamp registered across all series, `null` before any data has arrived. */
207
- #w = null;
263
+ #S = null;
208
264
  /** Latest data timestamp registered across all series, `null` before any data has arrived. */
209
- #e = null;
265
+ #s = null;
210
266
  /**
211
267
  * Previous {@link #dataEnd} value. Streaming-target math reads it to
212
268
  * preserve any pan offset across ticks: a user who panned a few bars
213
269
  * left of the tail keeps that offset as new bars arrive, instead of
214
270
  * snapping back to the natural-pin position every frame.
215
271
  */
216
- #N = null;
272
+ #q = null;
217
273
  /**
218
274
  * Warm-up window flag — `setVisibleRange({ from, bars })` arms it and
219
275
  * the streaming-target math suppresses pan ticks while the data hasn't
220
276
  * reached the right edge yet. Cleared by user interaction or once the
221
277
  * data fills the window.
222
278
  */
223
- #M = !1;
279
+ #L = !1;
224
280
  /**
225
281
  * Cap for fit-to-data. When the data span exceeds this, the fit anchors
226
282
  * the right edge and trims the left. Resolved once at construction from
227
283
  * `options.viewport.maxVisibleBars`.
228
284
  */
229
- #St;
285
+ #zt;
230
286
  /**
231
287
  * Committed logical X range — the "where X should be" target. Engine
232
288
  * eases its visual toward this. Pan / zoom / setRange / fitContent /
@@ -234,69 +290,75 @@ class Li extends wt {
234
290
  * as "uninitialized" by `#computeXTarget` until first paint.
235
291
  */
236
292
  #h = { from: 0, to: 0 };
237
- /** Padded Y range — engine's raw Y target plus pixel padding from `#padding`. */
238
- #B = { min: 0, max: 0 };
239
293
  /**
240
294
  * Auto-scroll flag — when `true`, streaming ticks slide X to track the
241
295
  * data tail. Pan flips this off when the gesture pushes the tail off
242
296
  * screen; chart's per-frame autoscroll check re-engages it when a
243
297
  * follow-up pan brings the tail back into view.
244
298
  */
245
- #m = !0;
299
+ #p = !0;
246
300
  /** Resolved viewport padding (top/bottom px + left/right pixels-or-intervals). */
247
- #l;
301
+ #d;
248
302
  /** Cached chart width — passed by interactions on every call; kept for the rare
249
303
  * call that omits it (e.g. programmatic `chart.pan(...)` without explicit width). */
250
- #E = 0;
304
+ #J = 0;
251
305
  /** Active performance monitor, or `null` when instrumentation is disabled (the default). */
252
- #k;
306
+ #M;
253
307
  /** When true, `destroy()` tears down the monitor; false for caller-supplied monitors we must not destroy. */
254
- #Q = !1;
308
+ #dt = !1;
255
309
  /** Visible HUD overlay (mounted by the perf config); non-null only when the caller requested one. */
256
- #tt = null;
257
- constructor(t, i) {
258
- super(), i?.axis && (this.#T = i.axis, this.#p = { min: i.axis.y?.min, max: i.axis.y?.max }), this.#n = i?.theme ?? Rt.theme, this.#F = i?.grid?.visible !== !1, this.#A = O(i?.fade), this.#d = j.resolve(i?.animations), this.#St = lt(i?.viewport?.maxVisibleBars), this.#l = F(i?.padding), this.#R = new ft();
259
- const e = this.#d.axis.y, s = this.#d.axis.x;
260
- this.#s = tt({
310
+ #ut = null;
311
+ constructor(t, e) {
312
+ super(), e?.axis && (this.#v = e.axis), this.#r = this.#v.x?.type ?? "time", this.#n = e?.theme ?? Zt.theme, this.#G = e?.grid?.visible !== !1, this.#C = Y(e?.fade), this.#g = at.resolve(e?.animations), this.#zt = xt(e?.viewport?.maxVisibleBars), this.#d = E(e?.padding), this.#F = new Mt();
313
+ const i = this.#g.axis.y, s = this.#g.axis.x;
314
+ this.#e = lt({
261
315
  initial: { yRange: { min: 0, max: 0 }, xRange: { from: 0, to: 0 } },
262
316
  y: {
263
- curve: e.curve,
264
- settleMs: e.settleMs,
265
- stickyMs: e.stickyMs,
266
- stickyFloorMs: e.stickyFloorMs,
267
- gestureMs: e.gestureMs,
268
- toggleMs: this.#d.toggleMs
317
+ curve: i.curve,
318
+ settleMs: i.settleMs,
319
+ stickyMs: i.stickyMs,
320
+ stickyFloorMs: i.stickyFloorMs,
321
+ gestureMs: i.gestureMs,
322
+ toggleMs: this.#g.toggleMs
269
323
  },
270
324
  x: {
271
325
  curve: s.curve,
272
326
  settleMs: s.settleMs,
273
327
  gestureMs: s.gestureMs
274
328
  },
275
- computeXTarget: () => this.#Jt(),
276
- computeYTarget: ({ xTarget: o }) => this.#ht(o),
329
+ computeXTarget: () => this.#we(),
330
+ computeYTarget: ({ xTarget: o, scale: h }) => this.#_(o, h),
277
331
  onWake: () => this.#i?.markDirty()
278
- }), this.#yt = i?.onEdgeReached, this.#_ = i?.viewport?.initialRange;
279
- const r = ct(i?.perf);
280
- this.#k = r.monitor, this.#Q = r.ownsMonitor, this.#a = new it(t, this.#k ?? void 0), this.timeScale = new Lt(), this.yScale = new Mt(), this.yScale.setTransform(this.#T.y?.scale ?? null), N({ y: this.#T.y, isPlottable: this.#Y }), this.timeScale.setLocale(i?.locale), this.timeScale.setTimeZone(i?.timeZone);
281
- const n = this.#d.axis.ticksMs;
282
- this.timeScale.tickTracker.setFadeMs(n), this.yScale.tickTracker.setFadeMs(n), this.#H = new K({
332
+ }), e?.onEdgeReached && this.checkTimeOnlyFeature("onEdgeReached") && (this.#Rt = e.onEdgeReached), this.#it = e?.viewport?.initialRange;
333
+ const r = vt(e?.perf);
334
+ this.#M = r.monitor, this.#dt = r.ownsMonitor, this.#l = new ct(t, this.#M ?? void 0), this.timeScale = new Nt(), this.timeScale.setType(this.#r), this.timeScale.setBandPadding(this.#v.x?.padding), this.yScale = new U(), this.#P = j(this.yScale, this.#v.y), this.yScale.setTransform(this.#v.y?.scale ?? null), C({ y: this.#v.y, isPlottable: this.#gt(void 0) }), this.timeScale.setLocale(e?.locale), this.timeScale.setTimeZone(e?.timeZone);
335
+ const n = this.#g.axis.ticksMs;
336
+ this.timeScale.tickTracker.setFadeMs(n), this.yScale.tickTracker.setFadeMs(n), this.#Xt(e?.scales ?? {}), this.#St(), this.#E = new nt({
283
337
  initial: 0,
284
- duration: this.#d.axis.gridMs,
285
- lerp: (o, l, h) => o + (l - o) * h
286
- }), this.#i = new Y((o) => this.#dt(o)), this.#o = new Y(
287
- (o) => this.#ut("overlay", o, () => this.renderOverlay(o))
288
- ), this.#k && r.hud && (this.#tt = r.hud(t, this.#k));
289
- const a = i?.interactive !== !1;
290
- this.#P = a ? new Tt(this.#a.canvas, this, this.timeScale, this.yScale) : null, this.#a.on("resize", () => {
291
- this.syncScales(), this.#dt(), this.emit("viewportChange");
292
- }), this.syncScales(), this.#P?.on("crosshairMove", (o) => {
293
- this.#q = "pointer", this.#c = o, this.#o.markDirty(), this.emit("crosshairMove", o), this.#rt(o);
294
- }), this.#P?.on("click", (o) => {
295
- this.emit("pointClick", { ...o, spatialHit: this.#Dt(o) });
296
- }), this.#P?.on("dblclick", (o) => {
297
- this.fitContent(), this.emit("pointDoubleClick", { ...o, spatialHit: this.#Dt(o) });
338
+ duration: this.#g.axis.gridMs,
339
+ lerp: (o, h, l) => o + (h - o) * l
340
+ }), this.#i = new Z((o) => this.#wt(o)), this.#c = new Z(
341
+ (o) => this.#Tt("overlay", o, () => this.renderOverlay(o))
342
+ ), this.#M && r.hud && (this.#ut = r.hud(t, this.#M));
343
+ const a = e?.interactive !== !1;
344
+ this.#V = a ? new Bt(this.#l.canvas, this, this.timeScale, this.yScale) : null, this.#l.on("resize", () => {
345
+ this.syncScales(), this.#wt(), this.emit("viewportChange");
346
+ }), this.syncScales(), this.#V?.on("crosshairMove", (o) => {
347
+ const h = o === null ? null : this.#mt(o);
348
+ this.#ot = "pointer", this.#f = h, this.#c.markDirty(), this.emit("crosshairMove", h), this.#bt(h);
349
+ }), this.#V?.on("click", (o) => {
350
+ const h = this.#mt(o);
351
+ this.emit("pointClick", { ...h, spatialHit: this.#Ut(h) });
352
+ }), this.#V?.on("dblclick", (o) => {
353
+ const h = this.#mt(o);
354
+ this.fitContent(), this.emit("pointDoubleClick", { ...h, spatialHit: this.#Ut(h) });
298
355
  });
299
356
  }
357
+ /** A pointer position on a category axis snaps to the nearest band; `x` is the coordinate in the axis domain. */
358
+ #mt(t) {
359
+ const e = this.timeScale.snapToBand(t.time);
360
+ return { ...t, time: e, x: this.timeScale.domainValue(e) };
361
+ }
300
362
  /**
301
363
  * Bump overlay version + emit `overlayChange`. Call from every mutation
302
364
  * path whose output overlay components care about: data, series list,
@@ -309,9 +371,9 @@ class Li extends wt {
309
371
  * `setSeriesVisible` / `setLayerVisible` calls — produce one overlay
310
372
  * render instead of N.
311
373
  */
312
- #f() {
313
- if (this.#bt++, this.#L > 0) {
314
- this.#J = !0;
374
+ #u() {
375
+ if (this.#Ht++, this.#R > 0) {
376
+ this.#ct = !0;
315
377
  return;
316
378
  }
317
379
  this.emit("overlayChange");
@@ -325,45 +387,59 @@ class Li extends wt {
325
387
  * @internal
326
388
  */
327
389
  getOverlayVersion() {
328
- return this.#bt;
390
+ return this.#Ht;
329
391
  }
330
392
  /**
331
393
  * Return a series ID: use the provided hint if it's non-empty and not already taken,
332
394
  * otherwise generate a new auto ID. Auto-generated IDs never collide with each other
333
395
  * or with user-provided IDs because they use a monotonically increasing counter.
334
396
  */
335
- #Et(t) {
336
- if (t && !this.#t.some((e) => e.id === t))
397
+ #ne(t) {
398
+ if (t && !this.#t.some((i) => i.id === t))
337
399
  return t;
338
- let i;
400
+ let e;
339
401
  do
340
- i = `series_${++Et}`;
341
- while (this.#t.some((e) => e.id === i));
342
- return i;
402
+ e = `series_${++oe}`;
403
+ while (this.#t.some((i) => i.id === e));
404
+ return e;
343
405
  }
344
- addSeries(t, i = {}) {
345
- const e = typeof t == "string" ? Dt(t) : t;
346
- if (!e)
406
+ addSeries(t, e = {}) {
407
+ const i = typeof t == "string" ? Xt(t) : t;
408
+ if (!i)
347
409
  throw new Error(
348
410
  `[wick-charts] Unknown series type '${String(t)}'. The string form of addSeries resolves through the series registry — call registerBuiltinSeries() once at startup, or pass the definition directly: chart.addSeries(LineSeriesDef, options).`
349
411
  );
350
- const { layers: s, id: r, label: n, labels: a, ...o } = i, l = s ?? 1;
351
- M({ seriesId: r ?? e.type, stacking: o.stacking, isPlottable: this.#Y });
352
- const h = {
353
- ...this.#d.defaults(e.type),
354
- ...o,
355
- ...this.#d.overrides(e.type)
356
- }, d = e.create({ theme: this.#n, layerCount: l }, h), m = a ?? (typeof n == "string" ? [n] : void 0), c = this.#Et(r);
357
- d.onDataChanged?.(() => this.onDataChanged());
358
- const f = this.#Z();
359
- return this.#t.push({ id: c, labels: m, renderer: d, visible: !0 }), this.#D = null, this.#st(), this.#et(f), this.emit("seriesChange"), this.#f(), c;
412
+ const { layers: s, id: r, label: n, labels: a, scale: o, ...h } = e, l = s ?? 1;
413
+ o === void 0 && L({
414
+ seriesId: r ?? i.type,
415
+ stacking: h.stacking,
416
+ isPlottable: this.#gt(o)
417
+ });
418
+ const d = {
419
+ ...this.#g.defaults(i.type),
420
+ ...h,
421
+ ...this.#g.overrides(i.type)
422
+ }, f = i.create({ theme: this.#n, layerCount: l }, d);
423
+ v(f) && Ct({ seriesType: i.type, axisType: this.#r, supported: i.xTypes ?? ["time"] });
424
+ const c = a ?? (typeof n == "string" ? [n] : void 0), m = this.#ne(r);
425
+ f.onDataChanged?.(() => this.onDataChanged());
426
+ const y = this.#Z();
427
+ return this.#t.push({
428
+ id: m,
429
+ labels: c,
430
+ renderer: f,
431
+ visible: !0,
432
+ scale: o,
433
+ xKeys: { x: !1, time: !1 },
434
+ categoryPoints: []
435
+ }), this.#z = null, this.#et(), this.#tt(y), this.emit("seriesChange"), this.#u(), m;
360
436
  }
361
437
  /** Remove a series by ID and clean up its resources. */
362
438
  removeSeries(t) {
363
- const i = this.#t.findIndex((e) => e.id === t);
364
- if (i >= 0) {
365
- const e = this.#Z();
366
- this.#t[i].renderer.dispose(), this.#t.splice(i, 1), this.#D = null, this.#st(), this.#et(e), this.#i.markDirty(), this.emit("seriesChange"), this.#f();
439
+ const e = this.#t.findIndex((i) => i.id === t);
440
+ if (e >= 0) {
441
+ const i = this.#Z();
442
+ this.#t[e].renderer.dispose(), this.#t.splice(e, 1), this.#z = null, this.#r === "category" && this.#Bt(), this.#et(), this.#tt(i), this.#i.markDirty(), this.emit("seriesChange"), this.#u();
367
443
  }
368
444
  }
369
445
  // --- Markers (point annotations) -----------------------------------------
@@ -377,77 +453,82 @@ class Li extends wt {
377
453
  * appear in series queries.
378
454
  */
379
455
  addMarker(t) {
380
- const i = `marker_${++Yt}`;
381
- return this.#u.push(this.#kt(i, t)), this.#o.markDirty(), i;
456
+ const e = `marker_${++he}`;
457
+ return this.#y.push(this.#Ft(e, t)), this.#c.markDirty(), e;
382
458
  }
383
459
  /** Replace a marker's config in place. No-op when the id is unknown. */
384
- updateMarker(t, i) {
385
- const e = this.#u.findIndex((s) => s.id === t);
386
- e !== -1 && (this.#u[e] = this.#kt(t, i), this.#o.markDirty());
460
+ updateMarker(t, e) {
461
+ const i = this.#y.findIndex((s) => s.id === t);
462
+ i !== -1 && (this.#y[i] = this.#Ft(t, e), this.#c.markDirty());
387
463
  }
388
464
  /** Remove a marker by id. No-op when the id is unknown. */
389
465
  removeMarker(t) {
390
- const i = this.#u.findIndex((e) => e.id === t);
391
- i !== -1 && (this.#u.splice(i, 1), this.#o.markDirty());
466
+ const e = this.#y.findIndex((i) => i.id === t);
467
+ e !== -1 && (this.#y.splice(e, 1), this.#c.markDirty());
392
468
  }
393
469
  /** Read-only snapshot of the registered markers (normalized). Mutate via add / update / removeMarker. */
394
470
  getMarkers() {
395
- return this.#u;
471
+ return this.#y;
396
472
  }
397
- #kt(t, i) {
473
+ #Ft(t, e) {
398
474
  return {
399
475
  id: t,
400
- time: x(i.time),
401
- value: i.value,
402
- seriesId: i.seriesId,
403
- shape: i.shape ?? "dot",
404
- pulse: i.pulse ?? !1,
405
- label: i.label,
406
- color: i.color
476
+ time: k(e.time),
477
+ value: e.value,
478
+ seriesId: e.seriesId,
479
+ shape: e.shape ?? "dot",
480
+ pulse: e.pulse ?? !1,
481
+ label: e.label,
482
+ color: e.color,
483
+ scale: e.scale
407
484
  };
408
485
  }
486
+ /** The scale a marker's value maps through: its own `scale`, else its series' scale. */
487
+ #ae(t) {
488
+ return t.scale !== void 0 || t.seriesId === void 0 ? this.getYScale(t.scale) : this.getYScale(this.getSeriesScale(t.seriesId));
489
+ }
409
490
  /** Resolve a marker's Y value: explicit `value`, else snapped to its series at `time`. Null when neither resolves. */
410
- #Yt(t) {
491
+ #oe(t) {
411
492
  if (t.value !== void 0 && Number.isFinite(t.value)) return t.value;
412
493
  if (t.seriesId === void 0) return null;
413
- const i = this.getDataAtTime(t.seriesId, t.time);
414
- return i ? X(i) : null;
494
+ const e = this.getDataAtTime(t.seriesId, t.time);
495
+ return e ? G(e) : null;
415
496
  }
416
497
  /** Resolve a marker's color: explicit `color`, else the series color, else the theme line color. */
417
- #Xt(t) {
498
+ #he(t) {
418
499
  if (t.color !== void 0) return t.color;
419
500
  if (t.seriesId !== void 0) {
420
- const i = this.getSeriesColor(t.seriesId);
421
- if (i !== null) return i;
501
+ const e = this.getSeriesColor(t.seriesId);
502
+ if (e !== null) return e;
422
503
  }
423
504
  return this.#n.line.color;
424
505
  }
425
- #Zt(t, i, e) {
426
- if (this.#u.length === 0) return;
427
- const s = performance.now() / (J * 2 * Math.PI) % 1, r = this.#it();
428
- for (const n of this.#u) {
429
- const a = this.#Yt(n);
506
+ #le(t, e, i) {
507
+ if (this.#y.length === 0) return;
508
+ const s = performance.now() / (ot * 2 * Math.PI) % 1, r = this.#pt();
509
+ for (const n of this.#y) {
510
+ const a = this.#oe(n);
430
511
  if (a === null) continue;
431
- const o = this.#Lt(r, n.time);
512
+ const o = this.#Yt(r, n.time);
432
513
  if (o <= 0) continue;
433
- const l = t.context.globalAlpha;
434
- o < 1 && (t.context.globalAlpha = l * o), xt({
514
+ const h = t.context.globalAlpha;
515
+ o < 1 && (t.context.globalAlpha = h * o), Ot({
435
516
  scope: t,
436
517
  timeScale: this.timeScale,
437
- yScale: this.yScale,
518
+ yScale: this.#ae(n),
438
519
  theme: this.#n,
439
520
  marker: {
440
521
  time: n.time,
441
522
  value: a,
442
- color: this.#Xt(n),
523
+ color: this.#he(n),
443
524
  shape: n.shape,
444
525
  pulse: n.pulse,
445
526
  label: n.label
446
527
  },
447
528
  phase: s,
448
- plotWidth: i,
449
- plotHeight: e
450
- }), o < 1 && (t.context.globalAlpha = l);
529
+ plotWidth: e,
530
+ plotHeight: i
531
+ }), o < 1 && (t.context.globalAlpha = h);
451
532
  }
452
533
  }
453
534
  // --- Time regions (shaded bands) -----------------------------------------
@@ -463,45 +544,45 @@ class Li extends wt {
463
544
  * legend.
464
545
  */
465
546
  addRegion(t) {
466
- const i = `region_${++Xt}`;
467
- return this.#x.push(this.#At(i, t)), this.#i.markDirty(), i;
547
+ const e = `region_${++le}`;
548
+ return this.checkTimeOnlyFeature("TimeRegion") && (this.#T.push(this.#Ot(e, t)), this.#i.markDirty()), e;
468
549
  }
469
550
  /** Replace a region's config in place. No-op when the id is unknown. */
470
- updateRegion(t, i) {
471
- const e = this.#x.findIndex((s) => s.id === t);
472
- e !== -1 && (this.#x[e] = this.#At(t, i), this.#i.markDirty());
551
+ updateRegion(t, e) {
552
+ const i = this.#T.findIndex((s) => s.id === t);
553
+ i !== -1 && (this.#T[i] = this.#Ot(t, e), this.#i.markDirty());
473
554
  }
474
555
  /** Remove a region by id. No-op when the id is unknown. */
475
556
  removeRegion(t) {
476
- const i = this.#x.findIndex((e) => e.id === t);
477
- i !== -1 && (this.#x.splice(i, 1), this.#i.markDirty());
557
+ const e = this.#T.findIndex((i) => i.id === t);
558
+ e !== -1 && (this.#T.splice(e, 1), this.#i.markDirty());
478
559
  }
479
560
  /** Read-only snapshot of the registered regions (normalized). Mutate via add / update / removeRegion. */
480
561
  getRegions() {
481
- return this.#x;
562
+ return this.#T;
482
563
  }
483
- #At(t, i) {
484
- const { to: e } = i, s = e === void 0 || e === "now" ? null : x(e);
564
+ #Ot(t, e) {
565
+ const { to: i } = e, s = i === void 0 || i === "now" ? null : k(i);
485
566
  return {
486
567
  id: t,
487
- from: x(i.from),
568
+ from: k(e.from),
488
569
  to: s,
489
- fill: i.fill,
490
- color: i.color,
491
- label: i.label
570
+ fill: e.fill,
571
+ color: e.color,
572
+ label: e.label
492
573
  };
493
574
  }
494
- #_t(t, i, e) {
495
- if (this.#x.length !== 0)
496
- for (const s of this.#x) {
497
- const { color: r, fill: n } = kt(s, this.#n);
498
- At({
575
+ #ce(t, e, i) {
576
+ if (this.#T.length !== 0)
577
+ for (const s of this.#T) {
578
+ const { color: r, fill: n } = Wt(s, this.#n);
579
+ Yt({
499
580
  scope: t,
500
581
  timeScale: this.timeScale,
501
582
  theme: this.#n,
502
583
  region: { from: s.from, to: s.to, fill: n, color: r, label: s.label },
503
- plotWidth: i,
504
- plotHeight: e
584
+ plotWidth: e,
585
+ plotHeight: i
505
586
  });
506
587
  }
507
588
  }
@@ -517,44 +598,45 @@ class Li extends wt {
517
598
  * legend.
518
599
  */
519
600
  addLine(t) {
520
- const i = `line_${++Zt}`;
521
- return this.#y.push(this.#wt(i, t)), this.#o.markDirty(), i;
601
+ const e = `line_${++ce}`;
602
+ return this.#b.push(this.#$t(e, t)), this.#c.markDirty(), e;
522
603
  }
523
604
  /** Replace a reference line's config in place. No-op when the id is unknown. */
524
- updateLine(t, i) {
525
- const e = this.#y.findIndex((s) => s.id === t);
526
- e !== -1 && (this.#y[e] = this.#wt(t, i), this.#o.markDirty());
605
+ updateLine(t, e) {
606
+ const i = this.#b.findIndex((s) => s.id === t);
607
+ i !== -1 && (this.#b[i] = this.#$t(t, e), this.#c.markDirty());
527
608
  }
528
609
  /** Remove a reference line by id. No-op when the id is unknown. */
529
610
  removeLine(t) {
530
- const i = this.#y.findIndex((e) => e.id === t);
531
- i !== -1 && (this.#y.splice(i, 1), this.#o.markDirty());
611
+ const e = this.#b.findIndex((i) => i.id === t);
612
+ e !== -1 && (this.#b.splice(e, 1), this.#c.markDirty());
532
613
  }
533
614
  /** Read-only snapshot of the registered reference lines (normalized). Mutate via add / update / removeLine. */
534
615
  getLines() {
535
- return this.#y;
616
+ return this.#b;
536
617
  }
537
- #wt(t, i) {
538
- const { time: e, value: s } = i, r = {
618
+ #$t(t, e) {
619
+ const { time: i, value: s } = e, r = {
539
620
  id: t,
540
- color: i.color,
541
- label: i.label,
542
- style: i.style ?? "dashed",
543
- width: i.width ?? 1
621
+ color: e.color,
622
+ label: e.label,
623
+ style: e.style ?? "dashed",
624
+ width: e.width ?? 1,
625
+ scale: e.scale
544
626
  };
545
- return e !== void 0 ? { ...r, orientation: "vertical", coord: x(e) } : { ...r, orientation: "horizontal", coord: s ?? Number.NaN };
627
+ return i !== void 0 ? { ...r, orientation: "vertical", coord: k(i) } : { ...r, orientation: "horizontal", coord: s ?? Number.NaN };
546
628
  }
547
- #Gt(t, i, e) {
548
- if (this.#y.length === 0) return;
549
- const s = this.#it();
550
- for (const r of this.#y) {
551
- const n = s >= 1 ? 1 : r.orientation === "vertical" ? this.#Lt(s, r.coord) : W(s);
629
+ #de(t, e, i) {
630
+ if (this.#b.length === 0) return;
631
+ const s = this.#pt();
632
+ for (const r of this.#b) {
633
+ const n = s >= 1 ? 1 : r.orientation === "vertical" ? this.#Yt(s, r.coord) : W(s);
552
634
  if (n <= 0) continue;
553
635
  const a = t.context.globalAlpha;
554
- n < 1 && (t.context.globalAlpha = a * n), St({
636
+ n < 1 && (t.context.globalAlpha = a * n), $t({
555
637
  scope: t,
556
638
  timeScale: this.timeScale,
557
- yScale: this.yScale,
639
+ yScale: this.getYScale(r.scale),
558
640
  theme: this.#n,
559
641
  line: {
560
642
  orientation: r.orientation,
@@ -564,8 +646,8 @@ class Li extends wt {
564
646
  width: r.width,
565
647
  label: r.label
566
648
  },
567
- plotWidth: i,
568
- plotHeight: e
649
+ plotWidth: e,
650
+ plotHeight: i
569
651
  }), n < 1 && (t.context.globalAlpha = a);
570
652
  }
571
653
  }
@@ -576,15 +658,23 @@ class Li extends wt {
576
658
  * underlying animator mutates between frames.
577
659
  */
578
660
  getAnimationState() {
579
- return this.#Tt(this.#s.getAnimationState());
661
+ return this.#ft(this.#e.getAnimationState());
662
+ }
663
+ /** The engine animates Y in scale space (the scale's forward); readers outside it get values. */
664
+ #ft(t, e = this.yScale) {
665
+ const i = e.getTransform();
666
+ return i === null ? t : { ...t, yRange: Ht(t.yRange, i) };
667
+ }
668
+ /** The animation state as a named scale's series see it: its own Y, in values. */
669
+ #Wt(t, e) {
670
+ const i = this.#e.getScaleYRange(e) ?? t.yRange;
671
+ return this.#ft({ ...t, yRange: i }, this.getYScale(e));
580
672
  }
581
- /** The engine animates Y in scale space (`axis.y.scale`'s forward); readers outside it get values. */
582
- #Tt(t) {
583
- const i = this.yScale.getTransform();
584
- return i === null ? t : { ...t, yRange: pt(t.yRange, i) };
673
+ /** `isPlottable` of a scale (`undefined` = default), as a predicate for the guard helpers. */
674
+ #gt(t) {
675
+ const e = this.getYScale(t);
676
+ return (i) => e.isPlottable(i);
585
677
  }
586
- /** `yScale.isPlottable`, bound once for the helpers that take a predicate. */
587
- #Y = (t) => this.yScale.isPlottable(t);
588
678
  /**
589
679
  * Reveal-front position in `[0, 1]` of any in-flight initial-load intro.
590
680
  * With a `seriesId`, that series' own front; without, the minimum across
@@ -594,14 +684,14 @@ class Li extends wt {
594
684
  */
595
685
  getIntroFront(t) {
596
686
  if (t !== void 0)
597
- return this.#t.find((e) => e.id === t)?.renderer.getIntroFront?.() ?? 1;
598
- let i = 1;
599
- for (const e of this.#t) {
600
- if (!e.visible) continue;
601
- const s = e.renderer.getIntroFront?.();
602
- s !== void 0 && s < i && (i = s);
687
+ return this.#t.find((i) => i.id === t)?.renderer.getIntroFront?.() ?? 1;
688
+ let e = 1;
689
+ for (const i of this.#t) {
690
+ if (!i.visible) continue;
691
+ const s = i.renderer.getIntroFront?.();
692
+ s !== void 0 && s < e && (e = s);
603
693
  }
604
- return i;
694
+ return e;
605
695
  }
606
696
  /**
607
697
  * Reveal front for annotations (markers / reference lines). The same as
@@ -611,10 +701,10 @@ class Li extends wt {
611
701
  * has actually been observed in progress, so an annotation drawn on a chart
612
702
  * that has no data yet still rides the reveal once data arrives.
613
703
  */
614
- #it() {
615
- if (this.#ft) return 1;
704
+ #pt() {
705
+ if (this.#Ct) return 1;
616
706
  const t = this.getIntroFront();
617
- return t < 1 ? (this.#mt = !0, t) : (this.#mt && (this.#ft = !0), 1);
707
+ return t < 1 ? (this.#Pt = !0, t) : (this.#Pt && (this.#Ct = !0), 1);
618
708
  }
619
709
  /**
620
710
  * Entrance alpha for an overlay pinned at `time`, given the intro reveal
@@ -622,10 +712,10 @@ class Li extends wt {
622
712
  * then fades in over a short band — so annotations surface in step with the
623
713
  * series drawing past them. `1` once the intro has settled.
624
714
  */
625
- #Lt(t, i) {
715
+ #Yt(t, e) {
626
716
  if (t >= 1) return 1;
627
- const { from: e, to: s } = this.timeScale.getRange(), r = s - e, n = r > 0 ? (i - e) / r : 0;
628
- return Ct((t - n) / 0.12, 0, 1);
717
+ const { from: i, to: s } = this.timeScale.getRange(), r = s - i, n = r > 0 ? (e - i) / r : 0;
718
+ return _t((t - n) / 0.12, 0, 1);
629
719
  }
630
720
  /**
631
721
  * Replace all data for a series.
@@ -635,15 +725,127 @@ class Li extends wt {
635
725
  *
636
726
  * For line/bar accepts `TimePointInput[]` (time may be `Date`); for candlestick accepts `OHLCInput[]`;
637
727
  * for pie accepts `PieSliceData[]`. Time fields are normalized internally.
728
+ *
729
+ * On a category axis a replace is how a bar other than the last changes value,
730
+ * so it eases like a stream: points that keep their band ease to their new values.
638
731
  */
639
- setSeriesData(t, i, e) {
640
- const s = this.#t.find((r) => r.id === t);
641
- s && (this.#nt = !0, s.renderer.setData(i, e));
732
+ setSeriesData(t, e, i) {
733
+ const s = this.#t.find((a) => a.id === t);
734
+ if (!s) return;
735
+ const r = this.#r === "category" && this.#vt(s);
736
+ if (this.#kt = !r, !this.#vt(s)) {
737
+ s.renderer.setData(e, i);
738
+ return;
739
+ }
740
+ const n = this.#Et(s, e ?? []);
741
+ if (!r) {
742
+ s.renderer.setData(n, i);
743
+ return;
744
+ }
745
+ this.#xt({ entry: s, layerIndex: i, mutate: () => n });
642
746
  }
643
747
  /** Append a new data point to the end of a series (real-time tick). */
644
- appendData(t, i, e) {
645
- const s = this.#t.find((r) => r.id === t);
646
- s === void 0 || s.renderer.appendPoint === void 0 || s.renderer.appendPoint(i, e);
748
+ appendData(t, e, i) {
749
+ const s = this.#t.find((n) => n.id === t);
750
+ if (s === void 0 || s.renderer.appendPoint === void 0) return;
751
+ const r = s.renderer.appendPoint.bind(s.renderer);
752
+ this.#yt({
753
+ entry: s,
754
+ layerIndex: i,
755
+ points: [e],
756
+ apply: ([n]) => r(n, i),
757
+ mutateCategory: (n, a) => [...n, ...a]
758
+ });
759
+ }
760
+ /**
761
+ * Run a store mutation with its points mapped onto the X axis. A time axis
762
+ * passes them through and a numeric axis drops the ones that don't fit. A
763
+ * category axis applies `mutateCategory` to the layer's input-order points
764
+ * instead, then re-sets the store from them.
765
+ */
766
+ #yt(t) {
767
+ const { entry: e, apply: i } = t;
768
+ if (!this.#vt(e)) {
769
+ i(t.points.slice());
770
+ return;
771
+ }
772
+ const s = this.#Et(e, t.points);
773
+ if (s.length !== 0) {
774
+ if (this.#r === "numeric") {
775
+ i(s);
776
+ return;
777
+ }
778
+ this.#xt({
779
+ entry: e,
780
+ layerIndex: t.layerIndex,
781
+ mutate: (r) => t.mutateCategory(r, s)
782
+ });
783
+ }
784
+ }
785
+ /** Replace a category layer's input-order points with `mutate`'s result and sync the categories and stores. */
786
+ #xt(t) {
787
+ const { entry: e } = t, i = t.layerIndex ?? 0;
788
+ e.categoryPoints[i] = t.mutate(e.categoryPoints[i] ?? []), this.#Bt({ entry: e, layerIndex: i });
789
+ }
790
+ /** Whether `entry`'s points go through the numeric / category ingest. */
791
+ #vt(t) {
792
+ return this.#r !== "time" && v(t.renderer);
793
+ }
794
+ /**
795
+ * Points that fit the X axis, a numeric one with its `x` copied to `time`.
796
+ * The rest are skipped with a dev warning, as is a series mixing `x` and `time`.
797
+ */
798
+ #Et(t, e) {
799
+ const i = [];
800
+ for (const s of e) {
801
+ const r = s.x !== void 0;
802
+ if (r && (t.xKeys.x = !0), s.time !== void 0 && (t.xKeys.time = !0), !(this.#r === "category" ? typeof s.x == "string" : !r || typeof s.x == "number")) {
803
+ this.#I(
804
+ `x-misfit:${t.id}`,
805
+ () => Lt({ seriesId: t.id, axisType: this.#r, x: s.x })
806
+ );
807
+ continue;
808
+ }
809
+ i.push(r && this.#r === "numeric" ? { ...s, time: s.x } : s);
810
+ }
811
+ return t.xKeys.x && t.xKeys.time && this.#I(`x-mixed:${t.id}`, () => Dt({ seriesId: t.id })), i;
812
+ }
813
+ /** Category points with `time` set to their band index. */
814
+ #ue(t) {
815
+ return t.map((e) => ({ ...e, time: this.#Y.get(e.x) }));
816
+ }
817
+ /**
818
+ * Re-derive the categories — first-seen order across the series (mount
819
+ * order, then layers, then points in input order) — and re-set every store
820
+ * whose band indices moved, plus the `changed` layer.
821
+ */
822
+ #Bt(t) {
823
+ const e = /* @__PURE__ */ new Set();
824
+ for (const a of this.#t)
825
+ for (const o of a.categoryPoints)
826
+ for (const h of o ?? [])
827
+ e.add(h.x);
828
+ const i = [...e], s = this.timeScale.getCategories(), r = i.length !== s.length || i.some((a, o) => a !== s[o]), n = this.#Y;
829
+ this.#Y = new Map(i.map((a, o) => [a, o])), this.timeScale.setCategories(i), this.batch(() => {
830
+ for (const a of this.#t)
831
+ this.#me({ entry: a, previous: n, changed: t });
832
+ r && this.onDataChanged();
833
+ });
834
+ }
835
+ /** Re-set `entry`'s layers whose points moved band, or that `changed` names, from their input-order points. */
836
+ #me(t) {
837
+ const { entry: e, previous: i, changed: s } = t;
838
+ for (let r = 0; r < e.categoryPoints.length; r++) {
839
+ const n = e.categoryPoints[r] ?? [], a = s?.entry === e && s.layerIndex === r, o = n.some(
840
+ (d) => i.get(d.x) !== this.#Y.get(d.x)
841
+ );
842
+ if (!a && !o) continue;
843
+ const h = this.#ue(n).sort((d, f) => d.time - f.time), l = n.some((d) => {
844
+ const f = i.get(d.x);
845
+ return f !== void 0 && f !== this.#Y.get(d.x);
846
+ });
847
+ a && !l && e.renderer.morphData !== void 0 ? e.renderer.morphData(h, r) : e.renderer.setData(h, r);
848
+ }
647
849
  }
648
850
  /**
649
851
  * Prepend older points to the front of a series — the "load more history on
@@ -658,15 +860,33 @@ class Li extends wt {
658
860
  * first point; the sync reconciler guarantees this before calling here.
659
861
  * No-op if the renderer doesn't support prepend.
660
862
  */
661
- prependData(t, i, e) {
662
- const s = this.#t.find((n) => n.id === t);
863
+ prependData(t, e, i) {
864
+ const s = this.#t.find((a) => a.id === t);
663
865
  if (s === void 0 || s.renderer.prependPoints === void 0) return;
664
- const r = this.#K.left;
665
- r !== null && s.renderer.setHistoryHandoff !== void 0 && performance.now() - r.at < Ot && (s.renderer.setHistoryHandoff(r.bars), this.#z.left = !0, this.#$.left = null, this.#K.left = null, this.#o.markDirty()), s.renderer.prependPoints(i, e);
866
+ const r = this.#ht.left;
867
+ r !== null && s.renderer.setHistoryHandoff !== void 0 && performance.now() - r.at < Qt && (s.renderer.setHistoryHandoff(r.bars), this.#B.left = !0, this.#K.left = null, this.#ht.left = null, this.#c.markDirty());
868
+ const n = s.renderer.prependPoints.bind(s.renderer);
869
+ this.#yt({
870
+ entry: s,
871
+ layerIndex: i,
872
+ points: e,
873
+ apply: (a) => n(a, i),
874
+ mutateCategory: (a, o) => [...o, ...a]
875
+ });
666
876
  }
667
877
  /** Update the last data point of a series in place (e.g. live candle update). */
668
- updateData(t, i, e) {
669
- this.#t.find((s) => s.id === t)?.renderer.updateLastPoint?.(i, e);
878
+ updateData(t, e, i) {
879
+ const s = this.#t.find((n) => n.id === t);
880
+ if (s === void 0 || s.renderer.updateLastPoint === void 0) return;
881
+ const r = s.renderer.updateLastPoint.bind(s.renderer);
882
+ this.#yt({
883
+ entry: s,
884
+ layerIndex: i,
885
+ points: [e],
886
+ apply: ([n]) => r(n, i),
887
+ // Like the store, an empty layer has no last point to update.
888
+ mutateCategory: (n, a) => n.length === 0 ? [] : [...n.slice(0, -1), ...a]
889
+ });
670
890
  }
671
891
  /**
672
892
  * Keep only the most recent `count` points of a series — drop the oldest
@@ -681,37 +901,59 @@ class Li extends wt {
681
901
  * chart.keepLast('feed', 100);
682
902
  * ```
683
903
  */
684
- keepLast(t, i, e) {
685
- this.#t.find((s) => s.id === t)?.renderer.keepLast?.(i, e);
904
+ keepLast(t, e, i) {
905
+ const s = this.#t.find((r) => r.id === t);
906
+ if (s?.renderer.keepLast !== void 0) {
907
+ if (this.#r !== "category") {
908
+ s.renderer.keepLast(e, i);
909
+ return;
910
+ }
911
+ this.#xt({
912
+ entry: s,
913
+ layerIndex: i,
914
+ mutate: (r) => e < 0 ? [...r] : r.slice(Math.max(0, r.length - e))
915
+ });
916
+ }
686
917
  }
687
- /** Update visual options (color, width, etc.) for an existing series. */
688
- updateSeriesOptions(t, i) {
689
- const e = this.#t.find((h) => h.id === t);
690
- if (!e) return;
691
- "stacking" in i && M({ seriesId: t, stacking: i.stacking, isPlottable: this.#Y });
692
- const s = e.labels, r = e.renderer.getLayerColors().slice(), n = this.#d.overrides(e.renderer.kind);
693
- e.renderer.updateOptions({ ...i, ...n }), "label" in i && typeof i.label == "string" && (e.labels = [i.label]), this.#i.markDirty();
694
- const a = e.renderer.getLayerColors(), o = r.length !== a.length || r.some((h, d) => h !== a[d]), l = !_(s, e.labels);
695
- (o || l) && this.#f();
918
+ /**
919
+ * Update visual options (color, width, etc.) for an existing series. `scale`
920
+ * (time series) moves it onto a named Y scale, `undefined` back to the default.
921
+ */
922
+ updateSeriesOptions(t, e) {
923
+ const i = this.#t.find((u) => u.id === t);
924
+ if (!i) return;
925
+ const { scale: s, ...r } = e, n = "scale" in e, a = n ? s : i.scale;
926
+ if (("stacking" in e || n) && a === void 0) {
927
+ const u = "stacking" in e ? e.stacking : tt(i.renderer);
928
+ L({ seriesId: t, stacking: u, isPlottable: this.#gt(a) });
929
+ }
930
+ const o = i.labels, h = i.renderer.getLayerColors().slice(), l = this.#g.overrides(i.renderer.kind);
931
+ i.renderer.updateOptions({ ...r, ...l }), "label" in e && typeof e.label == "string" && (i.labels = [e.label]), this.#i.markDirty();
932
+ const d = this.#D(i);
933
+ i.scale = a;
934
+ const f = this.#D(i) !== d;
935
+ f && this.#xe();
936
+ const c = i.renderer.getLayerColors(), m = h.length !== c.length || h.some((u, g) => u !== c[g]), y = !Q(o, i.labels);
937
+ (m || y || f) && this.#u();
696
938
  }
697
939
  /**
698
940
  * Batch multiple updates: suppress recomputes until `fn` returns. Exceptions
699
941
  * inside `fn` still flush the batch so counters don't leak across calls.
700
942
  */
701
943
  batch(t) {
702
- this.#L++;
944
+ this.#R++;
703
945
  try {
704
946
  t();
705
947
  } finally {
706
- if (--this.#L <= 0) {
707
- if (this.#L = 0, this.#j)
708
- this.#j = !1, this.#V = !1, this.onDataChanged();
709
- else if (this.#V) {
710
- this.#V = !1, this.#g = !0;
711
- const i = performance.now();
712
- this.#s.onSeriesVisibilityChanged(i), this.#b(i), this.#i.markDirty();
948
+ if (--this.#R <= 0) {
949
+ if (this.#R = 0, this.#lt)
950
+ this.#lt = !1, this.#N = !1, this.onDataChanged();
951
+ else if (this.#N) {
952
+ this.#N = !1, this.#m = !0;
953
+ const e = performance.now();
954
+ this.#e.onSeriesVisibilityChanged(e), this.#x(e), this.#i.markDirty();
713
955
  }
714
- this.#J && (this.#J = !1, this.emit("overlayChange"));
956
+ this.#ct && (this.#ct = !1, this.emit("overlayChange"));
715
957
  }
716
958
  }
717
959
  }
@@ -722,18 +964,18 @@ class Li extends wt {
722
964
  * computation immediately so the axis can start moving while the line
723
965
  * fades out in parallel.
724
966
  */
725
- setSeriesVisible(t, i) {
726
- const e = this.#t.find((n) => n.id === t);
727
- if (!e || e.visible === i) return;
728
- e.visible = i, this.#f(), this.#st();
729
- const s = this.#d.toggleMs;
730
- if (e.renderer.setAlpha?.(i ? 1 : 0, s), this.#i.markDirty(), this.#L > 0) {
731
- this.#V = !0;
967
+ setSeriesVisible(t, e) {
968
+ const i = this.#t.find((n) => n.id === t);
969
+ if (!i || i.visible === e) return;
970
+ i.visible = e, this.#u(), this.#et();
971
+ const s = this.#g.toggleMs;
972
+ if (i.renderer.setAlpha?.(e ? 1 : 0, s), this.#i.markDirty(), this.#R > 0) {
973
+ this.#N = !0;
732
974
  return;
733
975
  }
734
- this.#g = !0;
976
+ this.#m = !0;
735
977
  const r = performance.now();
736
- this.#s.onSeriesVisibilityChanged(r), this.#b(r);
978
+ this.#e.onSeriesVisibilityChanged(r), this.#x(r);
737
979
  }
738
980
  /**
739
981
  * Whether streaming ticks slide the visible range to track the data tail.
@@ -743,10 +985,10 @@ class Li extends wt {
743
985
  * containing the tail.
744
986
  */
745
987
  getAutoScroll() {
746
- return this.#m;
988
+ return this.#p;
747
989
  }
748
990
  isSeriesVisible(t) {
749
- return this.#t.find((i) => i.id === t)?.visible ?? !0;
991
+ return this.#t.find((e) => e.id === t)?.visible ?? !0;
750
992
  }
751
993
  /**
752
994
  * Show or hide a specific layer within a multi-layer series.
@@ -758,31 +1000,31 @@ class Li extends wt {
758
1000
  * `getValueRange` reading `store.isVisible()`); the alpha fade keeps the
759
1001
  * layer's geometry on screen until the engine's Y ease settles.
760
1002
  */
761
- setLayerVisible(t, i, e) {
1003
+ setLayerVisible(t, e, i) {
762
1004
  const s = this.#t.find((a) => a.id === t);
763
- if (!s || s.renderer.getLayerCount() <= 1 || s.renderer.isLayerVisible(i) === e) return;
764
- s.renderer.setLayerVisible(i, e), this.#f();
765
- const r = this.#d.toggleMs;
766
- if (s.renderer.setLayerAlpha?.(i, e ? 1 : 0, r), this.#i.markDirty(), this.#L > 0) {
767
- this.#V = !0;
1005
+ if (!s || s.renderer.getLayerCount() <= 1 || s.renderer.isLayerVisible(e) === i) return;
1006
+ s.renderer.setLayerVisible(e, i), this.#u();
1007
+ const r = this.#g.toggleMs;
1008
+ if (s.renderer.setLayerAlpha?.(e, i ? 1 : 0, r), this.#i.markDirty(), this.#R > 0) {
1009
+ this.#N = !0;
768
1010
  return;
769
1011
  }
770
- this.#g = !0;
1012
+ this.#m = !0;
771
1013
  const n = performance.now();
772
- this.#s.onSeriesVisibilityChanged(n), this.#b(n);
1014
+ this.#e.onSeriesVisibilityChanged(n), this.#x(n);
773
1015
  }
774
- isLayerVisible(t, i) {
775
- return this.#t.find((e) => e.id === t)?.renderer.isLayerVisible(i) ?? !0;
1016
+ isLayerVisible(t, e) {
1017
+ return this.#t.find((i) => i.id === t)?.renderer.isLayerVisible(e) ?? !0;
776
1018
  }
777
1019
  /** Auto-fit the viewport to show all data across every series. */
778
1020
  fitContent() {
779
- const { first: t, last: i } = this.getDataBounds();
780
- if (t === void 0 || i === void 0) return;
781
- const e = this.#a.size.media.width - this.yAxisWidth;
782
- this.#O(t, i, e), this.#G({ xEase: !0 });
1021
+ const { first: t, last: e } = this.getDataBounds();
1022
+ if (t === void 0 || e === void 0) return;
1023
+ const i = this.#l.size.media.width - this.#w();
1024
+ this.#O(t, e, i), this.#st({ xEase: !0 });
783
1025
  }
784
1026
  getVisibleRange() {
785
- return this.#h.from === 0 && this.#h.to === 0 ? this.#s.getAnimationState().xRange : this.#h;
1027
+ return this.#h.from === 0 && this.#h.to === 0 ? this.#e.getAnimationState().xRange : this.#h;
786
1028
  }
787
1029
  /**
788
1030
  * Return the full span of registered data, or `null` before any data has
@@ -790,7 +1032,7 @@ class Li extends wt {
790
1032
  * tracking and is cheaper than recomputing from series stores.
791
1033
  */
792
1034
  getDataRange() {
793
- return this.#w === null || this.#e === null ? null : { from: this.#w, to: this.#e };
1035
+ return this.#S === null || this.#s === null ? null : { from: this.#S, to: this.#s };
794
1036
  }
795
1037
  /**
796
1038
  * Imperatively set the visible time range. Three forms:
@@ -816,35 +1058,35 @@ class Li extends wt {
816
1058
  * multi-chart sync so a synced pane tracks the originating gesture in
817
1059
  * lockstep rather than lagging behind on the long sticky contract.
818
1060
  */
819
- setVisibleRange(t, i) {
1061
+ setVisibleRange(t, e) {
820
1062
  if (typeof t == "number") {
821
1063
  if (!Number.isInteger(t) || t < 2) return;
822
1064
  const { first: n, last: a } = this.getDataBounds();
823
1065
  if (n === void 0 || a === void 0) return;
824
- const o = Math.max(n, a - (t - 1) * this.#r), l = this.#a.size.media.width - this.yAxisWidth;
825
- this.#O(o, a, l), this.#G();
1066
+ const o = Math.max(n, a - (t - 1) * this.#o), h = this.#l.size.media.width - this.#w();
1067
+ this.#O(o, a, h), this.#st();
826
1068
  return;
827
1069
  }
828
1070
  if ("bars" in t) {
829
1071
  if (!Number.isInteger(t.bars) || t.bars < 2) return;
830
- const n = x(t.from), a = n + t.bars * this.#r;
831
- this.#m = this.#e !== null && this.#e >= n && this.#e <= a, this.#C({ from: n, to: a }, { emitChange: !0 }), this.#M = !0, this.#G();
1072
+ const n = k(t.from), a = n + t.bars * this.#o;
1073
+ this.#p = this.#s !== null && this.#s >= n && this.#s <= a, this.#H({ from: n, to: a }, { emitChange: !0 }), this.#L = !0, this.#st();
832
1074
  return;
833
1075
  }
834
- const e = x(t.from), s = x(t.to);
835
- this.#m = this.#e !== null && this.#e >= e && this.#e <= s, this.#M = !1;
836
- const r = this.#C({ from: e, to: s }, { emitChange: !0 });
837
- if (i?.gesture) {
838
- r && this.#Qt();
1076
+ const i = k(t.from), s = k(t.to);
1077
+ this.#p = this.#s !== null && this.#s >= i && this.#s <= s, this.#L = !1;
1078
+ const r = this.#H({ from: i, to: s }, { emitChange: !0 });
1079
+ if (e?.gesture) {
1080
+ r && this.#Te();
839
1081
  return;
840
1082
  }
841
- this.#G();
1083
+ this.#st();
842
1084
  }
843
1085
  getYRange() {
844
- return this.#B;
1086
+ return this.#P.range;
845
1087
  }
846
1088
  getCrosshairPosition() {
847
- return this.#c;
1089
+ return this.#f;
848
1090
  }
849
1091
  /**
850
1092
  * Programmatically set or clear the crosshair. Same effect as the user
@@ -881,34 +1123,34 @@ class Li extends wt {
881
1123
  */
882
1124
  setCrosshair(t) {
883
1125
  if (t === null) {
884
- if (this.#c === null) return;
885
- this.#c = null, this.#o.markDirty(), this.emit("crosshairMove", null);
1126
+ if (this.#f === null) return;
1127
+ this.#f = null, this.#c.markDirty(), this.emit("crosshairMove", null);
886
1128
  return;
887
1129
  }
888
- const { time: i } = t;
889
- if (!Number.isFinite(i)) return;
890
- const e = this.#c;
1130
+ const { time: e } = t;
1131
+ if (!Number.isFinite(e)) return;
1132
+ const i = this.#f;
891
1133
  let s;
892
- if (t.y !== void 0 ? s = t.y : e !== null ? s = e.y : s = this.yScale.yToValue(this.yScale.getMediaHeight() / 2), !Number.isFinite(s) || e !== null && e.time === i && e.y === s) return;
893
- const r = this.plotLeft + this.timeScale.timeToX(i), n = this.yScale.valueToY(s);
894
- this.#q = "time", this.#c = { mediaX: r, mediaY: n, time: i, y: s }, this.#o.markDirty(), this.emit("crosshairMove", this.#c);
1134
+ if (t.y !== void 0 ? s = t.y : i !== null ? s = i.y : s = this.yScale.yToValue(this.yScale.getMediaHeight() / 2), !Number.isFinite(s) || i !== null && i.time === e && i.y === s) return;
1135
+ const r = this.plotLeft + this.timeScale.timeToX(e), n = this.yScale.valueToY(s);
1136
+ this.#ot = "time", this.#f = { mediaX: r, mediaY: n, time: e, x: this.timeScale.domainValue(e), y: s }, this.#c.markDirty(), this.emit("crosshairMove", this.#f);
895
1137
  }
896
1138
  /** Get the last visible value and whether the absolute last point is on screen. */
897
1139
  getLastValue(t) {
898
- return at(t, this.#t, this.#s.getAnimationState().xRange);
1140
+ return gt(t, this.#t, this.#e.getAnimationState().xRange);
899
1141
  }
900
1142
  /** Get the second-to-last value, useful for computing change. */
901
1143
  getPreviousClose(t) {
902
- return ot(t, this.#t);
1144
+ return pt(t, this.#t);
903
1145
  }
904
1146
  getLastData(t) {
905
- const i = this.#t.find((e) => e.id === t);
906
- return !i || !b(i.renderer) ? null : i.renderer.getLastDataPoint();
1147
+ const e = this.#t.find((i) => i.id === t);
1148
+ return !e || !v(e.renderer) ? null : e.renderer.getLastDataPoint();
907
1149
  }
908
1150
  /** Find the data point closest to the given timestamp within one data interval. */
909
- getDataAtTime(t, i) {
910
- const e = this.#t.find((s) => s.id === t);
911
- return e ? e.renderer.getDataAtTime?.(i, this.#r) ?? null : null;
1151
+ getDataAtTime(t, e) {
1152
+ const i = this.#t.find((s) => s.id === t);
1153
+ return i ? i.renderer.getDataAtTime?.(e, this.#X()) ?? null : null;
912
1154
  }
913
1155
  /**
914
1156
  * Get all layers' data at a given time for multi-layer series (Bar/Line
@@ -916,12 +1158,21 @@ class Li extends wt {
916
1158
  * snapped sample time — callers must not derive layer identity from the
917
1159
  * array index, because hidden layers are filtered out.
918
1160
  */
919
- getLayerSnapshots(t, i) {
920
- const e = this.#t.find((s) => s.id === t);
921
- return e?.visible ? e.renderer.getLayerSnapshots?.(i, this.#r) ?? null : null;
1161
+ getLayerSnapshots(t, e) {
1162
+ const i = this.#t.find((s) => s.id === t);
1163
+ return i?.visible ? i.renderer.getLayerSnapshots?.(e, this.#X()) ?? null : null;
1164
+ }
1165
+ /**
1166
+ * How far from a point the crosshair and hit-tests still find it — one data
1167
+ * interval on a time axis; on a category axis half a step, so a band only
1168
+ * finds its own point; on a numeric axis the whole data span, so a point
1169
+ * past a wide gap is still the nearest.
1170
+ */
1171
+ #X() {
1172
+ return this.#r === "category" ? 0.5 : this.#r === "time" || this.#S === null || this.#s === null ? this.#o : Math.max(this.#o, this.#s - this.#S);
922
1173
  }
923
1174
  getSeriesIds() {
924
- return this.#D || (this.#D = this.#t.map((t) => t.id)), this.#D.slice();
1175
+ return this.#z || (this.#z = this.#t.map((t) => t.id)), this.#z.slice();
925
1176
  }
926
1177
  /**
927
1178
  * True if any visible series has a time axis (line / bar / candlestick).
@@ -929,9 +1180,9 @@ class Li extends wt {
929
1180
  * has no meaningful x/y coordinate system, so the dashed hairlines would
930
1181
  * just be visual noise.
931
1182
  */
932
- #Mt() {
1183
+ #Nt() {
933
1184
  for (const t of this.#t)
934
- if (t.visible && b(t.renderer))
1185
+ if (t.visible && v(t.renderer))
935
1186
  return !0;
936
1187
  return !1;
937
1188
  }
@@ -941,26 +1192,43 @@ class Li extends wt {
941
1192
  * to reserve and nothing slides under an axis column. Reads registration,
942
1193
  * not visibility — a legend toggle must not shift the layout.
943
1194
  */
944
- #X() {
945
- return this.#t.length === 0 ? !1 : this.#t.every((t) => !b(t.renderer));
1195
+ #Q() {
1196
+ return this.#t.length === 0 ? !1 : this.#t.every((t) => !v(t.renderer));
946
1197
  }
947
1198
  #Z() {
948
- return { yAxisWidth: this.yAxisWidth, xAxisHeight: this.xAxisHeight, yAxisPosition: this.yAxisPosition };
1199
+ return {
1200
+ yAxisWidth: this.yAxisWidth,
1201
+ xAxisHeight: this.xAxisHeight,
1202
+ yAxisPosition: this.yAxisPosition,
1203
+ leftWidth: this.getAxisColumnWidth("left"),
1204
+ rightWidth: this.getAxisColumnWidth("right")
1205
+ };
949
1206
  }
950
- /** Resync the scales after the axis gutters moved, and let DOM axis / label
951
- * components re-read the layout, as they do after a resize. */
952
- #et(t) {
953
- const i = this.#Z();
954
- (i.yAxisWidth !== t.yAxisWidth || i.xAxisHeight !== t.xAxisHeight || i.yAxisPosition !== t.yAxisPosition) && (this.syncScales(), this.emit("viewportChange"), this.#i.markDirty());
1207
+ /** Reassign the Y-axis columns, then resync the scales if the gutters moved
1208
+ * and let DOM axis / label components re-read the layout, as they do after a resize. */
1209
+ #tt(t) {
1210
+ this.#St();
1211
+ const e = this.#Z();
1212
+ (e.yAxisWidth !== t.yAxisWidth || e.xAxisHeight !== t.xAxisHeight || e.yAxisPosition !== t.yAxisPosition || e.leftWidth !== t.leftWidth || e.rightWidth !== t.rightWidth) && (this.syncScales(), this.emit("viewportChange"), this.#i.markDirty());
955
1213
  }
956
1214
  /**
957
1215
  * Whether pan/zoom gestures currently apply — true when any visible series
958
- * has a time axis. Part of the {@link PanZoomTarget} surface: on a
959
- * spatial-only chart (pie, heatmap) the interaction layer stops capturing
960
- * wheel and touch events, so the page scrolls normally under the cursor.
1216
+ * has a time axis and `axis.x.panZoom` allows it. Part of the
1217
+ * {@link PanZoomTarget} surface: otherwise the interaction layer stops
1218
+ * capturing wheel and touch events, so the page scrolls normally under the cursor.
961
1219
  */
962
1220
  canPanZoom() {
963
- return this.#Mt();
1221
+ return (this.#v.x?.panZoom ?? this.#r !== "category") && this.#Nt();
1222
+ }
1223
+ /**
1224
+ * Whether a time-only feature (Navigator, EdgeLoader, TimeRegion) can run
1225
+ * on this chart's X axis. On a numeric or category axis it warns once per
1226
+ * feature in dev and returns `false` — the caller then does nothing.
1227
+ *
1228
+ * @internal
1229
+ */
1230
+ checkTimeOnlyFeature(t) {
1231
+ return this.#r === "time" ? !0 : (this.#I(`time-only:${t}`, () => Rt({ feature: t, axisType: this.#r })), !1);
964
1232
  }
965
1233
  /**
966
1234
  * Push the current pan/zoom capability into the interaction layer (canvas
@@ -969,8 +1237,8 @@ class Li extends wt {
969
1237
  * every mutation that can flip {@link canPanZoom}: series add/remove and
970
1238
  * series visibility.
971
1239
  */
972
- #st() {
973
- this.#P?.syncPanZoomMode();
1240
+ #et() {
1241
+ this.#V?.syncPanZoomMode();
974
1242
  }
975
1243
  /**
976
1244
  * Filter `getSeriesIds()` by renderer type. A concrete kind (`'pie'`,
@@ -983,18 +1251,18 @@ class Li extends wt {
983
1251
  * - `opts.singleLayerOnly` — exclude series with more than one layer.
984
1252
  * Useful for YLabel fallback priority (stick to a single line first).
985
1253
  */
986
- getSeriesIdsByType(t, i) {
987
- const e = i?.visibleOnly === !0, s = i?.singleLayerOnly === !0, r = [];
1254
+ getSeriesIdsByType(t, e) {
1255
+ const i = e?.visibleOnly === !0, s = e?.singleLayerOnly === !0, r = [];
988
1256
  for (const n of this.#t) {
989
- if (!(t === "time" ? b(n.renderer) : n.renderer.kind === t)) continue;
1257
+ if (!(t === "time" ? v(n.renderer) : n.renderer.kind === t)) continue;
990
1258
  const a = n.renderer.getLayerCount();
991
1259
  if (!(s && a > 1)) {
992
- if (e) {
1260
+ if (i) {
993
1261
  if (!n.visible) continue;
994
1262
  if (a > 1) {
995
1263
  let o = !1;
996
- for (let l = 0; l < a; l++)
997
- if (n.renderer.isLayerVisible(l)) {
1264
+ for (let h = 0; h < a; h++)
1265
+ if (n.renderer.isLayerVisible(h)) {
998
1266
  o = !0;
999
1267
  break;
1000
1268
  }
@@ -1013,7 +1281,7 @@ class Li extends wt {
1013
1281
  * Returns `null` for unknown ids or empty series.
1014
1282
  */
1015
1283
  getStackedLastValue(t) {
1016
- return ht(t, this.#t, this.#s.getAnimationState().xRange);
1284
+ return yt(t, this.#t, this.#e.getAnimationState().xRange);
1017
1285
  }
1018
1286
  /**
1019
1287
  * Stacked value at an arbitrary `time` — the cumulative stack top at that
@@ -1022,13 +1290,13 @@ class Li extends wt {
1022
1290
  * (Candlestick, single-layer Line/Bar). Returns `null` for unknown ids or
1023
1291
  * when no sample sits near `time`. Used by the YLabel intro count-up start.
1024
1292
  */
1025
- getStackedValueAtTime(t, i) {
1026
- const e = this.#t.find((n) => n.id === t);
1027
- if (!e) return null;
1028
- const s = e.renderer.getStackedValueAtTime?.(i, this.#r);
1293
+ getStackedValueAtTime(t, e) {
1294
+ const i = this.#t.find((n) => n.id === t);
1295
+ if (!i) return null;
1296
+ const s = i.renderer.getStackedValueAtTime?.(e, this.#X());
1029
1297
  if (s != null) return s;
1030
- const r = e.renderer.getDataAtTime?.(i, this.#r);
1031
- return !r || zt(r) ? null : X(r);
1298
+ const r = i.renderer.getDataAtTime?.(e, this.#X());
1299
+ return !r || jt(r) ? null : G(r);
1032
1300
  }
1033
1301
  /**
1034
1302
  * Per-layer last snapshots with each layer's own `time`. Returns `null`
@@ -1037,7 +1305,7 @@ class Li extends wt {
1037
1305
  * when layers advance at different rates (ragged streams).
1038
1306
  */
1039
1307
  getLayerLastSnapshots(t) {
1040
- return this.#t.find((i) => i.id === t)?.renderer.getLayerLastSnapshots?.() ?? null;
1308
+ return this.#t.find((e) => e.id === t)?.renderer.getLayerLastSnapshots?.() ?? null;
1041
1309
  }
1042
1310
  /**
1043
1311
  * Shallow view of the internal series entries so unit tests can introspect
@@ -1051,16 +1319,16 @@ class Li extends wt {
1051
1319
  }
1052
1320
  /** Get the primary display color for a series. */
1053
1321
  getSeriesColor(t) {
1054
- const i = this.#t.find((e) => e.id === t);
1055
- return i ? i.renderer.getLayerColors()[0] ?? this.#n.line.color : null;
1322
+ const e = this.#t.find((i) => i.id === t);
1323
+ return e ? e.renderer.getLayerColors()[0] ?? this.#n.line.color : null;
1056
1324
  }
1057
1325
  /** The series' primary name (first layer). Used for single-layer overlays. */
1058
1326
  getSeriesLabel(t) {
1059
- return this.#t.find((i) => i.id === t)?.labels?.[0];
1327
+ return this.#t.find((e) => e.id === t)?.labels?.[0];
1060
1328
  }
1061
1329
  /** The display name of one layer, or `undefined` when that layer is unnamed. */
1062
- getLayerLabel(t, i) {
1063
- return this.#t.find((e) => e.id === t)?.labels?.[i];
1330
+ getLayerLabel(t, e) {
1331
+ return this.#t.find((i) => i.id === t)?.labels?.[e];
1064
1332
  }
1065
1333
  /**
1066
1334
  * Set every layer's display name at once (index = layer). Drives the tooltip,
@@ -1068,9 +1336,9 @@ class Li extends wt {
1068
1336
  * per-layer labels in `data` change. No-ops (and skips the overlay bump) when
1069
1337
  * the labels are unchanged, so it's safe to call on every data tick.
1070
1338
  */
1071
- setSeriesLabels(t, i) {
1072
- const e = this.#t.find((s) => s.id === t);
1073
- !e || _(e.labels, i) || (e.labels = i, this.#f());
1339
+ setSeriesLabels(t, e) {
1340
+ const i = this.#t.find((s) => s.id === t);
1341
+ !i || Q(i.labels, e) || (i.labels = e, this.#u());
1074
1342
  }
1075
1343
  /**
1076
1344
  * Set the per-layer color overrides carried in `data` (sparse — `undefined`
@@ -1079,92 +1347,220 @@ class Li extends wt {
1079
1347
  * the renderer, which keeps them separate from the theme base so a theme swap
1080
1348
  * preserves them. Called by the framework wrappers; no-ops when unchanged.
1081
1349
  */
1082
- setLayerColors(t, i) {
1083
- const e = this.#t.find((s) => s.id === t);
1084
- !e || _t(e.colorOverrides, i) || (e.colorOverrides = i, e.renderer.setColorOverrides?.(i), this.#i.markDirty(), this.#f());
1350
+ setLayerColors(t, e) {
1351
+ const i = this.#t.find((s) => s.id === t);
1352
+ !i || de(i.colorOverrides, e) || (i.colorOverrides = e, i.renderer.setColorOverrides?.(e), this.#i.markDirty(), this.#u());
1085
1353
  }
1086
1354
  /** Get per-layer colors for a series. Returns null for single-layer non-bar/line series. */
1087
1355
  getSeriesLayers(t) {
1088
- const i = this.#t.find((r) => r.id === t);
1089
- if (!i) return null;
1090
- const e = i.renderer.getLayerCount();
1091
- if (e <= 1) return null;
1092
- const s = i.renderer.getLayerColors();
1093
- return Array.from({ length: e }, (r, n) => ({ color: s[n % s.length] }));
1356
+ const e = this.#t.find((r) => r.id === t);
1357
+ if (!e) return null;
1358
+ const i = e.renderer.getLayerCount();
1359
+ if (i <= 1) return null;
1360
+ const s = e.renderer.getLayerColors();
1361
+ return Array.from({ length: i }, (r, n) => ({ color: s[n % s.length] }));
1094
1362
  }
1095
1363
  /** Get all slices with computed colors and percentages. Returns null for series without slice data (e.g. candlestick, line, bar). */
1096
1364
  getSliceInfo(t) {
1097
- return this.#t.find((i) => i.id === t)?.renderer.getSliceInfo?.(this.#n) ?? null;
1365
+ return this.#t.find((e) => e.id === t)?.renderer.getSliceInfo?.(this.#n) ?? null;
1098
1366
  }
1099
1367
  /** Get hover info (label/value/percent/color) for the currently hovered element, or null. */
1100
1368
  getHoverInfo(t) {
1101
- return this.#t.find((i) => i.id === t)?.renderer.getHoverInfo?.(this.#n) ?? null;
1369
+ return this.#t.find((e) => e.id === t)?.renderer.getHoverInfo?.(this.#n) ?? null;
1102
1370
  }
1103
1371
  /** Apply a new theme and update series colors where appropriate. */
1104
1372
  setTheme(t) {
1105
- const i = this.#n;
1373
+ const e = this.#n;
1106
1374
  this.#n = t;
1107
- for (const e of this.#t)
1108
- e.renderer.applyTheme(t, i);
1109
- this.#i.markDirty(), this.#f();
1375
+ for (const i of this.#t)
1376
+ i.renderer.applyTheme(t, e);
1377
+ this.#i.markDirty(), this.#u();
1110
1378
  }
1111
1379
  getTheme() {
1112
1380
  return this.#n;
1113
1381
  }
1114
1382
  /** BCP 47 locale applied to the built-in time-axis / tooltip / crosshair formatting. */
1115
1383
  setLocale(t) {
1116
- this.timeScale.setLocale(t), this.#f();
1384
+ this.timeScale.setLocale(t), this.#u();
1117
1385
  }
1118
1386
  getLocale() {
1119
1387
  return this.timeScale.getLocale();
1120
1388
  }
1121
1389
  /** IANA timezone applied to the same built-in date-time formatting. */
1122
1390
  setTimeZone(t) {
1123
- this.timeScale.setTimeZone(t), this.#f();
1391
+ this.timeScale.setTimeZone(t), this.#u();
1124
1392
  }
1125
1393
  getTimeZone() {
1126
1394
  return this.timeScale.getTimeZone();
1127
1395
  }
1128
1396
  /** Update axis configuration and re-render. */
1129
1397
  setAxis(t) {
1130
- const i = t.y?.scale ?? null, e = (a) => Pt(i, a);
1131
- for (const a of this.#t)
1132
- b(a.renderer) && M({ seriesId: a.id, stacking: a.renderer.stacking, isPlottable: e });
1133
- const s = this.#Z(), r = i !== this.yScale.getTransform();
1134
- this.#T = t, this.#p = { min: t.y?.min, max: t.y?.max }, N({ y: t.y, isPlottable: e });
1135
- const n = performance.now();
1136
- r && (this.yScale.setTransform(i), this.yScale.tickTracker.reset(), this.#ht() === null && this.#s.snap({ y: { min: 0, max: 0 } }, n)), this.#g = !0, this.#s.onAxisReconfig(n), this.#b(n), this.#et(s), this.#i.markDirty();
1398
+ const e = t.y?.scale ?? null, i = (d) => _(e, d);
1399
+ for (const d of this.#t)
1400
+ !v(d.renderer) || d.scale !== void 0 || L({ seriesId: d.id, stacking: d.renderer.stacking, isPlottable: i });
1401
+ const s = this.#Z(), r = e !== this.yScale.getTransform(), n = t.x?.type ?? "time";
1402
+ n !== this.#r && this.#I("x-type", () => It({ from: this.#r, to: n }));
1403
+ const a = this.timeScale.getBandPadding();
1404
+ this.timeScale.setBandPadding(t.x?.padding);
1405
+ const o = this.timeScale.getBandPadding(), h = a.inner !== o.inner || a.outer !== o.outer;
1406
+ this.#v = t, this.#P.bounds = { min: t.y?.min, max: t.y?.max }, C({ y: t.y, isPlottable: i });
1407
+ const l = performance.now();
1408
+ r && (this.yScale.setTransform(e), this.yScale.tickTracker.reset(), this.#_() === null && this.#e.snap({ y: { min: 0, max: 0 } }, l)), this.#m = !0, this.#e.onAxisReconfig(l), this.#x(l), this.#tt(s), this.#et(), h && this.#r === "category" && this.fitContent(), this.#i.markDirty();
1409
+ }
1410
+ /**
1411
+ * Replace the named Y scales (see {@link ChartOptions.scales}). Added,
1412
+ * removed and edited scales apply in place: a changed transform resets that
1413
+ * scale's ticks, and every scale refits. A deep-equal replay is a no-op.
1414
+ */
1415
+ setScales(t) {
1416
+ const e = t ?? {};
1417
+ if (Ut(e, this.#At)) return;
1418
+ const i = this.#Z(), s = this.#Xt(e);
1419
+ this.#St();
1420
+ const r = performance.now(), n = {};
1421
+ for (const a of s)
1422
+ this.#_(void 0, a) === null && (n[a] = { min: 0, max: 0 });
1423
+ this.#e.snap({ scales: n }, r), this.#m = !0, this.#e.onAxisReconfig(r), this.#x(r), this.#tt(i), this.emit("viewportChange"), this.#u(), this.#i.markDirty();
1424
+ }
1425
+ /**
1426
+ * Create, update and drop named-scale slots to match `next`, in its key
1427
+ * order. Returns the ids of existing scales whose transform changed.
1428
+ */
1429
+ #Xt(t) {
1430
+ const e = /* @__PURE__ */ new Map(), i = [];
1431
+ for (const [s, r] of Object.entries(t)) {
1432
+ const n = r.scale ?? null;
1433
+ let a = this.#a.get(s);
1434
+ if (a === void 0) {
1435
+ const o = new U();
1436
+ o.setTransform(n), o.tickTracker.setFadeMs(this.#g.axis.ticksMs), a = { ...j(o, r), config: r, column: null };
1437
+ } else n !== a.yScale.getTransform() && (a.yScale.setTransform(n), a.yScale.tickTracker.reset(), i.push(s));
1438
+ a.config = { ...r }, a.bounds = { min: r.min, max: r.max }, C({
1439
+ y: r,
1440
+ isPlottable: (o) => _(n, o),
1441
+ path: `scales.${s}`
1442
+ }), e.set(s, a);
1443
+ }
1444
+ return this.#a = e, this.#At = Object.fromEntries([...e].map(([s, r]) => [s, r.config])), this.#e.setYScales([...e.keys()]), i;
1445
+ }
1446
+ /**
1447
+ * Hand out the Y-axis columns: the default axis keeps its side, and each
1448
+ * visible named scale takes its side unless an earlier one holds it —
1449
+ * that one becomes an overlay scale (dev warning).
1450
+ */
1451
+ #St() {
1452
+ const t = /* @__PURE__ */ new Set();
1453
+ this.yAxisWidth > 0 && t.add(this.yAxisPosition);
1454
+ const e = q(this.yAxisPosition);
1455
+ for (const [i, s] of this.#a) {
1456
+ if (s.column = null, this.#j(s.config) <= 0) continue;
1457
+ const r = s.config.position ?? e;
1458
+ if (t.has(r)) {
1459
+ this.#I(`side:${i}`, () => kt({ scaleId: i, side: r }));
1460
+ continue;
1461
+ }
1462
+ t.add(r), s.column = r;
1463
+ }
1464
+ }
1465
+ /** Dev warnings for a series' `scale`: an id `scales` doesn't define, a percent stack on a scale without 0. */
1466
+ #fe(t) {
1467
+ const e = t.scale;
1468
+ e !== void 0 && (this.#a.has(e) || this.#I(`unknown:${e}`, () => wt({ seriesId: t.id, scaleId: e })), tt(t.renderer) === "percent" && !this.getYScale(this.#D(t)).isPlottable(0) && this.#I(`percent:${t.id}`, () => Tt({ seriesId: t.id })));
1469
+ }
1470
+ #I(t, e) {
1471
+ this.#Mt.has(t) || (this.#Mt.add(t), e());
1472
+ }
1473
+ /** Ids of the named Y scales, in declaration order. */
1474
+ getYScaleIds() {
1475
+ return [...this.#a.keys()];
1476
+ }
1477
+ /** The Y scale named `scaleId`; the default scale for `undefined` or an id `scales` doesn't define. */
1478
+ getYScale(t) {
1479
+ return t === void 0 ? this.yScale : this.#a.get(t)?.yScale ?? this.yScale;
1480
+ }
1481
+ /**
1482
+ * The column a scale's axis sits in — always the default axis's for
1483
+ * `undefined`; `null` for an overlay scale or an unknown id.
1484
+ */
1485
+ getYScaleColumn(t) {
1486
+ if (t === void 0) return { position: this.yAxisPosition, width: this.yAxisWidth };
1487
+ const e = this.#a.get(t);
1488
+ return e === void 0 || e.column === null ? null : { position: e.column, width: this.#j(e.config) };
1489
+ }
1490
+ /** The named scale a series maps through, `undefined` for the default scale. */
1491
+ getSeriesScale(t) {
1492
+ const e = this.#t.find((i) => i.id === t);
1493
+ return e === void 0 ? void 0 : this.#D(e);
1494
+ }
1495
+ /** `scale` if `options.scales` defines it, else `undefined` (the default scale). */
1496
+ #ge(t) {
1497
+ return t !== void 0 && this.#a.has(t) ? t : void 0;
1498
+ }
1499
+ #D(t) {
1500
+ return this.#ge(t.scale);
1501
+ }
1502
+ #pe(t) {
1503
+ return t === void 0 ? this.#P : this.#a.get(t) ?? this.#P;
1504
+ }
1505
+ /** The series a scale autoscales from. */
1506
+ #ye(t) {
1507
+ return this.#a.size === 0 ? this.#t : this.#t.filter((e) => this.#D(e) === t);
1508
+ }
1509
+ /** Snap every scale to a fresh fit — what each one holds just changed. */
1510
+ #xe() {
1511
+ this.#m = !0;
1512
+ const t = performance.now();
1513
+ this.#e.onAxisReconfig(t), this.#x(t), this.emit("viewportChange"), this.#i.markDirty();
1514
+ }
1515
+ /**
1516
+ * Install (or clear, with `null`) the value formatter `<YAxis format>` puts
1517
+ * on a scale — its axis labels, crosshair label and `YLabel` read it. `scale`
1518
+ * picks a named scale; an id `scales` doesn't define is a no-op.
1519
+ */
1520
+ setYAxisFormat(t) {
1521
+ t.scale !== void 0 && !this.#a.has(t.scale) || (this.getYScale(t.scale).setFormat(t.format), this.#u());
1137
1522
  }
1138
1523
  /**
1139
1524
  * Apply label-density knobs driven by `<YAxis labelCount=… minLabelSpacing=…>`
1140
1525
  * — kept separate from {@link setAxis} so component props don't force a
1141
- * Y-animation reset. Triggers a viewportChange + repaint so static charts
1142
- * react without waiting for pan/zoom.
1526
+ * Y-animation reset. `scale` picks a named scale's axis — an id `scales`
1527
+ * doesn't define is a no-op. Triggers a viewportChange + repaint so static
1528
+ * charts react without waiting for pan/zoom.
1143
1529
  */
1144
1530
  setYAxisLabelDensity(t) {
1531
+ if (t.scale !== void 0 && !this.#a.has(t.scale)) return;
1532
+ const e = this.getYScale(t.scale);
1145
1533
  let i = !1;
1146
- "labelCount" in t && (this.yScale.setLabelCount(t.labelCount ?? null), i = !0), "minLabelSpacing" in t && (this.yScale.setMinSpacing(t.minLabelSpacing ?? null), i = !0), i && (this.syncScales(), this.emit("viewportChange"), this.#i.markDirty());
1534
+ "labelCount" in t && (e.setLabelCount(t.labelCount ?? null), i = !0), "minLabelSpacing" in t && (e.setMinSpacing(t.minLabelSpacing ?? null), i = !0), i && (this.syncScales(), this.emit("viewportChange"), this.#i.markDirty());
1147
1535
  }
1148
- /** Label-density knobs for the time axis — mirror of {@link setYAxisLabelDensity}. */
1536
+ /**
1537
+ * Install (or clear, with `null`) the X label formatter `<XAxis format>`
1538
+ * puts on the axis — its tick labels, the crosshair label, the tooltip and
1539
+ * info-bar headers read it.
1540
+ */
1541
+ setXAxisFormat(t) {
1542
+ this.timeScale.setFormat(t), this.#u();
1543
+ }
1544
+ /** Label-density knobs for the X axis — mirror of {@link setYAxisLabelDensity}. */
1149
1545
  setTimeAxisLabelDensity(t) {
1150
- let i = !1;
1151
- "labelCount" in t && (this.timeScale.setLabelCount(t.labelCount ?? null), i = !0), "minLabelSpacing" in t && (this.timeScale.setMinSpacing(t.minLabelSpacing ?? null), i = !0), i && (this.syncScales(), this.emit("viewportChange"), this.#i.markDirty());
1546
+ let e = !1;
1547
+ "labelCount" in t && (this.timeScale.setLabelCount(t.labelCount ?? null), e = !0), "minLabelSpacing" in t && (this.timeScale.setMinSpacing(t.minLabelSpacing ?? null), e = !0), e && (this.syncScales(), this.emit("viewportChange"), this.#i.markDirty());
1152
1548
  }
1153
1549
  getMediaSize() {
1154
- return this.#a.size.media;
1550
+ return this.#l.size.media;
1155
1551
  }
1156
1552
  /** Returns layout metrics for the chart area, Y axis, and time axis. */
1157
1553
  getLayout() {
1158
- const t = this.#a.size.media, i = this.yAxisWidth, e = this.xAxisHeight;
1554
+ const t = this.#l.size.media, e = this.yAxisWidth, i = this.xAxisHeight, s = t.width - this.#w();
1159
1555
  return {
1160
- chartArea: { x: this.plotLeft, y: 0, width: t.width - i, height: t.height - e },
1161
- yAxisWidth: i,
1556
+ chartArea: { x: this.plotLeft, y: 0, width: s, height: t.height - i },
1557
+ yAxisWidth: e,
1162
1558
  yAxisPosition: this.yAxisPosition,
1163
- xAxisHeight: e
1559
+ xAxisHeight: i
1164
1560
  };
1165
1561
  }
1166
1562
  getDataInterval() {
1167
- return this.#r;
1563
+ return this.#o;
1168
1564
  }
1169
1565
  /**
1170
1566
  * Update viewport padding at runtime. Refits the visible time range to
@@ -1175,26 +1571,26 @@ class Li extends wt {
1175
1571
  * InfoBar ResizeObserver).
1176
1572
  */
1177
1573
  setPadding(t) {
1178
- const i = this.#l;
1179
- this.#l = F(t);
1180
- const e = this.#l, s = !$(i.left, e.left) || !$(i.right, e.right), r = i.top !== e.top || i.bottom !== e.bottom;
1574
+ const e = this.#d;
1575
+ this.#d = E(t);
1576
+ const i = this.#d, s = !B(e.left, i.left) || !B(e.right, i.right), r = e.top !== i.top || e.bottom !== i.bottom;
1181
1577
  if (s) {
1182
1578
  const { first: n, last: a } = this.getDataBounds();
1183
1579
  if (n !== void 0 && a !== void 0) {
1184
- const o = this.#a.size.media.width - this.yAxisWidth;
1580
+ const o = this.#l.size.media.width - this.#w();
1185
1581
  this.#O(n, a, o);
1186
1582
  }
1187
1583
  }
1188
1584
  if (s || r) {
1189
- this.#g = !0;
1585
+ this.#m = !0;
1190
1586
  const n = performance.now();
1191
- this.#s.onAxisReconfig(n), this.#b(n);
1587
+ this.#e.onAxisReconfig(n), this.#x(n);
1192
1588
  }
1193
1589
  this.syncScales(), this.#i.markDirty(), r && this.emit("viewportChange");
1194
1590
  }
1195
1591
  /** Show or hide the background grid. Takes effect on the next render frame. */
1196
1592
  setGrid(t) {
1197
- this.#F = t.visible, this.#i.markDirty();
1593
+ this.#G = t.visible, this.#i.markDirty();
1198
1594
  }
1199
1595
  /**
1200
1596
  * Set or clear the edge fade masks (see {@link ChartOptions.fade}).
@@ -1204,14 +1600,14 @@ class Li extends wt {
1204
1600
  * the next render frame.
1205
1601
  */
1206
1602
  setFade(t) {
1207
- const i = O(t), e = this.#A;
1208
- i.top === e.top && i.right === e.right && i.left === e.left || (this.#A = i, this.#i.markDirty());
1603
+ const e = Y(t), i = this.#C;
1604
+ e.top === i.top && e.right === i.right && e.left === i.left || (this.#C = e, this.#i.markDirty());
1209
1605
  }
1210
1606
  /** Report the floating header's height (see {@link headerHeight}). The
1211
1607
  * framework wrappers keep it in sync with the measured Title / InfoBar. */
1212
1608
  setHeaderHeight(t) {
1213
- const i = Number.isFinite(t) && t > 0 ? t : 0;
1214
- i !== this.#U && (this.#U = i, this.#f());
1609
+ const e = Number.isFinite(t) && t > 0 ? t : 0;
1610
+ e !== this.#at && (this.#at = e, this.#u());
1215
1611
  }
1216
1612
  /**
1217
1613
  * Set the visual state for one side of the chart. Typically called in
@@ -1222,24 +1618,24 @@ class Li extends wt {
1222
1618
  * The state persists until replaced. `has-more` is accepted for API
1223
1619
  * symmetry and currently renders identically to `idle`.
1224
1620
  */
1225
- setEdgeState(t, i) {
1226
- if (this.#S[t] === i) return;
1227
- const e = this.#S[t] === "loading", s = this.#z[t];
1228
- this.#z[t] = !1, this.#$[t] = e && !s && (i === "idle" || i === "has-more") ? performance.now() : null, this.#S[t] = i, this.#o.markDirty();
1621
+ setEdgeState(t, e) {
1622
+ if (this.#A[t] === e || !this.checkTimeOnlyFeature("EdgeLoader")) return;
1623
+ const i = this.#A[t] === "loading", s = this.#B[t];
1624
+ this.#B[t] = !1, this.#K[t] = i && !s && (e === "idle" || e === "has-more") ? performance.now() : null, this.#A[t] = e, this.#c.markDirty();
1229
1625
  }
1230
1626
  /** Read the current host-declared state for a given edge. */
1231
1627
  getEdgeState(t) {
1232
- return this.#S[t];
1628
+ return this.#A[t];
1233
1629
  }
1234
1630
  /**
1235
1631
  * Alpha for a side's `loading` exit fade, or `null` when none is in
1236
1632
  * flight. Self-cleaning: an elapsed fade clears its start stamp.
1237
1633
  */
1238
- #Pt(t) {
1239
- const i = this.#$[t];
1240
- if (i === null) return null;
1241
- const e = (performance.now() - i) / Wt;
1242
- return e >= 1 ? (this.#$[t] = null, null) : 1 - W(Math.max(0, e));
1634
+ #Zt(t) {
1635
+ const e = this.#K[t];
1636
+ if (e === null) return null;
1637
+ const i = (performance.now() - e) / Jt;
1638
+ return i >= 1 ? (this.#K[t] = null, null) : 1 - W(Math.max(0, i));
1243
1639
  }
1244
1640
  /**
1245
1641
  * Swap the painter used for a side's `loading` spinner — the same
@@ -1247,8 +1643,8 @@ class Li extends wt {
1247
1643
  * (default) falls back to the built-in three-dot spinner. Takes effect
1248
1644
  * immediately if that side is currently in the `loading` state.
1249
1645
  */
1250
- setEdgeIndicator(t, i) {
1251
- this.#vt[t] = i, this.#S[t] === "loading" && this.#o.markDirty();
1646
+ setEdgeIndicator(t, e) {
1647
+ e !== null && !this.checkTimeOnlyFeature("EdgeLoader") || (this.#It[t] = e, this.#A[t] === "loading" && this.#c.markDirty());
1252
1648
  }
1253
1649
  /**
1254
1650
  * Resolve the boundary point's Y position and series kind on the primary
@@ -1258,14 +1654,14 @@ class Li extends wt {
1258
1654
  * `open` (the value immediately preceding the boundary); `right` from the
1259
1655
  * last visible candle's `close`.
1260
1656
  */
1261
- #Ut(t, i) {
1262
- const e = this.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 })[0] ?? this.getSeriesIdsByType("time", { visibleOnly: !0 })[0];
1263
- if (!e) return null;
1264
- const s = this.getDataAtTime(e, i);
1657
+ #ve(t, e) {
1658
+ const i = this.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 })[0] ?? this.getSeriesIdsByType("time", { visibleOnly: !0 })[0];
1659
+ if (!i) return null;
1660
+ const s = this.getDataAtTime(i, e);
1265
1661
  if (!s) return null;
1266
- const r = this.#t.find((n) => n.id === e);
1662
+ const r = this.#t.find((n) => n.id === i);
1267
1663
  return r ? {
1268
- valueY: this.yScale.valueToY(et(t, s)),
1664
+ valueY: this.getYScale(this.#D(r)).valueToY(dt(t, s)),
1269
1665
  seriesKind: r.renderer.kind
1270
1666
  } : null;
1271
1667
  }
@@ -1275,8 +1671,8 @@ class Li extends wt {
1275
1671
  * placeholder shapes to match the real bars that will render once data
1276
1672
  * lands. `null` when the viewport has no meaningful span yet.
1277
1673
  */
1278
- #qt() {
1279
- const t = this.timeScale.barWidthMedia(this.#r);
1674
+ #Se() {
1675
+ const t = this.timeScale.barWidthMedia(this.#o);
1280
1676
  return Number.isFinite(t) && t > 0 ? t : null;
1281
1677
  }
1282
1678
  /**
@@ -1294,18 +1690,18 @@ class Li extends wt {
1294
1690
  * probed, not kind-gated: a kind check here silently kills hover for the
1295
1691
  * next spatial renderer.
1296
1692
  */
1297
- #rt(t) {
1298
- const i = this.#a.size, e = this.#l, s = {
1299
- top: e.top * i.verticalPixelRatio,
1300
- bottom: e.bottom * i.verticalPixelRatio
1693
+ #bt(t) {
1694
+ const e = this.#l.size, i = this.#d, s = {
1695
+ top: i.top * e.verticalPixelRatio,
1696
+ bottom: i.bottom * e.verticalPixelRatio
1301
1697
  };
1302
1698
  let r = !1, n = null;
1303
1699
  for (const a of this.#t) {
1304
1700
  if (!a.renderer.hitTest || !a.renderer.setHoverIndex) continue;
1305
1701
  let o = -1;
1306
1702
  if (t) {
1307
- const l = (t.mediaX - this.plotLeft) * i.horizontalPixelRatio, h = t.mediaY * i.verticalPixelRatio;
1308
- o = a.renderer.hitTest(l, h, i.bitmap.width, i.bitmap.height, s);
1703
+ const h = (t.mediaX - this.plotLeft) * e.horizontalPixelRatio, l = t.mediaY * e.verticalPixelRatio;
1704
+ o = a.renderer.hitTest(h, l, e.bitmap.width, e.bitmap.height, s);
1309
1705
  }
1310
1706
  a.renderer.setHoverIndex(o) && (r = !0), o >= 0 && !n && (n = { seriesId: a.id, index: o, info: a.renderer.getHoverInfo?.(this.#n) ?? null });
1311
1707
  }
@@ -1318,14 +1714,14 @@ class Li extends wt {
1318
1714
  * Returns the first spatial series (pie, heatmap, or a custom spatial kind)
1319
1715
  * whose `hitTest` claims the position, or `null` if none does.
1320
1716
  */
1321
- #Dt(t) {
1322
- const i = this.#a.size, e = this.#l, s = {
1323
- top: e.top * i.verticalPixelRatio,
1324
- bottom: e.bottom * i.verticalPixelRatio
1325
- }, r = (t.mediaX - this.plotLeft) * i.horizontalPixelRatio, n = t.mediaY * i.verticalPixelRatio;
1717
+ #Ut(t) {
1718
+ const e = this.#l.size, i = this.#d, s = {
1719
+ top: i.top * e.verticalPixelRatio,
1720
+ bottom: i.bottom * e.verticalPixelRatio
1721
+ }, r = (t.mediaX - this.plotLeft) * e.horizontalPixelRatio, n = t.mediaY * e.verticalPixelRatio;
1326
1722
  for (const a of this.#t) {
1327
1723
  if (!a.renderer.hitTest) continue;
1328
- const o = a.renderer.hitTest(r, n, i.bitmap.width, i.bitmap.height, s);
1724
+ const o = a.renderer.hitTest(r, n, e.bitmap.width, e.bitmap.height, s);
1329
1725
  if (o >= 0) return { seriesId: a.id, index: o };
1330
1726
  }
1331
1727
  return null;
@@ -1350,70 +1746,81 @@ class Li extends wt {
1350
1746
  * viewport didn't move, the re-derived values are bit-identical and the
1351
1747
  * method exits without emitting.
1352
1748
  */
1353
- #Kt() {
1354
- const t = this.#c;
1355
- if (t === null) return;
1356
- const { from: i, to: e } = this.timeScale.getRange(), s = this.yScale.getRange(), r = Math.abs(e - i) * 1e-9, n = Math.abs(s.max - s.min) * 1e-9, a = 1e-6;
1357
- let o;
1358
- if (this.#q === "pointer") {
1359
- const l = this.timeScale.xToTime(t.mediaX - this.plotLeft), h = this.yScale.yToValue(t.mediaY);
1360
- if (!Number.isFinite(l) || !Number.isFinite(h) || Math.abs(l - t.time) <= r && Math.abs(h - t.y) <= n) return;
1361
- o = { mediaX: t.mediaX, mediaY: t.mediaY, time: l, y: h };
1749
+ #be() {
1750
+ const t = this.#nt;
1751
+ this.#nt = !1;
1752
+ const e = this.#f;
1753
+ if (e === null) return;
1754
+ const { from: i, to: s } = this.timeScale.getRange(), r = this.yScale.getRange(), n = Math.abs(s - i) * 1e-9, a = Math.abs(r.max - r.min) * 1e-9, o = 1e-6;
1755
+ let h;
1756
+ if (this.#ot === "pointer") {
1757
+ const l = this.timeScale.snapToBand(this.timeScale.xToTime(e.mediaX - this.plotLeft)), d = this.yScale.yToValue(e.mediaY);
1758
+ if (!Number.isFinite(l) || !Number.isFinite(d) || Math.abs(l - e.time) <= n && Math.abs(d - e.y) <= a && !t && e.x === this.timeScale.domainValue(e.time)) return;
1759
+ h = { mediaX: e.mediaX, mediaY: e.mediaY, time: l, x: this.timeScale.domainValue(l), y: d };
1362
1760
  } else {
1363
- if (t.time < i || t.time > e) {
1364
- this.#c = null, this.#o.markDirty(), this.emit("crosshairMove", null), this.#rt(null);
1761
+ if (e.time < i || e.time > s) {
1762
+ this.#f = null, this.#c.markDirty(), this.emit("crosshairMove", null), this.#bt(null);
1365
1763
  return;
1366
1764
  }
1367
- const l = this.plotLeft + this.timeScale.timeToX(t.time), h = this.yScale.valueToY(t.y);
1368
- if (!Number.isFinite(l) || !Number.isFinite(h) || Math.abs(l - t.mediaX) <= a && Math.abs(h - t.mediaY) <= a) return;
1369
- o = { mediaX: l, mediaY: h, time: t.time, y: t.y };
1765
+ const l = this.plotLeft + this.timeScale.timeToX(e.time), d = this.yScale.valueToY(e.y);
1766
+ if (!Number.isFinite(l) || !Number.isFinite(d) || Math.abs(l - e.mediaX) <= o && Math.abs(d - e.mediaY) <= o && !t && e.x === this.timeScale.domainValue(e.time)) return;
1767
+ h = { mediaX: l, mediaY: d, time: e.time, x: this.timeScale.domainValue(e.time), y: e.y };
1370
1768
  }
1371
- this.#c = o, this.#o.markDirty(), this.emit("crosshairMove", o), this.#rt(o);
1769
+ this.#f = h, this.#c.markDirty(), this.emit("crosshairMove", h), this.#bt(h);
1372
1770
  }
1373
1771
  /** Tear down the chart: cancel animations, remove listeners, and detach the canvas. */
1374
1772
  destroy() {
1375
1773
  for (const t of this.#t) t.renderer.dispose();
1376
- this.#t = [], this.#u = [], this.#x = [], this.#y = [], this.#D = null, this.#i.destroy(), this.#o.destroy(), this.#P?.destroy(), this.#a.destroy(), this.#tt?.destroy(), this.#tt = null, this.#Q && this.#k?.destroy(), this.#k = null, this.#Q = !1, this.removeAllListeners();
1774
+ this.#t = [], this.#y = [], this.#T = [], this.#b = [], this.#z = null, this.#i.destroy(), this.#c.destroy(), this.#V?.destroy(), this.#l.destroy(), this.#ut?.destroy(), this.#ut = null, this.#dt && this.#M?.destroy(), this.#M = null, this.#dt = !1, this.removeAllListeners();
1377
1775
  }
1378
1776
  /** The attached performance monitor, or `null` when instrumentation is disabled. */
1379
1777
  getPerfMonitor() {
1380
- return this.#k;
1778
+ return this.#M;
1381
1779
  }
1382
1780
  /** Compute the earliest and latest timestamps across all series. */
1383
1781
  getDataBounds() {
1384
- let t, i;
1385
- for (const e of this.#t) {
1386
- if (!e.visible || !b(e.renderer)) continue;
1387
- const s = e.renderer.getTimeBounds();
1388
- s !== null && ((t === void 0 || s.first < t) && (t = s.first), (i === void 0 || s.last > i) && (i = s.last));
1782
+ let t, e;
1783
+ for (const i of this.#t) {
1784
+ if (!i.visible || !v(i.renderer)) continue;
1785
+ const s = i.renderer.getTimeBounds();
1786
+ s !== null && ((t === void 0 || s.first < t) && (t = s.first), (e === void 0 || s.last > e) && (e = s.last));
1389
1787
  }
1390
- return { first: t, last: i };
1788
+ return { first: t, last: e };
1391
1789
  }
1392
1790
  /** Set by `setSeriesData`/bulk-replace paths so the next `onDataChanged`
1393
1791
  * snaps the Y range (instead of easing) and `yScale.getRange()` reflects
1394
1792
  * the new domain synchronously. Cleared by `onDataChanged` after each run. */
1395
- #nt = !1;
1793
+ #kt = !1;
1396
1794
  onDataChanged() {
1397
- if (this.#L > 0) {
1398
- this.#j = !0;
1795
+ if (this.#R > 0) {
1796
+ this.#lt = !0;
1399
1797
  return;
1400
1798
  }
1401
1799
  this.updateDataInterval();
1402
- const { first: t, last: i } = this.getDataBounds();
1403
- t !== void 0 && (this.#w = t), i !== void 0 && (this.#N = this.#e, this.#e = i);
1404
- const e = this.#nt;
1405
- this.#nt = !1;
1406
- const s = !this.#g && t !== void 0;
1407
- e && (this.yScale.tickTracker.reset(), this.timeScale.tickTracker.reset()), this.#xt = e;
1800
+ const { first: t, last: e } = this.getDataBounds();
1801
+ t !== void 0 && (this.#S = t), e !== void 0 && (this.#q = this.#s, this.#s = e);
1802
+ const i = this.#kt;
1803
+ this.#kt = !1;
1804
+ const s = !this.#m && t !== void 0;
1805
+ if (i) {
1806
+ this.yScale.tickTracker.reset(), this.timeScale.tickTracker.reset();
1807
+ for (const n of this.#a.values())
1808
+ n.yScale.tickTracker.reset();
1809
+ }
1810
+ this.#Vt = i;
1408
1811
  const r = performance.now();
1409
- e || s ? (this.#R.seed(r), this.#s.onDataReplaced(r)) : this.#s.onPointAppended(r), this.#s.lastXTarget !== null && (this.#W = !0), t !== void 0 && (this.#g = !0), this.#b(r), this.syncScales(), s ? this.#dt() : this.#i.markDirty(), this.emit("dataUpdate"), this.#f();
1812
+ i || s ? (this.#F.seed(r), this.#e.onDataReplaced(r)) : this.#e.onPointAppended(r), this.#e.lastXTarget !== null && (this.#U = !0), t !== void 0 && (this.#m = !0), this.#x(r), this.syncScales(), s ? this.#wt() : this.#i.markDirty(), this.emit("dataUpdate"), this.#u();
1410
1813
  }
1411
1814
  updateDataInterval() {
1815
+ if (this.#r === "category") {
1816
+ this.#o = 1;
1817
+ return;
1818
+ }
1412
1819
  for (const t of this.#t) {
1413
- if (!b(t.renderer)) continue;
1414
- const i = t.renderer.sampleTimes(20);
1415
- if (i.length >= 2) {
1416
- this.#r = Vt(i);
1820
+ if (!v(t.renderer)) continue;
1821
+ const e = t.renderer.sampleTimes(20);
1822
+ if (e.length >= 2) {
1823
+ this.#o = qt(e);
1417
1824
  break;
1418
1825
  }
1419
1826
  }
@@ -1423,58 +1830,51 @@ class Li extends wt {
1423
1830
  * data regardless of the caller's requested kind. Also reset by paths
1424
1831
  * that intentionally invalidate the current Y baseline (axis bound
1425
1832
  * change, empty viewport). */
1426
- #g = !1;
1833
+ #m = !1;
1427
1834
  /** Whether the engine's X slot has received its first emit. Pre-init X
1428
1835
  * emits are forced to `instant` so first paint snaps to the fitted
1429
1836
  * range — without this guard the engine would ease from `{0,0}` into
1430
1837
  * the new target and the very first frame would render with a
1431
1838
  * zero-width X domain. */
1432
- #W = !1;
1839
+ #U = !1;
1433
1840
  /**
1434
1841
  * One-shot `setVisibleRange` argument applied on the first data arrival.
1435
1842
  * Captured at chart construction from {@link ChartOptions.viewport.initialRange};
1436
1843
  * cleared the moment it's consumed so subsequent data swaps don't snap
1437
1844
  * back to the initial window.
1438
1845
  */
1439
- #_;
1440
- /** Last Y bounds the engine resolved into {@link AnimationState.yRange}.
1441
- * Used by {@link renderMain} to detect Y movement frame-to-frame so
1442
- * `viewportChange` only emits when the Y range actually shifted — DOM
1443
- * axis labels subscribe to that event and re-renders on every tick are
1444
- * expensive. Initialised to NaN so the first real Y emit always passes
1445
- * the change check. */
1446
- #at = Number.NaN;
1447
- #ot = Number.NaN;
1846
+ #it;
1448
1847
  /** Dev warning for values the Y scale can't place — fires once per chart. */
1449
- #Rt = !1;
1848
+ #_t = !1;
1450
1849
  /**
1451
- * Sample the visible data window and return the resolved Y bounds in scale
1452
- * space (see {@link resolveYTarget}), or `null` when no series has data
1453
- * inside the X destination range.
1850
+ * Sample the visible data window and return the resolved Y bounds of one
1851
+ * scale (`undefined` = default) in its scale space (see {@link resolveYTarget}),
1852
+ * or `null` when none of that scale's series has data inside the X destination range.
1454
1853
  * Sampling runs against `logicalRange` (the X target, not the animating
1455
1854
  * current), so Y stays on a stable target while X eases — both dimensions
1456
1855
  * converge together. Clears {@link #yInited} on empty so the next emit
1457
1856
  * with valid data lands as an instant snap.
1458
1857
  */
1459
- #ht(t) {
1460
- const i = t ?? this.#h, e = this.#p.min !== void 0 && this.#p.min !== "auto" && typeof this.#p.min != "number" || this.#p.max !== void 0 && this.#p.max !== "auto" && typeof this.#p.max != "number" ? [] : null, s = this.yScale.getTransform(), r = s === null ? void 0 : this.#Y;
1461
- r && this.#jt(i);
1462
- const n = yt({ targetVisible: i, series: this.#t, allValues: e, plottable: r });
1463
- return n === null ? (this.#g = !1, null) : vt({ raw: n, bounds: this.#p, allValues: e ?? [], transform: s });
1858
+ #_(t, e) {
1859
+ const i = t ?? this.#h, { yScale: s, bounds: r } = this.#pe(e), n = r.min !== void 0 && r.min !== "auto" && typeof r.min != "number" || r.max !== void 0 && r.max !== "auto" && typeof r.max != "number" ? [] : null, a = this.#ye(e), o = s.getTransform(), h = o === null ? void 0 : (d) => s.isPlottable(d);
1860
+ h && this.#ke({ window: i, series: a, yScale: s });
1861
+ const l = Vt({ targetVisible: i, series: a, allValues: n, plottable: h });
1862
+ return l === null ? (e === void 0 && (this.#m = !1), null) : (e !== void 0 && (this.#m = !0), zt({ raw: l, bounds: r, allValues: n ?? [], transform: o }));
1464
1863
  }
1465
1864
  /** Dev-only sweep behind {@link warnUnplottableValues}; stops once it has warned. */
1466
- #jt(t) {
1467
- if (!(this.#Rt || process.env.NODE_ENV === "production"))
1468
- for (const i of this.#t) {
1469
- if (!i.visible || !b(i.renderer)) continue;
1470
- const e = i.renderer.getValueRange(t.from, t.to);
1471
- if (e === null) continue;
1472
- const s = (i.renderer.stacking === "normal" ? e.min < 0 && !this.yScale.isPlottable(e.min) : !this.yScale.isPlottable(e.min)) ? e.min : this.yScale.isPlottable(e.max) ? null : e.max;
1473
- if (s !== null) {
1474
- this.#Rt = !0, mt({ seriesId: i.id, value: s });
1475
- return;
1476
- }
1865
+ #ke(t) {
1866
+ if (this.#_t || process.env.NODE_ENV === "production") return;
1867
+ const { window: e, series: i, yScale: s } = t;
1868
+ for (const r of i) {
1869
+ if (!r.visible || !v(r.renderer)) continue;
1870
+ const n = r.renderer.getValueRange(e.from, e.to);
1871
+ if (n === null) continue;
1872
+ const a = (r.renderer.stacking === "normal" || r.renderer.stacking === "percent" ? n.min < 0 && !s.isPlottable(n.min) : !s.isPlottable(n.min)) ? n.min : s.isPlottable(n.max) ? null : n.max;
1873
+ if (a !== null) {
1874
+ this.#_t = !0, At({ seriesId: r.id, value: a });
1875
+ return;
1477
1876
  }
1877
+ }
1478
1878
  }
1479
1879
  /**
1480
1880
  * Engine pull-on-demand X target. Called by the engine's `computeXTarget`
@@ -1494,29 +1894,31 @@ class Li extends wt {
1494
1894
  * - **autoscroll off** → `null` (no X retarget; user has panned away
1495
1895
  * from the tail).
1496
1896
  */
1497
- #Jt() {
1498
- if (this.#w === null || this.#e === null) return null;
1499
- const { from: t, to: i } = this.#h, e = t === 0 && i === 0, s = this.#a.size.media.width - this.yAxisWidth;
1500
- if (e) {
1501
- if (this.#O(this.#w, this.#e, s), this.#_ !== void 0 && (this.setVisibleRange(this.#_), this.#_ = void 0, !this.#m && this.#e !== null)) {
1897
+ #we() {
1898
+ if (this.#S === null || this.#s === null) return null;
1899
+ const { from: t, to: e } = this.#h, i = t === 0 && e === 0, s = this.#l.size.media.width - this.#w();
1900
+ if (this.#r === "category")
1901
+ return this.#O(this.#S, this.#s, s), this.#h;
1902
+ if (i) {
1903
+ if (this.#O(this.#S, this.#s, s), this.#it !== void 0 && (this.setVisibleRange(this.#it), this.#it = void 0, !this.#p && this.#s !== null)) {
1502
1904
  const { to: n } = this.#h;
1503
- this.#e >= n && this.#e - n <= this.#r * 1.5 && (this.#m = !0);
1905
+ this.#s >= n && this.#s - n <= this.#o * 1.5 && (this.#p = !0);
1504
1906
  }
1505
1907
  return this.#h;
1506
1908
  }
1507
- if (!this.#m) return null;
1508
- if (this.#xt)
1509
- return this.#O(this.#w, this.#e, s), this.#h;
1510
- const r = gt({
1909
+ if (!this.#p) return null;
1910
+ if (this.#Vt)
1911
+ return this.#O(this.#S, this.#s, s), this.#h;
1912
+ const r = Pt({
1511
1913
  currentLogical: this.#h,
1512
- lastTime: this.#e,
1513
- prevDataEnd: this.#N,
1514
- dataInterval: this.#r,
1515
- paddingRight: this.#l.right,
1914
+ lastTime: this.#s,
1915
+ prevDataEnd: this.#q,
1916
+ dataInterval: this.#o,
1917
+ paddingRight: this.#d.right,
1516
1918
  chartWidth: s,
1517
- holdUntilFilled: this.#M
1919
+ holdUntilFilled: this.#L
1518
1920
  });
1519
- return r.releaseHold && (this.#M = !1), r.reengageAutoScroll && (this.#m = !0), r.newLogical === null ? null : (this.#R.observe(performance.now()), this.#s.setXSettleMs(this.#R.pickSettleMs(this.#d.axis.x.settleMs)), this.#C(r.newLogical, { emitChange: !1, skipValidation: !0 }), this.#N = this.#e, r.newLogical);
1921
+ return r.releaseHold && (this.#L = !1), r.reengageAutoScroll && (this.#p = !0), r.newLogical === null ? null : (this.#F.observe(performance.now()), this.#e.setXSettleMs(this.#F.pickSettleMs(this.#g.axis.x.settleMs)), this.#H(r.newLogical, { emitChange: !1, skipValidation: !0 }), this.#q = this.#s, r.newLogical);
1520
1922
  }
1521
1923
  /**
1522
1924
  * Fit the viewport's logical X range around a data span. Mirrors the
@@ -1524,16 +1926,21 @@ class Li extends wt {
1524
1926
  * forces autoscroll on (data tail is in the new window by construction),
1525
1927
  * commits the new range via `viewport.setRange` (which emits `change`).
1526
1928
  */
1527
- #O(t, i, e) {
1528
- const s = this.#l, r = nt({
1929
+ #O(t, e, i) {
1930
+ const s = this.timeScale.bandRange();
1931
+ if (this.#r === "category" && s !== null) {
1932
+ this.#L = !1, this.#p = !0, this.#H(s, { emitChange: !0, skipValidation: !0 });
1933
+ return;
1934
+ }
1935
+ const r = this.#d, n = ft({
1529
1936
  firstTime: t,
1530
- lastTime: i,
1531
- dataInterval: this.#r,
1532
- maxVisibleBars: this.#St,
1533
- chartWidth: e,
1534
- padding: { left: s.left, right: s.right }
1937
+ lastTime: e,
1938
+ dataInterval: this.#o,
1939
+ maxVisibleBars: this.#zt,
1940
+ chartWidth: i,
1941
+ padding: { left: r.left, right: r.right }
1535
1942
  });
1536
- this.#M = !1, this.#m = !0, this.#C(r, { emitChange: !0 });
1943
+ this.#L = !1, this.#p = !0, this.#H(n, { emitChange: !0 });
1537
1944
  }
1538
1945
  /**
1539
1946
  * Commit a programmatic zoom — `fitContent` or `setVisibleRange`. Reads
@@ -1549,15 +1956,15 @@ class Li extends wt {
1549
1956
  * passes `xEase: true` so X eases instead — viewport and axis converge
1550
1957
  * on the same frame.
1551
1958
  */
1552
- #G(t) {
1553
- const i = this.#h;
1554
- if (i.to <= i.from) return;
1555
- const e = performance.now();
1556
- if (!this.#W || !this.#g) {
1557
- this.#Ct(e);
1959
+ #st(t) {
1960
+ const e = this.#h;
1961
+ if (e.to <= e.from) return;
1962
+ const i = performance.now();
1963
+ if (!this.#U || !this.#m) {
1964
+ this.#Gt(i);
1558
1965
  return;
1559
1966
  }
1560
- this.#s.onProgrammaticZoom({ xTarget: i, xEase: t?.xEase }, e), this.#b(e);
1967
+ this.#e.onProgrammaticZoom({ xTarget: e, xEase: t?.xEase }, i), this.#x(i);
1561
1968
  }
1562
1969
  /**
1563
1970
  * Commit the current logical X to the engine on the *gesture* profile:
@@ -1571,15 +1978,15 @@ class Li extends wt {
1571
1978
  * instead of lagging on the long sticky contract. First paint still snaps
1572
1979
  * (same as the programmatic path) so synchronous readers see final values.
1573
1980
  */
1574
- #Qt() {
1981
+ #Te() {
1575
1982
  const t = this.#h;
1576
1983
  if (t.to <= t.from) return;
1577
- const i = performance.now();
1578
- if (!this.#W || !this.#g) {
1579
- this.#Ct(i);
1984
+ const e = performance.now();
1985
+ if (!this.#U || !this.#m) {
1986
+ this.#Gt(e);
1580
1987
  return;
1581
1988
  }
1582
- this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#b(i);
1989
+ this.#e.onPanZoom({ xTarget: t, yAuto: !0 }, e), this.#x(e);
1583
1990
  }
1584
1991
  /**
1585
1992
  * First-paint snap: land both axes on the current logical X immediately
@@ -1588,9 +1995,13 @@ class Li extends wt {
1588
1995
  * commits — both must snap before the chart has painted once so a
1589
1996
  * synchronous `getYRange()` / `getVisibleRange()` read sees final values.
1590
1997
  */
1591
- #Ct(t) {
1592
- const i = this.#h, e = this.#ht(i);
1593
- this.#W = !0, e !== null && (this.#g = !0), this.#s.snap({ x: i, y: e ?? void 0 }, t), this.#b(t);
1998
+ #Gt(t) {
1999
+ const e = this.#h, i = this.#_(e), s = {};
2000
+ for (const r of this.#a.keys()) {
2001
+ const n = this.#_(e, r);
2002
+ n !== null && (s[r] = n);
2003
+ }
2004
+ this.#U = !0, i !== null && (this.#m = !0), this.#e.snap({ x: e, y: i ?? void 0, scales: s }, t), this.#x(t);
1594
2005
  }
1595
2006
  /**
1596
2007
  * Validate and commit a new logical X range. Mirrors the old
@@ -1610,9 +2021,9 @@ class Li extends wt {
1610
2021
  * see `setVisibleRange`'s gesture path, where a per-frame echo would
1611
2022
  * otherwise re-arm and stall the in-flight Y ease.
1612
2023
  */
1613
- #C(t, i) {
1614
- const { from: e, to: s } = t;
1615
- return !Number.isFinite(e) || !Number.isFinite(s) || s <= e || !i.skipValidation && (s - e) / this.#r < 2 || this.#h.from === e && this.#h.to === s ? !1 : (this.#h = { from: e, to: s }, i.emitChange && (this.syncScales(), this.#i?.markDirty(), this.emit("viewportChange")), !0);
2024
+ #H(t, e) {
2025
+ const { from: i, to: s } = t;
2026
+ return !Number.isFinite(i) || !Number.isFinite(s) || s <= i || !e.skipValidation && (s - i) / this.#o < 2 || this.#h.from === i && this.#h.to === s ? !1 : (this.#h = { from: i, to: s }, e.emitChange && (this.syncScales(), this.#i?.markDirty(), this.emit("viewportChange")), !0);
1616
2027
  }
1617
2028
  /**
1618
2029
  * Apply pixel-padding to the engine's Y (scale space) and return it as
@@ -1621,28 +2032,25 @@ class Li extends wt {
1621
2032
  * that has an explicit (fixed) axis bound. Padding in scale space keeps
1622
2033
  * the pixel gutters even on a log axis.
1623
2034
  */
1624
- #It(t) {
1625
- const { min: i, max: e, chartHeight: s } = t, { min: r, max: n } = this.#ti(), a = e - i, o = s > 0 ? this.#l.top / s * a : 0, l = s > 0 ? this.#l.bottom / s * a : 0, h = {
1626
- min: r ? i : i - l,
1627
- max: n ? e : e + o
1628
- }, d = this.yScale.getTransform();
1629
- if (d === null) return h;
1630
- const m = d.inverse(h.min), c = d.inverse(h.max), f = this.yScale.isPlottable(m) && m <= d.inverse(i), g = this.yScale.isPlottable(c) && c >= d.inverse(e);
2035
+ #Ae(t) {
2036
+ const { slot: e, min: i, max: s, chartHeight: r } = t, { yScale: n } = e, { min: a, max: o } = this.#Me(e), h = s - i, l = r > 0 ? this.#d.top / r * h : 0, d = r > 0 ? this.#d.bottom / r * h : 0, f = {
2037
+ min: a ? i : i - d,
2038
+ max: o ? s : s + l
2039
+ }, c = n.getTransform();
2040
+ if (c === null) return f;
2041
+ const m = c.inverse(f.min), y = c.inverse(f.max), u = n.isPlottable(m) && m <= c.inverse(i), g = n.isPlottable(y) && y >= c.inverse(s);
1631
2042
  return {
1632
- min: f ? m : d.inverse(i),
1633
- max: g ? c : d.inverse(e)
2043
+ min: u ? m : c.inverse(i),
2044
+ max: g ? y : c.inverse(s)
1634
2045
  };
1635
2046
  }
1636
- /** Which Y edges the user pinned — a pinned edge takes no padding. */
1637
- #ti() {
2047
+ /** Which Y edges of a scale the user pinned — a pinned edge takes no padding. */
2048
+ #Me(t) {
1638
2049
  return {
1639
- min: this.#Ht(this.#p.min),
1640
- max: this.#Ht(this.#p.max)
2050
+ min: J(t.bounds.min, t.yScale),
2051
+ max: J(t.bounds.max, t.yScale)
1641
2052
  };
1642
2053
  }
1643
- #Ht(t) {
1644
- return t === void 0 || t === "auto" ? !1 : !(typeof t == "number" && !this.yScale.isPlottable(t));
1645
- }
1646
2054
  /**
1647
2055
  * Shift the visible time range by `timeDelta` ms. Hard-clamped so at
1648
2056
  * least one data point stays on screen — no rubber-band, no overshoot.
@@ -1653,18 +2061,18 @@ class Li extends wt {
1653
2061
  * as a gesture for visual easing. Public so consumers can drive pan
1654
2062
  * programmatically.
1655
2063
  */
1656
- pan(t, i = this.#E) {
1657
- i > 0 && (this.#E = i), this.#M = !1;
1658
- const e = dt({
2064
+ pan(t, e = this.#J) {
2065
+ e > 0 && (this.#J = e), this.#L = !1;
2066
+ const i = St({
1659
2067
  currentLogical: this.#h,
1660
2068
  timeDelta: t,
1661
- chartWidth: i,
1662
- dataInterval: this.#r,
1663
- padding: { left: this.#l.left, right: this.#l.right },
1664
- dataStart: this.#w,
1665
- dataEnd: this.#e
2069
+ chartWidth: e,
2070
+ dataInterval: this.#o,
2071
+ padding: { left: this.#d.left, right: this.#d.right },
2072
+ dataStart: this.#S,
2073
+ dataEnd: this.#s
1666
2074
  });
1667
- e.newLogical !== null && (this.#m = !e.autoScrollOff, this.#C(e.newLogical, { emitChange: !0 }), e.edgeReached !== null && (this.#pt[e.edgeReached.side] = e.edgeReached.boundaryTime, this.#yt?.(e.edgeReached)), this.#R.pause(), this.#zt());
2075
+ i.newLogical !== null && (this.#p = !i.autoScrollOff, this.#H(i.newLogical, { emitChange: !0 }), i.edgeReached !== null && (this.#Dt[i.edgeReached.side] = i.edgeReached.boundaryTime, this.#Rt?.(i.edgeReached)), this.#F.pause(), this.#Kt());
1668
2076
  }
1669
2077
  /**
1670
2078
  * Zoom around a time anchor. `factor < 1` zooms in, `> 1` zooms out.
@@ -1681,37 +2089,37 @@ class Li extends wt {
1681
2089
  * and produce a visible jump). Cursor-anchored zoom is intentionally
1682
2090
  * sacrificed in follow-live mode — pan first to inspect history.
1683
2091
  */
1684
- zoomAt(t, i, e = this.#E) {
1685
- e > 0 && (this.#E = e), this.#M = !1;
1686
- const s = ut({
2092
+ zoomAt(t, e, i = this.#J) {
2093
+ i > 0 && (this.#J = i), this.#L = !1;
2094
+ const s = bt({
1687
2095
  currentLogical: this.#h,
1688
2096
  centerTime: t,
1689
- factor: i,
1690
- chartWidth: e,
1691
- dataInterval: this.#r,
1692
- padding: { left: this.#l.left, right: this.#l.right },
1693
- dataStart: this.#w,
1694
- dataEnd: this.#e
2097
+ factor: e,
2098
+ chartWidth: i,
2099
+ dataInterval: this.#o,
2100
+ padding: { left: this.#d.left, right: this.#d.right },
2101
+ dataStart: this.#S,
2102
+ dataEnd: this.#s
1695
2103
  });
1696
2104
  if (s.newLogical === null) return;
1697
2105
  let r = s.newLogical;
1698
- if (this.#m && this.#e !== null) {
1699
- const n = r.to - r.from, a = B(this.#l.right, n, this.#r, e), o = this.#e + a;
1700
- r = { from: o - n, to: o }, this.#N = this.#e;
2106
+ if (this.#p && this.#s !== null) {
2107
+ const n = r.to - r.from, a = N(this.#d.right, n, this.#o, i), o = this.#s + a;
2108
+ r = { from: o - n, to: o }, this.#q = this.#s;
1701
2109
  }
1702
- this.#C(r, { emitChange: !0 }), this.#R.pause(), this.#zt();
2110
+ this.#H(r, { emitChange: !0 }), this.#F.pause(), this.#Kt();
1703
2111
  }
1704
2112
  /**
1705
2113
  * Common tail for `pan` / `zoomAt`: routes the just-committed `#logical`
1706
2114
  * to the engine as a gesture so the X spring retargets and the Y spring
1707
2115
  * eases over `animations.axis.y.gesture`.
1708
2116
  */
1709
- #zt() {
2117
+ #Kt() {
1710
2118
  const t = this.#h;
1711
2119
  if (t.to <= t.from) return;
1712
- this.#W = !0, this.#g = !0;
1713
- const i = performance.now();
1714
- this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#b(i);
2120
+ this.#U = !0, this.#m = !0;
2121
+ const e = performance.now();
2122
+ this.#e.onPanZoom({ xTarget: t, yAuto: !0 }, e), this.#x(e);
1715
2123
  }
1716
2124
  /**
1717
2125
  * Tick the engine at the given wall clock and push the resolved X / Y
@@ -1726,11 +2134,27 @@ class Li extends wt {
1726
2134
  * `effectiveNow === startWall` and the prune step keeps zero-duration
1727
2135
  * events long enough for the slot processor to snap them.
1728
2136
  */
1729
- #b(t) {
1730
- const i = this.#s.tick(t), e = this.#a.size;
1731
- if (e.media.width === 0 || e.media.height === 0) return;
1732
- const s = e.media.height - this.xAxisHeight;
1733
- this.#B = this.#It({ min: i.yRange.min, max: i.yRange.max, chartHeight: s }), this.#at = i.yRange.min, this.#ot = i.yRange.max, this.syncScales();
2137
+ #x(t) {
2138
+ const e = this.#e.tick(t), i = this.#l.size;
2139
+ if (i.media.width === 0 || i.media.height === 0) return;
2140
+ const s = i.media.height - this.xAxisHeight;
2141
+ for (const { slot: r, range: n } of this.#jt(e))
2142
+ this.#qt({ slot: r, range: n, chartHeight: s });
2143
+ this.syncScales();
2144
+ }
2145
+ /** Each scale's slot with the engine's current Y (scale space) for it. */
2146
+ #jt(t) {
2147
+ const e = [{ slot: this.#P, range: t.yRange }];
2148
+ for (const [i, s] of this.#a) {
2149
+ const r = this.#e.getScaleYRange(i);
2150
+ r !== null && e.push({ slot: s, range: r });
2151
+ }
2152
+ return e;
2153
+ }
2154
+ /** Pad an engine Y (scale space) into the slot's value range and remember it. */
2155
+ #qt(t) {
2156
+ const { slot: e, range: i, chartHeight: s } = t, r = e.range;
2157
+ e.range = this.#Ae({ slot: e, min: i.min, max: i.max, chartHeight: s }), e.prevMin = i.min, e.prevMax = i.max, (r.min !== e.range.min || r.max !== e.range.max) && e !== this.#P && (this.#nt = !0);
1734
2158
  }
1735
2159
  /**
1736
2160
  * Lightweight scale sync: updates timeScale/yScale from current viewport state
@@ -1738,90 +2162,100 @@ class Li extends wt {
1738
2162
  * handler, so DOM axis components always read fresh coordinates on re-render.
1739
2163
  */
1740
2164
  syncScales() {
1741
- const t = this.#a.size;
2165
+ const t = this.#l.size;
1742
2166
  if (t.media.width === 0 || t.media.height === 0) return;
1743
- const i = t.media.width - this.yAxisWidth, e = t.media.height - this.xAxisHeight;
2167
+ const e = t.media.width - this.#w(), i = t.media.height - this.xAxisHeight;
1744
2168
  this.timeScale.update(
1745
- this.#s.getAnimationState().xRange,
1746
- i,
2169
+ this.#e.getAnimationState().xRange,
2170
+ e,
1747
2171
  t.horizontalPixelRatio,
1748
- this.#r
1749
- ), this.yScale.update(this.#B, e, t.verticalPixelRatio);
2172
+ this.#o
2173
+ ), this.yScale.update(this.#P.range, i, t.verticalPixelRatio);
2174
+ for (const s of this.#a.values())
2175
+ s.yScale.update(s.range, i, t.verticalPixelRatio);
1750
2176
  }
1751
2177
  /** Expensive: background, grid, all series. Only on data/viewport/resize change. */
1752
2178
  renderMain(t) {
1753
- const i = this.#a.size;
1754
- if (i.media.width === 0 || i.media.height === 0) return;
1755
- const e = typeof t == "number" ? t : performance.now(), s = this.#s.tick(e);
2179
+ const e = this.#l.size;
2180
+ if (e.media.width === 0 || e.media.height === 0) return;
2181
+ const i = typeof t == "number" ? t : performance.now(), s = this.#e.tick(i);
1756
2182
  s.animating && this.#i.markDirty();
1757
- const r = s.yRange.min, n = s.yRange.max, a = r !== this.#at || n !== this.#ot;
1758
- if (a) {
1759
- const c = i.media.height - this.xAxisHeight;
1760
- this.#B = this.#It({ min: r, max: n, chartHeight: c }), this.#at = r, this.#ot = n;
1761
- }
1762
- if (this.syncScales(), this.#Kt(), this.#e !== null && !this.#m) {
1763
- const c = this.#s.lastXTarget ?? this.#h, f = c.to - c.from;
1764
- if (f > 0) {
1765
- const g = this.#a.size.media.width - this.yAxisWidth, u = B(this.#l.right, f, this.#r, g), p = this.#r * 0.5;
1766
- Math.abs(c.to - (this.#e + u)) < p && (this.#m = !0);
2183
+ const r = e.media.height - this.xAxisHeight;
2184
+ let n = !1;
2185
+ for (const { slot: c, range: m } of this.#jt(s))
2186
+ m.min === c.prevMin && m.max === c.prevMax || (this.#qt({ slot: c, range: m, chartHeight: r }), n = !0);
2187
+ if (this.syncScales(), this.#be(), this.#s !== null && !this.#p) {
2188
+ const c = this.#e.lastXTarget ?? this.#h, m = c.to - c.from;
2189
+ if (m > 0) {
2190
+ const y = this.#l.size.media.width - this.#w(), u = N(this.#d.right, m, this.#o, y), g = this.#o * 0.5;
2191
+ Math.abs(c.to - (this.#s + u)) < g && (this.#p = !0);
1767
2192
  }
1768
2193
  }
1769
- a && this.emit("viewportChange");
1770
- let o = !1, l = !1, h = !1;
1771
- this.#a.useMainLayer((c) => {
1772
- const { context: f, bitmapSize: g } = c, u = (i.media.width - this.yAxisWidth) * i.horizontalPixelRatio, p = (i.media.height - this.xAxisHeight) * i.verticalPixelRatio, S = this.#I(c);
1773
- f.clearRect(0, 0, g.width, g.height), f.save(), f.translate(S, 0), f.beginPath(), f.rect(0, 0, this.#Bt(c, u), p), f.clip(), this.yScale.tickTracker.setCurrentTicks(this.yScale.niceTickValues()), this.timeScale.tickTracker.setCurrentTicks(this.timeScale.niceTickValues(this.#r).ticks), this.yScale.tickTracker.tick(e), this.timeScale.tickTracker.tick(e);
1774
- const A = this.yScale.tickTracker.snapshot(), k = this.timeScale.tickTracker.snapshot();
1775
- o = A.isAnimating, l = k.isAnimating, (A.isAnimating || k.isAnimating || s.animating) && this.emit("tickFrame");
1776
- const T = this.#F ? 1 : 0;
1777
- if ((T === 0 || A.entries.length > 0 || k.entries.length > 0) && T !== this.#H.target) {
1778
- const v = Q() ? 0 : this.#d.axis.gridMs;
1779
- this.#H.setTarget(T, { now: e, duration: v });
2194
+ n && this.emit("viewportChange");
2195
+ let a = !1, o = !1, h = !1, l = !1;
2196
+ this.#l.useMainLayer((c) => {
2197
+ const { context: m, bitmapSize: y } = c, u = (e.media.width - this.#w()) * e.horizontalPixelRatio, g = (e.media.height - this.xAxisHeight) * e.verticalPixelRatio, S = this.#W(c);
2198
+ m.clearRect(0, 0, y.width, y.height), m.save(), m.translate(S, 0), m.beginPath(), m.rect(0, 0, this.#re(c, u), g), m.clip(), this.yScale.tickTracker.setCurrentTicks(this.yScale.niceTickValues()), this.timeScale.tickTracker.setCurrentTicks(this.timeScale.niceTickValues(this.#o).ticks), this.yScale.tickTracker.tick(i), this.timeScale.tickTracker.tick(i);
2199
+ for (const x of this.#a.values()) {
2200
+ const b = x.yScale.tickTracker;
2201
+ b.setCurrentTicks(x.yScale.niceTickValues()), b.tick(i) && (h = !0);
1780
2202
  }
1781
- h = this.#H.tick(e);
1782
- const D = this.#H.current, R = {
2203
+ const w = this.yScale.tickTracker.snapshot(), T = this.timeScale.tickTracker.snapshot();
2204
+ a = w.isAnimating, o = T.isAnimating, (w.isAnimating || T.isAnimating || h || s.animating) && this.emit("tickFrame");
2205
+ const A = this.#G ? 1 : 0;
2206
+ if ((A === 0 || w.entries.length > 0 || T.entries.length > 0) && A !== this.#E.target) {
2207
+ const x = ht() ? 0 : this.#g.axis.gridMs;
2208
+ this.#E.setTarget(A, { now: i, duration: x });
2209
+ }
2210
+ l = this.#E.tick(i);
2211
+ const R = this.#E.current, I = this.#r === "category" ? ie : T, H = {
1783
2212
  scope: c,
1784
2213
  timeScale: this.timeScale,
1785
2214
  yScale: this.yScale,
1786
2215
  theme: this.#n,
1787
- yTicks: A,
1788
- timeTicks: k,
1789
- alpha: D
1790
- }, L = this.#$t(c, u);
1791
- L === 0 && E(R), this.#_t(c, u, p);
1792
- const C = this.#l, G = { top: C.top, bottom: C.bottom }, U = this.#Tt(s), I = this.#k;
1793
- for (const v of this.#t) {
1794
- const w = v.renderer.getAlpha?.() ?? (v.visible ? 1 : 0);
1795
- if (w <= 0) continue;
1796
- const z = {
2216
+ yTicks: w,
2217
+ timeTicks: I,
2218
+ alpha: R
2219
+ }, M = this.#ie(c, u);
2220
+ M === 0 && X(H), this.#ce(c, u, g);
2221
+ const V = this.#d, et = { top: V.top, bottom: V.bottom }, it = this.#ft(s), z = this.#M;
2222
+ for (const x of this.#t) {
2223
+ this.#fe(x);
2224
+ const b = x.renderer.getAlpha?.() ?? (x.visible ? 1 : 0);
2225
+ if (b <= 0) continue;
2226
+ const P = this.#D(x), st = this.getYScale(P), O = {
1797
2227
  scope: c,
1798
2228
  timeScale: this.timeScale,
1799
- yScale: this.yScale,
2229
+ yScale: st,
1800
2230
  theme: this.#n,
1801
- dataInterval: this.#r,
1802
- padding: G,
1803
- state: U,
1804
- seriesId: v.id
1805
- }, V = f.globalAlpha;
1806
- if (w < 1 && (f.globalAlpha = V * w), I) {
1807
- const q = performance.now();
1808
- v.renderer.render(z), I.recordSeries(v.id, performance.now() - q, e);
2231
+ dataInterval: this.#o,
2232
+ padding: et,
2233
+ state: P === void 0 ? it : this.#Wt(s, P),
2234
+ seriesId: x.id
2235
+ }, $ = m.globalAlpha;
2236
+ if (b < 1 && (m.globalAlpha = $ * b), z) {
2237
+ const rt = performance.now();
2238
+ x.renderer.render(O), z.recordSeries(x.id, performance.now() - rt, i);
1809
2239
  } else
1810
- v.renderer.render(z);
1811
- w < 1 && (f.globalAlpha = V);
2240
+ x.renderer.render(O);
2241
+ b < 1 && (m.globalAlpha = $);
1812
2242
  }
1813
- f.restore();
1814
- const H = { left: S, width: u, height: p };
1815
- L === 0 ? (this.#Wt({ scope: c, pane: H, timeTicks: k, alpha: D }), this.#lt(c, u)) : (this.#lt(c, u), this.#si({ grid: R, pane: H, overhang: L })), this.#Ft(c, u), this.#Vt(c, p);
1816
- }), this.#it();
2243
+ m.restore();
2244
+ const F = { left: S, width: u, height: g };
2245
+ M === 0 ? (this.#Qt({ scope: c, pane: F, timeTicks: I, alpha: R }), this.#$(c, u, "left"), this.#$(c, u, "right")) : (this.#$(c, u, "left"), this.#Le({ grid: H, pane: F, overhang: M }), this.#$(c, u, "right")), this.#ee(c, u), this.#Jt(c, g);
2246
+ }), this.#pt();
1817
2247
  let d = !1;
1818
2248
  for (const c of this.#t)
1819
2249
  if (c.renderer.needsAnimation) {
1820
2250
  d = !0, this.#i.markDirty();
1821
2251
  break;
1822
2252
  }
1823
- const m = o || l;
1824
- (m || h) && this.#i.markDirty(), !s.animating && !d && !m && (this.yScale.tickTracker.markArmed(), this.timeScale.tickTracker.markArmed());
2253
+ const f = a || o || h;
2254
+ if ((f || l) && this.#i.markDirty(), !s.animating && !d && !f) {
2255
+ this.yScale.tickTracker.markArmed(), this.timeScale.tickTracker.markArmed();
2256
+ for (const c of this.#a.values())
2257
+ c.yScale.tickTracker.markArmed();
2258
+ }
1825
2259
  }
1826
2260
  /**
1827
2261
  * Erase the main layer's alpha across the top fade zone so content
@@ -1830,12 +2264,12 @@ class Li extends wt {
1830
2264
  * a paint-over — so the container background behind the canvas (solid or
1831
2265
  * CSS gradient) shows through untouched.
1832
2266
  */
1833
- #Vt(t, i) {
1834
- if (this.#A.top <= 0) return;
1835
- const e = Math.min(Math.round(this.#A.top * t.verticalPixelRatio), Math.round(i));
1836
- if (e < 1) return;
2267
+ #Jt(t, e) {
2268
+ if (this.#C.top <= 0) return;
2269
+ const i = Math.min(Math.round(this.#C.top * t.verticalPixelRatio), Math.round(e));
2270
+ if (i < 1) return;
1837
2271
  const { context: s } = t;
1838
- s.save(), s.globalCompositeOperation = "destination-out", s.fillStyle = this.#ni(s, e), s.fillRect(0, 0, t.bitmapSize.width, e), s.restore();
2272
+ s.save(), s.globalCompositeOperation = "destination-out", s.fillStyle = this.#Re(s, i), s.fillRect(0, 0, t.bitmapSize.width, i), s.restore();
1839
2273
  }
1840
2274
  /**
1841
2275
  * Run the vertical gridlines a short stub past the pane floor, dissolving
@@ -1845,23 +2279,23 @@ class Li extends wt {
1845
2279
  * nothing else paints) and faded with the same destination-out ramp the
1846
2280
  * edge masks use, so the taper stays correct over any background.
1847
2281
  */
1848
- #Wt(t) {
1849
- const { scope: i, pane: e, timeTicks: s, alpha: r } = t;
1850
- if (!this.#F && r <= 0.01 || this.xAxisHeight === 0) return;
1851
- const { context: n, horizontalPixelRatio: a, verticalPixelRatio: o } = i, l = Math.round(e.height), h = Math.round(P * o);
1852
- if (h < 1) return;
2282
+ #Qt(t) {
2283
+ const { scope: e, pane: i, timeTicks: s, alpha: r } = t;
2284
+ if (!this.#G && r <= 0.01 || this.xAxisHeight === 0) return;
2285
+ const { context: n, horizontalPixelRatio: a, verticalPixelRatio: o } = e, h = Math.round(i.height), l = Math.round(D * o);
2286
+ if (l < 1) return;
1853
2287
  const d = this.#n.grid;
1854
- n.save(), n.strokeStyle = d.color, d.style === "dashed" ? n.setLineDash([4 * a, 4 * a]) : d.style === "dotted" && n.setLineDash([1 * a, 3 * a]), n.lineDashOffset = l, n.lineWidth = It(a);
1855
- const m = Ht(a), c = Math.round(e.width);
1856
- for (const { value: f, opacity: g } of s.entries) {
1857
- const u = g * r;
2288
+ n.save(), n.strokeStyle = d.color, d.style === "dashed" ? n.setLineDash([4 * a, 4 * a]) : d.style === "dotted" && n.setLineDash([1 * a, 3 * a]), n.lineDashOffset = h, n.lineWidth = Gt(a);
2289
+ const f = Kt(a), c = Math.round(i.width);
2290
+ for (const { value: m, opacity: y } of s.entries) {
2291
+ const u = y * r;
1858
2292
  if (u <= 0.01) continue;
1859
- const p = Math.round(this.timeScale.timeToBitmapX(f)) + m;
1860
- if (p < 0 || p > c) continue;
1861
- const S = e.left + p;
1862
- n.globalAlpha = u, n.beginPath(), n.moveTo(S, l), n.lineTo(S, l + h), n.stroke();
2293
+ const g = Math.round(this.timeScale.timeToBitmapX(m)) + f;
2294
+ if (g < 0 || g > c) continue;
2295
+ const S = i.left + g;
2296
+ n.globalAlpha = u, n.beginPath(), n.moveTo(S, h), n.lineTo(S, h + l), n.stroke();
1863
2297
  }
1864
- n.restore(), this.#Ot(i, e.height);
2298
+ n.restore(), this.#te(e, i.height);
1865
2299
  }
1866
2300
  /**
1867
2301
  * Erase ramp over the below-pane overrun strip — the gridline tail stubs
@@ -1869,12 +2303,12 @@ class Li extends wt {
1869
2303
  * erased at the pane floor, total at the strip's bottom edge, so whatever
1870
2304
  * crosses the floor tapers out short of the time-axis labels.
1871
2305
  */
1872
- #Ot(t, i) {
2306
+ #te(t, e) {
1873
2307
  if (this.xAxisHeight === 0) return;
1874
- const e = Math.round(i), s = Math.round(P * t.verticalPixelRatio);
2308
+ const i = Math.round(e), s = Math.round(D * t.verticalPixelRatio);
1875
2309
  if (s < 1) return;
1876
2310
  const { context: r } = t;
1877
- r.save(), r.globalCompositeOperation = "destination-out", r.fillStyle = this.#oi({ context: r, start: e, height: s }), r.fillRect(0, e, t.bitmapSize.width, s), r.restore();
2311
+ r.save(), r.globalCompositeOperation = "destination-out", r.fillStyle = this.#He({ context: r, start: i, height: s }), r.fillRect(0, i, t.bitmapSize.width, s), r.restore();
1878
2312
  }
1879
2313
  /**
1880
2314
  * Crosshair hairlines with the same edge melt the main layer runs: the
@@ -1886,18 +2320,18 @@ class Li extends wt {
1886
2320
  * edge. Erases alpha (destination-out), so it must run while the
1887
2321
  * crosshair is the only content on the layer.
1888
2322
  */
1889
- #ii(t) {
1890
- const i = this.#c;
1891
- if (i === null) return;
1892
- const { scope: e, chartBitmapWidth: s, chartBitmapHeight: r } = t, { context: n } = e, a = this.#I(e), o = this.#Bt(e, s), l = this.#$t(e, s), h = this.xAxisHeight > 0 ? Math.round(P * e.verticalPixelRatio) : 0;
1893
- n.save(), n.translate(a, 0), n.beginPath(), n.rect(-l, 0, o + l, r + h), n.clip(), bt({
1894
- scope: e,
1895
- bitmapX: i.mediaX * e.horizontalPixelRatio - a,
1896
- bitmapY: i.mediaY * e.verticalPixelRatio,
2323
+ #Pe(t) {
2324
+ const e = this.#f;
2325
+ if (e === null) return;
2326
+ const { scope: i, chartBitmapWidth: s, chartBitmapHeight: r } = t, { context: n } = i, a = this.#W(i), o = this.#re(i, s), h = this.#ie(i, s), l = this.xAxisHeight > 0 ? Math.round(D * i.verticalPixelRatio) : 0;
2327
+ n.save(), n.translate(a, 0), n.beginPath(), n.rect(-h, 0, o + h, r + l), n.clip(), Ft({
2328
+ scope: i,
2329
+ bitmapX: e.mediaX * i.horizontalPixelRatio - a,
2330
+ bitmapY: e.mediaY * i.verticalPixelRatio,
1897
2331
  theme: this.#n,
1898
2332
  pane: { width: s, height: r },
1899
- leftOverhang: l
1900
- }), n.restore(), this.#Ot(e, r), l === 0 ? this.#lt(e, s) : this.#Nt(e, l), this.#Ft(e, s), this.#Vt(e, r);
2333
+ leftOverhang: h
2334
+ }), n.restore(), this.#te(i, r), h === 0 ? this.#$(i, s, "left") : this.#se(i, h), this.#$(i, s, "right"), this.#ee(i, s), this.#Jt(i, r);
1901
2335
  }
1902
2336
  /**
1903
2337
  * Horizontal companion to {@link #applyTopFade}: dissolve content that
@@ -1906,88 +2340,93 @@ class Li extends wt {
1906
2340
  * Runs the full bitmap height so the below-pane gridline tail stubs melt
1907
2341
  * at the edges in step with their lines above.
1908
2342
  */
1909
- #lt(t, i) {
1910
- const e = this.#ct(t, i);
1911
- if (e === null) return;
1912
- const { context: s } = t, r = Math.min(e.inner, e.outer), n = Math.abs(e.outer - e.inner);
1913
- s.save(), s.globalCompositeOperation = "destination-out", s.fillStyle = this.#ai({ context: s, zone: e }), s.fillRect(r, 0, n, t.bitmapSize.height), s.restore();
2343
+ #$(t, e, i) {
2344
+ const s = this.#rt(t, e, i);
2345
+ if (s === null) return;
2346
+ const { context: r } = t, n = Math.min(s.inner, s.outer), a = Math.abs(s.outer - s.inner);
2347
+ r.save(), r.globalCompositeOperation = "destination-out", r.fillStyle = this.#Ie({ context: r, side: i, zone: s }), r.fillRect(n, 0, a, t.bitmapSize.height), r.restore();
1914
2348
  }
1915
2349
  /** Dissolve content leaving through a plain canvas edge — the side opposite
1916
2350
  * the Y axis, or either side on a spatial-only chart — when configured. */
1917
- #Ft(t, i) {
1918
- const { context: e } = t;
1919
- for (const s of this.#ei()) {
1920
- const r = this.#ri({ scope: t, paneBitmapWidth: i, side: s });
2351
+ #ee(t, e) {
2352
+ const { context: i } = t;
2353
+ for (const s of this.#Ce()) {
2354
+ const r = this.#De({ scope: t, paneBitmapWidth: e, side: s });
1921
2355
  if (r === null) continue;
1922
2356
  const n = Math.min(r.edge, r.inner), a = Math.abs(r.inner - r.edge);
1923
- e.save(), e.globalCompositeOperation = "destination-out", e.fillStyle = this.#li({ context: e, side: s, zone: r }), e.fillRect(n, 0, a, t.bitmapSize.height), e.restore();
2357
+ i.save(), i.globalCompositeOperation = "destination-out", i.fillStyle = this.#ze({ context: i, side: s, zone: r }), i.fillRect(n, 0, a, t.bitmapSize.height), i.restore();
1924
2358
  }
1925
2359
  }
1926
- /** Sides whose fade is a plain edge zone rather than the axis-column ramp. */
1927
- #ei() {
1928
- return this.#X() ? $t : this.yAxisPosition === "left" ? Bt : Nt;
2360
+ /** Sides whose fade is a plain edge zone rather than the axis-column ramp —
2361
+ * neither the default axis's side nor one a named scale's column holds. */
2362
+ #Ce() {
2363
+ if (this.#Q()) return ee;
2364
+ const t = q(this.yAxisPosition);
2365
+ return this.getAxisColumnWidth(t) > 0 ? se : t === "left" ? re : ne;
1929
2366
  }
1930
2367
  /** Bitmap px the gridlines and crosshair run under a left Y-axis column, where
1931
- * data can't follow (see {@link #axisFadeZone}). `0` on the right or with the fade off. */
1932
- #$t(t, i) {
1933
- return this.yAxisPosition !== "left" || this.#ct(t, i) === null ? 0 : Math.min(Math.round(Z * t.horizontalPixelRatio), this.#I(t));
2368
+ * data can't follow (see {@link #axisFadeZone}). `0` without one or with its fade off. */
2369
+ #ie(t, e) {
2370
+ return this.#rt(t, e, "left") === null ? 0 : Math.min(Math.round(K * t.horizontalPixelRatio), this.#W(t));
1934
2371
  }
1935
2372
  /** Left-axis grid pass, run after the axis fade: `destination-over` keeps the
1936
2373
  * gridlines out of the data ramp, then they taper out under the column. */
1937
- #si(t) {
1938
- const { grid: i, pane: e, overhang: s } = t, { scope: r } = i, { context: n } = r;
1939
- n.save(), n.globalCompositeOperation = "destination-over", n.save(), n.translate(e.left, 0), n.beginPath(), n.rect(-s, 0, e.width + s, e.height), n.clip(), E({ ...i, leftOverhang: s }), n.restore(), this.#Wt({ scope: r, pane: e, timeTicks: i.timeTicks, alpha: i.alpha ?? 1 }), n.restore(), this.#Nt(r, s);
2374
+ #Le(t) {
2375
+ const { grid: e, pane: i, overhang: s } = t, { scope: r } = e, { context: n } = r;
2376
+ n.save(), n.globalCompositeOperation = "destination-over";
2377
+ const a = this.#rt(r, i.width, "right")?.intrusion ?? 0;
2378
+ n.save(), n.translate(i.left, 0), n.beginPath(), n.rect(-s, 0, i.width + s + a, i.height), n.clip(), X({ ...e, leftOverhang: s }), n.restore(), this.#Qt({ scope: r, pane: i, timeTicks: e.timeTicks, alpha: e.alpha ?? 1 }), n.restore(), this.#se(r, s);
1940
2379
  }
1941
2380
  /** Erase the {@link #gridOverhang} strip: none at the pane edge, total at its outer end. */
1942
- #Nt(t, i) {
1943
- const e = this.#I(t), s = e - i, { context: r } = t;
1944
- r.save(), r.globalCompositeOperation = "destination-out", r.fillStyle = this.#hi({ context: r, start: s, end: e }), r.fillRect(s, 0, i, t.bitmapSize.height), r.restore();
2381
+ #se(t, e) {
2382
+ const i = this.#W(t), s = i - e, { context: r } = t;
2383
+ r.save(), r.globalCompositeOperation = "destination-out", r.fillStyle = this.#Ve({ context: r, start: s, end: i }), r.fillRect(s, 0, e, t.bitmapSize.height), r.restore();
1945
2384
  }
1946
2385
  /** Canvas bitmap x of the pane's left edge — whole device pixels, so the
1947
2386
  * translated pane keeps crisp strokes. */
1948
- #I(t) {
2387
+ #W(t) {
1949
2388
  return Math.round(this.plotLeft * t.horizontalPixelRatio);
1950
2389
  }
1951
- /** Pane-local width of the pane clip: the pane plus the axis-side fade
2390
+ /** Pane-local width of the pane clip: the pane plus the right fade
1952
2391
  * intrusion, so content can slide under a right Y-axis column before the
1953
2392
  * mask dissolves it. */
1954
- #Bt(t, i) {
1955
- const e = this.#ct(t, i)?.intrusion ?? 0;
1956
- return i + e;
2393
+ #re(t, e) {
2394
+ const i = this.#rt(t, e, "right")?.intrusion ?? 0;
2395
+ return e + i;
1957
2396
  }
1958
2397
  /**
1959
- * Bitmap geometry of the fade zone on the Y-axis side. On the right the
2398
+ * Bitmap geometry of the fade zone under the Y-axis column on `side`. On the right the
1960
2399
  * ramp finishes just inside the column — {@link AXIS_FADE_END_GAP_PX} past
1961
2400
  * the pane edge, still short of where the label glyphs start — so content
1962
2401
  * is fully erased before it can cross any axis text; everything wider
1963
2402
  * spills back into the pane as the soft lead-in. On the left it finishes
1964
2403
  * at the pane edge. Erasing starts at `inner` and is total at `outer`;
1965
2404
  * `intrusion` is what the pane clip extends by (content past the ramp is
1966
- * total-erased anyway). `null` when the axis is hidden, its side's fade
2405
+ * total-erased anyway). `null` when no column sits on `side`, its fade
1967
2406
  * is `0`, or the chart is spatial-only.
1968
2407
  */
1969
- #ct(t, i) {
1970
- if (this.#X()) return null;
1971
- const e = this.yAxisPosition === "left", s = t.bitmapSize.width, r = e ? this.#I(t) : Math.max(0, Math.round(s - i));
1972
- if (r === 0) return null;
1973
- const n = t.horizontalPixelRatio, a = Math.round(Z * n), o = Math.round(Ft * n), l = e ? 0 : Math.min(a, r), h = e ? a + o : l + o, d = e ? this.#A.left : this.#A.right, m = d === null ? h : Math.round(d * n);
2408
+ #rt(t, e, i) {
2409
+ if (this.#Q()) return null;
2410
+ const s = i === "left", r = t.bitmapSize.width, n = this.#W(t), a = s ? n : Math.max(0, Math.round(r - n - e));
2411
+ if (a === 0) return null;
2412
+ const o = t.horizontalPixelRatio, h = Math.round(K * o), l = Math.round(te * o), d = s ? 0 : Math.min(h, a), f = s ? h + l : d + l, c = s ? this.#C.left : this.#C.right, m = c === null ? f : Math.round(c * o);
1974
2413
  if (m < 1) return null;
1975
- if (e) {
1976
- const g = r, u = Math.min(m, s - g);
1977
- return { inner: g + u, outer: g, intrusion: l };
2414
+ if (s) {
2415
+ const g = a, S = Math.min(m, r - g);
2416
+ return { inner: g + S, outer: g, intrusion: d };
1978
2417
  }
1979
- const c = i + l, f = Math.min(m, c);
1980
- return { inner: c - f, outer: c, intrusion: l };
2418
+ const y = n + e + d, u = Math.min(m, y);
2419
+ return { inner: y - u, outer: y, intrusion: d };
1981
2420
  }
1982
2421
  /** Bitmap geometry of a plain fade at the `side` canvas edge — total erase
1983
2422
  * at `edge`, none at `inner`. Off unless that side's fade is configured. */
1984
- #ri(t) {
1985
- const { scope: i, paneBitmapWidth: e, side: s } = t, r = this.#A[s];
2423
+ #De(t) {
2424
+ const { scope: e, paneBitmapWidth: i, side: s } = t, r = this.#C[s];
1986
2425
  if (r === null) return null;
1987
- const n = Math.min(Math.round(r * i.horizontalPixelRatio), Math.round(e));
2426
+ const n = Math.min(Math.round(r * e.horizontalPixelRatio), Math.round(i));
1988
2427
  if (n < 1) return null;
1989
2428
  if (s === "right") {
1990
- const a = i.bitmapSize.width;
2429
+ const a = e.bitmapSize.width;
1991
2430
  return { edge: a, inner: a - n };
1992
2431
  }
1993
2432
  return { edge: 0, inner: n };
@@ -1996,15 +2435,15 @@ class Li extends wt {
1996
2435
  * changes (resize, DPR change, `setFade`). Ease-out ramp (`1 - t²`):
1997
2436
  * near-total erasure across the upper half keeps a floating header
1998
2437
  * legible, then a soft tail into full opacity at the zone's bottom. */
1999
- #ni(t, i) {
2000
- const e = this.#v.top;
2001
- if (e !== null && e.height === i) return e.gradient;
2002
- const s = t.createLinearGradient(0, 0, 0, i);
2438
+ #Re(t, e) {
2439
+ const i = this.#k.top;
2440
+ if (i !== null && i.height === e) return i.gradient;
2441
+ const s = t.createLinearGradient(0, 0, 0, e);
2003
2442
  for (let r = 0; r <= 4; r++) {
2004
2443
  const n = r / 4;
2005
2444
  s.addColorStop(n, `rgba(0, 0, 0, ${1 - n * n})`);
2006
2445
  }
2007
- return this.#v.top = { height: i, gradient: s }, s;
2446
+ return this.#k.top = { height: e, gradient: s }, s;
2008
2447
  }
2009
2448
  /** Axis-column mask — erase grows from 0 at the ramp's inner start to
2010
2449
  * total at its outer end on an ease-in (`s³`): zero slope *and* zero
@@ -2012,51 +2451,51 @@ class Li extends wt {
2012
2451
  * candles at rest and the aggressive erasing all happens deep under the
2013
2452
  * axis column. Nine stops keep the piecewise interpolation smooth across
2014
2453
  * the zone. */
2015
- #ai(t) {
2016
- const { context: i, zone: e } = t, s = this.#v.axis;
2017
- if (s !== null && s.inner === e.inner && s.outer === e.outer) return s.gradient;
2018
- const r = i.createLinearGradient(e.inner, 0, e.outer, 0);
2019
- for (let n = 0; n <= 8; n++) {
2020
- const a = n / 8;
2021
- r.addColorStop(a, `rgba(0, 0, 0, ${a * a * a})`);
2454
+ #Ie(t) {
2455
+ const { context: e, side: i, zone: s } = t, r = this.#k.axis[i];
2456
+ if (r !== null && r.inner === s.inner && r.outer === s.outer) return r.gradient;
2457
+ const n = e.createLinearGradient(s.inner, 0, s.outer, 0);
2458
+ for (let a = 0; a <= 8; a++) {
2459
+ const o = a / 8;
2460
+ n.addColorStop(o, `rgba(0, 0, 0, ${o * o * o})`);
2022
2461
  }
2023
- return this.#v.axis = { inner: e.inner, outer: e.outer, gradient: r }, r;
2462
+ return this.#k.axis[i] = { inner: s.inner, outer: s.outer, gradient: n }, n;
2024
2463
  }
2025
2464
  /** Erase ramp for the gridline tail stubs — nothing erased at the pane
2026
2465
  * floor, total at the stub tip, ease-in (`t²`) so a stub holds strength
2027
2466
  * briefly before melting away short of the label glyphs. */
2028
- #oi(t) {
2029
- const i = this.#v.tail;
2030
- if (i !== null && i.start === t.start && i.height === t.height) return i.gradient;
2031
- const e = t.context.createLinearGradient(0, t.start, 0, t.start + t.height);
2467
+ #He(t) {
2468
+ const e = this.#k.tail;
2469
+ if (e !== null && e.start === t.start && e.height === t.height) return e.gradient;
2470
+ const i = t.context.createLinearGradient(0, t.start, 0, t.start + t.height);
2032
2471
  for (let s = 0; s <= 4; s++) {
2033
2472
  const r = s / 4;
2034
- e.addColorStop(r, `rgba(0, 0, 0, ${r * r})`);
2473
+ i.addColorStop(r, `rgba(0, 0, 0, ${r * r})`);
2035
2474
  }
2036
- return this.#v.tail = { start: t.start, height: t.height, gradient: e }, e;
2475
+ return this.#k.tail = { start: t.start, height: t.height, gradient: i }, i;
2037
2476
  }
2038
2477
  /** Gridline overhang ramp — ease-in (`t²`) like the tail stubs. */
2039
- #hi(t) {
2040
- const i = this.#v.overhang;
2041
- if (i !== null && i.start === t.start && i.end === t.end) return i.gradient;
2042
- const e = t.context.createLinearGradient(t.end, 0, t.start, 0);
2478
+ #Ve(t) {
2479
+ const e = this.#k.overhang;
2480
+ if (e !== null && e.start === t.start && e.end === t.end) return e.gradient;
2481
+ const i = t.context.createLinearGradient(t.end, 0, t.start, 0);
2043
2482
  for (let s = 0; s <= 4; s++) {
2044
2483
  const r = s / 4;
2045
- e.addColorStop(r, `rgba(0, 0, 0, ${r * r})`);
2484
+ i.addColorStop(r, `rgba(0, 0, 0, ${r * r})`);
2046
2485
  }
2047
- return this.#v.overhang = { start: t.start, end: t.end, gradient: e }, e;
2486
+ return this.#k.overhang = { start: t.start, end: t.end, gradient: i }, i;
2048
2487
  }
2049
2488
  /** Opposite-edge mask — mirror of the top ramp: total erase at the
2050
2489
  * canvas edge, fully opaque at the zone's inner boundary. */
2051
- #li(t) {
2052
- const { context: i, side: e, zone: s } = t, r = this.#v.edge[e];
2490
+ #ze(t) {
2491
+ const { context: e, side: i, zone: s } = t, r = this.#k.edge[i];
2053
2492
  if (r !== null && r.edge === s.edge && r.inner === s.inner) return r.gradient;
2054
- const n = i.createLinearGradient(s.edge, 0, s.inner, 0);
2493
+ const n = e.createLinearGradient(s.edge, 0, s.inner, 0);
2055
2494
  for (let a = 0; a <= 4; a++) {
2056
2495
  const o = a / 4;
2057
2496
  n.addColorStop(o, `rgba(0, 0, 0, ${1 - o * o})`);
2058
2497
  }
2059
- return this.#v.edge[e] = { edge: s.edge, inner: s.inner, gradient: n }, n;
2498
+ return this.#k.edge[i] = { edge: s.edge, inner: s.inner, gradient: n }, n;
2060
2499
  }
2061
2500
  /**
2062
2501
  * Paint a full frame: the main layer, then the overlay synchronously on
@@ -2067,94 +2506,98 @@ class Li extends wt {
2067
2506
  * `markDirty` calls made *during* `renderOverlay` (pulse, edge spinner)
2068
2507
  * land after the consume and re-arm cleanly for the next frame.
2069
2508
  */
2070
- #dt(t) {
2071
- this.#ut("main", t, () => this.renderMain(t)), this.#o.consume(), this.#ut("overlay", t, () => this.renderOverlay(t));
2509
+ #wt(t) {
2510
+ this.#Tt("main", t, () => this.renderMain(t)), this.#c.consume(), this.#Tt("overlay", t, () => this.renderOverlay(t));
2072
2511
  }
2073
2512
  /** Run a layer's paint, booking duration + draw calls to the perf monitor
2074
2513
  * when one is attached — keeps overlay time out of the `main` samples. */
2075
- #ut(t, i, e) {
2076
- const s = this.#k;
2514
+ #Tt(t, e, i) {
2515
+ const s = this.#M;
2077
2516
  if (!s) {
2078
- e();
2517
+ i();
2079
2518
  return;
2080
2519
  }
2081
2520
  s.resetDrawCalls(t);
2082
2521
  const r = performance.now();
2083
- e(), s.recordFrame(t, performance.now() - r, i ?? r);
2522
+ i(), s.recordFrame(t, performance.now() - r, e ?? r);
2084
2523
  }
2085
2524
  /** Cheap overlay: crosshair, nearest-point dots, pulse animation, edge indicator. */
2086
2525
  renderOverlay(t) {
2087
- const i = this.#a.size;
2088
- if (i.media.width === 0 || i.media.height === 0) return;
2089
- let e = !1;
2090
- for (const h of this.#t)
2091
- if (h.visible && h.renderer.overlayNeedsAnimation) {
2092
- e = !0;
2526
+ const e = this.#l.size;
2527
+ if (e.media.width === 0 || e.media.height === 0) return;
2528
+ let i = !1;
2529
+ for (const l of this.#t)
2530
+ if (l.visible && l.renderer.overlayNeedsAnimation) {
2531
+ i = !0;
2093
2532
  break;
2094
2533
  }
2095
- this.#u.some((h) => h.pulse) && (e = !0);
2534
+ this.#y.some((l) => l.pulse) && (i = !0);
2096
2535
  const s = {
2097
- left: this.#z.left && this.#S.left === "loading" ? "idle" : this.#S.left,
2098
- right: this.#z.right && this.#S.right === "loading" ? "idle" : this.#S.right
2536
+ left: this.#B.left && this.#A.left === "loading" ? "idle" : this.#A.left,
2537
+ right: this.#B.right && this.#A.right === "loading" ? "idle" : this.#A.right
2099
2538
  }, r = {
2100
- left: this.#Pt("left"),
2101
- right: this.#Pt("right")
2102
- }, n = r.left !== null || r.right !== null, a = s.left === "loading" || s.right === "loading" || n, o = s.left !== "idle" || s.right !== "idle" || n, l = !!this.#c || e || o || this.#u.length > 0 || this.#y.length > 0;
2103
- if (!(!l && !this.#gt)) {
2104
- if (this.#gt = l, this.#a.useOverlayLayer((h) => {
2105
- if (!l) return;
2106
- const d = (i.media.width - this.yAxisWidth) * i.horizontalPixelRatio, m = (i.media.height - this.xAxisHeight) * i.verticalPixelRatio;
2107
- this.#c && this.#Mt() && this.#ii({ scope: h, chartBitmapWidth: d, chartBitmapHeight: m }), h.context.save(), h.context.translate(this.#I(h), 0), h.context.beginPath(), h.context.rect(0, 0, d, m), h.context.clip();
2108
- const c = this.#l, f = { top: c.top, bottom: c.bottom }, g = this.getAnimationState();
2109
- for (const u of this.#t)
2110
- u.visible && u.renderer.drawOverlay?.({
2111
- scope: h,
2539
+ left: this.#Zt("left"),
2540
+ right: this.#Zt("right")
2541
+ }, n = r.left !== null || r.right !== null, a = s.left === "loading" || s.right === "loading" || n, o = s.left !== "idle" || s.right !== "idle" || n, h = !!this.#f || i || o || this.#y.length > 0 || this.#b.length > 0;
2542
+ if (!(!h && !this.#Lt)) {
2543
+ if (this.#Lt = h, this.#l.useOverlayLayer((l) => {
2544
+ if (!h) return;
2545
+ const d = (e.media.width - this.#w()) * e.horizontalPixelRatio, f = (e.media.height - this.xAxisHeight) * e.verticalPixelRatio;
2546
+ this.#f && this.#Nt() && this.#Pe({ scope: l, chartBitmapWidth: d, chartBitmapHeight: f }), l.context.save(), l.context.translate(this.#W(l), 0), l.context.beginPath(), l.context.rect(0, 0, d, f), l.context.clip();
2547
+ const c = this.#d, m = { top: c.top, bottom: c.bottom }, y = this.getAnimationState();
2548
+ for (const u of this.#t) {
2549
+ if (!u.visible) continue;
2550
+ const g = this.#D(u);
2551
+ u.renderer.drawOverlay?.({
2552
+ scope: l,
2112
2553
  timeScale: this.timeScale,
2113
- yScale: this.yScale,
2554
+ yScale: this.getYScale(g),
2114
2555
  theme: this.#n,
2115
- dataInterval: this.#r,
2116
- padding: f,
2117
- crosshair: this.#c,
2118
- state: g,
2556
+ // Overlays only snap the crosshair dots with it.
2557
+ dataInterval: this.#X(),
2558
+ padding: m,
2559
+ crosshair: this.#f,
2560
+ state: g === void 0 ? y : this.#Wt(y, g),
2119
2561
  seriesId: u.id
2120
2562
  });
2121
- this.#Gt(h, d, m), this.#Zt(h, d, m), o && st({
2122
- scope: h,
2123
- chartMediaWidth: i.media.width - this.yAxisWidth,
2124
- chartMediaHeight: i.media.height - this.xAxisHeight,
2563
+ }
2564
+ this.#de(l, d, f), this.#le(l, d, f), o && ut({
2565
+ scope: l,
2566
+ chartMediaWidth: e.media.width - this.#w(),
2567
+ chartMediaHeight: e.media.height - this.xAxisHeight,
2125
2568
  timeScale: this.timeScale,
2126
2569
  theme: this.#n,
2127
2570
  edgeStates: s,
2128
2571
  edgeExitFades: r,
2129
- edgeIndicatorFns: this.#vt,
2130
- resolveBoundary: (u) => rt(u, this.#pt[u], this.getDataBounds()),
2131
- resolveEdgeAnchor: (u, p) => this.#Ut(u, p),
2132
- resolveEdgeBarSpacing: () => this.#qt(),
2133
- reportPlaceholders: (u, p) => {
2134
- this.#K[u] = { bars: p, at: performance.now() };
2572
+ edgeIndicatorFns: this.#It,
2573
+ resolveBoundary: (u) => mt(u, this.#Dt[u], this.getDataBounds()),
2574
+ resolveEdgeAnchor: (u, g) => this.#ve(u, g),
2575
+ resolveEdgeBarSpacing: () => this.#Se(),
2576
+ reportPlaceholders: (u, g) => {
2577
+ this.#ht[u] = { bars: g, at: performance.now() };
2135
2578
  }
2136
- }), h.context.restore();
2137
- }), e) {
2138
- const { from: h, to: d } = this.timeScale.getRange();
2139
- let m = !1;
2579
+ }), l.context.restore();
2580
+ }), i) {
2581
+ const { from: l, to: d } = this.timeScale.getRange();
2582
+ let f = !1;
2140
2583
  for (const c of this.#t)
2141
- if (!(!c.visible || !c.renderer.overlayNeedsAnimation) && (c.renderer.hasOverlayContentInRange?.(h, d) ?? !0)) {
2142
- m = !0;
2584
+ if (!(!c.visible || !c.renderer.overlayNeedsAnimation) && (c.renderer.hasOverlayContentInRange?.(l, d) ?? !0)) {
2585
+ f = !0;
2143
2586
  break;
2144
2587
  }
2145
- if (!m) {
2146
- for (const c of this.#u)
2147
- if (c.pulse && c.time >= h && c.time <= d) {
2148
- m = !0;
2588
+ if (!f) {
2589
+ for (const c of this.#y)
2590
+ if (c.pulse && c.time >= l && c.time <= d) {
2591
+ f = !0;
2149
2592
  break;
2150
2593
  }
2151
2594
  }
2152
- m && this.#o.markDirty();
2595
+ f && this.#c.markDirty();
2153
2596
  }
2154
- a && this.#o.markDirty();
2597
+ a && this.#c.markDirty();
2155
2598
  }
2156
2599
  }
2157
2600
  }
2158
2601
  export {
2159
- Li as ChartInstance
2602
+ _e as ChartInstance
2160
2603
  };