-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Key Highlights: - Major API refactor to support multiline charts
- Loading branch information
Showing
22 changed files
with
790 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Line, MultiLineChart } from "@codeherence/react-native-graph"; | ||
import { Circle, Group } from "@shopify/react-native-skia"; | ||
import * as Haptics from "expo-haptics"; | ||
import { ScrollView, StyleSheet } from "react-native"; | ||
import { runOnJS, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated"; | ||
import { useSafeAreaInsets } from "react-native-safe-area-context"; | ||
|
||
import { priceMap } from "../src/store/prices"; | ||
|
||
const gestureStartImpact = () => { | ||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); | ||
}; | ||
|
||
export default () => { | ||
const cursorShown = useSharedValue(false); | ||
const x = useSharedValue(0); | ||
const y = useSharedValue(0); | ||
const { bottom, left, right } = useSafeAreaInsets(); | ||
|
||
const opacity = useDerivedValue(() => { | ||
// return cursorShown.value ? 1 : 0; | ||
// Use a timing function to animate the opacity | ||
return withTiming(cursorShown.value ? 1 : 0, { duration: 200 }); | ||
}); | ||
|
||
return ( | ||
<ScrollView | ||
style={styles.container} | ||
contentContainerStyle={[ | ||
styles.contentContainer, | ||
{ | ||
paddingBottom: bottom, | ||
paddingLeft: left, | ||
paddingRight: right, | ||
}, | ||
]} | ||
showsVerticalScrollIndicator={false} | ||
> | ||
<MultiLineChart | ||
isStatic={false} | ||
points={priceMap} | ||
style={styles.chart} | ||
// ExtraCanvasElements={ | ||
// <> | ||
// <Group color="blue" opacity={opacity}> | ||
// <Circle cx={x} cy={y} r={10} /> | ||
// </Group> | ||
// </> | ||
// } | ||
onPanGestureBegin={(payload) => { | ||
"worklet"; | ||
cursorShown.value = true; | ||
x.value = payload.event.x; | ||
y.value = payload.event.y; | ||
runOnJS(gestureStartImpact)(); | ||
}} | ||
onPanGestureEnd={() => { | ||
"worklet"; | ||
cursorShown.value = false; | ||
}} | ||
onPanGestureChange={(payload) => { | ||
"worklet"; | ||
x.value = payload.event.x; | ||
y.value = payload.event.y; | ||
}} | ||
> | ||
{(args) => ( | ||
<> | ||
<Line points={args.points.aapl} strokeWidth={1} color="green" /> | ||
<Line points={args.points.msft} strokeWidth={1} color="purple" /> | ||
<Line points={args.points.nvda} strokeWidth={1} color="black" /> | ||
<Line points={args.points.unity} strokeWidth={1} color="orange" /> | ||
</> | ||
)} | ||
</MultiLineChart> | ||
</ScrollView> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
container: { flex: 1 }, | ||
contentContainer: { flexGrow: 1 }, | ||
chart: { flex: 1, maxHeight: 200 }, | ||
price: { fontSize: 32 }, | ||
buttonContainer: { | ||
flexDirection: "row", | ||
justifyContent: "center", | ||
paddingVertical: 12, | ||
}, | ||
toggleBtn: { | ||
padding: 12, | ||
backgroundColor: "blue", | ||
borderRadius: 12, | ||
}, | ||
buttonText: { | ||
color: "white", | ||
fontSize: 16, | ||
}, | ||
}); |
Binary file not shown.
Binary file not shown.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
src/components/MultiLineChart/InteractiveMultiLineChart.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { Canvas } from "@shopify/react-native-skia"; | ||
import React, { useCallback, useState } from "react"; | ||
import { LayoutChangeEvent, StyleSheet, View } from "react-native"; | ||
import { GestureDetector } from "react-native-gesture-handler"; | ||
|
||
import type { MultiLineChartProps } from "./MultiLineChart"; | ||
import { useMultiLineChartContext } from "./context"; | ||
import { UseGestureProps, useGestures } from "./useGestures"; | ||
import { batchedUpdates } from "../../libs/batchedUpdates"; | ||
|
||
export interface InteractiveLineChartProps<Data extends Record<string, [number, number][]>> { | ||
gestureLongPressDelay?: number; | ||
/** | ||
* Extra elements to render on the canvas. This prop is separated from the children prop to allow | ||
* for clear separation between line chart elements and extra elements. | ||
*/ | ||
ExtraCanvasElements?: JSX.Element; | ||
onPanGestureBegin?: UseGestureProps<Data>["onPanGestureBegin"]; | ||
onPanGestureChange?: UseGestureProps<Data>["onPanGestureChange"]; | ||
onPanGestureEnd?: UseGestureProps<Data>["onPanGestureEnd"]; | ||
} | ||
|
||
export const InteractiveMultiLineChart = <Data extends Record<string, [number, number][]>>({ | ||
points, | ||
children, | ||
gestureLongPressDelay = 200, | ||
ExtraCanvasElements, | ||
onCanvasResize, | ||
onPanGestureBegin, | ||
onPanGestureChange, | ||
onPanGestureEnd, | ||
...viewProps | ||
}: MultiLineChartProps<Data, false>) => { | ||
const [layoutComputed, setLayoutComputed] = useState(false); | ||
const { height, width, setCanvasSize } = useMultiLineChartContext(); | ||
|
||
const gestures = useGestures({ | ||
points, | ||
height, | ||
precision: 2, | ||
gestureLongPressDelay, | ||
onPanGestureBegin, | ||
onPanGestureChange, | ||
onPanGestureEnd, | ||
}); | ||
|
||
const onLayout = useCallback((e: LayoutChangeEvent) => { | ||
// Batch the updates to avoid unnecessary re-renders | ||
batchedUpdates(() => { | ||
setLayoutComputed(true); | ||
onCanvasResize?.(e.nativeEvent.layout.width, e.nativeEvent.layout.height); | ||
setCanvasSize(e.nativeEvent.layout); | ||
}); | ||
}, []); | ||
|
||
return ( | ||
<View style={[styles.root, viewProps.style]} {...viewProps}> | ||
<GestureDetector gesture={gestures}> | ||
<View style={styles.container} onLayout={onLayout}> | ||
<Canvas style={{ height, width }}> | ||
{!layoutComputed | ||
? null | ||
: // Since the children need to be invoked, we invoke the children then inject the width and height manually. | ||
(() => { | ||
const invokedChildren = children({ height, width, points }); | ||
if (!React.isValidElement(invokedChildren)) return null; | ||
|
||
if (invokedChildren.type === React.Fragment) { | ||
// If the child is a fragment, iteratively clone all children | ||
return React.Children.map(invokedChildren.props.children, (c) => { | ||
if (!React.isValidElement(c)) return null; | ||
return React.cloneElement(c, { | ||
// @ts-ignore | ||
...c.props, | ||
width, | ||
height, | ||
}); | ||
}); | ||
} | ||
|
||
return React.cloneElement(invokedChildren, { | ||
...invokedChildren.props, | ||
width, | ||
height, | ||
}); | ||
})()} | ||
|
||
{ExtraCanvasElements} | ||
</Canvas> | ||
</View> | ||
</GestureDetector> | ||
</View> | ||
); | ||
}; | ||
InteractiveMultiLineChart.displayName = "StaticMultiLineChart"; | ||
|
||
const styles = StyleSheet.create({ | ||
root: { position: "relative", overflow: "hidden" }, | ||
container: { flex: 1 }, | ||
canvas: { flex: 1 }, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Path, PathProps } from "@shopify/react-native-skia"; | ||
import { useMemo } from "react"; | ||
|
||
import { computeGraphData, computePath, ComputePathProps } from "../../utils/math"; | ||
|
||
interface LineProps extends Pick<PathProps, "children" | "color" | "strokeWidth" | "stroke"> { | ||
/** An array of tuples representing [time, value] pairs. */ | ||
points?: [number, number][]; | ||
/** | ||
* The width of the canvas. This value is not modifiable and will be injected by the parent component. | ||
*/ | ||
width?: number; | ||
/** | ||
* The height of the canvas. This value is not modifiable and will be injected by the parent component. | ||
*/ | ||
height?: number; | ||
strokeWidth?: number; | ||
curveType?: ComputePathProps["curveType"]; | ||
} | ||
|
||
export const Line: React.FC<LineProps> = ({ | ||
points = [], | ||
width = 0, | ||
height = 0, | ||
strokeWidth = 2, | ||
curveType = "linear", | ||
...pathProps | ||
}) => { | ||
const data = useMemo(() => { | ||
return computeGraphData(points); | ||
}, [points]); | ||
|
||
const path = useMemo(() => { | ||
return computePath({ ...data, width, height, cursorRadius: 0, curveType }); | ||
}, [data, width, height, curveType]); | ||
|
||
return <Path style="stroke" strokeWidth={strokeWidth} color="gray" {...pathProps} path={path} />; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from "react"; | ||
import type { ViewProps } from "react-native"; | ||
|
||
import { InteractiveLineChartProps, InteractiveMultiLineChart } from "./InteractiveMultiLineChart"; | ||
import { StaticMultiLineChart } from "./StaticMultiLineChart"; | ||
import { MultiLineChartProvider } from "./context"; | ||
|
||
export type MultiLineChartProps< | ||
Data extends Record<string, [number, number][]>, | ||
Static extends boolean = false, | ||
> = React.PropsWithChildren< | ||
{ | ||
points: Data; | ||
onCanvasResize?: (width: number, height: number) => void; | ||
} & Exclude<ViewProps, "children"> & { | ||
children: (args: { points: Data; height: number; width: number }) => React.ReactNode; | ||
} & (Static extends true | ||
? { isStatic: true } | ||
: { isStatic: false } & InteractiveLineChartProps<Data>) | ||
>; | ||
|
||
export const MultiLineChart = < | ||
Data extends Record<string, [number, number][]>, | ||
Static extends boolean = false, | ||
>({ | ||
isStatic = false, | ||
...props | ||
}: MultiLineChartProps<Data, Static>) => { | ||
return ( | ||
<MultiLineChartProvider> | ||
{/* <StaticMultiLineChart {...props} /> */} | ||
{isStatic ? ( | ||
<StaticMultiLineChart isStatic {...props} /> | ||
) : ( | ||
<InteractiveMultiLineChart isStatic={false} {...props} /> | ||
)} | ||
</MultiLineChartProvider> | ||
); | ||
}; | ||
MultiLineChart.displayName = "MultiLineChart"; |
Oops, something went wrong.