UNPKG

react-native-svg-charts

Version:

Customizable charts (Line, Bar, Area, Pie, Circle, Progress) for React Native

231 lines (196 loc) 6.88 kB
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 BarChart extends PureComponent { state = { width: 0, height: 0, } _onLayout(event) { const { nativeEvent: { layout: { height, width }, }, } = event this.setState({ height, width }) } calcXScale(domain) { const { horizontal, contentInset: { left = 0, right = 0 }, spacingInner, spacingOuter, clamp, } = this.props const { width } = this.state if (horizontal) { return scale .scaleLinear() .domain(domain) .range([left, width - right]) .clamp(clamp) } return scale .scaleBand() .domain(domain) .range([left, width - right]) .paddingInner([spacingInner]) .paddingOuter([spacingOuter]) } calcYScale(domain) { const { horizontal, contentInset: { top = 0, bottom = 0 }, spacingInner, spacingOuter, clamp, } = this.props const { height } = this.state if (horizontal) { return scale .scaleBand() .domain(domain) .range([top, height - bottom]) .paddingInner([spacingInner]) .paddingOuter([spacingOuter]) } return scale .scaleLinear() .domain(domain) .range([height - bottom, top]) .clamp(clamp) } calcAreas(x, y) { const { horizontal, data, yAccessor } = this.props const values = data.map((item) => yAccessor({ item })) if (horizontal) { return data.map((bar, index) => ({ bar, path: shape .area() .y((value, _index) => (_index === 0 ? y(index) : y(index) + y.bandwidth())) .x0(x(0)) .x1((value) => x(value)) .defined((value) => typeof value === 'number')([values[index], values[index]]), })) } return data.map((bar, index) => ({ bar, path: shape .area() .y0(y(0)) .y1((value) => y(value)) .x((value, _index) => (_index === 0 ? x(index) : x(index) + x.bandwidth())) .defined((value) => typeof value === 'number')([values[index], values[index]]), })) } calcExtent() { const { data, gridMin, gridMax, yAccessor } = this.props const values = data.map((obj) => yAccessor({ item: obj })) const extent = array.extent([...values, gridMax, gridMin]) const { yMin = extent[0], yMax = extent[1] } = this.props return [yMin, yMax] } calcIndexes() { const { data } = this.props return data.map((_, index) => index) } render() { const { data, animate, animationDuration, style, numberOfTicks, svg, horizontal, children } = this.props const { height, width } = this.state if (data.length === 0) { return <View style={style} /> } const extent = this.calcExtent() const indexes = this.calcIndexes() const ticks = array.ticks(extent[0], extent[1], numberOfTicks) const xDomain = horizontal ? extent : indexes const yDomain = horizontal ? indexes : extent const x = this.calcXScale(xDomain) const y = this.calcYScale(yDomain) const bandwidth = horizontal ? y.bandwidth() : x.bandwidth() const areas = this.calcAreas(x, y).filter( (area) => area.bar !== null && area.bar !== undefined && area.path !== null ) const extraProps = { x, y, width, height, bandwidth, ticks, data, } 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) } })} {areas.map((area, index) => { const { bar: { svg: barSvg = {} }, path, } = area return ( <Path key={index} {...svg} {...barSvg} d={path} animate={animate} animationDuration={animationDuration} /> ) })} {React.Children.map(children, (child) => { if (child && !child.props.belowChart) { return React.cloneElement(child, extraProps) } })} </Svg> )} </View> </View> ) } } BarChart.propTypes = { data: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.object])).isRequired, style: PropTypes.any, spacingInner: PropTypes.number, spacingOuter: PropTypes.number, animate: PropTypes.bool, animationDuration: PropTypes.number, contentInset: PropTypes.shape({ top: PropTypes.number, left: PropTypes.number, right: PropTypes.number, bottom: PropTypes.number, }), numberOfTicks: PropTypes.number, gridMin: PropTypes.number, gridMax: PropTypes.number, svg: PropTypes.object, yMin: PropTypes.any, yMax: PropTypes.any, clamp: PropTypes.bool, } BarChart.defaultProps = { spacingInner: 0.05, spacingOuter: 0.05, contentInset: {}, numberOfTicks: 10, svg: {}, yAccessor: ({ item }) => item, } export default BarChart