-
Notifications
You must be signed in to change notification settings - Fork 467
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #153 from Shopify/feature/73-path-interpolation
Path interpolation
- Loading branch information
Showing
11 changed files
with
354 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { | ||
Canvas, | ||
Fill, | ||
LinearGradient, | ||
Paint, | ||
Path, | ||
Spring, | ||
useValue, | ||
vec, | ||
} from "@shopify/react-native-skia"; | ||
import { runSpring } from "@shopify/react-native-skia/src/animation/Animation/functions"; | ||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | ||
import { StyleSheet, Text, View } from "react-native"; | ||
|
||
import { createGraphPath } from "./createGraphPath"; | ||
import type { GraphProps } from "./types"; | ||
|
||
export const Interpolation: React.FC<GraphProps> = ({ height, width }) => { | ||
const path = useMemo( | ||
() => createGraphPath(width, height, 60), | ||
[height, width] | ||
); | ||
const path2 = useMemo( | ||
() => createGraphPath(width, height, 60), | ||
[height, width] | ||
); | ||
|
||
const progress = useValue(0); | ||
const [toggled, setToggled] = useState(false); | ||
const onPress = useCallback(() => setToggled((p) => !p), []); | ||
useEffect(() => { | ||
runSpring(progress, toggled ? 1 : 0, Spring.Config.Gentle); | ||
}, [progress, toggled]); | ||
|
||
return ( | ||
<View style={{ height, marginBottom: 10 }} onTouchEnd={onPress}> | ||
<Canvas style={styles.graph}> | ||
<Fill color="black" /> | ||
<Paint> | ||
<LinearGradient | ||
start={vec(0, height * 0.5)} | ||
end={vec(width * 0.5, height * 0.5)} | ||
colors={["black", "#cccc66"]} | ||
/> | ||
</Paint> | ||
<Path | ||
path={() => path.interpolate(path2, progress.value)} | ||
strokeWidth={4} | ||
style="stroke" | ||
strokeJoin="round" | ||
strokeCap="round" | ||
/> | ||
</Canvas> | ||
<Text>Touch graph to interpolate</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
graph: { | ||
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,68 @@ | ||
import { | ||
Canvas, | ||
Easing, | ||
Fill, | ||
LinearGradient, | ||
Paint, | ||
Path, | ||
useValue, | ||
vec, | ||
} from "@shopify/react-native-skia"; | ||
import { runTiming } from "@shopify/react-native-skia/src/animation/Animation/functions"; | ||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | ||
import { StyleSheet, Text, View } from "react-native"; | ||
|
||
import { createGraphPath, createZeroPath } from "./createGraphPath"; | ||
import type { GraphProps } from "./types"; | ||
|
||
export const MountAnimation: React.FC<GraphProps> = ({ height, width }) => { | ||
const zeroPath = useMemo( | ||
() => createZeroPath(width, height, 60), | ||
[height, width] | ||
); | ||
const path = useMemo( | ||
() => createGraphPath(width, height, 60, false), | ||
[height, width] | ||
); | ||
|
||
const progress = useValue(0); | ||
const [toggled, setToggled] = useState(false); | ||
const onPress = useCallback(() => setToggled((p) => !p), []); | ||
|
||
useEffect(() => { | ||
runTiming(progress, { | ||
to: toggled ? 1 : 0, | ||
duration: 350, | ||
easing: Easing.inOut(Easing.cubic), | ||
}); | ||
}, [progress, toggled]); | ||
|
||
return ( | ||
<View style={{ height, marginBottom: 10 }} onTouchEnd={onPress}> | ||
<Canvas style={styles.graph}> | ||
<Fill color="black" /> | ||
<Paint> | ||
<LinearGradient | ||
start={vec(0, height * 0.5)} | ||
end={vec(width * 0.5, height * 0.5)} | ||
colors={["black", "#3B8EA5"]} | ||
/> | ||
</Paint> | ||
<Path | ||
path={() => path.interpolate(zeroPath, progress.value)} | ||
strokeWidth={4} | ||
style="stroke" | ||
strokeJoin="round" | ||
strokeCap="round" | ||
/> | ||
</Canvas> | ||
<Text>Touch to toggle between "unmounted" and "mounted"</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
graph: { | ||
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,98 @@ | ||
import type { IPath } from "@shopify/react-native-skia"; | ||
import { | ||
Line, | ||
Canvas, | ||
Circle, | ||
Fill, | ||
LinearGradient, | ||
Paint, | ||
Path, | ||
useTouchHandler, | ||
useValue, | ||
Text as SkiaText, | ||
vec, | ||
} from "@shopify/react-native-skia"; | ||
import React, { useMemo } from "react"; | ||
import { StyleSheet, Text, View } from "react-native"; | ||
|
||
import { createGraphPath } from "./createGraphPath"; | ||
import type { GraphProps } from "./types"; | ||
|
||
export const Slider: React.FC<GraphProps> = ({ height, width }) => { | ||
const path = useMemo( | ||
() => createGraphPath(width, height, 60, false), | ||
[height, width] | ||
); | ||
|
||
const progress = useValue( | ||
getPointAtPositionInPath(width / 2, width, 60, path) | ||
); | ||
|
||
const touchHandler = useTouchHandler({ | ||
onActive: ({ x }) => | ||
(progress.value = getPointAtPositionInPath(x, width, 60, path)), | ||
}); | ||
|
||
return ( | ||
<View style={{ height, marginBottom: 10 }}> | ||
<Canvas style={styles.graph} onTouch={touchHandler}> | ||
<Fill color="black" /> | ||
<Paint> | ||
<LinearGradient | ||
start={vec(0, height * 0.5)} | ||
end={vec(width * 0.5, height * 0.5)} | ||
colors={["black", "#DA4167"]} | ||
/> | ||
</Paint> | ||
<Path | ||
path={path} | ||
strokeWidth={4} | ||
style="stroke" | ||
strokeJoin="round" | ||
strokeCap="round" | ||
/> | ||
<Paint color="#fff" /> | ||
<Circle c={() => progress.value} r={10} /> | ||
<Circle color="#DA4167" c={() => progress.value} r={7.5} /> | ||
<SkiaText | ||
familyName="Arial" | ||
size={12} | ||
x={() => progress.value.x - 24} | ||
y={() => progress.value.y - 18} | ||
value={() => "$ " + progress.value.x.toFixed(2)} | ||
/> | ||
<Line | ||
p1={() => vec(progress.value.x, progress.value.y + 14)} | ||
p2={() => vec(progress.value.x, height)} | ||
/> | ||
</Canvas> | ||
<Text>Touch and drag to move center point</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
const getPointAtPositionInPath = ( | ||
x: number, | ||
width: number, | ||
steps: number, | ||
path: IPath | ||
) => { | ||
const index = Math.max(0, Math.floor(x / (width / steps))); | ||
const fraction = (x / (width / steps)) % 1; | ||
const p1 = path.getPoint(index); | ||
if (index < path.countPoints() - 1) { | ||
const p2 = path.getPoint(index + 1); | ||
// Interpolate between p1 and p2 | ||
return { | ||
x: p1.x + (p2.x - p1.x) * fraction, | ||
y: p1.y + (p2.y - p1.y) * fraction, | ||
}; | ||
} | ||
return p1; | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
graph: { | ||
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,43 @@ | ||
import { Skia } from "@shopify/react-native-skia"; | ||
|
||
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; | ||
}; | ||
|
||
export const createZeroPath = ( | ||
width: number, | ||
height: number, | ||
steps: number | ||
) => { | ||
const retVal = Skia.Path.Make(); | ||
const y = height / 2; | ||
retVal.moveTo(0, y); | ||
for (let i = 0; i < width; i += width / steps) { | ||
retVal.lineTo(i, y); | ||
} | ||
return retVal; | ||
}; |
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,26 @@ | ||
import React from "react"; | ||
import { View, StyleSheet, useWindowDimensions } from "react-native"; | ||
|
||
import { Interpolation } from "./Interpolation"; | ||
import { MountAnimation } from "./Mount"; | ||
import { Slider } from "./Slider"; | ||
|
||
const Padding = 10; | ||
|
||
export const GraphsScreen: React.FC = () => { | ||
const { width, height } = useWindowDimensions(); | ||
return ( | ||
<View style={styles.container}> | ||
<MountAnimation height={height * 0.25} width={width - Padding * 2} /> | ||
<Interpolation height={height * 0.25} width={width - Padding * 2} /> | ||
<Slider height={height * 0.25} width={width - Padding * 2} /> | ||
</View> | ||
); | ||
}; | ||
|
||
const styles = StyleSheet.create({ | ||
container: { | ||
flex: 1, | ||
padding: Padding, | ||
}, | ||
}); |
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,4 @@ | ||
export type GraphProps = { | ||
height: number; | ||
width: number; | ||
}; |
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
Oops, something went wrong.