react-native-pathjs-charts
Version:
Cross platform React Native charting library based on path-js and react-native-svg
214 lines (180 loc) • 7.33 kB
JavaScript
/*
Copyright 2016 Capital One Services, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
SPDX-Copyright: Copyright (c) Capital One Services, LLC
SPDX-License-Identifier: Apache-2.0
*/
import extractProps from 'react-native-svg/lib/extract/extractProps';
import React, {Component} from 'react'
import {Text as ReactText, Animated} from 'react-native'
import Svg,{ G, Path, Text, Circle} from 'react-native-svg'
import { Colors, Options, cyclic, identity, fontAdapt } from './util'
import _ from 'lodash'
const Pie = require('paths-js/pie')
const ANIMATION_START_DELAY = 1000;
export default class PieChart extends Component {
static defaultProps = {
options: {
margin: {top: 20, left: 20, right: 20, bottom: 20},
width: 600,
height: 600,
color: '#2980B9',
r: 100,
R: 200,
legendPosition: 'topLeft',
animate: {
enabled: false,
type: 'oneByOne',
duration: 200,
fillTransition: 3
},
label: {
fontFamily: 'Arial',
fontSize: 14,
bold: true,
color: '#ECF0F1'
}
},
}
constructor(props){
super(props);
this._animationRefArray = {};
this._startAnimation = this._startAnimation.bind(this)
this._createAnimation = this._createAnimation.bind(this)
this._onAnimationFinished = this._onAnimationFinished.bind(this)
if (this._shouldAnim()) this._animationArray = new Array(props.data.length)
}
_shouldAnim = () => this.props.options.animate.enabled && this.props.data.length > 1
color(i) {
let color = this.props.color || (this.props.options && this.props.options.color)
if (Array.isArray(color)) {
if (i >= color.length) {
const pallete = Colors.mix(color[i % color.length])
return Colors.string(cyclic(pallete, i))
}
return color[i];
} else {
if (color && !_.isString(color)) color = color.color
let pallete = this.props.pallete || (this.props.options && this.props.options.pallete) || Colors.mix(color || '#9ac7f7')
return Colors.string(cyclic(pallete, i))
}
}
get defaultRange() {
return _.map(Array(this.props.data && this.props.data.length),function(){return 0})
}
_onAnimationFinished(){
this._animationArray.forEach((anim) => anim.removeAllListeners())
}
_createAnimation(index, fill) {
const maxVal = this.props.data[index][this.props.accessorKey]
this._animationArray[index] = new Animated.Value(0)
this._animationArray[index].addListener(v => {
const sliceRef = this._animationRefArray[`SLICE${index}`]
if (sliceRef === undefined) return
if (sliceRef._finished) return;
const animValue = this._animationArray[index].interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
extrapolate: 'clamp'
})
const props = {
fill: fill,
fillOpacity: animValue.__getValue()
}
const nativeProps = extractProps(props, sliceRef)
sliceRef.setNativeProps(nativeProps)
if (props.fillOpacity === 1) sliceRef._finished = true
});
}
_startAnimation(){
Animated.sequence(
this._animationArray.map(a =>
Animated.timing(a, {
toValue: 1,
duration: this.props.options.animate.duration
})
)
).start(this._onAnimationFinished)
}
render() {
const noDataMsg = this.props.noDataMessage || 'No data available'
if (this.props.data === undefined) return (<ReactText>{noDataMsg}</ReactText>)
let options = new Options(this.props)
let x = (options.chartWidth / 2) - (options.margin.left || 0)
let y = (options.chartHeight / 2) - (options.margin.top || 0)
let radius = Math.min(x, y)
let r = this.props.r
r = (isNaN(r) ? (this.props.options && this.props.options.r) : r)
r = (isNaN(r) ? (radius / 2) : r)
let R = this.props.R
R = (R || (this.props.options && this.props.options.R))
R = (R || radius)
let [centerX, centerY] = this.props.center || (this.props.options && this.props.options.center) || [x, y]
let textStyle = fontAdapt(options.label)
let slices
if (this.props.data.length === 1) {
let item = this.props.data[0]
let outerFill = (item.color && Colors.string(item.color)) || this.color(0)
let innerFill = this.props.monoItemInnerFillColor || '#fff'
let stroke = typeof fill === 'string' ? outerFill : Colors.darkenColor(outerFill)
slices = (
<G>
<Circle r={R} cx={centerX} cy={centerY} stroke={stroke} fill={outerFill}/>
<Circle r={r} cx={centerX} cy={centerY} stroke={stroke} fill={innerFill}/>
<Text fontFamily={textStyle.fontFamily}
fontSize={textStyle.fontSize}
fontWeight={textStyle.fontWeight}
fontStyle={textStyle.fontStyle}
fill={textStyle.fill}
textAnchor="middle"
x={centerX}
y={centerY - R + ((R-r)/2)}>{item.name}</Text>
</G>
)
} else {
let chart = Pie({
center: [centerX, centerY],
r,
R,
data: this.props.data,
accessor: this.props.accessor || identity(this.props.accessorKey)
})
slices = chart.curves.map( (c, i) => {
let fill = (c.item.color && Colors.string(c.item.color)) || this.color(i)
let stroke = typeof fill === 'string' ? fill : Colors.darkenColor(fill)
const opacity = this._shouldAnim() ? 0 : 1
const finalStroke = this._shouldAnim() ? undefined : stroke
if (this._shouldAnim()) this._createAnimation(i, fill);
return (
<G key={ i }>
<Path ref={ref => (this._animationRefArray[`SLICE${i}`] = ref)} fill={fill} fillOpacity={opacity} d={c.sector.path.print() } stroke={finalStroke} />
<G x={options.margin.left} y={options.margin.top}>
<Text fontFamily={textStyle.fontFamily}
fontSize={textStyle.fontSize}
fontWeight={textStyle.fontWeight}
fontStyle={textStyle.fontStyle}
fill={textStyle.fill}
textAnchor="middle"
x={c.sector.centroid[0]}
y={c.sector.centroid[1]}>{ c.item.name }</Text>
</G>
</G>
)
})
}
if (this._shouldAnim()) setTimeout(this._startAnimation, ANIMATION_START_DELAY)
let returnValue = <Svg width={options.width} height={options.height}>
<G x={options.margin.left} y={options.margin.top}>
{ slices }
</G>
</Svg>
return returnValue
}
}