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,165 @@
|
|
|
1
|
+
import { Skia, type SkPath } from "@shopify/react-native-skia";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import {
|
|
4
|
+
useDerivedValue,
|
|
5
|
+
useFrameCallback,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
type SharedValue,
|
|
8
|
+
} from "react-native-reanimated";
|
|
9
|
+
import { MS_PER_FRAME_60FPS } from "../constants";
|
|
10
|
+
import type { SingleEngineState } from "../core/useLiveChartEngine";
|
|
11
|
+
import { buildCandleGeometry } from "../draw/candle";
|
|
12
|
+
import type { ChartPadding } from "../draw/line";
|
|
13
|
+
import { lerp } from "../math/lerp";
|
|
14
|
+
import type { CandlePoint } from "../types";
|
|
15
|
+
|
|
16
|
+
const CANDLE_WIDTH_LERP_SPEED = 0.08;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Persistent candle `SkPath`s — two per geometry slot (up/down bodies + up/down wicks).
|
|
20
|
+
* Each derived value ping-pongs between its pair and mutates the picked path in place
|
|
21
|
+
* so we don't allocate a new JSI-backed `SkPath` per frame. See {@link useChartPaths}
|
|
22
|
+
* for the rationale (this was the primary driver of the iOS memory climb under live
|
|
23
|
+
* ticking).
|
|
24
|
+
*/
|
|
25
|
+
export function useCandlePaths(
|
|
26
|
+
engine: SingleEngineState,
|
|
27
|
+
padding: ChartPadding,
|
|
28
|
+
candles: SharedValue<CandlePoint[]> | undefined,
|
|
29
|
+
liveCandle: SharedValue<CandlePoint | null> | undefined,
|
|
30
|
+
candleWidthSecs: number,
|
|
31
|
+
active: boolean,
|
|
32
|
+
) {
|
|
33
|
+
const targetCandleWidth = useDerivedValue(() => candleWidthSecs);
|
|
34
|
+
const displayCandleWidth = useSharedValue(candleWidthSecs);
|
|
35
|
+
|
|
36
|
+
const cacheRef = useRef<{
|
|
37
|
+
upBodiesA: SkPath;
|
|
38
|
+
upBodiesB: SkPath;
|
|
39
|
+
downBodiesA: SkPath;
|
|
40
|
+
downBodiesB: SkPath;
|
|
41
|
+
upWicksA: SkPath;
|
|
42
|
+
upWicksB: SkPath;
|
|
43
|
+
downWicksA: SkPath;
|
|
44
|
+
downWicksB: SkPath;
|
|
45
|
+
ubTick: boolean;
|
|
46
|
+
dbTick: boolean;
|
|
47
|
+
uwTick: boolean;
|
|
48
|
+
dwTick: boolean;
|
|
49
|
+
} | null>(null);
|
|
50
|
+
if (cacheRef.current === null) {
|
|
51
|
+
cacheRef.current = {
|
|
52
|
+
upBodiesA: Skia.Path.Make(),
|
|
53
|
+
upBodiesB: Skia.Path.Make(),
|
|
54
|
+
downBodiesA: Skia.Path.Make(),
|
|
55
|
+
downBodiesB: Skia.Path.Make(),
|
|
56
|
+
upWicksA: Skia.Path.Make(),
|
|
57
|
+
upWicksB: Skia.Path.Make(),
|
|
58
|
+
downWicksA: Skia.Path.Make(),
|
|
59
|
+
downWicksB: Skia.Path.Make(),
|
|
60
|
+
ubTick: false,
|
|
61
|
+
dbTick: false,
|
|
62
|
+
uwTick: false,
|
|
63
|
+
dwTick: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
useFrameCallback((frameInfo) => {
|
|
68
|
+
"worklet";
|
|
69
|
+
if (!active) return;
|
|
70
|
+
const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
|
|
71
|
+
displayCandleWidth.set(
|
|
72
|
+
lerp(
|
|
73
|
+
displayCandleWidth.get(),
|
|
74
|
+
targetCandleWidth.get(),
|
|
75
|
+
CANDLE_WIDTH_LERP_SPEED,
|
|
76
|
+
dt,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/* istanbul ignore next -- worklet */
|
|
82
|
+
const geometry = useDerivedValue(() => {
|
|
83
|
+
if (!active || !candles) return { bodies: [], wicks: [] };
|
|
84
|
+
return buildCandleGeometry(
|
|
85
|
+
candles.value,
|
|
86
|
+
liveCandle?.value ?? null,
|
|
87
|
+
padding,
|
|
88
|
+
engine.canvasWidth.value,
|
|
89
|
+
engine.canvasHeight.value,
|
|
90
|
+
engine.timestamp.value - engine.displayWindow.value,
|
|
91
|
+
engine.displayWindow.value,
|
|
92
|
+
engine.displayMin.value,
|
|
93
|
+
engine.displayMax.value,
|
|
94
|
+
displayCandleWidth.get(),
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/* istanbul ignore next -- worklet */
|
|
99
|
+
const upBodiesPath = useDerivedValue(() => {
|
|
100
|
+
const cache = cacheRef.current!;
|
|
101
|
+
cache.ubTick = !cache.ubTick;
|
|
102
|
+
const path = cache.ubTick ? cache.upBodiesA : cache.upBodiesB;
|
|
103
|
+
path.reset();
|
|
104
|
+
const { bodies } = geometry.value;
|
|
105
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
106
|
+
if (bodies[i].up) {
|
|
107
|
+
path.addRect(
|
|
108
|
+
Skia.XYWHRect(bodies[i].x, bodies[i].y, bodies[i].w, bodies[i].h),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return path;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/* istanbul ignore next -- worklet */
|
|
116
|
+
const downBodiesPath = useDerivedValue(() => {
|
|
117
|
+
const cache = cacheRef.current!;
|
|
118
|
+
cache.dbTick = !cache.dbTick;
|
|
119
|
+
const path = cache.dbTick ? cache.downBodiesA : cache.downBodiesB;
|
|
120
|
+
path.reset();
|
|
121
|
+
const { bodies } = geometry.value;
|
|
122
|
+
for (let i = 0; i < bodies.length; i++) {
|
|
123
|
+
if (!bodies[i].up) {
|
|
124
|
+
path.addRect(
|
|
125
|
+
Skia.XYWHRect(bodies[i].x, bodies[i].y, bodies[i].w, bodies[i].h),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return path;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/* istanbul ignore next -- worklet */
|
|
133
|
+
const upWicksPath = useDerivedValue(() => {
|
|
134
|
+
const cache = cacheRef.current!;
|
|
135
|
+
cache.uwTick = !cache.uwTick;
|
|
136
|
+
const path = cache.uwTick ? cache.upWicksA : cache.upWicksB;
|
|
137
|
+
path.reset();
|
|
138
|
+
const { wicks } = geometry.value;
|
|
139
|
+
for (let i = 0; i < wicks.length; i++) {
|
|
140
|
+
if (wicks[i].up) {
|
|
141
|
+
path.moveTo(wicks[i].x, wicks[i].y1);
|
|
142
|
+
path.lineTo(wicks[i].x, wicks[i].y2);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return path;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/* istanbul ignore next -- worklet */
|
|
149
|
+
const downWicksPath = useDerivedValue(() => {
|
|
150
|
+
const cache = cacheRef.current!;
|
|
151
|
+
cache.dwTick = !cache.dwTick;
|
|
152
|
+
const path = cache.dwTick ? cache.downWicksA : cache.downWicksB;
|
|
153
|
+
path.reset();
|
|
154
|
+
const { wicks } = geometry.value;
|
|
155
|
+
for (let i = 0; i < wicks.length; i++) {
|
|
156
|
+
if (!wicks[i].up) {
|
|
157
|
+
path.moveTo(wicks[i].x, wicks[i].y1);
|
|
158
|
+
path.lineTo(wicks[i].x, wicks[i].y2);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return path;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return { upBodiesPath, downBodiesPath, upWicksPath, downWicksPath } as const;
|
|
165
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
import type { LayoutChangeEvent } from "react-native";
|
|
4
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Track canvas dimensions via `onLayout` and sync them into engine shared values.
|
|
8
|
+
* Returns `{ layoutHeight, onLayout }` — pass `onLayout` to the container View
|
|
9
|
+
* and use `layoutHeight` for conditional rendering (e.g. minimum height guards).
|
|
10
|
+
*/
|
|
11
|
+
export function useCanvasLayout(engine: ChartEngineLayout) {
|
|
12
|
+
const [layoutHeight, setLayoutHeight] = useState(0);
|
|
13
|
+
|
|
14
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
15
|
+
const { width, height } = e.nativeEvent.layout;
|
|
16
|
+
engine.canvasWidth.set(width);
|
|
17
|
+
engine.canvasHeight.set(height);
|
|
18
|
+
setLayoutHeight(height);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return { layoutHeight, onLayout } as const;
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ResolvedGradientConfig } from "../core/resolveConfig";
|
|
2
|
+
import type { ChartPadding } from "../draw/line";
|
|
3
|
+
import { parseColorRgb } from "../theme";
|
|
4
|
+
import type { LiveChartPalette } from "../types";
|
|
5
|
+
|
|
6
|
+
export interface ChartColors {
|
|
7
|
+
/** Background color derived from the theme palette. */
|
|
8
|
+
backgroundColor: string;
|
|
9
|
+
/** Y coordinate of the gradient bottom edge — accounts for bottom inset. */
|
|
10
|
+
gradientEnd: number;
|
|
11
|
+
/** Top gradient stop — custom opacity or palette default. */
|
|
12
|
+
gradientTopColor: string;
|
|
13
|
+
/** Bottom gradient stop — custom opacity or palette default. */
|
|
14
|
+
gradientBottomColor: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Derives background and gradient colors from the resolved palette and
|
|
19
|
+
* optional GradientConfig opacities. The React Compiler memoizes the result
|
|
20
|
+
* so strings are only recomputed when inputs change.
|
|
21
|
+
*/
|
|
22
|
+
export function useChartColors(
|
|
23
|
+
palette: LiveChartPalette,
|
|
24
|
+
gradientCfg: ResolvedGradientConfig | null,
|
|
25
|
+
accentColor: string,
|
|
26
|
+
layoutHeight: number,
|
|
27
|
+
padding: ChartPadding,
|
|
28
|
+
): ChartColors {
|
|
29
|
+
const backgroundColor = `rgb(${palette.bgRgb[0]}, ${palette.bgRgb[1]}, ${palette.bgRgb[2]})`;
|
|
30
|
+
const gradientEnd = Math.max(1, layoutHeight - padding.bottom);
|
|
31
|
+
|
|
32
|
+
const [r, g, b] = parseColorRgb(accentColor);
|
|
33
|
+
const gradientTopColor =
|
|
34
|
+
gradientCfg?.topOpacity !== undefined
|
|
35
|
+
? `rgba(${r}, ${g}, ${b}, ${gradientCfg.topOpacity})`
|
|
36
|
+
: palette.fillTop;
|
|
37
|
+
const gradientBottomColor =
|
|
38
|
+
gradientCfg?.bottomOpacity !== undefined
|
|
39
|
+
? `rgba(${r}, ${g}, ${b}, ${gradientCfg.bottomOpacity})`
|
|
40
|
+
: palette.fillBottom;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
backgroundColor,
|
|
44
|
+
gradientEnd,
|
|
45
|
+
gradientTopColor,
|
|
46
|
+
gradientBottomColor,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Skia, type SkPath } from "@shopify/react-native-skia";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
4
|
+
import type { SingleEngineState } from "../core/useLiveChartEngine";
|
|
5
|
+
import { buildLinePoints, type ChartPadding } from "../draw/line";
|
|
6
|
+
import { drawSpline, makeSplineScratch } from "../math/spline";
|
|
7
|
+
import { blendPtsY, squigglifyPts } from "../math/squiggly";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Persistent `linePath` / `fillPath` `SkPath`s that are **mutated in place** each frame.
|
|
11
|
+
*
|
|
12
|
+
* Why not `useDerivedValue(() => Skia.Path.Make())`? Each call would allocate a brand-new
|
|
13
|
+
* JSI-backed `SkPath`, and on iOS the native GC lags the JS GC under a steady firehose —
|
|
14
|
+
* resident memory climbs steadily for as long as the chart is mounted. Here we allocate
|
|
15
|
+
* two paths per curve once and ping-pong the reference each frame so Reanimated's
|
|
16
|
+
* value-equality early-return still fires its subscribers (i.e. the Skia reanimated
|
|
17
|
+
* container re-records and repaints).
|
|
18
|
+
*/
|
|
19
|
+
export function useChartPaths(
|
|
20
|
+
engine: SingleEngineState,
|
|
21
|
+
padding: ChartPadding,
|
|
22
|
+
morphT?: SharedValue<number>,
|
|
23
|
+
) {
|
|
24
|
+
// Two persistent paths per curve; ping-pong so the returned reference changes
|
|
25
|
+
// every frame, which keeps Reanimated's setter from short-circuiting the notify.
|
|
26
|
+
const cacheRef = useRef<{
|
|
27
|
+
lineA: SkPath;
|
|
28
|
+
lineB: SkPath;
|
|
29
|
+
fillA: SkPath;
|
|
30
|
+
fillB: SkPath;
|
|
31
|
+
tick: boolean;
|
|
32
|
+
ptsA: number[];
|
|
33
|
+
ptsB: number[];
|
|
34
|
+
ptsTick: boolean;
|
|
35
|
+
scratch: ReturnType<typeof makeSplineScratch>;
|
|
36
|
+
} | null>(null);
|
|
37
|
+
if (cacheRef.current === null) {
|
|
38
|
+
cacheRef.current = {
|
|
39
|
+
lineA: Skia.Path.Make(),
|
|
40
|
+
lineB: Skia.Path.Make(),
|
|
41
|
+
fillA: Skia.Path.Make(),
|
|
42
|
+
fillB: Skia.Path.Make(),
|
|
43
|
+
tick: false,
|
|
44
|
+
// Ping-pong point buffers: reused across frames but the returned reference
|
|
45
|
+
// still alternates, so Reanimated keeps notifying linePath/fillPath.
|
|
46
|
+
ptsA: [] as number[],
|
|
47
|
+
ptsB: [] as number[],
|
|
48
|
+
ptsTick: false,
|
|
49
|
+
scratch: makeSplineScratch(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const flatPts = useDerivedValue(() => {
|
|
54
|
+
const cache = cacheRef.current!;
|
|
55
|
+
cache.ptsTick = !cache.ptsTick;
|
|
56
|
+
const buf = cache.ptsTick ? cache.ptsA : cache.ptsB;
|
|
57
|
+
const realPts = buildLinePoints(
|
|
58
|
+
engine.data.get(),
|
|
59
|
+
engine.displayValue.get(),
|
|
60
|
+
engine.timestamp.get(),
|
|
61
|
+
engine.displayWindow.get(),
|
|
62
|
+
engine.displayMin.get(),
|
|
63
|
+
engine.displayMax.get(),
|
|
64
|
+
engine.canvasWidth.get(),
|
|
65
|
+
engine.canvasHeight.get(),
|
|
66
|
+
padding,
|
|
67
|
+
buf,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Skip blending when fully revealed or no morphT provided
|
|
71
|
+
const t = morphT?.get() ?? 1;
|
|
72
|
+
if (t >= 1 || realPts.length === 0) return realPts;
|
|
73
|
+
|
|
74
|
+
// Compute squiggly Y values at the same X positions as the real line
|
|
75
|
+
const centerY =
|
|
76
|
+
(engine.canvasHeight.get() - padding.bottom + padding.top) / 2;
|
|
77
|
+
const squigglyPts = squigglifyPts(realPts, engine.timestamp.get(), centerY);
|
|
78
|
+
|
|
79
|
+
// Blend center-out: centre of chart reveals first, edges last
|
|
80
|
+
return blendPtsY(
|
|
81
|
+
squigglyPts,
|
|
82
|
+
realPts,
|
|
83
|
+
t,
|
|
84
|
+
padding,
|
|
85
|
+
engine.canvasWidth.get(),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const linePath = useDerivedValue(() => {
|
|
90
|
+
const cache = cacheRef.current!;
|
|
91
|
+
const pts = flatPts.get();
|
|
92
|
+
cache.tick = !cache.tick;
|
|
93
|
+
const path = cache.tick ? cache.lineA : cache.lineB;
|
|
94
|
+
path.reset();
|
|
95
|
+
const n = pts.length >> 1;
|
|
96
|
+
if (n < 2) return path;
|
|
97
|
+
path.moveTo(pts[0], pts[1]);
|
|
98
|
+
drawSpline(path, pts, cache.scratch);
|
|
99
|
+
return path;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const fillPath = useDerivedValue(() => {
|
|
103
|
+
const cache = cacheRef.current!;
|
|
104
|
+
const pts = flatPts.get();
|
|
105
|
+
// Use the same tick flag flipped by linePath — flipping again here would
|
|
106
|
+
// keep both paths in sync, so just read the current parity.
|
|
107
|
+
const path = cache.tick ? cache.fillA : cache.fillB;
|
|
108
|
+
path.reset();
|
|
109
|
+
const n = pts.length >> 1;
|
|
110
|
+
if (n < 2) return path;
|
|
111
|
+
path.moveTo(pts[0], pts[1]);
|
|
112
|
+
drawSpline(path, pts, cache.scratch);
|
|
113
|
+
const bottom = engine.canvasHeight.get() - padding.bottom;
|
|
114
|
+
path.lineTo(pts[(n - 1) * 2], bottom);
|
|
115
|
+
path.lineTo(pts[0], bottom);
|
|
116
|
+
path.close();
|
|
117
|
+
return path;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return { linePath, fillPath } as const;
|
|
121
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useLayoutEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Easing,
|
|
4
|
+
useAnimatedReaction,
|
|
5
|
+
useDerivedValue,
|
|
6
|
+
useSharedValue,
|
|
7
|
+
withTiming,
|
|
8
|
+
type SharedValue,
|
|
9
|
+
} from "react-native-reanimated";
|
|
10
|
+
import { smoothstep } from "../math/squiggly";
|
|
11
|
+
|
|
12
|
+
/** Duration for chart reveal (loading/empty → live) and collapse (live → empty). */
|
|
13
|
+
export const CHART_REVEAL_DURATION_MS = 600;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Per-overlay reveal delay (fraction of morphT at which the overlay starts
|
|
17
|
+
* fading in). Earlier delays = earlier appearance.
|
|
18
|
+
*/
|
|
19
|
+
const DELAY = {
|
|
20
|
+
fill: 0.05,
|
|
21
|
+
yAxis: 0.15,
|
|
22
|
+
line: 0.0,
|
|
23
|
+
dot: 0.4,
|
|
24
|
+
badge: 0.55,
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Maps a [0,1] morphT to a [0,1] opacity, starting at `delay` and reaching
|
|
29
|
+
* 1 at morphT=1. Uses smoothstep for a polished ease-in/out.
|
|
30
|
+
*/
|
|
31
|
+
export function revealRamp(morphT: number, delay: number): number {
|
|
32
|
+
"worklet";
|
|
33
|
+
if (delay >= 1) return 0;
|
|
34
|
+
return smoothstep((morphT - delay) / (1 - delay));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ChartRevealState {
|
|
38
|
+
/** 0 = loading/empty shell, 1 = fully live chart (matches web chartReveal). */
|
|
39
|
+
morphT: SharedValue<number>;
|
|
40
|
+
/** True while loading=true */
|
|
41
|
+
isLoading: SharedValue<boolean>;
|
|
42
|
+
/** True when not loading and fewer than two samples. */
|
|
43
|
+
isEmpty: SharedValue<boolean>;
|
|
44
|
+
yAxisOpacity: SharedValue<number>;
|
|
45
|
+
fillOpacity: SharedValue<number>;
|
|
46
|
+
lineOpacity: SharedValue<number>;
|
|
47
|
+
dotOpacity: SharedValue<number>;
|
|
48
|
+
badgeOpacity: SharedValue<number>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Drives loading / empty / live visibility.
|
|
53
|
+
*
|
|
54
|
+
* Chart is fully revealed only when `!loading && hasData`. `morphT` animates
|
|
55
|
+
* between 0 and 1 when that condition changes. `isEmpty` is derived as
|
|
56
|
+
* `!loading && !hasData` for the empty overlay label.
|
|
57
|
+
*/
|
|
58
|
+
export function useChartReveal(
|
|
59
|
+
loading: boolean,
|
|
60
|
+
hasData: SharedValue<boolean>,
|
|
61
|
+
): ChartRevealState {
|
|
62
|
+
// Best-guess initial so the common case — a live chart that already has data
|
|
63
|
+
// and isn't loading — paints fully revealed with no flash. The reaction below
|
|
64
|
+
// snaps it to the exact state on its first run.
|
|
65
|
+
const morphT = useSharedValue(loading ? 0 : 1);
|
|
66
|
+
const isLoading = useSharedValue(loading);
|
|
67
|
+
|
|
68
|
+
useLayoutEffect(() => {
|
|
69
|
+
isLoading.set(loading);
|
|
70
|
+
}, [loading, isLoading]);
|
|
71
|
+
|
|
72
|
+
// The reaction reads presence/loading on the UI thread (a worklet, never during
|
|
73
|
+
// render — so no Reanimated "reading `value` during render" warning), seeds the
|
|
74
|
+
// exact reveal state on its first run, then animates on later changes.
|
|
75
|
+
useAnimatedReaction(
|
|
76
|
+
() => !isLoading.get() && hasData.get(),
|
|
77
|
+
(chartVisible, prev) => {
|
|
78
|
+
"worklet";
|
|
79
|
+
if (prev === null || prev === undefined) {
|
|
80
|
+
// First run: snap to the correct initial reveal state (no animation).
|
|
81
|
+
morphT.set(chartVisible ? 1 : 0);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (prev !== chartVisible) {
|
|
85
|
+
morphT.set(
|
|
86
|
+
withTiming(chartVisible ? 1 : 0, {
|
|
87
|
+
duration: CHART_REVEAL_DURATION_MS,
|
|
88
|
+
easing: Easing.out(Easing.cubic),
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
[hasData],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const isEmpty = useDerivedValue(() => !isLoading.get() && !hasData.get());
|
|
97
|
+
|
|
98
|
+
const yAxisOpacity = useDerivedValue(() =>
|
|
99
|
+
revealRamp(morphT.get(), DELAY.yAxis),
|
|
100
|
+
);
|
|
101
|
+
const fillOpacity = useDerivedValue(() =>
|
|
102
|
+
revealRamp(morphT.get(), DELAY.fill),
|
|
103
|
+
);
|
|
104
|
+
const lineOpacity = useDerivedValue(() =>
|
|
105
|
+
revealRamp(morphT.get(), DELAY.line),
|
|
106
|
+
);
|
|
107
|
+
const dotOpacity = useDerivedValue(() => revealRamp(morphT.get(), DELAY.dot));
|
|
108
|
+
const badgeOpacity = useDerivedValue(() =>
|
|
109
|
+
revealRamp(morphT.get(), DELAY.badge),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
morphT,
|
|
114
|
+
isLoading,
|
|
115
|
+
// useDerivedValue is SharedValue-compatible for `.value` reads in Skia.
|
|
116
|
+
isEmpty: isEmpty as unknown as SharedValue<boolean>,
|
|
117
|
+
yAxisOpacity,
|
|
118
|
+
fillOpacity,
|
|
119
|
+
lineOpacity,
|
|
120
|
+
dotOpacity,
|
|
121
|
+
badgeOpacity,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
matchFont,
|
|
3
|
+
Skia,
|
|
4
|
+
useFont,
|
|
5
|
+
type SkFont,
|
|
6
|
+
} from "@shopify/react-native-skia";
|
|
7
|
+
|
|
8
|
+
import { resolveFontConfig } from "../core/resolveConfig";
|
|
9
|
+
import type { FontConfig } from "../types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolves Skia text for charts: optional bundled `typeface` via `useFont`, otherwise
|
|
13
|
+
* `matchFont` with optional custom `fontManager`.
|
|
14
|
+
*/
|
|
15
|
+
export function useChartSkiaFont(
|
|
16
|
+
fontProp: FontConfig | undefined,
|
|
17
|
+
defaultFamily: string,
|
|
18
|
+
defaultSize: number,
|
|
19
|
+
): SkFont {
|
|
20
|
+
const resolved = resolveFontConfig(fontProp, defaultFamily, defaultSize);
|
|
21
|
+
const { fontFamily, fontSize, fontWeight } = resolved;
|
|
22
|
+
const typefaceSource = fontProp?.typeface ?? null;
|
|
23
|
+
const customFont = useFont(typefaceSource, fontSize);
|
|
24
|
+
|
|
25
|
+
const fallbackFont = matchFont(
|
|
26
|
+
{ fontFamily, fontSize, fontWeight },
|
|
27
|
+
fontProp?.fontManager ?? Skia.FontMgr.System(),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (typefaceSource != null) {
|
|
31
|
+
return customFont ?? fallbackFont;
|
|
32
|
+
}
|
|
33
|
+
return fallbackFont;
|
|
34
|
+
}
|