react-native-livechart 4.4.0 → 4.5.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/resolveConfig.d.ts +35 -1
- package/dist/core/resolveConfig.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 +82 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChart.tsx +126 -42
- package/src/components/LiveChartSeries.tsx +95 -26
- package/src/components/LoadingOverlay.tsx +25 -6
- package/src/constants.ts +17 -0
- package/src/core/resolveConfig.ts +63 -0
- 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 +84 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
-
import { View } from "react-native";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
3
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
4
|
-
import {
|
|
4
|
+
import Animated, {
|
|
5
5
|
useAnimatedReaction,
|
|
6
|
+
useAnimatedStyle,
|
|
6
7
|
useDerivedValue,
|
|
7
8
|
useSharedValue,
|
|
9
|
+
withTiming,
|
|
8
10
|
} from "react-native-reanimated";
|
|
9
11
|
import { scheduleOnRN } from "react-native-worklets";
|
|
10
12
|
|
|
@@ -22,7 +24,11 @@ import {
|
|
|
22
24
|
vec,
|
|
23
25
|
} from "@shopify/react-native-skia";
|
|
24
26
|
|
|
25
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_ACCENT_COLOR,
|
|
29
|
+
HOLD_TO_SCRUB_MS,
|
|
30
|
+
SCRUB_OVERLAY_FADE_MS,
|
|
31
|
+
} from "../constants";
|
|
26
32
|
import {
|
|
27
33
|
resolveAreaDots,
|
|
28
34
|
resolveAxisLabel,
|
|
@@ -32,11 +38,13 @@ import {
|
|
|
32
38
|
resolveGradient,
|
|
33
39
|
resolveGridStyle,
|
|
34
40
|
resolveLeftEdgeFade,
|
|
41
|
+
resolveLoading,
|
|
35
42
|
resolveMarkerCluster,
|
|
36
43
|
resolveMetrics,
|
|
37
44
|
resolvePulse,
|
|
38
45
|
resolveScrub,
|
|
39
46
|
resolveScrubAction,
|
|
47
|
+
resolveTransitions,
|
|
40
48
|
resolveReturnToLiveMs,
|
|
41
49
|
resolveSelectionDot,
|
|
42
50
|
resolveThreshold,
|
|
@@ -208,6 +216,7 @@ function useLiveChartController({
|
|
|
208
216
|
timeWindow = 30,
|
|
209
217
|
paused = false,
|
|
210
218
|
loading = false,
|
|
219
|
+
transitions,
|
|
211
220
|
// `static` is a reserved word — alias it so the destructure parses.
|
|
212
221
|
static: isStatic = false,
|
|
213
222
|
smoothing = 0.08,
|
|
@@ -535,7 +544,17 @@ function useLiveChartController({
|
|
|
535
544
|
candles,
|
|
536
545
|
});
|
|
537
546
|
|
|
538
|
-
|
|
547
|
+
// Resolve the loading shell: null = not loading, else the styled config (a
|
|
548
|
+
// non-null result is the "is loading" flag and carries the look).
|
|
549
|
+
const loadingCfg = resolveLoading(loading);
|
|
550
|
+
const loadingActive = loadingCfg !== null;
|
|
551
|
+
const transitionsCfg = resolveTransitions(transitions);
|
|
552
|
+
const reveal = useChartReveal(
|
|
553
|
+
loadingActive,
|
|
554
|
+
hasData,
|
|
555
|
+
isStatic,
|
|
556
|
+
transitionsCfg.reveal,
|
|
557
|
+
);
|
|
539
558
|
|
|
540
559
|
// After data clears, keep last snapshot until morphT finishes dropping (web parity).
|
|
541
560
|
const { lineEngineData, candlesEngine, liveEngine } =
|
|
@@ -594,6 +613,7 @@ function useLiveChartController({
|
|
|
594
613
|
const { lineGroupOpacity, candleGroupOpacity } = useModeBlend(
|
|
595
614
|
isCandle,
|
|
596
615
|
reveal.lineOpacity,
|
|
616
|
+
transitionsCfg.mode,
|
|
597
617
|
);
|
|
598
618
|
|
|
599
619
|
// ── Per-frame derived values ───────────────────────────────────────────
|
|
@@ -662,6 +682,9 @@ function useLiveChartController({
|
|
|
662
682
|
// live value when `followViewEdge` tracks the scrolled-back window.
|
|
663
683
|
engine.edgeValue,
|
|
664
684
|
badgeCfg?.followViewEdge ?? false,
|
|
685
|
+
// Match the standalone loading squiggle's wave during the reveal morph.
|
|
686
|
+
loadingCfg?.amplitude,
|
|
687
|
+
loadingCfg?.speed,
|
|
665
688
|
);
|
|
666
689
|
|
|
667
690
|
// Area-dots fill shader color as a vec4 (channels 0..1), with the config
|
|
@@ -1027,6 +1050,25 @@ function useLiveChartController({
|
|
|
1027
1050
|
(selectionDotDuringScrub && crosshair.scrubActive.value ? 0 : 1),
|
|
1028
1051
|
);
|
|
1029
1052
|
|
|
1053
|
+
// Fade the annotation overlays (markers + reference lines) out while scrubbing
|
|
1054
|
+
// when `scrub.hideOverlays` is set. Eased off the scrub-ACTIVE flag — not the
|
|
1055
|
+
// crosshair's edge-proximity fade, which drops to 0 near the live dot and would
|
|
1056
|
+
// resurface the overlays mid-scrub. Only this group opacity animates; the
|
|
1057
|
+
// marker atlas / reference-line draws stay intact (one batched draw each).
|
|
1058
|
+
const fadeOverlaysOnScrub =
|
|
1059
|
+
!isStatic && scrubCfg !== null && scrubCfg.hideOverlaysOnScrub === true;
|
|
1060
|
+
const overlayScrubFade = useDerivedValue(() =>
|
|
1061
|
+
fadeOverlaysOnScrub
|
|
1062
|
+
? withTiming(crosshair.scrubActive.get() ? 0 : 1, {
|
|
1063
|
+
duration: SCRUB_OVERLAY_FADE_MS,
|
|
1064
|
+
})
|
|
1065
|
+
: 1,
|
|
1066
|
+
);
|
|
1067
|
+
// Markers already fade with the dot reveal; fold the scrub-hide fade in too.
|
|
1068
|
+
const markerGroupOpacity = useDerivedValue(
|
|
1069
|
+
() => reveal.dotOpacity.get() * overlayScrubFade.get(),
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1030
1072
|
return {
|
|
1031
1073
|
// passthrough props the render needs
|
|
1032
1074
|
style,
|
|
@@ -1090,6 +1132,11 @@ function useLiveChartController({
|
|
|
1090
1132
|
// engine + reveal
|
|
1091
1133
|
engine,
|
|
1092
1134
|
reveal,
|
|
1135
|
+
// loading shell styling (null → not loading)
|
|
1136
|
+
loadingLineColor: loadingCfg?.color,
|
|
1137
|
+
loadingStrokeWidth: loadingCfg?.strokeWidth,
|
|
1138
|
+
loadingAmplitude: loadingCfg?.amplitude,
|
|
1139
|
+
loadingSpeed: loadingCfg?.speed,
|
|
1093
1140
|
// derived render values
|
|
1094
1141
|
backgroundColor,
|
|
1095
1142
|
gradientEnd,
|
|
@@ -1121,6 +1168,8 @@ function useLiveChartController({
|
|
|
1121
1168
|
dotX,
|
|
1122
1169
|
dotY,
|
|
1123
1170
|
liveDotOpacity,
|
|
1171
|
+
overlayScrubFade,
|
|
1172
|
+
markerGroupOpacity,
|
|
1124
1173
|
momentumSV,
|
|
1125
1174
|
tradeMarkers,
|
|
1126
1175
|
degenPack,
|
|
@@ -1309,6 +1358,8 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1309
1358
|
markersActive,
|
|
1310
1359
|
markersSV,
|
|
1311
1360
|
markerClusterCfg,
|
|
1361
|
+
markerGroupOpacity,
|
|
1362
|
+
overlayScrubFade,
|
|
1312
1363
|
renderMarker,
|
|
1313
1364
|
emptyText,
|
|
1314
1365
|
metricsCfg,
|
|
@@ -1318,6 +1369,10 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1318
1369
|
yAxisEntries,
|
|
1319
1370
|
badgeUsesRightGutter,
|
|
1320
1371
|
gridStyleCfg,
|
|
1372
|
+
loadingLineColor,
|
|
1373
|
+
loadingStrokeWidth,
|
|
1374
|
+
loadingAmplitude,
|
|
1375
|
+
loadingSpeed,
|
|
1321
1376
|
} = model;
|
|
1322
1377
|
|
|
1323
1378
|
return (
|
|
@@ -1350,21 +1405,24 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1350
1405
|
|
|
1351
1406
|
{/* Index keys: reference lines are a positional array and two may share
|
|
1352
1407
|
value + label (e.g. duplicate working orders at the same price), which a
|
|
1353
|
-
content-derived key would collapse to one.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1408
|
+
content-derived key would collapse to one. Wrapped in a fade group so
|
|
1409
|
+
`scrub.hideOverlaysOnScrub` can ease the lines out while scrubbing. */}
|
|
1410
|
+
<Group opacity={overlayScrubFade}>
|
|
1411
|
+
{allRefLines.map((rl, i) => (
|
|
1412
|
+
<ReferenceLineOverlay
|
|
1413
|
+
key={i}
|
|
1414
|
+
engine={engine}
|
|
1415
|
+
padding={effectivePadding}
|
|
1416
|
+
line={rl}
|
|
1417
|
+
palette={palette}
|
|
1418
|
+
formatValue={formatValue}
|
|
1419
|
+
font={skiaFont}
|
|
1420
|
+
fontProp={fontProp}
|
|
1421
|
+
dragValues={dragValues}
|
|
1422
|
+
index={i}
|
|
1423
|
+
/>
|
|
1424
|
+
))}
|
|
1425
|
+
</Group>
|
|
1368
1426
|
|
|
1369
1427
|
{/* Threshold marker line + label (behind the chart line). */}
|
|
1370
1428
|
{thresholdCfg?.line && (
|
|
@@ -1523,7 +1581,7 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1523
1581
|
)}
|
|
1524
1582
|
|
|
1525
1583
|
{markersActive && (
|
|
1526
|
-
<Group opacity={
|
|
1584
|
+
<Group opacity={markerGroupOpacity}>
|
|
1527
1585
|
<MarkerOverlay
|
|
1528
1586
|
markers={markersSV}
|
|
1529
1587
|
engine={engine}
|
|
@@ -1570,6 +1628,10 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1570
1628
|
badgeTail={badgeCfg?.tail ?? true}
|
|
1571
1629
|
badgeMetrics={metricsCfg.badge}
|
|
1572
1630
|
emptyMetrics={metricsCfg.emptyState}
|
|
1631
|
+
lineColor={loadingLineColor}
|
|
1632
|
+
lineStrokeWidth={loadingStrokeWidth}
|
|
1633
|
+
waveAmplitude={loadingAmplitude}
|
|
1634
|
+
waveSpeed={loadingSpeed}
|
|
1573
1635
|
/>
|
|
1574
1636
|
</Group>
|
|
1575
1637
|
);
|
|
@@ -1747,10 +1809,11 @@ function ChartRefBadgeLayer({ model }: { model: LiveChartModel }) {
|
|
|
1747
1809
|
skiaFont,
|
|
1748
1810
|
fontProp,
|
|
1749
1811
|
degenShakeTransform,
|
|
1812
|
+
overlayScrubFade,
|
|
1750
1813
|
} = model;
|
|
1751
1814
|
if (allRefLines.length === 0) return null;
|
|
1752
1815
|
return (
|
|
1753
|
-
<Group transform={degenShakeTransform}>
|
|
1816
|
+
<Group transform={degenShakeTransform} opacity={overlayScrubFade}>
|
|
1754
1817
|
{allRefLines.map((rl, i) => (
|
|
1755
1818
|
<ReferenceLineOverlay
|
|
1756
1819
|
key={i}
|
|
@@ -1853,8 +1916,15 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
1853
1916
|
topConnector,
|
|
1854
1917
|
bottomConnector,
|
|
1855
1918
|
lineIsLinear,
|
|
1919
|
+
overlayScrubFade,
|
|
1856
1920
|
} = model;
|
|
1857
1921
|
|
|
1922
|
+
// Mirror the Skia overlay fade onto the RN custom-overlay siblings (custom
|
|
1923
|
+
// markers / reference-line tags) so `scrub.hideOverlaysOnScrub` hides them too.
|
|
1924
|
+
const overlayFadeStyle = useAnimatedStyle(() => ({
|
|
1925
|
+
opacity: overlayScrubFade.get(),
|
|
1926
|
+
}));
|
|
1927
|
+
|
|
1858
1928
|
return (
|
|
1859
1929
|
<GestureDetector gesture={rootGesture}>
|
|
1860
1930
|
<View
|
|
@@ -1921,33 +1991,47 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
1921
1991
|
/>
|
|
1922
1992
|
|
|
1923
1993
|
{/* Custom-rendered markers — RN views floated over the canvas (non-Skia),
|
|
1924
|
-
pinned to each marker's live position. Sibling of <Canvas>.
|
|
1994
|
+
pinned to each marker's live position. Sibling of <Canvas>. Wrapped in
|
|
1995
|
+
a box-none fade layer so `scrub.hideOverlaysOnScrub` hides them with the
|
|
1996
|
+
Skia markers (the wrapper is full-bleed; children keep their own
|
|
1997
|
+
absolute positions). */}
|
|
1925
1998
|
{markersActive && renderMarker && (
|
|
1926
|
-
<
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1999
|
+
<Animated.View
|
|
2000
|
+
pointerEvents="box-none"
|
|
2001
|
+
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
2002
|
+
>
|
|
2003
|
+
<CustomMarkerOverlay
|
|
2004
|
+
markers={markersSV}
|
|
2005
|
+
renderMarker={renderMarker}
|
|
2006
|
+
engine={engine}
|
|
2007
|
+
padding={effectivePadding}
|
|
2008
|
+
lineData={engine.data}
|
|
2009
|
+
lineLinear={lineIsLinear}
|
|
2010
|
+
cluster={markerClusterCfg}
|
|
2011
|
+
/>
|
|
2012
|
+
</Animated.View>
|
|
1935
2013
|
)}
|
|
1936
2014
|
|
|
1937
2015
|
{/* Custom-rendered reference-line tags — RN views floated over the canvas
|
|
1938
2016
|
(non-Skia), pinned to each Form-A line's value. Sibling of <Canvas>.
|
|
1939
|
-
Built-in Skia tags for these lines are suppressed (no double-draw).
|
|
2017
|
+
Built-in Skia tags for these lines are suppressed (no double-draw).
|
|
2018
|
+
Same box-none fade wrapper as the custom markers above. */}
|
|
1940
2019
|
{renderReferenceLine && allRefLines.length > 0 && (
|
|
1941
|
-
<
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2020
|
+
<Animated.View
|
|
2021
|
+
pointerEvents="box-none"
|
|
2022
|
+
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
2023
|
+
>
|
|
2024
|
+
<CustomReferenceLineOverlay
|
|
2025
|
+
lines={allRefLines}
|
|
2026
|
+
renderReferenceLine={renderReferenceLine}
|
|
2027
|
+
custom={refLineCustom}
|
|
2028
|
+
engine={engine}
|
|
2029
|
+
padding={effectivePadding}
|
|
2030
|
+
formatValue={formatValue}
|
|
2031
|
+
dragValues={dragValues}
|
|
2032
|
+
dragActive={dragActive}
|
|
2033
|
+
/>
|
|
2034
|
+
</Animated.View>
|
|
1951
2035
|
)}
|
|
1952
2036
|
|
|
1953
2037
|
{/* Custom scrub tooltip — an RN view floated over the canvas (non-Skia),
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Canvas, Group } from "@shopify/react-native-skia";
|
|
8
8
|
import { useLayoutEffect, useState } from "react";
|
|
9
|
-
import { View } from "react-native";
|
|
9
|
+
import { StyleSheet, View } from "react-native";
|
|
10
10
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
11
|
-
import {
|
|
11
|
+
import Animated, {
|
|
12
12
|
useAnimatedReaction,
|
|
13
|
+
useAnimatedStyle,
|
|
13
14
|
useDerivedValue,
|
|
14
15
|
useSharedValue,
|
|
16
|
+
withTiming,
|
|
15
17
|
type SharedValue,
|
|
16
18
|
} from "react-native-reanimated";
|
|
17
19
|
import { scheduleOnRN } from "react-native-worklets";
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
DEFAULT_ACCENT_COLOR,
|
|
20
22
|
HOLD_TO_SCRUB_MS,
|
|
21
23
|
MAX_MULTI_SERIES,
|
|
24
|
+
SCRUB_OVERLAY_FADE_MS,
|
|
22
25
|
} from "../constants";
|
|
23
26
|
import {
|
|
24
27
|
lineColorsSignatureFromArray,
|
|
@@ -33,12 +36,14 @@ import {
|
|
|
33
36
|
resolveGridStyle,
|
|
34
37
|
resolveLeftEdgeFade,
|
|
35
38
|
resolveLegend,
|
|
39
|
+
resolveLoading,
|
|
36
40
|
resolveMarkerCluster,
|
|
37
41
|
resolveMetrics,
|
|
38
42
|
resolveMultiSeriesDot,
|
|
39
43
|
resolveReturnToLiveMs,
|
|
40
44
|
resolveScrub,
|
|
41
45
|
resolveSelectionDot,
|
|
46
|
+
resolveTransitions,
|
|
42
47
|
resolveXAxis,
|
|
43
48
|
resolveYAxis,
|
|
44
49
|
resolveZoom,
|
|
@@ -126,6 +131,7 @@ function useLiveChartSeriesController({
|
|
|
126
131
|
timeWindow = 30,
|
|
127
132
|
paused = false,
|
|
128
133
|
loading = false,
|
|
134
|
+
transitions,
|
|
129
135
|
smoothing = 0.08,
|
|
130
136
|
exaggerate = false,
|
|
131
137
|
nonNegative = false,
|
|
@@ -289,7 +295,18 @@ function useLiveChartSeriesController({
|
|
|
289
295
|
return false;
|
|
290
296
|
});
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
// Resolve the loading shell: null = not loading, else the styled config.
|
|
299
|
+
const loadingCfg = resolveLoading(loading);
|
|
300
|
+
const loadingActive = loadingCfg !== null;
|
|
301
|
+
// Multi-series is always lines, so only the reveal transition applies (no
|
|
302
|
+
// candle↔line crossfade); `transitions.mode` is accepted but inert here.
|
|
303
|
+
const transitionsCfg = resolveTransitions(transitions);
|
|
304
|
+
const reveal = useChartReveal(
|
|
305
|
+
loadingActive,
|
|
306
|
+
hasData,
|
|
307
|
+
false,
|
|
308
|
+
transitionsCfg.reveal,
|
|
309
|
+
);
|
|
293
310
|
|
|
294
311
|
const effectiveSeries = useMultiSeriesReverseMorphInputs({
|
|
295
312
|
series,
|
|
@@ -453,6 +470,23 @@ function useLiveChartSeriesController({
|
|
|
453
470
|
|
|
454
471
|
const backgroundColor = `rgb(${palette.bgRgb[0]}, ${palette.bgRgb[1]}, ${palette.bgRgb[2]})`;
|
|
455
472
|
|
|
473
|
+
// Fade markers + reference lines out while scrubbing when
|
|
474
|
+
// `scrub.hideOverlaysOnScrub` is set. Eased off the scrub-ACTIVE flag (not the
|
|
475
|
+
// crosshair edge fade, which would resurface them near the live dot); only a
|
|
476
|
+
// group opacity animates — the overlay draws stay intact. See `LiveChart`.
|
|
477
|
+
const fadeOverlaysOnScrub =
|
|
478
|
+
scrubCfg !== null && scrubCfg.hideOverlaysOnScrub === true;
|
|
479
|
+
const overlayScrubFade = useDerivedValue(() =>
|
|
480
|
+
fadeOverlaysOnScrub
|
|
481
|
+
? withTiming(crosshair.scrubActive.get() ? 0 : 1, {
|
|
482
|
+
duration: SCRUB_OVERLAY_FADE_MS,
|
|
483
|
+
})
|
|
484
|
+
: 1,
|
|
485
|
+
);
|
|
486
|
+
const markerGroupOpacity = useDerivedValue(
|
|
487
|
+
() => reveal.dotOpacity.get() * overlayScrubFade.get(),
|
|
488
|
+
);
|
|
489
|
+
|
|
456
490
|
return {
|
|
457
491
|
// passthrough props the render needs
|
|
458
492
|
series,
|
|
@@ -484,6 +518,11 @@ function useLiveChartSeriesController({
|
|
|
484
518
|
// engine + reveal
|
|
485
519
|
engine,
|
|
486
520
|
reveal,
|
|
521
|
+
// loading shell styling (null → not loading)
|
|
522
|
+
loadingLineColor: loadingCfg?.color,
|
|
523
|
+
loadingStrokeWidth: loadingCfg?.strokeWidth,
|
|
524
|
+
loadingAmplitude: loadingCfg?.amplitude,
|
|
525
|
+
loadingSpeed: loadingCfg?.speed,
|
|
487
526
|
effectiveSeries,
|
|
488
527
|
layoutHeight,
|
|
489
528
|
onLayout,
|
|
@@ -500,6 +539,8 @@ function useLiveChartSeriesController({
|
|
|
500
539
|
markersActive,
|
|
501
540
|
markersSV,
|
|
502
541
|
markerClusterCfg,
|
|
542
|
+
markerGroupOpacity,
|
|
543
|
+
overlayScrubFade,
|
|
503
544
|
renderMarker,
|
|
504
545
|
// selection dot: resolved config + fallback color (the leading series' color)
|
|
505
546
|
selectionDot: selectionDotCfg,
|
|
@@ -545,10 +586,16 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
545
586
|
markersActive,
|
|
546
587
|
markersSV,
|
|
547
588
|
markerClusterCfg,
|
|
589
|
+
markerGroupOpacity,
|
|
590
|
+
overlayScrubFade,
|
|
548
591
|
renderMarker,
|
|
549
592
|
series,
|
|
550
593
|
emptyText,
|
|
551
594
|
metricsCfg,
|
|
595
|
+
loadingLineColor,
|
|
596
|
+
loadingStrokeWidth,
|
|
597
|
+
loadingAmplitude,
|
|
598
|
+
loadingSpeed,
|
|
552
599
|
} = model;
|
|
553
600
|
|
|
554
601
|
return (
|
|
@@ -570,18 +617,21 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
570
617
|
|
|
571
618
|
{/* Index keys: reference lines are a positional array and two may share
|
|
572
619
|
value + label (e.g. duplicate working orders at the same price), which a
|
|
573
|
-
content-derived key would collapse to one.
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
620
|
+
content-derived key would collapse to one. Fade group lets
|
|
621
|
+
`scrub.hideOverlaysOnScrub` ease the lines out while scrubbing. */}
|
|
622
|
+
<Group opacity={overlayScrubFade}>
|
|
623
|
+
{allRefLines.map((rl, i) => (
|
|
624
|
+
<ReferenceLineOverlay
|
|
625
|
+
key={i}
|
|
626
|
+
engine={engine}
|
|
627
|
+
padding={effectivePadding}
|
|
628
|
+
line={rl}
|
|
629
|
+
palette={palette}
|
|
630
|
+
formatValue={formatValue}
|
|
631
|
+
font={skiaFont}
|
|
632
|
+
/>
|
|
633
|
+
))}
|
|
634
|
+
</Group>
|
|
585
635
|
|
|
586
636
|
{dotCfg.valueLine && (
|
|
587
637
|
<Group opacity={reveal.lineOpacity}>
|
|
@@ -654,7 +704,7 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
654
704
|
)}
|
|
655
705
|
|
|
656
706
|
{markersActive && (
|
|
657
|
-
<Group opacity={
|
|
707
|
+
<Group opacity={markerGroupOpacity}>
|
|
658
708
|
<MarkerOverlay
|
|
659
709
|
markers={markersSV}
|
|
660
710
|
engine={engine}
|
|
@@ -680,6 +730,10 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
680
730
|
strokeWidth={strokeWidth}
|
|
681
731
|
badge={false}
|
|
682
732
|
emptyMetrics={metricsCfg.emptyState}
|
|
733
|
+
lineColor={loadingLineColor}
|
|
734
|
+
lineStrokeWidth={loadingStrokeWidth}
|
|
735
|
+
waveAmplitude={loadingAmplitude}
|
|
736
|
+
waveSpeed={loadingSpeed}
|
|
683
737
|
/>
|
|
684
738
|
</Group>
|
|
685
739
|
);
|
|
@@ -726,10 +780,11 @@ function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
726
780
|
formatValue,
|
|
727
781
|
skiaFont,
|
|
728
782
|
degenShakeTransform,
|
|
783
|
+
overlayScrubFade,
|
|
729
784
|
} = model;
|
|
730
785
|
if (allRefLines.length === 0) return null;
|
|
731
786
|
return (
|
|
732
|
-
<Group transform={degenShakeTransform}>
|
|
787
|
+
<Group transform={degenShakeTransform} opacity={overlayScrubFade}>
|
|
733
788
|
{allRefLines.map((rl, i) => (
|
|
734
789
|
<ReferenceLineOverlay
|
|
735
790
|
key={i}
|
|
@@ -778,8 +833,15 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
778
833
|
markersSV,
|
|
779
834
|
markerClusterCfg,
|
|
780
835
|
renderMarker,
|
|
836
|
+
overlayScrubFade,
|
|
781
837
|
} = model;
|
|
782
838
|
|
|
839
|
+
// Mirror the Skia overlay fade onto the RN custom-marker sibling so
|
|
840
|
+
// `scrub.hideOverlaysOnScrub` hides it with the Skia markers.
|
|
841
|
+
const overlayFadeStyle = useAnimatedStyle(() => ({
|
|
842
|
+
opacity: overlayScrubFade.get(),
|
|
843
|
+
}));
|
|
844
|
+
|
|
783
845
|
// Extend the scrub dim past the plot's right edge to fully cover the series
|
|
784
846
|
// dots (with their halo) and pulse rings, all centered on that edge. The
|
|
785
847
|
// gutter reserves room beyond this for the value/Y-axis labels, drawn on top.
|
|
@@ -876,16 +938,23 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
876
938
|
/>
|
|
877
939
|
|
|
878
940
|
{/* Custom-rendered markers — RN views floated over the canvas
|
|
879
|
-
(non-Skia), pinned to each marker's live position.
|
|
941
|
+
(non-Skia), pinned to each marker's live position. Box-none fade
|
|
942
|
+
wrapper so `scrub.hideOverlaysOnScrub` hides them with the Skia
|
|
943
|
+
markers (full-bleed; children keep their own absolute positions). */}
|
|
880
944
|
{markersActive && renderMarker && (
|
|
881
|
-
<
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
945
|
+
<Animated.View
|
|
946
|
+
pointerEvents="box-none"
|
|
947
|
+
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
948
|
+
>
|
|
949
|
+
<CustomMarkerOverlay
|
|
950
|
+
markers={markersSV}
|
|
951
|
+
renderMarker={renderMarker}
|
|
952
|
+
engine={engine}
|
|
953
|
+
padding={effectivePadding}
|
|
954
|
+
series={series}
|
|
955
|
+
cluster={markerClusterCfg}
|
|
956
|
+
/>
|
|
957
|
+
</Animated.View>
|
|
889
958
|
)}
|
|
890
959
|
</View>
|
|
891
960
|
</GestureDetector>
|
|
@@ -62,6 +62,10 @@ export function LoadingOverlay({
|
|
|
62
62
|
badgeTail = true,
|
|
63
63
|
badgeMetrics = BADGE_METRICS_DEFAULTS,
|
|
64
64
|
emptyMetrics = EMPTY_STATE_METRICS_DEFAULTS,
|
|
65
|
+
lineColor,
|
|
66
|
+
lineStrokeWidth,
|
|
67
|
+
waveAmplitude = 14,
|
|
68
|
+
waveSpeed = 1,
|
|
65
69
|
}: {
|
|
66
70
|
engine: ChartEngineLayout;
|
|
67
71
|
padding: ChartPadding;
|
|
@@ -73,6 +77,14 @@ export function LoadingOverlay({
|
|
|
73
77
|
isEmpty: SharedValue<boolean> | { value: boolean };
|
|
74
78
|
emptyText: string;
|
|
75
79
|
strokeWidth: number;
|
|
80
|
+
/** Loading squiggle + skeleton color. Omit → theme `gridLine`. */
|
|
81
|
+
lineColor?: string;
|
|
82
|
+
/** Loading squiggle stroke width. Omit → `strokeWidth`. */
|
|
83
|
+
lineStrokeWidth?: number;
|
|
84
|
+
/** Breathing-wave base amplitude (px). */
|
|
85
|
+
waveAmplitude?: number;
|
|
86
|
+
/** Breathing-wave speed multiplier. */
|
|
87
|
+
waveSpeed?: number;
|
|
76
88
|
/** Mirror the badge prop so labels align with GridOverlay's label positions. */
|
|
77
89
|
badge?: boolean;
|
|
78
90
|
/** Whether the badge tail spike is shown; affects the left inset used for skeleton alignment. */
|
|
@@ -86,6 +98,11 @@ export function LoadingOverlay({
|
|
|
86
98
|
const leftInset =
|
|
87
99
|
badgeMetrics.dotGap + badgeTailAndCap(font.getSize(), badgeTail, badgeMetrics);
|
|
88
100
|
|
|
101
|
+
// Loading-shell color (squiggle + skeleton placeholders) and squiggle stroke,
|
|
102
|
+
// both overridable via `loading={{ color, strokeWidth }}`.
|
|
103
|
+
const loadingColor = lineColor ?? palette.gridLine;
|
|
104
|
+
const loadingStroke = lineStrokeWidth ?? strokeWidth;
|
|
105
|
+
|
|
89
106
|
// Squiggly path — built into a reused PathBuilder and detach()-ed each frame.
|
|
90
107
|
const squigglyBuilder = usePathBuilder();
|
|
91
108
|
|
|
@@ -100,6 +117,8 @@ export function LoadingOverlay({
|
|
|
100
117
|
engine.canvasHeight.get(),
|
|
101
118
|
padding,
|
|
102
119
|
engine.timestamp.get(),
|
|
120
|
+
waveAmplitude,
|
|
121
|
+
waveSpeed,
|
|
103
122
|
);
|
|
104
123
|
return buildSplineDetached(b, pts);
|
|
105
124
|
});
|
|
@@ -225,8 +244,8 @@ export function LoadingOverlay({
|
|
|
225
244
|
<Path
|
|
226
245
|
path={squigglyPath}
|
|
227
246
|
style="stroke"
|
|
228
|
-
strokeWidth={
|
|
229
|
-
color={
|
|
247
|
+
strokeWidth={loadingStroke}
|
|
248
|
+
color={loadingColor}
|
|
230
249
|
strokeCap="round"
|
|
231
250
|
strokeJoin="round"
|
|
232
251
|
/>
|
|
@@ -255,7 +274,7 @@ export function LoadingOverlay({
|
|
|
255
274
|
width={RECT_W}
|
|
256
275
|
height={RECT_H}
|
|
257
276
|
r={RECT_R}
|
|
258
|
-
color={
|
|
277
|
+
color={loadingColor}
|
|
259
278
|
/>
|
|
260
279
|
<RoundedRect
|
|
261
280
|
x={lx}
|
|
@@ -263,7 +282,7 @@ export function LoadingOverlay({
|
|
|
263
282
|
width={RECT_W}
|
|
264
283
|
height={RECT_H}
|
|
265
284
|
r={RECT_R}
|
|
266
|
-
color={
|
|
285
|
+
color={loadingColor}
|
|
267
286
|
/>
|
|
268
287
|
<RoundedRect
|
|
269
288
|
x={lx}
|
|
@@ -271,7 +290,7 @@ export function LoadingOverlay({
|
|
|
271
290
|
width={RECT_W}
|
|
272
291
|
height={RECT_H}
|
|
273
292
|
r={RECT_R}
|
|
274
|
-
color={
|
|
293
|
+
color={loadingColor}
|
|
275
294
|
/>
|
|
276
295
|
<RoundedRect
|
|
277
296
|
x={lx}
|
|
@@ -279,7 +298,7 @@ export function LoadingOverlay({
|
|
|
279
298
|
width={RECT_W}
|
|
280
299
|
height={RECT_H}
|
|
281
300
|
r={RECT_R}
|
|
282
|
-
color={
|
|
301
|
+
color={loadingColor}
|
|
283
302
|
/>
|
|
284
303
|
|
|
285
304
|
{/* Empty state label */}
|
package/src/constants.ts
CHANGED
|
@@ -31,6 +31,23 @@ export const HOLD_TO_SCRUB_MS = 500;
|
|
|
31
31
|
*/
|
|
32
32
|
export const RETURN_TO_LIVE_MS = 450;
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Duration (ms) of the fade applied to annotation overlays (markers + reference
|
|
36
|
+
* lines) when `scrub.hideOverlaysOnScrub` is on — how long they take to ease out as a
|
|
37
|
+
* scrub starts and back in on release. Driven by the scrub-active flag, eased on
|
|
38
|
+
* the UI thread. Shared by `LiveChart` and `LiveChartSeries`.
|
|
39
|
+
*/
|
|
40
|
+
export const SCRUB_OVERLAY_FADE_MS = 150;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Base wave amplitude (px) of the breathing loading squiggle — it breathes
|
|
44
|
+
* between `0.4×` and `1.0×` this. Overridable via `loading={{ amplitude }}`.
|
|
45
|
+
*/
|
|
46
|
+
export const LOADING_WAVE_AMPLITUDE = 14;
|
|
47
|
+
|
|
48
|
+
/** Default breathing-wave speed multiplier (`1` = built-in cadence). */
|
|
49
|
+
export const LOADING_WAVE_SPEED = 1;
|
|
50
|
+
|
|
34
51
|
// ─── Metric default tokens (single source of truth for LiveChartMetrics) ─────
|
|
35
52
|
// The resolved `metrics` config (see resolveMetrics) is assembled from these
|
|
36
53
|
// objects. Draw/worklet helpers default their metric params to the matching
|