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