react-native-livechart 3.0.0 → 3.1.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/README.md +9 -3
- package/dist/components/AxisLabelOverlay.d.ts +29 -0
- package/dist/components/AxisLabelOverlay.d.ts.map +1 -0
- package/dist/components/CrosshairLine.d.ts +12 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +15 -1
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/DegenParticlesOverlay.d.ts +17 -0
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
- package/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/components/SelectionDot.d.ts +32 -0
- package/dist/components/SelectionDot.d.ts.map +1 -0
- package/dist/core/resolveConfig.d.ts +46 -1
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +6 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/draw/particleAtlas.d.ts +39 -0
- package/dist/draw/particleAtlas.d.ts.map +1 -0
- package/dist/hooks/crosshairShared.d.ts +10 -0
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/useCandlePaths.d.ts +3 -1
- package/dist/hooks/useCandlePaths.d.ts.map +1 -1
- package/dist/hooks/useChartColors.d.ts +5 -0
- package/dist/hooks/useChartColors.d.ts.map +1 -1
- package/dist/hooks/useChartReveal.d.ts +3 -1
- package/dist/hooks/useChartReveal.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
- package/dist/hooks/useDegen.d.ts +3 -1
- package/dist/hooks/useDegen.d.ts.map +1 -1
- package/dist/hooks/useMarkers.d.ts +3 -1
- package/dist/hooks/useMarkers.d.ts.map +1 -1
- package/dist/hooks/useTradeStream.d.ts +3 -1
- package/dist/hooks/useTradeStream.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AxisLabelOverlay.tsx +133 -0
- package/src/components/CrosshairLine.tsx +27 -0
- package/src/components/CrosshairOverlay.tsx +50 -18
- package/src/components/DegenParticlesOverlay.tsx +93 -109
- package/src/components/LiveChart.tsx +66 -10
- package/src/components/LiveChartSeries.tsx +40 -0
- package/src/components/SelectionDot.tsx +92 -0
- package/src/core/resolveConfig.ts +98 -0
- package/src/core/useLiveChartEngine.ts +78 -27
- package/src/draw/particleAtlas.ts +95 -0
- package/src/hooks/crosshairShared.ts +26 -0
- package/src/hooks/useCandlePaths.ts +3 -1
- package/src/hooks/useChartColors.ts +19 -0
- package/src/hooks/useChartReveal.ts +7 -4
- package/src/hooks/useCrosshair.ts +46 -7
- package/src/hooks/useCrosshairSeries.ts +47 -8
- package/src/hooks/useDegen.ts +6 -0
- package/src/hooks/useMarkers.ts +3 -0
- package/src/hooks/useTradeStream.ts +3 -0
- package/src/index.ts +4 -0
- package/src/types.ts +101 -0
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useDerivedValue,
|
|
6
6
|
useSharedValue,
|
|
7
7
|
} from "react-native-reanimated";
|
|
8
|
+
import { scheduleOnRN } from "react-native-worklets";
|
|
8
9
|
import type { MultiEngineState } from "../core/useLiveChartEngine";
|
|
9
10
|
import { type ChartPadding } from "../draw/line";
|
|
10
11
|
import type { ScrubPointMulti } from "../types";
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
} from "./crosshairSeries";
|
|
15
16
|
import {
|
|
16
17
|
computeCrosshairOpacity,
|
|
18
|
+
computeScrubDotY,
|
|
17
19
|
computeScrubTime,
|
|
18
20
|
HIDDEN_TOOLTIP,
|
|
19
21
|
type CrosshairState,
|
|
@@ -30,9 +32,14 @@ export function useCrosshairSeries(
|
|
|
30
32
|
onScrub?: (point: ScrubPointMulti | null) => void,
|
|
31
33
|
/** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
|
|
32
34
|
panGestureDelay = 0,
|
|
35
|
+
onGestureStart?: () => void,
|
|
36
|
+
onGestureEnd?: () => void,
|
|
33
37
|
): CrosshairState {
|
|
34
38
|
const scrubX = useSharedValue(-1);
|
|
35
39
|
const scrubActive = useSharedValue(false);
|
|
40
|
+
// Tracks whether the active scrub phase actually began, so a tap that never
|
|
41
|
+
// activates doesn't emit a spurious onGestureEnd.
|
|
42
|
+
const gestureStarted = useSharedValue(false);
|
|
36
43
|
|
|
37
44
|
const scrubTime = useDerivedValue(() =>
|
|
38
45
|
computeScrubTime(
|
|
@@ -62,9 +69,34 @@ export function useCrosshairSeries(
|
|
|
62
69
|
),
|
|
63
70
|
);
|
|
64
71
|
|
|
72
|
+
// Y pixel of the scrub intersection (leading-series value) — used by the
|
|
73
|
+
// selection dot. -1 when there's no value to mark.
|
|
74
|
+
const scrubDotY = useDerivedValue(() =>
|
|
75
|
+
computeScrubDotY(
|
|
76
|
+
scrubValue.get(),
|
|
77
|
+
engine.displayMin.get(),
|
|
78
|
+
engine.displayMax.get(),
|
|
79
|
+
engine.canvasHeight.get(),
|
|
80
|
+
padding.top,
|
|
81
|
+
padding.bottom,
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
|
|
65
85
|
const tooltipLayout = useSharedValue(HIDDEN_TOOLTIP);
|
|
66
86
|
|
|
87
|
+
/* istanbul ignore next */
|
|
88
|
+
function handleGestureStart() {
|
|
89
|
+
onGestureStart?.();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* istanbul ignore next */
|
|
93
|
+
function handleGestureEnd() {
|
|
94
|
+
onGestureEnd?.();
|
|
95
|
+
}
|
|
96
|
+
|
|
67
97
|
const hasOnScrub = onScrub != null;
|
|
98
|
+
const hasOnGestureStart = onGestureStart != null;
|
|
99
|
+
const hasOnGestureEnd = onGestureEnd != null;
|
|
68
100
|
|
|
69
101
|
useAnimatedReaction(
|
|
70
102
|
() => {
|
|
@@ -77,14 +109,14 @@ export function useCrosshairSeries(
|
|
|
77
109
|
if (chartW <= 0) return "__pending__";
|
|
78
110
|
const r = interpolateSeriesAtTime(engine.series.get(), time);
|
|
79
111
|
if (r.primary === null) return "__pending__";
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
const dotY = computeScrubDotY(
|
|
113
|
+
r.primary,
|
|
114
|
+
engine.displayMin.get(),
|
|
115
|
+
engine.displayMax.get(),
|
|
116
|
+
engine.canvasHeight.get(),
|
|
117
|
+
padding.top,
|
|
118
|
+
padding.bottom,
|
|
119
|
+
);
|
|
88
120
|
return JSON.stringify({
|
|
89
121
|
time,
|
|
90
122
|
x,
|
|
@@ -136,6 +168,8 @@ export function useCrosshairSeries(
|
|
|
136
168
|
if (!enabled) return;
|
|
137
169
|
scrubX.set(e.x);
|
|
138
170
|
scrubActive.set(true);
|
|
171
|
+
gestureStarted.set(true);
|
|
172
|
+
if (hasOnGestureStart) scheduleOnRN(handleGestureStart);
|
|
139
173
|
},
|
|
140
174
|
)
|
|
141
175
|
.onUpdate(
|
|
@@ -149,6 +183,10 @@ export function useCrosshairSeries(
|
|
|
149
183
|
/* istanbul ignore next */ () => {
|
|
150
184
|
"worklet";
|
|
151
185
|
scrubActive.set(false);
|
|
186
|
+
if (gestureStarted.get()) {
|
|
187
|
+
gestureStarted.set(false);
|
|
188
|
+
if (hasOnGestureEnd) scheduleOnRN(handleGestureEnd);
|
|
189
|
+
}
|
|
152
190
|
},
|
|
153
191
|
);
|
|
154
192
|
|
|
@@ -164,6 +202,7 @@ export function useCrosshairSeries(
|
|
|
164
202
|
scrubValue,
|
|
165
203
|
crosshairOpacity,
|
|
166
204
|
tooltipLayout,
|
|
205
|
+
scrubDotY,
|
|
167
206
|
gesture,
|
|
168
207
|
};
|
|
169
208
|
}
|
package/src/hooks/useDegen.ts
CHANGED
|
@@ -20,6 +20,8 @@ export function useDegen(
|
|
|
20
20
|
momentumSV: SharedValue<Momentum>,
|
|
21
21
|
cfg: ResolvedDegenConfig | null,
|
|
22
22
|
onShake?: (payload: DegenShakePayload) => void,
|
|
23
|
+
/** Static charts run no loops: register the frame callback without starting it. */
|
|
24
|
+
isStatic = false,
|
|
23
25
|
): {
|
|
24
26
|
pack: SharedValue<Float64Array<ArrayBuffer>>;
|
|
25
27
|
packRevision: SharedValue<number>;
|
|
@@ -248,6 +250,10 @@ export function useDegen(
|
|
|
248
250
|
}
|
|
249
251
|
prevActiveCount.set(activeCount);
|
|
250
252
|
},
|
|
253
|
+
// Static charts register the callback but never start it, so no degen frame
|
|
254
|
+
// loop runs. Live charts keep the default (autostart) loop unchanged. The
|
|
255
|
+
// controller also forces `cfg` to null in static, so degen is doubly off.
|
|
256
|
+
!isStatic,
|
|
251
257
|
);
|
|
252
258
|
|
|
253
259
|
const shakeTransform = useDerivedValue(() => {
|
package/src/hooks/useMarkers.ts
CHANGED
|
@@ -30,6 +30,8 @@ export function useMarkers(
|
|
|
30
30
|
onMarkerHover?: (event: MarkerHoverEvent | null) => void,
|
|
31
31
|
seriesSV?: SharedValue<SeriesConfig[]>,
|
|
32
32
|
lineData?: SharedValue<LiveChartPoint[]>,
|
|
33
|
+
/** Static charts run no loops: register without starting. Default `true`. */
|
|
34
|
+
autostart = true,
|
|
33
35
|
): { projected: SharedValue<ProjectedMarker[]>; tapGesture: ReturnType<typeof Gesture.Tap> } {
|
|
34
36
|
const projected = useSharedValue<ProjectedMarker[]>([]);
|
|
35
37
|
const cacheRef = useRef<{
|
|
@@ -73,6 +75,7 @@ export function useMarkers(
|
|
|
73
75
|
});
|
|
74
76
|
projected.set(buf);
|
|
75
77
|
},
|
|
78
|
+
autostart,
|
|
76
79
|
);
|
|
77
80
|
|
|
78
81
|
const tapGesture = Gesture.Tap().onEnd(
|
|
@@ -25,6 +25,8 @@ export function useTradeStream(
|
|
|
25
25
|
tradeStream: SharedValue<TradeEvent[]>,
|
|
26
26
|
padding: ChartPadding,
|
|
27
27
|
active: boolean,
|
|
28
|
+
/** Static charts run no loops: register without starting. Default `true`. */
|
|
29
|
+
autostart = true,
|
|
28
30
|
): SharedValue<TradeMarker[]> {
|
|
29
31
|
const markers = useSharedValue<TradeMarker[]>([]);
|
|
30
32
|
const state = useSharedValue<TradeStreamState>(createTradeStreamState());
|
|
@@ -58,6 +60,7 @@ export function useTradeStream(
|
|
|
58
60
|
const chartH = chartBottom - chartTop;
|
|
59
61
|
markers.set(projectLabels(s, chartTop, chartH));
|
|
60
62
|
},
|
|
63
|
+
autostart,
|
|
61
64
|
);
|
|
62
65
|
|
|
63
66
|
return markers;
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { useTradeStream } from "./hooks/useTradeStream";
|
|
|
28
28
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
29
29
|
|
|
30
30
|
export type {
|
|
31
|
+
AxisLabelConfig,
|
|
31
32
|
BadgeConfig,
|
|
32
33
|
BadgeMetrics,
|
|
33
34
|
BadgeVariant,
|
|
@@ -67,6 +68,9 @@ export type {
|
|
|
67
68
|
ScrubPointCore,
|
|
68
69
|
ScrubPointMulti,
|
|
69
70
|
ScrubSeriesValue,
|
|
71
|
+
SelectionDotConfig,
|
|
72
|
+
SelectionDotProps,
|
|
73
|
+
SelectionDotRingConfig,
|
|
70
74
|
SeriesConfig,
|
|
71
75
|
ThemeMode,
|
|
72
76
|
TradeEvent,
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement } from "react";
|
|
1
2
|
import type { ViewStyle } from "react-native";
|
|
2
3
|
import type { SharedValue } from "react-native-reanimated";
|
|
3
4
|
|
|
@@ -150,6 +151,11 @@ export interface GradientConfig {
|
|
|
150
151
|
topOpacity?: number;
|
|
151
152
|
/** Opacity at the bottom of the gradient. Default `0`. */
|
|
152
153
|
bottomOpacity?: number;
|
|
154
|
+
/** Explicit gradient color stops (top → bottom) for the area fill. Overrides
|
|
155
|
+
* topOpacity/bottomOpacity when provided. Must have at least 2 entries. */
|
|
156
|
+
colors?: string[];
|
|
157
|
+
/** Optional stop positions (0..1, ascending) matching `colors` length. */
|
|
158
|
+
positions?: number[];
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
/** Value badge pill configuration. */
|
|
@@ -170,6 +176,24 @@ export interface YAxisConfig {
|
|
|
170
176
|
minGap?: number;
|
|
171
177
|
}
|
|
172
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Axis edge label — the value floated at the plot's top or bottom edge
|
|
181
|
+
* (Robinhood-style high/low). Pass `topLabel`/`bottomLabel` as `true` for the
|
|
182
|
+
* batteries-included value label (the chart's current top/bottom Y-axis bound,
|
|
183
|
+
* updated each frame), an object to configure it, or `{ render }` to float a
|
|
184
|
+
* fully custom element instead.
|
|
185
|
+
*/
|
|
186
|
+
export interface AxisLabelConfig {
|
|
187
|
+
/** Formatter for the built-in value. Defaults to the chart's `formatValue`. */
|
|
188
|
+
format?: (v: number) => string;
|
|
189
|
+
/** Text color. Defaults to a muted label color (`palette.gridLabel`). */
|
|
190
|
+
color?: string;
|
|
191
|
+
/** Horizontal alignment within the plot width. Default `"right"`. */
|
|
192
|
+
position?: "left" | "right";
|
|
193
|
+
/** Full custom element, floated at the edge. Overrides the built-in value label. */
|
|
194
|
+
render?: () => ReactElement | null;
|
|
195
|
+
}
|
|
196
|
+
|
|
173
197
|
/** X-axis (time) configuration. */
|
|
174
198
|
export interface XAxisConfig {
|
|
175
199
|
/** Minimum pixel gap between time labels. Default `60`. */
|
|
@@ -213,6 +237,55 @@ export interface ScrubConfig {
|
|
|
213
237
|
panGestureDelay?: number;
|
|
214
238
|
}
|
|
215
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Props passed to a custom {@link SelectionDotConfig.component} — the dot drawn
|
|
242
|
+
* at the scrub intersection while scrubbing. All positional inputs are
|
|
243
|
+
* SharedValues so the dot animates on the UI thread without re-renders.
|
|
244
|
+
*/
|
|
245
|
+
export interface SelectionDotProps {
|
|
246
|
+
/** Scrub X in canvas px. */
|
|
247
|
+
x: SharedValue<number>;
|
|
248
|
+
/** Scrub Y in canvas px (the line/value intersection). */
|
|
249
|
+
y: SharedValue<number>;
|
|
250
|
+
/** Whether scrubbing is active. */
|
|
251
|
+
active: SharedValue<boolean>;
|
|
252
|
+
/** Crosshair fade opacity (0..1), already ramped. */
|
|
253
|
+
opacity: SharedValue<number>;
|
|
254
|
+
/** Resolved dot color (accent/series color). */
|
|
255
|
+
color: string;
|
|
256
|
+
/** Suggested dot radius in px. */
|
|
257
|
+
size: number;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Outer ring drawn around the built-in selection dot (the subtle halo). */
|
|
261
|
+
export interface SelectionDotRingConfig {
|
|
262
|
+
/** Ring color. Defaults to the dot color. */
|
|
263
|
+
color?: string;
|
|
264
|
+
/** Ring thickness in pixels — how far the halo extends past the dot. Default `2`. */
|
|
265
|
+
width?: number;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Selection-dot styling — the dot drawn at the scrub intersection while
|
|
270
|
+
* scrubbing. Pass `selectionDot={false}` to hide it, an object to configure the
|
|
271
|
+
* built-in dot, or `{ component }` to supply a fully custom Skia dot.
|
|
272
|
+
*/
|
|
273
|
+
export interface SelectionDotConfig {
|
|
274
|
+
/** Dot radius in px. Default `4`. */
|
|
275
|
+
size?: number;
|
|
276
|
+
/** Dot color. Defaults to the line / leading-series color. */
|
|
277
|
+
color?: string;
|
|
278
|
+
/**
|
|
279
|
+
* Outer ring around the dot. `true`/config = on, `false` = off. Default on.
|
|
280
|
+
*/
|
|
281
|
+
ring?: boolean | SelectionDotRingConfig;
|
|
282
|
+
/**
|
|
283
|
+
* Fully custom Skia dot — receives the scrub position as SharedValues. When
|
|
284
|
+
* set, the `size` / `color` / `ring` knobs are ignored.
|
|
285
|
+
*/
|
|
286
|
+
component?: ComponentType<SelectionDotProps>;
|
|
287
|
+
}
|
|
288
|
+
|
|
216
289
|
/** Left-edge fade — soft erase so the chart blends into the left gutter (web liveline parity). */
|
|
217
290
|
export interface LeftEdgeFadeConfig {
|
|
218
291
|
/**
|
|
@@ -695,6 +768,20 @@ export interface LiveChartCoreProps {
|
|
|
695
768
|
yAxis?: boolean | YAxisConfig;
|
|
696
769
|
/** X-axis time labels. `true` = defaults, `false` = hidden, or pass `XAxisConfig`. Default `true`. */
|
|
697
770
|
xAxis?: boolean | XAxisConfig;
|
|
771
|
+
/**
|
|
772
|
+
* Label floated at the top edge of the plot area. `true` = the built-in value
|
|
773
|
+
* label showing the chart's current TOP Y-axis bound (updated each frame),
|
|
774
|
+
* `false`/omitted = none, or pass `AxisLabelConfig` to configure it (or supply
|
|
775
|
+
* a custom `render`). Default off.
|
|
776
|
+
*/
|
|
777
|
+
topLabel?: boolean | AxisLabelConfig;
|
|
778
|
+
/**
|
|
779
|
+
* Label floated at the bottom edge of the plot area. `true` = the built-in
|
|
780
|
+
* value label showing the chart's current BOTTOM Y-axis bound (updated each
|
|
781
|
+
* frame), `false`/omitted = none, or pass `AxisLabelConfig` (or a custom
|
|
782
|
+
* `render`). Default off.
|
|
783
|
+
*/
|
|
784
|
+
bottomLabel?: boolean | AxisLabelConfig;
|
|
698
785
|
/** Reference lines / bands drawn into the chart. Supports all three `ReferenceLine` forms. */
|
|
699
786
|
referenceLines?: ReferenceLine[];
|
|
700
787
|
/** Per-instance grid-line styling. Pass an object to override color / width / dash / opacity. */
|
|
@@ -737,6 +824,16 @@ export interface LiveChartCoreProps {
|
|
|
737
824
|
accessibilityRole?: "image" | "none" | "adjustable" | "summary";
|
|
738
825
|
/** Crosshair scrubbing on hover/drag. `true` = defaults, `false` = disabled, or pass `ScrubConfig`. Default `true`. */
|
|
739
826
|
scrub?: boolean | ScrubConfig;
|
|
827
|
+
/**
|
|
828
|
+
* Selection dot drawn at the scrub intersection while scrubbing. `true`/omitted
|
|
829
|
+
* = built-in dot, `false` = hidden, or pass `SelectionDotConfig` (`size`,
|
|
830
|
+
* `color`, `ring`, or a custom `component`). Default `true`.
|
|
831
|
+
*/
|
|
832
|
+
selectionDot?: boolean | SelectionDotConfig;
|
|
833
|
+
/** Called once when the user starts scrubbing/panning the chart. */
|
|
834
|
+
onGestureStart?: () => void;
|
|
835
|
+
/** Called once when the user stops scrubbing/panning the chart. */
|
|
836
|
+
onGestureEnd?: () => void;
|
|
740
837
|
/**
|
|
741
838
|
* Fade out chart content on the left (destination-out style). `true` = defaults, `false` = off,
|
|
742
839
|
* or pass `LeftEdgeFadeConfig`. Default `true`.
|
|
@@ -796,6 +893,10 @@ export interface LiveChartProps extends LiveChartCoreProps {
|
|
|
796
893
|
data: SharedValue<LiveChartPoint[]>;
|
|
797
894
|
/** Latest live value for smooth interpolation between data updates. */
|
|
798
895
|
value: SharedValue<number>;
|
|
896
|
+
/** Render once with no per-frame animation loop — for many small charts (sparklines)
|
|
897
|
+
* in a list. Pulse/scrub/degen and the entry animation are disabled. Frame the data
|
|
898
|
+
* with `timeWindow` + `nowOverride` (see the historical-data-fill pattern). */
|
|
899
|
+
static?: boolean;
|
|
799
900
|
/** Called when the user scrubs the crosshair. `null` when scrub ends. */
|
|
800
901
|
onScrub?: (point: ScrubPoint | null) => void;
|
|
801
902
|
/**
|