Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pan / Zoom #413

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e570a77
Foundation of pan/zoom feature
keithluchtel Oct 16, 2024
f43f801
fix render issue with scale
keithluchtel Oct 28, 2024
49349e9
Add 'isActive' state
keithluchtel Oct 29, 2024
1eba907
Refactor hook to match press state
keithluchtel Oct 29, 2024
7de461b
Separate pan and zoom active states
keithluchtel Oct 29, 2024
c157ee8
Switch to using `onStart` for detecting active state
keithluchtel Oct 29, 2024
b6c8dc1
Add back touch gesture
keithluchtel Oct 29, 2024
c61c971
Remove unused file
keithluchtel Oct 29, 2024
65daaf9
remove unintentional change
keithluchtel Oct 29, 2024
bf574b8
remove unintentional change
keithluchtel Oct 29, 2024
25ea4d1
Add pan zoom example
keithluchtel Oct 29, 2024
4752dde
Remove debug background
keithluchtel Oct 29, 2024
866b319
Add clipping def around axis line rendering
keithluchtel Oct 30, 2024
3f003a6
Begin adding actions
keithluchtel Oct 30, 2024
b5330b4
Remove unnecessary transform matrix
keithluchtel Oct 30, 2024
280d9ca
Add setScale and setTranslate actions
keithluchtel Oct 30, 2024
44653f4
Add more actions and setup example
keithluchtel Oct 30, 2024
7bf15b5
Fix warning
keithluchtel Oct 30, 2024
dd2ed47
Adjust y axis scale
keithluchtel Oct 30, 2024
1c25e15
Extract gesture handler to own file
keithluchtel Oct 30, 2024
acabfc0
Add pan/zoom to polar charts
keithluchtel Oct 31, 2024
5bd3ea1
Merge remote-tracking branch 'origin/main' into zoom
keithluchtel Oct 31, 2024
b8e119f
Support multiple y axis rescale
keithluchtel Oct 31, 2024
6e179b2
Add foundation for allowing transform gesture configuration
keithluchtel Oct 31, 2024
3af27c5
remove some actions for the time being
keithluchtel Oct 31, 2024
c1b97d7
Make rescaling the axis ticks configurable
keithluchtel Oct 31, 2024
c2e8709
Enable rescaling on sample app
keithluchtel Oct 31, 2024
816e1ad
Remove actions in favor of util functions
keithluchtel Oct 31, 2024
61e46fc
Update pan zoom example
keithluchtel Oct 31, 2024
9db55b3
Add pan/zoom to some examples
keithluchtel Nov 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion example/app/bar-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@ import {
} from "@shopify/react-native-skia";
import React, { useState } from "react";
import { SafeAreaView, ScrollView, StyleSheet, View } from "react-native";
import { Bar, CartesianChart } from "victory-native";
import {
Bar,
CartesianChart,
getTransformComponents,
setScale,
setTranslate,
useChartTransformState,
} from "victory-native";
import { useDarkMode } from "react-native-dark";
import {
useAnimatedReaction,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import inter from "../assets/inter-medium.ttf";
import { appColors } from "./consts/colors";
import { InputSwitch } from "../components/InputSwitch";
Expand Down Expand Up @@ -37,12 +49,46 @@ export default function BarChartPage(props: { segment: string }) {
const [labelPosition, setLabelPosition] = useState<
"top" | "bottom" | "left" | "right"
>("top");
const { state } = useChartTransformState();

const k = useSharedValue(1);
const tx = useSharedValue(0);
const ty = useSharedValue(0);

useAnimatedReaction(
() => {
return state.panActive.value || state.zoomActive.value;
},
(cv, pv) => {
if (!cv && pv) {
const vals = getTransformComponents(state.matrix.value);
k.value = vals.scaleX;
tx.value = vals.translateX;
ty.value = vals.translateY;

k.value = withTiming(1);
tx.value = withTiming(0);
ty.value = withTiming(0);
}
},
);

useAnimatedReaction(
() => {
return { k: k.value, tx: tx.value, ty: ty.value };
},
({ k, tx, ty }) => {
const m = setTranslate(state.matrix.value, tx, ty);
state.matrix.value = setScale(m, k);
},
);

return (
<>
<SafeAreaView style={styles.safeView}>
<View style={styles.chart}>
<CartesianChart
transformState={state}
xKey="month"
padding={5}
yKeys={["listenCount"]}
Expand Down
9 changes: 7 additions & 2 deletions example/app/consts/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const ChartRoutes: {
{
title: "Bar Chart",
description:
"This is a single Bar chart in Victory that supports customized spacing between each bar.",
"This is a single Bar chart in Victory that supports customized spacing between each bar as well as pan/zoom.",
path: "/bar-chart",
},
{
Expand Down Expand Up @@ -103,7 +103,7 @@ export const ChartRoutes: {
{
title: "Pie Chart",
description:
"This is a Pie chart in Victory. It has support for customizing each slice and adding insets.",
"This is a Pie chart in Victory. It has support for customizing each slice and adding insets as well as pan/zoom",
path: "/pie-chart",
},
{
Expand All @@ -123,6 +123,11 @@ export const ChartRoutes: {
description: "This is an Area chart with dashed X and Y axes.",
path: "/dashed-axes",
},
{
title: "Pan Zoom",
description: "This is an example of pan zoom functionality",
path: "/pan-zoom",
},
];

if (__DEV__) {
Expand Down
61 changes: 60 additions & 1 deletion example/app/multiple-y-axes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@ import { useFont } from "@shopify/react-native-skia";
import * as React from "react";
import { useState } from "react";
import { SafeAreaView, ScrollView, StyleSheet, View } from "react-native";
import { CartesianChart, Line, Bar, Area } from "victory-native";
import {
CartesianChart,
Line,
Bar,
Area,
useChartTransformState,
getTransformComponents,
setTranslate,
setScale,
} from "victory-native";
import {
useAnimatedReaction,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import inter from "../assets/inter-medium.ttf";
import { appColors } from "./consts/colors";
import { InputSlider } from "../components/InputSlider";
import { InfoCard } from "../components/InfoCard";

const randomNumber = () => Math.floor(Math.random() * (50 - 25 + 1)) + 25;
const randomNumber2 = () => Math.floor(Math.random() * (50 - 25 + 1)) + 10000;
Expand Down Expand Up @@ -40,22 +55,62 @@ export default function MultipleYAxesPage() {
const [priceYDomain, setPriceYDomain] = useState<[number, number]>([
100, 200,
]);
const { state } = useChartTransformState();

const red = "#a04d4d";
const blue = "#1e1e59";
const lightBlue = "#6dc9e8";
const green = "#74b567";
const gray = "#232323";

const k = useSharedValue(1);
const tx = useSharedValue(0);
const ty = useSharedValue(0);

useAnimatedReaction(
() => {
return state.panActive.value || state.zoomActive.value;
},
(cv, pv) => {
if (!cv && pv) {
const vals = getTransformComponents(state.matrix.value);
k.value = vals.scaleX;
tx.value = vals.translateX;
ty.value = vals.translateY;

k.value = withTiming(1);
tx.value = withTiming(0);
ty.value = withTiming(0);
}
},
);

useAnimatedReaction(
() => {
return { k: k.value, tx: tx.value, ty: ty.value };
},
({ k, tx, ty }) => {
const m = setTranslate(state.matrix.value, tx, ty);
state.matrix.value = setScale(m, k);
},
);

return (
<SafeAreaView style={styles.safeView}>
<ScrollView>
<View style={styles.chart}>
<CartesianChart
transformState={state}
transformConfig={{
pan: {
activateAfterLongPress: 100,
},
}}
xKey="day"
padding={25}
yKeys={["sales", "profit"]}
xAxis={{
enableRescaling: true,
font,
labelColor: "orange",
formatXLabel: (value) => {
Expand All @@ -76,6 +131,7 @@ export default function MultipleYAxesPage() {
return value.toFixed(0);
},
lineColor: "pink",
enableRescaling: true,
},
{
yKeys: ["profit"],
Expand Down Expand Up @@ -107,6 +163,9 @@ export default function MultipleYAxesPage() {
</>
)}
</CartesianChart>
<View style={{ paddingHorizontal: 20 }}>
<InfoCard>This chart supports pan/zoom</InfoCard>
</View>
</View>
{/* Multi bar, with negative values */}
<View style={styles.chart}>
Expand Down
191 changes: 191 additions & 0 deletions example/app/pan-zoom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import {
useAnimatedReaction,
useSharedValue,
withTiming,
} from "react-native-reanimated";

export const PanZoom = () => {};
import * as React from "react";
import { StyleSheet, View, SafeAreaView, ScrollView } from "react-native";
import {
CartesianChart,
getTransformComponents,
Line,
setScale,
setTranslate,
useChartTransformState,
} from "victory-native";
import {
multiply4,
scale,
translate,
useFont,
} from "@shopify/react-native-skia";
import { useState } from "react";
import { appColors } from "./consts/colors";
import inter from "../assets/inter-medium.ttf";

import { Button } from "../components/Button";

export default function PanZoomPage() {
const font = useFont(inter, 12);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
const { state } = useChartTransformState();

const k = useSharedValue(1);
const tx = useSharedValue(0);
const ty = useSharedValue(0);

useAnimatedReaction(
() => {
return state.panActive.value || state.zoomActive.value;
},
(cv, pv) => {
if (!cv && pv) {
const vals = getTransformComponents(state.matrix.value);
k.value = vals.scaleX;
tx.value = vals.translateX;
ty.value = vals.translateY;

k.value = withTiming(1);
tx.value = withTiming(0);
ty.value = withTiming(0);
}
},
);

useAnimatedReaction(
() => {
return { k: k.value, tx: tx.value, ty: ty.value };
},
({ k, tx, ty }) => {
const m = setTranslate(state.matrix.value, tx, ty);
state.matrix.value = setScale(m, k);
},
);

return (
<SafeAreaView style={styles.safeView}>
<View style={{ flex: 1, maxHeight: 400, padding: 32 }}>
<CartesianChart
data={DATA}
xKey="day"
yKeys={["highTmp"]}
yAxis={[
{
font: font,
enableRescaling: true,
},
]}
xAxis={{
enableRescaling: true,
font: font,
}}
transformState={state}
onChartBoundsChange={({ top, left, right, bottom }) => {
setWidth(right - left);
setHeight(bottom - top);
}}
>
{({ points }) => {
return (
<>
<Line points={points.highTmp} color="red" strokeWidth={3} />
</>
);
}}
</CartesianChart>
</View>
<ScrollView
contentContainerStyle={{
paddingHorizontal: 20,
}}
>
<View style={{ gap: 10 }}>
<View style={{ flexDirection: "row", gap: 20 }}>
<Button
title={"Pan Left"}
style={{ flex: 1 }}
onPress={() => {
state.matrix.value = multiply4(
state.matrix.value,
translate(10, 0),
);
}}
/>
<Button
title={"Pan Right"}
style={{ flex: 1 }}
onPress={() => {
state.matrix.value = multiply4(
state.matrix.value,
translate(-10, 0, 0),
);
}}
/>
</View>
<View style={{ flexDirection: "row", gap: 20 }}>
<Button
title={"Pan Up"}
style={{ flex: 1 }}
onPress={() => {
state.matrix.value = multiply4(
state.matrix.value,
translate(0, 10),
);
}}
/>
<Button
title={"Pan Down"}
style={{ flex: 1 }}
onPress={() => {
state.matrix.value = multiply4(
state.matrix.value,
translate(0, -10),
);
}}
/>
</View>
<View style={{ flexDirection: "row", gap: 20 }}>
<Button
title={"Zoom In"}
style={{ flex: 1 }}
onPress={() => {
state.matrix.value = multiply4(
state.matrix.value,
scale(1.25, 1.25, 1, { x: width / 2, y: height / 2 }),
);
}}
/>
<Button
title={"Zoom Out"}
style={{ flex: 1 }}
onPress={() => {
state.matrix.value = multiply4(
state.matrix.value,
scale(0.75, 0.75, 1, { x: width / 2, y: height / 2 }),
);
}}
/>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}

const DATA = Array.from({ length: 31 }, (_, i) => ({
day: i,
highTmp: 40 + 30 * Math.random(),
}));

const styles = StyleSheet.create({
safeView: {
flex: 1,
backgroundColor: appColors.viewBackground.light,
$dark: {
backgroundColor: appColors.viewBackground.dark,
},
},
});
Loading