Skip to content

Instantly share code, notes, and snippets.

@tanner-west
Created October 2, 2023 11:57
Show Gist options
  • Save tanner-west/c8301b91f30a9cebabfe0154b696eadf to your computer and use it in GitHub Desktop.
Save tanner-west/c8301b91f30a9cebabfe0154b696eadf to your computer and use it in GitHub Desktop.
import React from "react";
import {
View,
SafeAreaView,
useWindowDimensions,
TextInput,
} from "react-native";
import Animated, {
Extrapolate,
interpolate,
useAnimatedRef,
useDerivedValue,
useSharedValue,
useAnimatedProps,
SharedValue,
} from "react-native-reanimated";
import Svg, { Line, Circle, Text as SVGText } from "react-native-svg";
import Slider from "@react-native-community/slider";
import {
useFonts,
IBMPlexSans_400Regular_Italic,
} from "@expo-google-fonts/ibm-plex-sans";
import { StatusBar } from "expo-status-bar";
const blue = "#005686";
const bluer = "#0084d6";
const bluest = "#009eff";
const sixteenPoints = [
0, 22.5, 45, 67.5, 90, 112.5, 135, 157.5, 180, 202.5, 225, 247.5, 270, 292.5,
315, 337.5, 360,
];
const AngleCircle = ({
x,
y,
myAngle,
currentAngle,
}: {
x: number;
y: number;
myAngle: number;
currentAngle: SharedValue<number>;
}) => {
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
const animatedCircleProps = useAnimatedProps(() => {
const r = interpolate(
currentAngle.value,
[myAngle - 10, myAngle, myAngle + 10],
[3, 20, 3],
Extrapolate.CLAMP
);
return {
r,
};
});
return (
<AnimatedCircle
x={x}
y={y}
r={5}
fill={blue}
animatedProps={animatedCircleProps}
style={{ zIndex: 100 }}
/>
);
};
const TrigScreen = () => {
const { width } = useWindowDimensions();
const AnimatedLine = Animated.createAnimatedComponent(Line);
const AnimatedText = Animated.createAnimatedComponent(TextInput);
const radius = width / 2;
const aref = useAnimatedRef<Animated.ScrollView>();
const angle = useSharedValue(0);
const coords = useDerivedValue(() => {
const radians = angle.value * (Math.PI / 180);
const x = Math.floor(radius * (1 + Math.sin(radians)));
const y = Math.floor(radius * (1 - Math.cos(radians)));
return { x, y };
});
const getCoordsForAngle = (angle: number) => {
const radians = angle * (Math.PI / 180);
const x = Math.floor(radius * (1 + Math.sin(radians)));
const y = Math.floor(radius * (1 - Math.cos(radians)));
return { x, y };
};
const animatedTextInputProps = useAnimatedProps(() => {
return {
text: angle.value.toString() + "°",
fontSize: 45,
editable: false,
};
});
const animatedOppositeProps = useAnimatedProps(() => {
const { x, y } = coords.value;
return {
x1: x,
y1: radius,
x2: x,
y2: y,
};
});
const animatedHypotenuseProps = useAnimatedProps(() => {
const { x, y } = coords.value;
return {
x1: radius,
y1: radius,
x2: x,
y2: y,
};
});
const animatedAdjacentProps = useAnimatedProps(() => {
const { x, y } = coords.value;
return {
x1: radius,
y1: radius,
x2: x,
y2: radius,
};
});
const animatedXCoordProps = useAnimatedProps(() => {
return {
text: `x = sin(θ) = ${coords.value.x}`,
fontSize: 45,
editable: false,
};
});
const animatedYCoordProps = useAnimatedProps(() => {
return {
text: `y = cos(θ) = ${coords.value.y}`,
fontSize: 45,
editable: false,
};
});
let [fontsLoaded] = useFonts({
IBMPlexSans_400Regular_Italic,
});
if (!fontsLoaded) {
return null;
}
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: "#ddd",
}}
>
<StatusBar style={"dark"} />
<View style={{ flex: 1, justifyContent: "center" }}>
<View
style={{
flex: 2,
justifyContent: "flex-end",
alignItems: "center",
}}
>
<AnimatedText
animatedProps={animatedTextInputProps}
style={{
fontFamily: "IBMPlexSans_400Regular_Italic",
color: blue,
}}
/>
<AnimatedText
animatedProps={animatedXCoordProps}
style={{
fontFamily: "IBMPlexSans_400Regular_Italic",
color: bluer,
}}
/>
<AnimatedText
animatedProps={animatedYCoordProps}
style={{
fontFamily: "IBMPlexSans_400Regular_Italic",
color: bluest,
}}
/>
</View>
<View style={{ flex: 1, justifyContent: "center" }}>
<Slider
style={{ marginHorizontal: 15 }}
onValueChange={(value) => (angle.value = value)}
minimumValue={0}
maximumValue={360}
step={1}
minimumTrackTintColor={blue}
/>
</View>
</View>
<View style={{ flex: 1 }}>
<Svg
viewBox={`0 -20 ${width} ${width + 50}`}
style={{
height: width,
width,
}}
>
{sixteenPoints.map((a) => {
const { x, y } = getCoordsForAngle(a);
return (
<AngleCircle
x={x}
y={y}
myAngle={a}
currentAngle={angle}
key={a}
/>
);
})}
{sixteenPoints.map((a) => {
const { x, y } = getCoordsForAngle(a);
return (
<Line x1={0} y1={y} y2={y} x2={width} stroke={"#bbb"} key={a} />
);
})}
{sixteenPoints.map((a) => {
const { x, y } = getCoordsForAngle(a);
return (
<Line x1={x} y1={0} y2={width} x2={x} stroke={"#bbb"} key={a} />
);
})}
<AnimatedLine
animatedProps={animatedHypotenuseProps}
stroke={blue}
strokeWidth={2}
/>
<AnimatedLine
animatedProps={animatedOppositeProps}
stroke={bluest}
strokeWidth={2}
/>
<AnimatedLine
animatedProps={animatedAdjacentProps}
stroke={bluer}
strokeWidth={2}
/>
<Circle
x={width / 2}
y={width / 2}
r={width / 2}
stroke={blue}
fillOpacity={0}
/>
</Svg>
</View>
</SafeAreaView>
);
};
export default TrigScreen;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment