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,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Group,
|
|
3
|
+
Text as SkiaText,
|
|
4
|
+
type SkFont,
|
|
5
|
+
} from "@shopify/react-native-skia";
|
|
6
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
7
|
+
import type { ChartPadding } from "../draw/line";
|
|
8
|
+
import type { TradeMarker } from "../draw/trade";
|
|
9
|
+
import { lerpColor } from "../math/color";
|
|
10
|
+
import type { LiveChartPalette } from "../types";
|
|
11
|
+
|
|
12
|
+
const MAX_TRADE_LABELS = 20;
|
|
13
|
+
|
|
14
|
+
// Stable per-slot keys for the fixed-size, positional label pool: slot `i` always
|
|
15
|
+
// renders ring-buffer index `i` and the tape never reorders, so each label keeps a
|
|
16
|
+
// permanent identity. Keying by these ids (instead of the bare array index) makes
|
|
17
|
+
// that explicit and avoids remounting labels as the tape scrolls.
|
|
18
|
+
const TAPE_SLOT_KEYS = Array.from(
|
|
19
|
+
{ length: MAX_TRADE_LABELS },
|
|
20
|
+
(_, i) => `trade-label-${i}`,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const GREEN: [number, number, number] = [34, 197, 94];
|
|
24
|
+
const RED: [number, number, number] = [239, 68, 68];
|
|
25
|
+
|
|
26
|
+
function TapeLabel({
|
|
27
|
+
index,
|
|
28
|
+
markers,
|
|
29
|
+
bgRgb,
|
|
30
|
+
labelX,
|
|
31
|
+
font,
|
|
32
|
+
groupOpacity,
|
|
33
|
+
}: {
|
|
34
|
+
index: number;
|
|
35
|
+
markers: SharedValue<TradeMarker[]>;
|
|
36
|
+
bgRgb: [number, number, number];
|
|
37
|
+
labelX: number;
|
|
38
|
+
font: SkFont;
|
|
39
|
+
groupOpacity: SharedValue<number>;
|
|
40
|
+
}) {
|
|
41
|
+
/* istanbul ignore next -- worklet */
|
|
42
|
+
const y = useDerivedValue(() => markers.value[index]?.y ?? -200);
|
|
43
|
+
/* istanbul ignore next -- worklet */
|
|
44
|
+
const text = useDerivedValue(() => markers.value[index]?.label ?? " ");
|
|
45
|
+
|
|
46
|
+
/* istanbul ignore next -- worklet */
|
|
47
|
+
const fillColor = useDerivedValue(() => {
|
|
48
|
+
const m = markers.value[index];
|
|
49
|
+
if (!m) return "transparent";
|
|
50
|
+
const base = m.green ? GREEN : RED;
|
|
51
|
+
return lerpColor(base, bgRgb, 1 - m.alpha);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/* istanbul ignore next -- worklet */
|
|
55
|
+
const outlineColor = useDerivedValue(() => {
|
|
56
|
+
const m = markers.value[index];
|
|
57
|
+
if (!m) return "transparent";
|
|
58
|
+
return `rgb(${bgRgb[0]},${bgRgb[1]},${bgRgb[2]})`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/* istanbul ignore next -- worklet */
|
|
62
|
+
const opacity = useDerivedValue(() => {
|
|
63
|
+
const m = markers.value[index];
|
|
64
|
+
if (!m) return 0;
|
|
65
|
+
return groupOpacity.value;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Group opacity={opacity}>
|
|
70
|
+
<SkiaText
|
|
71
|
+
x={labelX}
|
|
72
|
+
y={y}
|
|
73
|
+
text={text}
|
|
74
|
+
font={font}
|
|
75
|
+
color={outlineColor}
|
|
76
|
+
style="stroke"
|
|
77
|
+
strokeWidth={0.5}
|
|
78
|
+
opacity={0.75}
|
|
79
|
+
/>
|
|
80
|
+
<SkiaText x={labelX} y={y} text={text} font={font} color={fillColor} />
|
|
81
|
+
</Group>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function TradeStreamOverlay({
|
|
86
|
+
markers,
|
|
87
|
+
palette,
|
|
88
|
+
padding,
|
|
89
|
+
font,
|
|
90
|
+
opacity,
|
|
91
|
+
labelOffsetX = 8,
|
|
92
|
+
}: {
|
|
93
|
+
markers: SharedValue<TradeMarker[]>;
|
|
94
|
+
palette: LiveChartPalette;
|
|
95
|
+
padding: ChartPadding;
|
|
96
|
+
font: SkFont;
|
|
97
|
+
opacity: SharedValue<number>;
|
|
98
|
+
labelOffsetX?: number;
|
|
99
|
+
}) {
|
|
100
|
+
const labelX = padding.left + labelOffsetX;
|
|
101
|
+
// Fixed-size persistent slot pool (MAX_TRADE_LABELS). Each <TapeLabel> renders
|
|
102
|
+
// whatever trade currently occupies slot `i` (read from `markers` by index);
|
|
103
|
+
// the list never reorders or filters, so each slot keys by its stable per-slot
|
|
104
|
+
// id (see TAPE_SLOT_KEYS) rather than the bare array index.
|
|
105
|
+
const slots = TAPE_SLOT_KEYS.map((slotKey, i) => (
|
|
106
|
+
<TapeLabel
|
|
107
|
+
key={slotKey}
|
|
108
|
+
index={i}
|
|
109
|
+
markers={markers}
|
|
110
|
+
bgRgb={palette.bgRgb}
|
|
111
|
+
labelX={labelX}
|
|
112
|
+
font={font}
|
|
113
|
+
groupOpacity={opacity}
|
|
114
|
+
/>
|
|
115
|
+
));
|
|
116
|
+
return <Group>{slots}</Group>;
|
|
117
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DashPathEffect,
|
|
3
|
+
Path,
|
|
4
|
+
Skia,
|
|
5
|
+
type SkPath,
|
|
6
|
+
} from "@shopify/react-native-skia";
|
|
7
|
+
import { useRef } from "react";
|
|
8
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
9
|
+
|
|
10
|
+
import type { ChartPadding } from "../draw/line";
|
|
11
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A dashed horizontal line that tracks the live display value — sitting at
|
|
15
|
+
* the same Y as the badge and live dot. Rendered behind the chart line.
|
|
16
|
+
*/
|
|
17
|
+
export function ValueLineOverlay({
|
|
18
|
+
dotY,
|
|
19
|
+
engine,
|
|
20
|
+
padding,
|
|
21
|
+
strokeWidth,
|
|
22
|
+
intervals,
|
|
23
|
+
color,
|
|
24
|
+
}: {
|
|
25
|
+
dotY: SharedValue<number>;
|
|
26
|
+
engine: ChartEngineLayout;
|
|
27
|
+
padding: ChartPadding;
|
|
28
|
+
strokeWidth: number;
|
|
29
|
+
intervals: [number, number];
|
|
30
|
+
color: string;
|
|
31
|
+
}) {
|
|
32
|
+
// Ping-pong persistent paths — avoid allocating a JSI-backed SkPath per frame.
|
|
33
|
+
const cacheRef = useRef<{
|
|
34
|
+
a: SkPath;
|
|
35
|
+
b: SkPath;
|
|
36
|
+
tick: boolean;
|
|
37
|
+
} | null>(null);
|
|
38
|
+
if (cacheRef.current === null) {
|
|
39
|
+
cacheRef.current = {
|
|
40
|
+
a: Skia.Path.Make(),
|
|
41
|
+
b: Skia.Path.Make(),
|
|
42
|
+
tick: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const path = useDerivedValue(() => {
|
|
47
|
+
const cache = cacheRef.current!;
|
|
48
|
+
cache.tick = !cache.tick;
|
|
49
|
+
const p = cache.tick ? cache.a : cache.b;
|
|
50
|
+
p.reset();
|
|
51
|
+
const y = dotY.get();
|
|
52
|
+
if (y < 0) return p;
|
|
53
|
+
p.moveTo(padding.left, y);
|
|
54
|
+
p.lineTo(engine.canvasWidth.get() - padding.right, y);
|
|
55
|
+
return p;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Path path={path} style="stroke" strokeWidth={strokeWidth} color={color}>
|
|
60
|
+
<DashPathEffect intervals={intervals} />
|
|
61
|
+
</Path>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Text as SkiaText, type SkFont } from "@shopify/react-native-skia";
|
|
2
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
3
|
+
import type { ChartEngineWithLiveValue } from "../core/useLiveChartEngine";
|
|
4
|
+
import type { ChartPadding } from "../draw/line";
|
|
5
|
+
import type { LiveChartPalette, Momentum } from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Large live-value text rendered in the top-left of the plot. Tracks the
|
|
9
|
+
* engine's smoothed display value on the UI thread; optionally tinted by
|
|
10
|
+
* momentum (green up / red down / accent flat).
|
|
11
|
+
*/
|
|
12
|
+
export function ValueTextOverlay({
|
|
13
|
+
engine,
|
|
14
|
+
padding,
|
|
15
|
+
palette,
|
|
16
|
+
font,
|
|
17
|
+
formatValue,
|
|
18
|
+
momentum,
|
|
19
|
+
momentumColor,
|
|
20
|
+
}: {
|
|
21
|
+
engine: ChartEngineWithLiveValue;
|
|
22
|
+
padding: ChartPadding;
|
|
23
|
+
palette: LiveChartPalette;
|
|
24
|
+
font: SkFont;
|
|
25
|
+
formatValue: (v: number) => string;
|
|
26
|
+
momentum?: SharedValue<Momentum>;
|
|
27
|
+
momentumColor: boolean;
|
|
28
|
+
}) {
|
|
29
|
+
const text = useDerivedValue(() => formatValue(engine.displayValue.value));
|
|
30
|
+
|
|
31
|
+
const color = useDerivedValue(() => {
|
|
32
|
+
if (!momentumColor || !momentum) return palette.line;
|
|
33
|
+
const m = momentum.value;
|
|
34
|
+
if (m === "up") return palette.dotUp;
|
|
35
|
+
if (m === "down") return palette.dotDown;
|
|
36
|
+
return palette.dotFlat;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const x = padding.left + 6;
|
|
40
|
+
const y = padding.top + font.getSize();
|
|
41
|
+
|
|
42
|
+
return <SkiaText x={x} y={y} text={text} font={font} color={color} />;
|
|
43
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Group,
|
|
3
|
+
Path,
|
|
4
|
+
Skia,
|
|
5
|
+
type SkFont,
|
|
6
|
+
type SkPath,
|
|
7
|
+
} from "@shopify/react-native-skia";
|
|
8
|
+
import { useRef } from "react";
|
|
9
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
10
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
11
|
+
import type { ChartPadding } from "../draw/line";
|
|
12
|
+
import type { XAxisEntry } from "../hooks/useXAxis";
|
|
13
|
+
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
14
|
+
import type { LiveChartPalette } from "../types";
|
|
15
|
+
import { AnimatedLabel } from "./AnimatedLabel";
|
|
16
|
+
|
|
17
|
+
const MAX_X_LABELS = 10;
|
|
18
|
+
const TICK_HEIGHT = 5;
|
|
19
|
+
const LABEL_OFFSET_Y = 19;
|
|
20
|
+
|
|
21
|
+
export function XAxisOverlay({
|
|
22
|
+
entries,
|
|
23
|
+
engine,
|
|
24
|
+
padding,
|
|
25
|
+
palette,
|
|
26
|
+
font,
|
|
27
|
+
}: {
|
|
28
|
+
entries: SharedValue<XAxisEntry[]>;
|
|
29
|
+
engine: ChartEngineLayout;
|
|
30
|
+
padding: ChartPadding;
|
|
31
|
+
palette: LiveChartPalette;
|
|
32
|
+
font: SkFont;
|
|
33
|
+
}) {
|
|
34
|
+
const axisCacheRef = useRef<{
|
|
35
|
+
a: SkPath;
|
|
36
|
+
b: SkPath;
|
|
37
|
+
tick: boolean;
|
|
38
|
+
} | null>(null);
|
|
39
|
+
if (axisCacheRef.current === null) {
|
|
40
|
+
axisCacheRef.current = {
|
|
41
|
+
a: Skia.Path.Make(),
|
|
42
|
+
b: Skia.Path.Make(),
|
|
43
|
+
tick: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const axisPath = useDerivedValue(() => {
|
|
48
|
+
"worklet";
|
|
49
|
+
const axisCache = axisCacheRef.current!;
|
|
50
|
+
axisCache.tick = !axisCache.tick;
|
|
51
|
+
const path = axisCache.tick ? axisCache.a : axisCache.b;
|
|
52
|
+
path.reset();
|
|
53
|
+
const w = engine.canvasWidth.get();
|
|
54
|
+
const h = engine.canvasHeight.get();
|
|
55
|
+
const lineY = h - padding.bottom;
|
|
56
|
+
|
|
57
|
+
// Bottom axis line
|
|
58
|
+
path.moveTo(padding.left, lineY);
|
|
59
|
+
path.lineTo(w - padding.right, lineY);
|
|
60
|
+
|
|
61
|
+
// Tick marks
|
|
62
|
+
const items = entries.get();
|
|
63
|
+
for (let i = 0; i < items.length; i++) {
|
|
64
|
+
path.moveTo(items[i].x, lineY);
|
|
65
|
+
path.lineTo(items[i].x, lineY + TICK_HEIGHT);
|
|
66
|
+
}
|
|
67
|
+
return path;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Transform XAxisEntry[] into { x, y, label, alpha } for AnimatedLabel
|
|
71
|
+
const labelEntries = useDerivedValue(() => {
|
|
72
|
+
"worklet";
|
|
73
|
+
const items = entries.get();
|
|
74
|
+
const h = engine.canvasHeight.get();
|
|
75
|
+
const y = h - padding.bottom + LABEL_OFFSET_Y;
|
|
76
|
+
const n = items.length;
|
|
77
|
+
const out: { x: number; y: number; label: string; alpha: number }[] = [];
|
|
78
|
+
for (let i = 0; i < n; i++) {
|
|
79
|
+
const e = items[i];
|
|
80
|
+
out.push({
|
|
81
|
+
x: e.x - measureFontTextWidth(font, e.label) / 2,
|
|
82
|
+
y,
|
|
83
|
+
label: e.label,
|
|
84
|
+
alpha: e.alpha,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Group>
|
|
92
|
+
<Path
|
|
93
|
+
path={axisPath}
|
|
94
|
+
style="stroke"
|
|
95
|
+
strokeWidth={1}
|
|
96
|
+
color={palette.gridLine}
|
|
97
|
+
/>
|
|
98
|
+
{Array.from({ length: MAX_X_LABELS }, (_, i) => (
|
|
99
|
+
<AnimatedLabel
|
|
100
|
+
key={i}
|
|
101
|
+
entries={labelEntries}
|
|
102
|
+
index={i}
|
|
103
|
+
font={font}
|
|
104
|
+
color={palette.timeLabel}
|
|
105
|
+
/>
|
|
106
|
+
))}
|
|
107
|
+
</Group>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DashPathEffect,
|
|
3
|
+
Group,
|
|
4
|
+
Path,
|
|
5
|
+
Skia,
|
|
6
|
+
type SkFont,
|
|
7
|
+
type SkPath,
|
|
8
|
+
} from "@shopify/react-native-skia";
|
|
9
|
+
import { useRef } from "react";
|
|
10
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
11
|
+
import { BADGE_DOT_GAP } from "../constants";
|
|
12
|
+
import type { ResolvedGridStyleConfig } from "../core/resolveConfig";
|
|
13
|
+
import type { YAxisEntry } from "../draw/grid";
|
|
14
|
+
import {
|
|
15
|
+
badgeTailAndCap,
|
|
16
|
+
gutterCenteredTextLeftX,
|
|
17
|
+
gutterRightAlignedTextLeftX,
|
|
18
|
+
pillTextLeftX,
|
|
19
|
+
type ChartPadding,
|
|
20
|
+
} from "../draw/line";
|
|
21
|
+
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
22
|
+
import type { LiveChartPalette } from "../types";
|
|
23
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
24
|
+
import { AnimatedLabel } from "./AnimatedLabel";
|
|
25
|
+
|
|
26
|
+
const MAX_Y_LABELS = 15;
|
|
27
|
+
|
|
28
|
+
export function YAxisOverlay({
|
|
29
|
+
entries,
|
|
30
|
+
engine,
|
|
31
|
+
padding,
|
|
32
|
+
palette,
|
|
33
|
+
font,
|
|
34
|
+
badge = false,
|
|
35
|
+
badgeTail = true,
|
|
36
|
+
seriesLabelInset = 0,
|
|
37
|
+
gridStyle,
|
|
38
|
+
}: {
|
|
39
|
+
entries: SharedValue<YAxisEntry[]>;
|
|
40
|
+
engine: ChartEngineLayout;
|
|
41
|
+
padding: ChartPadding;
|
|
42
|
+
palette: LiveChartPalette;
|
|
43
|
+
font: SkFont;
|
|
44
|
+
/** When true, use the asymmetric pill centering formula so labels align with badge text. */
|
|
45
|
+
badge?: boolean;
|
|
46
|
+
/** Whether the badge tail spike is shown; affects the left inset used for label alignment. */
|
|
47
|
+
badgeTail?: boolean;
|
|
48
|
+
/** When > 0, series labels occupy the left portion of the gutter; Y-axis labels right-align. */
|
|
49
|
+
seriesLabelInset?: number;
|
|
50
|
+
/** Grid-line styling overrides. Omit for the legacy solid 1px line. */
|
|
51
|
+
gridStyle?: ResolvedGridStyleConfig;
|
|
52
|
+
}) {
|
|
53
|
+
const gridColor = gridStyle?.color ?? palette.gridLine;
|
|
54
|
+
const gridWidth = gridStyle?.strokeWidth ?? 1;
|
|
55
|
+
const gridIntervals = gridStyle?.intervals ?? [];
|
|
56
|
+
const gridOpacity = gridStyle?.opacity ?? 1;
|
|
57
|
+
const gridCacheRef = useRef<{
|
|
58
|
+
a: SkPath;
|
|
59
|
+
b: SkPath;
|
|
60
|
+
tick: boolean;
|
|
61
|
+
} | null>(null);
|
|
62
|
+
if (gridCacheRef.current === null) {
|
|
63
|
+
gridCacheRef.current = {
|
|
64
|
+
a: Skia.Path.Make(),
|
|
65
|
+
b: Skia.Path.Make(),
|
|
66
|
+
tick: false,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const gridLinesPath = useDerivedValue(() => {
|
|
71
|
+
const gridCache = gridCacheRef.current!;
|
|
72
|
+
gridCache.tick = !gridCache.tick;
|
|
73
|
+
const path = gridCache.tick ? gridCache.a : gridCache.b;
|
|
74
|
+
path.reset();
|
|
75
|
+
const items = entries.get();
|
|
76
|
+
const w = engine.canvasWidth.get();
|
|
77
|
+
for (let i = 0; i < items.length; i++) {
|
|
78
|
+
path.moveTo(padding.left, items[i].y);
|
|
79
|
+
path.lineTo(w - padding.right, items[i].y);
|
|
80
|
+
}
|
|
81
|
+
return path;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const leftInset = BADGE_DOT_GAP + badgeTailAndCap(font.getSize(), badgeTail);
|
|
85
|
+
|
|
86
|
+
const labelEntries = useDerivedValue(() => {
|
|
87
|
+
const items = entries.get();
|
|
88
|
+
const w = engine.canvasWidth.get();
|
|
89
|
+
const fm = font.getMetrics();
|
|
90
|
+
const baselineOffset = (fm.ascent + fm.descent) / 2;
|
|
91
|
+
const result: { x: number; y: number; label: string; alpha: number }[] = [];
|
|
92
|
+
for (let i = 0; i < items.length; i++) {
|
|
93
|
+
const e = items[i];
|
|
94
|
+
const textW = measureFontTextWidth(font, e.label);
|
|
95
|
+
const x = badge
|
|
96
|
+
? pillTextLeftX(w, padding.right, leftInset, textW)
|
|
97
|
+
: seriesLabelInset > 0
|
|
98
|
+
? gutterRightAlignedTextLeftX(w, textW)
|
|
99
|
+
: gutterCenteredTextLeftX(w, padding.right, textW);
|
|
100
|
+
result.push({
|
|
101
|
+
x,
|
|
102
|
+
y: e.y - baselineOffset,
|
|
103
|
+
label: e.label,
|
|
104
|
+
alpha: e.alpha,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Group>
|
|
112
|
+
<Group opacity={gridOpacity}>
|
|
113
|
+
<Path
|
|
114
|
+
path={gridLinesPath}
|
|
115
|
+
style="stroke"
|
|
116
|
+
strokeWidth={gridWidth}
|
|
117
|
+
color={gridColor}
|
|
118
|
+
>
|
|
119
|
+
{gridIntervals.length > 0 && (
|
|
120
|
+
<DashPathEffect intervals={gridIntervals} />
|
|
121
|
+
)}
|
|
122
|
+
</Path>
|
|
123
|
+
</Group>
|
|
124
|
+
{Array.from({ length: MAX_Y_LABELS }, (_, i) => (
|
|
125
|
+
<AnimatedLabel
|
|
126
|
+
key={i}
|
|
127
|
+
entries={labelEntries}
|
|
128
|
+
index={i}
|
|
129
|
+
font={font}
|
|
130
|
+
color={palette.gridLabel}
|
|
131
|
+
/>
|
|
132
|
+
))}
|
|
133
|
+
</Group>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SeriesConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Exported for tests — mirrors chip labels without subscribing to every data tick. */
|
|
4
|
+
export function seriesMetaSig(s: SeriesConfig[]): string {
|
|
5
|
+
"worklet";
|
|
6
|
+
let out = "";
|
|
7
|
+
for (let i = 0; i < s.length; i++) {
|
|
8
|
+
if (i > 0) out += "\x1e";
|
|
9
|
+
const x = s[i];
|
|
10
|
+
out += `${x.id}\x1f${x.label ?? ""}\x1f${x.color ?? ""}\x1f${x.visible === false ? 0 : 1}`;
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Milliseconds per frame at 60 fps — baseline for frame-rate-independent lerp. */
|
|
2
|
+
export const MS_PER_FRAME_60FPS = 16.67;
|
|
3
|
+
|
|
4
|
+
/** Extra speed applied to catch up when the live dot lags behind the target. */
|
|
5
|
+
export const ADAPTIVE_SPEED_BOOST = 0.12;
|
|
6
|
+
|
|
7
|
+
/** Horizontal padding inside the badge pill, on each side of the label. */
|
|
8
|
+
export const BADGE_PILL_PAD_X = 10;
|
|
9
|
+
/** Vertical padding above and below the label inside the badge pill. */
|
|
10
|
+
export const BADGE_PILL_PAD_Y = 3;
|
|
11
|
+
/** Length of the badge tail (the pointed spike toward the dot). */
|
|
12
|
+
export const BADGE_TAIL_LEN = 5;
|
|
13
|
+
/** Gap between the pill's right edge and the canvas right edge. */
|
|
14
|
+
export const BADGE_MARGIN_RIGHT = 4;
|
|
15
|
+
/** Gap between the live dot and the badge tail tip. */
|
|
16
|
+
export const BADGE_DOT_GAP = 12;
|
|
17
|
+
|
|
18
|
+
/** Maximum simultaneous series rendered (paths/dots slots). */
|
|
19
|
+
export const MAX_MULTI_SERIES = 12;
|
|
20
|
+
|
|
21
|
+
/** Default width (px) of the left-edge fade band */
|
|
22
|
+
export const FADE_EDGE_WIDTH = 40;
|
|
23
|
+
|
|
24
|
+
/** Empty-state label opacity. */
|
|
25
|
+
export const EMPTY_STATE_LABEL_ALPHA = 0.35;
|
|
26
|
+
/** Half-padding (px) around empty text for the squiggle “gap” erase band. */
|
|
27
|
+
export const EMPTY_TEXT_GAP_PAD = 20;
|
|
28
|
+
/** Horizontal fade width (px) on each side of the empty-text gap. */
|
|
29
|
+
export const EMPTY_GAP_FADE_WIDTH = 30;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Floats per degen particle slot: `x, y, vx, vy, t0, active, size, colorIndex`.
|
|
33
|
+
* `colorIndex` selects which entry of the renderer's color list a particle uses
|
|
34
|
+
* (multi-series sets it per series; single-series cycles it per particle).
|
|
35
|
+
* Layout is fixed; not on `DegenOptions` because changing it needs matching renderer changes.
|
|
36
|
+
*/
|
|
37
|
+
export const DEGEN_STRIDE = 8;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default chart accent color — the full light/dark palette is derived from this
|
|
41
|
+
* when a consumer omits `accentColor`. Single source of truth for the brand
|
|
42
|
+
* default; change it here to re-brand the default everywhere.
|
|
43
|
+
*/
|
|
44
|
+
export const DEFAULT_ACCENT_COLOR = "#3323E6";
|