Skip to content

Instantly share code, notes, and snippets.

@Eldelshell
Last active January 11, 2017 18:36
Show Gist options
  • Save Eldelshell/40d10c3e343dec98e704a42f50ce815e to your computer and use it in GitHub Desktop.
Save Eldelshell/40d10c3e343dec98e704a42f50ce815e to your computer and use it in GitHub Desktop.
React Native Pie Chart
import React, { PropTypes } from 'react';
import { View, Platform } from 'react-native';
import { Surface, Shape, Path, Group } from 'ReactNativeART';
/**
* Custom implementation of the react-native-circular-progress that allows to
* render ART donut charts. Instead of accepting a single set of fill & color values,
* this components takes an array of objects.
* https://github.com/bgryszko/react-native-circular-progress
* @example
* <Donut
* rotation={0}
* size={80}
* width={8}
* data={[{fill: 80, color: 'red'},{fill: 20, color: 'green'}]}
* backgroundColor='orange'></Donut>
*/
export default class Donut extends React.Component {
static propTypes = {
style: View.propTypes.style,
size: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
backgroundColor: PropTypes.string,
rotation: PropTypes.number,
linecap: PropTypes.string,
children: PropTypes.func,
data: PropTypes.array.isRequired
}
static defaultProps = {
backgroundColor: '#e4e4e4',
rotation: 90,
linecap: 'butt',
data: []
}
componentWillMount() {
// Check we reach 100
const sum = this.props.data.map((d) => d.fill).reduce((i, j) => i + j);
if(sum > 100){
console.warn(`[Donut] Total values (${sum}) are higher than 100`);
}else if(sum < 100){
console.warn(`[Donut] Total values (${sum}) are less than 100`);
}
}
circlePath(cx, cy, r, startDegree, endDegree) {
const p = Path();
if (Platform.OS === 'ios') {
p.path.push(0, cx + r, cy);
p.path.push(4, cx, cy, r, startDegree * Math.PI / 180, endDegree * Math.PI / 180, 1);
} else {
// For Android we have to resort to drawing low-level Path primitives, as ART does not support
// arbitrary circle segments. It also does not support strokeDash.
// Furthermore, the ART implementation seems to be buggy/different than the iOS one.
// MoveTo is not needed on Android
p.path.push(4, cx, cy, r, startDegree * Math.PI / 180, (startDegree - endDegree) * Math.PI / 180, 0);
}
return p;
}
extractFill(fill) {
if (fill < 0.01) {
return 0;
} else if (fill > 100) {
return 100;
}
return fill;
}
renderCircle(fill, color, width, linecap, size) {
const f = this.extractFill(fill);
const circlePath = this.circlePath(size / 2, size / 2, size / 2 - width / 2, 0, 360 * f / 100);
return (
<Shape d={circlePath} stroke={color} strokeWidth={width} strokeCap={linecap}/>
);
}
renderCircles() {
const { size, width, backgroundColor, rotation, linecap } = this.props;
const backgroundPath = this.circlePath(size / 2, size / 2, size / 2 - width / 2, 0, 360);
let prevFill = 0;
const circles = [];
circles.push((
<Group key='donut-bg' rotation={rotation - 90} originX={size/2} originY={size/2}>
<Shape d={backgroundPath} stroke={backgroundColor} strokeWidth={width}/>
</Group>
));
for (let i = 0; i < this.props.data.length; i++) {
const d = this.props.data[i];
circles.push((
<Group key={`d-${i}`} rotation={(rotation - 90) + prevFill} originX={size/2} originY={size/2}>
{ this.renderCircle(d.fill, d.color, width, linecap, size) }
</Group>
));
prevFill = ((360 * d.fill) / 100) + prevFill;
}
return circles;
}
render() {
const { size, style, children } = this.props;
return (
<View style={style}>
<Surface width={size} height={size}>
{ this.renderCircles() }
</Surface>
{ children }
</View>
);
}
}
@Eldelshell
Copy link
Author

Eldelshell commented Dec 30, 2016

This is how it looks:

selection_999 052

It's based on the great work of @bgryszko on https://github.com/bgryszko/react-native-circular-progress

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment