react-native-livechart 4.4.0 → 4.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/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/components/LoadingOverlay.d.ts +9 -1
- package/dist/components/LoadingOverlay.d.ts.map +1 -1
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/liveChartEngineTick.d.ts +11 -0
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/liveChartSeriesEngineTick.d.ts +11 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +35 -1
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +13 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts +13 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/hooks/useChartPaths.d.ts +5 -1
- package/dist/hooks/useChartPaths.d.ts.map +1 -1
- package/dist/hooks/useChartReveal.d.ts +3 -1
- package/dist/hooks/useChartReveal.d.ts.map +1 -1
- package/dist/hooks/useModeBlend.d.ts +4 -1
- package/dist/hooks/useModeBlend.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/squiggly.d.ts +8 -5
- package/dist/math/squiggly.d.ts.map +1 -1
- package/dist/types.d.ts +110 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChart.tsx +128 -42
- package/src/components/LiveChartSeries.tsx +97 -26
- package/src/components/LoadingOverlay.tsx +25 -6
- package/src/constants.ts +17 -0
- package/src/core/liveChartEngineTick.ts +26 -10
- package/src/core/liveChartSeriesEngineTick.ts +22 -4
- package/src/core/resolveConfig.ts +63 -0
- package/src/core/useLiveChartEngine.ts +40 -1
- package/src/core/useLiveChartSeriesEngine.ts +35 -1
- package/src/hooks/useChartPaths.ts +11 -1
- package/src/hooks/useChartReveal.ts +7 -2
- package/src/hooks/useModeBlend.ts +5 -3
- package/src/index.ts +2 -0
- package/src/math/squiggly.ts +21 -7
- package/src/types.ts +112 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @see https://github.com/benjitaylor/liveline
|
|
7
7
|
*/
|
|
8
|
-
import { useEffect, useState } from "react";
|
|
8
|
+
import { useEffect, useRef, useState } from "react";
|
|
9
9
|
import {
|
|
10
10
|
cancelAnimation,
|
|
11
11
|
Easing,
|
|
@@ -62,6 +62,13 @@ export interface EngineConfig {
|
|
|
62
62
|
* {@link LiveChartProps.static}.
|
|
63
63
|
*/
|
|
64
64
|
static?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Opaque key that snaps the framing to its target in one frame whenever it
|
|
67
|
+
* changes (the next tick bypasses `smoothing` for the window, Y-range, and
|
|
68
|
+
* value, then normal easing resumes). Lets a timeframe / dataset switch land
|
|
69
|
+
* instantly while live ticks stay smooth. See {@link LiveChartProps.snapKey}.
|
|
70
|
+
*/
|
|
71
|
+
snapKey?: string | number;
|
|
65
72
|
mode?: "line" | "candle";
|
|
66
73
|
candles?: SharedValue<CandlePoint[]>;
|
|
67
74
|
liveCandle?: SharedValue<CandlePoint | null>;
|
|
@@ -193,6 +200,12 @@ export interface EngineFrameRefs {
|
|
|
193
200
|
liveEdgeSV?: SharedValue<number>;
|
|
194
201
|
/** Receives the smoothed right-edge value each frame. Optional for callers/tests. */
|
|
195
202
|
edgeValueSV?: SharedValue<number>;
|
|
203
|
+
/**
|
|
204
|
+
* One-shot "snap the framing this frame" flag (set by the `snapKey` effect).
|
|
205
|
+
* Consumed and cleared by {@link applyLiveChartEngineFrame} after the tick.
|
|
206
|
+
* Optional for callers/tests.
|
|
207
|
+
*/
|
|
208
|
+
snapSV?: SharedValue<boolean>;
|
|
196
209
|
modeSV: SharedValue<"line" | "candle">;
|
|
197
210
|
candles?: SharedValue<CandlePoint[]>;
|
|
198
211
|
liveCandle?: SharedValue<CandlePoint | null>;
|
|
@@ -212,6 +225,9 @@ export function applyLiveChartEngineFrame(
|
|
|
212
225
|
): void {
|
|
213
226
|
"worklet";
|
|
214
227
|
const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
|
|
228
|
+
// One-shot settle: a `snapKey` change set this flag; consume it for this tick
|
|
229
|
+
// and clear it below so only this frame snaps (live ticks stay smoothed).
|
|
230
|
+
const snap = sv.snapSV?.value ?? false;
|
|
215
231
|
const state = {
|
|
216
232
|
displayValue: sv.displayValue.value,
|
|
217
233
|
displayMin: sv.displayMin.value,
|
|
@@ -243,6 +259,7 @@ export function applyLiveChartEngineFrame(
|
|
|
243
259
|
points: sv.data.value,
|
|
244
260
|
nowSeconds: Date.now() / 1000,
|
|
245
261
|
paused: sv.pausedSV.value,
|
|
262
|
+
snap,
|
|
246
263
|
viewEnd: sv.viewEndSV?.value,
|
|
247
264
|
returnT: sv.returnTSV?.value,
|
|
248
265
|
returnFrom: sv.returnFromSV?.value,
|
|
@@ -262,6 +279,13 @@ export function applyLiveChartEngineFrame(
|
|
|
262
279
|
sv.extremaMaxValue.value = state.extremaMaxValue;
|
|
263
280
|
sv.extremaMinTime.value = state.extremaMinTime;
|
|
264
281
|
sv.extremaMaxTime.value = state.extremaMaxTime;
|
|
282
|
+
// Clear the one-shot snap so the next frame eases normally again — but only
|
|
283
|
+
// once a real (measured) frame consumed it. The tick early-returns on a
|
|
284
|
+
// zero-size canvas (before applying the snap), so keep the flag pending until
|
|
285
|
+
// the canvas has laid out, or a snap arriving pre-layout would be dropped.
|
|
286
|
+
if (snap && sv.snapSV && sv.canvasWidth.value !== 0 && sv.canvasHeight.value !== 0) {
|
|
287
|
+
sv.snapSV.value = false;
|
|
288
|
+
}
|
|
265
289
|
}
|
|
266
290
|
|
|
267
291
|
export function useLiveChartEngine(
|
|
@@ -352,6 +376,20 @@ export function useLiveChartEngine(
|
|
|
352
376
|
// no useDerivedValue bridging, no closure serialization per tick.
|
|
353
377
|
const { data, value, candles, liveCandle } = config;
|
|
354
378
|
|
|
379
|
+
// One-shot "snap the framing" flag. The effect below flips it to `true` when
|
|
380
|
+
// `snapKey` changes; the next frame consumes it (bypassing `smoothing` for the
|
|
381
|
+
// window / range / value) and clears it, so a timeframe / dataset switch lands
|
|
382
|
+
// in one frame while live ticks stay smoothed. See `snapKey`.
|
|
383
|
+
const snapSV = useSharedValue(false);
|
|
384
|
+
// Compare against the previous key (not a "first render" flag) so React 18
|
|
385
|
+
// StrictMode's double-invoked mount effect can't fire a spurious snap.
|
|
386
|
+
const lastSnapKey = useRef(config.snapKey);
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (config.snapKey === lastSnapKey.current) return;
|
|
389
|
+
lastSnapKey.current = config.snapKey;
|
|
390
|
+
snapSV.set(true);
|
|
391
|
+
}, [config.snapKey, snapSV]);
|
|
392
|
+
|
|
355
393
|
// Static charts run zero per-frame loops. `isStaticSV` gates both the
|
|
356
394
|
// frame-callback autostart and the one-shot settle reaction below.
|
|
357
395
|
const isStaticSV = useDerivedValue(() => config.static ?? false);
|
|
@@ -385,6 +423,7 @@ export function useLiveChartEngine(
|
|
|
385
423
|
viewWindowSV: viewWindow,
|
|
386
424
|
liveEdgeSV: liveEdge,
|
|
387
425
|
edgeValueSV: edgeValue,
|
|
426
|
+
snapSV,
|
|
388
427
|
modeSV,
|
|
389
428
|
candles,
|
|
390
429
|
liveCandle,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRef, useState } from "react";
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
2
|
import {
|
|
3
3
|
cancelAnimation,
|
|
4
4
|
Easing,
|
|
@@ -41,6 +41,13 @@ export interface MultiSeriesEngineConfig {
|
|
|
41
41
|
* {@link RETURN_TO_LIVE_MS}. Resolved from `timeScroll.returnToLive`. See #164.
|
|
42
42
|
*/
|
|
43
43
|
returnToLiveMs?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Opaque key that snaps the framing to its target in one frame whenever it
|
|
46
|
+
* changes (the next tick bypasses `smoothing` for the window, Y-range, and
|
|
47
|
+
* series tips, then normal easing resumes). Lets a timeframe / dataset switch
|
|
48
|
+
* land instantly while live ticks stay smooth. See {@link LiveChartSeriesProps.snapKey}.
|
|
49
|
+
*/
|
|
50
|
+
snapKey?: string | number;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export interface MultiEngineFrameRefs {
|
|
@@ -74,6 +81,12 @@ export interface MultiEngineFrameRefs {
|
|
|
74
81
|
viewWindowSV?: SharedValue<number | null>;
|
|
75
82
|
/** Receives the computed live edge each frame. Optional for callers/tests. */
|
|
76
83
|
liveEdgeSV?: SharedValue<number>;
|
|
84
|
+
/**
|
|
85
|
+
* One-shot "snap the framing this frame" flag (set by the `snapKey` effect).
|
|
86
|
+
* Consumed and cleared by {@link applyLiveChartSeriesEngineFrame} after the
|
|
87
|
+
* tick. Optional for callers/tests.
|
|
88
|
+
*/
|
|
89
|
+
snapSV?: SharedValue<boolean>;
|
|
77
90
|
extremaMinValue: SharedValue<number>;
|
|
78
91
|
extremaMaxValue: SharedValue<number>;
|
|
79
92
|
extremaMinTime: SharedValue<number>;
|
|
@@ -110,6 +123,9 @@ export function applyLiveChartSeriesEngineFrame(
|
|
|
110
123
|
): void {
|
|
111
124
|
"worklet";
|
|
112
125
|
const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
|
|
126
|
+
// One-shot settle: a `snapKey` change set this flag; consume it for this tick
|
|
127
|
+
// and clear it below so only this frame snaps (live ticks stay smoothed).
|
|
128
|
+
const snap = sv.snapSV?.value ?? false;
|
|
113
129
|
const seriesSnap = sv.series.value;
|
|
114
130
|
const curDv = sv.displaySeriesValues.value;
|
|
115
131
|
const curOp = sv.seriesOpacities.value;
|
|
@@ -157,6 +173,7 @@ export function applyLiveChartSeriesEngineFrame(
|
|
|
157
173
|
series: seriesSnap,
|
|
158
174
|
nowSeconds: Date.now() / 1000,
|
|
159
175
|
paused: sv.pausedSV.value,
|
|
176
|
+
snap,
|
|
160
177
|
viewEnd: sv.viewEndSV?.value,
|
|
161
178
|
returnT: sv.returnTSV?.value,
|
|
162
179
|
returnFrom: sv.returnFromSV?.value,
|
|
@@ -173,6 +190,12 @@ export function applyLiveChartSeriesEngineFrame(
|
|
|
173
190
|
sv.extremaMaxValue.value = state.extremaMaxValue;
|
|
174
191
|
sv.extremaMinTime.value = state.extremaMinTime;
|
|
175
192
|
sv.extremaMaxTime.value = state.extremaMaxTime;
|
|
193
|
+
// Clear the one-shot snap so the next frame eases normally again — but only
|
|
194
|
+
// once a real (measured) frame consumed it (the tick early-returns on a
|
|
195
|
+
// zero-size canvas before applying the snap). See applyLiveChartEngineFrame.
|
|
196
|
+
if (snap && sv.snapSV && sv.canvasWidth.value !== 0 && sv.canvasHeight.value !== 0) {
|
|
197
|
+
sv.snapSV.value = false;
|
|
198
|
+
}
|
|
176
199
|
}
|
|
177
200
|
|
|
178
201
|
/**
|
|
@@ -234,6 +257,16 @@ export function useLiveChartSeriesEngine(
|
|
|
234
257
|
const value = useSharedValue(0);
|
|
235
258
|
const displayValue = useSharedValue(0);
|
|
236
259
|
|
|
260
|
+
// One-shot "snap the framing" flag — flipped by the effect when `snapKey`
|
|
261
|
+
// changes, consumed + cleared by the next frame (mirrors useLiveChartEngine).
|
|
262
|
+
const snapSV = useSharedValue(false);
|
|
263
|
+
const lastSnapKey = useRef(config.snapKey);
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (config.snapKey === lastSnapKey.current) return;
|
|
266
|
+
lastSnapKey.current = config.snapKey;
|
|
267
|
+
snapSV.set(true);
|
|
268
|
+
}, [config.snapKey, snapSV]);
|
|
269
|
+
|
|
237
270
|
const { series } = config;
|
|
238
271
|
|
|
239
272
|
// Reused per-frame output buffers (ping-ponged) — see applyLiveChartSeriesEngineFrame.
|
|
@@ -273,6 +306,7 @@ export function useLiveChartSeriesEngine(
|
|
|
273
306
|
returnFromSV: returnFrom,
|
|
274
307
|
viewWindowSV: viewWindow,
|
|
275
308
|
liveEdgeSV: liveEdge,
|
|
309
|
+
snapSV,
|
|
276
310
|
extremaMinValue,
|
|
277
311
|
extremaMaxValue,
|
|
278
312
|
extremaMinTime,
|
|
@@ -35,6 +35,10 @@ export function useChartPaths(
|
|
|
35
35
|
*/
|
|
36
36
|
edgeValue?: SharedValue<number>,
|
|
37
37
|
followViewEdge = false,
|
|
38
|
+
/** Loading squiggle wave amplitude (px) for the reveal morph. Default 14. */
|
|
39
|
+
squiggleAmplitude = 14,
|
|
40
|
+
/** Loading squiggle wave speed multiplier for the reveal morph. Default 1. */
|
|
41
|
+
squiggleSpeed = 1,
|
|
38
42
|
) {
|
|
39
43
|
const lineBuilder = usePathBuilder();
|
|
40
44
|
const fillBuilder = usePathBuilder();
|
|
@@ -86,7 +90,13 @@ export function useChartPaths(
|
|
|
86
90
|
// Compute squiggly Y values at the same X positions as the real line
|
|
87
91
|
const centerY =
|
|
88
92
|
(engine.canvasHeight.get() - padding.bottom + padding.top) / 2;
|
|
89
|
-
const squigglyPts = squigglifyPts(
|
|
93
|
+
const squigglyPts = squigglifyPts(
|
|
94
|
+
realPts,
|
|
95
|
+
engine.timestamp.get(),
|
|
96
|
+
centerY,
|
|
97
|
+
squiggleAmplitude,
|
|
98
|
+
squiggleSpeed,
|
|
99
|
+
);
|
|
90
100
|
|
|
91
101
|
// Blend center-out: centre of chart reveals first, edges last
|
|
92
102
|
return blendPtsY(
|
|
@@ -60,6 +60,8 @@ export function useChartReveal(
|
|
|
60
60
|
hasData: SharedValue<boolean>,
|
|
61
61
|
/** Static charts skip the entry ramp: morphT snaps to its target, no `withTiming`. */
|
|
62
62
|
isStatic = false,
|
|
63
|
+
/** Reveal/collapse duration (ms). Default {@link CHART_REVEAL_DURATION_MS}; `0` snaps. */
|
|
64
|
+
revealDuration: number = CHART_REVEAL_DURATION_MS,
|
|
63
65
|
): ChartRevealState {
|
|
64
66
|
// Best-guess initial so the common case — a live chart that already has data
|
|
65
67
|
// and isn't loading — paints fully revealed with no flash. The reaction below
|
|
@@ -85,15 +87,18 @@ export function useChartReveal(
|
|
|
85
87
|
return;
|
|
86
88
|
}
|
|
87
89
|
if (prev !== chartVisible) {
|
|
90
|
+
// 0ms → withTiming resolves on the next frame (effectively a snap), so an
|
|
91
|
+
// explicit `transitions={{ reveal: 0 }}` / `transitions={false}` removes
|
|
92
|
+
// the grow-in without a special-case branch.
|
|
88
93
|
morphT.set(
|
|
89
94
|
withTiming(chartVisible ? 1 : 0, {
|
|
90
|
-
duration:
|
|
95
|
+
duration: revealDuration,
|
|
91
96
|
easing: Easing.out(Easing.cubic),
|
|
92
97
|
}),
|
|
93
98
|
);
|
|
94
99
|
}
|
|
95
100
|
},
|
|
96
|
-
[hasData, isStatic],
|
|
101
|
+
[hasData, isStatic, revealDuration],
|
|
97
102
|
);
|
|
98
103
|
|
|
99
104
|
const isEmpty = useDerivedValue(() => !isLoading.get() && !hasData.get());
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type SharedValue,
|
|
8
8
|
} from "react-native-reanimated";
|
|
9
9
|
|
|
10
|
-
const MODE_BLEND_DURATION_MS = 300;
|
|
10
|
+
export const MODE_BLEND_DURATION_MS = 300;
|
|
11
11
|
|
|
12
12
|
export interface ModeBlendState {
|
|
13
13
|
/** 0 = fully line, 1 = fully candle */
|
|
@@ -28,17 +28,19 @@ export interface ModeBlendState {
|
|
|
28
28
|
export function useModeBlend(
|
|
29
29
|
isCandle: boolean,
|
|
30
30
|
lineOpacity: SharedValue<number>,
|
|
31
|
+
/** Crossfade duration (ms). Default {@link MODE_BLEND_DURATION_MS}; `0` snaps. */
|
|
32
|
+
duration: number = MODE_BLEND_DURATION_MS,
|
|
31
33
|
): ModeBlendState {
|
|
32
34
|
const modeBlend = useSharedValue(isCandle ? 1 : 0);
|
|
33
35
|
|
|
34
36
|
useEffect(() => {
|
|
35
37
|
modeBlend.set(
|
|
36
38
|
withTiming(isCandle ? 1 : 0, {
|
|
37
|
-
duration
|
|
39
|
+
duration,
|
|
38
40
|
easing: Easing.inOut(Easing.ease),
|
|
39
41
|
}),
|
|
40
42
|
);
|
|
41
|
-
}, [isCandle, modeBlend]);
|
|
43
|
+
}, [isCandle, modeBlend, duration]);
|
|
42
44
|
|
|
43
45
|
const lineGroupOpacity = useDerivedValue(
|
|
44
46
|
() => lineOpacity.get() * (1 - modeBlend.get()),
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,7 @@ export type {
|
|
|
64
64
|
LiveChartPoint,
|
|
65
65
|
LiveChartProps,
|
|
66
66
|
LiveChartSeriesProps,
|
|
67
|
+
LoadingConfig,
|
|
67
68
|
Marker,
|
|
68
69
|
MarkerClusterConfig,
|
|
69
70
|
MarkerGroupBadge,
|
|
@@ -97,6 +98,7 @@ export type {
|
|
|
97
98
|
ThresholdLineConfig,
|
|
98
99
|
TooltipRenderProps,
|
|
99
100
|
TradeEvent,
|
|
101
|
+
TransitionConfig,
|
|
100
102
|
ValueLineConfig,
|
|
101
103
|
VisibleRange,
|
|
102
104
|
VolumeConfig,
|
package/src/math/squiggly.ts
CHANGED
|
@@ -4,16 +4,26 @@ import type { ChartPadding } from "../draw/line";
|
|
|
4
4
|
* Composite sine squiggly — two overlapping frequencies with a breathing
|
|
5
5
|
* amplitude envelope. Matches the original web LiveChart loading animation.
|
|
6
6
|
*
|
|
7
|
-
* amplitude =
|
|
8
|
-
* y = centerY + amplitude * (sin(0.035*x + 1.2*t) + 0.45*sin(0.08*x + 2.1*t))
|
|
7
|
+
* amplitude = base * (0.4 + 0.6 * sin(0.8 * t·speed)) // 0.4×→1.0× base
|
|
8
|
+
* y = centerY + amplitude * (sin(0.035*x + 1.2*t·speed) + 0.45*sin(0.08*x + 2.1*t·speed))
|
|
9
|
+
*
|
|
10
|
+
* `base` (default `14`) scales the wave height; `speed` (default `1`) scales the
|
|
11
|
+
* time phase so the ripple/breathing runs faster or slower (`0` freezes it).
|
|
9
12
|
*/
|
|
10
|
-
export function squigglyYAt(
|
|
13
|
+
export function squigglyYAt(
|
|
14
|
+
x: number,
|
|
15
|
+
centerY: number,
|
|
16
|
+
t: number,
|
|
17
|
+
base = 14,
|
|
18
|
+
speed = 1,
|
|
19
|
+
): number {
|
|
11
20
|
"worklet";
|
|
12
|
-
const
|
|
21
|
+
const ts = t * speed;
|
|
22
|
+
const amplitude = base * (0.4 + 0.6 * Math.sin(0.8 * ts));
|
|
13
23
|
return (
|
|
14
24
|
centerY +
|
|
15
25
|
amplitude *
|
|
16
|
-
(Math.sin(0.035 * x + 1.2 *
|
|
26
|
+
(Math.sin(0.035 * x + 1.2 * ts) + 0.45 * Math.sin(0.08 * x + 2.1 * ts))
|
|
17
27
|
);
|
|
18
28
|
}
|
|
19
29
|
|
|
@@ -27,6 +37,8 @@ export function buildSquigglyPts(
|
|
|
27
37
|
canvasHeight: number,
|
|
28
38
|
padding: ChartPadding,
|
|
29
39
|
t: number,
|
|
40
|
+
base = 14,
|
|
41
|
+
speed = 1,
|
|
30
42
|
): number[] {
|
|
31
43
|
"worklet";
|
|
32
44
|
const leftEdge = padding.left;
|
|
@@ -41,7 +53,7 @@ export function buildSquigglyPts(
|
|
|
41
53
|
for (let i = 0; i < count; i++) {
|
|
42
54
|
const x = leftEdge + Math.min(i * step, chartW);
|
|
43
55
|
pts[i * 2] = x;
|
|
44
|
-
pts[i * 2 + 1] = squigglyYAt(x, centerY, t);
|
|
56
|
+
pts[i * 2 + 1] = squigglyYAt(x, centerY, t, base, speed);
|
|
45
57
|
}
|
|
46
58
|
return pts;
|
|
47
59
|
}
|
|
@@ -54,13 +66,15 @@ export function squigglifyPts(
|
|
|
54
66
|
flatPts: number[],
|
|
55
67
|
t: number,
|
|
56
68
|
centerY: number,
|
|
69
|
+
base = 14,
|
|
70
|
+
speed = 1,
|
|
57
71
|
): number[] {
|
|
58
72
|
"worklet";
|
|
59
73
|
const n = flatPts.length;
|
|
60
74
|
const out: number[] = new Array(n);
|
|
61
75
|
for (let i = 0; i < n; i += 2) {
|
|
62
76
|
out[i] = flatPts[i];
|
|
63
|
-
out[i + 1] = squigglyYAt(flatPts[i], centerY, t);
|
|
77
|
+
out[i + 1] = squigglyYAt(flatPts[i], centerY, t, base, speed);
|
|
64
78
|
}
|
|
65
79
|
return out;
|
|
66
80
|
}
|
package/src/types.ts
CHANGED
|
@@ -720,6 +720,23 @@ export interface ScrubConfig {
|
|
|
720
720
|
* Default `0`.
|
|
721
721
|
*/
|
|
722
722
|
panGestureDelay?: number;
|
|
723
|
+
/**
|
|
724
|
+
* Fade the annotation overlays — buy/sell **markers** and **reference lines**
|
|
725
|
+
* (both the built-in Skia tags/lines and any custom `renderMarker` /
|
|
726
|
+
* `renderReferenceLine` RN views) — out while scrubbing, so they don't clutter
|
|
727
|
+
* the crosshair read-out. Reverses on release.
|
|
728
|
+
*
|
|
729
|
+
* The fade is driven by the **scrub-active** state (not the crosshair's
|
|
730
|
+
* edge-proximity fade, which would resurface the overlays as the crosshair
|
|
731
|
+
* nears the live dot) and eased on the UI thread over `SCRUB_OVERLAY_FADE_MS`.
|
|
732
|
+
* It animates only a **group opacity** — the marker atlas and reference-line
|
|
733
|
+
* geometry are left intact (still one batched draw each), so it's far cheaper
|
|
734
|
+
* than emptying / rebuilding overlay data per scrub. The leading dot is
|
|
735
|
+
* governed separately by `selectionDot`.
|
|
736
|
+
*
|
|
737
|
+
* `false` / omitted keeps the overlays visible while scrubbing (default).
|
|
738
|
+
*/
|
|
739
|
+
hideOverlaysOnScrub?: boolean;
|
|
723
740
|
}
|
|
724
741
|
|
|
725
742
|
/**
|
|
@@ -1586,6 +1603,60 @@ export interface VisibleRange {
|
|
|
1586
1603
|
following: boolean;
|
|
1587
1604
|
}
|
|
1588
1605
|
|
|
1606
|
+
/**
|
|
1607
|
+
* Per-transition animation durations (ms). Passed as the object form of
|
|
1608
|
+
* {@link LiveChartCoreProps.transitions} to tune or disable the built-in
|
|
1609
|
+
* animations. Omit a field to keep its default; `0` makes that transition
|
|
1610
|
+
* instant (snap). Setting `transitions={false}` is shorthand for all `0`.
|
|
1611
|
+
*/
|
|
1612
|
+
export interface TransitionConfig {
|
|
1613
|
+
/**
|
|
1614
|
+
* Reveal / collapse transition — the grow-in **opacity** fade when data first
|
|
1615
|
+
* appears (and the fade-out when it goes away or `loading` toggles). This is
|
|
1616
|
+
* the fade that plays on a timeframe change and when a `line` chart's data
|
|
1617
|
+
* appears (e.g. switching from candle to line). Default `600`. `0` = instant.
|
|
1618
|
+
*
|
|
1619
|
+
* Note this animates opacity only, not geometry: it does not control the time
|
|
1620
|
+
* window / Y-range *easing* on a timeframe or dataset change (that's
|
|
1621
|
+
* `smoothing`). To make that framing settle in one frame instead of sliding,
|
|
1622
|
+
* use {@link LiveChartCoreProps.snapKey}.
|
|
1623
|
+
*/
|
|
1624
|
+
reveal?: number;
|
|
1625
|
+
/**
|
|
1626
|
+
* Candle ↔ line crossfade duration when `mode` changes. Single-series
|
|
1627
|
+
* `LiveChart` only (multi-series is always lines). Default `300`. `0` = instant.
|
|
1628
|
+
*/
|
|
1629
|
+
mode?: number;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
/**
|
|
1633
|
+
* Styling for the breathing-line loading shell (the {@link LiveChartCoreProps.loading}
|
|
1634
|
+
* state). Every field is optional — pass it as the object form of `loading` to
|
|
1635
|
+
* restyle the shell; omit a field to keep its default.
|
|
1636
|
+
*/
|
|
1637
|
+
export interface LoadingConfig {
|
|
1638
|
+
/**
|
|
1639
|
+
* Color of the loading squiggle **and** the skeleton Y-axis placeholders.
|
|
1640
|
+
* Default: the theme's `gridLine` color.
|
|
1641
|
+
*/
|
|
1642
|
+
color?: string;
|
|
1643
|
+
/**
|
|
1644
|
+
* Stroke width (px) of the loading squiggle. Default: the chart's line
|
|
1645
|
+
* `strokeWidth`.
|
|
1646
|
+
*/
|
|
1647
|
+
strokeWidth?: number;
|
|
1648
|
+
/**
|
|
1649
|
+
* Base wave amplitude (px) of the breathing squiggle — it breathes between
|
|
1650
|
+
* `0.4×` and `1.0×` this. Default `14`.
|
|
1651
|
+
*/
|
|
1652
|
+
amplitude?: number;
|
|
1653
|
+
/**
|
|
1654
|
+
* Multiplier on the breathing-wave animation cadence: `>1` ripples faster,
|
|
1655
|
+
* `<1` slower, `0` freezes it. Default `1`.
|
|
1656
|
+
*/
|
|
1657
|
+
speed?: number;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1589
1660
|
/** Props shared between `LiveChart` and `LiveChartSeries`. */
|
|
1590
1661
|
export interface LiveChartCoreProps {
|
|
1591
1662
|
/** Color scheme. Default `"dark"`. */
|
|
@@ -1602,17 +1673,57 @@ export interface LiveChartCoreProps {
|
|
|
1602
1673
|
timeWindow?: number;
|
|
1603
1674
|
/** Freeze chart scrolling. Resume catches up to real time. Default `false`. */
|
|
1604
1675
|
paused?: boolean;
|
|
1676
|
+
/**
|
|
1677
|
+
* Tune or disable the built-in transition animations. `true` / omitted keeps
|
|
1678
|
+
* the defaults; `false` makes them all instant (no grow-in on data
|
|
1679
|
+
* appear/timeframe change, no candle↔line crossfade); a {@link TransitionConfig}
|
|
1680
|
+
* sets per-transition durations in ms (`reveal`, `mode`), where `0` snaps that
|
|
1681
|
+
* one. Live value tracking is governed separately by `smoothing`; static-entry
|
|
1682
|
+
* suppression by `static`.
|
|
1683
|
+
*/
|
|
1684
|
+
transitions?: boolean | TransitionConfig;
|
|
1605
1685
|
/**
|
|
1606
1686
|
* Breathing-line loading shell. When this becomes `false`, the chart reveals
|
|
1607
1687
|
* only if there is data (≥2 line points or ≥2 committed candles).
|
|
1688
|
+
*
|
|
1689
|
+
* `true` shows the shell with the defaults; pass a {@link LoadingConfig} to
|
|
1690
|
+
* restyle it — `color` / `strokeWidth` for the squiggle + skeleton, `amplitude`
|
|
1691
|
+
* / `speed` for the breathing wave. `false` / omitted is "not loading". Toggle
|
|
1692
|
+
* between the config and `false` as data loads (e.g. `loading={isLoading && cfg}`).
|
|
1693
|
+
* The loading→live reveal duration is governed separately by
|
|
1694
|
+
* {@link TransitionConfig.reveal} (the `transitions` prop).
|
|
1608
1695
|
*/
|
|
1609
|
-
loading?: boolean;
|
|
1696
|
+
loading?: boolean | LoadingConfig;
|
|
1610
1697
|
/**
|
|
1611
1698
|
* Value-lerp speed — how quickly the drawn value, time window, and Y-range chase
|
|
1612
1699
|
* their targets each frame (0 = frozen, 1 = instant). Equivalent to liveline's
|
|
1613
1700
|
* `lerpSpeed`. Default `0.08`.
|
|
1614
1701
|
*/
|
|
1615
1702
|
smoothing?: number;
|
|
1703
|
+
/**
|
|
1704
|
+
* Snap the framing to its target in a single frame whenever this key changes —
|
|
1705
|
+
* without giving up smooth live ticks. On a timeframe / dataset switch, the
|
|
1706
|
+
* window, Y-range, and value otherwise *ease* toward their new targets over
|
|
1707
|
+
* `smoothing`, which reads as a slide. Bump `snapKey` at that moment and the
|
|
1708
|
+
* next frame jumps straight to the new framing (no slide), then normal
|
|
1709
|
+
* `smoothing` resumes for subsequent live ticks.
|
|
1710
|
+
*
|
|
1711
|
+
* Pass any value that changes once per discrete view change — the current
|
|
1712
|
+
* timeframe id, or a counter you increment when you swap the `data` / `candles`
|
|
1713
|
+
* array for a different range:
|
|
1714
|
+
*
|
|
1715
|
+
* ```tsx
|
|
1716
|
+
* const [tf, setTf] = useState("1H");
|
|
1717
|
+
* <LiveChart data={data} value={value} timeWindow={windowFor(tf)} snapKey={tf} />
|
|
1718
|
+
* ```
|
|
1719
|
+
*
|
|
1720
|
+
* Unlike `transitions` (which only governs the reveal fade and the candle↔line
|
|
1721
|
+
* crossfade — opacity, not geometry) and `static` (which turns off the live
|
|
1722
|
+
* loop entirely), `snapKey` settles only the geometry and only for one frame.
|
|
1723
|
+
* Leaves the timestamp / time-scroll position untouched. Omit to keep easing on
|
|
1724
|
+
* every change (the default).
|
|
1725
|
+
*/
|
|
1726
|
+
snapKey?: string | number;
|
|
1616
1727
|
/** Tight Y-axis — small value moves fill the full chart height. Default `false`. */
|
|
1617
1728
|
exaggerate?: boolean;
|
|
1618
1729
|
/**
|