diff --git a/CHANGELOG.md b/CHANGELOG.md index 672ebf94..2bac4704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,10 @@ -5.x.0 - -* Added `cornerRadius` to progressCircle -* Adhere to `Svg` api with `height` and `width` instead of `flex: 1` -* StackedBarChart now supports `svg` prop for each data entry! Allowing onPress among other things. -* StackedAreaChart now supports `svg` prop for each area! Allowing onPress among other things - * The two above changes does remove the other "svg" props from the charts, for example `renderGradient` - that is now replaces with the same gradient API as the other charts (i.e children). -* PieChart supports `(start|end)Angle` +5.2.0 + +* Add `x(Min|Max)` and `y(Min|Max)` +* Add `clamp(X|Y)` to use in conjunction with above props (default `false`) +* Add xScale to StackedAreaChart +* Deprecate `AreaChart` prop `start` in favour of `baseline` due to better naming. + diff --git a/README.md b/README.md index e7787161..8497191e 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,11 @@ yarn storybook | contentInset | { top: 0, left: 0, right: 0, bottom: 0 } | An object that specifies how much fake "margin" to use inside of the SVG canvas. This is particularly helpful on Android where `overflow: "visible"` isn't supported and might cause clipping. Note: important to have same contentInset on axis's and chart | | numberOfTicks | 10 | We use [d3-array](https://github.com/d3/d3-array#ticks) to evenly distribute the grid and dataPoints on the yAxis. This prop specifies how many "ticks" we should try to render. Note: important that this prop is the same on both the chart and on the yAxis | | showGrid | true | Whether or not to show the grid lines | -| gridMin | undefined | Normally the graph tries to draw from edge to edge within the view bounds. Using this prop will allow the grid to reach further than the actual dataPoints. [Example](#gridmin/max) | -| gridMax | undefined | The same as "gridMin" but will instead increase the grids maximum value | -| children | undefined | One or many `react-native-svg` components that will be used to enhance your chart| +| yMin | undefined | Alter how the chart bounds are calculated | +| yMax | undefined | Alter how the chart bounds are calculated | +| xMin | undefined | Alter how the chart bounds are calculated | +| xMax | undefined | Alter how the chart bounds are calculated | +| children | undefined | One or many `react-native-svg` components that will be used to enhance your chart | ## Common arguments to children diff --git a/src/bar-chart/bar-chart-grouped.js b/src/bar-chart/bar-chart-grouped.js index 03537455..31174325 100644 --- a/src/bar-chart/bar-chart-grouped.js +++ b/src/bar-chart/bar-chart-grouped.js @@ -15,6 +15,7 @@ class GroupedBarChart extends BarChart { }, spacingInner, spacingOuter, + clamp, } = this.props const { width } = this.state @@ -23,6 +24,7 @@ class GroupedBarChart extends BarChart { return scale.scaleLinear() .domain(domain) .range([ left, width - right ]) + .clamp(clamp) } return scale.scaleBand() @@ -41,6 +43,7 @@ class GroupedBarChart extends BarChart { top = 0, bottom = 0, }, + clamp, } = this.props const { height } = this.state @@ -56,6 +59,7 @@ class GroupedBarChart extends BarChart { return scale.scaleLinear() .domain(domain) .range([ height - bottom, top ]) + .clamp(clamp) } calcAreas(x, y) { @@ -138,7 +142,14 @@ class GroupedBarChart extends BarChart { data.map(obj => obj.data.map(item => yAccessor({ item }))) ) - return array.extent([ ...dataExtent, gridMax, gridMin ]) + const extent = array.extent([ ...dataExtent, gridMax, gridMin ]) + + const { + yMin = extent[ 0 ], + yMax = extent[ 1 ], + } = this.props + + return [ yMin, yMax ] } calcIndexes() { diff --git a/src/bar-chart/bar-chart.js b/src/bar-chart/bar-chart.js index 00c54a5f..a39e9077 100644 --- a/src/bar-chart/bar-chart.js +++ b/src/bar-chart/bar-chart.js @@ -28,6 +28,7 @@ class BarChart extends PureComponent { }, spacingInner, spacingOuter, + clamp, } = this.props const { width } = this.state @@ -36,6 +37,7 @@ class BarChart extends PureComponent { return scale.scaleLinear() .domain(domain) .range([ left, width - right ]) + .clamp(clamp) } return scale.scaleBand() @@ -54,6 +56,7 @@ class BarChart extends PureComponent { }, spacingInner, spacingOuter, + clamp, } = this.props const { height } = this.state @@ -69,6 +72,7 @@ class BarChart extends PureComponent { return scale.scaleLinear() .domain(domain) .range([ height - bottom, top ]) + .clamp(clamp) } calcAreas(x, y) { @@ -107,7 +111,14 @@ class BarChart extends PureComponent { const { data, gridMin, gridMax, yAccessor } = this.props const values = data.map(obj => yAccessor({ item: obj })) - return array.extent([ ...values, gridMax, gridMin ]) + const extent = array.extent([ ...values, gridMax, gridMin ]) + + const { + yMin = extent[ 0 ], + yMax = extent[ 1 ], + } = this.props + + return [ yMin, yMax ] } calcIndexes() { @@ -230,6 +241,10 @@ BarChart.propTypes = { gridMin: PropTypes.number, gridMax: PropTypes.number, svg: PropTypes.object, + + yMin: PropTypes.any, + yMax: PropTypes.any, + clamp: PropTypes.bool, } BarChart.defaultProps = { diff --git a/src/chart.js b/src/chart.js index 092e0be9..22241293 100644 --- a/src/chart.js +++ b/src/chart.js @@ -43,6 +43,8 @@ class Chart extends PureComponent { }, gridMax, gridMin, + clampX, + clampY, svg, children, } = this.props @@ -64,14 +66,23 @@ class Chart extends PureComponent { 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(yExtent) + .domain([ yMin, yMax ]) .range([ height - bottom, top ]) + .clamp(clampY) const x = xScale() - .domain(xExtent) + .domain([ xMin, xMax ]) .range([ left, width - right ]) + .clamp(clampX) const paths = this.createPaths({ data: mappedData, @@ -152,7 +163,13 @@ Chart.propTypes = { gridMin: PropTypes.number, gridMax: PropTypes.number, - gridProps: PropTypes.object, + + yMin: PropTypes.any, + yMax: PropTypes.any, + xMin: PropTypes.any, + xMax: PropTypes.any, + clampX: PropTypes.bool, + clampY: PropTypes.bool, xScale: PropTypes.func, yScale: PropTypes.func, diff --git a/src/stacked-area-chart.js b/src/stacked-area-chart.js index 3fb1a3ed..95ad8ccc 100644 --- a/src/stacked-area-chart.js +++ b/src/stacked-area-chart.js @@ -53,6 +53,10 @@ class AreaStack extends PureComponent { offset, order, svgs, + xAccessor, + xScale, + clampY, + clampX, } = this.props const { height, width } = this.state @@ -67,24 +71,36 @@ class AreaStack extends PureComponent { .offset(offset) (data) - //double merge arrays to extract just the values - const values = array.merge(array.merge(series)) + //double merge arrays to extract just the yValues + const yValues = array.merge(array.merge(series)) + const xValues = data.map((item, index) => xAccessor({ item, index })) - const extent = array.extent([ ...values, gridMin, gridMax ]) - const ticks = array.ticks(extent[ 0 ], extent[ 1 ], numberOfTicks) + 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 = scale.scaleLinear() - .domain([ extent[ 0 ], extent[ 1 ] ]) + .domain([ yMin, yMax ]) .range([ height - bottom, top ]) + .clamp(clampY) - const x = scale.scaleLinear() - .domain([ 0, data.length - 1 ]) + const x = xScale() + .domain([ xMin, xMax ]) .range([ left, width - right ]) + .clamp(clampX) + + const ticks = y.ticks(numberOfTicks) const areas = series.map((serie, index) => { const path = shape.area() - .x((d, index) => x(index)) + .x((d, index) => x(xAccessor({ item: d.data, index }))) .y0(d => y(d[ 0 ])) .y1(d => y(d[ 1 ])) .curve(curve) @@ -168,6 +184,15 @@ AreaStack.propTypes = { }), numberOfTicks: PropTypes.number, showGrid: PropTypes.bool, + xScale: PropTypes.func, + xAccessor: PropTypes.func, + + yMin: PropTypes.any, + yMax: PropTypes.any, + xMin: PropTypes.any, + xMax: PropTypes.any, + clampX: PropTypes.bool, + clampY: PropTypes.bool, } AreaStack.defaultProps = { @@ -179,6 +204,8 @@ AreaStack.defaultProps = { contentInset: {}, numberOfTicks: 10, showGrid: true, + xScale: scale.scaleLinear, + xAccessor: ({ index }) => index, } export default AreaStack diff --git a/src/x-axis.js b/src/x-axis.js index 81c6a905..24b215cb 100644 --- a/src/x-axis.js +++ b/src/x-axis.js @@ -61,6 +61,8 @@ class XAxis extends PureComponent { numberOfTicks, svg, children, + min, + max, } = this.props const { height, width } = this.state @@ -71,7 +73,9 @@ class XAxis extends PureComponent { const values = data.map((item, index) => xAccessor({ item, index })) const extent = array.extent(values) - const domain = scale === d3Scale.scaleBand ? values : extent + const domain = scale === d3Scale.scaleBand ? + values : + [ min || extent[ 0 ], max || extent[ 1 ] ] const x = this._getX(domain) const ticks = numberOfTicks ? x.ticks(numberOfTicks) : values @@ -142,6 +146,8 @@ XAxis.propTypes = { numberOfTicks: PropTypes.number, xAccessor: PropTypes.func, svg: PropTypes.object, + min: PropTypes.any, + max: PropTypes.any, } XAxis.defaultProps = { diff --git a/src/y-axis.js b/src/y-axis.js index dbda17af..eef9c7c6 100644 --- a/src/y-axis.js +++ b/src/y-axis.js @@ -60,8 +60,6 @@ class YAxis extends PureComponent { yAccessor, numberOfTicks, formatLabel, - min, - max, svg, children, } = this.props @@ -75,15 +73,21 @@ class YAxis extends PureComponent { const values = data.map((item, index) => yAccessor({ item, index })) const extent = array.extent([ ...values, min, max ]) - const ticks = scale === d3Scale.scaleBand ? - values : - array.ticks(extent[ 0 ], extent[ 1 ], numberOfTicks) - const domain = scale === d3Scale.scaleBand ? values : extent + const { + min = extent[ 0 ], + max = extent[ 1 ], + } = this.props + + const domain = scale === d3Scale.scaleBand ? values : [ min, max ] //invert range to support svg coordinate system const y = this.getY(domain) + const ticks = scale === d3Scale.scaleBand ? + values : + y.ticks(numberOfTicks) + const longestValue = ticks .map((value, index) => formatLabel(value, index)) .reduce((prev, curr) => prev.toString().length > curr.toString().length ? prev : curr, 0) diff --git a/storybook/stories/area-chart/index.js b/storybook/stories/area-chart/index.js index cf9d2f67..7f9717ca 100644 --- a/storybook/stories/area-chart/index.js +++ b/storybook/stories/area-chart/index.js @@ -3,9 +3,13 @@ import { storiesOf } from '@storybook/react-native' import Regular from './standard' import Partial from './partial' +import WithGradient from './with-gradient' +import WithDifferentBase from './with-differen-base' import ShowcaseCard from '../showcase-card' storiesOf('AreaChart', module) .addDecorator(getStory => { getStory() }) .add('Standard', () => ) .add('Partial', () => ) + .add('With gradient', () => ) + .add('With different base', () => ) diff --git a/storybook/stories/area-chart/with-differen-base.js b/storybook/stories/area-chart/with-differen-base.js new file mode 100644 index 00000000..0ba5b394 --- /dev/null +++ b/storybook/stories/area-chart/with-differen-base.js @@ -0,0 +1,28 @@ +import React from 'react' +import { AreaChart, Grid } from 'react-native-svg-charts' +import * as shape from 'd3-shape' +import * as array from 'd3-array' + +class DifferentBaseExample extends React.PureComponent { + + render() { + + const data = [ 50, 10, 40, 95, -4, -24, 85, 91, 35, 53, -53, 24, 50, -20, -80 ] + const min = array.extent(data)[ 0 ] + + return ( + + + + ) + } +} + +export default DifferentBaseExample diff --git a/storybook/stories/decorators/gradient-area.js b/storybook/stories/area-chart/with-gradient.js similarity index 100% rename from storybook/stories/decorators/gradient-area.js rename to storybook/stories/area-chart/with-gradient.js diff --git a/storybook/stories/area-stack/index.js b/storybook/stories/area-stack/index.js index 1c024115..d44a89a9 100644 --- a/storybook/stories/area-stack/index.js +++ b/storybook/stories/area-stack/index.js @@ -3,6 +3,7 @@ import { storiesOf } from '@storybook/react-native' import Regular from './standard' import WithTimeScale from './with-time-scale' +import WithGradient from './with-gradient' import WithYAxis from './with-y-axis' import ShowcaseCard from '../showcase-card' @@ -11,3 +12,4 @@ storiesOf('StackedAreaChart', module) .add('Standard', () => ) .add('With time scale', () => ) .add('With y axis', () => ) + .add('With gradient', () => ) diff --git a/storybook/stories/area-stack/standard.js b/storybook/stories/area-stack/standard.js index dbdaf1e7..620a62c3 100644 --- a/storybook/stories/area-stack/standard.js +++ b/storybook/stories/area-stack/standard.js @@ -53,7 +53,6 @@ class AreaStackChartExample extends React.PureComponent { keys={ keys } colors={ colors } curve={ shape.curveNatural } - showGrid={ false } svgs={ svgs } > diff --git a/storybook/stories/area-stack/with-gradient.js b/storybook/stories/area-stack/with-gradient.js new file mode 100644 index 00000000..11706735 --- /dev/null +++ b/storybook/stories/area-stack/with-gradient.js @@ -0,0 +1,75 @@ +import React from 'react' +import { StackedAreaChart, Grid } from 'react-native-svg-charts' +import { Defs, Stop, LinearGradient } from 'react-native-svg' +import * as shape from 'd3-shape' + +class AreaStackChartExample extends React.PureComponent { + + render() { + + const data = [ + { + month: new Date(2015, 0, 1), + apples: 3840, + bananas: 1920, + cherries: 960, + dates: 400, + }, + { + month: new Date(2015, 1, 1), + apples: 1600, + bananas: 1440, + cherries: 960, + dates: 400, + }, + { + month: new Date(2015, 2, 1), + apples: 640, + bananas: 960, + cherries: 3640, + dates: 400, + }, + { + month: new Date(2015, 3, 1), + apples: 3320, + bananas: 480, + cherries: 640, + dates: 400, + }, + ] + + const Gradient = ({ index }) => ( + + + + + + + ) + + const colors = [ '#8800cc', '#aa00ff', '#cc66ff', '#eeccff' ] + const keys = [ 'apples', 'bananas', 'cherries', 'dates' ] + const svgs = [ + { fill: 'url(#gradient)' }, + { onPress: () => console.log('bananas') }, + { onPress: () => console.log('cherries') }, + { onPress: () => console.log('dates') }, + ] + + return ( + + + + + ) + } +} + +export default AreaStackChartExample diff --git a/storybook/stories/area-stack/with-time-scale.js b/storybook/stories/area-stack/with-time-scale.js index 5f9e3de4..a64d22df 100644 --- a/storybook/stories/area-stack/with-time-scale.js +++ b/storybook/stories/area-stack/with-time-scale.js @@ -1,7 +1,9 @@ import React from 'react' -import { StackedAreaChart, Grid } from 'react-native-svg-charts' +import { View } from 'react-native' +import { StackedAreaChart, Grid, XAxis } from 'react-native-svg-charts' import * as shape from 'd3-shape' import * as scale from 'd3-scale' +import dateFns from 'date-fns' class AreaStackChartExample extends React.PureComponent { @@ -9,28 +11,28 @@ class AreaStackChartExample extends React.PureComponent { const data = [ { - month: new Date(2015, 0, 1), + month: new Date(2018, 5, 1), apples: 3840, bananas: 1920, cherries: 960, dates: 400, }, { - month: new Date(2015, 1, 1), + month: new Date(2018, 8, 1), apples: 1600, bananas: 1440, cherries: 960, dates: 400, }, { - month: new Date(2015, 2, 1), + month: new Date(2018, 9, 1), apples: 640, bananas: 960, cherries: 3640, dates: 400, }, { - month: new Date(2015, 10, 1), + month: new Date(2018, 10, 1), apples: 3320, bananas: 480, cherries: 640, @@ -40,27 +42,30 @@ class AreaStackChartExample extends React.PureComponent { const colors = [ '#8800cc', '#aa00ff', '#cc66ff', '#eeccff' ] const keys = [ 'apples', 'bananas', 'cherries', 'dates' ] - const svgs = [ - { onPress: () => console.log('apples') }, - { onPress: () => console.log('bananas') }, - { onPress: () => console.log('cherries') }, - { onPress: () => console.log('dates') }, - ] return ( - data.month } - > - - + + item.month } + > + + + item.month } + scale={ scale.scaleTime } + formatLabel={ (value) => dateFns.format(value, 'MMM') } + /> + ) } } diff --git a/storybook/stories/bar-chart/index.js b/storybook/stories/bar-chart/index.js index db52a9eb..fb2b2e8e 100644 --- a/storybook/stories/bar-chart/index.js +++ b/storybook/stories/bar-chart/index.js @@ -5,6 +5,8 @@ import Standard from './standard' import Horizontal from './horizontal' import Grouped from './grouped' import GroupedHorizontal from './horizontal-grouped' +import WithGradient from './with-gradient' +import WithYMinMax from './with-y-min-max' import ShowcaseCard from '../showcase-card' storiesOf('BarChart', module) @@ -13,3 +15,5 @@ storiesOf('BarChart', module) .add('Grouped', () => ) .add('Horizontal', () => ) .add('Horizontal - grouped', () => ) + .add('With gradient', () => ) + .add('With ymin/max', () => ) diff --git a/storybook/stories/decorators/gradient-bar.js b/storybook/stories/bar-chart/with-gradient.js similarity index 100% rename from storybook/stories/decorators/gradient-bar.js rename to storybook/stories/bar-chart/with-gradient.js diff --git a/storybook/stories/bar-chart/with-y-min-max.js b/storybook/stories/bar-chart/with-y-min-max.js new file mode 100644 index 00000000..97aed611 --- /dev/null +++ b/storybook/stories/bar-chart/with-y-min-max.js @@ -0,0 +1,40 @@ +import React from 'react' +import { View } from 'react-native' +import { BarChart, Grid, YAxis } from 'react-native-svg-charts' + +class BarChartExample extends React.PureComponent { + + render() { + + const fill = 'rgb(134, 65, 244)' + const data = [ 50, 10, 40, 95, -4, -24, null, 85, undefined, 0, 35, 53, -53, 24, 50, -20, -80 ] + const contentInset = { top: 10, bottom: 10 } + const yMax = 10 + const yMin = -10 + + return ( + + + + + + + ) + } + +} + +export default BarChartExample diff --git a/storybook/stories/decorators/index.js b/storybook/stories/decorators/index.js index d6d937f5..e3322358 100644 --- a/storybook/stories/decorators/index.js +++ b/storybook/stories/decorators/index.js @@ -4,9 +4,6 @@ import { storiesOf } from '@storybook/react-native' import Grid from './custom-grid' import Decorator1 from './decorator-1' import Decorator2 from './decorator-2' -import GradientArea from './gradient-area' -import GradientLine from './gradient-line' -import GradientBar from './gradient-bar' import ShowcaseCard from '../showcase-card' storiesOf('Decorators', module) @@ -14,7 +11,4 @@ storiesOf('Decorators', module) .add('Custom grid', () => ) .add('1', () => ) .add('2', () => ) - .add('Gradient area', () => ) - .add('Gradient line', () => ) - .add('Gradient bar', () => ) diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 3916df33..3638a01e 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -6,5 +6,6 @@ import './line-chart' import './pie-chart' import './progress-circle' import './x-axis' +import './y-axis' import './decorators' import './others' diff --git a/storybook/stories/line-chart/index.js b/storybook/stories/line-chart/index.js index e1796440..e851a129 100644 --- a/storybook/stories/line-chart/index.js +++ b/storybook/stories/line-chart/index.js @@ -3,9 +3,11 @@ import { storiesOf } from '@storybook/react-native' import Standard from './standard' import Partial from './partial' +import WithGradient from './with-gradient' import ShowcaseCard from '../showcase-card' storiesOf('LineChart') .addDecorator(getStory => { getStory() }) .add('Standard', () => ) .add('Partial', () => ) + .add('With gradient', () => ) diff --git a/storybook/stories/decorators/gradient-line.js b/storybook/stories/line-chart/with-gradient.js similarity index 100% rename from storybook/stories/decorators/gradient-line.js rename to storybook/stories/line-chart/with-gradient.js diff --git a/storybook/stories/others/index.js b/storybook/stories/others/index.js index 7576707a..67f18337 100644 --- a/storybook/stories/others/index.js +++ b/storybook/stories/others/index.js @@ -2,10 +2,12 @@ import React from 'react' import { storiesOf } from '@storybook/react-native' import YMinMax from './y-min-max' +import XMinMax from './x-min-max' import LayeredChart from './layered-charts' import ShowcaseCard from '../showcase-card' storiesOf('Others', module) .addDecorator(getStory => { getStory() }) .add('YMinMax', () => ) + .add('XMinMax', () => ) .add('Layered chart', () => ) diff --git a/storybook/stories/others/x-min-max.js b/storybook/stories/others/x-min-max.js new file mode 100644 index 00000000..4239be1e --- /dev/null +++ b/storybook/stories/others/x-min-max.js @@ -0,0 +1,69 @@ +import React from 'react' +import { View } from 'react-native' +import { AreaChart, XAxis } from 'react-native-svg-charts' +import * as scale from 'd3-scale' +import dateFns from 'date-fns' + +class GridMinMaxExample extends React.PureComponent { + + render() { + + const data = [ + { + value: 50, + date: dateFns.setHours(new Date(2018, 0, 0), 6), + }, + { + value: 10, + date: dateFns.setHours(new Date(2018, 0, 0), 9), + }, + { + value: 150, + date: dateFns.setHours(new Date(2018, 0, 0), 15), + }, + { + value: 10, + date: dateFns.setHours(new Date(2018, 0, 0), 18), + }, + { + value: 100, + date: dateFns.setHours(new Date(2018, 0, 0), 21), + }, + { + value: 20, + date: dateFns.setHours(new Date(2018, 0, 0), 24), + }, + ] + + const xMin = dateFns.setHours(new Date(2018, 0, 0), 0) + + return ( + + item.date } + yAccessor={ ({ item }) => item.value } + yMax={ 200 } + /> + item.date } + formatLabel={ (value) => dateFns.format(value, 'HH') } + min={ xMin } + /> + + ) + } + +} + +export default GridMinMaxExample diff --git a/storybook/stories/others/y-min-max.js b/storybook/stories/others/y-min-max.js index 26b74fb8..634afde9 100644 --- a/storybook/stories/others/y-min-max.js +++ b/storybook/stories/others/y-min-max.js @@ -23,8 +23,8 @@ class GridMinMaxExample extends React.PureComponent { data={ data } svg={{ fill: 'rgba(134, 65, 244, 0.2)' }} curve={ shape.curveNatural } - gridMax={ 500 } - gridMin={ -500 } + yMax={ 500 } + yMin={ -500 } >