@wick-charts/vue 0.6.3 → 0.7.0

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