Created
April 18, 2022 20:13
-
-
Save terrysahaidak/0028878c4422b3d85eb8f55b4c83123b to your computer and use it in GitHub Desktop.
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
import { | |
Canvas, | |
LinearGradient, | |
Path, | |
useDerivedValue, | |
useValue, | |
vec, | |
Skia, | |
SkPath, | |
runTiming, | |
SkiaAnimation, | |
} from '@shopify/react-native-skia'; | |
import React, {useEffect, useRef, useState} from 'react'; | |
import { | |
StyleSheet, | |
Text, | |
View, | |
Dimensions, | |
TouchableOpacity, | |
} from 'react-native'; | |
const window = Dimensions.get('window'); | |
export type GraphProps = { | |
height?: number; | |
width?: number; | |
currentPath: SkPath; | |
previousPath: SkPath; | |
}; | |
export const createGraphPath = ( | |
width: number, | |
height: number, | |
steps: number, | |
round = true, | |
) => { | |
const retVal = Skia.Path.Make(); | |
let y = height / 2; | |
retVal.moveTo(0, y); | |
const prevPt = {x: 0, y}; | |
for (let i = 0; i < width; i += width / steps) { | |
// increase y by a random amount between -10 and 10 | |
y += Math.random() * 30 - 15; | |
y = Math.max(height * 0.2, Math.min(y, height * 0.7)); | |
if (round && i > 0) { | |
const xMid = (prevPt.x + i) / 2; | |
const yMid = (prevPt!.y + y) / 2; | |
retVal.quadTo(prevPt.x, prevPt.y, xMid, yMid); | |
prevPt.x = i; | |
prevPt.y = y; | |
} else { | |
retVal.lineTo(i, y); | |
} | |
} | |
return retVal; | |
}; | |
const data = [ | |
{ | |
title: 'Hour', | |
path: createGraphPath(window.width, 400, 60), | |
}, | |
{ | |
title: 'Day', | |
path: createGraphPath(window.width, 400, 60), | |
}, | |
{ | |
title: 'Month', | |
path: createGraphPath(window.width, 400, 60), | |
}, | |
{ | |
title: 'Year', | |
path: createGraphPath(window.width, 400, 60), | |
}, | |
]; | |
export const Graph: React.FC<GraphProps> = ({ | |
height = window.height, | |
width = window.width, | |
currentPath, | |
previousPath, | |
}) => { | |
const progress = useValue(1); | |
const [path, setPath] = useState(previousPath); | |
const [nextPath, setNextPath] = useState(currentPath); | |
const animationRef = useRef<SkiaAnimation>(); | |
useEffect(() => { | |
progress.animation = undefined; | |
// Create new random path | |
setNextPath(currentPath); | |
// Start animation | |
animationRef.current = runTiming(progress, 1, {duration: 350}, () => { | |
// Update main path to nextpath | |
setPath(currentPath); | |
// Reset progress | |
progress.current = 0; | |
}); | |
}, [progress, currentPath, previousPath]); | |
const interpolatedPath = useDerivedValue( | |
() => nextPath.interpolate(path, progress.current), | |
[progress, path, nextPath], | |
); | |
return ( | |
<View style={styles.chartContainer}> | |
<Canvas style={styles.graph}> | |
<Path | |
path={interpolatedPath} | |
strokeWidth={4} | |
style="stroke" | |
strokeJoin="round" | |
color="black" | |
strokeCap="round"> | |
<LinearGradient | |
start={vec(0, height * 0.5)} | |
end={vec(width * 0.5, height * 0.5)} | |
colors={['black', '#cccc66']} | |
/> | |
</Path> | |
</Canvas> | |
</View> | |
); | |
}; | |
function usePrevious(value) { | |
const prevValue = useRef(); | |
useEffect(() => { | |
prevValue.current = value; | |
}, [value]); | |
return prevValue.current; | |
} | |
export default function Interpolation() { | |
const [currentIndex, setIndex] = useState(0); | |
const currentPath = data[currentIndex]; | |
const previousPath = data[usePrevious(currentIndex) ?? currentIndex]; | |
return ( | |
<View style={styles.container}> | |
<Graph currentPath={currentPath.path} previousPath={previousPath.path} /> | |
<Timeline | |
currentIndex={currentIndex} | |
timelines={data} | |
onChange={setIndex} | |
/> | |
</View> | |
); | |
} | |
function Timeline({timelines, onChange, currentIndex}) { | |
return ( | |
<View style={styles.timelineContainer}> | |
{timelines.map((item, index) => ( | |
<TouchableOpacity key={index} onPress={() => onChange(index)}> | |
<View style={styles.textContainer}> | |
<Text | |
style={[styles.text, currentIndex === index && styles.active]}> | |
{item.title} | |
</Text> | |
</View> | |
</TouchableOpacity> | |
))} | |
</View> | |
); | |
} | |
const styles = StyleSheet.create({ | |
container: {flex: 1, backgroundColor: 'white', justifyContent: 'center'}, | |
chartContainer: {height: 300, width: window.width, backgroundColor: 'white'}, | |
graph: { | |
flex: 1, | |
}, | |
timelineContainer: {flexDirection: 'row', padding: 20, alignSelf: 'center'}, | |
textContainer: {padding: 20}, | |
text: {color: 'gray', fontSize: 18, fontWeight: '500'}, | |
active: {color: 'black'}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment