react-native-livechart 1.0.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/LICENSE +33 -0
- package/README.md +198 -0
- package/dist/components/AnimatedLabel.d.ts +21 -0
- package/dist/components/AnimatedLabel.d.ts.map +1 -0
- package/dist/components/BadgeOverlay.d.ts +16 -0
- package/dist/components/BadgeOverlay.d.ts.map +1 -0
- package/dist/components/CrosshairLine.d.ts +20 -0
- package/dist/components/CrosshairLine.d.ts.map +1 -0
- package/dist/components/CrosshairOverlay.d.ts +29 -0
- package/dist/components/CrosshairOverlay.d.ts.map +1 -0
- package/dist/components/DegenParticlesOverlay.d.ts +16 -0
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -0
- package/dist/components/DotOverlay.d.ts +17 -0
- package/dist/components/DotOverlay.d.ts.map +1 -0
- package/dist/components/LeftEdgeFade.d.ts +9 -0
- package/dist/components/LeftEdgeFade.d.ts.map +1 -0
- package/dist/components/LiveChart.d.ts +3 -0
- package/dist/components/LiveChart.d.ts.map +1 -0
- package/dist/components/LiveChartSeries.d.ts +3 -0
- package/dist/components/LiveChartSeries.d.ts.map +1 -0
- package/dist/components/LiveChartTransition.d.ts +28 -0
- package/dist/components/LiveChartTransition.d.ts.map +1 -0
- package/dist/components/LoadingOverlay.d.ts +24 -0
- package/dist/components/LoadingOverlay.d.ts.map +1 -0
- package/dist/components/MarkerOverlay.d.ts +25 -0
- package/dist/components/MarkerOverlay.d.ts.map +1 -0
- package/dist/components/MultiSeriesDots.d.ts +11 -0
- package/dist/components/MultiSeriesDots.d.ts.map +1 -0
- package/dist/components/MultiSeriesStroke.d.ts +14 -0
- package/dist/components/MultiSeriesStroke.d.ts.map +1 -0
- package/dist/components/MultiSeriesTooltipStack.d.ts +10 -0
- package/dist/components/MultiSeriesTooltipStack.d.ts.map +1 -0
- package/dist/components/MultiSeriesValueLabels.d.ts +11 -0
- package/dist/components/MultiSeriesValueLabels.d.ts.map +1 -0
- package/dist/components/MultiSeriesValueLines.d.ts +10 -0
- package/dist/components/MultiSeriesValueLines.d.ts.map +1 -0
- package/dist/components/ReferenceLineOverlay.d.ts +19 -0
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -0
- package/dist/components/SeriesToggleChips.d.ts +15 -0
- package/dist/components/SeriesToggleChips.d.ts.map +1 -0
- package/dist/components/TradeStreamOverlay.d.ts +14 -0
- package/dist/components/TradeStreamOverlay.d.ts.map +1 -0
- package/dist/components/ValueLineOverlay.d.ts +16 -0
- package/dist/components/ValueLineOverlay.d.ts.map +1 -0
- package/dist/components/ValueTextOverlay.d.ts +20 -0
- package/dist/components/ValueTextOverlay.d.ts.map +1 -0
- package/dist/components/XAxisOverlay.d.ts +14 -0
- package/dist/components/XAxisOverlay.d.ts.map +1 -0
- package/dist/components/YAxisOverlay.d.ts +23 -0
- package/dist/components/YAxisOverlay.d.ts.map +1 -0
- package/dist/components/seriesMetaSig.d.ts +4 -0
- package/dist/components/seriesMetaSig.d.ts.map +1 -0
- package/dist/constants.d.ts +38 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/liveChartEngineTick.d.ts +45 -0
- package/dist/core/liveChartEngineTick.d.ts.map +1 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts +38 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -0
- package/dist/core/multiSeriesLayout.d.ts +22 -0
- package/dist/core/multiSeriesLayout.d.ts.map +1 -0
- package/dist/core/resolveConfig.d.ts +188 -0
- package/dist/core/resolveConfig.d.ts.map +1 -0
- package/dist/core/useLiveChartEngine.d.ts +79 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts +68 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -0
- package/dist/draw/candle.d.ts +25 -0
- package/dist/draw/candle.d.ts.map +1 -0
- package/dist/draw/grid.d.ts +24 -0
- package/dist/draw/grid.d.ts.map +1 -0
- package/dist/draw/line.d.ts +86 -0
- package/dist/draw/line.d.ts.map +1 -0
- package/dist/draw/trade.d.ts +38 -0
- package/dist/draw/trade.d.ts.map +1 -0
- package/dist/hooks/crosshairSeries.d.ts +13 -0
- package/dist/hooks/crosshairSeries.d.ts.map +1 -0
- package/dist/hooks/crosshairShared.d.ts +72 -0
- package/dist/hooks/crosshairShared.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +24 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/resolveChartLayout.d.ts +42 -0
- package/dist/hooks/resolveChartLayout.d.ts.map +1 -0
- package/dist/hooks/useBadge.d.ts +14 -0
- package/dist/hooks/useBadge.d.ts.map +1 -0
- package/dist/hooks/useCandlePaths.d.ts +19 -0
- package/dist/hooks/useCandlePaths.d.ts.map +1 -0
- package/dist/hooks/useCanvasLayout.d.ts +12 -0
- package/dist/hooks/useCanvasLayout.d.ts.map +1 -0
- package/dist/hooks/useChartColors.d.ts +20 -0
- package/dist/hooks/useChartColors.d.ts.map +1 -0
- package/dist/hooks/useChartPaths.d.ts +19 -0
- package/dist/hooks/useChartPaths.d.ts.map +1 -0
- package/dist/hooks/useChartReveal.d.ts +30 -0
- package/dist/hooks/useChartReveal.d.ts.map +1 -0
- package/dist/hooks/useChartSkiaFont.d.ts +8 -0
- package/dist/hooks/useChartSkiaFont.d.ts.map +1 -0
- package/dist/hooks/useCrosshair.d.ts +20 -0
- package/dist/hooks/useCrosshair.d.ts.map +1 -0
- package/dist/hooks/useCrosshairSeries.d.ts +10 -0
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -0
- package/dist/hooks/useDegen.d.ts +17 -0
- package/dist/hooks/useDegen.d.ts.map +1 -0
- package/dist/hooks/useLiveChartHasData.d.ts +17 -0
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -0
- package/dist/hooks/useLiveDot.d.ts +12 -0
- package/dist/hooks/useLiveDot.d.ts.map +1 -0
- package/dist/hooks/useMarkers.d.ts +15 -0
- package/dist/hooks/useMarkers.d.ts.map +1 -0
- package/dist/hooks/useModeBlend.d.ts +18 -0
- package/dist/hooks/useModeBlend.d.ts.map +1 -0
- package/dist/hooks/useMomentum.d.ts +16 -0
- package/dist/hooks/useMomentum.d.ts.map +1 -0
- package/dist/hooks/useMultiSeriesDegen.d.ts +25 -0
- package/dist/hooks/useMultiSeriesDegen.d.ts.map +1 -0
- package/dist/hooks/useMultiSeriesLinePaths.d.ts +15 -0
- package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -0
- package/dist/hooks/useReferenceLine.d.ts +32 -0
- package/dist/hooks/useReferenceLine.d.ts.map +1 -0
- package/dist/hooks/useReverseMorphEngineInputs.d.ts +24 -0
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -0
- package/dist/hooks/useTradeStream.d.ts +12 -0
- package/dist/hooks/useTradeStream.d.ts.map +1 -0
- package/dist/hooks/useXAxis.d.ts +32 -0
- package/dist/hooks/useXAxis.d.ts.map +1 -0
- package/dist/hooks/useYAxis.d.ts +13 -0
- package/dist/hooks/useYAxis.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/measureFontTextWidth.d.ts +7 -0
- package/dist/lib/measureFontTextWidth.d.ts.map +1 -0
- package/dist/lib/monoFontFamily.d.ts +6 -0
- package/dist/lib/monoFontFamily.d.ts.map +1 -0
- package/dist/math/color.d.ts +5 -0
- package/dist/math/color.d.ts.map +1 -0
- package/dist/math/degenTick.d.ts +57 -0
- package/dist/math/degenTick.d.ts.map +1 -0
- package/dist/math/interpolate.d.ts +7 -0
- package/dist/math/interpolate.d.ts.map +1 -0
- package/dist/math/intervals.d.ts +3 -0
- package/dist/math/intervals.d.ts.map +1 -0
- package/dist/math/lerp.d.ts +7 -0
- package/dist/math/lerp.d.ts.map +1 -0
- package/dist/math/markers.d.ts +44 -0
- package/dist/math/markers.d.ts.map +1 -0
- package/dist/math/momentum.d.ts +8 -0
- package/dist/math/momentum.d.ts.map +1 -0
- package/dist/math/pickCandle.d.ts +8 -0
- package/dist/math/pickCandle.d.ts.map +1 -0
- package/dist/math/range.d.ts +10 -0
- package/dist/math/range.d.ts.map +1 -0
- package/dist/math/referenceLines.d.ts +20 -0
- package/dist/math/referenceLines.d.ts.map +1 -0
- package/dist/math/spline.d.ts +45 -0
- package/dist/math/spline.d.ts.map +1 -0
- package/dist/math/squiggly.d.ts +36 -0
- package/dist/math/squiggly.d.ts.map +1 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/types.d.ts +720 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/components/AnimatedLabel.tsx +41 -0
- package/src/components/BadgeOverlay.tsx +39 -0
- package/src/components/CrosshairLine.tsx +90 -0
- package/src/components/CrosshairOverlay.tsx +170 -0
- package/src/components/DegenParticlesOverlay.tsx +141 -0
- package/src/components/DotOverlay.tsx +69 -0
- package/src/components/LeftEdgeFade.tsx +36 -0
- package/src/components/LiveChart.tsx +821 -0
- package/src/components/LiveChartSeries.tsx +607 -0
- package/src/components/LiveChartTransition.tsx +135 -0
- package/src/components/LoadingOverlay.tsx +301 -0
- package/src/components/MarkerOverlay.tsx +331 -0
- package/src/components/MultiSeriesDots.tsx +117 -0
- package/src/components/MultiSeriesStroke.tsx +79 -0
- package/src/components/MultiSeriesTooltipStack.tsx +78 -0
- package/src/components/MultiSeriesValueLabels.tsx +103 -0
- package/src/components/MultiSeriesValueLines.tsx +115 -0
- package/src/components/ReferenceLineOverlay.tsx +317 -0
- package/src/components/SeriesToggleChips.tsx +201 -0
- package/src/components/TradeStreamOverlay.tsx +117 -0
- package/src/components/ValueLineOverlay.tsx +63 -0
- package/src/components/ValueTextOverlay.tsx +43 -0
- package/src/components/XAxisOverlay.tsx +109 -0
- package/src/components/YAxisOverlay.tsx +135 -0
- package/src/components/seriesMetaSig.ts +13 -0
- package/src/constants.ts +44 -0
- package/src/core/liveChartEngineTick.ts +185 -0
- package/src/core/liveChartSeriesEngineTick.ts +163 -0
- package/src/core/multiSeriesLayout.ts +80 -0
- package/src/core/resolveConfig.ts +538 -0
- package/src/core/useLiveChartEngine.ts +207 -0
- package/src/core/useLiveChartSeriesEngine.ts +215 -0
- package/src/draw/candle.ts +210 -0
- package/src/draw/grid.ts +174 -0
- package/src/draw/line.ts +257 -0
- package/src/draw/trade.ts +202 -0
- package/src/hooks/crosshairSeries.ts +78 -0
- package/src/hooks/crosshairShared.ts +296 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/resolveChartLayout.ts +144 -0
- package/src/hooks/useBadge.ts +192 -0
- package/src/hooks/useCandlePaths.ts +165 -0
- package/src/hooks/useCanvasLayout.ts +22 -0
- package/src/hooks/useChartColors.ts +48 -0
- package/src/hooks/useChartPaths.ts +121 -0
- package/src/hooks/useChartReveal.ts +123 -0
- package/src/hooks/useChartSkiaFont.ts +34 -0
- package/src/hooks/useCrosshair.ts +250 -0
- package/src/hooks/useCrosshairSeries.ts +163 -0
- package/src/hooks/useDegen.ts +262 -0
- package/src/hooks/useLiveChartHasData.ts +31 -0
- package/src/hooks/useLiveDot.ts +31 -0
- package/src/hooks/useMarkers.ts +95 -0
- package/src/hooks/useModeBlend.ts +52 -0
- package/src/hooks/useMomentum.ts +37 -0
- package/src/hooks/useMultiSeriesDegen.ts +260 -0
- package/src/hooks/useMultiSeriesLinePaths.ts +76 -0
- package/src/hooks/useReferenceLine.ts +212 -0
- package/src/hooks/useReverseMorphEngineInputs.ts +154 -0
- package/src/hooks/useTradeStream.ts +64 -0
- package/src/hooks/useXAxis.ts +200 -0
- package/src/hooks/useYAxis.ts +48 -0
- package/src/index.ts +69 -0
- package/src/lib/format.ts +36 -0
- package/src/lib/measureFontTextWidth.ts +10 -0
- package/src/lib/monoFontFamily.ts +8 -0
- package/src/math/color.ts +31 -0
- package/src/math/degenTick.ts +151 -0
- package/src/math/interpolate.ts +32 -0
- package/src/math/intervals.ts +17 -0
- package/src/math/lerp.ts +17 -0
- package/src/math/markers.ts +175 -0
- package/src/math/momentum.ts +38 -0
- package/src/math/pickCandle.ts +42 -0
- package/src/math/range.ts +54 -0
- package/src/math/referenceLines.ts +56 -0
- package/src/math/spline.ts +183 -0
- package/src/math/squiggly.ts +108 -0
- package/src/theme.ts +231 -0
- package/src/types.ts +789 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isValidElement,
|
|
3
|
+
type ReactElement,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useEffect,
|
|
6
|
+
useReducer,
|
|
7
|
+
useRef,
|
|
8
|
+
} from "react";
|
|
9
|
+
import { StyleSheet, View, type ViewStyle } from "react-native";
|
|
10
|
+
import Animated, {
|
|
11
|
+
useAnimatedStyle,
|
|
12
|
+
useSharedValue,
|
|
13
|
+
withTiming,
|
|
14
|
+
} from "react-native-reanimated";
|
|
15
|
+
|
|
16
|
+
export interface LiveChartTransitionProps {
|
|
17
|
+
/** Key of the active child to show. Must match a child's `key`. */
|
|
18
|
+
active: string;
|
|
19
|
+
/** Chart elements, each with a unique `key`. */
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
/** Cross-fade duration in ms. Default `300`. */
|
|
22
|
+
duration?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Keep every child mounted for the lifetime of the transition (only the
|
|
25
|
+
* opacity animates). Each chart's engine then settles its y-range once, up
|
|
26
|
+
* front, so switching is a pure cross-fade with no reveal/range re-animation.
|
|
27
|
+
* Costs more (all engines run continuously). Default `false` (mount the
|
|
28
|
+
* active child + unmount the outgoing one after the fade).
|
|
29
|
+
*/
|
|
30
|
+
keepMounted?: boolean;
|
|
31
|
+
/** Container style. */
|
|
32
|
+
style?: ViewStyle;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function FadeLayer({
|
|
36
|
+
visible,
|
|
37
|
+
duration,
|
|
38
|
+
children,
|
|
39
|
+
}: {
|
|
40
|
+
visible: boolean;
|
|
41
|
+
duration: number;
|
|
42
|
+
children: ReactNode;
|
|
43
|
+
}) {
|
|
44
|
+
const opacity = useSharedValue(visible ? 1 : 0);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
opacity.set(withTiming(visible ? 1 : 0, { duration }));
|
|
47
|
+
}, [visible, duration, opacity]);
|
|
48
|
+
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.get() }));
|
|
49
|
+
return (
|
|
50
|
+
<Animated.View
|
|
51
|
+
pointerEvents={visible ? "auto" : "none"}
|
|
52
|
+
style={[StyleSheet.absoluteFill, animatedStyle]}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
</Animated.View>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type MountedAction =
|
|
60
|
+
| { type: "mount"; key: string }
|
|
61
|
+
| { type: "unmount"; key: string };
|
|
62
|
+
|
|
63
|
+
/** Tracks which child keys are currently mounted during a cross-fade. */
|
|
64
|
+
function mountedReducer(
|
|
65
|
+
mounted: Set<string>,
|
|
66
|
+
action: MountedAction,
|
|
67
|
+
): Set<string> {
|
|
68
|
+
switch (action.type) {
|
|
69
|
+
case "mount":
|
|
70
|
+
if (mounted.has(action.key)) return mounted;
|
|
71
|
+
return new Set(mounted).add(action.key);
|
|
72
|
+
case "unmount": {
|
|
73
|
+
if (!mounted.has(action.key)) return mounted;
|
|
74
|
+
const next = new Set(mounted);
|
|
75
|
+
next.delete(action.key);
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Cross-fades between chart instances by `key` (e.g. line ↔ candle). Mounts the
|
|
83
|
+
* active child plus any outgoing child still fading out; unmounts the outgoing
|
|
84
|
+
* child once the fade completes. RN/Reanimated analogue of liveline's
|
|
85
|
+
* `LivelineTransition`.
|
|
86
|
+
*/
|
|
87
|
+
export function LiveChartTransition({
|
|
88
|
+
active,
|
|
89
|
+
children,
|
|
90
|
+
duration = 300,
|
|
91
|
+
keepMounted = false,
|
|
92
|
+
style,
|
|
93
|
+
}: LiveChartTransitionProps) {
|
|
94
|
+
// Use the raw children (not Children.toArray, which rewrites keys to ".$key").
|
|
95
|
+
const childArray = (
|
|
96
|
+
Array.isArray(children) ? children : [children]
|
|
97
|
+
).filter(isValidElement) as ReactElement[];
|
|
98
|
+
|
|
99
|
+
const [mounted, dispatch] = useReducer(
|
|
100
|
+
mountedReducer,
|
|
101
|
+
active,
|
|
102
|
+
(initial) => new Set([initial]),
|
|
103
|
+
);
|
|
104
|
+
const prevRef = useRef(active);
|
|
105
|
+
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
if (keepMounted) return;
|
|
108
|
+
if (active === prevRef.current) return;
|
|
109
|
+
const outgoing = prevRef.current;
|
|
110
|
+
prevRef.current = active;
|
|
111
|
+
dispatch({ type: "mount", key: active });
|
|
112
|
+
const timer = setTimeout(() => {
|
|
113
|
+
dispatch({ type: "unmount", key: outgoing });
|
|
114
|
+
}, duration + 50);
|
|
115
|
+
return () => clearTimeout(timer);
|
|
116
|
+
}, [active, duration, keepMounted]);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<View style={[styles.root, style]}>
|
|
120
|
+
{childArray.map((child) => {
|
|
121
|
+
const key = String(child.key ?? "");
|
|
122
|
+
if (!keepMounted && !mounted.has(key)) return null;
|
|
123
|
+
return (
|
|
124
|
+
<FadeLayer key={key} visible={key === active} duration={duration}>
|
|
125
|
+
{child}
|
|
126
|
+
</FadeLayer>
|
|
127
|
+
);
|
|
128
|
+
})}
|
|
129
|
+
</View>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const styles = StyleSheet.create({
|
|
134
|
+
root: { flex: 1, position: "relative" },
|
|
135
|
+
});
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Group,
|
|
3
|
+
LinearGradient,
|
|
4
|
+
Path,
|
|
5
|
+
Rect,
|
|
6
|
+
RoundedRect,
|
|
7
|
+
Skia,
|
|
8
|
+
Text as SkiaText,
|
|
9
|
+
vec,
|
|
10
|
+
type SkFont,
|
|
11
|
+
type SkPath,
|
|
12
|
+
} from "@shopify/react-native-skia";
|
|
13
|
+
import { useRef } from "react";
|
|
14
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
15
|
+
import {
|
|
16
|
+
BADGE_DOT_GAP,
|
|
17
|
+
EMPTY_GAP_FADE_WIDTH,
|
|
18
|
+
EMPTY_STATE_LABEL_ALPHA,
|
|
19
|
+
EMPTY_TEXT_GAP_PAD,
|
|
20
|
+
} from "../constants";
|
|
21
|
+
import {
|
|
22
|
+
badgeTailAndCap,
|
|
23
|
+
gutterCenteredTextLeftX,
|
|
24
|
+
pillTextLeftX,
|
|
25
|
+
type ChartPadding,
|
|
26
|
+
} from "../draw/line";
|
|
27
|
+
import { drawSpline } from "../math/spline";
|
|
28
|
+
import { buildSquigglyPts } from "../math/squiggly";
|
|
29
|
+
import type { LiveChartPalette } from "../types";
|
|
30
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
31
|
+
|
|
32
|
+
const PLACEHOLDER_LABEL_COUNT = 4;
|
|
33
|
+
const RECT_W = 16;
|
|
34
|
+
const RECT_H = 4;
|
|
35
|
+
const RECT_R = 4;
|
|
36
|
+
const RECT_SPACING = 32;
|
|
37
|
+
|
|
38
|
+
function buildSplineInto(path: ReturnType<typeof Skia.Path.Make>, pts: number[]) {
|
|
39
|
+
"worklet";
|
|
40
|
+
path.reset();
|
|
41
|
+
const n = pts.length >> 1;
|
|
42
|
+
if (n < 2) return path;
|
|
43
|
+
path.moveTo(pts[0], pts[1]);
|
|
44
|
+
drawSpline(path, pts);
|
|
45
|
+
return path;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function LoadingOverlay({
|
|
49
|
+
engine,
|
|
50
|
+
padding,
|
|
51
|
+
palette,
|
|
52
|
+
font,
|
|
53
|
+
morphT,
|
|
54
|
+
isLoading,
|
|
55
|
+
isEmpty,
|
|
56
|
+
emptyText,
|
|
57
|
+
strokeWidth,
|
|
58
|
+
badge = false,
|
|
59
|
+
badgeTail = true,
|
|
60
|
+
}: {
|
|
61
|
+
engine: ChartEngineLayout;
|
|
62
|
+
padding: ChartPadding;
|
|
63
|
+
palette: LiveChartPalette;
|
|
64
|
+
font: SkFont;
|
|
65
|
+
morphT: SharedValue<number>;
|
|
66
|
+
isLoading: SharedValue<boolean>;
|
|
67
|
+
/** Derived or shared; only `.value` is read on the UI thread. */
|
|
68
|
+
isEmpty: SharedValue<boolean> | { value: boolean };
|
|
69
|
+
emptyText: string;
|
|
70
|
+
strokeWidth: number;
|
|
71
|
+
/** Mirror the badge prop so labels align with GridOverlay's label positions. */
|
|
72
|
+
badge?: boolean;
|
|
73
|
+
/** Whether the badge tail spike is shown; affects the left inset used for skeleton alignment. */
|
|
74
|
+
badgeTail?: boolean;
|
|
75
|
+
}) {
|
|
76
|
+
// Same left-inset formula as GridOverlay (only used when badge=true)
|
|
77
|
+
const leftInset = BADGE_DOT_GAP + badgeTailAndCap(font.getSize(), badgeTail);
|
|
78
|
+
|
|
79
|
+
// Ping-pong persistent paths — avoid allocating a JSI-backed SkPath per frame
|
|
80
|
+
// while the squiggly loading/empty-state animation is running.
|
|
81
|
+
const squigglyCacheRef = useRef<{
|
|
82
|
+
a: SkPath;
|
|
83
|
+
b: SkPath;
|
|
84
|
+
tick: boolean;
|
|
85
|
+
} | null>(null);
|
|
86
|
+
if (squigglyCacheRef.current === null) {
|
|
87
|
+
squigglyCacheRef.current = {
|
|
88
|
+
a: Skia.Path.Make(),
|
|
89
|
+
b: Skia.Path.Make(),
|
|
90
|
+
tick: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Squiggly path — animated each frame via timestamp
|
|
95
|
+
const squigglyPath = useDerivedValue(() => {
|
|
96
|
+
const squigglyCache = squigglyCacheRef.current!;
|
|
97
|
+
squigglyCache.tick = !squigglyCache.tick;
|
|
98
|
+
const path = squigglyCache.tick ? squigglyCache.a : squigglyCache.b;
|
|
99
|
+
if (!isLoading.get() && !isEmpty.value && morphT.get() >= 1) {
|
|
100
|
+
path.reset();
|
|
101
|
+
return path;
|
|
102
|
+
}
|
|
103
|
+
const pts = buildSquigglyPts(
|
|
104
|
+
engine.canvasWidth.get(),
|
|
105
|
+
engine.canvasHeight.get(),
|
|
106
|
+
padding,
|
|
107
|
+
engine.timestamp.get(),
|
|
108
|
+
);
|
|
109
|
+
return buildSplineInto(path, pts);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Fades out quickly as the reveal animation begins (first 33% of morphT)
|
|
113
|
+
const groupOpacity = useDerivedValue(() => {
|
|
114
|
+
if (!isLoading.get() && !isEmpty.value) {
|
|
115
|
+
return Math.max(0, 1 - morphT.get() * 3);
|
|
116
|
+
}
|
|
117
|
+
return 1;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Placeholder Y-axis rects: skeleton pills centred in the right gutter
|
|
121
|
+
const labelLayout = useDerivedValue(() => {
|
|
122
|
+
const w = engine.canvasWidth.get();
|
|
123
|
+
const h = engine.canvasHeight.get();
|
|
124
|
+
if (w === 0 || h === 0) {
|
|
125
|
+
return { x: -200, ys: [0, 0, 0, 0] as [number, number, number, number] };
|
|
126
|
+
}
|
|
127
|
+
const chartH = h - padding.top - padding.bottom;
|
|
128
|
+
// Mirror GridOverlay: pillTextLeftX when badge is on, gutterCenteredTextLeftX otherwise
|
|
129
|
+
const x = badge
|
|
130
|
+
? pillTextLeftX(w, padding.right, leftInset, RECT_W)
|
|
131
|
+
: gutterCenteredTextLeftX(w, padding.right, RECT_W);
|
|
132
|
+
// Centre the group of rects around the chart's vertical midpoint
|
|
133
|
+
const groupH = (PLACEHOLDER_LABEL_COUNT - 1) * RECT_SPACING;
|
|
134
|
+
const groupStartCY = padding.top + chartH / 2 - groupH / 2;
|
|
135
|
+
const ys: [number, number, number, number] = [0, 0, 0, 0];
|
|
136
|
+
for (let i = 0; i < PLACEHOLDER_LABEL_COUNT; i++) {
|
|
137
|
+
const rowCenterY = groupStartCY + i * RECT_SPACING;
|
|
138
|
+
ys[i] = rowCenterY - RECT_H / 2; // top of rect so center lands on rowCenterY
|
|
139
|
+
}
|
|
140
|
+
return { x, ys };
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// X and Y positions for the placeholder rects
|
|
144
|
+
const lx = useDerivedValue(() => labelLayout.get().x);
|
|
145
|
+
const ly0 = useDerivedValue(() => labelLayout.get().ys[0]);
|
|
146
|
+
const ly1 = useDerivedValue(() => labelLayout.get().ys[1]);
|
|
147
|
+
const ly2 = useDerivedValue(() => labelLayout.get().ys[2]);
|
|
148
|
+
const ly3 = useDerivedValue(() => labelLayout.get().ys[3]);
|
|
149
|
+
|
|
150
|
+
// Empty-state text + gradient gap
|
|
151
|
+
const emptyGapLayout = useDerivedValue(() => {
|
|
152
|
+
const w = engine.canvasWidth.get();
|
|
153
|
+
const h = engine.canvasHeight.get();
|
|
154
|
+
if (w === 0 || h === 0) {
|
|
155
|
+
return {
|
|
156
|
+
gapLeft: 0,
|
|
157
|
+
gapRight: 0,
|
|
158
|
+
centerY: 0,
|
|
159
|
+
eraseH: 0,
|
|
160
|
+
fadeW: EMPTY_GAP_FADE_WIDTH,
|
|
161
|
+
textX: 0,
|
|
162
|
+
textY: 0,
|
|
163
|
+
textW: 0,
|
|
164
|
+
showGap: 0,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const chartH = h - padding.top - padding.bottom;
|
|
168
|
+
const centerY = padding.top + chartH / 2;
|
|
169
|
+
const chartCentreX = (padding.left + w - padding.right) / 2;
|
|
170
|
+
const textW = font.measureText(emptyText).width;
|
|
171
|
+
const gapHalf = textW / 2 + EMPTY_TEXT_GAP_PAD;
|
|
172
|
+
const fadeW = EMPTY_GAP_FADE_WIDTH;
|
|
173
|
+
const gapLeft = chartCentreX - gapHalf - fadeW;
|
|
174
|
+
const gapRight = chartCentreX + gapHalf + fadeW;
|
|
175
|
+
const eraseH = Math.max(56, chartH * 0.28) + strokeWidth;
|
|
176
|
+
const fm = font.getMetrics();
|
|
177
|
+
const textX = chartCentreX - textW / 2;
|
|
178
|
+
const textY = centerY - (fm.ascent + fm.descent) / 2;
|
|
179
|
+
return {
|
|
180
|
+
gapLeft,
|
|
181
|
+
gapRight,
|
|
182
|
+
centerY,
|
|
183
|
+
eraseH,
|
|
184
|
+
fadeW,
|
|
185
|
+
textX,
|
|
186
|
+
textY,
|
|
187
|
+
textW,
|
|
188
|
+
showGap: isEmpty.value ? 1 : 0,
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const gapLeft = useDerivedValue(() => emptyGapLayout.get().gapLeft);
|
|
193
|
+
const gapTop = useDerivedValue(
|
|
194
|
+
() => emptyGapLayout.get().centerY - emptyGapLayout.get().eraseH / 2,
|
|
195
|
+
);
|
|
196
|
+
const gapWidth = useDerivedValue(
|
|
197
|
+
() => emptyGapLayout.get().gapRight - emptyGapLayout.get().gapLeft,
|
|
198
|
+
);
|
|
199
|
+
const gapHeight = useDerivedValue(() => emptyGapLayout.get().eraseH);
|
|
200
|
+
const emptyTextX = useDerivedValue(() => emptyGapLayout.get().textX);
|
|
201
|
+
const emptyTextY = useDerivedValue(() => emptyGapLayout.get().textY);
|
|
202
|
+
const showGapGroup = useDerivedValue(() => emptyGapLayout.get().showGap);
|
|
203
|
+
|
|
204
|
+
const gapGradientPositions = useDerivedValue(() => {
|
|
205
|
+
const L = emptyGapLayout.get().gapRight - emptyGapLayout.get().gapLeft;
|
|
206
|
+
const fw = emptyGapLayout.get().fadeW;
|
|
207
|
+
if (L <= 0) return [0, 0, 1, 1] as number[];
|
|
208
|
+
const t1 = Math.min(fw / L, 0.49);
|
|
209
|
+
const t2 = Math.max(1 - fw / L, 0.51);
|
|
210
|
+
return [0, t1, t2, 1];
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const gapGradEnd = useDerivedValue(() =>
|
|
214
|
+
vec(
|
|
215
|
+
Math.max(1, emptyGapLayout.get().gapRight - emptyGapLayout.get().gapLeft),
|
|
216
|
+
0,
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const emptyLabelText = useDerivedValue(() =>
|
|
221
|
+
isEmpty.value ? emptyText : "",
|
|
222
|
+
);
|
|
223
|
+
const emptyLabelOpacity = useDerivedValue(() =>
|
|
224
|
+
isEmpty.value ? EMPTY_STATE_LABEL_ALPHA : 0,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<Group opacity={groupOpacity}>
|
|
229
|
+
{/* Squiggly loading line */}
|
|
230
|
+
<Path
|
|
231
|
+
path={squigglyPath}
|
|
232
|
+
style="stroke"
|
|
233
|
+
strokeWidth={strokeWidth}
|
|
234
|
+
color={palette.gridLine}
|
|
235
|
+
strokeCap="round"
|
|
236
|
+
strokeJoin="round"
|
|
237
|
+
/>
|
|
238
|
+
|
|
239
|
+
{/* Erase a soft horizontal band through the squiggle for the label (dstOut) */}
|
|
240
|
+
<Group opacity={showGapGroup} blendMode="dstOut">
|
|
241
|
+
<Rect x={gapLeft} y={gapTop} width={gapWidth} height={gapHeight}>
|
|
242
|
+
<LinearGradient
|
|
243
|
+
start={vec(0, 0)}
|
|
244
|
+
end={gapGradEnd}
|
|
245
|
+
colors={[
|
|
246
|
+
"rgba(0,0,0,0)",
|
|
247
|
+
"rgba(0,0,0,1)",
|
|
248
|
+
"rgba(0,0,0,1)",
|
|
249
|
+
"rgba(0,0,0,0)",
|
|
250
|
+
]}
|
|
251
|
+
positions={gapGradientPositions}
|
|
252
|
+
/>
|
|
253
|
+
</Rect>
|
|
254
|
+
</Group>
|
|
255
|
+
|
|
256
|
+
{/* Skeleton Y-axis label placeholders */}
|
|
257
|
+
<RoundedRect
|
|
258
|
+
x={lx}
|
|
259
|
+
y={ly0}
|
|
260
|
+
width={RECT_W}
|
|
261
|
+
height={RECT_H}
|
|
262
|
+
r={RECT_R}
|
|
263
|
+
color={palette.gridLine}
|
|
264
|
+
/>
|
|
265
|
+
<RoundedRect
|
|
266
|
+
x={lx}
|
|
267
|
+
y={ly1}
|
|
268
|
+
width={RECT_W}
|
|
269
|
+
height={RECT_H}
|
|
270
|
+
r={RECT_R}
|
|
271
|
+
color={palette.gridLine}
|
|
272
|
+
/>
|
|
273
|
+
<RoundedRect
|
|
274
|
+
x={lx}
|
|
275
|
+
y={ly2}
|
|
276
|
+
width={RECT_W}
|
|
277
|
+
height={RECT_H}
|
|
278
|
+
r={RECT_R}
|
|
279
|
+
color={palette.gridLine}
|
|
280
|
+
/>
|
|
281
|
+
<RoundedRect
|
|
282
|
+
x={lx}
|
|
283
|
+
y={ly3}
|
|
284
|
+
width={RECT_W}
|
|
285
|
+
height={RECT_H}
|
|
286
|
+
r={RECT_R}
|
|
287
|
+
color={palette.gridLine}
|
|
288
|
+
/>
|
|
289
|
+
|
|
290
|
+
{/* Empty state label */}
|
|
291
|
+
<SkiaText
|
|
292
|
+
x={emptyTextX}
|
|
293
|
+
y={emptyTextY}
|
|
294
|
+
text={emptyLabelText}
|
|
295
|
+
font={font}
|
|
296
|
+
color={palette.gridLabel}
|
|
297
|
+
opacity={emptyLabelOpacity}
|
|
298
|
+
/>
|
|
299
|
+
</Group>
|
|
300
|
+
);
|
|
301
|
+
}
|