@wick-charts/vue 0.6.2 → 0.6.4

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