Skip to content

Commit 1ffc372

Browse files
authored
New component - PieChart (#3470)
* Added new component - PieChart * Add null return for PieChart component when Svg or Path is not available
1 parent a50b5f1 commit 1ffc372

File tree

8 files changed

+281
-0
lines changed

8 files changed

+281
-0
lines changed

demo/src/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,9 @@ module.exports = {
267267
get Pinterest() {
268268
return require('./screens/realExamples/Pinterest').default;
269269
},
270+
get PieChartScreen() {
271+
return require('./screens/componentScreens/PieChartScreen.tsx').default;
272+
},
270273
get ListActionsScreen() {
271274
return require('./screens/realExamples/ListActions/ListActionsScreen').default;
272275
},

demo/src/screens/MenuStructure.js

+6
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ export const navigationData = {
104104
{title: 'SortableGridList', tags: 'sort grid list drag', screen: 'unicorn.components.SortableGridListScreen'}
105105
]
106106
},
107+
Charts: {
108+
title: 'Charts',
109+
screens: [
110+
{title: 'PieChart', tags: 'pie chart data', screen: 'unicorn.components.PieChartScreen'}
111+
]
112+
},
107113
LayoutsAndTemplates: {
108114
title: 'Layouts & Templates',
109115
screens: [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from 'react';
2+
import {ScrollView} from 'react-native';
3+
import {View, PieChart, Card, Text, Badge, PieChartSegmentProps, Colors} from 'react-native-ui-lib';
4+
5+
const SEGMENTS: PieChartSegmentProps[] = [
6+
{
7+
percentage: 40,
8+
color: Colors.blue30
9+
},
10+
{
11+
percentage: 30,
12+
color: Colors.red30
13+
},
14+
{
15+
percentage: 20,
16+
color: Colors.green30
17+
},
18+
{
19+
percentage: 10,
20+
color: Colors.purple30
21+
}
22+
];
23+
24+
const MONOCHROME_SEGMENTS: PieChartSegmentProps[] = [
25+
{
26+
percentage: 40,
27+
color: Colors.blue70
28+
},
29+
{
30+
percentage: 30,
31+
color: Colors.blue50
32+
},
33+
{
34+
percentage: 20,
35+
color: Colors.blue30
36+
},
37+
{
38+
percentage: 10,
39+
color: Colors.blue10
40+
}
41+
];
42+
43+
const NOT_FULL_PIECHART: PieChartSegmentProps[] = [
44+
{
45+
percentage: 30,
46+
color: Colors.blue30
47+
},
48+
{
49+
percentage: 40,
50+
color: Colors.red30
51+
}
52+
];
53+
54+
const PieChartScreen = () => {
55+
const renderSegmentLabel = (segment: PieChartSegmentProps, text: string) => {
56+
const {percentage, color} = segment;
57+
return (
58+
<View row gap-s1 marginB-s1 key={text}>
59+
<Badge size={10} containerStyle={{justifyContent: 'center'}} backgroundColor={color}/>
60+
<View>
61+
<Text>{text}</Text>
62+
<Text marginL-s1>{percentage}%</Text>
63+
</View>
64+
</View>
65+
);
66+
};
67+
68+
const renderPieChartCard = (segments: PieChartSegmentProps[]) => {
69+
return (
70+
<Card row spread paddingL-s2 paddingR-s10 paddingV-s2>
71+
<View centerV>
72+
<PieChart segments={segments} diameter={150}/>
73+
</View>
74+
<View height={'100%'} gap-s1>
75+
{segments.map((segment, index) => renderSegmentLabel(segment, `Value ${index + 1}`))}
76+
</View>
77+
</Card>
78+
);
79+
};
80+
81+
return (
82+
<ScrollView>
83+
<View padding-page gap-s2>
84+
<Text text50L marginB-s2>
85+
PieChart
86+
</Text>
87+
{renderPieChartCard(SEGMENTS)}
88+
<Text text50L marginV-s2>
89+
Monochrome colors
90+
</Text>
91+
{renderPieChartCard(MONOCHROME_SEGMENTS)}
92+
<Text text50L marginV-s2>
93+
Not Full PieChart
94+
</Text>
95+
{renderPieChartCard(NOT_FULL_PIECHART)}
96+
</View>
97+
</ScrollView>
98+
);
99+
};
100+
101+
export default PieChartScreen;

demo/src/screens/componentScreens/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export function registerScreens(registrar) {
22
registrar('unicorn.components.ActionSheetScreen', () => require('./ActionSheetScreen').default);
3+
registrar('unicorn.components.PieChartScreen', () => require('./PieChartScreen').default);
34
registrar('unicorn.components.ActionBarScreen', () => require('./ActionBarScreen').default);
45
registrar('unicorn.components.AvatarsScreen', () => require('./AvatarsScreen').default);
56
registrar('unicorn.components.AnimatedImageScreen', () => require('./AnimatedImageScreen').default);
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "PieChart",
3+
"category": "charts",
4+
"description": "Pie Chart",
5+
"example": "https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/PieChartScreen.tsx",
6+
"props": [
7+
{"name": "segments", "type": "PieChartSegmentProps[]", "description": "Pie chart segments array"},
8+
{"name": "diameter", "type": "number", "description": "Pie chart diameter"},
9+
{"name": "dividerWidth", "type": "number", "description": "The width of the divider between the segments"},
10+
{"name": "dividerColor", "type": "ColorValue", "description": "The color of the divider between the segments"}
11+
],
12+
"snippet": [
13+
"<PieChart segments={[{percentage: 50, color: Colors.blue30}, {percentage: 30, color: Colors.red30}, {percentage: 20, color: Colors.green30}]} diameter={144}/>"
14+
]
15+
}
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
import {ColorValue, StyleSheet} from 'react-native';
3+
import View from '../view';
4+
import {SvgPackage} from '../../optionalDependencies';
5+
import {Colors} from '../../style';
6+
const {Svg, Path} = SvgPackage;
7+
8+
export type PieSegmentProps = {
9+
/**
10+
* The percentage of pie the segment should cover
11+
*/
12+
percentage: number;
13+
/**
14+
* The radius of the containing pie
15+
*/
16+
radius: number;
17+
/**
18+
* The color of the segment
19+
*/
20+
color: string;
21+
/**
22+
* The start angle of the segment
23+
*/
24+
startAngle?: number;
25+
/**
26+
* The padding between the segments and the container of the pie.
27+
*/
28+
padding?: number;
29+
/**
30+
* The width of the divider between the segments
31+
*/
32+
dividerWidth?: number;
33+
/**
34+
* The color of the divider between the segments
35+
*/
36+
dividerColor?: ColorValue;
37+
};
38+
39+
const PieSegment = (props: PieSegmentProps) => {
40+
const {
41+
percentage,
42+
radius,
43+
color,
44+
startAngle = 0,
45+
padding = 0,
46+
dividerWidth = 4,
47+
dividerColor = Colors.$backgroundDefault
48+
} = props;
49+
50+
const actualRadius = radius - padding;
51+
const centerXAndY = radius;
52+
const amountToCover = (percentage / 100) * 360;
53+
const angleFromTop = startAngle - 90;
54+
55+
const startRad = (angleFromTop * Math.PI) / 180;
56+
const endRad = startRad + (amountToCover * Math.PI) / 180;
57+
58+
const startX = centerXAndY + Math.cos(startRad) * actualRadius;
59+
const startY = centerXAndY + Math.sin(startRad) * actualRadius;
60+
const endX = centerXAndY + Math.cos(endRad) * actualRadius;
61+
const endY = centerXAndY + Math.sin(endRad) * actualRadius;
62+
63+
const largeArcFlag = amountToCover > 180 ? 1 : 0;
64+
const sweepFlag = 1;
65+
66+
const arcPath = `
67+
M ${centerXAndY} ${centerXAndY}
68+
L ${startX} ${startY}
69+
A ${actualRadius} ${actualRadius} 0 ${largeArcFlag} ${sweepFlag} ${endX} ${endY}
70+
Z
71+
`;
72+
const startBorderLine = `M ${centerXAndY} ${centerXAndY} L ${startX} ${startY}`;
73+
const endBorderLine = `M ${centerXAndY} ${centerXAndY} L ${endX} ${endY}`;
74+
75+
const arc = <Path d={arcPath} fill={color}/>;
76+
const borders = (
77+
<Path
78+
d={`${startBorderLine} ${endBorderLine}`}
79+
fill="none"
80+
stroke={dividerColor}
81+
strokeWidth={dividerWidth / 2}
82+
strokeLinejoin="round"
83+
/>
84+
);
85+
const totalSize = radius * 2 + padding;
86+
87+
return (
88+
<View style={styles.container}>
89+
<Svg width={totalSize} height={totalSize} viewBox={`0 0 ${totalSize} ${totalSize}`} style={styles.svg}>
90+
{arc}
91+
{borders}
92+
</Svg>
93+
</View>
94+
);
95+
};
96+
97+
export default PieSegment;
98+
99+
const styles = StyleSheet.create({
100+
container: {
101+
position: 'absolute'
102+
},
103+
svg: {
104+
position: 'absolute'
105+
}
106+
});

src/components/pieChart/index.tsx

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import View from '../view';
3+
import PieSegment, {PieSegmentProps} from './PieSegment';
4+
import {SvgPackage} from '../../optionalDependencies';
5+
const {Svg, Path} = SvgPackage;
6+
7+
export type PieChartSegmentProps = Pick<PieSegmentProps, 'percentage' | 'color'>;
8+
9+
export type PieChartProps = {
10+
/**
11+
* Pie chart segments array
12+
*/
13+
segments: PieChartSegmentProps[];
14+
/**
15+
* Pie chart diameter
16+
*/
17+
diameter?: number;
18+
} & Pick<PieSegmentProps, 'dividerWidth' | 'dividerColor'>;
19+
20+
const DEFAULT_DIAMETER = 144;
21+
22+
const PieChart = (props: PieChartProps) => {
23+
const {segments, diameter = DEFAULT_DIAMETER, ...others} = props;
24+
25+
if (!Svg || !Path) {
26+
console.error(`RNUILib PieChart requires installing "@react-native-svg" dependency`);
27+
return null;
28+
}
29+
30+
const renderPieSegments = () => {
31+
let currentStartAngle = 0;
32+
33+
return segments.map((segment, index) => {
34+
const startAngle = currentStartAngle;
35+
currentStartAngle += (segment.percentage / 100) * 360;
36+
return (
37+
<PieSegment key={index} {...segment} {...others} startAngle={startAngle} radius={diameter / 2}/>
38+
);
39+
});
40+
};
41+
return (
42+
<View width={diameter} height={diameter}>
43+
{renderPieSegments()}
44+
</View>
45+
);
46+
};
47+
48+
export default PieChart;

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export {
123123
PickerItemsListProps,
124124
PickerMethods
125125
} from './components/picker';
126+
export {default as PieChart, PieChartSegmentProps} from './components/pieChart';
126127
export {default as ProgressBar, ProgressBarProps} from './components/progressBar';
127128
export {default as ProgressiveImage, ProgressiveImageProps} from './components/progressiveImage';
128129
export {default as RadioButton, RadioButtonProps} from './components/radioButton';

0 commit comments

Comments
 (0)