@wick-charts/vue 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/dist/BarSeries.vue.js +26 -20
- package/dist/CandlestickSeries.vue.js +30 -24
- package/dist/ChartContainer.vue.js +88 -65
- 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/y-target.js +13 -12
- package/dist/core/dist/chart.js +908 -626
- package/dist/core/dist/components/annotation-chip.js +28 -0
- 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 +67 -43
- package/dist/core/dist/perf/perf-hud.js +2 -2
- 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 +1411 -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 +134 -12
- 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,87 +1,124 @@
|
|
|
1
|
-
import { AnimationConfig as
|
|
1
|
+
import { AnimationConfig as F, DEFAULT_LINE_PULSE as E } from "./animation/config.js";
|
|
2
|
+
import { easeOutCubic as L } from "./animation/easing.js";
|
|
2
3
|
import { createViewportEngine as N } from "./animation/viewport-engine.js";
|
|
3
4
|
import { CanvasManager as W } from "./canvas-manager.js";
|
|
4
|
-
import { drawEdgeIndicators as
|
|
5
|
-
import { computeFitToData as
|
|
6
|
-
import { getLastValue as
|
|
7
|
-
import { resolveMaxVisibleBars as
|
|
8
|
-
import { computePan as
|
|
9
|
-
import { StreamingCadence as
|
|
10
|
-
import { computeStreamingTarget as
|
|
11
|
-
import { resolvePaddingTime as
|
|
12
|
-
import { computeTargetYRange as
|
|
13
|
-
import { renderCrosshair as
|
|
14
|
-
import { renderGrid as
|
|
15
|
-
import { renderMarker as
|
|
16
|
-
import { renderReferenceLine as
|
|
17
|
-
import { resolveTimeRegionColors as
|
|
18
|
-
import { EventEmitter as
|
|
19
|
-
import { InteractionHandler as
|
|
20
|
-
import { RenderScheduler as
|
|
21
|
-
import { XScale as
|
|
22
|
-
import { YScale as
|
|
23
|
-
import { resolveSeriesDefinition as
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
5
|
+
import { resolveEdgeAnchorValue as O, drawEdgeIndicators as Y, resolveEdgeBoundary as B } from "./chart/edge-indicators.js";
|
|
6
|
+
import { computeFitToData as X } from "./chart/fit-to-data.js";
|
|
7
|
+
import { getLastValue as Z, getPreviousClose as _, getStackedLastValue as U } from "./chart/last-value.js";
|
|
8
|
+
import { resolveMaxVisibleBars as $, resolvePadding as C, resolvePerfOptions as G, isSameHorizontalPadding as R } from "./chart/options.js";
|
|
9
|
+
import { computePan as q, computeZoom as K } from "./chart/pan-zoom-math.js";
|
|
10
|
+
import { StreamingCadence as j } from "./chart/streaming-cadence.js";
|
|
11
|
+
import { computeStreamingTarget as J } from "./chart/streaming-target.js";
|
|
12
|
+
import { resolvePaddingTime as M } from "./chart/viewport-padding.js";
|
|
13
|
+
import { computeTargetYRange as Q, resolveBound as I } from "./chart/y-target.js";
|
|
14
|
+
import { renderCrosshair as tt } from "./components/crosshair.js";
|
|
15
|
+
import { renderGrid as it } from "./components/grid.js";
|
|
16
|
+
import { renderMarker as et } from "./components/marker.js";
|
|
17
|
+
import { renderReferenceLine as st } from "./components/reference-line.js";
|
|
18
|
+
import { resolveTimeRegionColors as rt, renderTimeRegion as nt } from "./components/time-region.js";
|
|
19
|
+
import { EventEmitter as at } from "./events.js";
|
|
20
|
+
import { InteractionHandler as ht } from "./interactions/handler.js";
|
|
21
|
+
import { RenderScheduler as P } from "./render-scheduler.js";
|
|
22
|
+
import { XScale as ot } from "./scales/x-scale.js";
|
|
23
|
+
import { YScale as lt } from "./scales/y-scale.js";
|
|
24
|
+
import { resolveSeriesDefinition as ct } from "./series/definition.js";
|
|
25
|
+
import { isTimeSeriesRenderer as S } from "./series/types.js";
|
|
26
|
+
import { catppuccin as dt } from "./theme/themes/catppuccin.js";
|
|
27
|
+
import { clamp as mt } from "./utils/math.js";
|
|
28
|
+
import { normalizeTime as y, detectInterval as ut } from "./utils/time.js";
|
|
29
|
+
const ft = 200, gt = 1500;
|
|
30
|
+
let pt = 0, yt = 0, vt = 0, bt = 0;
|
|
31
|
+
function H(f, t) {
|
|
32
|
+
return f === t ? !0 : !f || !t || f.length !== t.length ? !1 : f.every((i, e) => i === t[e]);
|
|
29
33
|
}
|
|
30
|
-
function
|
|
31
|
-
return
|
|
34
|
+
function St(f, t) {
|
|
35
|
+
return f === t ? !0 : !f || !t || f.length !== t.length ? !1 : f.every((i, e) => i === t[e]);
|
|
32
36
|
}
|
|
33
|
-
class
|
|
37
|
+
class qt extends at {
|
|
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
|
+
#C;
|
|
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
|
+
#tt = !1;
|
|
67
|
+
#it = !1;
|
|
54
68
|
/** Active visual theme (colors, fonts, grid style). */
|
|
55
|
-
#
|
|
69
|
+
#n;
|
|
56
70
|
/** Whether to render the background grid. */
|
|
57
|
-
#
|
|
71
|
+
#O;
|
|
58
72
|
/** Detected time interval between data points (milliseconds). */
|
|
59
|
-
#
|
|
73
|
+
#r = 6e4;
|
|
60
74
|
/** Current crosshair position, null when cursor is outside the chart. */
|
|
61
|
-
#
|
|
75
|
+
#d = null;
|
|
76
|
+
/**
|
|
77
|
+
* What the crosshair is anchored to, deciding which half of the position
|
|
78
|
+
* survives a viewport move (see {@link #syncCrosshairToViewport}):
|
|
79
|
+
* `'pointer'` (real hover) keeps `mediaX`/`mediaY` and re-resolves
|
|
80
|
+
* `time`/`y`; `'time'` (programmatic {@link setCrosshair}) keeps
|
|
81
|
+
* `time`/`y` and re-projects the pixel position.
|
|
82
|
+
*/
|
|
83
|
+
#Y = "pointer";
|
|
62
84
|
/** Whether the last overlay pass painted anything — gates the full-bitmap
|
|
63
85
|
* clear so an empty overlay isn't cleared again every main frame. */
|
|
64
|
-
#
|
|
86
|
+
#et = !1;
|
|
65
87
|
/** User-specified Y-axis bounds (auto, fixed, percentage). */
|
|
66
|
-
#
|
|
88
|
+
#l = {};
|
|
67
89
|
/** Cached series ID list — invalidated on add/remove. */
|
|
68
|
-
#
|
|
90
|
+
#T = null;
|
|
69
91
|
/** Axis visibility and sizing configuration. */
|
|
70
|
-
#
|
|
92
|
+
#H = {};
|
|
71
93
|
/** Host-declared state per edge — drives the edge-indicator overlay. */
|
|
72
|
-
#
|
|
94
|
+
#S = { left: "idle", right: "idle" };
|
|
95
|
+
/** Wall-clock start of a `loading` exit fade per side — set when the host
|
|
96
|
+
* drops `loading` back to `idle`/`has-more`, so the indicator fades out
|
|
97
|
+
* over {@link EDGE_EXIT_FADE_MS} instead of vanishing the same frame the
|
|
98
|
+
* fetched data lands. `null` = no fade in flight. */
|
|
99
|
+
#V = { left: null, right: null };
|
|
73
100
|
/** Cached boundary timestamps from the last `edgeReached` emission, by side. */
|
|
74
|
-
#
|
|
101
|
+
#st = { left: null, right: null };
|
|
102
|
+
/** Latest placeholder shapes a `loading` indicator reported per side —
|
|
103
|
+
* the morph hand-off candidate consumed by {@link prependData}. */
|
|
104
|
+
#B = { left: null, right: null };
|
|
105
|
+
/** True after a prepend consumed a side's placeholder snapshot: the series'
|
|
106
|
+
* reveal owns those pixels now, so the still-`loading` indicator is hidden
|
|
107
|
+
* and the upcoming `loading → idle` transition skips its exit fade. Reset
|
|
108
|
+
* by the next {@link setEdgeState} call. */
|
|
109
|
+
#R = { left: !1, right: !1 };
|
|
75
110
|
/** Host-supplied callback fired when the user releases a pan/zoom past a data edge. */
|
|
76
|
-
#
|
|
111
|
+
#rt;
|
|
112
|
+
/** Per-side custom painter for the edge `loading` spinner. `null` falls back to the built-in dots. */
|
|
113
|
+
#nt = { left: null, right: null };
|
|
77
114
|
/** Nesting depth for batch updates. Suppresses recomputes while > 0. */
|
|
78
|
-
#
|
|
115
|
+
#A = 0;
|
|
79
116
|
/** True when batched operations include data changes (triggers full onDataChanged on end). */
|
|
80
|
-
|
|
117
|
+
#X = !1;
|
|
81
118
|
/** True when batched operations include visibility changes (triggers Y-range + redraw on end). */
|
|
82
|
-
#
|
|
119
|
+
#M = !1;
|
|
83
120
|
/** True when batched operations bumped overlay version (emits a single overlayChange on flush). */
|
|
84
|
-
#
|
|
121
|
+
#Z = !1;
|
|
85
122
|
/**
|
|
86
123
|
* Monotonic counter bumped on any mutation that affects overlay output —
|
|
87
124
|
* data, visibility, series options, theme. Used by snapshot helpers in
|
|
@@ -91,17 +128,17 @@ class Ot extends st {
|
|
|
91
128
|
*
|
|
92
129
|
* @internal
|
|
93
130
|
*/
|
|
94
|
-
#
|
|
131
|
+
#at = 0;
|
|
95
132
|
get yAxisWidth() {
|
|
96
|
-
const
|
|
97
|
-
return
|
|
133
|
+
const t = this.#H.y;
|
|
134
|
+
return t?.visible === !1 ? 0 : t?.width ?? 55;
|
|
98
135
|
}
|
|
99
136
|
get xAxisHeight() {
|
|
100
|
-
const
|
|
101
|
-
return
|
|
137
|
+
const t = this.#H.x;
|
|
138
|
+
return t?.visible === !1 ? 0 : t?.height ?? 30;
|
|
102
139
|
}
|
|
103
140
|
/** Resolved animation config derived from `options.animations` at construction. */
|
|
104
|
-
#
|
|
141
|
+
#g;
|
|
105
142
|
/**
|
|
106
143
|
* Single source of truth for the chart's X / Y viewport animation.
|
|
107
144
|
* Push-model API: chart emits intent signals (`onPointAppended`,
|
|
@@ -110,7 +147,7 @@ class Ot extends st {
|
|
|
110
147
|
* matching targets back through `computeXTarget` / `computeYTarget`
|
|
111
148
|
* closures supplied at construction.
|
|
112
149
|
*/
|
|
113
|
-
#
|
|
150
|
+
#s;
|
|
114
151
|
/** EMA-smoothed real-wall-clock cadence between streaming appends. The
|
|
115
152
|
* X spring's `setSettleMs` is fed from this so the settle time stays just
|
|
116
153
|
* longer than the producer's tick interval — the spring never quite
|
|
@@ -126,31 +163,31 @@ class Ot extends st {
|
|
|
126
163
|
* field carries the decision across that gap. The closure also reads
|
|
127
164
|
* `#dataStart` / `#dataEnd` directly for the timestamps.
|
|
128
165
|
*/
|
|
129
|
-
#
|
|
166
|
+
#ht = !1;
|
|
130
167
|
/** Earliest data timestamp registered across all series, `null` before any data has arrived. */
|
|
131
168
|
#k = null;
|
|
132
169
|
/** Latest data timestamp registered across all series, `null` before any data has arrived. */
|
|
133
|
-
#
|
|
170
|
+
#i = null;
|
|
134
171
|
/**
|
|
135
172
|
* Previous {@link #dataEnd} value. Streaming-target math reads it to
|
|
136
173
|
* preserve any pan offset across ticks: a user who panned a few bars
|
|
137
174
|
* left of the tail keeps that offset as new bars arrive, instead of
|
|
138
175
|
* snapping back to the natural-pin position every frame.
|
|
139
176
|
*/
|
|
140
|
-
#
|
|
177
|
+
#z = null;
|
|
141
178
|
/**
|
|
142
179
|
* Warm-up window flag — `setVisibleRange({ from, bars })` arms it and
|
|
143
180
|
* the streaming-target math suppresses pan ticks while the data hasn't
|
|
144
181
|
* reached the right edge yet. Cleared by user interaction or once the
|
|
145
182
|
* data fills the window.
|
|
146
183
|
*/
|
|
147
|
-
#
|
|
184
|
+
#w = !1;
|
|
148
185
|
/**
|
|
149
186
|
* Cap for fit-to-data. When the data span exceeds this, the fit anchors
|
|
150
187
|
* the right edge and trims the left. Resolved once at construction from
|
|
151
188
|
* `options.viewport.maxVisibleBars`.
|
|
152
189
|
*/
|
|
153
|
-
#
|
|
190
|
+
#ot;
|
|
154
191
|
/**
|
|
155
192
|
* Committed logical X range — the "where X should be" target. Engine
|
|
156
193
|
* eases its visual toward this. Pan / zoom / setRange / fitContent /
|
|
@@ -159,29 +196,29 @@ class Ot extends st {
|
|
|
159
196
|
*/
|
|
160
197
|
#o = { from: 0, to: 0 };
|
|
161
198
|
/** Padded Y range — engine's raw Y target plus pixel padding from `#padding`. */
|
|
162
|
-
#
|
|
199
|
+
#F = { min: 0, max: 0 };
|
|
163
200
|
/**
|
|
164
201
|
* Auto-scroll flag — when `true`, streaming ticks slide X to track the
|
|
165
202
|
* data tail. Pan flips this off when the gesture pushes the tail off
|
|
166
203
|
* screen; chart's per-frame autoscroll check re-engages it when a
|
|
167
204
|
* follow-up pan brings the tail back into view.
|
|
168
205
|
*/
|
|
169
|
-
#
|
|
206
|
+
#u = !0;
|
|
170
207
|
/** Resolved viewport padding (top/bottom px + left/right pixels-or-intervals). */
|
|
171
|
-
#
|
|
208
|
+
#c;
|
|
172
209
|
/** Cached chart width — passed by interactions on every call; kept for the rare
|
|
173
210
|
* call that omits it (e.g. programmatic `chart.pan(...)` without explicit width). */
|
|
174
|
-
#
|
|
211
|
+
#E = 0;
|
|
175
212
|
/** Active performance monitor, or `null` when instrumentation is disabled (the default). */
|
|
176
213
|
#x;
|
|
177
214
|
/** When true, `destroy()` tears down the monitor; false for caller-supplied monitors we must not destroy. */
|
|
178
|
-
#
|
|
215
|
+
#_ = !1;
|
|
179
216
|
/** Visible HUD overlay (mounted by the perf config); non-null only when the caller requested one. */
|
|
180
|
-
#
|
|
181
|
-
constructor(
|
|
182
|
-
super(),
|
|
183
|
-
const e = this.#
|
|
184
|
-
this.#
|
|
217
|
+
#U = null;
|
|
218
|
+
constructor(t, i) {
|
|
219
|
+
super(), i?.axis && (this.#H = i.axis, this.#l = { min: i.axis.y?.min, max: i.axis.y?.max }), this.#n = i?.theme ?? dt.theme, this.#O = i?.grid?.visible !== !1, this.#g = F.resolve(i?.animations), this.#ot = $(i?.viewport?.maxVisibleBars), this.#c = C(i?.padding), this.#D = new j();
|
|
220
|
+
const e = this.#g.axis.y, s = this.#g.axis.x;
|
|
221
|
+
this.#s = N({
|
|
185
222
|
initial: { yRange: { min: 0, max: 0 }, xRange: { from: 0, to: 0 } },
|
|
186
223
|
y: {
|
|
187
224
|
curve: e.curve,
|
|
@@ -189,28 +226,32 @@ class Ot extends st {
|
|
|
189
226
|
stickyMs: e.stickyMs,
|
|
190
227
|
stickyFloorMs: e.stickyFloorMs,
|
|
191
228
|
gestureMs: e.gestureMs,
|
|
192
|
-
toggleMs: this.#
|
|
229
|
+
toggleMs: this.#g.toggleMs
|
|
193
230
|
},
|
|
194
231
|
x: {
|
|
195
232
|
curve: s.curve,
|
|
196
233
|
settleMs: s.settleMs,
|
|
197
234
|
gestureMs: s.gestureMs
|
|
198
235
|
},
|
|
199
|
-
computeXTarget: () => this.#
|
|
200
|
-
computeYTarget: ({ xTarget: a }) => this.#
|
|
201
|
-
onWake: () => this.#
|
|
202
|
-
}), this.#
|
|
203
|
-
const r =
|
|
204
|
-
this.#x = r.monitor, this.#
|
|
205
|
-
const n = this.#
|
|
206
|
-
this.timeScale.tickTracker.setFadeMs(n), this.yScale.tickTracker.setFadeMs(n), this.#
|
|
207
|
-
(a) => this.#
|
|
208
|
-
), this.#x && r.hud && (this.#
|
|
209
|
-
const h =
|
|
210
|
-
this.#
|
|
211
|
-
this.syncScales(), this.#
|
|
212
|
-
}), this.syncScales(), this.#
|
|
213
|
-
this.#
|
|
236
|
+
computeXTarget: () => this.#Rt(),
|
|
237
|
+
computeYTarget: ({ xTarget: a }) => this.#gt(a),
|
|
238
|
+
onWake: () => this.#e?.markDirty()
|
|
239
|
+
}), this.#rt = i?.onEdgeReached, this.#N = i?.viewport?.initialRange;
|
|
240
|
+
const r = G(i?.perf);
|
|
241
|
+
this.#x = r.monitor, this.#_ = r.ownsMonitor, this.#a = new W(t, this.#x ?? void 0), this.timeScale = new ot(), this.yScale = new lt(), this.timeScale.setLocale(i?.locale), this.timeScale.setTimeZone(i?.timeZone);
|
|
242
|
+
const n = this.#g.axis.ticksMs;
|
|
243
|
+
this.timeScale.tickTracker.setFadeMs(n), this.yScale.tickTracker.setFadeMs(n), this.#e = new P((a) => this.#J(a)), this.#h = new P(
|
|
244
|
+
(a) => this.#Q("overlay", a, () => this.renderOverlay(a))
|
|
245
|
+
), this.#x && r.hud && (this.#U = r.hud(t, this.#x));
|
|
246
|
+
const h = i?.interactive !== !1;
|
|
247
|
+
this.#C = h ? new ht(this.#a.canvas, this, this.timeScale, this.yScale) : null, this.#a.on("resize", () => {
|
|
248
|
+
this.syncScales(), this.#J(), this.emit("viewportChange");
|
|
249
|
+
}), this.syncScales(), this.#C?.on("crosshairMove", (a) => {
|
|
250
|
+
this.#Y = "pointer", this.#d = a, this.#h.markDirty(), this.emit("crosshairMove", a), this.#G(a);
|
|
251
|
+
}), this.#C?.on("click", (a) => {
|
|
252
|
+
this.emit("pointClick", { ...a, spatialHit: this.#ft(a) });
|
|
253
|
+
}), this.#C?.on("dblclick", (a) => {
|
|
254
|
+
this.fitContent(), this.emit("pointDoubleClick", { ...a, spatialHit: this.#ft(a) });
|
|
214
255
|
});
|
|
215
256
|
}
|
|
216
257
|
/**
|
|
@@ -225,9 +266,9 @@ class Ot extends st {
|
|
|
225
266
|
* `setSeriesVisible` / `setLayerVisible` calls — produce one overlay
|
|
226
267
|
* render instead of N.
|
|
227
268
|
*/
|
|
228
|
-
#
|
|
229
|
-
if (this.#
|
|
230
|
-
this.#
|
|
269
|
+
#p() {
|
|
270
|
+
if (this.#at++, this.#A > 0) {
|
|
271
|
+
this.#Z = !0;
|
|
231
272
|
return;
|
|
232
273
|
}
|
|
233
274
|
this.emit("overlayChange");
|
|
@@ -241,39 +282,39 @@ class Ot extends st {
|
|
|
241
282
|
* @internal
|
|
242
283
|
*/
|
|
243
284
|
getOverlayVersion() {
|
|
244
|
-
return this.#
|
|
285
|
+
return this.#at;
|
|
245
286
|
}
|
|
246
287
|
/**
|
|
247
288
|
* Return a series ID: use the provided hint if it's non-empty and not already taken,
|
|
248
289
|
* otherwise generate a new auto ID. Auto-generated IDs never collide with each other
|
|
249
290
|
* or with user-provided IDs because they use a monotonically increasing counter.
|
|
250
291
|
*/
|
|
251
|
-
#
|
|
252
|
-
if (
|
|
253
|
-
return
|
|
254
|
-
let
|
|
292
|
+
#bt(t) {
|
|
293
|
+
if (t && !this.#t.some((e) => e.id === t))
|
|
294
|
+
return t;
|
|
295
|
+
let i;
|
|
255
296
|
do
|
|
256
|
-
|
|
257
|
-
while (this.#t.some((e) => e.id ===
|
|
258
|
-
return
|
|
297
|
+
i = `series_${++pt}`;
|
|
298
|
+
while (this.#t.some((e) => e.id === i));
|
|
299
|
+
return i;
|
|
259
300
|
}
|
|
260
|
-
addSeries(
|
|
261
|
-
const e = typeof
|
|
301
|
+
addSeries(t, i = {}) {
|
|
302
|
+
const e = typeof t == "string" ? ct(t) : t;
|
|
262
303
|
if (!e)
|
|
263
304
|
throw new Error(
|
|
264
|
-
`[wick-charts] Unknown series type '${String(
|
|
305
|
+
`[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).`
|
|
265
306
|
);
|
|
266
|
-
const { layers: s, id: r, label: n, labels: h, ...a } =
|
|
267
|
-
...this.#
|
|
307
|
+
const { layers: s, id: r, label: n, labels: h, ...a } = i, l = s ?? 1, o = {
|
|
308
|
+
...this.#g.defaults(e.type),
|
|
268
309
|
...a,
|
|
269
|
-
...this.#
|
|
270
|
-
}, u = e.create({ theme: this.#
|
|
271
|
-
return u.onDataChanged?.(() => this.onDataChanged()), this.#t.push({ id:
|
|
310
|
+
...this.#g.overrides(e.type)
|
|
311
|
+
}, u = e.create({ theme: this.#n, layerCount: l }, o), d = h ?? (typeof n == "string" ? [n] : void 0), c = this.#bt(r);
|
|
312
|
+
return u.onDataChanged?.(() => this.onDataChanged()), this.#t.push({ id: c, labels: d, renderer: u, visible: !0 }), this.#T = null, this.emit("seriesChange"), this.#p(), c;
|
|
272
313
|
}
|
|
273
314
|
/** Remove a series by ID and clean up its resources. */
|
|
274
|
-
removeSeries(
|
|
275
|
-
const
|
|
276
|
-
|
|
315
|
+
removeSeries(t) {
|
|
316
|
+
const i = this.#t.findIndex((e) => e.id === t);
|
|
317
|
+
i >= 0 && (this.#t[i].renderer.dispose(), this.#t.splice(i, 1), this.#T = null, this.#e.markDirty(), this.emit("seriesChange"), this.#p());
|
|
277
318
|
}
|
|
278
319
|
// --- Markers (point annotations) -----------------------------------------
|
|
279
320
|
/**
|
|
@@ -285,72 +326,78 @@ class Ot extends st {
|
|
|
285
326
|
* are excluded from tooltips, the legend, and the Y-range autoscale, and never
|
|
286
327
|
* appear in series queries.
|
|
287
328
|
*/
|
|
288
|
-
addMarker(
|
|
289
|
-
const
|
|
290
|
-
return this.#
|
|
329
|
+
addMarker(t) {
|
|
330
|
+
const i = `marker_${++yt}`;
|
|
331
|
+
return this.#m.push(this.#lt(i, t)), this.#h.markDirty(), i;
|
|
291
332
|
}
|
|
292
333
|
/** Replace a marker's config in place. No-op when the id is unknown. */
|
|
293
|
-
updateMarker(
|
|
294
|
-
const e = this.#
|
|
295
|
-
e !== -1 && (this.#
|
|
334
|
+
updateMarker(t, i) {
|
|
335
|
+
const e = this.#m.findIndex((s) => s.id === t);
|
|
336
|
+
e !== -1 && (this.#m[e] = this.#lt(t, i), this.#h.markDirty());
|
|
296
337
|
}
|
|
297
338
|
/** Remove a marker by id. No-op when the id is unknown. */
|
|
298
|
-
removeMarker(
|
|
299
|
-
const
|
|
300
|
-
|
|
339
|
+
removeMarker(t) {
|
|
340
|
+
const i = this.#m.findIndex((e) => e.id === t);
|
|
341
|
+
i !== -1 && (this.#m.splice(i, 1), this.#h.markDirty());
|
|
301
342
|
}
|
|
302
343
|
/** Read-only snapshot of the registered markers (normalized). Mutate via add / update / removeMarker. */
|
|
303
344
|
getMarkers() {
|
|
304
|
-
return this.#
|
|
345
|
+
return this.#m;
|
|
305
346
|
}
|
|
306
|
-
#
|
|
347
|
+
#lt(t, i) {
|
|
307
348
|
return {
|
|
308
|
-
id:
|
|
309
|
-
time:
|
|
310
|
-
value:
|
|
311
|
-
seriesId:
|
|
312
|
-
shape:
|
|
313
|
-
pulse:
|
|
314
|
-
label:
|
|
315
|
-
color:
|
|
349
|
+
id: t,
|
|
350
|
+
time: y(i.time),
|
|
351
|
+
value: i.value,
|
|
352
|
+
seriesId: i.seriesId,
|
|
353
|
+
shape: i.shape ?? "dot",
|
|
354
|
+
pulse: i.pulse ?? !1,
|
|
355
|
+
label: i.label,
|
|
356
|
+
color: i.color
|
|
316
357
|
};
|
|
317
358
|
}
|
|
318
359
|
/** Resolve a marker's Y value: explicit `value`, else snapped to its series at `time`. Null when neither resolves. */
|
|
319
|
-
#
|
|
320
|
-
if (
|
|
321
|
-
if (
|
|
322
|
-
const
|
|
323
|
-
return
|
|
360
|
+
#St(t) {
|
|
361
|
+
if (t.value !== void 0 && Number.isFinite(t.value)) return t.value;
|
|
362
|
+
if (t.seriesId === void 0) return null;
|
|
363
|
+
const i = this.getDataAtTime(t.seriesId, t.time);
|
|
364
|
+
return i ? "close" in i ? i.close : i.value : null;
|
|
324
365
|
}
|
|
325
366
|
/** Resolve a marker's color: explicit `color`, else the series color, else the theme line color. */
|
|
326
|
-
#
|
|
327
|
-
if (
|
|
328
|
-
if (
|
|
329
|
-
const
|
|
330
|
-
if (
|
|
367
|
+
#xt(t) {
|
|
368
|
+
if (t.color !== void 0) return t.color;
|
|
369
|
+
if (t.seriesId !== void 0) {
|
|
370
|
+
const i = this.getSeriesColor(t.seriesId);
|
|
371
|
+
if (i !== null) return i;
|
|
331
372
|
}
|
|
332
|
-
return this.#
|
|
333
|
-
}
|
|
334
|
-
#
|
|
335
|
-
if (this.#
|
|
336
|
-
const
|
|
337
|
-
for (const
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
373
|
+
return this.#n.line.color;
|
|
374
|
+
}
|
|
375
|
+
#kt(t, i, e) {
|
|
376
|
+
if (this.#m.length === 0) return;
|
|
377
|
+
const s = performance.now() / (E * 2 * Math.PI) % 1, r = this.#$();
|
|
378
|
+
for (const n of this.#m) {
|
|
379
|
+
const h = this.#St(n);
|
|
380
|
+
if (h === null) continue;
|
|
381
|
+
const a = this.#mt(r, n.time);
|
|
382
|
+
if (a <= 0) continue;
|
|
383
|
+
const l = t.context.globalAlpha;
|
|
384
|
+
a < 1 && (t.context.globalAlpha = l * a), et({
|
|
385
|
+
scope: t,
|
|
341
386
|
timeScale: this.timeScale,
|
|
342
387
|
yScale: this.yScale,
|
|
343
|
-
theme: this.#
|
|
388
|
+
theme: this.#n,
|
|
344
389
|
marker: {
|
|
345
|
-
time:
|
|
346
|
-
value:
|
|
347
|
-
color: this.#
|
|
348
|
-
shape:
|
|
349
|
-
pulse:
|
|
350
|
-
label:
|
|
390
|
+
time: n.time,
|
|
391
|
+
value: h,
|
|
392
|
+
color: this.#xt(n),
|
|
393
|
+
shape: n.shape,
|
|
394
|
+
pulse: n.pulse,
|
|
395
|
+
label: n.label
|
|
351
396
|
},
|
|
352
|
-
phase:
|
|
353
|
-
|
|
397
|
+
phase: s,
|
|
398
|
+
plotWidth: i,
|
|
399
|
+
plotHeight: e
|
|
400
|
+
}), a < 1 && (t.context.globalAlpha = l);
|
|
354
401
|
}
|
|
355
402
|
}
|
|
356
403
|
// --- Time regions (shaded bands) -----------------------------------------
|
|
@@ -365,45 +412,45 @@ class Ot extends st {
|
|
|
365
412
|
* series model — they never affect the Y-range autoscale, tooltips, or the
|
|
366
413
|
* legend.
|
|
367
414
|
*/
|
|
368
|
-
addRegion(
|
|
369
|
-
const
|
|
370
|
-
return this.#
|
|
415
|
+
addRegion(t) {
|
|
416
|
+
const i = `region_${++vt}`;
|
|
417
|
+
return this.#b.push(this.#ct(i, t)), this.#e.markDirty(), i;
|
|
371
418
|
}
|
|
372
419
|
/** Replace a region's config in place. No-op when the id is unknown. */
|
|
373
|
-
updateRegion(
|
|
374
|
-
const e = this.#
|
|
375
|
-
e !== -1 && (this.#
|
|
420
|
+
updateRegion(t, i) {
|
|
421
|
+
const e = this.#b.findIndex((s) => s.id === t);
|
|
422
|
+
e !== -1 && (this.#b[e] = this.#ct(t, i), this.#e.markDirty());
|
|
376
423
|
}
|
|
377
424
|
/** Remove a region by id. No-op when the id is unknown. */
|
|
378
|
-
removeRegion(
|
|
379
|
-
const
|
|
380
|
-
|
|
425
|
+
removeRegion(t) {
|
|
426
|
+
const i = this.#b.findIndex((e) => e.id === t);
|
|
427
|
+
i !== -1 && (this.#b.splice(i, 1), this.#e.markDirty());
|
|
381
428
|
}
|
|
382
429
|
/** Read-only snapshot of the registered regions (normalized). Mutate via add / update / removeRegion. */
|
|
383
430
|
getRegions() {
|
|
384
|
-
return this.#
|
|
431
|
+
return this.#b;
|
|
385
432
|
}
|
|
386
|
-
#
|
|
387
|
-
const { to: e } =
|
|
433
|
+
#ct(t, i) {
|
|
434
|
+
const { to: e } = i, s = e === void 0 || e === "now" ? null : y(e);
|
|
388
435
|
return {
|
|
389
|
-
id:
|
|
390
|
-
from:
|
|
436
|
+
id: t,
|
|
437
|
+
from: y(i.from),
|
|
391
438
|
to: s,
|
|
392
|
-
fill:
|
|
393
|
-
color:
|
|
394
|
-
label:
|
|
439
|
+
fill: i.fill,
|
|
440
|
+
color: i.color,
|
|
441
|
+
label: i.label
|
|
395
442
|
};
|
|
396
443
|
}
|
|
397
|
-
#
|
|
398
|
-
if (this.#
|
|
399
|
-
for (const s of this.#
|
|
400
|
-
const { color: r, fill: n } =
|
|
401
|
-
|
|
402
|
-
scope:
|
|
444
|
+
#At(t, i, e) {
|
|
445
|
+
if (this.#b.length !== 0)
|
|
446
|
+
for (const s of this.#b) {
|
|
447
|
+
const { color: r, fill: n } = rt(s, this.#n);
|
|
448
|
+
nt({
|
|
449
|
+
scope: t,
|
|
403
450
|
timeScale: this.timeScale,
|
|
404
|
-
theme: this.#
|
|
451
|
+
theme: this.#n,
|
|
405
452
|
region: { from: s.from, to: s.to, fill: n, color: r, label: s.label },
|
|
406
|
-
plotWidth:
|
|
453
|
+
plotWidth: i,
|
|
407
454
|
plotHeight: e
|
|
408
455
|
});
|
|
409
456
|
}
|
|
@@ -419,53 +466,58 @@ class Ot extends st {
|
|
|
419
466
|
* series model — they never affect the Y-range autoscale, tooltips, or the
|
|
420
467
|
* legend.
|
|
421
468
|
*/
|
|
422
|
-
addLine(
|
|
423
|
-
const
|
|
424
|
-
return this.#
|
|
469
|
+
addLine(t) {
|
|
470
|
+
const i = `line_${++bt}`;
|
|
471
|
+
return this.#y.push(this.#dt(i, t)), this.#h.markDirty(), i;
|
|
425
472
|
}
|
|
426
473
|
/** Replace a reference line's config in place. No-op when the id is unknown. */
|
|
427
|
-
updateLine(
|
|
428
|
-
const e = this.#
|
|
429
|
-
e !== -1 && (this.#
|
|
474
|
+
updateLine(t, i) {
|
|
475
|
+
const e = this.#y.findIndex((s) => s.id === t);
|
|
476
|
+
e !== -1 && (this.#y[e] = this.#dt(t, i), this.#h.markDirty());
|
|
430
477
|
}
|
|
431
478
|
/** Remove a reference line by id. No-op when the id is unknown. */
|
|
432
|
-
removeLine(
|
|
433
|
-
const
|
|
434
|
-
|
|
479
|
+
removeLine(t) {
|
|
480
|
+
const i = this.#y.findIndex((e) => e.id === t);
|
|
481
|
+
i !== -1 && (this.#y.splice(i, 1), this.#h.markDirty());
|
|
435
482
|
}
|
|
436
483
|
/** Read-only snapshot of the registered reference lines (normalized). Mutate via add / update / removeLine. */
|
|
437
484
|
getLines() {
|
|
438
|
-
return this.#
|
|
439
|
-
}
|
|
440
|
-
#
|
|
441
|
-
const { time: e, value: s } =
|
|
442
|
-
id:
|
|
443
|
-
color:
|
|
444
|
-
label:
|
|
445
|
-
style:
|
|
446
|
-
width:
|
|
485
|
+
return this.#y;
|
|
486
|
+
}
|
|
487
|
+
#dt(t, i) {
|
|
488
|
+
const { time: e, value: s } = i, r = {
|
|
489
|
+
id: t,
|
|
490
|
+
color: i.color,
|
|
491
|
+
label: i.label,
|
|
492
|
+
style: i.style ?? "dashed",
|
|
493
|
+
width: i.width ?? 1
|
|
447
494
|
};
|
|
448
|
-
return e !== void 0 ? { ...r, orientation: "vertical", coord:
|
|
449
|
-
}
|
|
450
|
-
#
|
|
451
|
-
if (this.#
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
495
|
+
return e !== void 0 ? { ...r, orientation: "vertical", coord: y(e) } : { ...r, orientation: "horizontal", coord: s ?? Number.NaN };
|
|
496
|
+
}
|
|
497
|
+
#wt(t, i, e) {
|
|
498
|
+
if (this.#y.length === 0) return;
|
|
499
|
+
const s = this.#$();
|
|
500
|
+
for (const r of this.#y) {
|
|
501
|
+
const n = s >= 1 ? 1 : r.orientation === "vertical" ? this.#mt(s, r.coord) : L(s);
|
|
502
|
+
if (n <= 0) continue;
|
|
503
|
+
const h = t.context.globalAlpha;
|
|
504
|
+
n < 1 && (t.context.globalAlpha = h * n), st({
|
|
505
|
+
scope: t,
|
|
506
|
+
timeScale: this.timeScale,
|
|
507
|
+
yScale: this.yScale,
|
|
508
|
+
theme: this.#n,
|
|
509
|
+
line: {
|
|
510
|
+
orientation: r.orientation,
|
|
511
|
+
coord: r.coord,
|
|
512
|
+
color: r.color ?? this.#n.line.color,
|
|
513
|
+
style: r.style,
|
|
514
|
+
width: r.width,
|
|
515
|
+
label: r.label
|
|
516
|
+
},
|
|
517
|
+
plotWidth: i,
|
|
518
|
+
plotHeight: e
|
|
519
|
+
}), n < 1 && (t.context.globalAlpha = h);
|
|
520
|
+
}
|
|
469
521
|
}
|
|
470
522
|
/**
|
|
471
523
|
* Read-only snapshot of the chart's X / Y viewport animation. The same
|
|
@@ -474,7 +526,49 @@ class Ot extends st {
|
|
|
474
526
|
* underlying animator mutates between frames.
|
|
475
527
|
*/
|
|
476
528
|
getAnimationState() {
|
|
477
|
-
return this.#
|
|
529
|
+
return this.#s.getAnimationState();
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Reveal-front position in `[0, 1]` of any in-flight initial-load intro.
|
|
533
|
+
* With a `seriesId`, that series' own front; without, the minimum across
|
|
534
|
+
* every visible time series — overlays wait for the slowest reveal. `1`
|
|
535
|
+
* whenever nothing is introing. Overlay components (the Y-label badge,
|
|
536
|
+
* annotations) read this to reveal their entrance in step with the draw.
|
|
537
|
+
*/
|
|
538
|
+
getIntroFront(t) {
|
|
539
|
+
if (t !== void 0)
|
|
540
|
+
return this.#t.find((e) => e.id === t)?.renderer.getIntroFront?.() ?? 1;
|
|
541
|
+
let i = 1;
|
|
542
|
+
for (const e of this.#t) {
|
|
543
|
+
if (!e.visible) continue;
|
|
544
|
+
const s = e.renderer.getIntroFront?.();
|
|
545
|
+
s !== void 0 && s < i && (i = s);
|
|
546
|
+
}
|
|
547
|
+
return i;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Reveal front for annotations (markers / reference lines). The same as
|
|
551
|
+
* {@link getIntroFront}() during the initial reveal, but latched to `1` once
|
|
552
|
+
* that first reveal has settled — so introducing another series afterward
|
|
553
|
+
* doesn't re-hide overlays that already surfaced. Latches only after an intro
|
|
554
|
+
* has actually been observed in progress, so an annotation drawn on a chart
|
|
555
|
+
* that has no data yet still rides the reveal once data arrives.
|
|
556
|
+
*/
|
|
557
|
+
#$() {
|
|
558
|
+
if (this.#it) return 1;
|
|
559
|
+
const t = this.getIntroFront();
|
|
560
|
+
return t < 1 ? (this.#tt = !0, t) : (this.#tt && (this.#it = !0), 1);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Entrance alpha for an overlay pinned at `time`, given the intro reveal
|
|
564
|
+
* `front`. Transparent until the sweep reaches the element's normalized X,
|
|
565
|
+
* then fades in over a short band — so annotations surface in step with the
|
|
566
|
+
* series drawing past them. `1` once the intro has settled.
|
|
567
|
+
*/
|
|
568
|
+
#mt(t, i) {
|
|
569
|
+
if (t >= 1) return 1;
|
|
570
|
+
const { from: e, to: s } = this.timeScale.getRange(), r = s - e, n = r > 0 ? (i - e) / r : 0;
|
|
571
|
+
return mt((t - n) / 0.12, 0, 1);
|
|
478
572
|
}
|
|
479
573
|
/**
|
|
480
574
|
* Replace all data for a series.
|
|
@@ -485,18 +579,37 @@ class Ot extends st {
|
|
|
485
579
|
* For line/bar accepts `TimePointInput[]` (time may be `Date`); for candlestick accepts `OHLCInput[]`;
|
|
486
580
|
* for pie accepts `PieSliceData[]`. Time fields are normalized internally.
|
|
487
581
|
*/
|
|
488
|
-
setSeriesData(
|
|
489
|
-
const s = this.#t.find((r) => r.id ===
|
|
490
|
-
s && (this.#
|
|
582
|
+
setSeriesData(t, i, e) {
|
|
583
|
+
const s = this.#t.find((r) => r.id === t);
|
|
584
|
+
s && (this.#q = !0, s.renderer.setData(i, e));
|
|
491
585
|
}
|
|
492
586
|
/** Append a new data point to the end of a series (real-time tick). */
|
|
493
|
-
appendData(
|
|
494
|
-
const s = this.#t.find((r) => r.id ===
|
|
495
|
-
s === void 0 || s.renderer.appendPoint === void 0 || s.renderer.appendPoint(
|
|
587
|
+
appendData(t, i, e) {
|
|
588
|
+
const s = this.#t.find((r) => r.id === t);
|
|
589
|
+
s === void 0 || s.renderer.appendPoint === void 0 || s.renderer.appendPoint(i, e);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Prepend older points to the front of a series — the "load more history on
|
|
593
|
+
* scroll" path. Unlike {@link setSeriesData}, this deliberately does NOT set
|
|
594
|
+
* `#dataReplaceSnapPending`: the visible window and the data inside it don't
|
|
595
|
+
* change, so the viewport must stay put (X) and the Y axis eases if newly
|
|
596
|
+
* revealed history shifts the range — instead of the full-span re-fit and
|
|
597
|
+
* instant dual-axis snap a bulk `setSeriesData` replace would force. Routes
|
|
598
|
+
* through the streaming `onPointAppended` engine signal via `onDataChanged`.
|
|
599
|
+
*
|
|
600
|
+
* `points` must be sorted ascending and sit strictly before the current
|
|
601
|
+
* first point; the sync reconciler guarantees this before calling here.
|
|
602
|
+
* No-op if the renderer doesn't support prepend.
|
|
603
|
+
*/
|
|
604
|
+
prependData(t, i, e) {
|
|
605
|
+
const s = this.#t.find((n) => n.id === t);
|
|
606
|
+
if (s === void 0 || s.renderer.prependPoints === void 0) return;
|
|
607
|
+
const r = this.#B.left;
|
|
608
|
+
r !== null && s.renderer.setHistoryHandoff !== void 0 && performance.now() - r.at < gt && (s.renderer.setHistoryHandoff(r.bars), this.#R.left = !0, this.#V.left = null, this.#B.left = null, this.#h.markDirty()), s.renderer.prependPoints(i, e);
|
|
496
609
|
}
|
|
497
610
|
/** Update the last data point of a series in place (e.g. live candle update). */
|
|
498
|
-
updateData(
|
|
499
|
-
this.#t.find((s) => s.id ===
|
|
611
|
+
updateData(t, i, e) {
|
|
612
|
+
this.#t.find((s) => s.id === t)?.renderer.updateLastPoint?.(i, e);
|
|
500
613
|
}
|
|
501
614
|
/**
|
|
502
615
|
* Keep only the most recent `count` points of a series — drop the oldest
|
|
@@ -511,36 +624,36 @@ class Ot extends st {
|
|
|
511
624
|
* chart.keepLast('feed', 100);
|
|
512
625
|
* ```
|
|
513
626
|
*/
|
|
514
|
-
keepLast(
|
|
515
|
-
this.#t.find((s) => s.id ===
|
|
627
|
+
keepLast(t, i, e) {
|
|
628
|
+
this.#t.find((s) => s.id === t)?.renderer.keepLast?.(i, e);
|
|
516
629
|
}
|
|
517
630
|
/** Update visual options (color, width, etc.) for an existing series. */
|
|
518
|
-
updateSeriesOptions(
|
|
519
|
-
const e = this.#t.find((
|
|
631
|
+
updateSeriesOptions(t, i) {
|
|
632
|
+
const e = this.#t.find((o) => o.id === t);
|
|
520
633
|
if (!e) return;
|
|
521
|
-
const s = e.labels, r = e.renderer.getLayerColors().slice(), n = this.#
|
|
522
|
-
e.renderer.updateOptions({ ...
|
|
523
|
-
const h = e.renderer.getLayerColors(), a = r.length !== h.length || r.some((
|
|
524
|
-
(a ||
|
|
634
|
+
const s = e.labels, r = e.renderer.getLayerColors().slice(), n = this.#g.overrides(e.renderer.kind);
|
|
635
|
+
e.renderer.updateOptions({ ...i, ...n }), "label" in i && typeof i.label == "string" && (e.labels = [i.label]), this.#e.markDirty();
|
|
636
|
+
const h = e.renderer.getLayerColors(), a = r.length !== h.length || r.some((o, u) => o !== h[u]), l = !H(s, e.labels);
|
|
637
|
+
(a || l) && this.#p();
|
|
525
638
|
}
|
|
526
639
|
/**
|
|
527
640
|
* Batch multiple updates: suppress recomputes until `fn` returns. Exceptions
|
|
528
641
|
* inside `fn` still flush the batch so counters don't leak across calls.
|
|
529
642
|
*/
|
|
530
|
-
batch(
|
|
531
|
-
this.#
|
|
643
|
+
batch(t) {
|
|
644
|
+
this.#A++;
|
|
532
645
|
try {
|
|
533
|
-
|
|
646
|
+
t();
|
|
534
647
|
} finally {
|
|
535
|
-
if (--this.#
|
|
536
|
-
if (this.#
|
|
537
|
-
this
|
|
538
|
-
else if (this.#
|
|
539
|
-
this.#
|
|
540
|
-
const
|
|
541
|
-
this.#
|
|
648
|
+
if (--this.#A <= 0) {
|
|
649
|
+
if (this.#A = 0, this.#X)
|
|
650
|
+
this.#X = !1, this.#M = !1, this.onDataChanged();
|
|
651
|
+
else if (this.#M) {
|
|
652
|
+
this.#M = !1, this.#f = !0;
|
|
653
|
+
const i = performance.now();
|
|
654
|
+
this.#s.onAxisReconfig(i), this.#v(i), this.#e.markDirty();
|
|
542
655
|
}
|
|
543
|
-
this.#
|
|
656
|
+
this.#Z && (this.#Z = !1, this.emit("overlayChange"));
|
|
544
657
|
}
|
|
545
658
|
}
|
|
546
659
|
}
|
|
@@ -551,18 +664,18 @@ class Ot extends st {
|
|
|
551
664
|
* computation immediately so the axis can start moving while the line
|
|
552
665
|
* fades out in parallel.
|
|
553
666
|
*/
|
|
554
|
-
setSeriesVisible(
|
|
555
|
-
const e = this.#t.find((n) => n.id ===
|
|
556
|
-
if (!e || e.visible ===
|
|
557
|
-
e.visible =
|
|
558
|
-
const s = this.#
|
|
559
|
-
if (e.renderer.setAlpha?.(
|
|
560
|
-
this.#
|
|
667
|
+
setSeriesVisible(t, i) {
|
|
668
|
+
const e = this.#t.find((n) => n.id === t);
|
|
669
|
+
if (!e || e.visible === i) return;
|
|
670
|
+
e.visible = i, this.#p();
|
|
671
|
+
const s = this.#g.toggleMs;
|
|
672
|
+
if (e.renderer.setAlpha?.(i ? 1 : 0, s), this.#e.markDirty(), this.#A > 0) {
|
|
673
|
+
this.#M = !0;
|
|
561
674
|
return;
|
|
562
675
|
}
|
|
563
|
-
this.#
|
|
676
|
+
this.#f = !0;
|
|
564
677
|
const r = performance.now();
|
|
565
|
-
this.#
|
|
678
|
+
this.#s.onSeriesVisibilityChanged(r), this.#v(r);
|
|
566
679
|
}
|
|
567
680
|
/**
|
|
568
681
|
* Whether streaming ticks slide the visible range to track the data tail.
|
|
@@ -572,10 +685,10 @@ class Ot extends st {
|
|
|
572
685
|
* containing the tail.
|
|
573
686
|
*/
|
|
574
687
|
getAutoScroll() {
|
|
575
|
-
return this.#
|
|
688
|
+
return this.#u;
|
|
576
689
|
}
|
|
577
|
-
isSeriesVisible(
|
|
578
|
-
return this.#t.find((
|
|
690
|
+
isSeriesVisible(t) {
|
|
691
|
+
return this.#t.find((i) => i.id === t)?.visible ?? !0;
|
|
579
692
|
}
|
|
580
693
|
/**
|
|
581
694
|
* Show or hide a specific layer within a multi-layer series.
|
|
@@ -587,31 +700,31 @@ class Ot extends st {
|
|
|
587
700
|
* `getValueRange` reading `store.isVisible()`); the alpha fade keeps the
|
|
588
701
|
* layer's geometry on screen until the engine's Y ease settles.
|
|
589
702
|
*/
|
|
590
|
-
setLayerVisible(
|
|
591
|
-
const s = this.#t.find((h) => h.id ===
|
|
592
|
-
if (!s || s.renderer.getLayerCount() <= 1 || s.renderer.isLayerVisible(
|
|
593
|
-
s.renderer.setLayerVisible(
|
|
594
|
-
const r = this.#
|
|
595
|
-
if (s.renderer.setLayerAlpha?.(
|
|
596
|
-
this.#
|
|
703
|
+
setLayerVisible(t, i, e) {
|
|
704
|
+
const s = this.#t.find((h) => h.id === t);
|
|
705
|
+
if (!s || s.renderer.getLayerCount() <= 1 || s.renderer.isLayerVisible(i) === e) return;
|
|
706
|
+
s.renderer.setLayerVisible(i, e), this.#p();
|
|
707
|
+
const r = this.#g.toggleMs;
|
|
708
|
+
if (s.renderer.setLayerAlpha?.(i, e ? 1 : 0, r), this.#e.markDirty(), this.#A > 0) {
|
|
709
|
+
this.#M = !0;
|
|
597
710
|
return;
|
|
598
711
|
}
|
|
599
|
-
this.#
|
|
712
|
+
this.#f = !0;
|
|
600
713
|
const n = performance.now();
|
|
601
|
-
this.#
|
|
714
|
+
this.#s.onSeriesVisibilityChanged(n), this.#v(n);
|
|
602
715
|
}
|
|
603
|
-
isLayerVisible(
|
|
604
|
-
return this.#t.find((e) => e.id ===
|
|
716
|
+
isLayerVisible(t, i) {
|
|
717
|
+
return this.#t.find((e) => e.id === t)?.renderer.isLayerVisible(i) ?? !0;
|
|
605
718
|
}
|
|
606
719
|
/** Auto-fit the viewport to show all data across every series. */
|
|
607
720
|
fitContent() {
|
|
608
|
-
const { first:
|
|
609
|
-
if (
|
|
610
|
-
const e = this.#
|
|
611
|
-
this.#
|
|
721
|
+
const { first: t, last: i } = this.getDataBounds();
|
|
722
|
+
if (t === void 0 || i === void 0) return;
|
|
723
|
+
const e = this.#a.size.media.width - this.yAxisWidth;
|
|
724
|
+
this.#P(t, i, e), this.#W({ xEase: !0 });
|
|
612
725
|
}
|
|
613
726
|
getVisibleRange() {
|
|
614
|
-
return this.#o.from === 0 && this.#o.to === 0 ? this.#
|
|
727
|
+
return this.#o.from === 0 && this.#o.to === 0 ? this.#s.getAnimationState().xRange : this.#o;
|
|
615
728
|
}
|
|
616
729
|
/**
|
|
617
730
|
* Return the full span of registered data, or `null` before any data has
|
|
@@ -619,7 +732,7 @@ class Ot extends st {
|
|
|
619
732
|
* tracking and is cheaper than recomputing from series stores.
|
|
620
733
|
*/
|
|
621
734
|
getDataRange() {
|
|
622
|
-
return this.#k === null || this.#
|
|
735
|
+
return this.#k === null || this.#i === null ? null : { from: this.#k, to: this.#i };
|
|
623
736
|
}
|
|
624
737
|
/**
|
|
625
738
|
* Imperatively set the visible time range. Three forms:
|
|
@@ -645,35 +758,35 @@ class Ot extends st {
|
|
|
645
758
|
* multi-chart sync so a synced pane tracks the originating gesture in
|
|
646
759
|
* lockstep rather than lagging behind on the long sticky contract.
|
|
647
760
|
*/
|
|
648
|
-
setVisibleRange(
|
|
649
|
-
if (typeof
|
|
650
|
-
if (!Number.isInteger(
|
|
761
|
+
setVisibleRange(t, i) {
|
|
762
|
+
if (typeof t == "number") {
|
|
763
|
+
if (!Number.isInteger(t) || t < 2) return;
|
|
651
764
|
const { first: n, last: h } = this.getDataBounds();
|
|
652
765
|
if (n === void 0 || h === void 0) return;
|
|
653
|
-
const a = Math.max(n, h - (
|
|
654
|
-
this.#
|
|
766
|
+
const a = Math.max(n, h - (t - 1) * this.#r), l = this.#a.size.media.width - this.yAxisWidth;
|
|
767
|
+
this.#P(a, h, l), this.#W();
|
|
655
768
|
return;
|
|
656
769
|
}
|
|
657
|
-
if ("bars" in
|
|
658
|
-
if (!Number.isInteger(
|
|
659
|
-
const n =
|
|
660
|
-
this.#
|
|
770
|
+
if ("bars" in t) {
|
|
771
|
+
if (!Number.isInteger(t.bars) || t.bars < 2) return;
|
|
772
|
+
const n = y(t.from), h = n + t.bars * this.#r;
|
|
773
|
+
this.#u = this.#i !== null && this.#i >= n && this.#i <= h, this.#L({ from: n, to: h }, { emitChange: !0 }), this.#w = !0, this.#W();
|
|
661
774
|
return;
|
|
662
775
|
}
|
|
663
|
-
const e =
|
|
664
|
-
this.#
|
|
665
|
-
const r = this.#
|
|
666
|
-
if (
|
|
667
|
-
r && this.#
|
|
776
|
+
const e = y(t.from), s = y(t.to);
|
|
777
|
+
this.#u = this.#i !== null && this.#i >= e && this.#i <= s, this.#w = !1;
|
|
778
|
+
const r = this.#L({ from: e, to: s }, { emitChange: !0 });
|
|
779
|
+
if (i?.gesture) {
|
|
780
|
+
r && this.#Mt();
|
|
668
781
|
return;
|
|
669
782
|
}
|
|
670
|
-
this.#
|
|
783
|
+
this.#W();
|
|
671
784
|
}
|
|
672
785
|
getYRange() {
|
|
673
|
-
return this.#
|
|
786
|
+
return this.#F;
|
|
674
787
|
}
|
|
675
788
|
getCrosshairPosition() {
|
|
676
|
-
return this.#
|
|
789
|
+
return this.#d;
|
|
677
790
|
}
|
|
678
791
|
/**
|
|
679
792
|
* Programmatically set or clear the crosshair. Same effect as the user
|
|
@@ -700,45 +813,52 @@ class Ot extends st {
|
|
|
700
813
|
* current crosshair is a no-op (no emit). Combined with the y-preservation
|
|
701
814
|
* above, this lets N charts broadcast their hover to each other without
|
|
702
815
|
* a feedback-loop guard.
|
|
816
|
+
*
|
|
817
|
+
* The echo protection assumes the broadcast is **synchronous** (call
|
|
818
|
+
* `setCrosshair` directly from the `crosshairMove` listener). An echo
|
|
819
|
+
* routed through an async hop — a state store, a React render — can
|
|
820
|
+
* arrive with a stale `time` while the origin chart streams, miss the
|
|
821
|
+
* idempotency check, and flip that chart to time-anchored until the next
|
|
822
|
+
* real pointer move (see {@link #syncCrosshairToViewport}).
|
|
703
823
|
*/
|
|
704
|
-
setCrosshair(
|
|
705
|
-
if (
|
|
706
|
-
if (this.#
|
|
707
|
-
this.#
|
|
824
|
+
setCrosshair(t) {
|
|
825
|
+
if (t === null) {
|
|
826
|
+
if (this.#d === null) return;
|
|
827
|
+
this.#d = null, this.#h.markDirty(), this.emit("crosshairMove", null);
|
|
708
828
|
return;
|
|
709
829
|
}
|
|
710
|
-
const { time:
|
|
711
|
-
if (!Number.isFinite(
|
|
712
|
-
const e = this.#
|
|
830
|
+
const { time: i } = t;
|
|
831
|
+
if (!Number.isFinite(i)) return;
|
|
832
|
+
const e = this.#d;
|
|
713
833
|
let s;
|
|
714
|
-
if (
|
|
715
|
-
s =
|
|
834
|
+
if (t.y !== void 0)
|
|
835
|
+
s = t.y;
|
|
716
836
|
else if (e !== null)
|
|
717
837
|
s = e.y;
|
|
718
838
|
else {
|
|
719
|
-
const h = this.#
|
|
839
|
+
const h = this.#F;
|
|
720
840
|
s = (h.min + h.max) / 2;
|
|
721
841
|
}
|
|
722
|
-
if (!Number.isFinite(s) || e !== null && e.time ===
|
|
723
|
-
const r = this.timeScale.timeToX(
|
|
724
|
-
this.#
|
|
842
|
+
if (!Number.isFinite(s) || e !== null && e.time === i && e.y === s) return;
|
|
843
|
+
const r = this.timeScale.timeToX(i), n = this.yScale.valueToY(s);
|
|
844
|
+
this.#Y = "time", this.#d = { mediaX: r, mediaY: n, time: i, y: s }, this.#h.markDirty(), this.emit("crosshairMove", this.#d);
|
|
725
845
|
}
|
|
726
846
|
/** Get the last visible value and whether the absolute last point is on screen. */
|
|
727
|
-
getLastValue(
|
|
728
|
-
return
|
|
847
|
+
getLastValue(t) {
|
|
848
|
+
return Z(t, this.#t, this.#s.getAnimationState().xRange);
|
|
729
849
|
}
|
|
730
850
|
/** Get the second-to-last value, useful for computing change. */
|
|
731
|
-
getPreviousClose(
|
|
732
|
-
return
|
|
851
|
+
getPreviousClose(t) {
|
|
852
|
+
return _(t, this.#t);
|
|
733
853
|
}
|
|
734
|
-
getLastData(
|
|
735
|
-
const
|
|
736
|
-
return !
|
|
854
|
+
getLastData(t) {
|
|
855
|
+
const i = this.#t.find((e) => e.id === t);
|
|
856
|
+
return !i || !S(i.renderer) ? null : i.renderer.getLastDataPoint();
|
|
737
857
|
}
|
|
738
858
|
/** Find the data point closest to the given timestamp within one data interval. */
|
|
739
|
-
getDataAtTime(
|
|
740
|
-
const e = this.#t.find((s) => s.id ===
|
|
741
|
-
return e ? e.renderer.getDataAtTime?.(
|
|
859
|
+
getDataAtTime(t, i) {
|
|
860
|
+
const e = this.#t.find((s) => s.id === t);
|
|
861
|
+
return e ? e.renderer.getDataAtTime?.(i, this.#r) ?? null : null;
|
|
742
862
|
}
|
|
743
863
|
/**
|
|
744
864
|
* Get all layers' data at a given time for multi-layer series (Bar/Line
|
|
@@ -746,50 +866,52 @@ class Ot extends st {
|
|
|
746
866
|
* snapped sample time — callers must not derive layer identity from the
|
|
747
867
|
* array index, because hidden layers are filtered out.
|
|
748
868
|
*/
|
|
749
|
-
getLayerSnapshots(
|
|
750
|
-
const e = this.#t.find((s) => s.id ===
|
|
751
|
-
return e?.visible ? e.renderer.getLayerSnapshots?.(
|
|
869
|
+
getLayerSnapshots(t, i) {
|
|
870
|
+
const e = this.#t.find((s) => s.id === t);
|
|
871
|
+
return e?.visible ? e.renderer.getLayerSnapshots?.(i, this.#r) ?? null : null;
|
|
752
872
|
}
|
|
753
873
|
getSeriesIds() {
|
|
754
|
-
return this.#
|
|
874
|
+
return this.#T || (this.#T = this.#t.map((t) => t.id)), this.#T.slice();
|
|
755
875
|
}
|
|
756
876
|
/**
|
|
757
|
-
* True if any visible series
|
|
758
|
-
* to gate crosshair rendering — a
|
|
759
|
-
* coordinate system, so the dashed hairlines would
|
|
877
|
+
* True if any visible series has a time axis (line / bar / candlestick).
|
|
878
|
+
* Used to gate crosshair rendering — a spatial-only chart (pie, heatmap)
|
|
879
|
+
* has no meaningful x/y coordinate system, so the dashed hairlines would
|
|
880
|
+
* just be visual noise.
|
|
760
881
|
*/
|
|
761
|
-
#
|
|
762
|
-
for (const
|
|
763
|
-
if (
|
|
882
|
+
#Tt() {
|
|
883
|
+
for (const t of this.#t)
|
|
884
|
+
if (t.visible && S(t.renderer))
|
|
764
885
|
return !0;
|
|
765
886
|
return !1;
|
|
766
887
|
}
|
|
767
888
|
/**
|
|
768
|
-
* Filter `getSeriesIds()` by renderer type. `'pie'
|
|
769
|
-
* `'
|
|
889
|
+
* Filter `getSeriesIds()` by renderer type. A concrete kind (`'pie'`,
|
|
890
|
+
* `'heatmap'`, a custom kind, …) returns series of that exact kind;
|
|
891
|
+
* `'time'` returns every time-axis series (line/bar/candlestick and any
|
|
892
|
+
* custom time-series kind).
|
|
770
893
|
*
|
|
771
894
|
* - `opts.visibleOnly` — exclude series with `isSeriesVisible=false`; for
|
|
772
895
|
* multi-layer series also exclude when every layer is hidden.
|
|
773
896
|
* - `opts.singleLayerOnly` — exclude series with more than one layer.
|
|
774
897
|
* Useful for YLabel fallback priority (stick to a single line first).
|
|
775
898
|
*/
|
|
776
|
-
getSeriesIdsByType(
|
|
777
|
-
const e =
|
|
899
|
+
getSeriesIdsByType(t, i) {
|
|
900
|
+
const e = i?.visibleOnly === !0, s = i?.singleLayerOnly === !0, r = [];
|
|
778
901
|
for (const n of this.#t) {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
if (!(s && a > 1)) {
|
|
902
|
+
if (!(t === "time" ? S(n.renderer) : n.renderer.kind === t)) continue;
|
|
903
|
+
const h = n.renderer.getLayerCount();
|
|
904
|
+
if (!(s && h > 1)) {
|
|
783
905
|
if (e) {
|
|
784
906
|
if (!n.visible) continue;
|
|
785
|
-
if (
|
|
786
|
-
let
|
|
787
|
-
for (let l = 0; l <
|
|
907
|
+
if (h > 1) {
|
|
908
|
+
let a = !1;
|
|
909
|
+
for (let l = 0; l < h; l++)
|
|
788
910
|
if (n.renderer.isLayerVisible(l)) {
|
|
789
|
-
|
|
911
|
+
a = !0;
|
|
790
912
|
break;
|
|
791
913
|
}
|
|
792
|
-
if (!
|
|
914
|
+
if (!a) continue;
|
|
793
915
|
}
|
|
794
916
|
}
|
|
795
917
|
r.push(n.id);
|
|
@@ -803,8 +925,23 @@ class Ot extends st {
|
|
|
803
925
|
* series without stacked concepts (Candlestick, single-layer Line/Bar).
|
|
804
926
|
* Returns `null` for unknown ids or empty series.
|
|
805
927
|
*/
|
|
806
|
-
getStackedLastValue(
|
|
807
|
-
return
|
|
928
|
+
getStackedLastValue(t) {
|
|
929
|
+
return U(t, this.#t, this.#s.getAnimationState().xRange);
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Stacked value at an arbitrary `time` — the cumulative stack top at that
|
|
933
|
+
* sample, sharing a basis with {@link getStackedLastValue}. Falls back to the
|
|
934
|
+
* nearest raw sample (close / value) for renderers without a stacked concept
|
|
935
|
+
* (Candlestick, single-layer Line/Bar). Returns `null` for unknown ids or
|
|
936
|
+
* when no sample sits near `time`. Used by the YLabel intro count-up start.
|
|
937
|
+
*/
|
|
938
|
+
getStackedValueAtTime(t, i) {
|
|
939
|
+
const e = this.#t.find((n) => n.id === t);
|
|
940
|
+
if (!e) return null;
|
|
941
|
+
const s = e.renderer.getStackedValueAtTime?.(i, this.#r);
|
|
942
|
+
if (s != null) return s;
|
|
943
|
+
const r = e.renderer.getDataAtTime?.(i, this.#r);
|
|
944
|
+
return r ? "close" in r ? r.close : r.value : null;
|
|
808
945
|
}
|
|
809
946
|
/**
|
|
810
947
|
* Per-layer last snapshots with each layer's own `time`. Returns `null`
|
|
@@ -812,8 +949,8 @@ class Ot extends st {
|
|
|
812
949
|
* overlay components that must display every layer in last-mode even
|
|
813
950
|
* when layers advance at different rates (ragged streams).
|
|
814
951
|
*/
|
|
815
|
-
getLayerLastSnapshots(
|
|
816
|
-
return this.#t.find((
|
|
952
|
+
getLayerLastSnapshots(t) {
|
|
953
|
+
return this.#t.find((i) => i.id === t)?.renderer.getLayerLastSnapshots?.() ?? null;
|
|
817
954
|
}
|
|
818
955
|
/**
|
|
819
956
|
* Shallow view of the internal series entries so unit tests can introspect
|
|
@@ -823,20 +960,20 @@ class Ot extends st {
|
|
|
823
960
|
* @internal
|
|
824
961
|
*/
|
|
825
962
|
listSeriesForTest() {
|
|
826
|
-
return this.#t.map((
|
|
963
|
+
return this.#t.map((t) => ({ id: t.id, renderer: t.renderer }));
|
|
827
964
|
}
|
|
828
965
|
/** Get the primary display color for a series. */
|
|
829
|
-
getSeriesColor(
|
|
830
|
-
const
|
|
831
|
-
return
|
|
966
|
+
getSeriesColor(t) {
|
|
967
|
+
const i = this.#t.find((e) => e.id === t);
|
|
968
|
+
return i ? i.renderer.getLayerColors()[0] ?? this.#n.line.color : null;
|
|
832
969
|
}
|
|
833
970
|
/** The series' primary name (first layer). Used for single-layer overlays. */
|
|
834
|
-
getSeriesLabel(
|
|
835
|
-
return this.#t.find((
|
|
971
|
+
getSeriesLabel(t) {
|
|
972
|
+
return this.#t.find((i) => i.id === t)?.labels?.[0];
|
|
836
973
|
}
|
|
837
974
|
/** The display name of one layer, or `undefined` when that layer is unnamed. */
|
|
838
|
-
getLayerLabel(
|
|
839
|
-
return this.#t.find((e) => e.id ===
|
|
975
|
+
getLayerLabel(t, i) {
|
|
976
|
+
return this.#t.find((e) => e.id === t)?.labels?.[i];
|
|
840
977
|
}
|
|
841
978
|
/**
|
|
842
979
|
* Set every layer's display name at once (index = layer). Drives the tooltip,
|
|
@@ -844,9 +981,9 @@ class Ot extends st {
|
|
|
844
981
|
* per-layer labels in `data` change. No-ops (and skips the overlay bump) when
|
|
845
982
|
* the labels are unchanged, so it's safe to call on every data tick.
|
|
846
983
|
*/
|
|
847
|
-
setSeriesLabels(
|
|
848
|
-
const e = this.#t.find((s) => s.id ===
|
|
849
|
-
!e ||
|
|
984
|
+
setSeriesLabels(t, i) {
|
|
985
|
+
const e = this.#t.find((s) => s.id === t);
|
|
986
|
+
!e || H(e.labels, i) || (e.labels = i, this.#p());
|
|
850
987
|
}
|
|
851
988
|
/**
|
|
852
989
|
* Set the per-layer color overrides carried in `data` (sparse — `undefined`
|
|
@@ -855,44 +992,58 @@ class Ot extends st {
|
|
|
855
992
|
* the renderer, which keeps them separate from the theme base so a theme swap
|
|
856
993
|
* preserves them. Called by the framework wrappers; no-ops when unchanged.
|
|
857
994
|
*/
|
|
858
|
-
setLayerColors(
|
|
859
|
-
const e = this.#t.find((s) => s.id ===
|
|
860
|
-
!e ||
|
|
995
|
+
setLayerColors(t, i) {
|
|
996
|
+
const e = this.#t.find((s) => s.id === t);
|
|
997
|
+
!e || St(e.colorOverrides, i) || (e.colorOverrides = i, e.renderer.setColorOverrides?.(i), this.#e.markDirty(), this.#p());
|
|
861
998
|
}
|
|
862
999
|
/** Get per-layer colors for a series. Returns null for single-layer non-bar/line series. */
|
|
863
|
-
getSeriesLayers(
|
|
864
|
-
const
|
|
865
|
-
if (!
|
|
866
|
-
const e =
|
|
1000
|
+
getSeriesLayers(t) {
|
|
1001
|
+
const i = this.#t.find((r) => r.id === t);
|
|
1002
|
+
if (!i) return null;
|
|
1003
|
+
const e = i.renderer.getLayerCount();
|
|
867
1004
|
if (e <= 1) return null;
|
|
868
|
-
const s =
|
|
1005
|
+
const s = i.renderer.getLayerColors();
|
|
869
1006
|
return Array.from({ length: e }, (r, n) => ({ color: s[n % s.length] }));
|
|
870
1007
|
}
|
|
871
1008
|
/** Get all slices with computed colors and percentages. Returns null for series without slice data (e.g. candlestick, line, bar). */
|
|
872
|
-
getSliceInfo(
|
|
873
|
-
return this.#t.find((
|
|
1009
|
+
getSliceInfo(t) {
|
|
1010
|
+
return this.#t.find((i) => i.id === t)?.renderer.getSliceInfo?.(this.#n) ?? null;
|
|
874
1011
|
}
|
|
875
1012
|
/** Get hover info (label/value/percent/color) for the currently hovered element, or null. */
|
|
876
|
-
getHoverInfo(
|
|
877
|
-
return this.#t.find((
|
|
1013
|
+
getHoverInfo(t) {
|
|
1014
|
+
return this.#t.find((i) => i.id === t)?.renderer.getHoverInfo?.(this.#n) ?? null;
|
|
878
1015
|
}
|
|
879
1016
|
/** Apply a new theme and update series colors where appropriate. */
|
|
880
|
-
setTheme(
|
|
881
|
-
const
|
|
882
|
-
this.#
|
|
1017
|
+
setTheme(t) {
|
|
1018
|
+
const i = this.#n;
|
|
1019
|
+
this.#n = t;
|
|
883
1020
|
for (const e of this.#t)
|
|
884
|
-
e.renderer.applyTheme(
|
|
885
|
-
this.#
|
|
1021
|
+
e.renderer.applyTheme(t, i);
|
|
1022
|
+
this.#e.markDirty(), this.#p();
|
|
886
1023
|
}
|
|
887
1024
|
getTheme() {
|
|
888
|
-
return this.#
|
|
1025
|
+
return this.#n;
|
|
1026
|
+
}
|
|
1027
|
+
/** BCP 47 locale applied to the built-in time-axis / tooltip / crosshair formatting. */
|
|
1028
|
+
setLocale(t) {
|
|
1029
|
+
this.timeScale.setLocale(t), this.#p();
|
|
1030
|
+
}
|
|
1031
|
+
getLocale() {
|
|
1032
|
+
return this.timeScale.getLocale();
|
|
1033
|
+
}
|
|
1034
|
+
/** IANA timezone applied to the same built-in date-time formatting. */
|
|
1035
|
+
setTimeZone(t) {
|
|
1036
|
+
this.timeScale.setTimeZone(t), this.#p();
|
|
1037
|
+
}
|
|
1038
|
+
getTimeZone() {
|
|
1039
|
+
return this.timeScale.getTimeZone();
|
|
889
1040
|
}
|
|
890
1041
|
/** Update axis configuration and re-render. */
|
|
891
|
-
setAxis(
|
|
892
|
-
const
|
|
893
|
-
this.#
|
|
1042
|
+
setAxis(t) {
|
|
1043
|
+
const i = this.yAxisWidth, e = this.xAxisHeight;
|
|
1044
|
+
this.#H = t, this.#l = { min: t.y?.min, max: t.y?.max }, this.#f = !0;
|
|
894
1045
|
const s = performance.now();
|
|
895
|
-
this.#
|
|
1046
|
+
this.#s.onAxisReconfig(s), this.#v(s), (this.yAxisWidth !== i || this.xAxisHeight !== e) && this.syncScales(), this.#e.markDirty();
|
|
896
1047
|
}
|
|
897
1048
|
/**
|
|
898
1049
|
* Apply label-density knobs driven by `<YAxis labelCount=… minLabelSpacing=…>`
|
|
@@ -900,29 +1051,29 @@ class Ot extends st {
|
|
|
900
1051
|
* Y-animation reset. Triggers a viewportChange + repaint so static charts
|
|
901
1052
|
* react without waiting for pan/zoom.
|
|
902
1053
|
*/
|
|
903
|
-
setYAxisLabelDensity(
|
|
904
|
-
let
|
|
905
|
-
"labelCount" in
|
|
1054
|
+
setYAxisLabelDensity(t) {
|
|
1055
|
+
let i = !1;
|
|
1056
|
+
"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());
|
|
906
1057
|
}
|
|
907
1058
|
/** Label-density knobs for the time axis — mirror of {@link setYAxisLabelDensity}. */
|
|
908
|
-
setTimeAxisLabelDensity(
|
|
909
|
-
let
|
|
910
|
-
"labelCount" in
|
|
1059
|
+
setTimeAxisLabelDensity(t) {
|
|
1060
|
+
let i = !1;
|
|
1061
|
+
"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());
|
|
911
1062
|
}
|
|
912
1063
|
getMediaSize() {
|
|
913
|
-
return this.#
|
|
1064
|
+
return this.#a.size.media;
|
|
914
1065
|
}
|
|
915
1066
|
/** Returns layout metrics for the chart area, Y axis, and time axis. */
|
|
916
1067
|
getLayout() {
|
|
917
|
-
const
|
|
1068
|
+
const t = this.#a.size.media, i = this.yAxisWidth, e = this.xAxisHeight;
|
|
918
1069
|
return {
|
|
919
|
-
chartArea: { x: 0, y: 0, width:
|
|
920
|
-
yAxisWidth:
|
|
1070
|
+
chartArea: { x: 0, y: 0, width: t.width - i, height: t.height - e },
|
|
1071
|
+
yAxisWidth: i,
|
|
921
1072
|
xAxisHeight: e
|
|
922
1073
|
};
|
|
923
1074
|
}
|
|
924
1075
|
getDataInterval() {
|
|
925
|
-
return this.#
|
|
1076
|
+
return this.#r;
|
|
926
1077
|
}
|
|
927
1078
|
/**
|
|
928
1079
|
* Update viewport padding at runtime. Refits the visible time range to
|
|
@@ -932,27 +1083,27 @@ class Ot extends st {
|
|
|
932
1083
|
* a wrapper re-applies padding reactively (e.g. in response to a Title /
|
|
933
1084
|
* InfoBar ResizeObserver).
|
|
934
1085
|
*/
|
|
935
|
-
setPadding(
|
|
936
|
-
const
|
|
937
|
-
this.#
|
|
938
|
-
const e = this.#
|
|
1086
|
+
setPadding(t) {
|
|
1087
|
+
const i = this.#c;
|
|
1088
|
+
this.#c = C(t);
|
|
1089
|
+
const e = this.#c, s = !R(i.left, e.left) || !R(i.right, e.right), r = i.top !== e.top || i.bottom !== e.bottom;
|
|
939
1090
|
if (s) {
|
|
940
1091
|
const { first: n, last: h } = this.getDataBounds();
|
|
941
1092
|
if (n !== void 0 && h !== void 0) {
|
|
942
|
-
const a = this.#
|
|
943
|
-
this.#
|
|
1093
|
+
const a = this.#a.size.media.width - this.yAxisWidth;
|
|
1094
|
+
this.#P(n, h, a);
|
|
944
1095
|
}
|
|
945
1096
|
}
|
|
946
1097
|
if (s || r) {
|
|
947
|
-
this.#
|
|
1098
|
+
this.#f = !0;
|
|
948
1099
|
const n = performance.now();
|
|
949
|
-
this.#
|
|
1100
|
+
this.#s.onAxisReconfig(n), this.#v(n);
|
|
950
1101
|
}
|
|
951
|
-
this.syncScales(), this.#
|
|
1102
|
+
this.syncScales(), this.#e.markDirty(), r && this.emit("viewportChange");
|
|
952
1103
|
}
|
|
953
1104
|
/** Show or hide the background grid. Takes effect on the next render frame. */
|
|
954
|
-
setGrid(
|
|
955
|
-
this.#
|
|
1105
|
+
setGrid(t) {
|
|
1106
|
+
this.#O = t.visible, this.#e.markDirty();
|
|
956
1107
|
}
|
|
957
1108
|
/**
|
|
958
1109
|
* Set the visual state for one side of the chart. Typically called in
|
|
@@ -963,12 +1114,62 @@ class Ot extends st {
|
|
|
963
1114
|
* The state persists until replaced. `has-more` is accepted for API
|
|
964
1115
|
* symmetry and currently renders identically to `idle`.
|
|
965
1116
|
*/
|
|
966
|
-
setEdgeState(
|
|
967
|
-
|
|
1117
|
+
setEdgeState(t, i) {
|
|
1118
|
+
if (this.#S[t] === i) return;
|
|
1119
|
+
const e = this.#S[t] === "loading", s = this.#R[t];
|
|
1120
|
+
this.#R[t] = !1, this.#V[t] = e && !s && (i === "idle" || i === "has-more") ? performance.now() : null, this.#S[t] = i, this.#h.markDirty();
|
|
968
1121
|
}
|
|
969
1122
|
/** Read the current host-declared state for a given edge. */
|
|
970
|
-
getEdgeState(
|
|
971
|
-
return this.#
|
|
1123
|
+
getEdgeState(t) {
|
|
1124
|
+
return this.#S[t];
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Alpha for a side's `loading` exit fade, or `null` when none is in
|
|
1128
|
+
* flight. Self-cleaning: an elapsed fade clears its start stamp.
|
|
1129
|
+
*/
|
|
1130
|
+
#ut(t) {
|
|
1131
|
+
const i = this.#V[t];
|
|
1132
|
+
if (i === null) return null;
|
|
1133
|
+
const e = (performance.now() - i) / ft;
|
|
1134
|
+
return e >= 1 ? (this.#V[t] = null, null) : 1 - L(Math.max(0, e));
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Swap the painter used for a side's `loading` spinner — the same
|
|
1138
|
+
* pluggable-function contract as the series intro animations. `null`
|
|
1139
|
+
* (default) falls back to the built-in three-dot spinner. Takes effect
|
|
1140
|
+
* immediately if that side is currently in the `loading` state.
|
|
1141
|
+
*/
|
|
1142
|
+
setEdgeIndicator(t, i) {
|
|
1143
|
+
this.#nt[t] = i, this.#S[t] === "loading" && this.#h.markDirty();
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Resolve the boundary point's Y position and series kind on the primary
|
|
1147
|
+
* visible time series, for a custom edge indicator that wants to start
|
|
1148
|
+
* exactly where the real chart leaves off and match its shape (candle vs.
|
|
1149
|
+
* plain bar/line). `left` continues from the first visible candle's
|
|
1150
|
+
* `open` (the value immediately preceding the boundary); `right` from the
|
|
1151
|
+
* last visible candle's `close`.
|
|
1152
|
+
*/
|
|
1153
|
+
#Dt(t, i) {
|
|
1154
|
+
const e = this.getSeriesIdsByType("time", { visibleOnly: !0, singleLayerOnly: !0 })[0] ?? this.getSeriesIdsByType("time", { visibleOnly: !0 })[0];
|
|
1155
|
+
if (!e) return null;
|
|
1156
|
+
const s = this.getDataAtTime(e, i);
|
|
1157
|
+
if (!s) return null;
|
|
1158
|
+
const r = this.#t.find((n) => n.id === e);
|
|
1159
|
+
return r ? {
|
|
1160
|
+
valueY: this.yScale.valueToY(O(t, s)),
|
|
1161
|
+
seriesKind: r.renderer.kind
|
|
1162
|
+
} : null;
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Resolve the media-pixel spacing between adjacent bars at the chart's
|
|
1166
|
+
* current zoom level, for a custom edge indicator that wants to size its
|
|
1167
|
+
* placeholder shapes to match the real bars that will render once data
|
|
1168
|
+
* lands. `null` when the viewport has no meaningful span yet.
|
|
1169
|
+
*/
|
|
1170
|
+
#Lt() {
|
|
1171
|
+
const t = this.timeScale.barWidthMedia(this.#r);
|
|
1172
|
+
return Number.isFinite(t) && t > 0 ? t : null;
|
|
972
1173
|
}
|
|
973
1174
|
/**
|
|
974
1175
|
* Notify chart that a YLabel overlay is mounted / unmounted. Currently a
|
|
@@ -976,33 +1177,95 @@ class Ot extends st {
|
|
|
976
1177
|
* chart area to make room for the badge.
|
|
977
1178
|
* TODO: implement reflow.
|
|
978
1179
|
*/
|
|
979
|
-
setYLabel(
|
|
1180
|
+
setYLabel(t) {
|
|
980
1181
|
}
|
|
981
1182
|
/**
|
|
982
1183
|
* Dispatch a crosshair move to every renderer that supports spatial hover
|
|
983
|
-
* (
|
|
1184
|
+
* (pie, heatmap — anything implementing hitTest/setHoverIndex). Any change
|
|
1185
|
+
* in hover index schedules a main-layer redraw. Keep this loop capability-
|
|
1186
|
+
* probed, not kind-gated: a kind check here silently kills hover for the
|
|
1187
|
+
* next spatial renderer.
|
|
984
1188
|
*/
|
|
985
|
-
#
|
|
986
|
-
const
|
|
987
|
-
top: e.top *
|
|
988
|
-
bottom: e.bottom *
|
|
1189
|
+
#G(t) {
|
|
1190
|
+
const i = this.#a.size, e = this.#c, s = {
|
|
1191
|
+
top: e.top * i.verticalPixelRatio,
|
|
1192
|
+
bottom: e.bottom * i.verticalPixelRatio
|
|
989
1193
|
};
|
|
990
|
-
let r = !1;
|
|
991
|
-
for (const
|
|
992
|
-
if (!
|
|
993
|
-
let
|
|
994
|
-
if (
|
|
995
|
-
const
|
|
996
|
-
|
|
1194
|
+
let r = !1, n = null;
|
|
1195
|
+
for (const h of this.#t) {
|
|
1196
|
+
if (!h.renderer.hitTest || !h.renderer.setHoverIndex) continue;
|
|
1197
|
+
let a = -1;
|
|
1198
|
+
if (t) {
|
|
1199
|
+
const l = t.mediaX * i.horizontalPixelRatio, o = t.mediaY * i.verticalPixelRatio;
|
|
1200
|
+
a = h.renderer.hitTest(l, o, i.bitmap.width, i.bitmap.height, s);
|
|
1201
|
+
}
|
|
1202
|
+
h.renderer.setHoverIndex(a) && (r = !0), a >= 0 && !n && (n = { seriesId: h.id, index: a, info: h.renderer.getHoverInfo?.(this.#n) ?? null });
|
|
1203
|
+
}
|
|
1204
|
+
r && (this.#e.markDirty(), this.emit("seriesHover", n));
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Read-only counterpart to {@link #updateHover} for `pointClick` — same
|
|
1208
|
+
* capability-probed `hitTest` scan, but doesn't touch `setHoverIndex` (a
|
|
1209
|
+
* click shouldn't move the hover state the last pointer move already set).
|
|
1210
|
+
* Returns the first spatial series (pie, heatmap, or a custom spatial kind)
|
|
1211
|
+
* whose `hitTest` claims the position, or `null` if none does.
|
|
1212
|
+
*/
|
|
1213
|
+
#ft(t) {
|
|
1214
|
+
const i = this.#a.size, e = this.#c, s = {
|
|
1215
|
+
top: e.top * i.verticalPixelRatio,
|
|
1216
|
+
bottom: e.bottom * i.verticalPixelRatio
|
|
1217
|
+
}, r = t.mediaX * i.horizontalPixelRatio, n = t.mediaY * i.verticalPixelRatio;
|
|
1218
|
+
for (const h of this.#t) {
|
|
1219
|
+
if (!h.renderer.hitTest) continue;
|
|
1220
|
+
const a = h.renderer.hitTest(r, n, i.bitmap.width, i.bitmap.height, s);
|
|
1221
|
+
if (a >= 0) return { seriesId: h.id, index: a };
|
|
1222
|
+
}
|
|
1223
|
+
return null;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Re-derive the crosshair after the viewport moved beneath it. A streaming
|
|
1227
|
+
* auto-scroll, wheel zoom, or animated Y re-fit shifts the pixel↔data
|
|
1228
|
+
* mapping while the cursor stays put — without this, the stored position
|
|
1229
|
+
* goes stale: the tooltip freezes on the old time/values while the data
|
|
1230
|
+
* (and any snap dots anchored to it) slides away underneath.
|
|
1231
|
+
*
|
|
1232
|
+
* Which half of the position is truth depends on {@link #crosshairAnchor}:
|
|
1233
|
+
* a pointer-anchored crosshair stays under the cursor and re-resolves
|
|
1234
|
+
* `time`/`y`; a time-anchored one keeps `time`/`y` and re-projects the
|
|
1235
|
+
* pixel position so it tracks its data point across the screen. A
|
|
1236
|
+
* time-anchored point whose time scrolls out of the visible range clears
|
|
1237
|
+
* the crosshair entirely (mirroring pointer-leave) — otherwise the
|
|
1238
|
+
* edge-clamped tooltip would pin to the chart border, live-updating for
|
|
1239
|
+
* an invisible point.
|
|
1240
|
+
*
|
|
1241
|
+
* Called once per painted main frame (after `syncScales`). When the
|
|
1242
|
+
* viewport didn't move, the re-derived values are bit-identical and the
|
|
1243
|
+
* method exits without emitting.
|
|
1244
|
+
*/
|
|
1245
|
+
#Ct() {
|
|
1246
|
+
const t = this.#d;
|
|
1247
|
+
if (t === null) return;
|
|
1248
|
+
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, h = 1e-6;
|
|
1249
|
+
let a;
|
|
1250
|
+
if (this.#Y === "pointer") {
|
|
1251
|
+
const l = this.timeScale.xToTime(t.mediaX), o = this.yScale.yToValue(t.mediaY);
|
|
1252
|
+
if (!Number.isFinite(l) || !Number.isFinite(o) || Math.abs(l - t.time) <= r && Math.abs(o - t.y) <= n) return;
|
|
1253
|
+
a = { mediaX: t.mediaX, mediaY: t.mediaY, time: l, y: o };
|
|
1254
|
+
} else {
|
|
1255
|
+
if (t.time < i || t.time > e) {
|
|
1256
|
+
this.#d = null, this.#h.markDirty(), this.emit("crosshairMove", null), this.#G(null);
|
|
1257
|
+
return;
|
|
997
1258
|
}
|
|
998
|
-
|
|
1259
|
+
const l = this.timeScale.timeToX(t.time), o = this.yScale.valueToY(t.y);
|
|
1260
|
+
if (!Number.isFinite(l) || !Number.isFinite(o) || Math.abs(l - t.mediaX) <= h && Math.abs(o - t.mediaY) <= h) return;
|
|
1261
|
+
a = { mediaX: l, mediaY: o, time: t.time, y: t.y };
|
|
999
1262
|
}
|
|
1000
|
-
|
|
1263
|
+
this.#d = a, this.#h.markDirty(), this.emit("crosshairMove", a), this.#G(a);
|
|
1001
1264
|
}
|
|
1002
1265
|
/** Tear down the chart: cancel animations, remove listeners, and detach the canvas. */
|
|
1003
1266
|
destroy() {
|
|
1004
|
-
for (const
|
|
1005
|
-
this.#t = [], this.#
|
|
1267
|
+
for (const t of this.#t) t.renderer.dispose();
|
|
1268
|
+
this.#t = [], this.#m = [], this.#b = [], this.#y = [], this.#T = null, this.#e.destroy(), this.#h.destroy(), this.#C?.destroy(), this.#a.destroy(), this.#U?.destroy(), this.#U = null, this.#_ && this.#x?.destroy(), this.#x = null, this.#_ = !1, this.removeAllListeners();
|
|
1006
1269
|
}
|
|
1007
1270
|
/** The attached performance monitor, or `null` when instrumentation is disabled. */
|
|
1008
1271
|
getPerfMonitor() {
|
|
@@ -1010,39 +1273,39 @@ class Ot extends st {
|
|
|
1010
1273
|
}
|
|
1011
1274
|
/** Compute the earliest and latest timestamps across all series. */
|
|
1012
1275
|
getDataBounds() {
|
|
1013
|
-
let
|
|
1276
|
+
let t, i;
|
|
1014
1277
|
for (const e of this.#t) {
|
|
1015
|
-
if (!e.visible || e.renderer
|
|
1278
|
+
if (!e.visible || !S(e.renderer)) continue;
|
|
1016
1279
|
const s = e.renderer.getTimeBounds();
|
|
1017
|
-
s !== null && ((
|
|
1280
|
+
s !== null && ((t === void 0 || s.first < t) && (t = s.first), (i === void 0 || s.last > i) && (i = s.last));
|
|
1018
1281
|
}
|
|
1019
|
-
return { first:
|
|
1282
|
+
return { first: t, last: i };
|
|
1020
1283
|
}
|
|
1021
1284
|
/** Set by `setSeriesData`/bulk-replace paths so the next `onDataChanged`
|
|
1022
1285
|
* snaps the Y range (instead of easing) and `yScale.getRange()` reflects
|
|
1023
1286
|
* the new domain synchronously. Cleared by `onDataChanged` after each run. */
|
|
1024
|
-
#
|
|
1287
|
+
#q = !1;
|
|
1025
1288
|
onDataChanged() {
|
|
1026
|
-
if (this.#
|
|
1027
|
-
this
|
|
1289
|
+
if (this.#A > 0) {
|
|
1290
|
+
this.#X = !0;
|
|
1028
1291
|
return;
|
|
1029
1292
|
}
|
|
1030
1293
|
this.updateDataInterval();
|
|
1031
|
-
const { first:
|
|
1032
|
-
|
|
1033
|
-
const e = this.#
|
|
1034
|
-
this.#
|
|
1035
|
-
const s = !this.#
|
|
1036
|
-
e && (this.yScale.tickTracker.reset(), this.timeScale.tickTracker.reset()), this.#
|
|
1294
|
+
const { first: t, last: i } = this.getDataBounds();
|
|
1295
|
+
t !== void 0 && (this.#k = t), i !== void 0 && (this.#z = this.#i, this.#i = i);
|
|
1296
|
+
const e = this.#q;
|
|
1297
|
+
this.#q = !1;
|
|
1298
|
+
const s = !this.#f && t !== void 0;
|
|
1299
|
+
e && (this.yScale.tickTracker.reset(), this.timeScale.tickTracker.reset()), this.#ht = e;
|
|
1037
1300
|
const r = performance.now();
|
|
1038
|
-
e || s ? (this.#D.seed(r), this.#
|
|
1301
|
+
e || s ? (this.#D.seed(r), this.#s.onDataReplaced(r)) : this.#s.onPointAppended(r), this.#s.lastXTarget !== null && (this.#I = !0), t !== void 0 && (this.#f = !0), this.#v(r), this.syncScales(), s ? this.#J() : this.#e.markDirty(), this.emit("dataUpdate"), this.#p();
|
|
1039
1302
|
}
|
|
1040
1303
|
updateDataInterval() {
|
|
1041
|
-
for (const
|
|
1042
|
-
if (
|
|
1043
|
-
const
|
|
1044
|
-
if (
|
|
1045
|
-
this.#
|
|
1304
|
+
for (const t of this.#t) {
|
|
1305
|
+
if (!S(t.renderer)) continue;
|
|
1306
|
+
const i = t.renderer.sampleTimes(20);
|
|
1307
|
+
if (i.length >= 2) {
|
|
1308
|
+
this.#r = ut(i);
|
|
1046
1309
|
break;
|
|
1047
1310
|
}
|
|
1048
1311
|
}
|
|
@@ -1052,28 +1315,28 @@ class Ot extends st {
|
|
|
1052
1315
|
* data regardless of the caller's requested kind. Also reset by paths
|
|
1053
1316
|
* that intentionally invalidate the current Y baseline (axis bound
|
|
1054
1317
|
* change, empty viewport). */
|
|
1055
|
-
#
|
|
1318
|
+
#f = !1;
|
|
1056
1319
|
/** Whether the engine's X slot has received its first emit. Pre-init X
|
|
1057
1320
|
* emits are forced to `instant` so first paint snaps to the fitted
|
|
1058
1321
|
* range — without this guard the engine would ease from `{0,0}` into
|
|
1059
1322
|
* the new target and the very first frame would render with a
|
|
1060
1323
|
* zero-width X domain. */
|
|
1061
|
-
#
|
|
1324
|
+
#I = !1;
|
|
1062
1325
|
/**
|
|
1063
1326
|
* One-shot `setVisibleRange` argument applied on the first data arrival.
|
|
1064
1327
|
* Captured at chart construction from {@link ChartOptions.viewport.initialRange};
|
|
1065
1328
|
* cleared the moment it's consumed so subsequent data swaps don't snap
|
|
1066
1329
|
* back to the initial window.
|
|
1067
1330
|
*/
|
|
1068
|
-
#
|
|
1331
|
+
#N;
|
|
1069
1332
|
/** Last Y bounds the engine resolved into {@link AnimationState.yRange}.
|
|
1070
1333
|
* Used by {@link renderMain} to detect Y movement frame-to-frame so
|
|
1071
1334
|
* `viewportChange` only emits when the Y range actually shifted — DOM
|
|
1072
1335
|
* axis labels subscribe to that event and re-renders on every tick are
|
|
1073
1336
|
* expensive. Initialised to NaN so the first real Y emit always passes
|
|
1074
1337
|
* the change check. */
|
|
1075
|
-
#
|
|
1076
|
-
#
|
|
1338
|
+
#K = Number.NaN;
|
|
1339
|
+
#j = Number.NaN;
|
|
1077
1340
|
/**
|
|
1078
1341
|
* Sample the visible data window and return the resolved Y bounds, or
|
|
1079
1342
|
* `null` when no series has data inside the X destination range.
|
|
@@ -1082,11 +1345,11 @@ class Ot extends st {
|
|
|
1082
1345
|
* converge together. Clears {@link #yInited} on empty so the next emit
|
|
1083
1346
|
* with valid data lands as an instant snap.
|
|
1084
1347
|
*/
|
|
1085
|
-
#
|
|
1086
|
-
const
|
|
1348
|
+
#gt(t) {
|
|
1349
|
+
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 = Q(i, this.#t, e);
|
|
1087
1350
|
if (s === null)
|
|
1088
|
-
return this.#
|
|
1089
|
-
const r =
|
|
1351
|
+
return this.#f = !1, null;
|
|
1352
|
+
const r = I(this.#l.min, s.min, s.max, e ?? [], "min"), n = I(this.#l.max, s.max, s.min, e ?? [], "max");
|
|
1090
1353
|
return { min: r, max: n };
|
|
1091
1354
|
}
|
|
1092
1355
|
/**
|
|
@@ -1107,24 +1370,29 @@ class Ot extends st {
|
|
|
1107
1370
|
* - **autoscroll off** → `null` (no X retarget; user has panned away
|
|
1108
1371
|
* from the tail).
|
|
1109
1372
|
*/
|
|
1110
|
-
#
|
|
1111
|
-
if (this.#k === null || this.#
|
|
1112
|
-
const { from:
|
|
1113
|
-
if (e)
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1373
|
+
#Rt() {
|
|
1374
|
+
if (this.#k === null || this.#i === null) return null;
|
|
1375
|
+
const { from: t, to: i } = this.#o, e = t === 0 && i === 0, s = this.#a.size.media.width - this.yAxisWidth;
|
|
1376
|
+
if (e) {
|
|
1377
|
+
if (this.#P(this.#k, this.#i, s), this.#N !== void 0 && (this.setVisibleRange(this.#N), this.#N = void 0, !this.#u && this.#i !== null)) {
|
|
1378
|
+
const { to: n } = this.#o;
|
|
1379
|
+
this.#i >= n && this.#i - n <= this.#r * 1.5 && (this.#u = !0);
|
|
1380
|
+
}
|
|
1381
|
+
return this.#o;
|
|
1382
|
+
}
|
|
1383
|
+
if (!this.#u) return null;
|
|
1384
|
+
if (this.#ht)
|
|
1385
|
+
return this.#P(this.#k, this.#i, s), this.#o;
|
|
1386
|
+
const r = J({
|
|
1119
1387
|
currentLogical: this.#o,
|
|
1120
|
-
lastTime: this.#
|
|
1121
|
-
prevDataEnd: this.#
|
|
1122
|
-
dataInterval: this.#
|
|
1123
|
-
paddingRight: this.#
|
|
1388
|
+
lastTime: this.#i,
|
|
1389
|
+
prevDataEnd: this.#z,
|
|
1390
|
+
dataInterval: this.#r,
|
|
1391
|
+
paddingRight: this.#c.right,
|
|
1124
1392
|
chartWidth: s,
|
|
1125
|
-
holdUntilFilled: this.#
|
|
1393
|
+
holdUntilFilled: this.#w
|
|
1126
1394
|
});
|
|
1127
|
-
return r.releaseHold && (this.#
|
|
1395
|
+
return r.releaseHold && (this.#w = !1), r.reengageAutoScroll && (this.#u = !0), r.newLogical === null ? null : (this.#D.observe(performance.now()), this.#s.setXSettleMs(this.#D.pickSettleMs(this.#g.axis.x.settleMs)), this.#L(r.newLogical, { emitChange: !1, skipValidation: !0 }), this.#z = this.#i, r.newLogical);
|
|
1128
1396
|
}
|
|
1129
1397
|
/**
|
|
1130
1398
|
* Fit the viewport's logical X range around a data span. Mirrors the
|
|
@@ -1132,16 +1400,16 @@ class Ot extends st {
|
|
|
1132
1400
|
* forces autoscroll on (data tail is in the new window by construction),
|
|
1133
1401
|
* commits the new range via `viewport.setRange` (which emits `change`).
|
|
1134
1402
|
*/
|
|
1135
|
-
#
|
|
1136
|
-
const s = this.#
|
|
1137
|
-
firstTime:
|
|
1138
|
-
lastTime:
|
|
1139
|
-
dataInterval: this.#
|
|
1140
|
-
maxVisibleBars: this.#
|
|
1403
|
+
#P(t, i, e) {
|
|
1404
|
+
const s = this.#c, r = X({
|
|
1405
|
+
firstTime: t,
|
|
1406
|
+
lastTime: i,
|
|
1407
|
+
dataInterval: this.#r,
|
|
1408
|
+
maxVisibleBars: this.#ot,
|
|
1141
1409
|
chartWidth: e,
|
|
1142
1410
|
padding: { left: s.left, right: s.right }
|
|
1143
1411
|
});
|
|
1144
|
-
this.#
|
|
1412
|
+
this.#w = !1, this.#u = !0, this.#L(r, { emitChange: !0 });
|
|
1145
1413
|
}
|
|
1146
1414
|
/**
|
|
1147
1415
|
* Commit a programmatic zoom — `fitContent` or `setVisibleRange`. Reads
|
|
@@ -1157,15 +1425,15 @@ class Ot extends st {
|
|
|
1157
1425
|
* passes `xEase: true` so X eases instead — viewport and axis converge
|
|
1158
1426
|
* on the same frame.
|
|
1159
1427
|
*/
|
|
1160
|
-
#
|
|
1161
|
-
const
|
|
1162
|
-
if (
|
|
1428
|
+
#W(t) {
|
|
1429
|
+
const i = this.#o;
|
|
1430
|
+
if (i.to <= i.from) return;
|
|
1163
1431
|
const e = performance.now();
|
|
1164
|
-
if (!this.#
|
|
1165
|
-
this.#
|
|
1432
|
+
if (!this.#I || !this.#f) {
|
|
1433
|
+
this.#pt(e);
|
|
1166
1434
|
return;
|
|
1167
1435
|
}
|
|
1168
|
-
this.#
|
|
1436
|
+
this.#s.onProgrammaticZoom({ xTarget: i, xEase: t?.xEase }, e), this.#v(e);
|
|
1169
1437
|
}
|
|
1170
1438
|
/**
|
|
1171
1439
|
* Commit the current logical X to the engine on the *gesture* profile:
|
|
@@ -1179,15 +1447,15 @@ class Ot extends st {
|
|
|
1179
1447
|
* instead of lagging on the long sticky contract. First paint still snaps
|
|
1180
1448
|
* (same as the programmatic path) so synchronous readers see final values.
|
|
1181
1449
|
*/
|
|
1182
|
-
#
|
|
1183
|
-
const
|
|
1184
|
-
if (
|
|
1185
|
-
const
|
|
1186
|
-
if (!this.#
|
|
1187
|
-
this.#
|
|
1450
|
+
#Mt() {
|
|
1451
|
+
const t = this.#o;
|
|
1452
|
+
if (t.to <= t.from) return;
|
|
1453
|
+
const i = performance.now();
|
|
1454
|
+
if (!this.#I || !this.#f) {
|
|
1455
|
+
this.#pt(i);
|
|
1188
1456
|
return;
|
|
1189
1457
|
}
|
|
1190
|
-
this.#
|
|
1458
|
+
this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#v(i);
|
|
1191
1459
|
}
|
|
1192
1460
|
/**
|
|
1193
1461
|
* First-paint snap: land both axes on the current logical X immediately
|
|
@@ -1196,9 +1464,9 @@ class Ot extends st {
|
|
|
1196
1464
|
* commits — both must snap before the chart has painted once so a
|
|
1197
1465
|
* synchronous `getYRange()` / `getVisibleRange()` read sees final values.
|
|
1198
1466
|
*/
|
|
1199
|
-
#
|
|
1200
|
-
const
|
|
1201
|
-
this.#
|
|
1467
|
+
#pt(t) {
|
|
1468
|
+
const i = this.#o, e = this.#gt(i);
|
|
1469
|
+
this.#I = !0, e !== null && (this.#f = !0), this.#s.snap({ x: i, y: e ?? void 0 }, t), this.#v(t);
|
|
1202
1470
|
}
|
|
1203
1471
|
/**
|
|
1204
1472
|
* Validate and commit a new logical X range. Mirrors the old
|
|
@@ -1218,9 +1486,9 @@ class Ot extends st {
|
|
|
1218
1486
|
* see `setVisibleRange`'s gesture path, where a per-frame echo would
|
|
1219
1487
|
* otherwise re-arm and stall the in-flight Y ease.
|
|
1220
1488
|
*/
|
|
1221
|
-
#
|
|
1222
|
-
const { from: e, to: s } =
|
|
1223
|
-
return !Number.isFinite(e) || !Number.isFinite(s) || s <= e || !
|
|
1489
|
+
#L(t, i) {
|
|
1490
|
+
const { from: e, to: s } = t;
|
|
1491
|
+
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);
|
|
1224
1492
|
}
|
|
1225
1493
|
/**
|
|
1226
1494
|
* Apply pixel-padding to the engine's raw Y target and store the
|
|
@@ -1228,11 +1496,11 @@ class Ot extends st {
|
|
|
1228
1496
|
* symmetric pad top / bottom from `#padding`, suppressed on the side
|
|
1229
1497
|
* that has an explicit (fixed) axis bound.
|
|
1230
1498
|
*/
|
|
1231
|
-
#
|
|
1232
|
-
const n =
|
|
1233
|
-
this.#
|
|
1234
|
-
min: s ?
|
|
1235
|
-
max: r ?
|
|
1499
|
+
#yt(t, i, e, s, r) {
|
|
1500
|
+
const n = i - t, h = e > 0 ? this.#c.top / e * n : 0, a = e > 0 ? this.#c.bottom / e * n : 0;
|
|
1501
|
+
this.#F = {
|
|
1502
|
+
min: s ? t : t - a,
|
|
1503
|
+
max: r ? i : i + h
|
|
1236
1504
|
};
|
|
1237
1505
|
}
|
|
1238
1506
|
/**
|
|
@@ -1245,18 +1513,18 @@ class Ot extends st {
|
|
|
1245
1513
|
* as a gesture for visual easing. Public so consumers can drive pan
|
|
1246
1514
|
* programmatically.
|
|
1247
1515
|
*/
|
|
1248
|
-
pan(
|
|
1249
|
-
|
|
1250
|
-
const e =
|
|
1516
|
+
pan(t, i = this.#E) {
|
|
1517
|
+
i > 0 && (this.#E = i), this.#w = !1;
|
|
1518
|
+
const e = q({
|
|
1251
1519
|
currentLogical: this.#o,
|
|
1252
|
-
timeDelta:
|
|
1253
|
-
chartWidth:
|
|
1254
|
-
dataInterval: this.#
|
|
1255
|
-
padding: { left: this.#
|
|
1520
|
+
timeDelta: t,
|
|
1521
|
+
chartWidth: i,
|
|
1522
|
+
dataInterval: this.#r,
|
|
1523
|
+
padding: { left: this.#c.left, right: this.#c.right },
|
|
1256
1524
|
dataStart: this.#k,
|
|
1257
|
-
dataEnd: this.#
|
|
1525
|
+
dataEnd: this.#i
|
|
1258
1526
|
});
|
|
1259
|
-
e.newLogical !== null && (this.#
|
|
1527
|
+
e.newLogical !== null && (this.#u = !e.autoScrollOff, this.#L(e.newLogical, { emitChange: !0 }), e.edgeReached !== null && (this.#st[e.edgeReached.side] = e.edgeReached.boundaryTime, this.#rt?.(e.edgeReached)), this.#D.pause(), this.#vt());
|
|
1260
1528
|
}
|
|
1261
1529
|
/**
|
|
1262
1530
|
* Zoom around a time anchor. `factor < 1` zooms in, `> 1` zooms out.
|
|
@@ -1273,37 +1541,37 @@ class Ot extends st {
|
|
|
1273
1541
|
* and produce a visible jump). Cursor-anchored zoom is intentionally
|
|
1274
1542
|
* sacrificed in follow-live mode — pan first to inspect history.
|
|
1275
1543
|
*/
|
|
1276
|
-
zoomAt(
|
|
1277
|
-
e > 0 && (this.#
|
|
1278
|
-
const s =
|
|
1544
|
+
zoomAt(t, i, e = this.#E) {
|
|
1545
|
+
e > 0 && (this.#E = e), this.#w = !1;
|
|
1546
|
+
const s = K({
|
|
1279
1547
|
currentLogical: this.#o,
|
|
1280
|
-
centerTime:
|
|
1281
|
-
factor:
|
|
1548
|
+
centerTime: t,
|
|
1549
|
+
factor: i,
|
|
1282
1550
|
chartWidth: e,
|
|
1283
|
-
dataInterval: this.#
|
|
1284
|
-
padding: { left: this.#
|
|
1551
|
+
dataInterval: this.#r,
|
|
1552
|
+
padding: { left: this.#c.left, right: this.#c.right },
|
|
1285
1553
|
dataStart: this.#k,
|
|
1286
|
-
dataEnd: this.#
|
|
1554
|
+
dataEnd: this.#i
|
|
1287
1555
|
});
|
|
1288
1556
|
if (s.newLogical === null) return;
|
|
1289
1557
|
let r = s.newLogical;
|
|
1290
|
-
if (this.#
|
|
1291
|
-
const n = r.to - r.from, h =
|
|
1292
|
-
r = { from: a - n, to: a }, this.#
|
|
1558
|
+
if (this.#u && this.#i !== null) {
|
|
1559
|
+
const n = r.to - r.from, h = M(this.#c.right, n, this.#r, e), a = this.#i + h;
|
|
1560
|
+
r = { from: a - n, to: a }, this.#z = this.#i;
|
|
1293
1561
|
}
|
|
1294
|
-
this.#
|
|
1562
|
+
this.#L(r, { emitChange: !0 }), this.#D.pause(), this.#vt();
|
|
1295
1563
|
}
|
|
1296
1564
|
/**
|
|
1297
1565
|
* Common tail for `pan` / `zoomAt`: routes the just-committed `#logical`
|
|
1298
1566
|
* to the engine as a gesture so the X spring retargets and the Y spring
|
|
1299
1567
|
* eases over `animations.axis.y.gesture`.
|
|
1300
1568
|
*/
|
|
1301
|
-
#
|
|
1302
|
-
const
|
|
1303
|
-
if (
|
|
1304
|
-
this.#
|
|
1305
|
-
const
|
|
1306
|
-
this.#
|
|
1569
|
+
#vt() {
|
|
1570
|
+
const t = this.#o;
|
|
1571
|
+
if (t.to <= t.from) return;
|
|
1572
|
+
this.#I = !0, this.#f = !0;
|
|
1573
|
+
const i = performance.now();
|
|
1574
|
+
this.#s.onPanZoom({ xTarget: t, yAuto: !0 }, i), this.#v(i);
|
|
1307
1575
|
}
|
|
1308
1576
|
/**
|
|
1309
1577
|
* Tick the engine at the given wall clock and push the resolved X / Y
|
|
@@ -1318,11 +1586,11 @@ class Ot extends st {
|
|
|
1318
1586
|
* `effectiveNow === startWall` and the prune step keeps zero-duration
|
|
1319
1587
|
* events long enough for the slot processor to snap them.
|
|
1320
1588
|
*/
|
|
1321
|
-
#
|
|
1322
|
-
const
|
|
1589
|
+
#v(t) {
|
|
1590
|
+
const i = this.#s.tick(t), e = this.#a.size;
|
|
1323
1591
|
if (e.media.width === 0 || e.media.height === 0) return;
|
|
1324
|
-
const s = this.#
|
|
1325
|
-
this.#
|
|
1592
|
+
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;
|
|
1593
|
+
this.#yt(i.yRange.min, i.yRange.max, n, s, r), this.#K = i.yRange.min, this.#j = i.yRange.max, this.syncScales();
|
|
1326
1594
|
}
|
|
1327
1595
|
/**
|
|
1328
1596
|
* Lightweight scale sync: updates timeScale/yScale from current viewport state
|
|
@@ -1330,79 +1598,79 @@ class Ot extends st {
|
|
|
1330
1598
|
* handler, so DOM axis components always read fresh coordinates on re-render.
|
|
1331
1599
|
*/
|
|
1332
1600
|
syncScales() {
|
|
1333
|
-
const
|
|
1334
|
-
if (
|
|
1335
|
-
const
|
|
1601
|
+
const t = this.#a.size;
|
|
1602
|
+
if (t.media.width === 0 || t.media.height === 0) return;
|
|
1603
|
+
const i = t.media.width - this.yAxisWidth, e = t.media.height - this.xAxisHeight;
|
|
1336
1604
|
this.timeScale.update(
|
|
1337
|
-
this.#
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
this.#
|
|
1341
|
-
), this.yScale.update(this.#
|
|
1605
|
+
this.#s.getAnimationState().xRange,
|
|
1606
|
+
i,
|
|
1607
|
+
t.horizontalPixelRatio,
|
|
1608
|
+
this.#r
|
|
1609
|
+
), this.yScale.update(this.#F, e, t.verticalPixelRatio);
|
|
1342
1610
|
}
|
|
1343
1611
|
/** Expensive: background, grid, all series. Only on data/viewport/resize change. */
|
|
1344
|
-
renderMain(
|
|
1345
|
-
const
|
|
1346
|
-
if (
|
|
1347
|
-
const e = typeof
|
|
1348
|
-
s.animating && this.#
|
|
1349
|
-
const r = s.yRange.min, n = s.yRange.max, h = r !== this.#
|
|
1612
|
+
renderMain(t) {
|
|
1613
|
+
const i = this.#a.size;
|
|
1614
|
+
if (i.media.width === 0 || i.media.height === 0) return;
|
|
1615
|
+
const e = typeof t == "number" ? t : performance.now(), s = this.#s.tick(e);
|
|
1616
|
+
s.animating && this.#e.markDirty();
|
|
1617
|
+
const r = s.yRange.min, n = s.yRange.max, h = r !== this.#K || n !== this.#j;
|
|
1350
1618
|
if (h) {
|
|
1351
|
-
const
|
|
1352
|
-
this.#
|
|
1619
|
+
const d = this.#l.min !== void 0 && this.#l.min !== "auto", c = this.#l.max !== void 0 && this.#l.max !== "auto", p = i.media.height - this.xAxisHeight;
|
|
1620
|
+
this.#yt(r, n, p, d, c), this.#K = r, this.#j = n;
|
|
1353
1621
|
}
|
|
1354
|
-
if (this.syncScales(), this.#
|
|
1355
|
-
const
|
|
1356
|
-
if (
|
|
1357
|
-
const
|
|
1358
|
-
Math.abs(
|
|
1622
|
+
if (this.syncScales(), this.#Ct(), this.#i !== null && !this.#u) {
|
|
1623
|
+
const d = this.#s.lastXTarget ?? this.#o, c = d.to - d.from;
|
|
1624
|
+
if (c > 0) {
|
|
1625
|
+
const p = this.#a.size.media.width - this.yAxisWidth, v = M(this.#c.right, c, this.#r, p), m = this.#r * 0.5;
|
|
1626
|
+
Math.abs(d.to - (this.#i + v)) < m && (this.#u = !0);
|
|
1359
1627
|
}
|
|
1360
1628
|
}
|
|
1361
1629
|
h && this.emit("viewportChange");
|
|
1362
|
-
let a = !1,
|
|
1363
|
-
this.#
|
|
1364
|
-
const { context:
|
|
1365
|
-
|
|
1366
|
-
const
|
|
1367
|
-
a =
|
|
1368
|
-
scope:
|
|
1630
|
+
let a = !1, l = !1;
|
|
1631
|
+
this.#a.useMainLayer((d) => {
|
|
1632
|
+
const { context: c, bitmapSize: p } = d, v = (i.media.width - this.yAxisWidth) * i.horizontalPixelRatio, m = (i.media.height - this.xAxisHeight) * i.verticalPixelRatio;
|
|
1633
|
+
c.clearRect(0, 0, p.width, p.height), c.save(), c.beginPath(), c.rect(0, 0, v, m), c.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);
|
|
1634
|
+
const g = this.yScale.tickTracker.snapshot(), k = this.timeScale.tickTracker.snapshot();
|
|
1635
|
+
a = g.isAnimating, l = k.isAnimating, (g.isAnimating || k.isAnimating || s.animating) && this.emit("tickFrame"), this.#O && it({
|
|
1636
|
+
scope: d,
|
|
1369
1637
|
timeScale: this.timeScale,
|
|
1370
1638
|
yScale: this.yScale,
|
|
1371
|
-
theme: this.#
|
|
1372
|
-
yTicks:
|
|
1373
|
-
timeTicks:
|
|
1374
|
-
}), this.#
|
|
1375
|
-
const
|
|
1376
|
-
for (const
|
|
1377
|
-
const x =
|
|
1639
|
+
theme: this.#n,
|
|
1640
|
+
yTicks: g,
|
|
1641
|
+
timeTicks: k
|
|
1642
|
+
}), this.#At(d, v, m);
|
|
1643
|
+
const A = this.#c, V = { top: A.top, bottom: A.bottom }, w = this.#x;
|
|
1644
|
+
for (const b of this.#t) {
|
|
1645
|
+
const x = b.renderer.getAlpha?.() ?? (b.visible ? 1 : 0);
|
|
1378
1646
|
if (x <= 0) continue;
|
|
1379
|
-
const
|
|
1380
|
-
scope:
|
|
1647
|
+
const T = {
|
|
1648
|
+
scope: d,
|
|
1381
1649
|
timeScale: this.timeScale,
|
|
1382
1650
|
yScale: this.yScale,
|
|
1383
|
-
theme: this.#
|
|
1384
|
-
dataInterval: this.#
|
|
1385
|
-
padding:
|
|
1651
|
+
theme: this.#n,
|
|
1652
|
+
dataInterval: this.#r,
|
|
1653
|
+
padding: V,
|
|
1386
1654
|
state: s,
|
|
1387
|
-
seriesId:
|
|
1388
|
-
},
|
|
1389
|
-
if (x < 1 && (
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1655
|
+
seriesId: b.id
|
|
1656
|
+
}, D = c.globalAlpha;
|
|
1657
|
+
if (x < 1 && (c.globalAlpha = D * x), w) {
|
|
1658
|
+
const z = performance.now();
|
|
1659
|
+
b.renderer.render(T), w.recordSeries(b.id, performance.now() - z, e);
|
|
1392
1660
|
} else
|
|
1393
|
-
|
|
1394
|
-
x < 1 && (
|
|
1661
|
+
b.renderer.render(T);
|
|
1662
|
+
x < 1 && (c.globalAlpha = D);
|
|
1395
1663
|
}
|
|
1396
|
-
|
|
1397
|
-
});
|
|
1398
|
-
let
|
|
1399
|
-
for (const
|
|
1400
|
-
if (
|
|
1401
|
-
|
|
1664
|
+
c.restore();
|
|
1665
|
+
}), this.#$();
|
|
1666
|
+
let o = !1;
|
|
1667
|
+
for (const d of this.#t)
|
|
1668
|
+
if (d.renderer.needsAnimation) {
|
|
1669
|
+
o = !0, this.#e.markDirty();
|
|
1402
1670
|
break;
|
|
1403
1671
|
}
|
|
1404
|
-
const u = a ||
|
|
1405
|
-
!s.animating && !
|
|
1672
|
+
const u = a || l;
|
|
1673
|
+
!s.animating && !o && !u && (this.yScale.tickTracker.markArmed(), this.timeScale.tickTracker.markArmed());
|
|
1406
1674
|
}
|
|
1407
1675
|
/**
|
|
1408
1676
|
* Paint a full frame: the main layer, then the overlay synchronously on
|
|
@@ -1413,83 +1681,97 @@ class Ot extends st {
|
|
|
1413
1681
|
* `markDirty` calls made *during* `renderOverlay` (pulse, edge spinner)
|
|
1414
1682
|
* land after the consume and re-arm cleanly for the next frame.
|
|
1415
1683
|
*/
|
|
1416
|
-
#
|
|
1417
|
-
this.#
|
|
1684
|
+
#J(t) {
|
|
1685
|
+
this.#Q("main", t, () => this.renderMain(t)), this.#h.consume(), this.#Q("overlay", t, () => this.renderOverlay(t));
|
|
1418
1686
|
}
|
|
1419
1687
|
/** Run a layer's paint, booking duration + draw calls to the perf monitor
|
|
1420
1688
|
* when one is attached — keeps overlay time out of the `main` samples. */
|
|
1421
|
-
#
|
|
1689
|
+
#Q(t, i, e) {
|
|
1422
1690
|
const s = this.#x;
|
|
1423
1691
|
if (!s) {
|
|
1424
1692
|
e();
|
|
1425
1693
|
return;
|
|
1426
1694
|
}
|
|
1427
|
-
s.resetDrawCalls(
|
|
1695
|
+
s.resetDrawCalls(t);
|
|
1428
1696
|
const r = performance.now();
|
|
1429
|
-
e(), s.recordFrame(
|
|
1697
|
+
e(), s.recordFrame(t, performance.now() - r, i ?? r);
|
|
1430
1698
|
}
|
|
1431
1699
|
/** Cheap overlay: crosshair, nearest-point dots, pulse animation, edge indicator. */
|
|
1432
|
-
renderOverlay(
|
|
1433
|
-
const
|
|
1434
|
-
if (
|
|
1700
|
+
renderOverlay(t) {
|
|
1701
|
+
const i = this.#a.size;
|
|
1702
|
+
if (i.media.width === 0 || i.media.height === 0) return;
|
|
1435
1703
|
let e = !1;
|
|
1436
|
-
for (const
|
|
1437
|
-
if (
|
|
1704
|
+
for (const o of this.#t)
|
|
1705
|
+
if (o.visible && o.renderer.overlayNeedsAnimation) {
|
|
1438
1706
|
e = !0;
|
|
1439
1707
|
break;
|
|
1440
1708
|
}
|
|
1441
|
-
this.#
|
|
1442
|
-
const s =
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1709
|
+
this.#m.some((o) => o.pulse) && (e = !0);
|
|
1710
|
+
const s = {
|
|
1711
|
+
left: this.#R.left && this.#S.left === "loading" ? "idle" : this.#S.left,
|
|
1712
|
+
right: this.#R.right && this.#S.right === "loading" ? "idle" : this.#S.right
|
|
1713
|
+
}, r = {
|
|
1714
|
+
left: this.#ut("left"),
|
|
1715
|
+
right: this.#ut("right")
|
|
1716
|
+
}, n = r.left !== null || r.right !== null, h = s.left === "loading" || s.right === "loading" || n, a = s.left !== "idle" || s.right !== "idle" || n, l = !!this.#d || e || a || this.#m.length > 0 || this.#y.length > 0;
|
|
1717
|
+
if (!(!l && !this.#et)) {
|
|
1718
|
+
if (this.#et = l, this.#a.useOverlayLayer((o) => {
|
|
1719
|
+
if (!l) return;
|
|
1720
|
+
const u = (i.media.width - this.yAxisWidth) * i.horizontalPixelRatio, d = (i.media.height - this.xAxisHeight) * i.verticalPixelRatio;
|
|
1721
|
+
if (o.context.save(), o.context.beginPath(), o.context.rect(0, 0, u, d), o.context.clip(), this.#d && this.#Tt()) {
|
|
1722
|
+
const m = this.#d.mediaX * i.horizontalPixelRatio, g = this.#d.mediaY * i.verticalPixelRatio;
|
|
1723
|
+
tt(o, m, g, this.#n);
|
|
1450
1724
|
}
|
|
1451
|
-
const
|
|
1452
|
-
for (const
|
|
1453
|
-
|
|
1454
|
-
scope:
|
|
1725
|
+
const c = this.#c, p = { top: c.top, bottom: c.bottom }, v = this.#s.getAnimationState();
|
|
1726
|
+
for (const m of this.#t)
|
|
1727
|
+
m.visible && m.renderer.drawOverlay?.({
|
|
1728
|
+
scope: o,
|
|
1455
1729
|
timeScale: this.timeScale,
|
|
1456
1730
|
yScale: this.yScale,
|
|
1457
|
-
theme: this.#
|
|
1458
|
-
dataInterval: this.#
|
|
1459
|
-
padding:
|
|
1460
|
-
crosshair: this.#
|
|
1461
|
-
state:
|
|
1462
|
-
seriesId:
|
|
1731
|
+
theme: this.#n,
|
|
1732
|
+
dataInterval: this.#r,
|
|
1733
|
+
padding: p,
|
|
1734
|
+
crosshair: this.#d,
|
|
1735
|
+
state: v,
|
|
1736
|
+
seriesId: m.id
|
|
1463
1737
|
});
|
|
1464
|
-
this.#
|
|
1465
|
-
scope:
|
|
1466
|
-
|
|
1738
|
+
this.#wt(o, u, d), this.#kt(o, u, d), a && Y({
|
|
1739
|
+
scope: o,
|
|
1740
|
+
chartMediaWidth: i.media.width - this.yAxisWidth,
|
|
1741
|
+
chartMediaHeight: i.media.height - this.xAxisHeight,
|
|
1467
1742
|
timeScale: this.timeScale,
|
|
1468
|
-
theme: this.#
|
|
1469
|
-
edgeStates:
|
|
1470
|
-
|
|
1471
|
-
|
|
1743
|
+
theme: this.#n,
|
|
1744
|
+
edgeStates: s,
|
|
1745
|
+
edgeExitFades: r,
|
|
1746
|
+
edgeIndicatorFns: this.#nt,
|
|
1747
|
+
resolveBoundary: (m) => B(m, this.#st[m], this.getDataBounds()),
|
|
1748
|
+
resolveEdgeAnchor: (m, g) => this.#Dt(m, g),
|
|
1749
|
+
resolveEdgeBarSpacing: () => this.#Lt(),
|
|
1750
|
+
reportPlaceholders: (m, g) => {
|
|
1751
|
+
this.#B[m] = { bars: g, at: performance.now() };
|
|
1752
|
+
}
|
|
1753
|
+
}), o.context.restore();
|
|
1472
1754
|
}), e) {
|
|
1473
|
-
const { from:
|
|
1755
|
+
const { from: o, to: u } = this.timeScale.getRange();
|
|
1474
1756
|
let d = !1;
|
|
1475
|
-
for (const
|
|
1476
|
-
if (!(!
|
|
1757
|
+
for (const c of this.#t)
|
|
1758
|
+
if (!(!c.visible || !c.renderer.overlayNeedsAnimation) && (c.renderer.hasOverlayContentInRange?.(o, u) ?? !0)) {
|
|
1477
1759
|
d = !0;
|
|
1478
1760
|
break;
|
|
1479
1761
|
}
|
|
1480
1762
|
if (!d) {
|
|
1481
|
-
for (const
|
|
1482
|
-
if (
|
|
1763
|
+
for (const c of this.#m)
|
|
1764
|
+
if (c.pulse && c.time >= o && c.time <= u) {
|
|
1483
1765
|
d = !0;
|
|
1484
1766
|
break;
|
|
1485
1767
|
}
|
|
1486
1768
|
}
|
|
1487
|
-
d && this.#
|
|
1769
|
+
d && this.#h.markDirty();
|
|
1488
1770
|
}
|
|
1489
|
-
|
|
1771
|
+
h && this.#h.markDirty();
|
|
1490
1772
|
}
|
|
1491
1773
|
}
|
|
1492
1774
|
}
|
|
1493
1775
|
export {
|
|
1494
|
-
|
|
1776
|
+
qt as ChartInstance
|
|
1495
1777
|
};
|