Overview
One of the things we all end up needing to do at some point in our career is creating custom charts. One of the approaches we can take is to create charts with SVG's.
And today I'm going to use React Native together with react-native-svg, obviously there are other bookstores that do the same but I'm only going to use this one because it's the one I feel more confident working with.
Today's example
Today we are going to create a chart that will take into account three values, the monetary amount spent on groceries, the amount spent on expenses and the regular expenses.
Let's code
Let's install the following dependencies:
npm install react-native-svg
Now we can start working on our component.
// @src/App.js
import React from 'react';
const App = () => {
return (
// ...
);
};
export default App;
Then we will import the View component and StyleSheet from React Native to create our container.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
const App = () => {
return (
<View style={styles.container}>
// ...
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});
Next we will create our chart wrapper which will contain the chart and the amount that has already been spent.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
const App = () => {
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
// ...
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now let's import Svg, G (Group) and Circle from react-native-svg, so we can start working on our chart.
But first we have to start working on our data, so let's define the radius of the circle and the circumference of the circle.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
// ...
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Then we will declare three amounts, the groceries, the bills and the regular. Now let's declare the total which is the sum of the previous three.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
// ...
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
After that we will calculate the percentage of each one according to the total.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
// ...
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
What remains to be done is to calculate the displacement of each one's stroke.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
// ...
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Last but not the least, we will calculate the angle of each one.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
// ...
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now we can start working on our jsx. First let's establish the screen space that will be used, using the Svg tag.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
// ...
</Svg>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now we're going to use the G tag and we're going to define an axis and then give it a little rotation. The use of this tag is due to the fact that we end up having more than one geometric shape, which in this case is the circle.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
<G rotation={-90} originX="90" originY="90">
// ...
</G>
</Svg>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now let's do conditional rendering, this is because the total can have zero value, because we start the month without any expenses, they will gradually accumulate. So if the total value is zero, we will show a circle with a white stroke and the radius we defined earlier.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
<G rotation={-90} originX="90" originY="90">
{ total === 0 ? (
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F1F6F9"
fill="transparent"
strokeWidth="40"
/>
) : (
<>
// ...
</>
)
}
</G>
</Svg>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now let's create the first circle that will correspond to the expenses we had on buying groceries. So let's pass the value of the radius, the circumference, the stroke dash offset as a prop, and let's give it a rotation of 0 degrees.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
<G rotation={-90} originX="90" originY="90">
{ total === 0 ? (
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F1F6F9"
fill="transparent"
strokeWidth="40"
/>
) : (
<>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F05454"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={groceriesStrokeDashoffset}
rotation={0}
originX="90"
originY="90"
strokeLinecap="round"
/>
// ...
</>
)
}
</G>
</Svg>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now let's create the second circle that will correspond to the expenses that we had to pay bills. So we're going to pass the value of the radius, the circumference, the stroke dash offset as a prop, and the rotation will be the value of the angle of the groceries (because we want it to start after the stroke of the groceries).
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
<G rotation={-90} originX="90" originY="90">
{ total === 0 ? (
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F1F6F9"
fill="transparent"
strokeWidth="40"
/>
) : (
<>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F05454"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={groceriesStrokeDashoffset}
rotation={0}
originX="90"
originY="90"
strokeLinecap="round"
/>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#30475E"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={billsStrokeDashoffset}
rotation={groceriesAngle}
originX="90"
originY="90"
strokeLinecap="round"
/>
// ...
</>
)
}
</G>
</Svg>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Now let's create the third and last circle that will correspond to the expenses we had with regular expenses. So we will pass as prop the value of the radius, the circumference, the stroke dash offset, and the rotation will be the value of the regular expenses angle.
// @src/App.js
import React from 'react';
import { View, StyleSheet } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
<G rotation={-90} originX="90" originY="90">
{ total === 0 ? (
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F1F6F9"
fill="transparent"
strokeWidth="40"
/>
) : (
<>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F05454"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={groceriesStrokeDashoffset}
rotation={0}
originX="90"
originY="90"
strokeLinecap="round"
/>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#30475E"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={billsStrokeDashoffset}
rotation={groceriesAngle}
originX="90"
originY="90"
strokeLinecap="round"
/>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#222831"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={regularStrokeDashoffset}
rotation={regularAngle}
originX="90"
originY="90"
strokeLinecap="round"
/>
</>
)
}
</G>
</Svg>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
});
Last but not least, just show the amount that was spent, for that we will import the React Native Text component and create styles for it.
// @src/App.js
import React from 'react';
import { View, StyleSheet, Text } from "react-native";
import Svg, { G, Circle } from "react-native-svg";
const App = () => {
const radius = 70;
const circleCircumference = 2 * Math.PI * radius;
const groceries = 241;
const bills = 372;
const regular = 188;
const total = groceries + bills + regular;
const groceriesPercentage = (groceries / total) * 100;
const billsPercentage = (bills / total) * 100;
const regularPercentage = (regular / total) * 100;
const groceriesStrokeDashoffset =
circleCircumference - (circleCircumference * groceriesPercentage) / 100;
const billsStrokeDashoffset =
circleCircumference - (circleCircumference * billsPercentage) / 100;
const regularStrokeDashoffset =
circleCircumference - (circleCircumference * regularPercentage) / 100;
const groceriesAngle = (groceries / total) * 360;
const billsAngle = (bills / total) * 360;
const regularAngle = groceriesAngle + billsAngle;
return (
<View style={styles.container}>
<View style={styles.graphWrapper}>
<Svg height="160" width="160" viewBox="0 0 180 180">
<G rotation={-90} originX="90" originY="90">
{ total === 0 ? (
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F1F6F9"
fill="transparent"
strokeWidth="40"
/>
) : (
<>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#F05454"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={groceriesStrokeDashoffset}
rotation={0}
originX="90"
originY="90"
strokeLinecap="round"
/>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#30475E"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={billsStrokeDashoffset}
rotation={groceriesAngle}
originX="90"
originY="90"
strokeLinecap="round"
/>
<Circle
cx="50%"
cy="50%"
r={radius}
stroke="#222831"
fill="transparent"
strokeWidth="40"
strokeDasharray={circleCircumference}
strokeDashoffset={regularStrokeDashoffset}
rotation={regularAngle}
originX="90"
originY="90"
strokeLinecap="round"
/>
</>
)
}
</G>
</Svg>
<Text style={styles.label}>{total}€</Text>
</View>
</View>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
graphWrapper: {
alignItems: "center",
justifyContent: "center",
},
label: {
position: "absolute",
textAlign: "center",
fontWeight: "700",
fontSize: 24,
},
});
You should get a result similar to this:
Conclusion
As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻💻
Hope you have a great day! 🤙
Top comments (1)
Awesome!
Thank you very much!