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

feat: pie chart #191

Merged
merged 15 commits into from
Mar 20, 2024
6 changes: 6 additions & 0 deletions .changeset/friendly-buttons-approve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"victory-native": minor
"example": patch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want the example app in here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seemed like it automatically got included when I generated the changeset.

---

Add Pie/Donut charts
18 changes: 18 additions & 0 deletions example/app/consts/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ export const ChartRoutes: {
"This chart showcases using custom shaders from Skia, leveraging shader uniforms derived from Reanimated shared values.",
path: "/custom-shaders",
},
{
title: "Pie Chart",
description:
"This is a Pie chart in Victory. It has support for customizing each slice and adding insets.",
path: "/pie-chart",
},
{
title: "Donut Chart",
description:
"This is how to make a Donut chart in Victory. It is built off of the Pie chart using the `innerRadius` prop.",
path: "/donut-chart",
},
{
title: "Pie and Donut Assortment",
description:
"This is mixture of Pie and Donut charts, showing off the different ways to customize the charts.",
path: "/pie-and-donut-charts",
},
];

if (__DEV__) {
Expand Down
134 changes: 134 additions & 0 deletions example/app/donut-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { useState } from "react";
import { SafeAreaView, ScrollView, StyleSheet, View } from "react-native";
import { LinearGradient, vec } from "@shopify/react-native-skia";
import { Pie, PolarChart } from "victory-native";
import { InfoCard } from "example/components/InfoCard";
import { Button } from "example/components/Button";
import { appColors } from "./consts/colors";
import { descriptionForRoute } from "./consts/routes";

function calculateGradientPoints(
radius: number,
startAngle: number,
endAngle: number,
centerX: number,
centerY: number,
) {
// Calculate the midpoint angle of the slice for a central gradient effect
const midAngle = (startAngle + endAngle) / 2;

// Convert angles from degrees to radians
const startRad = (Math.PI / 180) * startAngle;
const midRad = (Math.PI / 180) * midAngle;

// Calculate start point (inner edge near the pie's center)
const startX = centerX + radius * 0.5 * Math.cos(startRad);
const startY = centerY + radius * 0.5 * Math.sin(startRad);

// Calculate end point (outer edge of the slice)
const endX = centerX + radius * Math.cos(midRad);
const endY = centerY + radius * Math.sin(midRad);

return { startX, startY, endX, endY };
}

const randomNumber = () => Math.floor(Math.random() * (50 - 25 + 1)) + 125;
function generateRandomColor(): string {
// Generating a random number between 0 and 0xFFFFFF
const randomColor = Math.floor(Math.random() * 0xffffff);
// Converting the number to a hexadecimal string and padding with zeros
return `#${randomColor.toString(16).padStart(6, "0")}`;
}

const DATA = (numberPoints = 5) =>
Array.from({ length: numberPoints }, (_, index) => ({
value: randomNumber(),
color: generateRandomColor(),
label: `Label ${index + 1}`,
}));

export default function DonutChart(props: { segment: string }) {
const description = descriptionForRoute(props.segment);
const [data, setData] = useState(DATA(5));

return (
<SafeAreaView style={styles.safeView}>
<ScrollView>
<View style={styles.chartContainer}>
<PolarChart
data={data}
colorKey={"color"}
valueKey={"value"}
labelKey={"label"}
>
<Pie.Chart innerRadius={"50%"}>
{({ slice }) => {
const { startX, startY, endX, endY } = calculateGradientPoints(
slice.radius,
slice.startAngle,
slice.endAngle,
slice.center.x,
slice.center.y,
);

return (
<>
<Pie.Slice>
<LinearGradient
start={vec(startX, startY)}
end={vec(endX, endY)}
colors={[slice.color, `${slice.color}50`]}
positions={[0, 1]}
/>
</Pie.Slice>
<Pie.SliceAngularInset
angularInset={{
angularStrokeWidth: 5,
angularStrokeColor: "white",
}}
/>
</>
);
}}
</Pie.Chart>
</PolarChart>
</View>

<View style={{ flexGrow: 1, padding: 15 }}>
<InfoCard style={{ flex: 0, marginBottom: 16 }}>
{description}
</InfoCard>

<View
style={{
flexDirection: "row",
gap: 12,
marginTop: 10,
marginBottom: 16,
}}
>
<Button
style={{ flex: 1 }}
onPress={() => setData((data) => DATA(data.length))}
title="Shuffle Data"
/>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}

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