react-native-simple-charts
Version:
Simple charts with gradient for react-native
443 lines (402 loc) • 13.6 kB
JavaScript
import React, { Component } from 'react';
import {
View,
ScrollView,
Dimensions,
TouchableOpacity,
InteractionManager,
ActivityIndicator,
Platform
} from 'react-native';
import Svg,{
LinearGradient,
Path,
Text,
Defs,
Stop,
G,
Circle,
Rect,
} from 'react-native-svg';
import * as Animatable from 'react-native-animatable'
import styles from './ChartStyle'
import {
getAxisXSectorsArray,
getAxisXLinesArray,
getAxisYLinesArray,
getPointPathArray,
transformYAxeData,
} from './ChartHelper'
import { BlurView } from 'react-native-blur';
const {width, height} = Dimensions.get('window');
export default class Chart extends Component {
constructor(props){
super(props)
if (this.props.data) {
const {transformedData, axisYData} = transformYAxeData(this.props.data, this.props.stepsOY, this.props.maxYValue)
this.data = transformedData
this.axisYData = axisYData
this.activeAxisXTextArray = this.props.activeAxisXTextArray || []
this.height = this.props.height
this.width = this.props.width || width * 1.2
this.graphHeight = this.height * 0.75
this.graphWidth = this.width * 0.75
this.Xwidth = this.graphWidth / this.props.scaleXAxis
this.Ywidth = this.graphHeight / (this.props.stepsOY - 0.3)
this.OX = this.props.leftPanelWidth
this.OY = this.Ywidth * this.props.stepsOY * 1.07
this.maxOY = 0
this.realWidth = this.OX + this.Xwidth * (this.data[0].chart.length)
this.maxOX = this.realWidth
}
this.state = {interactionsComplete: false}
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.data !== nextProps.data || this.state.interactionsComplete !== nextState.interactionsComplete ||
this.props.height !== nextProps.height) {
return true;
}
return false;
}
componentWillMount(){
if (this.props.data) {
this.calculateInitData()
}
}
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this.setState({interactionsComplete: true});
});
}
render() {
if (!this.props.data) {
return null
}
this.resetVariables()
let pathX = 'M ' + this.OX + ' ' + this.OY +
' ' + this.maxOX + ' ' + this.OY
return (
<View style={[styles.container, {backgroundColor: this.props.backgroundColor}]}>
{this.renderRootElement()}
<View style={[styles.leftPanel, !this.props.renderBlur || Platform.OS === 'android' ? {backgroundColor: this.props.leftPanelBG} : {}]}>
<Svg
height={this.height + 2}
width={this.props.leftPanelWidth}>
<Rect
x="0"
y="0"
width={this.props.leftPanelWidth}
height={this.height}
fill={'transparent'}
/>
{this.renderYAxe()}
{this.axisYData.map((text) => {
this.axisYTextCoordinates = this.axisYTextCoordinates ? this.axisYTextCoordinates - this.Ywidth : (this.OY - this.Ywidth)
return this.renderText(text, this.width * 0.05, this.axisYTextCoordinates, this.props.axisTextColor, this.props.axisTextOpacity)})}
</Svg>
</View>
{this.renderBlur()}
</View>
);
}
calculateInitData(){
this.axisXData = this.getAxisXData()
// this.axisYData = this.props.axisYData
this.chartHeight = this.graphHeight * 1.2 + this.Ywidth
this.convertedData = []
this.data.map((data) => {
this.convertedData.push(
this.convertData(data)
)
})
const stateData = {
axisXData: this.axisXData,
Xwidth: this.Xwidth,
Ywidth: this.Ywidth,
realWidth: this.realWidth,
OX: this.OX,
OY: this.OY,
maxOY: this.maxOY,
pointsData: this.data,
convertedData: this.convertedData,
height: this.height,
}
this.axisXSectorsArray = getAxisXSectorsArray(stateData)
this.axisXLinesArray = getAxisXLinesArray(stateData)
this.axisYLinesArray = getAxisYLinesArray(stateData, this.axisYData)
this.pointPathArray = getPointPathArray(stateData).pointPathArray
this.gradientPathArray = getPointPathArray(stateData).gradientPathArray
}
renderRootElement(){
return (
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.scrollView}>
<Svg
height={this.chartHeight}
width={this.realWidth}>
{this.axisXData.map((text, i) => {
this.axisXSectorsCoordinates = this.axisXSectorsCoordinates ? this.axisXSectorsCoordinates + this.Xwidth : this.OX
return this.renderAxisXSectors(i)})}
{this.axisXData.map((text, i) => {
this.axisXLinesCoordinates = this.axisXLinesCoordinates ? this.axisXLinesCoordinates + this.Xwidth : this.OX
return this.renderAxisXLines(i)})}
{this.axisYData.map((text,i) => {
this.axisYLinesCoordinates = this.axisYLinesCoordinates ? this.axisYLinesCoordinates - this.Ywidth : (this.OY - this.Ywidth)
return this.renderAxisYLines(i)})}
{this.renderXAxe()}
{this.axisXData.map((text) => {
let color = this.activeAxisXTextArray.includes(text) ? this.props.axisTextColorActive : this.props.axisTextColor
this.axisXTextCoordinates = this.axisXTextCoordinates ? this.axisXTextCoordinates + this.Xwidth : this.OX
return this.renderText(text, this.axisXTextCoordinates + this.Xwidth / 2, this.OY + this.Ywidth / 4, color, this.props.axisTextOpacity)})}
</Svg>
{this.renderMain()}
</ScrollView>
)
}
renderMain(){
this.globalPointIndex = -1
return (
<Animatable.View animation={'fadeIn'} duration={500} easing={'linear'} style={styles.main}>
<Svg
height={this.graphHeight + this.Ywidth / 1.5}
width={this.realWidth}>
{this.data.map((data, i) => {
return this.renderData(data, i)
})}
</Svg>
</Animatable.View>
)
}
renderBlur(){
if (this.props.renderBlur && Platform.OS === 'ios') {
return (
<BlurView
blurType="light"
blurAmount={10}
{...this.props.blurProps}
style={[styles.blurDark, {width: this.props.leftPanelWidth}]}
/>
)
}
}
renderData(data, index){
let convertedData = this.convertedData[index]
this.gradientIndex = -1
return (
<G key={Math.random()}>
{convertedData.map((chart, i) => {
if (i !== data.chart.length - 1) {
return this.renderPointPath(chart.x, chart.y, convertedData[i + 1].x, convertedData[i + 1].y, data.chart[i].props)
}
})}
</G>
)
}
convertData(data){
let convertedData = []
for (var i = 0; i < data.chart.length; i++) {
let chartObj = {
x: this.x(i),
y: this.y(data.chart[i].y)
}
convertedData.push(chartObj)
}
return convertedData
}
renderPointPath(x1,y1,x2,y2,props){
this.globalPointIndex++
return (
<G key={Math.random()}>
<Path
d={this.pointPathArray[this.globalPointIndex]}
stroke={props.strokeColor}
strokeWidth={props.strokeWidth}
opacity={props.strokeOpacity}
fill={'none'}/>
{props.fillGradient ? this.renderGradient(props.gradientStartColor, props.gradientEndColor) : ()=>{}}
{props.renderPoints ? this.renderPoints(x1,y1,props.pointColor1, props.strokeColor) : ()=>{}}
{props.renderPoints ? this.renderPoints(x2,y2,props.pointColor2, props.strokeColor) : ()=>{}}
</G>
)
}
renderGradient(startColor, endColor){
this.gradientIndex++
return (
<G key={Math.random()} style={{flex: 1}}>
<Defs>
<LinearGradient id="grad" x1="0" y1="0" x2="0" y2={this.OY}>
<Stop offset="1" stopColor={startColor || 'transparent'}
stopOpacity={this.props.gradientOpacityStart} />
<Stop offset="0" stopColor={endColor}
stopOpacity={this.props.gradientOpacityEnd} />
</LinearGradient>
</Defs>
<Path
d={this.gradientPathArray[this.gradientIndex]}
stroke="none"
fill={'url(#grad)'}/>
</G>
)
}
renderPoints(x,y, color, strokeColor){
return (
<Circle
key={Math.random()}
cx={x}
cy={y}
fill={color}
r={this.props.pointRadius ? this.props.pointRadius : '3'}/>
)
}
renderAxisXSectors(i){
if (this.props.renderAxisXSectors && this.axisXSectorsArray[i]) {
return (
<Path
d={this.axisXSectorsArray[i]}
key={Math.random()}
stroke="none"
opacity={this.props.axisXSectorsOpacity ? this.props.axisXSectorsOpacity : 1}
fill={this.props.axisXSectorsColor ? this.props.axisXSectorsColor : 'black'}/>
)
}
}
renderAxisXLines(i){
if (this.props.renderAxisXLines) {
return (
<Path
d={this.axisXLinesArray[i]}
key={Math.random()}
stroke={this.props.axisLinesColor}
opacity={this.props.axisLinesOpacity}
strokeWidth={this.props.axisLinesWidth}/>
)
}
}
renderAxisYLines(i){
if (this.props.renderAxisYLines) {
return (
<Path
d={this.axisYLinesArray[i]}
key={Math.random()}
stroke={this.props.axisLinesColor}
opacity={this.props.axisLinesOpacity}
strokeWidth={this.props.axisLinesWidth}/>
)
}
}
renderXAxe(){
if (!this.props.hideXAxe) {
let pathX = 'M ' + this.OX + ' ' + this.OY +
' ' + this.maxOX + ' ' + this.OY
return (
<Path
d={pathX}
stroke={this.props.axisColor}
opacity={this.props.axisOpacity}
strokeWidth={this.props.axisStrokeWidth}/>
)
}
}
renderYAxe(){
if (!this.props.hideYAxe) {
let pathY = 'M ' + this.OX + ' ' + this.OY +
' ' + this.OX + ' ' + this.maxOY
return (
<Path
d={pathY}
stroke={this.props.axisColor}
opacity={this.props.axisOpacity}
strokeWidth={this.props.axisStrokeWidth}/>
)
}
}
x(data){
return this.OX + data * this.Xwidth + this.Xwidth / 2
}
y(data){
return this.OY - data * this.Ywidth / 2
}
renderText(text, x, y, color, opacity){
if (Number(text) === text && text % 1 !== 0) {
text = text.toFixed(1)
}
return (
<Text
x={x}
y={y}
key={text + x + y}
stroke={color}
fill={color}
opacity={opacity}
textAnchor="middle">{text}</Text>
)
}
getAxisXData(){
let axisXData = []
for (var i = 0; i < this.data[0].chart.length; i++) {
axisXData.push(this.data[0].chart[i].x.toString())
}
return axisXData
}
resetVariables(){
this.axisXSectorsCoordinates = null
this.axisXLinesCoordinates = null
this.axisYLinesCoordinates = null
this.axisXTextCoordinates = null
this.axisYTextCoordinates = null
this.renderAxisXSector = false
}
}
Chart.defaultProps = {
height: 150,
backgroundColor: 'white',
stepsOY: 4,
axisTextColor: 'black',
axisTextOpacity: 1,
axisColor: 'black',
axisOpacity: 1,
axisLinesOpacity: 1,
axisLinesWidth: 1,
axisStrokeWidth: 2,
axisLinesColor: 'black',
scaleXAxis: 5.5,
renderAxisXLines: true,
renderAxisYLines: true,
leftPanelWidth: 50,
leftPanelBG: 'white',
renderBlur: true,
gradientOpacityStart: '0.0',
gradientOpacityEnd: '0.55',
}
Chart.propTypes = {
data: React.PropTypes.array.isRequired,
height: React.PropTypes.number,
width: React.PropTypes.number,
backgroundColor: React.PropTypes.any,
gradientOpacityStart: React.PropTypes.string,
gradientOpacityEnd: React.PropTypes.string,
axisTextColor: React.PropTypes.any,
axisTextOpacity: React.PropTypes.number,
activeAxisXTextArray: React.PropTypes.array, // you can set unique text color for special OX axis values
axisTextColorActive: React.PropTypes.any, //there is this color
hideXAxe: React.PropTypes.bool,
hideYAxe: React.PropTypes.bool,
axisStrokeWidth: React.PropTypes.number,
axisColor: React.PropTypes.any,
axisOpacity: React.PropTypes.number,
pointRadius: React.PropTypes.number, // chart point radius
axisLinesColor: React.PropTypes.string, // horizontal and vertical chart lines
axisLinesOpacity: React.PropTypes.number,
axisLinesWidth: React.PropTypes.number,
renderAxisXSectors: React.PropTypes.bool,
axisXSectorsColor: React.PropTypes.string,
axisXSectorsOpacity: React.PropTypes.number,
renderAxisXLines: React.PropTypes.bool, // vertical lines
renderAxisYLines: React.PropTypes.bool, //horizontal lines
leftPanelBG: React.PropTypes.any, // OY axis text backgroundColor
renderBlur: React.PropTypes.bool, // IOS only blured OY axis text
blurProps: React.PropTypes.any,
leftPanelWidth: React.PropTypes.number,
stepsOY: React.PropTypes.number, // number of section on axis Y
scaleXAxis: React.PropTypes.number, // define chart condensation
maxYValue: React.PropTypes.number, // set max OY value that can be rendered
}