react-native-svg-charts
Version:
Customizable charts (Line, Bar, Area, Pie, Circle, Progress) for React Native
182 lines (155 loc) • 4.96 kB
JavaScript
import * as array from 'd3-array'
import * as scale from 'd3-scale'
import * as shape from 'd3-shape'
import PropTypes from 'prop-types'
import React, { PureComponent } from 'react'
import { View } from 'react-native'
import Svg from 'react-native-svg'
import Path from '../animated-path'
class Chart extends PureComponent {
state = {
width: 0,
height: 0,
}
_onLayout(event) {
const {
nativeEvent: {
layout: { height, width },
},
} = event
this.setState({ height, width })
}
createPaths() {
throw 'Extending "Chart" requires you to override "createPaths'
}
render() {
const {
data,
xAccessor,
yAccessor,
yScale,
xScale,
style,
animate,
animationDuration,
numberOfTicks,
contentInset: { top = 0, bottom = 0, left = 0, right = 0 },
gridMax,
gridMin,
clampX,
clampY,
svg,
children,
} = this.props
const { width, height } = this.state
if (data.length === 0) {
return <View style={style} />
}
const mappedData = data.map((item, index) => ({
y: yAccessor({ item, index }),
x: xAccessor({ item, index }),
}))
const yValues = mappedData.map((item) => item.y)
const xValues = mappedData.map((item) => item.x)
const yExtent = array.extent([...yValues, gridMin, gridMax])
const xExtent = array.extent([...xValues])
const { yMin = yExtent[0], yMax = yExtent[1], xMin = xExtent[0], xMax = xExtent[1] } = this.props
//invert range to support svg coordinate system
const y = yScale()
.domain([yMin, yMax])
.range([height - bottom, top])
.clamp(clampY)
const x = xScale()
.domain([xMin, xMax])
.range([left, width - right])
.clamp(clampX)
const paths = this.createPaths({
data: mappedData,
x,
y,
})
const ticks = y.ticks(numberOfTicks)
const extraProps = {
x,
y,
data,
ticks,
width,
height,
...paths,
}
return (
<View style={style}>
<View style={{ flex: 1 }} onLayout={(event) => this._onLayout(event)}>
{height > 0 && width > 0 && (
<Svg style={{ height, width }}>
{React.Children.map(children, (child) => {
if (child && child.props.belowChart) {
return React.cloneElement(child, extraProps)
}
return null
})}
<Path
fill={'none'}
{...svg}
d={paths.path}
animate={animate}
animationDuration={animationDuration}
/>
{React.Children.map(children, (child) => {
if (child && !child.props.belowChart) {
return React.cloneElement(child, extraProps)
}
return null
})}
</Svg>
)}
</View>
</View>
)
}
}
Chart.propTypes = {
data: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.object),
PropTypes.arrayOf(PropTypes.number),
PropTypes.arrayOf(PropTypes.array),
]).isRequired,
svg: PropTypes.object,
style: PropTypes.any,
animate: PropTypes.bool,
animationDuration: PropTypes.number,
curve: PropTypes.func,
contentInset: PropTypes.shape({
top: PropTypes.number,
left: PropTypes.number,
right: PropTypes.number,
bottom: PropTypes.number,
}),
numberOfTicks: PropTypes.number,
gridMin: PropTypes.number,
gridMax: PropTypes.number,
yMin: PropTypes.any,
yMax: PropTypes.any,
xMin: PropTypes.any,
xMax: PropTypes.any,
clampX: PropTypes.bool,
clampY: PropTypes.bool,
xScale: PropTypes.func,
yScale: PropTypes.func,
xAccessor: PropTypes.func,
yAccessor: PropTypes.func,
}
Chart.defaultProps = {
svg: {},
width: 100,
height: 100,
curve: shape.curveLinear,
contentInset: {},
numberOfTicks: 10,
xScale: scale.scaleLinear,
yScale: scale.scaleLinear,
xAccessor: ({ index }) => index,
yAccessor: ({ item }) => item,
}
export default Chart