Last active
May 7, 2021 12:14
-
-
Save SatyaSnehith/c62503e836eb9b4f262c8bd4eb7e28e4 to your computer and use it in GitHub Desktop.
Doughnut chart in react native
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {Component, useState} from 'react'; | |
import {View, TouchableWithoutFeedback, StyleSheet, Animated, Easing} from 'react-native'; | |
import Svg, {Path} from 'react-native-svg'; | |
function getPos(x, y, radius, angle) { | |
let radians = (angle + 90.0) * Math.PI / 180.0; | |
return { | |
x: (radius * Math.sin(radians)) + x, | |
y: (radius * Math.cos(radians)) + y | |
}; | |
} | |
const Arc = (props) => { | |
var width = props.size; | |
var height = width; | |
var centerX = width / 2; | |
var centerY = centerX; | |
var outerRadius = width / 2; | |
var innerRadius = outerRadius - 40; | |
let pad = props.padding; | |
var startAngle = parseInt(props.startAngle) + pad; | |
var endAngle = parseInt(props.endAngle) - pad; | |
var outerStart = getPos(centerX, centerY, outerRadius, startAngle); | |
var innerStart = getPos(centerX, centerY, innerRadius, startAngle); | |
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"; | |
// var innerEnd = getPos(centerX, centerY, innerRadius, endAngle) | |
// var outerEnd = getPos(centerX, centerY, outerRadius, endAngle) | |
let fromStrokeWidth = 0; | |
let toStrokeWidth = 5; | |
props.select(() => { | |
//scale animation - increasing stroke width - stroke color must be same as arc color | |
strokeWidth.setValue(fromStrokeWidth) | |
Animated.timing(strokeWidth, { | |
toValue: toStrokeWidth, | |
duration: 350, | |
useNativeDriver: true, | |
}).start(); | |
//zIndex animation for avoiding overlaping with other arcs | |
zIndex.setValue(0) | |
Animated.timing(zIndex, { | |
toValue: 1, | |
duration: 350, | |
useNativeDriver: true, | |
}).start(); | |
}) | |
props.unselect(() => { | |
strokeWidth.setValue(toStrokeWidth) | |
Animated.timing(strokeWidth, { | |
toValue: fromStrokeWidth, | |
duration: 350, | |
useNativeDriver: true, | |
}).start(); | |
zIndex.setValue(1) | |
Animated.timing(zIndex, { | |
toValue: 0, | |
duration: 350, | |
useNativeDriver: true, | |
}).start(); | |
}) | |
const AnimatedPath = Animated.createAnimatedComponent(Path); | |
const AnimatedSvg = Animated.createAnimatedComponent(Svg); | |
//main animation | |
const anim = new Animated.Value(0); | |
let ir = []; | |
let ds = []; | |
for (let i = startAngle; i < endAngle; ++i) { | |
var outerEnd = getPos(centerX, centerY, outerRadius, i) | |
var innerEnd = getPos(centerX, centerY, innerRadius, i) | |
ir.push(parseInt(i - startAngle)); | |
ds.push(["M ", outerStart.x, outerStart.y, | |
" A ", outerRadius, outerRadius, "0", largeArcFlag, "0" , outerEnd.x , outerEnd.y, | |
" L ", innerEnd.x, innerEnd.y, | |
" A ", innerRadius, innerRadius, "0", largeArcFlag, "1", innerStart.x, innerStart.y, | |
" Z "].join(" ")); | |
} | |
Animated.timing(anim, { | |
toValue: endAngle - startAngle, | |
duration: 350, | |
useNativeDriver: true, | |
delay: props.animDelay, | |
easing: Easing.easeInCirc | |
}).start(); | |
//opacity animation | |
let opacity = new Animated.Value(0); | |
Animated.timing(opacity, { | |
toValue: 1, | |
duration: 700, | |
useNativeDriver: true, | |
delay: props.animDelay, | |
}).start(); | |
const strokeWidth = new Animated.Value(fromStrokeWidth); | |
const zIndex = new Animated.Value(0); | |
const elevation = new Animated.Value(0); | |
let padding = 10; | |
return( | |
<Animated.View style={[styles.center, styles.pos, { | |
width:(width + padding), | |
height:(height + padding), | |
opacity: opacity, | |
zIndex: zIndex, | |
alignItems: "center"}]}> | |
<AnimatedSvg> | |
<AnimatedPath | |
transform="translate(5, 5)" | |
d={anim.interpolate({ | |
inputRange: ir, | |
outputRange: ds, | |
})} stroke={props.color} strokeWidth={strokeWidth} strokeLinejoin="round" fill={props.color}/> | |
</AnimatedSvg> | |
</Animated.View> | |
) | |
} | |
const styles = StyleSheet.create({ | |
bg: { | |
backgroundColor: "#ffffff" | |
}, | |
border: { | |
borderColor: "#000000", | |
borderWidth: 10, | |
borderStyle: "solid" | |
}, | |
center: { | |
justifyContent: "center", | |
display: 'flex', | |
position: "relative", | |
}, | |
pos: { | |
position: "absolute" | |
} | |
}) | |
class Pie { | |
constructor(size, padding) { | |
this.size = size; | |
this.padding = padding; | |
this.arcs = []; | |
this.refs = []; | |
this.clicks = []; | |
this.startAngle = 0; | |
this.myRef = React.createRef(); | |
this.index = 1; | |
} | |
add(percent, color, key) { | |
let angle = 360 * percent / 100; | |
let endAngle = this.startAngle + angle; | |
let ref = { | |
startAngle: this.startAngle, | |
endAngle: endAngle, | |
key: key, | |
selected: false | |
}; | |
this.arcs.push( | |
<Arc | |
size={this.size} | |
key={key} | |
padding={this.padding} | |
startAngle={this.startAngle} | |
endAngle={endAngle} | |
color={color} | |
animDelay={150} | |
select={(action) => { | |
ref.select = action; | |
}} | |
unselect={(action) => { | |
ref.unselect = action; | |
}}/> | |
); | |
this.refs.push(ref); | |
this.index++; | |
this.startAngle = endAngle; | |
} | |
fireClick(angle) { | |
for (let i = 0; i < this.refs.length; ++i) { | |
let ref = this.refs[i]; | |
if (angle > ref.startAngle && angle < ref.endAngle) { | |
this.click(ref.key); | |
if (ref.selected) { | |
ref.unselect(); | |
ref.selected = false; | |
} else { | |
ref.select(); | |
ref.selected = true; | |
} | |
if (this.lastSelection != undefined && this.lastSelection.key != ref.key && this.lastSelection.selected) { | |
this.lastSelection.unselect(); | |
this.lastSelection.selected = false; | |
} | |
this.lastSelection = ref; | |
} | |
} | |
} | |
onClick(click) { | |
this.click = click; | |
} | |
handlePress(event) { | |
let x = event.nativeEvent.locationX; | |
let y = event.nativeEvent.locationY; | |
let center = this.size / 2; | |
let angle = Math.atan2(y - center, x - center) * 180 / Math.PI; | |
angle = angle < 0 ? -angle : 360 - angle; | |
console.log(angle); | |
this.fireClick(angle); | |
} | |
get() { | |
return ( | |
<TouchableWithoutFeedback onPress={(event) => {this.handlePress(event);}} > | |
<View style={[styles.center, styles.bg, { | |
width: this.size + 10, | |
height: this.size + 10, | |
alignSelf: "center" | |
}]}>{this.arcs}</View> | |
</TouchableWithoutFeedback> | |
); | |
} | |
} | |
class Example extends Component { | |
constructor(props) { | |
super(props); | |
} | |
render() { | |
let pie = new Pie(250, 0.5); | |
pie.add(10, "green", "c0"); | |
pie.add(5, "cyan", "c1"); | |
pie.add(1, "orange", "c2"); | |
pie.add(15, "gray", "c3"); | |
pie.add(15, "red", "c4"); | |
pie.add(10, "green", "c5"); | |
pie.add(5, "cyan", "c6"); | |
pie.add(9, "orange", "c7"); | |
pie.add(15, "gray", "c8"); | |
pie.add(15, "red", "c9"); | |
pie.onClick(function(key) { | |
console.log(key); | |
}); | |
return pie.get(); | |
} | |
} | |
export default Example; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment