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