import { Canvas, Path } from "@shopify/react-native-skia"; import { useCallback, useMemo, useState } from "react"; import { LayoutChangeEvent, StyleSheet, View } from "react-native"; import { GestureDetector } from "react-native-gesture-handler"; import { useSharedValue } from "react-native-reanimated"; import { AxisLabelContainer } from "./AxisLabel"; import { Cursor } from "./Cursor"; import { LineChartProps } from "./LineChart"; import { computePath, computeGraphData } from "./computations"; import { useGestures } from "./useGestures"; export const InteractiveLineChart: React.FC> = ({ points = [], precision = 2, strokeWidth = 2, cursorRadius = 8, cursorColor = "black", hideCursor = false, cursorLineColor = "rgba(0, 0, 0, 0.5)", cursorLineWidth = 2, hideCursorLine = false, curveType = "linear", TopAxisLabel = null, BottomAxisLabel = null, PathFill = null, onPanGestureBegin = null, onPanGestureChange = null, onPanGestureEnd = null, onHoverGestureBegin = null, onHoverGestureChange = null, onHoverGestureEnd = null, ...viewProps }) => { const [{ width, height }, setSize] = useState({ width: 0, height: 0 }); // Initially -cursorRadius so that the cursor is hidden const x = useSharedValue(-cursorRadius); const y = useSharedValue(0); // We separate the computation of the data from the rendering. This is so that these values are // not recomputed when the width or height of the chart changes, but only when the points change. const data = useMemo(() => computeGraphData(points), [points]); const path = useMemo(() => { return computePath({ ...data, width, height, cursorRadius, curveType }); }, [data, width, height, cursorRadius, curveType]); const gestures = useGestures({ x, y, path, height, minYValue: data.minValue, maxYValue: data.maxValue, cursorRadius, precision, onPanGestureBegin, onPanGestureChange, onPanGestureEnd, onHoverGestureBegin, onHoverGestureChange, onHoverGestureEnd, }); const onLayout = useCallback((e: LayoutChangeEvent) => { // Batch the updates to avoid unnecessary re-renders setSize(e.nativeEvent.layout); }, []); return ( {TopAxisLabel && ( )} {PathFill && PathFill({ width, height, strokeWidth })} {!hideCursor ? ( ) : null} {BottomAxisLabel && ( )} ); }; InteractiveLineChart.displayName = "InteractiveLineChart"; const styles = StyleSheet.create({ root: { position: "relative", overflow: "hidden" }, container: { flex: 1 }, canvas: { flex: 1 }, });