@wick-charts/vue 0.6.2 → 0.6.3

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 (44) hide show
  1. package/dist/ChartContainer.vue.js +43 -37
  2. package/dist/LineSeries.vue.js +2 -2
  3. package/dist/composables.js +39 -26
  4. package/dist/core/dist/axis/dom-labels.js +56 -43
  5. package/dist/core/dist/chart/options.js +5 -5
  6. package/dist/core/dist/chart/scale-guards.js +26 -0
  7. package/dist/core/dist/chart/y-target.js +46 -19
  8. package/dist/core/dist/chart.js +670 -519
  9. package/dist/core/dist/components/crosshair.js +8 -8
  10. package/dist/core/dist/components/grid.js +26 -17
  11. package/dist/core/dist/components/marker.js +28 -28
  12. package/dist/core/dist/components/reference-line.js +16 -16
  13. package/dist/core/dist/interactions/handler.js +3 -2
  14. package/dist/core/dist/interactions/zoom.js +6 -5
  15. package/dist/core/dist/navigator/controller.js +38 -35
  16. package/dist/core/dist/perf/perf-hud.js +3 -3
  17. package/dist/core/dist/scales/log-scale.js +76 -0
  18. package/dist/core/dist/scales/y-scale.js +134 -35
  19. package/dist/core/dist/series/bar-intro.js +11 -11
  20. package/dist/core/dist/series/bar.js +301 -251
  21. package/dist/core/dist/series/base-multi-layer.js +100 -76
  22. package/dist/core/dist/series/candlestick.js +244 -241
  23. package/dist/core/dist/series/line-intro.js +18 -18
  24. package/dist/core/dist/series/line.js +428 -390
  25. package/dist/core/dist/series/scale-stack.js +25 -0
  26. package/dist/core/dist/tooltip-position.js +5 -5
  27. package/dist/index.d.ts +246 -31
  28. package/dist/index.js +59 -57
  29. package/dist/ui/Crosshair.vue.js +14 -14
  30. package/dist/ui/HeatmapTooltip.vue.js +6 -6
  31. package/dist/ui/PieTooltip.vue.js +4 -4
  32. package/dist/ui/TimeAxis.vue.js +23 -22
  33. package/dist/ui/Tooltip.vue.js +71 -69
  34. package/dist/ui/YAxis.vue.js +25 -24
  35. package/dist/ui/YLabel.vue.js +48 -48
  36. package/package.json +2 -2
  37. package/src/ChartContainer.vue +20 -8
  38. package/src/composables.ts +35 -1
  39. package/src/index.ts +5 -0
  40. package/src/ui/Crosshair.vue +2 -2
  41. package/src/ui/TimeAxis.vue +7 -4
  42. package/src/ui/Tooltip.vue +4 -1
  43. package/src/ui/YAxis.vue +5 -3
  44. package/src/ui/YLabel.vue +3 -3
@@ -1,55 +1,56 @@
1
- import { Animator as Y } from "./animation/animator.js";
2
- import { AnimationConfig as X, DEFAULT_LINE_PULSE as $ } from "./animation/config.js";
3
- import { easeOutCubic as P } from "./animation/easing.js";
4
- import { prefersReducedMotion as Z } from "./animation/reduced-motion.js";
5
- import { createViewportEngine as _ } from "./animation/viewport-engine.js";
6
- import { CanvasManager as G } from "./canvas-manager.js";
7
- import { resolveEdgeAnchorValue as U, drawEdgeIndicators as K, resolveEdgeBoundary as j } from "./chart/edge-indicators.js";
8
- import { computeFitToData as q } from "./chart/fit-to-data.js";
9
- import { getLastValue as J, getPreviousClose as Q, getStackedLastValue as tt } from "./chart/last-value.js";
10
- import { resolveFade as H, resolveMaxVisibleBars as it, resolvePadding as z, resolvePerfOptions as et, isSameHorizontalPadding as V } from "./chart/options.js";
11
- import { computePan as st, computeZoom as rt } from "./chart/pan-zoom-math.js";
12
- import { StreamingCadence as nt } from "./chart/streaming-cadence.js";
13
- import { computeStreamingTarget as at } from "./chart/streaming-target.js";
14
- import { resolvePaddingTime as W } from "./chart/viewport-padding.js";
15
- import { computeTargetYRange as ht, resolveBound as F } from "./chart/y-target.js";
16
- import { renderCrosshair as ot } from "./components/crosshair.js";
17
- import { renderGrid as lt } from "./components/grid.js";
18
- import { renderMarker as ct } from "./components/marker.js";
19
- import { renderReferenceLine as dt } from "./components/reference-line.js";
20
- import { resolveTimeRegionColors as mt, renderTimeRegion as ut } from "./components/time-region.js";
21
- import { EventEmitter as ft } from "./events.js";
22
- import { InteractionHandler as gt } from "./interactions/handler.js";
23
- import { RenderScheduler as B } from "./render-scheduler.js";
24
- import { XScale as pt } from "./scales/x-scale.js";
25
- import { YScale as yt } from "./scales/y-scale.js";
26
- import { resolveSeriesDefinition as vt } from "./series/definition.js";
27
- import { isTimeSeriesRenderer as k } from "./series/types.js";
28
- import { catppuccin as xt } from "./theme/themes/catppuccin.js";
29
- import { clamp as bt } from "./utils/math.js";
30
- import { crispLineWidth as St, crispCenterOffset as kt } from "./utils/pixel-grid.js";
31
- import { normalizeTime as x, detectInterval as wt } from "./utils/time.js";
32
- const At = 200, Lt = 1500, Tt = 48, Mt = 12, T = 9;
33
- let Dt = 0, Rt = 0, Ct = 0, It = 0;
34
- function N(p, t) {
35
- return p === t ? !0 : !p || !t || p.length !== t.length ? !1 : p.every((i, e) => i === t[e]);
1
+ import { Animator as K } from "./animation/animator.js";
2
+ import { AnimationConfig as j, DEFAULT_LINE_PULSE as q } from "./animation/config.js";
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]);
36
37
  }
37
- function Pt(p, t) {
38
- return p === t ? !0 : !p || !t || p.length !== t.length ? !1 : p.every((i, e) => i === t[e]);
38
+ function Xt(y, t) {
39
+ return y === t ? !0 : !y || !t || y.length !== t.length ? !1 : y.every((i, e) => i === t[e]);
39
40
  }
40
- class ci extends ft {
41
+ class Ai extends At {
41
42
  /** Canvas lifecycle and DPR-aware sizing. */
42
43
  #a;
43
44
  /** Schedules main-layer redraws (background, grid, series). */
44
45
  #i;
45
46
  /** Schedules overlay redraws (crosshair). */
46
- #h;
47
+ #o;
47
48
  /** Maps time values to horizontal pixel coordinates. */
48
49
  timeScale;
49
50
  /** Maps price/value to vertical pixel coordinates. */
50
51
  yScale;
51
52
  /** Zoom, pan, crosshair — null when interactive=false. */
52
- #M;
53
+ #D;
53
54
  /** All registered series (candlestick, line, bar, pie). */
54
55
  #t = [];
55
56
  /** Point annotations (event markers) drawn on the overlay layer, kept out of the series model. */
@@ -66,8 +67,8 @@ class ci extends ft {
66
67
  * later — which re-arms `getIntroFront()`'s global min — can't blink settled
67
68
  * markers / reference lines back out.
68
69
  */
69
- #nt = !1;
70
- #at = !1;
70
+ #mt = !1;
71
+ #ft = !1;
71
72
  /** Active visual theme (colors, fonts, grid style). */
72
73
  #n;
73
74
  /** Whether to render the background grid. */
@@ -78,13 +79,15 @@ class ci extends ft {
78
79
  * per-tick fades stay snapped during mount (see the tracker's arming), so
79
80
  * this is what keeps `fitToData` tick churn from flickering the reveal.
80
81
  */
81
- #I;
82
- /** Resolved edge-fade zones — `right: null` tracks the live Y-axis width. */
83
- #k;
82
+ #H;
83
+ /** Resolved edge-fade zones — a `null` side is auto: the axis-column ramp on the Y-axis side, off on the other. */
84
+ #A;
85
+ /** See {@link headerHeight}. */
86
+ #U = 0;
84
87
  /** Memoized destination-out mask gradients, one per edge, keyed by the
85
88
  * zone's bitmap geometry so a resize / DPR change / `setFade` rebuilds
86
89
  * them and steady frames reuse them. */
87
- #w = { top: null, right: null, left: null, tail: null };
90
+ #v = { top: null, axis: null, edge: { left: null, right: null }, tail: null, overhang: null };
88
91
  /** Detected time interval between data points (milliseconds). */
89
92
  #r = 6e4;
90
93
  /** Current crosshair position, null when cursor is outside the chart. */
@@ -96,45 +99,45 @@ class ci extends ft {
96
99
  * `time`/`y`; `'time'` (programmatic {@link setCrosshair}) keeps
97
100
  * `time`/`y` and re-projects the pixel position.
98
101
  */
99
- #$ = "pointer";
102
+ #K = "pointer";
100
103
  /** Whether the last overlay pass painted anything — gates the full-bitmap
101
104
  * clear so an empty overlay isn't cleared again every main frame. */
102
- #ht = !1;
105
+ #gt = !1;
103
106
  /** User-specified Y-axis bounds (auto, fixed, percentage). */
104
- #d = {};
107
+ #p = {};
105
108
  /** Cached series ID list — invalidated on add/remove. */
106
- #D = null;
109
+ #P = null;
107
110
  /** Axis visibility and sizing configuration. */
108
- #B = {};
111
+ #w = {};
109
112
  /** Host-declared state per edge — drives the edge-indicator overlay. */
110
- #b = { left: "idle", right: "idle" };
113
+ #S = { left: "idle", right: "idle" };
111
114
  /** Wall-clock start of a `loading` exit fade per side — set when the host
112
115
  * drops `loading` back to `idle`/`has-more`, so the indicator fades out
113
116
  * over {@link EDGE_EXIT_FADE_MS} instead of vanishing the same frame the
114
117
  * fetched data lands. `null` = no fade in flight. */
115
118
  #N = { left: null, right: null };
116
119
  /** Cached boundary timestamps from the last `edgeReached` emission, by side. */
117
- #ot = { left: null, right: null };
120
+ #pt = { left: null, right: null };
118
121
  /** Latest placeholder shapes a `loading` indicator reported per side —
119
122
  * the morph hand-off candidate consumed by {@link prependData}. */
120
- #Z = { left: null, right: null };
123
+ #j = { left: null, right: null };
121
124
  /** True after a prepend consumed a side's placeholder snapshot: the series'
122
125
  * reveal owns those pixels now, so the still-`loading` indicator is hidden
123
126
  * and the upcoming `loading → idle` transition skips its exit fade. Reset
124
127
  * by the next {@link setEdgeState} call. */
125
- #P = { left: !1, right: !1 };
128
+ #z = { left: !1, right: !1 };
126
129
  /** Host-supplied callback fired when the user releases a pan/zoom past a data edge. */
127
- #lt;
130
+ #yt;
128
131
  /** Per-side custom painter for the edge `loading` spinner. `null` falls back to the built-in dots. */
129
- #ct = { left: null, right: null };
132
+ #vt = { left: null, right: null };
130
133
  /** Nesting depth for batch updates. Suppresses recomputes while > 0. */
131
134
  #L = 0;
132
135
  /** True when batched operations include data changes (triggers full onDataChanged on end). */
133
- #_ = !1;
136
+ #q = !1;
134
137
  /** True when batched operations include visibility changes (triggers Y-range + redraw on end). */
135
- #H = !1;
138
+ #V = !1;
136
139
  /** True when batched operations bumped overlay version (emits a single overlayChange on flush). */
137
- #G = !1;
140
+ #J = !1;
138
141
  /**
139
142
  * Monotonic counter bumped on any mutation that affects overlay output —
140
143
  * data, visibility, series options, theme. Used by snapshot helpers in
@@ -144,17 +147,36 @@ class ci extends ft {
144
147
  *
145
148
  * @internal
146
149
  */
147
- #dt = 0;
150
+ #bt = 0;
151
+ /** CSS px of the Y-axis column. The default collapses to `0` on a spatial-only chart. */
148
152
  get yAxisWidth() {
149
- const t = this.#B.y;
150
- return t?.visible === !1 ? 0 : t?.width ?? 55;
153
+ const t = this.#w.y;
154
+ return t?.visible === !1 ? 0 : t?.width !== void 0 ? t.width : this.#X() ? 0 : 55;
155
+ }
156
+ /** Side of the plot area the Y-axis column sits on. */
157
+ get yAxisPosition() {
158
+ return this.#w.y?.position ?? "right";
159
+ }
160
+ /**
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.
164
+ */
165
+ get plotLeft() {
166
+ return this.yAxisPosition === "left" ? this.yAxisWidth : 0;
151
167
  }
168
+ /** CSS px height of the floating Title / InfoBar header, `0` without one. A left
169
+ * axis shares its column with it, so tick labels fade out underneath. */
170
+ get headerHeight() {
171
+ return this.#U;
172
+ }
173
+ /** CSS px of the time-axis row. The default collapses to `0` on a spatial-only chart. */
152
174
  get xAxisHeight() {
153
- const t = this.#B.x;
154
- return t?.visible === !1 ? 0 : t?.height ?? 30;
175
+ const t = this.#w.x;
176
+ return t?.visible === !1 ? 0 : t?.height !== void 0 ? t.height : this.#X() ? 0 : 30;
155
177
  }
156
178
  /** Resolved animation config derived from `options.animations` at construction. */
157
- #m;
179
+ #d;
158
180
  /**
159
181
  * Single source of truth for the chart's X / Y viewport animation.
160
182
  * Push-model API: chart emits intent signals (`onPointAppended`,
@@ -179,9 +201,9 @@ class ci extends ft {
179
201
  * field carries the decision across that gap. The closure also reads
180
202
  * `#dataStart` / `#dataEnd` directly for the timestamps.
181
203
  */
182
- #mt = !1;
204
+ #xt = !1;
183
205
  /** Earliest data timestamp registered across all series, `null` before any data has arrived. */
184
- #A = null;
206
+ #T = null;
185
207
  /** Latest data timestamp registered across all series, `null` before any data has arrived. */
186
208
  #e = null;
187
209
  /**
@@ -190,51 +212,51 @@ class ci extends ft {
190
212
  * left of the tail keeps that offset as new bars arrive, instead of
191
213
  * snapping back to the natural-pin position every frame.
192
214
  */
193
- #O = null;
215
+ #E = null;
194
216
  /**
195
217
  * Warm-up window flag — `setVisibleRange({ from, bars })` arms it and
196
218
  * the streaming-target math suppresses pan ticks while the data hasn't
197
219
  * reached the right edge yet. Cleared by user interaction or once the
198
220
  * data fills the window.
199
221
  */
200
- #T = !1;
222
+ #M = !1;
201
223
  /**
202
224
  * Cap for fit-to-data. When the data span exceeds this, the fit anchors
203
225
  * the right edge and trims the left. Resolved once at construction from
204
226
  * `options.viewport.maxVisibleBars`.
205
227
  */
206
- #ut;
228
+ #St;
207
229
  /**
208
230
  * Committed logical X range — the "where X should be" target. Engine
209
231
  * eases its visual toward this. Pan / zoom / setRange / fitContent /
210
232
  * streaming-target writes go here. Initialized to `{0,0}` and treated
211
233
  * as "uninitialized" by `#computeXTarget` until first paint.
212
234
  */
213
- #o = { from: 0, to: 0 };
235
+ #h = { from: 0, to: 0 };
214
236
  /** Padded Y range — engine's raw Y target plus pixel padding from `#padding`. */
215
- #z = { min: 0, max: 0 };
237
+ #B = { min: 0, max: 0 };
216
238
  /**
217
239
  * Auto-scroll flag — when `true`, streaming ticks slide X to track the
218
240
  * data tail. Pan flips this off when the gesture pushes the tail off
219
241
  * screen; chart's per-frame autoscroll check re-engages it when a
220
242
  * follow-up pan brings the tail back into view.
221
243
  */
222
- #f = !0;
244
+ #m = !0;
223
245
  /** Resolved viewport padding (top/bottom px + left/right pixels-or-intervals). */
224
246
  #l;
225
247
  /** Cached chart width — passed by interactions on every call; kept for the rare
226
248
  * call that omits it (e.g. programmatic `chart.pan(...)` without explicit width). */
227
- #E = 0;
249
+ #$ = 0;
228
250
  /** Active performance monitor, or `null` when instrumentation is disabled (the default). */
229
- #S;
251
+ #k;
230
252
  /** When true, `destroy()` tears down the monitor; false for caller-supplied monitors we must not destroy. */
231
- #U = !1;
253
+ #Q = !1;
232
254
  /** Visible HUD overlay (mounted by the perf config); non-null only when the caller requested one. */
233
- #K = null;
255
+ #tt = null;
234
256
  constructor(t, i) {
235
- super(), i?.axis && (this.#B = i.axis, this.#d = { min: i.axis.y?.min, max: i.axis.y?.max }), this.#n = i?.theme ?? xt.theme, this.#F = i?.grid?.visible !== !1, this.#k = H(i?.fade), this.#m = X.resolve(i?.animations), this.#ut = it(i?.viewport?.maxVisibleBars), this.#l = z(i?.padding), this.#R = new nt();
236
- const e = this.#m.axis.y, s = this.#m.axis.x;
237
- this.#s = _({
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({
238
260
  initial: { yRange: { min: 0, max: 0 }, xRange: { from: 0, to: 0 } },
239
261
  y: {
240
262
  curve: e.curve,
@@ -242,36 +264,36 @@ class ci extends ft {
242
264
  stickyMs: e.stickyMs,
243
265
  stickyFloorMs: e.stickyFloorMs,
244
266
  gestureMs: e.gestureMs,
245
- toggleMs: this.#m.toggleMs
267
+ toggleMs: this.#d.toggleMs
246
268
  },
247
269
  x: {
248
270
  curve: s.curve,
249
271
  settleMs: s.settleMs,
250
272
  gestureMs: s.gestureMs
251
273
  },
252
- computeXTarget: () => this.#Ft(),
253
- computeYTarget: ({ xTarget: h }) => this.#St(h),
274
+ computeXTarget: () => this.#Jt(),
275
+ computeYTarget: ({ xTarget: o }) => this.#ht(o),
254
276
  onWake: () => this.#i?.markDirty()
255
- }), this.#lt = i?.onEdgeReached, this.#Y = i?.viewport?.initialRange;
256
- const r = et(i?.perf);
257
- this.#S = r.monitor, this.#U = r.ownsMonitor, this.#a = new G(t, this.#S ?? void 0), this.timeScale = new pt(), this.yScale = new yt(), this.timeScale.setLocale(i?.locale), this.timeScale.setTimeZone(i?.timeZone);
258
- const n = this.#m.axis.ticksMs;
259
- this.timeScale.tickTracker.setFadeMs(n), this.yScale.tickTracker.setFadeMs(n), this.#I = new Y({
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({
260
282
  initial: 0,
261
- duration: this.#m.axis.gridMs,
262
- lerp: (h, l, o) => h + (l - h) * o
263
- }), this.#i = new B((h) => this.#st(h)), this.#h = new B(
264
- (h) => this.#rt("overlay", h, () => this.renderOverlay(h))
265
- ), this.#S && r.hud && (this.#K = r.hud(t, this.#S));
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));
266
288
  const a = i?.interactive !== !1;
267
- this.#M = a ? new gt(this.#a.canvas, this, this.timeScale, this.yScale) : null, this.#a.on("resize", () => {
268
- this.syncScales(), this.#st(), this.emit("viewportChange");
269
- }), this.syncScales(), this.#M?.on("crosshairMove", (h) => {
270
- this.#$ = "pointer", this.#c = h, this.#h.markDirty(), this.emit("crosshairMove", h), this.#J(h);
271
- }), this.#M?.on("click", (h) => {
272
- this.emit("pointClick", { ...h, spatialHit: this.#bt(h) });
273
- }), this.#M?.on("dblclick", (h) => {
274
- this.fitContent(), this.emit("pointDoubleClick", { ...h, spatialHit: this.#bt(h) });
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) });
275
297
  });
276
298
  }
277
299
  /**
@@ -286,9 +308,9 @@ class ci extends ft {
286
308
  * `setSeriesVisible` / `setLayerVisible` calls — produce one overlay
287
309
  * render instead of N.
288
310
  */
289
- #p() {
290
- if (this.#dt++, this.#L > 0) {
291
- this.#G = !0;
311
+ #f() {
312
+ if (this.#bt++, this.#L > 0) {
313
+ this.#J = !0;
292
314
  return;
293
315
  }
294
316
  this.emit("overlayChange");
@@ -302,39 +324,46 @@ class ci extends ft {
302
324
  * @internal
303
325
  */
304
326
  getOverlayVersion() {
305
- return this.#dt;
327
+ return this.#bt;
306
328
  }
307
329
  /**
308
330
  * Return a series ID: use the provided hint if it's non-empty and not already taken,
309
331
  * otherwise generate a new auto ID. Auto-generated IDs never collide with each other
310
332
  * or with user-provided IDs because they use a monotonically increasing counter.
311
333
  */
312
- #Dt(t) {
334
+ #$t(t) {
313
335
  if (t && !this.#t.some((e) => e.id === t))
314
336
  return t;
315
337
  let i;
316
338
  do
317
- i = `series_${++Dt}`;
339
+ i = `series_${++Et}`;
318
340
  while (this.#t.some((e) => e.id === i));
319
341
  return i;
320
342
  }
321
343
  addSeries(t, i = {}) {
322
- const e = typeof t == "string" ? vt(t) : t;
344
+ const e = typeof t == "string" ? Dt(t) : t;
323
345
  if (!e)
324
346
  throw new Error(
325
347
  `[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).`
326
348
  );
327
- const { layers: s, id: r, label: n, labels: a, ...h } = i, l = s ?? 1, o = {
328
- ...this.#m.defaults(e.type),
329
- ...h,
330
- ...this.#m.overrides(e.type)
331
- }, m = e.create({ theme: this.#n, layerCount: l }, o), u = a ?? (typeof n == "string" ? [n] : void 0), c = this.#Dt(r);
332
- return m.onDataChanged?.(() => this.onDataChanged()), this.#t.push({ id: c, labels: u, renderer: m, visible: !0 }), this.#D = null, this.#q(), this.emit("seriesChange"), this.#p(), c;
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;
333
359
  }
334
360
  /** Remove a series by ID and clean up its resources. */
335
361
  removeSeries(t) {
336
362
  const i = this.#t.findIndex((e) => e.id === t);
337
- i >= 0 && (this.#t[i].renderer.dispose(), this.#t.splice(i, 1), this.#D = null, this.#q(), this.#i.markDirty(), this.emit("seriesChange"), this.#p());
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();
366
+ }
338
367
  }
339
368
  // --- Markers (point annotations) -----------------------------------------
340
369
  /**
@@ -347,24 +376,24 @@ class ci extends ft {
347
376
  * appear in series queries.
348
377
  */
349
378
  addMarker(t) {
350
- const i = `marker_${++Rt}`;
351
- return this.#u.push(this.#ft(i, t)), this.#h.markDirty(), i;
379
+ const i = `marker_${++Bt}`;
380
+ return this.#u.push(this.#kt(i, t)), this.#o.markDirty(), i;
352
381
  }
353
382
  /** Replace a marker's config in place. No-op when the id is unknown. */
354
383
  updateMarker(t, i) {
355
384
  const e = this.#u.findIndex((s) => s.id === t);
356
- e !== -1 && (this.#u[e] = this.#ft(t, i), this.#h.markDirty());
385
+ e !== -1 && (this.#u[e] = this.#kt(t, i), this.#o.markDirty());
357
386
  }
358
387
  /** Remove a marker by id. No-op when the id is unknown. */
359
388
  removeMarker(t) {
360
389
  const i = this.#u.findIndex((e) => e.id === t);
361
- i !== -1 && (this.#u.splice(i, 1), this.#h.markDirty());
390
+ i !== -1 && (this.#u.splice(i, 1), this.#o.markDirty());
362
391
  }
363
392
  /** Read-only snapshot of the registered markers (normalized). Mutate via add / update / removeMarker. */
364
393
  getMarkers() {
365
394
  return this.#u;
366
395
  }
367
- #ft(t, i) {
396
+ #kt(t, i) {
368
397
  return {
369
398
  id: t,
370
399
  time: x(i.time),
@@ -377,14 +406,14 @@ class ci extends ft {
377
406
  };
378
407
  }
379
408
  /** Resolve a marker's Y value: explicit `value`, else snapped to its series at `time`. Null when neither resolves. */
380
- #Rt(t) {
409
+ #Yt(t) {
381
410
  if (t.value !== void 0 && Number.isFinite(t.value)) return t.value;
382
411
  if (t.seriesId === void 0) return null;
383
412
  const i = this.getDataAtTime(t.seriesId, t.time);
384
413
  return i ? "close" in i ? i.close : i.value : null;
385
414
  }
386
415
  /** Resolve a marker's color: explicit `color`, else the series color, else the theme line color. */
387
- #Ct(t) {
416
+ #Xt(t) {
388
417
  if (t.color !== void 0) return t.color;
389
418
  if (t.seriesId !== void 0) {
390
419
  const i = this.getSeriesColor(t.seriesId);
@@ -392,16 +421,16 @@ class ci extends ft {
392
421
  }
393
422
  return this.#n.line.color;
394
423
  }
395
- #It(t, i, e) {
424
+ #Zt(t, i, e) {
396
425
  if (this.#u.length === 0) return;
397
- const s = performance.now() / ($ * 2 * Math.PI) % 1, r = this.#j();
426
+ const s = performance.now() / (q * 2 * Math.PI) % 1, r = this.#it();
398
427
  for (const n of this.#u) {
399
- const a = this.#Rt(n);
428
+ const a = this.#Yt(n);
400
429
  if (a === null) continue;
401
- const h = this.#yt(r, n.time);
402
- if (h <= 0) continue;
430
+ const o = this.#Lt(r, n.time);
431
+ if (o <= 0) continue;
403
432
  const l = t.context.globalAlpha;
404
- h < 1 && (t.context.globalAlpha = l * h), ct({
433
+ o < 1 && (t.context.globalAlpha = l * o), bt({
405
434
  scope: t,
406
435
  timeScale: this.timeScale,
407
436
  yScale: this.yScale,
@@ -409,7 +438,7 @@ class ci extends ft {
409
438
  marker: {
410
439
  time: n.time,
411
440
  value: a,
412
- color: this.#Ct(n),
441
+ color: this.#Xt(n),
413
442
  shape: n.shape,
414
443
  pulse: n.pulse,
415
444
  label: n.label
@@ -417,7 +446,7 @@ class ci extends ft {
417
446
  phase: s,
418
447
  plotWidth: i,
419
448
  plotHeight: e
420
- }), h < 1 && (t.context.globalAlpha = l);
449
+ }), o < 1 && (t.context.globalAlpha = l);
421
450
  }
422
451
  }
423
452
  // --- Time regions (shaded bands) -----------------------------------------
@@ -433,13 +462,13 @@ class ci extends ft {
433
462
  * legend.
434
463
  */
435
464
  addRegion(t) {
436
- const i = `region_${++Ct}`;
437
- return this.#x.push(this.#gt(i, t)), this.#i.markDirty(), i;
465
+ const i = `region_${++$t}`;
466
+ return this.#x.push(this.#At(i, t)), this.#i.markDirty(), i;
438
467
  }
439
468
  /** Replace a region's config in place. No-op when the id is unknown. */
440
469
  updateRegion(t, i) {
441
470
  const e = this.#x.findIndex((s) => s.id === t);
442
- e !== -1 && (this.#x[e] = this.#gt(t, i), this.#i.markDirty());
471
+ e !== -1 && (this.#x[e] = this.#At(t, i), this.#i.markDirty());
443
472
  }
444
473
  /** Remove a region by id. No-op when the id is unknown. */
445
474
  removeRegion(t) {
@@ -450,7 +479,7 @@ class ci extends ft {
450
479
  getRegions() {
451
480
  return this.#x;
452
481
  }
453
- #gt(t, i) {
482
+ #At(t, i) {
454
483
  const { to: e } = i, s = e === void 0 || e === "now" ? null : x(e);
455
484
  return {
456
485
  id: t,
@@ -461,11 +490,11 @@ class ci extends ft {
461
490
  label: i.label
462
491
  };
463
492
  }
464
- #Pt(t, i, e) {
493
+ #_t(t, i, e) {
465
494
  if (this.#x.length !== 0)
466
495
  for (const s of this.#x) {
467
- const { color: r, fill: n } = mt(s, this.#n);
468
- ut({
496
+ const { color: r, fill: n } = St(s, this.#n);
497
+ kt({
469
498
  scope: t,
470
499
  timeScale: this.timeScale,
471
500
  theme: this.#n,
@@ -487,24 +516,24 @@ class ci extends ft {
487
516
  * legend.
488
517
  */
489
518
  addLine(t) {
490
- const i = `line_${++It}`;
491
- return this.#y.push(this.#pt(i, t)), this.#h.markDirty(), i;
519
+ const i = `line_${++Yt}`;
520
+ return this.#y.push(this.#Tt(i, t)), this.#o.markDirty(), i;
492
521
  }
493
522
  /** Replace a reference line's config in place. No-op when the id is unknown. */
494
523
  updateLine(t, i) {
495
524
  const e = this.#y.findIndex((s) => s.id === t);
496
- e !== -1 && (this.#y[e] = this.#pt(t, i), this.#h.markDirty());
525
+ e !== -1 && (this.#y[e] = this.#Tt(t, i), this.#o.markDirty());
497
526
  }
498
527
  /** Remove a reference line by id. No-op when the id is unknown. */
499
528
  removeLine(t) {
500
529
  const i = this.#y.findIndex((e) => e.id === t);
501
- i !== -1 && (this.#y.splice(i, 1), this.#h.markDirty());
530
+ i !== -1 && (this.#y.splice(i, 1), this.#o.markDirty());
502
531
  }
503
532
  /** Read-only snapshot of the registered reference lines (normalized). Mutate via add / update / removeLine. */
504
533
  getLines() {
505
534
  return this.#y;
506
535
  }
507
- #pt(t, i) {
536
+ #Tt(t, i) {
508
537
  const { time: e, value: s } = i, r = {
509
538
  id: t,
510
539
  color: i.color,
@@ -514,14 +543,14 @@ class ci extends ft {
514
543
  };
515
544
  return e !== void 0 ? { ...r, orientation: "vertical", coord: x(e) } : { ...r, orientation: "horizontal", coord: s ?? Number.NaN };
516
545
  }
517
- #Ht(t, i, e) {
546
+ #Gt(t, i, e) {
518
547
  if (this.#y.length === 0) return;
519
- const s = this.#j();
548
+ const s = this.#it();
520
549
  for (const r of this.#y) {
521
- const n = s >= 1 ? 1 : r.orientation === "vertical" ? this.#yt(s, r.coord) : P(s);
550
+ const n = s >= 1 ? 1 : r.orientation === "vertical" ? this.#Lt(s, r.coord) : W(s);
522
551
  if (n <= 0) continue;
523
552
  const a = t.context.globalAlpha;
524
- n < 1 && (t.context.globalAlpha = a * n), dt({
553
+ n < 1 && (t.context.globalAlpha = a * n), xt({
525
554
  scope: t,
526
555
  timeScale: this.timeScale,
527
556
  yScale: this.yScale,
@@ -546,8 +575,15 @@ class ci extends ft {
546
575
  * underlying animator mutates between frames.
547
576
  */
548
577
  getAnimationState() {
549
- return this.#s.getAnimationState();
578
+ return this.#wt(this.#s.getAnimationState());
579
+ }
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) };
550
584
  }
585
+ /** `yScale.isPlottable`, bound once for the helpers that take a predicate. */
586
+ #Y = (t) => this.yScale.isPlottable(t);
551
587
  /**
552
588
  * Reveal-front position in `[0, 1]` of any in-flight initial-load intro.
553
589
  * With a `seriesId`, that series' own front; without, the minimum across
@@ -574,10 +610,10 @@ class ci extends ft {
574
610
  * has actually been observed in progress, so an annotation drawn on a chart
575
611
  * that has no data yet still rides the reveal once data arrives.
576
612
  */
577
- #j() {
578
- if (this.#at) return 1;
613
+ #it() {
614
+ if (this.#ft) return 1;
579
615
  const t = this.getIntroFront();
580
- return t < 1 ? (this.#nt = !0, t) : (this.#nt && (this.#at = !0), 1);
616
+ return t < 1 ? (this.#mt = !0, t) : (this.#mt && (this.#ft = !0), 1);
581
617
  }
582
618
  /**
583
619
  * Entrance alpha for an overlay pinned at `time`, given the intro reveal
@@ -585,10 +621,10 @@ class ci extends ft {
585
621
  * then fades in over a short band — so annotations surface in step with the
586
622
  * series drawing past them. `1` once the intro has settled.
587
623
  */
588
- #yt(t, i) {
624
+ #Lt(t, i) {
589
625
  if (t >= 1) return 1;
590
626
  const { from: e, to: s } = this.timeScale.getRange(), r = s - e, n = r > 0 ? (i - e) / r : 0;
591
- return bt((t - n) / 0.12, 0, 1);
627
+ return Rt((t - n) / 0.12, 0, 1);
592
628
  }
593
629
  /**
594
630
  * Replace all data for a series.
@@ -601,7 +637,7 @@ class ci extends ft {
601
637
  */
602
638
  setSeriesData(t, i, e) {
603
639
  const s = this.#t.find((r) => r.id === t);
604
- s && (this.#Q = !0, s.renderer.setData(i, e));
640
+ s && (this.#nt = !0, s.renderer.setData(i, e));
605
641
  }
606
642
  /** Append a new data point to the end of a series (real-time tick). */
607
643
  appendData(t, i, e) {
@@ -624,8 +660,8 @@ class ci extends ft {
624
660
  prependData(t, i, e) {
625
661
  const s = this.#t.find((n) => n.id === t);
626
662
  if (s === void 0 || s.renderer.prependPoints === void 0) return;
627
- const r = this.#Z.left;
628
- r !== null && s.renderer.setHistoryHandoff !== void 0 && performance.now() - r.at < Lt && (s.renderer.setHistoryHandoff(r.bars), this.#P.left = !0, this.#N.left = null, this.#Z.left = null, this.#h.markDirty()), s.renderer.prependPoints(i, e);
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);
629
665
  }
630
666
  /** Update the last data point of a series in place (e.g. live candle update). */
631
667
  updateData(t, i, e) {
@@ -649,12 +685,13 @@ class ci extends ft {
649
685
  }
650
686
  /** Update visual options (color, width, etc.) for an existing series. */
651
687
  updateSeriesOptions(t, i) {
652
- const e = this.#t.find((o) => o.id === t);
688
+ const e = this.#t.find((h) => h.id === t);
653
689
  if (!e) return;
654
- const s = e.labels, r = e.renderer.getLayerColors().slice(), n = this.#m.overrides(e.renderer.kind);
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);
655
692
  e.renderer.updateOptions({ ...i, ...n }), "label" in i && typeof i.label == "string" && (e.labels = [i.label]), this.#i.markDirty();
656
- const a = e.renderer.getLayerColors(), h = r.length !== a.length || r.some((o, m) => o !== a[m]), l = !N(s, e.labels);
657
- (h || l) && this.#p();
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();
658
695
  }
659
696
  /**
660
697
  * Batch multiple updates: suppress recomputes until `fn` returns. Exceptions
@@ -666,14 +703,14 @@ class ci extends ft {
666
703
  t();
667
704
  } finally {
668
705
  if (--this.#L <= 0) {
669
- if (this.#L = 0, this.#_)
670
- this.#_ = !1, this.#H = !1, this.onDataChanged();
671
- else if (this.#H) {
672
- this.#H = !1, this.#g = !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;
673
710
  const i = performance.now();
674
- this.#s.onAxisReconfig(i), this.#v(i), this.#i.markDirty();
711
+ this.#s.onAxisReconfig(i), this.#b(i), this.#i.markDirty();
675
712
  }
676
- this.#G && (this.#G = !1, this.emit("overlayChange"));
713
+ this.#J && (this.#J = !1, this.emit("overlayChange"));
677
714
  }
678
715
  }
679
716
  }
@@ -687,15 +724,15 @@ class ci extends ft {
687
724
  setSeriesVisible(t, i) {
688
725
  const e = this.#t.find((n) => n.id === t);
689
726
  if (!e || e.visible === i) return;
690
- e.visible = i, this.#p(), this.#q();
691
- const s = this.#m.toggleMs;
727
+ e.visible = i, this.#f(), this.#st();
728
+ const s = this.#d.toggleMs;
692
729
  if (e.renderer.setAlpha?.(i ? 1 : 0, s), this.#i.markDirty(), this.#L > 0) {
693
- this.#H = !0;
730
+ this.#V = !0;
694
731
  return;
695
732
  }
696
733
  this.#g = !0;
697
734
  const r = performance.now();
698
- this.#s.onSeriesVisibilityChanged(r), this.#v(r);
735
+ this.#s.onSeriesVisibilityChanged(r), this.#b(r);
699
736
  }
700
737
  /**
701
738
  * Whether streaming ticks slide the visible range to track the data tail.
@@ -705,7 +742,7 @@ class ci extends ft {
705
742
  * containing the tail.
706
743
  */
707
744
  getAutoScroll() {
708
- return this.#f;
745
+ return this.#m;
709
746
  }
710
747
  isSeriesVisible(t) {
711
748
  return this.#t.find((i) => i.id === t)?.visible ?? !0;
@@ -723,15 +760,15 @@ class ci extends ft {
723
760
  setLayerVisible(t, i, e) {
724
761
  const s = this.#t.find((a) => a.id === t);
725
762
  if (!s || s.renderer.getLayerCount() <= 1 || s.renderer.isLayerVisible(i) === e) return;
726
- s.renderer.setLayerVisible(i, e), this.#p();
727
- const r = this.#m.toggleMs;
763
+ s.renderer.setLayerVisible(i, e), this.#f();
764
+ const r = this.#d.toggleMs;
728
765
  if (s.renderer.setLayerAlpha?.(i, e ? 1 : 0, r), this.#i.markDirty(), this.#L > 0) {
729
- this.#H = !0;
766
+ this.#V = !0;
730
767
  return;
731
768
  }
732
769
  this.#g = !0;
733
770
  const n = performance.now();
734
- this.#s.onSeriesVisibilityChanged(n), this.#v(n);
771
+ this.#s.onSeriesVisibilityChanged(n), this.#b(n);
735
772
  }
736
773
  isLayerVisible(t, i) {
737
774
  return this.#t.find((e) => e.id === t)?.renderer.isLayerVisible(i) ?? !0;
@@ -741,10 +778,10 @@ class ci extends ft {
741
778
  const { first: t, last: i } = this.getDataBounds();
742
779
  if (t === void 0 || i === void 0) return;
743
780
  const e = this.#a.size.media.width - this.yAxisWidth;
744
- this.#W(t, i, e), this.#X({ xEase: !0 });
781
+ this.#O(t, i, e), this.#G({ xEase: !0 });
745
782
  }
746
783
  getVisibleRange() {
747
- return this.#o.from === 0 && this.#o.to === 0 ? this.#s.getAnimationState().xRange : this.#o;
784
+ return this.#h.from === 0 && this.#h.to === 0 ? this.#s.getAnimationState().xRange : this.#h;
748
785
  }
749
786
  /**
750
787
  * Return the full span of registered data, or `null` before any data has
@@ -752,7 +789,7 @@ class ci extends ft {
752
789
  * tracking and is cheaper than recomputing from series stores.
753
790
  */
754
791
  getDataRange() {
755
- return this.#A === null || this.#e === null ? null : { from: this.#A, to: this.#e };
792
+ return this.#T === null || this.#e === null ? null : { from: this.#T, to: this.#e };
756
793
  }
757
794
  /**
758
795
  * Imperatively set the visible time range. Three forms:
@@ -783,27 +820,27 @@ class ci extends ft {
783
820
  if (!Number.isInteger(t) || t < 2) return;
784
821
  const { first: n, last: a } = this.getDataBounds();
785
822
  if (n === void 0 || a === void 0) return;
786
- const h = Math.max(n, a - (t - 1) * this.#r), l = this.#a.size.media.width - this.yAxisWidth;
787
- this.#W(h, a, l), this.#X();
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();
788
825
  return;
789
826
  }
790
827
  if ("bars" in t) {
791
828
  if (!Number.isInteger(t.bars) || t.bars < 2) return;
792
829
  const n = x(t.from), a = n + t.bars * this.#r;
793
- this.#f = this.#e !== null && this.#e >= n && this.#e <= a, this.#C({ from: n, to: a }, { emitChange: !0 }), this.#T = !0, this.#X();
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();
794
831
  return;
795
832
  }
796
833
  const e = x(t.from), s = x(t.to);
797
- this.#f = this.#e !== null && this.#e >= e && this.#e <= s, this.#T = !1;
834
+ this.#m = this.#e !== null && this.#e >= e && this.#e <= s, this.#M = !1;
798
835
  const r = this.#C({ from: e, to: s }, { emitChange: !0 });
799
836
  if (i?.gesture) {
800
- r && this.#Bt();
837
+ r && this.#Qt();
801
838
  return;
802
839
  }
803
- this.#X();
840
+ this.#G();
804
841
  }
805
842
  getYRange() {
806
- return this.#z;
843
+ return this.#B;
807
844
  }
808
845
  getCrosshairPosition() {
809
846
  return this.#c;
@@ -844,36 +881,28 @@ class ci extends ft {
844
881
  setCrosshair(t) {
845
882
  if (t === null) {
846
883
  if (this.#c === null) return;
847
- this.#c = null, this.#h.markDirty(), this.emit("crosshairMove", null);
884
+ this.#c = null, this.#o.markDirty(), this.emit("crosshairMove", null);
848
885
  return;
849
886
  }
850
887
  const { time: i } = t;
851
888
  if (!Number.isFinite(i)) return;
852
889
  const e = this.#c;
853
890
  let s;
854
- if (t.y !== void 0)
855
- s = t.y;
856
- else if (e !== null)
857
- s = e.y;
858
- else {
859
- const a = this.#z;
860
- s = (a.min + a.max) / 2;
861
- }
862
- if (!Number.isFinite(s) || e !== null && e.time === i && e.y === s) return;
863
- const r = this.timeScale.timeToX(i), n = this.yScale.valueToY(s);
864
- this.#$ = "time", this.#c = { mediaX: r, mediaY: n, time: i, y: s }, this.#h.markDirty(), this.emit("crosshairMove", this.#c);
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);
865
894
  }
866
895
  /** Get the last visible value and whether the absolute last point is on screen. */
867
896
  getLastValue(t) {
868
- return J(t, this.#t, this.#s.getAnimationState().xRange);
897
+ return nt(t, this.#t, this.#s.getAnimationState().xRange);
869
898
  }
870
899
  /** Get the second-to-last value, useful for computing change. */
871
900
  getPreviousClose(t) {
872
- return Q(t, this.#t);
901
+ return at(t, this.#t);
873
902
  }
874
903
  getLastData(t) {
875
904
  const i = this.#t.find((e) => e.id === t);
876
- return !i || !k(i.renderer) ? null : i.renderer.getLastDataPoint();
905
+ return !i || !b(i.renderer) ? null : i.renderer.getLastDataPoint();
877
906
  }
878
907
  /** Find the data point closest to the given timestamp within one data interval. */
879
908
  getDataAtTime(t, i) {
@@ -891,7 +920,7 @@ class ci extends ft {
891
920
  return e?.visible ? e.renderer.getLayerSnapshots?.(i, this.#r) ?? null : null;
892
921
  }
893
922
  getSeriesIds() {
894
- return this.#D || (this.#D = this.#t.map((t) => t.id)), this.#D.slice();
923
+ return this.#P || (this.#P = this.#t.map((t) => t.id)), this.#P.slice();
895
924
  }
896
925
  /**
897
926
  * True if any visible series has a time axis (line / bar / candlestick).
@@ -899,12 +928,30 @@ class ci extends ft {
899
928
  * has no meaningful x/y coordinate system, so the dashed hairlines would
900
929
  * just be visual noise.
901
930
  */
902
- #vt() {
931
+ #Mt() {
903
932
  for (const t of this.#t)
904
- if (t.visible && k(t.renderer))
933
+ if (t.visible && b(t.renderer))
905
934
  return !0;
906
935
  return !1;
907
936
  }
937
+ /**
938
+ * True when series exist and none has a time axis (pie, heatmap): those
939
+ * renderers lay out against the whole canvas, so there are no axis gutters
940
+ * to reserve and nothing slides under an axis column. Reads registration,
941
+ * not visibility — a legend toggle must not shift the layout.
942
+ */
943
+ #X() {
944
+ return this.#t.length === 0 ? !1 : this.#t.every((t) => !b(t.renderer));
945
+ }
946
+ #Z() {
947
+ return { yAxisWidth: this.yAxisWidth, xAxisHeight: this.xAxisHeight, yAxisPosition: this.yAxisPosition };
948
+ }
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());
954
+ }
908
955
  /**
909
956
  * Whether pan/zoom gestures currently apply — true when any visible series
910
957
  * has a time axis. Part of the {@link PanZoomTarget} surface: on a
@@ -912,7 +959,7 @@ class ci extends ft {
912
959
  * wheel and touch events, so the page scrolls normally under the cursor.
913
960
  */
914
961
  canPanZoom() {
915
- return this.#vt();
962
+ return this.#Mt();
916
963
  }
917
964
  /**
918
965
  * Push the current pan/zoom capability into the interaction layer (canvas
@@ -921,8 +968,8 @@ class ci extends ft {
921
968
  * every mutation that can flip {@link canPanZoom}: series add/remove and
922
969
  * series visibility.
923
970
  */
924
- #q() {
925
- this.#M?.syncPanZoomMode();
971
+ #st() {
972
+ this.#D?.syncPanZoomMode();
926
973
  }
927
974
  /**
928
975
  * Filter `getSeriesIds()` by renderer type. A concrete kind (`'pie'`,
@@ -938,19 +985,19 @@ class ci extends ft {
938
985
  getSeriesIdsByType(t, i) {
939
986
  const e = i?.visibleOnly === !0, s = i?.singleLayerOnly === !0, r = [];
940
987
  for (const n of this.#t) {
941
- if (!(t === "time" ? k(n.renderer) : n.renderer.kind === t)) continue;
988
+ if (!(t === "time" ? b(n.renderer) : n.renderer.kind === t)) continue;
942
989
  const a = n.renderer.getLayerCount();
943
990
  if (!(s && a > 1)) {
944
991
  if (e) {
945
992
  if (!n.visible) continue;
946
993
  if (a > 1) {
947
- let h = !1;
994
+ let o = !1;
948
995
  for (let l = 0; l < a; l++)
949
996
  if (n.renderer.isLayerVisible(l)) {
950
- h = !0;
997
+ o = !0;
951
998
  break;
952
999
  }
953
- if (!h) continue;
1000
+ if (!o) continue;
954
1001
  }
955
1002
  }
956
1003
  r.push(n.id);
@@ -965,7 +1012,7 @@ class ci extends ft {
965
1012
  * Returns `null` for unknown ids or empty series.
966
1013
  */
967
1014
  getStackedLastValue(t) {
968
- return tt(t, this.#t, this.#s.getAnimationState().xRange);
1015
+ return ot(t, this.#t, this.#s.getAnimationState().xRange);
969
1016
  }
970
1017
  /**
971
1018
  * Stacked value at an arbitrary `time` — the cumulative stack top at that
@@ -1022,7 +1069,7 @@ class ci extends ft {
1022
1069
  */
1023
1070
  setSeriesLabels(t, i) {
1024
1071
  const e = this.#t.find((s) => s.id === t);
1025
- !e || N(e.labels, i) || (e.labels = i, this.#p());
1072
+ !e || Z(e.labels, i) || (e.labels = i, this.#f());
1026
1073
  }
1027
1074
  /**
1028
1075
  * Set the per-layer color overrides carried in `data` (sparse — `undefined`
@@ -1033,7 +1080,7 @@ class ci extends ft {
1033
1080
  */
1034
1081
  setLayerColors(t, i) {
1035
1082
  const e = this.#t.find((s) => s.id === t);
1036
- !e || Pt(e.colorOverrides, i) || (e.colorOverrides = i, e.renderer.setColorOverrides?.(i), this.#i.markDirty(), this.#p());
1083
+ !e || Xt(e.colorOverrides, i) || (e.colorOverrides = i, e.renderer.setColorOverrides?.(i), this.#i.markDirty(), this.#f());
1037
1084
  }
1038
1085
  /** Get per-layer colors for a series. Returns null for single-layer non-bar/line series. */
1039
1086
  getSeriesLayers(t) {
@@ -1058,31 +1105,34 @@ class ci extends ft {
1058
1105
  this.#n = t;
1059
1106
  for (const e of this.#t)
1060
1107
  e.renderer.applyTheme(t, i);
1061
- this.#i.markDirty(), this.#p();
1108
+ this.#i.markDirty(), this.#f();
1062
1109
  }
1063
1110
  getTheme() {
1064
1111
  return this.#n;
1065
1112
  }
1066
1113
  /** BCP 47 locale applied to the built-in time-axis / tooltip / crosshair formatting. */
1067
1114
  setLocale(t) {
1068
- this.timeScale.setLocale(t), this.#p();
1115
+ this.timeScale.setLocale(t), this.#f();
1069
1116
  }
1070
1117
  getLocale() {
1071
1118
  return this.timeScale.getLocale();
1072
1119
  }
1073
1120
  /** IANA timezone applied to the same built-in date-time formatting. */
1074
1121
  setTimeZone(t) {
1075
- this.timeScale.setTimeZone(t), this.#p();
1122
+ this.timeScale.setTimeZone(t), this.#f();
1076
1123
  }
1077
1124
  getTimeZone() {
1078
1125
  return this.timeScale.getTimeZone();
1079
1126
  }
1080
1127
  /** Update axis configuration and re-render. */
1081
1128
  setAxis(t) {
1082
- const i = this.yAxisWidth, e = this.xAxisHeight;
1083
- this.#B = t, this.#d = { min: t.y?.min, max: t.y?.max }, this.#g = !0;
1084
- const s = performance.now();
1085
- this.#s.onAxisReconfig(s), this.#v(s), (this.yAxisWidth !== i || this.xAxisHeight !== e) && this.syncScales(), this.#i.markDirty();
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();
1086
1136
  }
1087
1137
  /**
1088
1138
  * Apply label-density knobs driven by `<YAxis labelCount=… minLabelSpacing=…>`
@@ -1106,8 +1156,9 @@ class ci extends ft {
1106
1156
  getLayout() {
1107
1157
  const t = this.#a.size.media, i = this.yAxisWidth, e = this.xAxisHeight;
1108
1158
  return {
1109
- chartArea: { x: 0, y: 0, width: t.width - i, height: t.height - e },
1159
+ chartArea: { x: this.plotLeft, y: 0, width: t.width - i, height: t.height - e },
1110
1160
  yAxisWidth: i,
1161
+ yAxisPosition: this.yAxisPosition,
1111
1162
  xAxisHeight: e
1112
1163
  };
1113
1164
  }
@@ -1124,19 +1175,19 @@ class ci extends ft {
1124
1175
  */
1125
1176
  setPadding(t) {
1126
1177
  const i = this.#l;
1127
- this.#l = z(t);
1128
- const e = this.#l, s = !V(i.left, e.left) || !V(i.right, e.right), r = i.top !== e.top || i.bottom !== e.bottom;
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;
1129
1180
  if (s) {
1130
1181
  const { first: n, last: a } = this.getDataBounds();
1131
1182
  if (n !== void 0 && a !== void 0) {
1132
- const h = this.#a.size.media.width - this.yAxisWidth;
1133
- this.#W(n, a, h);
1183
+ const o = this.#a.size.media.width - this.yAxisWidth;
1184
+ this.#O(n, a, o);
1134
1185
  }
1135
1186
  }
1136
1187
  if (s || r) {
1137
1188
  this.#g = !0;
1138
1189
  const n = performance.now();
1139
- this.#s.onAxisReconfig(n), this.#v(n);
1190
+ this.#s.onAxisReconfig(n), this.#b(n);
1140
1191
  }
1141
1192
  this.syncScales(), this.#i.markDirty(), r && this.emit("viewportChange");
1142
1193
  }
@@ -1152,8 +1203,14 @@ class ci extends ft {
1152
1203
  * the next render frame.
1153
1204
  */
1154
1205
  setFade(t) {
1155
- const i = H(t), e = this.#k;
1156
- i.top === e.top && i.right === e.right && i.left === e.left || (this.#k = i, this.#i.markDirty());
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());
1208
+ }
1209
+ /** Report the floating header's height (see {@link headerHeight}). The
1210
+ * framework wrappers keep it in sync with the measured Title / InfoBar. */
1211
+ setHeaderHeight(t) {
1212
+ const i = Number.isFinite(t) && t > 0 ? t : 0;
1213
+ i !== this.#U && (this.#U = i, this.#f());
1157
1214
  }
1158
1215
  /**
1159
1216
  * Set the visual state for one side of the chart. Typically called in
@@ -1165,23 +1222,23 @@ class ci extends ft {
1165
1222
  * symmetry and currently renders identically to `idle`.
1166
1223
  */
1167
1224
  setEdgeState(t, i) {
1168
- if (this.#b[t] === i) return;
1169
- const e = this.#b[t] === "loading", s = this.#P[t];
1170
- this.#P[t] = !1, this.#N[t] = e && !s && (i === "idle" || i === "has-more") ? performance.now() : null, this.#b[t] = i, this.#h.markDirty();
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();
1171
1228
  }
1172
1229
  /** Read the current host-declared state for a given edge. */
1173
1230
  getEdgeState(t) {
1174
- return this.#b[t];
1231
+ return this.#S[t];
1175
1232
  }
1176
1233
  /**
1177
1234
  * Alpha for a side's `loading` exit fade, or `null` when none is in
1178
1235
  * flight. Self-cleaning: an elapsed fade clears its start stamp.
1179
1236
  */
1180
- #xt(t) {
1237
+ #Dt(t) {
1181
1238
  const i = this.#N[t];
1182
1239
  if (i === null) return null;
1183
- const e = (performance.now() - i) / At;
1184
- return e >= 1 ? (this.#N[t] = null, null) : 1 - P(Math.max(0, e));
1240
+ const e = (performance.now() - i) / zt;
1241
+ return e >= 1 ? (this.#N[t] = null, null) : 1 - W(Math.max(0, e));
1185
1242
  }
1186
1243
  /**
1187
1244
  * Swap the painter used for a side's `loading` spinner — the same
@@ -1190,7 +1247,7 @@ class ci extends ft {
1190
1247
  * immediately if that side is currently in the `loading` state.
1191
1248
  */
1192
1249
  setEdgeIndicator(t, i) {
1193
- this.#ct[t] = i, this.#b[t] === "loading" && this.#h.markDirty();
1250
+ this.#vt[t] = i, this.#S[t] === "loading" && this.#o.markDirty();
1194
1251
  }
1195
1252
  /**
1196
1253
  * Resolve the boundary point's Y position and series kind on the primary
@@ -1200,14 +1257,14 @@ class ci extends ft {
1200
1257
  * `open` (the value immediately preceding the boundary); `right` from the
1201
1258
  * last visible candle's `close`.
1202
1259
  */
1203
- #zt(t, i) {
1260
+ #Ut(t, i) {
1204
1261
  const e = this.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 })[0] ?? this.getSeriesIdsByType("time", { visibleOnly: !0 })[0];
1205
1262
  if (!e) return null;
1206
1263
  const s = this.getDataAtTime(e, i);
1207
1264
  if (!s) return null;
1208
1265
  const r = this.#t.find((n) => n.id === e);
1209
1266
  return r ? {
1210
- valueY: this.yScale.valueToY(U(t, s)),
1267
+ valueY: this.yScale.valueToY(it(t, s)),
1211
1268
  seriesKind: r.renderer.kind
1212
1269
  } : null;
1213
1270
  }
@@ -1217,7 +1274,7 @@ class ci extends ft {
1217
1274
  * placeholder shapes to match the real bars that will render once data
1218
1275
  * lands. `null` when the viewport has no meaningful span yet.
1219
1276
  */
1220
- #Vt() {
1277
+ #Kt() {
1221
1278
  const t = this.timeScale.barWidthMedia(this.#r);
1222
1279
  return Number.isFinite(t) && t > 0 ? t : null;
1223
1280
  }
@@ -1236,7 +1293,7 @@ class ci extends ft {
1236
1293
  * probed, not kind-gated: a kind check here silently kills hover for the
1237
1294
  * next spatial renderer.
1238
1295
  */
1239
- #J(t) {
1296
+ #rt(t) {
1240
1297
  const i = this.#a.size, e = this.#l, s = {
1241
1298
  top: e.top * i.verticalPixelRatio,
1242
1299
  bottom: e.bottom * i.verticalPixelRatio
@@ -1244,12 +1301,12 @@ class ci extends ft {
1244
1301
  let r = !1, n = null;
1245
1302
  for (const a of this.#t) {
1246
1303
  if (!a.renderer.hitTest || !a.renderer.setHoverIndex) continue;
1247
- let h = -1;
1304
+ let o = -1;
1248
1305
  if (t) {
1249
- const l = t.mediaX * i.horizontalPixelRatio, o = t.mediaY * i.verticalPixelRatio;
1250
- h = a.renderer.hitTest(l, o, i.bitmap.width, i.bitmap.height, s);
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);
1251
1308
  }
1252
- a.renderer.setHoverIndex(h) && (r = !0), h >= 0 && !n && (n = { seriesId: a.id, index: h, info: a.renderer.getHoverInfo?.(this.#n) ?? null });
1309
+ a.renderer.setHoverIndex(o) && (r = !0), o >= 0 && !n && (n = { seriesId: a.id, index: o, info: a.renderer.getHoverInfo?.(this.#n) ?? null });
1253
1310
  }
1254
1311
  r && (this.#i.markDirty(), this.emit("seriesHover", n));
1255
1312
  }
@@ -1260,15 +1317,15 @@ class ci extends ft {
1260
1317
  * Returns the first spatial series (pie, heatmap, or a custom spatial kind)
1261
1318
  * whose `hitTest` claims the position, or `null` if none does.
1262
1319
  */
1263
- #bt(t) {
1320
+ #Pt(t) {
1264
1321
  const i = this.#a.size, e = this.#l, s = {
1265
1322
  top: e.top * i.verticalPixelRatio,
1266
1323
  bottom: e.bottom * i.verticalPixelRatio
1267
- }, r = t.mediaX * i.horizontalPixelRatio, n = t.mediaY * i.verticalPixelRatio;
1324
+ }, r = (t.mediaX - this.plotLeft) * i.horizontalPixelRatio, n = t.mediaY * i.verticalPixelRatio;
1268
1325
  for (const a of this.#t) {
1269
1326
  if (!a.renderer.hitTest) continue;
1270
- const h = a.renderer.hitTest(r, n, i.bitmap.width, i.bitmap.height, s);
1271
- if (h >= 0) return { seriesId: a.id, index: h };
1327
+ const o = a.renderer.hitTest(r, n, i.bitmap.width, i.bitmap.height, s);
1328
+ if (o >= 0) return { seriesId: a.id, index: o };
1272
1329
  }
1273
1330
  return null;
1274
1331
  }
@@ -1292,40 +1349,40 @@ class ci extends ft {
1292
1349
  * viewport didn't move, the re-derived values are bit-identical and the
1293
1350
  * method exits without emitting.
1294
1351
  */
1295
- #Wt() {
1352
+ #jt() {
1296
1353
  const t = this.#c;
1297
1354
  if (t === null) return;
1298
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;
1299
- let h;
1300
- if (this.#$ === "pointer") {
1301
- const l = this.timeScale.xToTime(t.mediaX), o = this.yScale.yToValue(t.mediaY);
1302
- if (!Number.isFinite(l) || !Number.isFinite(o) || Math.abs(l - t.time) <= r && Math.abs(o - t.y) <= n) return;
1303
- h = { mediaX: t.mediaX, mediaY: t.mediaY, time: l, y: o };
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 };
1304
1361
  } else {
1305
1362
  if (t.time < i || t.time > e) {
1306
- this.#c = null, this.#h.markDirty(), this.emit("crosshairMove", null), this.#J(null);
1363
+ this.#c = null, this.#o.markDirty(), this.emit("crosshairMove", null), this.#rt(null);
1307
1364
  return;
1308
1365
  }
1309
- const l = this.timeScale.timeToX(t.time), o = this.yScale.valueToY(t.y);
1310
- if (!Number.isFinite(l) || !Number.isFinite(o) || Math.abs(l - t.mediaX) <= a && Math.abs(o - t.mediaY) <= a) return;
1311
- h = { mediaX: l, mediaY: o, time: t.time, y: t.y };
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 };
1312
1369
  }
1313
- this.#c = h, this.#h.markDirty(), this.emit("crosshairMove", h), this.#J(h);
1370
+ this.#c = o, this.#o.markDirty(), this.emit("crosshairMove", o), this.#rt(o);
1314
1371
  }
1315
1372
  /** Tear down the chart: cancel animations, remove listeners, and detach the canvas. */
1316
1373
  destroy() {
1317
1374
  for (const t of this.#t) t.renderer.dispose();
1318
- this.#t = [], this.#u = [], this.#x = [], this.#y = [], this.#D = null, this.#i.destroy(), this.#h.destroy(), this.#M?.destroy(), this.#a.destroy(), this.#K?.destroy(), this.#K = null, this.#U && this.#S?.destroy(), this.#S = null, this.#U = !1, this.removeAllListeners();
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();
1319
1376
  }
1320
1377
  /** The attached performance monitor, or `null` when instrumentation is disabled. */
1321
1378
  getPerfMonitor() {
1322
- return this.#S;
1379
+ return this.#k;
1323
1380
  }
1324
1381
  /** Compute the earliest and latest timestamps across all series. */
1325
1382
  getDataBounds() {
1326
1383
  let t, i;
1327
1384
  for (const e of this.#t) {
1328
- if (!e.visible || !k(e.renderer)) continue;
1385
+ if (!e.visible || !b(e.renderer)) continue;
1329
1386
  const s = e.renderer.getTimeBounds();
1330
1387
  s !== null && ((t === void 0 || s.first < t) && (t = s.first), (i === void 0 || s.last > i) && (i = s.last));
1331
1388
  }
@@ -1334,28 +1391,28 @@ class ci extends ft {
1334
1391
  /** Set by `setSeriesData`/bulk-replace paths so the next `onDataChanged`
1335
1392
  * snaps the Y range (instead of easing) and `yScale.getRange()` reflects
1336
1393
  * the new domain synchronously. Cleared by `onDataChanged` after each run. */
1337
- #Q = !1;
1394
+ #nt = !1;
1338
1395
  onDataChanged() {
1339
1396
  if (this.#L > 0) {
1340
- this.#_ = !0;
1397
+ this.#q = !0;
1341
1398
  return;
1342
1399
  }
1343
1400
  this.updateDataInterval();
1344
1401
  const { first: t, last: i } = this.getDataBounds();
1345
- t !== void 0 && (this.#A = t), i !== void 0 && (this.#O = this.#e, this.#e = i);
1346
- const e = this.#Q;
1347
- this.#Q = !1;
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;
1348
1405
  const s = !this.#g && t !== void 0;
1349
- e && (this.yScale.tickTracker.reset(), this.timeScale.tickTracker.reset()), this.#mt = e;
1406
+ e && (this.yScale.tickTracker.reset(), this.timeScale.tickTracker.reset()), this.#xt = e;
1350
1407
  const r = performance.now();
1351
- e || s ? (this.#R.seed(r), this.#s.onDataReplaced(r)) : this.#s.onPointAppended(r), this.#s.lastXTarget !== null && (this.#V = !0), t !== void 0 && (this.#g = !0), this.#v(r), this.syncScales(), s ? this.#st() : this.#i.markDirty(), this.emit("dataUpdate"), this.#p();
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();
1352
1409
  }
1353
1410
  updateDataInterval() {
1354
1411
  for (const t of this.#t) {
1355
- if (!k(t.renderer)) continue;
1412
+ if (!b(t.renderer)) continue;
1356
1413
  const i = t.renderer.sampleTimes(20);
1357
1414
  if (i.length >= 2) {
1358
- this.#r = wt(i);
1415
+ this.#r = Ht(i);
1359
1416
  break;
1360
1417
  }
1361
1418
  }
@@ -1371,36 +1428,52 @@ class ci extends ft {
1371
1428
  * range — without this guard the engine would ease from `{0,0}` into
1372
1429
  * the new target and the very first frame would render with a
1373
1430
  * zero-width X domain. */
1374
- #V = !1;
1431
+ #W = !1;
1375
1432
  /**
1376
1433
  * One-shot `setVisibleRange` argument applied on the first data arrival.
1377
1434
  * Captured at chart construction from {@link ChartOptions.viewport.initialRange};
1378
1435
  * cleared the moment it's consumed so subsequent data swaps don't snap
1379
1436
  * back to the initial window.
1380
1437
  */
1381
- #Y;
1438
+ #_;
1382
1439
  /** Last Y bounds the engine resolved into {@link AnimationState.yRange}.
1383
1440
  * Used by {@link renderMain} to detect Y movement frame-to-frame so
1384
1441
  * `viewportChange` only emits when the Y range actually shifted — DOM
1385
1442
  * axis labels subscribe to that event and re-renders on every tick are
1386
1443
  * expensive. Initialised to NaN so the first real Y emit always passes
1387
1444
  * the change check. */
1388
- #tt = Number.NaN;
1389
- #it = Number.NaN;
1390
- /**
1391
- * Sample the visible data window and return the resolved Y bounds, or
1392
- * `null` when no series has data inside the X destination range.
1445
+ #at = Number.NaN;
1446
+ #ot = Number.NaN;
1447
+ /** Dev warning for values the Y scale can't place — fires once per chart. */
1448
+ #Rt = !1;
1449
+ /**
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.
1393
1453
  * Sampling runs against `logicalRange` (the X target, not the animating
1394
1454
  * current), so Y stays on a stable target while X eases — both dimensions
1395
1455
  * converge together. Clears {@link #yInited} on empty so the next emit
1396
1456
  * with valid data lands as an instant snap.
1397
1457
  */
1398
- #St(t) {
1399
- const i = t ?? this.#o, e = this.#d.min !== void 0 && this.#d.min !== "auto" && typeof this.#d.min != "number" || this.#d.max !== void 0 && this.#d.max !== "auto" && typeof this.#d.max != "number" ? [] : null, s = ht(i, this.#t, e);
1400
- if (s === null)
1401
- return this.#g = !1, null;
1402
- const r = F(this.#d.min, s.min, s.max, e ?? [], "min"), n = F(this.#d.max, s.max, s.min, e ?? [], "max");
1403
- return { min: r, max: n };
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 });
1463
+ }
1464
+ /** 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
+ }
1476
+ }
1404
1477
  }
1405
1478
  /**
1406
1479
  * Engine pull-on-demand X target. Called by the engine's `computeXTarget`
@@ -1420,29 +1493,29 @@ class ci extends ft {
1420
1493
  * - **autoscroll off** → `null` (no X retarget; user has panned away
1421
1494
  * from the tail).
1422
1495
  */
1423
- #Ft() {
1424
- if (this.#A === null || this.#e === null) return null;
1425
- const { from: t, to: i } = this.#o, e = t === 0 && i === 0, s = this.#a.size.media.width - this.yAxisWidth;
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;
1426
1499
  if (e) {
1427
- if (this.#W(this.#A, this.#e, s), this.#Y !== void 0 && (this.setVisibleRange(this.#Y), this.#Y = void 0, !this.#f && this.#e !== null)) {
1428
- const { to: n } = this.#o;
1429
- this.#e >= n && this.#e - n <= this.#r * 1.5 && (this.#f = !0);
1500
+ if (this.#O(this.#T, this.#e, s), this.#_ !== void 0 && (this.setVisibleRange(this.#_), this.#_ = void 0, !this.#m && this.#e !== null)) {
1501
+ const { to: n } = this.#h;
1502
+ this.#e >= n && this.#e - n <= this.#r * 1.5 && (this.#m = !0);
1430
1503
  }
1431
- return this.#o;
1504
+ return this.#h;
1432
1505
  }
1433
- if (!this.#f) return null;
1434
- if (this.#mt)
1435
- return this.#W(this.#A, this.#e, s), this.#o;
1436
- const r = at({
1437
- currentLogical: this.#o,
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({
1510
+ currentLogical: this.#h,
1438
1511
  lastTime: this.#e,
1439
- prevDataEnd: this.#O,
1512
+ prevDataEnd: this.#E,
1440
1513
  dataInterval: this.#r,
1441
1514
  paddingRight: this.#l.right,
1442
1515
  chartWidth: s,
1443
- holdUntilFilled: this.#T
1516
+ holdUntilFilled: this.#M
1444
1517
  });
1445
- return r.releaseHold && (this.#T = !1), r.reengageAutoScroll && (this.#f = !0), r.newLogical === null ? null : (this.#R.observe(performance.now()), this.#s.setXSettleMs(this.#R.pickSettleMs(this.#m.axis.x.settleMs)), this.#C(r.newLogical, { emitChange: !1, skipValidation: !0 }), this.#O = this.#e, r.newLogical);
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);
1446
1519
  }
1447
1520
  /**
1448
1521
  * Fit the viewport's logical X range around a data span. Mirrors the
@@ -1450,16 +1523,16 @@ class ci extends ft {
1450
1523
  * forces autoscroll on (data tail is in the new window by construction),
1451
1524
  * commits the new range via `viewport.setRange` (which emits `change`).
1452
1525
  */
1453
- #W(t, i, e) {
1454
- const s = this.#l, r = q({
1526
+ #O(t, i, e) {
1527
+ const s = this.#l, r = rt({
1455
1528
  firstTime: t,
1456
1529
  lastTime: i,
1457
1530
  dataInterval: this.#r,
1458
- maxVisibleBars: this.#ut,
1531
+ maxVisibleBars: this.#St,
1459
1532
  chartWidth: e,
1460
1533
  padding: { left: s.left, right: s.right }
1461
1534
  });
1462
- this.#T = !1, this.#f = !0, this.#C(r, { emitChange: !0 });
1535
+ this.#M = !1, this.#m = !0, this.#C(r, { emitChange: !0 });
1463
1536
  }
1464
1537
  /**
1465
1538
  * Commit a programmatic zoom — `fitContent` or `setVisibleRange`. Reads
@@ -1475,15 +1548,15 @@ class ci extends ft {
1475
1548
  * passes `xEase: true` so X eases instead — viewport and axis converge
1476
1549
  * on the same frame.
1477
1550
  */
1478
- #X(t) {
1479
- const i = this.#o;
1551
+ #G(t) {
1552
+ const i = this.#h;
1480
1553
  if (i.to <= i.from) return;
1481
1554
  const e = performance.now();
1482
- if (!this.#V || !this.#g) {
1483
- this.#kt(e);
1555
+ if (!this.#W || !this.#g) {
1556
+ this.#Ct(e);
1484
1557
  return;
1485
1558
  }
1486
- this.#s.onProgrammaticZoom({ xTarget: i, xEase: t?.xEase }, e), this.#v(e);
1559
+ this.#s.onProgrammaticZoom({ xTarget: i, xEase: t?.xEase }, e), this.#b(e);
1487
1560
  }
1488
1561
  /**
1489
1562
  * Commit the current logical X to the engine on the *gesture* profile:
@@ -1497,15 +1570,15 @@ class ci extends ft {
1497
1570
  * instead of lagging on the long sticky contract. First paint still snaps
1498
1571
  * (same as the programmatic path) so synchronous readers see final values.
1499
1572
  */
1500
- #Bt() {
1501
- const t = this.#o;
1573
+ #Qt() {
1574
+ const t = this.#h;
1502
1575
  if (t.to <= t.from) return;
1503
1576
  const i = performance.now();
1504
- if (!this.#V || !this.#g) {
1505
- this.#kt(i);
1577
+ if (!this.#W || !this.#g) {
1578
+ this.#Ct(i);
1506
1579
  return;
1507
1580
  }
1508
- this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#v(i);
1581
+ this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#b(i);
1509
1582
  }
1510
1583
  /**
1511
1584
  * First-paint snap: land both axes on the current logical X immediately
@@ -1514,9 +1587,9 @@ class ci extends ft {
1514
1587
  * commits — both must snap before the chart has painted once so a
1515
1588
  * synchronous `getYRange()` / `getVisibleRange()` read sees final values.
1516
1589
  */
1517
- #kt(t) {
1518
- const i = this.#o, e = this.#St(i);
1519
- this.#V = !0, e !== null && (this.#g = !0), this.#s.snap({ x: i, y: e ?? void 0 }, t), this.#v(t);
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);
1520
1593
  }
1521
1594
  /**
1522
1595
  * Validate and commit a new logical X range. Mirrors the old
@@ -1538,28 +1611,37 @@ class ci extends ft {
1538
1611
  */
1539
1612
  #C(t, i) {
1540
1613
  const { from: e, to: s } = t;
1541
- return !Number.isFinite(e) || !Number.isFinite(s) || s <= e || !i.skipValidation && (s - e) / this.#r < 2 || this.#o.from === e && this.#o.to === s ? !1 : (this.#o = { from: e, to: s }, i.emitChange && (this.syncScales(), this.#i?.markDirty(), this.emit("viewportChange")), !0);
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);
1542
1615
  }
1543
1616
  /**
1544
- * Apply pixel-padding to the engine's raw Y target and store the
1545
- * result in `#yRange`. Mirrors the old `Viewport.setYRange` contract:
1617
+ * Apply pixel-padding to the engine's Y (scale space) and return it as
1618
+ * values for `#yRange`. Mirrors the old `Viewport.setYRange` contract:
1546
1619
  * symmetric pad top / bottom from `#padding`, suppressed on the side
1547
- * that has an explicit (fixed) axis bound.
1620
+ * that has an explicit (fixed) axis bound. Padding in scale space keeps
1621
+ * the pixel gutters even on a log axis.
1548
1622
  */
1549
- #wt(t) {
1550
- const { min: i, max: e, chartHeight: s } = t, { min: r, max: n } = this.#Nt(), a = e - i, h = s > 0 ? this.#l.top / s * a : 0, l = s > 0 ? this.#l.bottom / s * a : 0;
1551
- return {
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 = {
1552
1625
  min: r ? i : i - l,
1553
- max: n ? e : e + h
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);
1630
+ return {
1631
+ min: f ? m : d.inverse(i),
1632
+ max: g ? c : d.inverse(e)
1554
1633
  };
1555
1634
  }
1556
1635
  /** Which Y edges the user pinned — a pinned edge takes no padding. */
1557
- #Nt() {
1636
+ #ti() {
1558
1637
  return {
1559
- min: this.#d.min !== void 0 && this.#d.min !== "auto",
1560
- max: this.#d.max !== void 0 && this.#d.max !== "auto"
1638
+ min: this.#Ht(this.#p.min),
1639
+ max: this.#Ht(this.#p.max)
1561
1640
  };
1562
1641
  }
1642
+ #Ht(t) {
1643
+ return t === void 0 || t === "auto" ? !1 : !(typeof t == "number" && !this.yScale.isPlottable(t));
1644
+ }
1563
1645
  /**
1564
1646
  * Shift the visible time range by `timeDelta` ms. Hard-clamped so at
1565
1647
  * least one data point stays on screen — no rubber-band, no overshoot.
@@ -1570,18 +1652,18 @@ class ci extends ft {
1570
1652
  * as a gesture for visual easing. Public so consumers can drive pan
1571
1653
  * programmatically.
1572
1654
  */
1573
- pan(t, i = this.#E) {
1574
- i > 0 && (this.#E = i), this.#T = !1;
1575
- const e = st({
1576
- currentLogical: this.#o,
1655
+ pan(t, i = this.#$) {
1656
+ i > 0 && (this.#$ = i), this.#M = !1;
1657
+ const e = ct({
1658
+ currentLogical: this.#h,
1577
1659
  timeDelta: t,
1578
1660
  chartWidth: i,
1579
1661
  dataInterval: this.#r,
1580
1662
  padding: { left: this.#l.left, right: this.#l.right },
1581
- dataStart: this.#A,
1663
+ dataStart: this.#T,
1582
1664
  dataEnd: this.#e
1583
1665
  });
1584
- e.newLogical !== null && (this.#f = !e.autoScrollOff, this.#C(e.newLogical, { emitChange: !0 }), e.edgeReached !== null && (this.#ot[e.edgeReached.side] = e.edgeReached.boundaryTime, this.#lt?.(e.edgeReached)), this.#R.pause(), this.#At());
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());
1585
1667
  }
1586
1668
  /**
1587
1669
  * Zoom around a time anchor. `factor < 1` zooms in, `> 1` zooms out.
@@ -1598,37 +1680,37 @@ class ci extends ft {
1598
1680
  * and produce a visible jump). Cursor-anchored zoom is intentionally
1599
1681
  * sacrificed in follow-live mode — pan first to inspect history.
1600
1682
  */
1601
- zoomAt(t, i, e = this.#E) {
1602
- e > 0 && (this.#E = e), this.#T = !1;
1603
- const s = rt({
1604
- currentLogical: this.#o,
1683
+ zoomAt(t, i, e = this.#$) {
1684
+ e > 0 && (this.#$ = e), this.#M = !1;
1685
+ const s = dt({
1686
+ currentLogical: this.#h,
1605
1687
  centerTime: t,
1606
1688
  factor: i,
1607
1689
  chartWidth: e,
1608
1690
  dataInterval: this.#r,
1609
1691
  padding: { left: this.#l.left, right: this.#l.right },
1610
- dataStart: this.#A,
1692
+ dataStart: this.#T,
1611
1693
  dataEnd: this.#e
1612
1694
  });
1613
1695
  if (s.newLogical === null) return;
1614
1696
  let r = s.newLogical;
1615
- if (this.#f && this.#e !== null) {
1616
- const n = r.to - r.from, a = W(this.#l.right, n, this.#r, e), h = this.#e + a;
1617
- r = { from: h - n, to: h }, this.#O = this.#e;
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;
1618
1700
  }
1619
- this.#C(r, { emitChange: !0 }), this.#R.pause(), this.#At();
1701
+ this.#C(r, { emitChange: !0 }), this.#R.pause(), this.#zt();
1620
1702
  }
1621
1703
  /**
1622
1704
  * Common tail for `pan` / `zoomAt`: routes the just-committed `#logical`
1623
1705
  * to the engine as a gesture so the X spring retargets and the Y spring
1624
1706
  * eases over `animations.axis.y.gesture`.
1625
1707
  */
1626
- #At() {
1627
- const t = this.#o;
1708
+ #zt() {
1709
+ const t = this.#h;
1628
1710
  if (t.to <= t.from) return;
1629
- this.#V = !0, this.#g = !0;
1711
+ this.#W = !0, this.#g = !0;
1630
1712
  const i = performance.now();
1631
- this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#v(i);
1713
+ this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#b(i);
1632
1714
  }
1633
1715
  /**
1634
1716
  * Tick the engine at the given wall clock and push the resolved X / Y
@@ -1643,11 +1725,11 @@ class ci extends ft {
1643
1725
  * `effectiveNow === startWall` and the prune step keeps zero-duration
1644
1726
  * events long enough for the slot processor to snap them.
1645
1727
  */
1646
- #v(t) {
1728
+ #b(t) {
1647
1729
  const i = this.#s.tick(t), e = this.#a.size;
1648
1730
  if (e.media.width === 0 || e.media.height === 0) return;
1649
1731
  const s = e.media.height - this.xAxisHeight;
1650
- this.#z = this.#wt({ min: i.yRange.min, max: i.yRange.max, chartHeight: s }), this.#tt = i.yRange.min, this.#it = i.yRange.max, this.syncScales();
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();
1651
1733
  }
1652
1734
  /**
1653
1735
  * Lightweight scale sync: updates timeScale/yScale from current viewport state
@@ -1663,7 +1745,7 @@ class ci extends ft {
1663
1745
  i,
1664
1746
  t.horizontalPixelRatio,
1665
1747
  this.#r
1666
- ), this.yScale.update(this.#z, e, t.verticalPixelRatio);
1748
+ ), this.yScale.update(this.#B, e, t.verticalPixelRatio);
1667
1749
  }
1668
1750
  /** Expensive: background, grid, all series. Only on data/viewport/resize change. */
1669
1751
  renderMain(t) {
@@ -1671,80 +1753,74 @@ class ci extends ft {
1671
1753
  if (i.media.width === 0 || i.media.height === 0) return;
1672
1754
  const e = typeof t == "number" ? t : performance.now(), s = this.#s.tick(e);
1673
1755
  s.animating && this.#i.markDirty();
1674
- const r = s.yRange.min, n = s.yRange.max, a = r !== this.#tt || n !== this.#it;
1756
+ const r = s.yRange.min, n = s.yRange.max, a = r !== this.#at || n !== this.#ot;
1675
1757
  if (a) {
1676
1758
  const c = i.media.height - this.xAxisHeight;
1677
- this.#z = this.#wt({ min: r, max: n, chartHeight: c }), this.#tt = r, this.#it = n;
1759
+ this.#B = this.#It({ min: r, max: n, chartHeight: c }), this.#at = r, this.#ot = n;
1678
1760
  }
1679
- if (this.syncScales(), this.#Wt(), this.#e !== null && !this.#f) {
1680
- const c = this.#s.lastXTarget ?? this.#o, f = c.to - c.from;
1761
+ if (this.syncScales(), this.#jt(), this.#e !== null && !this.#m) {
1762
+ const c = this.#s.lastXTarget ?? this.#h, f = c.to - c.from;
1681
1763
  if (f > 0) {
1682
- const y = this.#a.size.media.width - this.yAxisWidth, d = W(this.#l.right, f, this.#r, y), g = this.#r * 0.5;
1683
- Math.abs(c.to - (this.#e + d)) < g && (this.#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);
1684
1766
  }
1685
1767
  }
1686
1768
  a && this.emit("viewportChange");
1687
- let h = !1, l = !1, o = !1;
1769
+ let o = !1, l = !1, h = !1;
1688
1770
  this.#a.useMainLayer((c) => {
1689
- const { context: f, bitmapSize: y } = c, d = (i.media.width - this.yAxisWidth) * i.horizontalPixelRatio, g = (i.media.height - this.xAxisHeight) * i.verticalPixelRatio;
1690
- f.clearRect(0, 0, y.width, y.height), f.save(), f.beginPath();
1691
- const b = this.#et(c, d);
1692
- f.rect(0, 0, d + b.intrusion, g), 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);
1693
- const w = this.yScale.tickTracker.snapshot(), S = this.timeScale.tickTracker.snapshot();
1694
- h = w.isAnimating, l = S.isAnimating, (w.isAnimating || S.isAnimating || s.animating) && this.emit("tickFrame");
1695
- const L = this.#F ? 1 : 0;
1696
- if ((L === 0 || w.entries.length > 0 || S.entries.length > 0) && L !== this.#I.target) {
1697
- const v = Z() ? 0 : this.#m.axis.gridMs;
1698
- this.#I.setTarget(L, { now: e, duration: v });
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 });
1699
1779
  }
1700
- o = this.#I.tick(e);
1701
- const M = this.#I.current;
1702
- lt({
1780
+ h = this.#H.tick(e);
1781
+ const P = this.#H.current, R = {
1703
1782
  scope: c,
1704
1783
  timeScale: this.timeScale,
1705
1784
  yScale: this.yScale,
1706
1785
  theme: this.#n,
1707
- yTicks: w,
1708
- timeTicks: S,
1709
- alpha: M
1710
- }), this.#Pt(c, d, g);
1711
- const D = this.#l, O = { top: D.top, bottom: D.bottom }, R = this.#S;
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;
1712
1792
  for (const v of this.#t) {
1713
- const A = v.renderer.getAlpha?.() ?? (v.visible ? 1 : 0);
1714
- if (A <= 0) continue;
1715
- const C = {
1793
+ const T = v.renderer.getAlpha?.() ?? (v.visible ? 1 : 0);
1794
+ if (T <= 0) continue;
1795
+ const z = {
1716
1796
  scope: c,
1717
1797
  timeScale: this.timeScale,
1718
1798
  yScale: this.yScale,
1719
1799
  theme: this.#n,
1720
1800
  dataInterval: this.#r,
1721
- padding: O,
1722
- state: s,
1801
+ padding: _,
1802
+ state: G,
1723
1803
  seriesId: v.id
1724
- }, I = f.globalAlpha;
1725
- if (A < 1 && (f.globalAlpha = I * A), R) {
1726
- const E = performance.now();
1727
- v.renderer.render(C), R.recordSeries(v.id, performance.now() - E, e);
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);
1728
1808
  } else
1729
- v.renderer.render(C);
1730
- A < 1 && (f.globalAlpha = I);
1809
+ v.renderer.render(z);
1810
+ T < 1 && (f.globalAlpha = V);
1731
1811
  }
1732
- f.restore(), this.#Ot({
1733
- scope: c,
1734
- paneBitmapWidth: d,
1735
- paneBitmapHeight: g,
1736
- timeTicks: S,
1737
- alpha: M
1738
- }), this.#Lt(c, g), this.#Mt(c, d);
1739
- }), this.#j();
1740
- let m = !1;
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();
1816
+ let d = !1;
1741
1817
  for (const c of this.#t)
1742
1818
  if (c.renderer.needsAnimation) {
1743
- m = !0, this.#i.markDirty();
1819
+ d = !0, this.#i.markDirty();
1744
1820
  break;
1745
1821
  }
1746
- const u = h || l;
1747
- (u || o) && this.#i.markDirty(), !s.animating && !m && !u && (this.yScale.tickTracker.markArmed(), this.timeScale.tickTracker.markArmed());
1822
+ const m = o || l;
1823
+ (m || h) && this.#i.markDirty(), !s.animating && !d && !m && (this.yScale.tickTracker.markArmed(), this.timeScale.tickTracker.markArmed());
1748
1824
  }
1749
1825
  /**
1750
1826
  * Erase the main layer's alpha across the top fade zone so content
@@ -1753,12 +1829,12 @@ class ci extends ft {
1753
1829
  * a paint-over — so the container background behind the canvas (solid or
1754
1830
  * CSS gradient) shows through untouched.
1755
1831
  */
1756
- #Lt(t, i) {
1757
- if (this.#k.top <= 0) return;
1758
- const e = Math.min(Math.round(this.#k.top * t.verticalPixelRatio), Math.round(i));
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));
1759
1835
  if (e < 1) return;
1760
1836
  const { context: s } = t;
1761
- s.save(), s.globalCompositeOperation = "destination-out", s.fillStyle = this.#Yt(s, e), s.fillRect(0, 0, t.bitmapSize.width, e), s.restore();
1837
+ s.save(), s.globalCompositeOperation = "destination-out", s.fillStyle = this.#ni(s, e), s.fillRect(0, 0, t.bitmapSize.width, e), s.restore();
1762
1838
  }
1763
1839
  /**
1764
1840
  * Run the vertical gridlines a short stub past the pane floor, dissolving
@@ -1768,21 +1844,23 @@ class ci extends ft {
1768
1844
  * nothing else paints) and faded with the same destination-out ramp the
1769
1845
  * edge masks use, so the taper stays correct over any background.
1770
1846
  */
1771
- #Ot(t) {
1772
- const { scope: i, paneBitmapWidth: e, paneBitmapHeight: s, timeTicks: r, alpha: n } = t;
1773
- if (!this.#F && n <= 0.01 || this.xAxisHeight === 0) return;
1774
- const { context: a, horizontalPixelRatio: h, verticalPixelRatio: l } = i, o = Math.round(s), m = Math.round(T * l);
1775
- if (m < 1) return;
1776
- const u = this.#n.grid;
1777
- a.save(), a.strokeStyle = u.color, u.style === "dashed" ? a.setLineDash([4 * h, 4 * h]) : u.style === "dotted" && a.setLineDash([1 * h, 3 * h]), a.lineDashOffset = o, a.lineWidth = St(h);
1778
- const c = kt(h), f = Math.round(e);
1779
- for (const { value: y, opacity: d } of r.entries) {
1780
- const g = d * n;
1781
- if (g <= 0.01) continue;
1782
- const b = Math.round(this.timeScale.timeToBitmapX(y)) + c;
1783
- b < 0 || b > f || (a.globalAlpha = g, a.beginPath(), a.moveTo(b, o), a.lineTo(b, o + m), a.stroke());
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;
1852
+ 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;
1857
+ 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();
1784
1862
  }
1785
- a.restore(), this.#Tt(i, s);
1863
+ n.restore(), this.#Ot(i, e.height);
1786
1864
  }
1787
1865
  /**
1788
1866
  * Erase ramp over the below-pane overrun strip — the gridline tail stubs
@@ -1790,16 +1868,16 @@ class ci extends ft {
1790
1868
  * erased at the pane floor, total at the strip's bottom edge, so whatever
1791
1869
  * crosses the floor tapers out short of the time-axis labels.
1792
1870
  */
1793
- #Tt(t, i) {
1871
+ #Ot(t, i) {
1794
1872
  if (this.xAxisHeight === 0) return;
1795
- const e = Math.round(i), s = Math.round(T * t.verticalPixelRatio);
1873
+ const e = Math.round(i), s = Math.round(D * t.verticalPixelRatio);
1796
1874
  if (s < 1) return;
1797
1875
  const { context: r } = t;
1798
- r.save(), r.globalCompositeOperation = "destination-out", r.fillStyle = this.#$t({ context: r, start: e, height: s }), r.fillRect(0, e, t.bitmapSize.width, s), r.restore();
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();
1799
1877
  }
1800
1878
  /**
1801
1879
  * Crosshair hairlines with the same edge melt the main layer runs: the
1802
- * pane clip widens by the right-fade intrusion and by a gridline-tail's
1880
+ * pane clip widens by the axis-side fade intrusion and by a gridline-tail's
1803
1881
  * overrun below the floor, then the shared erase ramps dissolve the
1804
1882
  * overhangs — the vertical line tapers toward the time-axis labels in
1805
1883
  * step with the gridline stubs and the horizontal line rides under the
@@ -1807,104 +1885,177 @@ class ci extends ft {
1807
1885
  * edge. Erases alpha (destination-out), so it must run while the
1808
1886
  * crosshair is the only content on the layer.
1809
1887
  */
1810
- #Et(t) {
1888
+ #ii(t) {
1811
1889
  const i = this.#c;
1812
1890
  if (i === null) return;
1813
- const { scope: e, chartBitmapWidth: s, chartBitmapHeight: r } = t, { context: n } = e, a = this.#et(e, s), h = this.xAxisHeight > 0 ? Math.round(T * e.verticalPixelRatio) : 0;
1814
- n.save(), n.beginPath(), n.rect(0, 0, s + a.intrusion, r + h), n.clip(), ot({
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({
1815
1893
  scope: e,
1816
- bitmapX: i.mediaX * e.horizontalPixelRatio,
1894
+ bitmapX: i.mediaX * e.horizontalPixelRatio - a,
1817
1895
  bitmapY: i.mediaY * e.verticalPixelRatio,
1818
1896
  theme: this.#n,
1819
- pane: { width: s, height: r }
1820
- }), n.restore(), this.#Tt(e, r), this.#Lt(e, r), this.#Mt(e, s);
1897
+ 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);
1821
1900
  }
1822
1901
  /**
1823
- * Horizontal companions to {@link #applyTopFade}: dissolve content that
1824
- * rides under the Y-axis column on the right (the pane clip is widened by
1825
- * the same zone) and, when configured, content leaving through the left
1826
- * pane edge. Same destination-out erase — background-safe over gradients.
1902
+ * Horizontal companion to {@link #applyTopFade}: dissolve content that
1903
+ * rides under the Y-axis column (the pane clip is widened by the same
1904
+ * zone). Same destination-out erase — background-safe over gradients.
1827
1905
  * Runs the full bitmap height so the below-pane gridline tail stubs melt
1828
1906
  * at the edges in step with their lines above.
1829
1907
  */
1830
- #Mt(t, i) {
1831
- const { context: e } = t, s = t.bitmapSize.height, r = this.#et(t, i);
1832
- r.width >= 1 && (e.save(), e.globalCompositeOperation = "destination-out", e.fillStyle = this.#Xt({ context: e, start: r.start, width: r.width }), e.fillRect(r.start, 0, r.width, s), e.restore());
1833
- const n = Math.min(Math.round(this.#k.left * t.horizontalPixelRatio), Math.round(i));
1834
- n >= 1 && (e.save(), e.globalCompositeOperation = "destination-out", e.fillStyle = this.#Zt(e, n), e.fillRect(0, 0, n, s), e.restore());
1835
- }
1836
- /**
1837
- * Bitmap geometry of the right fade zone. The ramp finishes just inside
1838
- * the Y-axis column — {@link RIGHT_FADE_END_GAP_PX} past the pane edge,
1839
- * still short of where the right-anchored label glyphs start — so content
1840
- * is fully erased before it can cross any axis text. Everything wider
1841
- * spills backward into the pane as the soft lead-in. `width: 0` (axis
1842
- * hidden, or `right: 0`) means no zone; `intrusion` is what the pane clip
1843
- * extends by (content past the ramp is total-erased anyway, so the clip
1844
- * stops with the ramp).
1845
- */
1846
- #et(t, i) {
1847
- const e = { start: 0, width: 0, intrusion: 0 }, s = Math.max(0, Math.round(t.bitmapSize.width - i));
1848
- if (s === 0) return e;
1849
- const r = t.horizontalPixelRatio, n = Math.min(Math.round(Mt * r), s), a = this.#k.right === null ? n + Math.round(Tt * r) : Math.round(this.#k.right * r);
1850
- if (a < 1) return e;
1851
- const h = i + n, l = Math.min(a, h);
1852
- return { start: h - l, width: l, intrusion: n };
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();
1913
+ }
1914
+ /** Dissolve content leaving through a plain canvas edge — the side opposite
1915
+ * 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 });
1920
+ if (r === null) continue;
1921
+ 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();
1923
+ }
1924
+ }
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;
1928
+ }
1929
+ /** 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));
1933
+ }
1934
+ /** Left-axis grid pass, run after the axis fade: `destination-over` keeps the
1935
+ * 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);
1939
+ }
1940
+ /** 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();
1944
+ }
1945
+ /** Canvas bitmap x of the pane's left edge — whole device pixels, so the
1946
+ * translated pane keeps crisp strokes. */
1947
+ #I(t) {
1948
+ return Math.round(this.plotLeft * t.horizontalPixelRatio);
1949
+ }
1950
+ /** Pane-local width of the pane clip: the pane plus the axis-side fade
1951
+ * intrusion, so content can slide under a right Y-axis column before the
1952
+ * mask dissolves it. */
1953
+ #Bt(t, i) {
1954
+ const e = this.#ct(t, i)?.intrusion ?? 0;
1955
+ return i + e;
1956
+ }
1957
+ /**
1958
+ * Bitmap geometry of the fade zone on the Y-axis side. On the right the
1959
+ * ramp finishes just inside the column — {@link AXIS_FADE_END_GAP_PX} past
1960
+ * the pane edge, still short of where the label glyphs start — so content
1961
+ * is fully erased before it can cross any axis text; everything wider
1962
+ * spills back into the pane as the soft lead-in. On the left it finishes
1963
+ * at the pane edge. Erasing starts at `inner` and is total at `outer`;
1964
+ * `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
1966
+ * is `0`, or the chart is spatial-only.
1967
+ */
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);
1973
+ 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 };
1977
+ }
1978
+ const c = i + l, f = Math.min(m, c);
1979
+ return { inner: c - f, outer: c, intrusion: l };
1980
+ }
1981
+ /** Bitmap geometry of a plain fade at the `side` canvas edge — total erase
1982
+ * 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];
1985
+ if (r === null) return null;
1986
+ const n = Math.min(Math.round(r * i.horizontalPixelRatio), Math.round(e));
1987
+ if (n < 1) return null;
1988
+ if (s === "right") {
1989
+ const a = i.bitmapSize.width;
1990
+ return { edge: a, inner: a - n };
1991
+ }
1992
+ return { edge: 0, inner: n };
1853
1993
  }
1854
1994
  /** Memoized mask gradient — rebuilt only when the bitmap zone height
1855
1995
  * changes (resize, DPR change, `setFade`). Ease-out ramp (`1 - t²`):
1856
1996
  * near-total erasure across the upper half keeps a floating header
1857
1997
  * legible, then a soft tail into full opacity at the zone's bottom. */
1858
- #Yt(t, i) {
1859
- const e = this.#w.top;
1998
+ #ni(t, i) {
1999
+ const e = this.#v.top;
1860
2000
  if (e !== null && e.height === i) return e.gradient;
1861
2001
  const s = t.createLinearGradient(0, 0, 0, i);
1862
2002
  for (let r = 0; r <= 4; r++) {
1863
2003
  const n = r / 4;
1864
2004
  s.addColorStop(n, `rgba(0, 0, 0, ${1 - n * n})`);
1865
2005
  }
1866
- return this.#w.top = { height: i, gradient: s }, s;
2006
+ return this.#v.top = { height: i, gradient: s }, s;
1867
2007
  }
1868
- /** Right-zone mask — erase grows from 0 at the ramp's inner start to
2008
+ /** Axis-column mask — erase grows from 0 at the ramp's inner start to
1869
2009
  * total at its outer end on an ease-in (`s³`): zero slope *and* zero
1870
2010
  * curvature at the entry, so the in-pane lead-in barely dims the newest
1871
2011
  * candles at rest and the aggressive erasing all happens deep under the
1872
2012
  * axis column. Nine stops keep the piecewise interpolation smooth across
1873
2013
  * the zone. */
1874
- #Xt(t) {
1875
- const i = this.#w.right;
1876
- if (i !== null && i.start === t.start && i.width === t.width) return i.gradient;
1877
- const e = t.context.createLinearGradient(t.start, 0, t.start + t.width, 0);
1878
- for (let s = 0; s <= 8; s++) {
1879
- const r = s / 8;
1880
- e.addColorStop(r, `rgba(0, 0, 0, ${r * r * r})`);
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})`);
1881
2021
  }
1882
- return this.#w.right = { start: t.start, width: t.width, gradient: e }, e;
2022
+ return this.#v.axis = { inner: e.inner, outer: e.outer, gradient: r }, r;
1883
2023
  }
1884
2024
  /** Erase ramp for the gridline tail stubs — nothing erased at the pane
1885
2025
  * floor, total at the stub tip, ease-in (`t²`) so a stub holds strength
1886
2026
  * briefly before melting away short of the label glyphs. */
1887
- #$t(t) {
1888
- const i = this.#w.tail;
2027
+ #oi(t) {
2028
+ const i = this.#v.tail;
1889
2029
  if (i !== null && i.start === t.start && i.height === t.height) return i.gradient;
1890
2030
  const e = t.context.createLinearGradient(0, t.start, 0, t.start + t.height);
1891
2031
  for (let s = 0; s <= 4; s++) {
1892
2032
  const r = s / 4;
1893
2033
  e.addColorStop(r, `rgba(0, 0, 0, ${r * r})`);
1894
2034
  }
1895
- return this.#w.tail = { start: t.start, height: t.height, gradient: e }, e;
1896
- }
1897
- /** Left-zone mask — mirror of the top ramp: total erase at the canvas
1898
- * edge, fully opaque at the zone's inner boundary. */
1899
- #Zt(t, i) {
1900
- const e = this.#w.left;
1901
- if (e !== null && e.width === i) return e.gradient;
1902
- const s = t.createLinearGradient(0, 0, i, 0);
1903
- for (let r = 0; r <= 4; r++) {
1904
- const n = r / 4;
1905
- s.addColorStop(n, `rgba(0, 0, 0, ${1 - n * n})`);
2035
+ return this.#v.tail = { start: t.start, height: t.height, gradient: e }, e;
2036
+ }
2037
+ /** 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);
2042
+ for (let s = 0; s <= 4; s++) {
2043
+ const r = s / 4;
2044
+ e.addColorStop(r, `rgba(0, 0, 0, ${r * r})`);
2045
+ }
2046
+ return this.#v.overhang = { start: t.start, end: t.end, gradient: e }, e;
2047
+ }
2048
+ /** Opposite-edge mask — mirror of the top ramp: total erase at the
2049
+ * 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];
2052
+ 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);
2054
+ for (let a = 0; a <= 4; a++) {
2055
+ const o = a / 4;
2056
+ n.addColorStop(o, `rgba(0, 0, 0, ${1 - o * o})`);
1906
2057
  }
1907
- return this.#w.left = { width: i, gradient: s }, s;
2058
+ return this.#v.edge[e] = { edge: s.edge, inner: s.inner, gradient: n }, n;
1908
2059
  }
1909
2060
  /**
1910
2061
  * Paint a full frame: the main layer, then the overlay synchronously on
@@ -1915,13 +2066,13 @@ class ci extends ft {
1915
2066
  * `markDirty` calls made *during* `renderOverlay` (pulse, edge spinner)
1916
2067
  * land after the consume and re-arm cleanly for the next frame.
1917
2068
  */
1918
- #st(t) {
1919
- this.#rt("main", t, () => this.renderMain(t)), this.#h.consume(), this.#rt("overlay", t, () => this.renderOverlay(t));
2069
+ #dt(t) {
2070
+ this.#ut("main", t, () => this.renderMain(t)), this.#o.consume(), this.#ut("overlay", t, () => this.renderOverlay(t));
1920
2071
  }
1921
2072
  /** Run a layer's paint, booking duration + draw calls to the perf monitor
1922
2073
  * when one is attached — keeps overlay time out of the `main` samples. */
1923
- #rt(t, i, e) {
1924
- const s = this.#S;
2074
+ #ut(t, i, e) {
2075
+ const s = this.#k;
1925
2076
  if (!s) {
1926
2077
  e();
1927
2078
  return;
@@ -1935,74 +2086,74 @@ class ci extends ft {
1935
2086
  const i = this.#a.size;
1936
2087
  if (i.media.width === 0 || i.media.height === 0) return;
1937
2088
  let e = !1;
1938
- for (const o of this.#t)
1939
- if (o.visible && o.renderer.overlayNeedsAnimation) {
2089
+ for (const h of this.#t)
2090
+ if (h.visible && h.renderer.overlayNeedsAnimation) {
1940
2091
  e = !0;
1941
2092
  break;
1942
2093
  }
1943
- this.#u.some((o) => o.pulse) && (e = !0);
2094
+ this.#u.some((h) => h.pulse) && (e = !0);
1944
2095
  const s = {
1945
- left: this.#P.left && this.#b.left === "loading" ? "idle" : this.#b.left,
1946
- right: this.#P.right && this.#b.right === "loading" ? "idle" : this.#b.right
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
1947
2098
  }, r = {
1948
- left: this.#xt("left"),
1949
- right: this.#xt("right")
1950
- }, n = r.left !== null || r.right !== null, a = s.left === "loading" || s.right === "loading" || n, h = s.left !== "idle" || s.right !== "idle" || n, l = !!this.#c || e || h || this.#u.length > 0 || this.#y.length > 0;
1951
- if (!(!l && !this.#ht)) {
1952
- if (this.#ht = l, this.#a.useOverlayLayer((o) => {
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) => {
1953
2104
  if (!l) return;
1954
- const m = (i.media.width - this.yAxisWidth) * i.horizontalPixelRatio, u = (i.media.height - this.xAxisHeight) * i.verticalPixelRatio;
1955
- this.#c && this.#vt() && this.#Et({ scope: o, chartBitmapWidth: m, chartBitmapHeight: u }), o.context.save(), o.context.beginPath(), o.context.rect(0, 0, m, u), o.context.clip();
1956
- const c = this.#l, f = { top: c.top, bottom: c.bottom }, y = this.#s.getAnimationState();
1957
- for (const d of this.#t)
1958
- d.visible && d.renderer.drawOverlay?.({
1959
- scope: o,
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,
1960
2111
  timeScale: this.timeScale,
1961
2112
  yScale: this.yScale,
1962
2113
  theme: this.#n,
1963
2114
  dataInterval: this.#r,
1964
2115
  padding: f,
1965
2116
  crosshair: this.#c,
1966
- state: y,
1967
- seriesId: d.id
2117
+ state: g,
2118
+ seriesId: u.id
1968
2119
  });
1969
- this.#Ht(o, m, u), this.#It(o, m, u), h && K({
1970
- scope: o,
2120
+ this.#Gt(h, d, m), this.#Zt(h, d, m), o && et({
2121
+ scope: h,
1971
2122
  chartMediaWidth: i.media.width - this.yAxisWidth,
1972
2123
  chartMediaHeight: i.media.height - this.xAxisHeight,
1973
2124
  timeScale: this.timeScale,
1974
2125
  theme: this.#n,
1975
2126
  edgeStates: s,
1976
2127
  edgeExitFades: r,
1977
- edgeIndicatorFns: this.#ct,
1978
- resolveBoundary: (d) => j(d, this.#ot[d], this.getDataBounds()),
1979
- resolveEdgeAnchor: (d, g) => this.#zt(d, g),
1980
- resolveEdgeBarSpacing: () => this.#Vt(),
1981
- reportPlaceholders: (d, g) => {
1982
- this.#Z[d] = { bars: g, at: performance.now() };
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() };
1983
2134
  }
1984
- }), o.context.restore();
2135
+ }), h.context.restore();
1985
2136
  }), e) {
1986
- const { from: o, to: m } = this.timeScale.getRange();
1987
- let u = !1;
2137
+ const { from: h, to: d } = this.timeScale.getRange();
2138
+ let m = !1;
1988
2139
  for (const c of this.#t)
1989
- if (!(!c.visible || !c.renderer.overlayNeedsAnimation) && (c.renderer.hasOverlayContentInRange?.(o, m) ?? !0)) {
1990
- u = !0;
2140
+ if (!(!c.visible || !c.renderer.overlayNeedsAnimation) && (c.renderer.hasOverlayContentInRange?.(h, d) ?? !0)) {
2141
+ m = !0;
1991
2142
  break;
1992
2143
  }
1993
- if (!u) {
2144
+ if (!m) {
1994
2145
  for (const c of this.#u)
1995
- if (c.pulse && c.time >= o && c.time <= m) {
1996
- u = !0;
2146
+ if (c.pulse && c.time >= h && c.time <= d) {
2147
+ m = !0;
1997
2148
  break;
1998
2149
  }
1999
2150
  }
2000
- u && this.#h.markDirty();
2151
+ m && this.#o.markDirty();
2001
2152
  }
2002
- a && this.#h.markDirty();
2153
+ a && this.#o.markDirty();
2003
2154
  }
2004
2155
  }
2005
2156
  }
2006
2157
  export {
2007
- ci as ChartInstance
2158
+ Ai as ChartInstance
2008
2159
  };