├── .watchmanconfig ├── .npmignore ├── screenshots └── README.png ├── .buckconfig ├── .babelrc ├── dist ├── util.js ├── constants.js ├── Circle.js ├── xAxis.js ├── Grid.js ├── yAxis.js ├── PieChart.js ├── BarChart.js ├── Wedge.js ├── LineChart.js └── Chart.js ├── src ├── constants.js ├── util.js ├── Circle.js ├── xAxis.js ├── BarChart.js ├── PieChart.js ├── Grid.js ├── yAxis.js ├── Wedge.js ├── LineChart.js └── Chart.js ├── .editorconfig ├── .gitignore ├── LICENSE ├── .eslintrc ├── react-native-chart.flow.js ├── .flowconfig ├── package.json └── README.md /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | screenshots 2 | -------------------------------------------------------------------------------- /screenshots/README.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomauty/react-native-chart/HEAD/screenshots/README.png -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "retainLines": true, 3 | "compact": true, 4 | "comments": false, 5 | "presets": ["react-native"], 6 | "plugins": ["transform-flow-strip-types"], 7 | "sourceMaps": false 8 | } 9 | -------------------------------------------------------------------------------- /dist/util.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});exports. 2 | 3 | uniqueValuesInDataSet=uniqueValuesInDataSet;function uniqueValuesInDataSet(data){ 4 | return data.reduce(function(result,d){ 5 | if(result.some(function(p){return p[1]===d[1];}))return result; 6 | result.push(d); 7 | return result;}, 8 | []);} -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | export const BLUE = '#4DC4E6'; 3 | export const BLACK = '#333333'; 4 | export const GREY = '#999999'; 5 | export const RED = '#DF8165'; 6 | export const WHITE = '#F5F5F5'; 7 | export const YELLOW = 'rgba(255, 205, 0, 0.9)'; 8 | export const GREEN = '#90C456'; 9 | export const DARK_PURPLE = '#374E5C'; 10 | export const LIGHT_PURPLE = '#4a697c'; 11 | -------------------------------------------------------------------------------- /dist/constants.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true}); 2 | var BLUE=exports.BLUE='#4DC4E6'; 3 | var BLACK=exports.BLACK='#333333'; 4 | var GREY=exports.GREY='#999999'; 5 | var RED=exports.RED='#DF8165'; 6 | var WHITE=exports.WHITE='#F5F5F5'; 7 | var YELLOW=exports.YELLOW='rgba(255, 205, 0, 0.9)'; 8 | var GREEN=exports.GREEN='#90C456'; 9 | var DARK_PURPLE=exports.DARK_PURPLE='#374E5C'; 10 | var LIGHT_PURPLE=exports.LIGHT_PURPLE='#4a697c'; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = tab 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | type Pair = [number, number]; 3 | export function uniqueValuesInDataSet(data : Pair[]) : Pair[] { 4 | return data.reduce((result : Pair[], d : Pair) => { 5 | if (result.some(p => p[1] === d[1])) return result; 6 | result.push(d); 7 | return result; 8 | }, []); 9 | } 10 | 11 | export function uniqueValuesInDataSets(data : [Pair[]], index : number) : Pair[] { 12 | let values = []; 13 | data.forEach(Graph => { 14 | Graph.forEach(XYPair => { 15 | if (values.indexOf(XYPair[index]) === -1) { 16 | values.push(XYPair[index]) 17 | } 18 | }) 19 | }); 20 | values.sort(function(a, b) { 21 | return a - b; 22 | }); 23 | return values 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # CocoaPods 25 | # 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | #Pods/ 31 | 32 | /node_modules/ 33 | .DS_Store 34 | lib 35 | project.xcworkspace 36 | 37 | # Android/IJ 38 | # 39 | .idea 40 | .gradle 41 | local.properties 42 | 43 | # node.js 44 | # 45 | node_modules/ 46 | npm-debug.log 47 | -------------------------------------------------------------------------------- /src/Circle.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React, { Component, PropTypes } from 'react'; 3 | import { ART } from 'react-native'; 4 | const { Path, Shape } = ART; 5 | import * as C from './constants'; 6 | 7 | export default class Circle extends Component { 8 | static propTypes = { 9 | radius: PropTypes.number.isRequired, 10 | x: PropTypes.number.isRequired, 11 | y: PropTypes.number.isRequired, 12 | onPress: PropTypes.func, 13 | fill: PropTypes.string, 14 | stroke: PropTypes.string, 15 | }; 16 | static defaultProps = { 17 | onPress: () => {}, 18 | radius: 2, 19 | fill: C.BLACK, 20 | stroke: C.BLACK, 21 | }; 22 | render() { 23 | const { x, y, radius } = this.props; 24 | const path = new Path().moveTo(x, y - radius).arc(0, radius * 2, radius).arc(0, radius * -2, radius).close(); 25 | return ( 26 | 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 onefold 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "globals": {}, 4 | "env": { 5 | "node": true 6 | }, 7 | "settings": { 8 | "ecmascript": 6, 9 | "jsx": true 10 | }, 11 | "plugins": [ 12 | "react", 13 | "react-native", 14 | "flowtype", 15 | "flow-vars" 16 | ], 17 | "extends": "airbnb", 18 | "rules": { 19 | "arrow-body-style": [0, "as-needed"], 20 | "camelcase": 0, 21 | "flow-vars/define-flow-type": 1, 22 | "flow-vars/use-flow-type": 1, 23 | "indent": [2, "tab"], 24 | "max-len": [2, 150, 4, {"ignoreComments": true, "ignoreUrls": true, "ignorePattern": "^\\s*var\\s.+=\\s*require\\s*\\("}], 25 | "no-alert": 0, 26 | "no-console": 1, 27 | "no-labels": 0, 28 | "no-param-reassign": [2, {"props": false}], 29 | "no-underscore-dangle": 0, 30 | "no-unused-vars": [1, {"args": "after-used", "argsIgnorePattern": "^_"}], 31 | "no-use-before-define": 0, 32 | "quote-props": 0, 33 | "quotes": 0, 34 | "react-native/no-unused-styles": 1, 35 | "react/jsx-indent": [2, "tab"], 36 | "react/jsx-indent-props": [2, "tab"], 37 | "react/jsx-no-bind": [1, { "allowArrowFunctions": true }], 38 | "react/no-multi-comp": 0, 39 | "react/prefer-stateless-function": 0, 40 | "space-infix-ops": 0, 41 | "strict": 0 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /react-native-chart.flow.js: -------------------------------------------------------------------------------- 1 | declare type Chart = { 2 | // Shared properties between most types 3 | data: Array>, 4 | type: 'line' | 'bar' | 'pie', 5 | highlightColor?: number | string, 6 | highlightIndices?: Array, 7 | onDataPointPress?: Function, 8 | axisColor?: number | string, 9 | axisLabelColor?: number | string, 10 | axisLineWidth?: number, 11 | gridColor?: number | string, 12 | gridLineWidth?: number, 13 | hideHorizontalGridLines?: boolean, 14 | hideVerticalGridLines?: boolean, 15 | showAxis?: boolean, 16 | showGrid?: boolean, 17 | showXAxisLabels?: boolean, 18 | showYAxisLabels?: boolean, 19 | style?: any, 20 | tightBounds?: boolean, 21 | verticalGridStep?: number, 22 | xAxisHeight?: number, 23 | yAxisTransform?: Function, 24 | yAxisWidth?: number, 25 | yAxisUseDecimal?: boolean, 26 | 27 | // Bar chart props 28 | color?: number | string, 29 | cornerRadius?: number, 30 | widthPercent?: number, 31 | 32 | // Line/multi-line chart props 33 | fillColor?: number | string, 34 | dataPointColor?: number | string, 35 | dataPointFillColor?: number | string, 36 | dataPointRadius?: number, 37 | lineWidth?: number, 38 | showDataPoint?: boolean, 39 | 40 | // Pie chart props 41 | sliceColors?: Array, 42 | 43 | // TODO 44 | highlightRadius?: number, 45 | pieCenterRatio?: number, 46 | animationDuration?: number, 47 | axisTitleColor?: number | string, 48 | axisTitleFontSize?: number, 49 | chartFontSize?: number, 50 | chartTitle?: string, 51 | chartTitleColor?: number | string, 52 | labelFontSize?: number, 53 | xAxisTitle?: string, 54 | yAxisTitle?: string, 55 | fillGradient?: Array, 56 | }; 57 | -------------------------------------------------------------------------------- /src/xAxis.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 'use strict'; 3 | import React, { Component, PropTypes } from 'react'; 4 | import { View, Text, StyleSheet } from 'react-native'; 5 | import { uniqueValuesInDataSets } from './util'; 6 | 7 | const styles = StyleSheet.create({ 8 | xAxisContainer: { 9 | flexDirection: 'row', 10 | flex: 0, 11 | backgroundColor: 'transparent', 12 | justifyContent: 'space-between', 13 | }, 14 | axisText: { 15 | flex: 1, 16 | backgroundColor: 'transparent', 17 | }, 18 | }); 19 | 20 | export default class XAxis extends Component { 21 | 22 | static propTypes = { 23 | axisColor: PropTypes.any.isRequired, 24 | axisLabelColor: PropTypes.any.isRequired, 25 | axisLineWidth: PropTypes.number.isRequired, 26 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.array)).isRequired, 27 | showXAxisLabels: PropTypes.bool.isRequired, 28 | style: PropTypes.any, 29 | width: PropTypes.number.isRequired, 30 | align: PropTypes.string, 31 | labelFontSize: PropTypes.number.isRequired, 32 | xAxisTransform: PropTypes.func, 33 | horizontalGridStep: PropTypes.number, 34 | }; 35 | static defaultProps = { 36 | align: 'center', 37 | }; 38 | 39 | render() { 40 | const data = uniqueValuesInDataSets(this.props.data || [[]], 0); 41 | let transform = (d) => d; 42 | if (this.props.xAxisTransform && typeof this.props.xAxisTransform === 'function') { 43 | transform = this.props.xAxisTransform; 44 | } 45 | return ( 46 | 56 | {(() => { 57 | if (!this.props.showXAxisLabels) return null; 58 | return data.map((d, i) => { 59 | let stepsBetweenVerticalLines = this.props.horizontalGridStep ? Math.round((data.length) / this.props.horizontalGridStep + 1) : 1; 60 | if (stepsBetweenVerticalLines < 1) stepsBetweenVerticalLines = 1; 61 | if (i % stepsBetweenVerticalLines !== 0) return null; 62 | const item = transform(d); 63 | if (typeof item !== 'number' && !item) return null; 64 | return ( 65 | {item} 76 | ); 77 | }); 78 | 79 | })()} 80 | 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/BarChart.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { View, StyleSheet, TouchableWithoutFeedback } from 'react-native'; 3 | import React, { Component } from 'react'; 4 | import * as C from './constants'; 5 | import Grid from './Grid'; 6 | 7 | const styles = StyleSheet.create({ 8 | default: { 9 | flex: 1, 10 | alignItems: 'flex-end', 11 | flexDirection: 'row', 12 | justifyContent: 'space-around', 13 | }, 14 | }); 15 | 16 | export default class BarChart extends Component { 17 | constructor(props : any) { 18 | super(props); 19 | this.state = { }; 20 | } 21 | 22 | _handlePress = (e : Object, dataPoint : number, index : number) => { 23 | if (this.props.data.onDataPointPress) { 24 | this.props.data.onDataPointPress(e, dataPoint, index); 25 | } 26 | }; 27 | 28 | _drawBar = (_dataPoint : [number, number], index : number) => { 29 | const [_x, dataPoint] = _dataPoint; 30 | const backgroundColor = this.props.color[0] || C.BLUE; 31 | // the index [0] is facilitate multi-line, fix later if need be 32 | const HEIGHT = this.props.height; 33 | const WIDTH = this.props.width; 34 | let widthPercent = this.props.widthPercent || 0.5; 35 | if (widthPercent > 1) widthPercent = 1; 36 | if (widthPercent < 0) widthPercent = 0; 37 | 38 | let minBound = this.props.minVerticalBound; 39 | let maxBound = this.props.maxVerticalBound; 40 | 41 | // For all same values, create a range anyway 42 | if (minBound === maxBound) { 43 | minBound -= this.props.verticalGridStep; 44 | maxBound += this.props.verticalGridStep; 45 | } 46 | 47 | const data = this.props.data || []; 48 | const width = (WIDTH / data.length * this.props.horizontalScale * 0.5) * widthPercent; 49 | const divisor = (maxBound - minBound <= 0) ? 0.00001 : (maxBound - minBound); 50 | const scale = HEIGHT / divisor; 51 | let height = HEIGHT - ((minBound * scale) + (HEIGHT - (dataPoint * scale))); 52 | if (height <= 0) height = 20; 53 | return ( 54 | this._handlePress(e, dataPoint, index)} 57 | > 58 | 67 | 68 | ); 69 | }; 70 | 71 | render() { 72 | const data = this.props.data || []; 73 | return ( 74 | 75 | 76 | {data.map(this._drawBar)} 77 | 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /dist/Circle.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});var _jsxFileName='src/Circle.js';var _createClass=function(){function defineProperties(target,props){for(var i=0;i, index : number) => colors[index] || colors[colors.length % index]; 9 | 10 | export default class PieChart extends Component { 11 | constructor(props : any) { 12 | super(props); 13 | this.state = { rotation: 0 }; 14 | (this:any).boundingAreas = {}; 15 | } 16 | shouldComponentUpdate(props : any) { 17 | return ( 18 | props.data !== this.props.data 19 | || props.height !== this.props.height 20 | || props.width !== this.props.width 21 | ); 22 | } 23 | 24 | // TODO: Handle press on chart by emitting event 25 | _handlePress = (_e : Object) => { 26 | // const { locationX, locationY } = e.nativeEvent; 27 | }; 28 | 29 | render() { 30 | if (!this.props.width || !this.props.height) return ; 31 | 32 | const COLORS = this.props.sliceColors || [ 33 | C.BLUE, 34 | C.GREY, 35 | C.RED, 36 | C.YELLOW, 37 | C.GREEN, 38 | C.DARK_PURPLE, 39 | C.LIGHT_PURPLE, 40 | ]; 41 | 42 | // TODO: Read stroke width from props? 43 | const STROKE_WIDTH = 1; 44 | const radius = (this.props.height / 2) - STROKE_WIDTH; 45 | 46 | const centerX = this.props.width / 2; 47 | const centerY = this.props.height / 2; 48 | 49 | // Gather sum of all data to determine angles 50 | let sum = 0; 51 | const data = this.props.data || []; 52 | data.forEach(n => { sum += (n[1] > 0) ? n[1] : 0.001; }); 53 | const sectors = data.map(n => Math.floor(360 * (n[1]/sum))); 54 | let startAngle = 0; 55 | 56 | const arcs = []; 57 | const colors = []; 58 | sectors.forEach((sectionPiece, i) => { 59 | let endAngle = startAngle + sectionPiece; 60 | if (endAngle > 360) { 61 | endAngle = 360; 62 | } 63 | if (endAngle - startAngle === 0) { 64 | startAngle += sectionPiece; 65 | return; 66 | } 67 | if ((i === sectors.length - 1) && endAngle < 360) { 68 | endAngle = 360; 69 | } 70 | arcs.push({ startAngle, endAngle, outerRadius: radius }); 71 | colors.push(getColor(COLORS, i)); 72 | startAngle += sectionPiece; 73 | }); 74 | return ( 75 | 76 | 77 | 78 | 79 | {arcs.map((arc, i) => { 80 | return ( 81 | 90 | ); 91 | })} 92 | 93 | 94 | 95 | 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*.web.js 5 | .*/*.android.js 6 | 7 | # Some modules have their own node_modules with overlap 8 | .*/node_modules/node-haste/.* 9 | 10 | # Ugh 11 | .*/node_modules/babel.* 12 | .*/node_modules/babylon.* 13 | .*/node_modules/invariant.* 14 | 15 | # Ignore react and fbjs where there are overlaps, but don't ignore 16 | # anything that react-native relies on 17 | .*/node_modules/fbjs/lib/Map.js 18 | .*/node_modules/fbjs/lib/ErrorUtils.js 19 | 20 | # Flow has a built-in definition for the 'react' module which we prefer to use 21 | # over the currently-untyped source 22 | .*/node_modules/react/react.js 23 | .*/node_modules/react/lib/React.js 24 | .*/node_modules/react/lib/ReactDOM.js 25 | 26 | .*/__mocks__/.* 27 | .*/__tests__/.* 28 | 29 | .*/commoner/test/source/widget/share.js 30 | 31 | # Ignore commoner tests 32 | .*/node_modules/commoner/test/.* 33 | 34 | # See https://github.com/facebook/flow/issues/442 35 | .*/react-tools/node_modules/commoner/lib/reader.js 36 | 37 | # Ignore jest 38 | .*/node_modules/jest-cli/.* 39 | 40 | # Ignore Website 41 | .*/website/.* 42 | 43 | # Ignore generators 44 | .*/local-cli/generator.* 45 | 46 | # Ignore BUCK generated folders 47 | .*\.buckd/ 48 | 49 | .*/node_modules/is-my-json-valid/test/.*\.json 50 | .*/node_modules/iconv-lite/encodings/tables/.*\.json 51 | .*/node_modules/y18n/test/.*\.json 52 | .*/node_modules/spdx-license-ids/spdx-license-ids.json 53 | .*/node_modules/spdx-exceptions/index.json 54 | .*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json 55 | .*/node_modules/resolve/lib/core.json 56 | .*/node_modules/jsonparse/samplejson/.*\.json 57 | .*/node_modules/json5/test/.*\.json 58 | .*/node_modules/ua-parser-js/test/.*\.json 59 | .*/node_modules/builtin-modules/builtin-modules.json 60 | .*/node_modules/binary-extensions/binary-extensions.json 61 | .*/node_modules/url-regex/tlds.json 62 | .*/node_modules/joi/.*\.json 63 | .*/node_modules/isemail/.*\.json 64 | .*/node_modules/tr46/.*\.json 65 | 66 | 67 | [include] 68 | react-native-chart.flow.js 69 | 70 | [libs] 71 | node_modules/react-native/Libraries/react-native/react-native-interface.js 72 | node_modules/react-native/flow 73 | flow/ 74 | 75 | [options] 76 | module.system=haste 77 | 78 | esproposal.class_static_fields=enable 79 | esproposal.class_instance_fields=enable 80 | 81 | munge_underscores=true 82 | 83 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 84 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 85 | 86 | suppress_type=$FlowIssue 87 | suppress_type=$FlowFixMe 88 | suppress_type=$FixMe 89 | 90 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 91 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 92 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 93 | 94 | [version] 95 | 0.25.0 96 | -------------------------------------------------------------------------------- /src/Grid.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { View, StyleSheet } from 'react-native'; 3 | import { uniqueValuesInDataSets } from './util'; 4 | 5 | export default class Grid extends Component { 6 | static propTypes = { 7 | showGrid: PropTypes.bool, 8 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.array)).isRequired, 9 | verticalGridStep: PropTypes.number.isRequired, 10 | horizontalGridStep: PropTypes.number, 11 | gridLineWidth: PropTypes.number, 12 | gridColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 13 | hideHorizontalGridLines: PropTypes.bool, 14 | hideVerticalGridLines: PropTypes.bool, 15 | height: PropTypes.number, 16 | width: PropTypes.number, 17 | type: PropTypes.oneOf(['line', 'bar', 'pie']).isRequired, 18 | }; 19 | static defaultProps = { 20 | 21 | }; 22 | 23 | render() { 24 | if (!this.props.showGrid) return null; 25 | const horizontalRange = []; 26 | const verticalRange = []; 27 | const xData = uniqueValuesInDataSets(this.props.data || [[]], 0); 28 | const yData = uniqueValuesInDataSets(this.props.data || [[]], 1); 29 | const horizontalSteps = (yData.length < this.props.verticalGridStep) ? yData.length : this.props.verticalGridStep; 30 | let stepsBetweenVerticalLines = this.props.horizontalGridStep ? Math.round(xData.length / this.props.horizontalGridStep) : 1; 31 | if (stepsBetweenVerticalLines < 1) stepsBetweenVerticalLines = 1; 32 | 33 | for (let i = horizontalSteps; i > 0; i--) horizontalRange.push(i); 34 | for (let i = xData.length - 1; i > 0; i -= stepsBetweenVerticalLines) verticalRange.push(i); 35 | 36 | const containerStyle = { width: this.props.width, height: this.props.height, position: 'absolute', left: 0 }; 37 | 38 | let intendedLineWidth = this.props.gridLineWidth; 39 | if (this.props.gridLineWidth < 1) { 40 | intendedLineWidth = StyleSheet.hairlineWidth; 41 | } 42 | 43 | const horizontalGridStyle = { 44 | height: this.props.height / this.props.verticalGridStep, 45 | width: this.props.width, 46 | borderTopColor: this.props.gridColor, 47 | borderTopWidth: intendedLineWidth, 48 | }; 49 | 50 | const verticalGridStyle = { 51 | height: this.props.height + 1, 52 | width: (this.props.width / (xData.length - 1)) * stepsBetweenVerticalLines, 53 | borderRightColor: this.props.gridColor, 54 | borderRightWidth: intendedLineWidth, 55 | }; 56 | 57 | return ( 58 | 59 | {(() => { 60 | if (this.props.hideHorizontalGridLines) return null; 61 | return ( 62 | 63 | {horizontalRange.map((_, i) => )} 64 | 65 | ); 66 | })()} 67 | {(() => { 68 | if (this.props.hideVerticalGridLines) return null; 69 | return ( 70 | 71 | {verticalRange.map((_, i) => )} 72 | 73 | ); 74 | })()} 75 | 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | { 5 | "name": "react-native-chart", 6 | "raw": "react-native-chart@1.0.8-beta", 7 | "rawSpec": "1.0.8-beta", 8 | "scope": null, 9 | "spec": "1.0.8-beta", 10 | "type": "version" 11 | }, 12 | "/Users/aw2taman/Desktop/CompassApp" 13 | ] 14 | ], 15 | "_from": "react-native-chart@1.0.8-beta", 16 | "_id": "react-native-chart@1.0.8-beta", 17 | "_inCache": true, 18 | "_installable": true, 19 | "_location": "/react-native-chart", 20 | "_nodeVersion": "6.1.0", 21 | "_npmOperationalInternal": { 22 | "host": "packages-12-west.internal.npmjs.com", 23 | "tmp": "tmp/react-native-chart-1.0.8-beta.tgz_1469582943137_0.5609592183027416" 24 | }, 25 | "_npmUser": { 26 | "email": "tom.auty@gmail.com", 27 | "name": "tomauty" 28 | }, 29 | "_npmVersion": "3.9.0", 30 | "_phantomChildren": {}, 31 | "_requested": { 32 | "name": "react-native-chart", 33 | "raw": "react-native-chart@1.0.8-beta", 34 | "rawSpec": "1.0.8-beta", 35 | "scope": null, 36 | "spec": "1.0.8-beta", 37 | "type": "version" 38 | }, 39 | "_requiredBy": [ 40 | "/" 41 | ], 42 | "_resolved": "https://registry.npmjs.org/react-native-chart/-/react-native-chart-1.0.8-beta.tgz", 43 | "_shasum": "c34b50ffe7f2acd6f519d614a134a7cd29135766", 44 | "_shrinkwrap": null, 45 | "_spec": "react-native-chart@1.0.8-beta", 46 | "_where": "/Users/aw2taman/Desktop/CompassApp", 47 | "dependencies": { 48 | "babel-cli": "^6.10.1", 49 | "babel-eslint": "^6.0.0", 50 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 51 | "babel-preset-react-native": "^1.5.3" 52 | }, 53 | "description": "[![Join the chat at https://gitter.im/tomauty/react-native-chart](https://badges.gitter.im/tomauty/react-native-chart.svg)](https://gitter.im/tomauty/react-native-chart) [![npm version](https://badge.fury.io/js/react-native-chart.svg)](https://badge.fury.", 54 | "devDependencies": { 55 | "eslint": "^2.8.0", 56 | "eslint-config-airbnb": "^8.0.0", 57 | "eslint-plugin-flow-vars": "^0.3.0", 58 | "eslint-plugin-flowtype": "^2.2.7", 59 | "eslint-plugin-import": "^1.6.0", 60 | "eslint-plugin-jsx-a11y": "^1.0.3", 61 | "eslint-plugin-react": "^5.0.1", 62 | "eslint-plugin-react-native": "^1.0.0" 63 | }, 64 | "directories": {}, 65 | "dist": { 66 | "shasum": "c34b50ffe7f2acd6f519d614a134a7cd29135766", 67 | "tarball": "https://registry.npmjs.org/react-native-chart/-/react-native-chart-1.0.8-beta.tgz" 68 | }, 69 | "gitHead": "0ab98300945474df1224f5a4f78a754c88067de9", 70 | "main": "./src/Chart.js", 71 | "maintainers": [ 72 | { 73 | "email": "hyunjincho@gmail.com", 74 | "name": "hyuncho" 75 | }, 76 | { 77 | "email": "tom.auty@gmail.com", 78 | "name": "tomauty" 79 | } 80 | ], 81 | "name": "react-native-chart", 82 | "optionalDependencies": {}, 83 | "readme": "ERROR: No README data found!", 84 | "scripts": { 85 | "build": "babel src --out-dir dist", 86 | "lint": "eslint src", 87 | "prepublish": "npm run build" 88 | }, 89 | "version": "1.0.8-beta" 90 | } 91 | -------------------------------------------------------------------------------- /src/yAxis.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React, { Component, PropTypes } from 'react'; 3 | import { View, Text, StyleSheet } from 'react-native'; 4 | import { uniqueValuesInDataSets } from './util'; 5 | 6 | const styles = StyleSheet.create({ 7 | yAxisContainer: { 8 | flexDirection: 'column', 9 | justifyContent: 'space-between', 10 | flex: 1, 11 | paddingVertical: 0, 12 | paddingRight: 5, 13 | alignItems: 'flex-end', 14 | }, 15 | }); 16 | 17 | 18 | export default class YAxis extends Component { 19 | 20 | static propTypes = { 21 | axisColor: PropTypes.any, 22 | axisLineWidth: PropTypes.number, 23 | data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.array)).isRequired, 24 | height: PropTypes.number.isRequired, 25 | placement: PropTypes.oneOf(['left', 'right']), 26 | verticalGridStep: PropTypes.number.isRequired, 27 | yAxisTransform: PropTypes.func, 28 | yAxisUseDecimal: PropTypes.bool, 29 | yAxisShortLabel: PropTypes.bool 30 | }; 31 | 32 | static defaultProps : any = { 33 | placement: 'left', 34 | }; 35 | 36 | constructor(props : any) { 37 | super(props); 38 | this.state = { bounds: { min: 0, max: 0 } }; 39 | } 40 | 41 | // Credits: Martin Sznapka, StackOverflow, QuestionID: 9461621 42 | shortenLargeNumber(num, useDecimal) { 43 | let digits = (useDecimal) ? 2 : 0; 44 | var units = ['K', 'M', 'B', 't', 'P', 'E', 'Z', 'Y'], 45 | decimal; 46 | for (var i=units.length-1; i>=0; i--) { 47 | decimal = Math.pow(1000, i+1); 48 | 49 | if(num <= -decimal || num >= decimal) { 50 | return +(num / decimal).toFixed(digits) + units[i]; 51 | } 52 | } 53 | return num; 54 | } 55 | 56 | _createLabelForYAxis = (index : number) => { 57 | let minBound = this.props.minVerticalBound; 58 | let maxBound = this.props.maxVerticalBound; 59 | 60 | // For all same values, create a range anyway 61 | if (minBound === maxBound) { 62 | minBound -= this.props.verticalGridStep; 63 | maxBound += this.props.verticalGridStep; 64 | } 65 | minBound = (minBound < 0) ? 0 : minBound; 66 | let label = minBound + (maxBound - minBound) / this.props.verticalGridStep * index; 67 | label = parseFloat(label.toFixed(3)); 68 | 69 | if (!this.props.yAxisUseDecimal) { 70 | label = Math.round(label); 71 | } 72 | 73 | if (this.props.yAxisShortLabel) { 74 | label = this.shortenLargeNumber(label, this.props.yAxisUseDecimal); 75 | } 76 | 77 | 78 | if (this.props.yAxisTransform && typeof this.props.yAxisTransform === 'function') { 79 | label = this.props.yAxisTransform(label); 80 | } 81 | return ( 82 | 89 | {label} 90 | 91 | ); 92 | }; 93 | 94 | render() { 95 | const range = []; 96 | const data = uniqueValuesInDataSets(this.props.data || [[]], 1); 97 | const steps = (data.length < this.props.verticalGridStep) ? data.length : this.props.verticalGridStep; 98 | for (let i = steps; i >= 0; i--) range.push(i); 99 | return ( 100 | 108 | {range.map(this._createLabelForYAxis)} 109 | 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /dist/xAxis.js: -------------------------------------------------------------------------------- 1 | 'use strict';Object.defineProperty(exports,"__esModule",{value:true});var _jsxFileName='src/xAxis.js';var _createClass=function(){function defineProperties(target,props){for(var i=0;i0;i--){horizontalRange.push(i);} 34 | for(var _i=data.length-1;_i>0;_i-=stepsBetweenVerticalLines){verticalRange.push(_i);} 35 | 36 | var containerStyle={width:this.props.width,height:this.props.height,position:'absolute',left:0}; 37 | 38 | var intendedLineWidth=this.props.gridLineWidth; 39 | if(this.props.gridLineWidth<1){ 40 | intendedLineWidth=_reactNative.StyleSheet.hairlineWidth;} 41 | 42 | 43 | var horizontalGridStyle={ 44 | height:this.props.height/this.props.verticalGridStep, 45 | width:this.props.width, 46 | borderTopColor:this.props.gridColor, 47 | borderTopWidth:intendedLineWidth}; 48 | 49 | 50 | var verticalGridStyle={ 51 | height:this.props.height+1, 52 | width:this.props.width/data.length*stepsBetweenVerticalLines, 53 | borderRightColor:this.props.gridColor, 54 | borderRightWidth:intendedLineWidth}; 55 | 56 | 57 | return ( 58 | _react2.default.createElement(_reactNative.View,{style:containerStyle,__source:{fileName:_jsxFileName,lineNumber:58}}, 59 | function(){ 60 | if(_this2.props.hideHorizontalGridLines)return null; 61 | return ( 62 | _react2.default.createElement(_reactNative.View,{style:{position:'absolute',flexDirection:'column',justifyContent:'space-around'},__source:{fileName:_jsxFileName,lineNumber:62}}, 63 | horizontalRange.map(function(_,i){return _react2.default.createElement(_reactNative.View,{key:i,style:horizontalGridStyle,__source:{fileName:_jsxFileName,lineNumber:63}});})));}(), 64 | 65 | 66 | 67 | function(){ 68 | if(_this2.props.hideVerticalGridLines)return null; 69 | return ( 70 | _react2.default.createElement(_reactNative.View,{style:{flexDirection:'row',position:'absolute',justifyContent:'space-around'},__source:{fileName:_jsxFileName,lineNumber:70}}, 71 | verticalRange.map(function(_,i){return _react2.default.createElement(_reactNative.View,{key:i,style:verticalGridStyle,__source:{fileName:_jsxFileName,lineNumber:71}});})));}()));}}]);return Grid;}(_react.Component);Grid.propTypes={showGrid:_react.PropTypes.bool,data:_react.PropTypes.arrayOf(_react.PropTypes.arrayOf(_react.PropTypes.array)).isRequired,verticalGridStep:_react.PropTypes.number.isRequired,horizontalGridStep:_react.PropTypes.number,gridLineWidth:_react.PropTypes.number,gridColor:_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string]),hideHorizontalGridLines:_react.PropTypes.bool,hideVerticalGridLines:_react.PropTypes.bool,height:_react.PropTypes.number,width:_react.PropTypes.number,type:_react.PropTypes.oneOf(['line','bar','pie']).isRequired};Grid.defaultProps={};exports.default=Grid; -------------------------------------------------------------------------------- /dist/yAxis.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});var _jsxFileName='src/yAxis.js';var _createClass=function(){function defineProperties(target,props){for(var i=0;i=0;i--){decimal=Math.pow(1000,i+1);if(num<=-decimal||num>=decimal){return +(num/decimal).toFixed(digits)+units[i];}}return num;}},{key:'render',value:function render() 91 | 92 | 93 | 94 | { 95 | var range=[]; 96 | var data=this.props.data||[[]]; 97 | var unique=(0,_util.uniqueValuesInDataSet)(data[0]); 98 | var steps=unique.length=0;i--){range.push(i);} 100 | return ( 101 | _react2.default.createElement(_reactNative.View,{ 102 | style:[ 103 | styles.yAxisContainer, 104 | this.props.style||{}, 105 | this.props.placement==='left'&&{borderRightColor:this.props.axisColor,borderRightWidth:this.props.axisLineWidth}, 106 | this.props.placement==='right'&&{borderLeftColor:this.props.axisColor,borderLeftWidth:this.props.axisLineWidth}],__source:{fileName:_jsxFileName,lineNumber:101}}, 107 | 108 | 109 | range.map(this._createLabelForYAxis)));}}]);return YAxis;}(_react.Component);YAxis.propTypes={axisColor:_react.PropTypes.any,axisLineWidth:_react.PropTypes.number,data:_react.PropTypes.arrayOf(_react.PropTypes.arrayOf(_react.PropTypes.array)).isRequired,height:_react.PropTypes.number.isRequired,placement:_react.PropTypes.oneOf(['left','right']),verticalGridStep:_react.PropTypes.number.isRequired,yAxisTransform:_react.PropTypes.func,yAxisUseDecimal:_react.PropTypes.bool,yAxisShortLabel:_react.PropTypes.bool};YAxis.defaultProps={placement:'left'};exports.default=YAxis; -------------------------------------------------------------------------------- /dist/PieChart.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});var _extends=Object.assign||function(target){for(var i=1;i0?n[1]:0.001;}); 53 | var sectors=data.map(function(n){return Math.floor(360*(n[1]/sum));}); 54 | var startAngle=0; 55 | 56 | var arcs=[]; 57 | var colors=[]; 58 | sectors.forEach(function(sectionPiece,i){ 59 | var endAngle=startAngle+sectionPiece; 60 | if(endAngle>360){ 61 | endAngle=360;} 62 | 63 | if(endAngle-startAngle===0){ 64 | startAngle+=sectionPiece; 65 | return;} 66 | 67 | if(i===sectors.length-1&&endAngle<360){ 68 | endAngle=360;} 69 | 70 | arcs.push({startAngle:startAngle,endAngle:endAngle,outerRadius:radius}); 71 | colors.push(getColor(COLORS,i)); 72 | startAngle+=sectionPiece;}); 73 | 74 | return ( 75 | _react2.default.createElement(_reactNative.TouchableWithoutFeedback,{onPress:this._handlePress,__source:{fileName:_jsxFileName,lineNumber:75}}, 76 | _react2.default.createElement(_reactNative.View,{__source:{fileName:_jsxFileName,lineNumber:76}}, 77 | _react2.default.createElement(Surface,{width:this.props.width,height:this.props.height,__source:{fileName:_jsxFileName,lineNumber:77}}, 78 | _react2.default.createElement(Group,{originX:centerX,width:this.props.width,height:this.props.height,originY:centerY,rotation:this.state.rotation,__source:{fileName:_jsxFileName,lineNumber:78}}, 79 | arcs.map(function(arc,i){ 80 | return ( 81 | _react2.default.createElement(_Wedge2.default,_extends({ 82 | stroke:colors[i], 83 | strokeWidth:STROKE_WIDTH, 84 | fill:colors[i], 85 | key:i, 86 | originX:centerX, 87 | originY:centerY}, 88 | arc,{__source:{fileName:_jsxFileName,lineNumber:81}})));}))))));}}]);return PieChart;}(_react.Component);exports.default=PieChart; -------------------------------------------------------------------------------- /dist/BarChart.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});var _extends=Object.assign||function(target){for(var i=1;i1)widthPercent=1; 36 | if(widthPercent<0)widthPercent=0; 37 | 38 | var minBound=_this.props.minVerticalBound; 39 | var maxBound=_this.props.maxVerticalBound; 40 | 41 | // For all same values, create a range anyway 42 | if(minBound===maxBound){ 43 | minBound-=_this.props.verticalGridStep; 44 | maxBound+=_this.props.verticalGridStep;} 45 | 46 | 47 | var data=_this.props.data||[]; 48 | var width=WIDTH/data.length*_this.props.horizontalScale*0.5*widthPercent; 49 | var divisor=maxBound-minBound<=0?0.00001:maxBound-minBound; 50 | var scale=HEIGHT/divisor; 51 | var height=HEIGHT-(minBound*scale+(HEIGHT-dataPoint*scale)); 52 | if(height<=0)height=20; 53 | return ( 54 | _react2.default.createElement(_reactNative.TouchableWithoutFeedback,{ 55 | key:index, 56 | onPress:function onPress(e){return _this._handlePress(e,dataPoint,index);},__source:{fileName:_jsxFileName,lineNumber:54}}, 57 | 58 | _react2.default.createElement(_reactNative.View,{ 59 | style:{ 60 | borderTopLeftRadius:_this.props.cornerRadius||0, 61 | borderTopRightRadius:_this.props.cornerRadius||0, 62 | backgroundColor:backgroundColor, 63 | width:width, 64 | height:height},__source:{fileName:_jsxFileName,lineNumber:58}})));};_this.state={};return _this;}_createClass(BarChart,[{key:'render',value:function render() 65 | 66 | 67 | 68 | 69 | 70 | 71 | { 72 | var data=this.props.data||[]; 73 | return ( 74 | _react2.default.createElement(_reactNative.View,{ref:'container',style:[styles.default],__source:{fileName:_jsxFileName,lineNumber:74}}, 75 | _react2.default.createElement(_Grid2.default,_extends({},this.props,{__source:{fileName:_jsxFileName,lineNumber:75}})), 76 | data.map(this._drawBar)));}}]);return BarChart;}(_react.Component);exports.default=BarChart; -------------------------------------------------------------------------------- /src/Wedge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Taken from react-art, changed for RN. 3 | * Copyright 2013-2014 Facebook, Inc. 4 | * All rights reserved. 5 | * 6 | * This source code is licensed under the BSD-style license found in the 7 | * LICENSE file in the root directory of this source tree. An additional grant 8 | * of patent rights can be found in the PATENTS file in the same directory. 9 | * 10 | * @providesModule Wedge.art 11 | * @typechecks 12 | * 13 | * Example usage: 14 | * 20 | * 21 | * Additional optional property: 22 | * (Int) innerRadius 23 | * 24 | */ 25 | 26 | import React, { Component, PropTypes } from 'react'; 27 | import { ART } from 'react-native'; 28 | const { Shape, Path } = ART; 29 | 30 | /** 31 | * Wedge is a React component for drawing circles, wedges and arcs. Like other 32 | * ReactART components, it must be used in a . 33 | */ 34 | export default class Wedge extends Component { 35 | 36 | static propTypes = { 37 | outerRadius: PropTypes.number.isRequired, 38 | startAngle: PropTypes.number.isRequired, 39 | endAngle: PropTypes.number.isRequired, 40 | originX: PropTypes.number.isRequired, 41 | originY: PropTypes.number.isRequired, 42 | innerRadius: PropTypes.number, 43 | }; 44 | 45 | 46 | constructor(props : any) { 47 | super(props); 48 | (this:any).circleRadians = Math.PI * 2; 49 | (this:any).radiansPerDegree = Math.PI / 180; 50 | (this:any)._degreesToRadians = this._degreesToRadians.bind(this); 51 | } 52 | 53 | /** 54 | * _degreesToRadians(degrees) 55 | * 56 | * Helper function to convert degrees to radians 57 | * 58 | * @param {number} degrees 59 | * @return {number} 60 | */ 61 | _degreesToRadians(degrees : number) : number { 62 | if (degrees !== 0 && degrees % 360 === 0) { // 360, 720, etc. 63 | return (this:any).circleRadians; 64 | } 65 | return degrees * (this:any).radiansPerDegree % (this:any).circleRadians; 66 | } 67 | 68 | /** 69 | * _createCirclePath(or, ir) 70 | * 71 | * Creates the ReactART Path for a complete circle. 72 | * 73 | * @param {number} or The outer radius of the circle 74 | * @param {number} ir The inner radius, greater than zero for a ring 75 | * @return {object} 76 | */ 77 | _createCirclePath(or : number, ir : number) : Path { 78 | const path = new Path(); 79 | 80 | path.move(0, or) 81 | .arc(or * 2, 0, or) 82 | .arc(-or * 2, 0, or); 83 | 84 | if (ir) { 85 | path.move(or - ir, 0) 86 | .counterArc(ir * 2, 0, ir) 87 | .counterArc(-ir * 2, 0, ir); 88 | } 89 | 90 | path.close(); 91 | 92 | return path; 93 | } 94 | 95 | /** 96 | * _createArcPath(sa, ea, ca, or, ir) 97 | * 98 | * Creates the ReactART Path for an arc or wedge. 99 | * 100 | * @param {number} startAngle The starting degrees relative to 12 o'clock 101 | * @param {number} endAngle The ending degrees relative to 12 o'clock 102 | * @param {number} or The outer radius in pixels 103 | * @param {number} ir The inner radius in pixels, greater than zero for an arc 104 | * @return {object} 105 | */ 106 | _createArcPath(originX : number, originY : number, startAngle : number, endAngle : number, or : number, ir : number) : Path { 107 | const path = new Path(); 108 | 109 | // angles in radians 110 | const sa = this._degreesToRadians(startAngle); 111 | const ea = this._degreesToRadians(endAngle); 112 | 113 | // central arc angle in radians 114 | const ca = sa > ea ? (this:any).circleRadians - sa + ea : ea - sa; 115 | 116 | // cached sine and cosine values 117 | const ss = Math.sin(sa); 118 | const es = Math.sin(ea); 119 | const sc = Math.cos(sa); 120 | const ec = Math.cos(ea); 121 | 122 | // cached differences 123 | const ds = es - ss; 124 | const dc = ec - sc; 125 | const dr = ir - or; 126 | 127 | // if the angle is over pi radians (180 degrees) 128 | // we will need to let the drawing method know. 129 | const large = ca > Math.PI; 130 | 131 | // TODO (sema) Please improve theses comments to make the math 132 | // more understandable. 133 | // 134 | // Formula for a point on a circle at a specific angle with a center 135 | // at (0, 0): 136 | // x = radius * Math.sin(radians) 137 | // y = radius * Math.cos(radians) 138 | // 139 | // For our starting point, we offset the formula using the outer 140 | // radius because our origin is at (top, left). 141 | // In typical web layout fashion, we are drawing in quadrant IV 142 | // (a.k.a. Southeast) where x is positive and y is negative. 143 | // 144 | // The arguments for path.arc and path.counterArc used below are: 145 | // (endX, endY, radiusX, radiusY, largeAngle) 146 | 147 | path.move(or + or * ss, or - or * sc) // move to starting point 148 | .arc(or * ds, or * -dc, or, or, large) // outer arc 149 | .line(dr * es, dr * -ec); // width of arc or wedge 150 | 151 | if (ir) { 152 | path.counterArc(ir * -ds, ir * dc, ir, ir, large); // inner arc 153 | } 154 | 155 | return path; 156 | } 157 | 158 | render() : any { 159 | // angles are provided in degrees 160 | const startAngle = this.props.startAngle; 161 | const endAngle = this.props.endAngle; 162 | // if (startAngle - endAngle === 0) { 163 | // return null; 164 | // } 165 | 166 | // radii are provided in pixels 167 | const innerRadius = this.props.innerRadius || 0; 168 | const outerRadius = this.props.outerRadius; 169 | 170 | const { originX, originY } = this.props; 171 | 172 | // sorted radii 173 | const ir = Math.min(innerRadius, outerRadius); 174 | const or = Math.max(innerRadius, outerRadius); 175 | 176 | let path; 177 | if (endAngle >= startAngle + 360) { 178 | path = this._createCirclePath(or, ir); 179 | } else { 180 | path = this._createArcPath(originX, originY, startAngle, endAngle, or, ir); 181 | } 182 | 183 | return ; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/LineChart.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React, { Component } from 'react'; 3 | import { Animated, ART, View, Platform, TouchableOpacity } from 'react-native'; 4 | const { Surface, Shape, Path } = ART; 5 | import * as C from './constants'; 6 | import Circle from './Circle'; 7 | const AnimatedShape = Animated.createAnimatedComponent(Shape); 8 | import Grid from './Grid'; 9 | import { uniqueValuesInDataSets } from './util'; 10 | 11 | const makeDataPoint = (x : number, y : number, data : any, index : number) => { 12 | 13 | let color = (data.color[index]) ? data.color[index] : C.BLUE; 14 | 15 | let fill = ((data.dataPointFillColor) && (data.dataPointFillColor[index])) ? 16 | (data.dataPointFillColor[index]) : color; 17 | 18 | let stroke = ((data.dataPointColor) && (data.dataPointColor[index])) ? 19 | (data.dataPointColor[index]) : color; 20 | 21 | return { 22 | x, 23 | y, 24 | radius: data.dataPointRadius, 25 | fill: fill, 26 | stroke: stroke }; 27 | }; 28 | 29 | const calculateDivisor = (minBound : number, maxBound : number) : number => { 30 | return (maxBound - minBound <= 0) ? 0.00001 : maxBound - minBound; 31 | }; 32 | 33 | const heightZero = (Platform.OS === 'ios') ? 0 : 1; 34 | 35 | export default class LineChart extends Component { 36 | 37 | constructor(props : any) { 38 | super(props); 39 | const heightValue = (props.animated) ? heightZero : props.height; 40 | const opacityValue = (props.animated) ? 0 : 1; 41 | this.state = { height: new Animated.Value(heightValue), opacity: new Animated.Value(opacityValue) }; 42 | } 43 | 44 | componentWillUpdate() { 45 | if (this.props.animated) { 46 | Animated.timing(this.state.opacity, { duration: 0, toValue: 0 }).start(); 47 | Animated.timing(this.state.height, { duration: 0, toValue: heightZero }).start(); 48 | } 49 | } 50 | 51 | componentDidUpdate() { 52 | if (this.props.animated) { 53 | Animated.timing(this.state.height, { duration: this.props.animationDuration, toValue: this.props.height }).start(); 54 | Animated.timing(this.state.opacity, { duration: this.props.animationDuration, toValue: 1 }).start(); 55 | } 56 | } 57 | 58 | _drawLine = () => { 59 | const containerHeight = this.props.height; 60 | const containerWidth = this.props.width; 61 | const data = this.props.data || [[]]; 62 | let minBound = this.props.minVerticalBound; 63 | let maxBound = this.props.maxVerticalBound; 64 | 65 | // For all same values, create a range anyway 66 | if (minBound === maxBound) { 67 | minBound -= this.props.verticalGridStep; 68 | maxBound += this.props.verticalGridStep; 69 | } 70 | 71 | const divisor = calculateDivisor(minBound, maxBound); 72 | const scale = (containerHeight + 1) / divisor; 73 | const horizontalStep = containerWidth / uniqueValuesInDataSets(data, 0).length; 74 | 75 | const dataPoints = []; 76 | const path = []; 77 | const fillPath = []; 78 | 79 | for (index = 0; index < data.length; index++) { 80 | var pathArray = [], fillPathArray = [], pathSubIndex = -1; 81 | let currentData = data[index] || []; 82 | const firstDataPoint = currentData[0][1]; 83 | let height = (minBound * scale) + (containerHeight - (firstDataPoint * scale)); 84 | if (height < 0) height = 0; 85 | 86 | const dataPointSet = []; 87 | dataPointSet.push(makeDataPoint(0, height, this.props, index)); 88 | 89 | let beginNewPath = true; 90 | currentData.forEach(([_, dataPoint], i) => { 91 | 92 | if (dataPoint === '') { 93 | // An empty within the graph, begin new Path next non-empty datapoint 94 | // beginNewPath = true; 95 | return; 96 | } 97 | 98 | let _height = (minBound * scale) + (containerHeight - (dataPoint * scale)); 99 | if (_height < 0) _height = 0; 100 | 101 | const x = horizontalStep * (i); 102 | const y = Math.round(_height); 103 | 104 | dataPointSet.push(makeDataPoint(x, y, this.props, index)); 105 | 106 | if ((beginNewPath) && (dataPoint !== '')) { 107 | pathArray.push(new Path().moveTo(x, y)); 108 | fillPathArray.push(new Path().moveTo(x, containerHeight).lineTo(x, height)); 109 | pathSubIndex++; 110 | beginNewPath = false; 111 | } else { 112 | pathArray[pathSubIndex].lineTo(x, y); 113 | fillPathArray[pathSubIndex].lineTo(x, y); 114 | } 115 | }); 116 | 117 | dataPoints.push(dataPointSet); 118 | 119 | for (g = 0; g < pathArray.length; g++) { 120 | fillPathArray[g].lineTo(dataPointSet[dataPointSet.length - 1].x, containerHeight); 121 | if (this.props.fillColor) { 122 | fillPathArray[g].moveTo(0, containerHeight); 123 | } 124 | 125 | if (pathArray[g].path.some(isNaN)) return null; 126 | } 127 | 128 | path.push(pathArray); 129 | fillPath.push(fillPathArray); 130 | } 131 | 132 | var multipleLines = dataPoints.map( (dataPointSet, index) => { 133 | let color = (this.props.color[index]) ? this.props.color[index] : C.BLUE; 134 | let allDisjointPaths = path[index].map( (singlePath) => { 135 | return ( 136 | 137 | ); 138 | }); 139 | return allDisjointPaths; 140 | }); 141 | 142 | var multipleFills = dataPoints.map( (dataPointSet, index) => { 143 | let allDisjointPaths = fillPath[index].map ( (singlePath, subIndex) => { 144 | return ( 145 | 146 | ); 147 | }); 148 | return allDisjointPaths; 149 | }); 150 | 151 | return ( 152 | 153 | 154 | 155 | { multipleLines } 156 | { multipleFills } 157 | 158 | 159 | 160 | 161 | 162 | {(() => { 163 | if (!this.props.showDataPoint) return null; 164 | 165 | var multipleDataPoints = dataPoints.map( (dataPointSet, index) => { 166 | let totalDataSet = dataPointSet.map((d, i) => { 167 | return ( 168 | alert(i)} /> 169 | ); 170 | }); 171 | return totalDataSet; 172 | }); 173 | 174 | return ( 175 | 176 | { multipleDataPoints } 177 | 178 | ); 179 | })()} 180 | 181 | ); 182 | }; 183 | 184 | render() : any { 185 | if (Platform.OS === 'ios') { 186 | return ( 187 | 188 | 189 | 190 | {this._drawLine()} 191 | 192 | 193 | ); 194 | } 195 | return ( 196 | 197 | 198 | 199 | {this._drawLine()} 200 | 201 | 202 | ); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /dist/Wedge.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});var _extends=Object.assign||function(target){for(var i=1;i 46 | * 47 | * Additional optional property: 48 | * (Int) innerRadius 49 | * 50 | */var Shape=_reactNative.ART.Shape;var Path=_reactNative.ART.Path; /** 51 | * Wedge is a React component for drawing circles, wedges and arcs. Like other 52 | * ReactART components, it must be used in a . 53 | */var Wedge=function(_Component){_inherits(Wedge,_Component);function Wedge(props){_classCallCheck(this,Wedge);var _this=_possibleConstructorReturn(this,Object.getPrototypeOf(Wedge).call(this,props));_this.circleRadians=Math.PI*2;_this.radiansPerDegree=Math.PI/180;_this._degreesToRadians=_this._degreesToRadians.bind(_this);return _this;} /** 54 | * _degreesToRadians(degrees) 55 | * 56 | * Helper function to convert degrees to radians 57 | * 58 | * @param {number} degrees 59 | * @return {number} 60 | */_createClass(Wedge,[{key:'_degreesToRadians',value:function _degreesToRadians( 61 | degrees){ 62 | if(degrees!==0&°rees%360===0){ // 360, 720, etc. 63 | return this.circleRadians;} 64 | 65 | return degrees*this.radiansPerDegree%this.circleRadians;} 66 | 67 | 68 | /** 69 | * _createCirclePath(or, ir) 70 | * 71 | * Creates the ReactART Path for a complete circle. 72 | * 73 | * @param {number} or The outer radius of the circle 74 | * @param {number} ir The inner radius, greater than zero for a ring 75 | * @return {object} 76 | */},{key:'_createCirclePath',value:function _createCirclePath( 77 | or,ir){ 78 | var path=new Path(); 79 | 80 | path.move(0,or). 81 | arc(or*2,0,or). 82 | arc(-or*2,0,or); 83 | 84 | if(ir){ 85 | path.move(or-ir,0). 86 | counterArc(ir*2,0,ir). 87 | counterArc(-ir*2,0,ir);} 88 | 89 | 90 | path.close(); 91 | 92 | return path;} 93 | 94 | 95 | /** 96 | * _createArcPath(sa, ea, ca, or, ir) 97 | * 98 | * Creates the ReactART Path for an arc or wedge. 99 | * 100 | * @param {number} startAngle The starting degrees relative to 12 o'clock 101 | * @param {number} endAngle The ending degrees relative to 12 o'clock 102 | * @param {number} or The outer radius in pixels 103 | * @param {number} ir The inner radius in pixels, greater than zero for an arc 104 | * @return {object} 105 | */},{key:'_createArcPath',value:function _createArcPath( 106 | originX,originY,startAngle,endAngle,or,ir){ 107 | var path=new Path(); 108 | 109 | // angles in radians 110 | var sa=this._degreesToRadians(startAngle); 111 | var ea=this._degreesToRadians(endAngle); 112 | 113 | // central arc angle in radians 114 | var ca=sa>ea?this.circleRadians-sa+ea:ea-sa; 115 | 116 | // cached sine and cosine values 117 | var ss=Math.sin(sa); 118 | var es=Math.sin(ea); 119 | var sc=Math.cos(sa); 120 | var ec=Math.cos(ea); 121 | 122 | // cached differences 123 | var ds=es-ss; 124 | var dc=ec-sc; 125 | var dr=ir-or; 126 | 127 | // if the angle is over pi radians (180 degrees) 128 | // we will need to let the drawing method know. 129 | var large=ca>Math.PI; 130 | 131 | // TODO (sema) Please improve theses comments to make the math 132 | // more understandable. 133 | // 134 | // Formula for a point on a circle at a specific angle with a center 135 | // at (0, 0): 136 | // x = radius * Math.sin(radians) 137 | // y = radius * Math.cos(radians) 138 | // 139 | // For our starting point, we offset the formula using the outer 140 | // radius because our origin is at (top, left). 141 | // In typical web layout fashion, we are drawing in quadrant IV 142 | // (a.k.a. Southeast) where x is positive and y is negative. 143 | // 144 | // The arguments for path.arc and path.counterArc used below are: 145 | // (endX, endY, radiusX, radiusY, largeAngle) 146 | 147 | path.move(or+or*ss,or-or*sc) // move to starting point 148 | .arc(or*ds,or*-dc,or,or,large) // outer arc 149 | .line(dr*es,dr*-ec); // width of arc or wedge 150 | 151 | if(ir){ 152 | path.counterArc(ir*-ds,ir*dc,ir,ir,large); // inner arc 153 | } 154 | 155 | return path;}},{key:'render',value:function render() 156 | 157 | 158 | { 159 | // angles are provided in degrees 160 | var startAngle=this.props.startAngle; 161 | var endAngle=this.props.endAngle; 162 | // if (startAngle - endAngle === 0) { 163 | // return null; 164 | // } 165 | 166 | // radii are provided in pixels 167 | var innerRadius=this.props.innerRadius||0; 168 | var outerRadius=this.props.outerRadius;var _props= 169 | 170 | this.props;var originX=_props.originX;var originY=_props.originY; 171 | 172 | // sorted radii 173 | var ir=Math.min(innerRadius,outerRadius); 174 | var or=Math.max(innerRadius,outerRadius); 175 | 176 | var path=void 0; 177 | if(endAngle>=startAngle+360){ 178 | path=this._createCirclePath(or,ir);}else 179 | { 180 | path=this._createArcPath(originX,originY,startAngle,endAngle,or,ir);} 181 | 182 | 183 | return _react2.default.createElement(Shape,_extends({},this.props,{d:path,__source:{fileName:_jsxFileName,lineNumber:183}}));}}]);return Wedge;}(_react.Component);Wedge.propTypes={outerRadius:_react.PropTypes.number.isRequired,startAngle:_react.PropTypes.number.isRequired,endAngle:_react.PropTypes.number.isRequired,originX:_react.PropTypes.number.isRequired,originY:_react.PropTypes.number.isRequired,innerRadius:_react.PropTypes.number};exports.default=Wedge; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-chart 2 | 3 | ### NOTE: I have not been able to maintain this repo. Recommend switching to [Victory Charts](https://github.com/FormidableLabs/victory-native). 4 | 5 | ![Screenshot](https://raw.githubusercontent.com/tomauty/react-native-chart/master/screenshots/README.png) 6 | 7 | ## Getting Started 8 | [![NPM](https://nodei.co/npm/react-native-chart.png?downloads=true)](https://nodei.co/npm/react-native-chart/) 9 | 10 | 1. `npm i react-native-chart --save` 11 | 12 | __Link ART to your project__ 13 | 14 | 1. Right click Libraries and click 'Add Files to {YourProject}' 15 | 16 | screen shot 2016-06-17 at 3 39 24 pm 17 | 18 | 2. Navigate to your project's node_modules/react-native/Libraries/ART and select 'ART.xcodeproj' 19 | 20 | screen shot 2016-06-17 at 3 39 42 pm 21 | 22 | 3. Go to Build Phases -> Link Binary With Libraries 23 | 24 | screen shot 2016-06-17 at 3 40 00 pm 25 | 26 | 4 Click the '+', and add libART.a 27 | 28 | screen shot 2016-06-17 at 3 40 03 pm 29 | 30 | Then rebuild. 31 | 32 | 33 | 34 | ## Usage 35 | ```javascript 36 | import React, { StyleSheet, View, Component } from 'react-native'; 37 | import Chart from 'react-native-chart'; 38 | 39 | const styles = StyleSheet.create({ 40 | container: { 41 | flex: 1, 42 | justifyContent: 'center', 43 | alignItems: 'center', 44 | backgroundColor: 'white', 45 | }, 46 | chart: { 47 | width: 200, 48 | height: 200, 49 | }, 50 | }); 51 | 52 | const data = [[ 53 | [0, 1], 54 | [1, 3], 55 | [3, 7], 56 | [4, 9], 57 | ]]; 58 | 59 | class SimpleChart extends Component { 60 | render() { 61 | return ( 62 | 63 | 71 | 72 | ); 73 | } 74 | } 75 | 76 | ``` 77 | ## Properties 78 | 79 | Use '' y-values to signify the 'render but empty' data points. 80 | 81 | | Property | Type | Description | Required | Default | 82 | | ----------------------- | ------------------------- | --------------------------------------------------------- | -------- | --------------------- | 83 | | data | Array< Array< [number, number] > > | An array of arrays of [x, y] pairs. | **Yes** | | 84 | | type | string | pie/bar/line | **Yes** | bar | 85 | | color | Array < string > | Color of bars/line in line chart | No | #4DC4E6 | 86 | | cornerRadius | number | Corner radius of bars in bar chart | No | 0 | 87 | | fillColor | Array < string > | Fill area colors in line chart | No | | 88 | | dataPointColor | Array < string > | Stroke colors for line chart data point | No | | 89 | | dataPointFillColor | Array < string > | Fill colors for line chart data point | No | | 90 | | dataPointRadius | number | Radius of the data point | No | 3 | 91 | | lineWidth | number | Width of line chart line | No | 1 | 92 | | showDataPoint | boolean | Show data points on line chart | No | false | 93 | | sliceColors | Array < string > | Array of colors for pie chart slices | **Yes** | [ < random colors > ] | 94 | | axisColor | string | Color of axis lines | No | #333333 | 95 | | axisLabelColor | string | Color of axis test | No | #333333 | 96 | | axisLineWidth | number | Width of axis lines | No | 1 | 97 | | gridColor | string | Color of grid lines | No | #333333 | 98 | | gridLineWidth | number | Width of grid lines | No | 0.5 | 99 | | hideHorizontalGridLines | boolean | Hide grid lines going from LTR | No | false | 100 | | hideVerticalGridLines | boolean | Hide grid lines going up -> down | No | false | 101 | | showAxis | boolean | Show the X and Y axes | No | true | 102 | | showGrid | boolean | Show the grid | No | true | 103 | | showXAxisLabels | boolean | Show X-Axis labels | No | true | 104 | | showYAxisLabels | boolean | Show Y-Axis labels | No | true | 105 | | style | object | Style on the container | No | {} | 106 | | tightBounds | boolean | Tighten min and max bounds strictly to min/max in dataset | No | false | 107 | | verticalGridStep | number | How many vertical grid lines to show | No | 4 | 108 | | horizontalGridStep | number | How many horizontal grid lines to show | No | all | 109 | | xAxisHeight | number | Height of X-axis container | No | 20 | 110 | | yAxisTransform | Function | Transform data point to y-axis label | No | (_) => _ | 111 | | xAxisTransform | Function | Transform data point to x-axis label | No | (_) => _ | 112 | | yAxisWidth | number | Width of the Y-axis container | No | 30 | 113 | | yAxisUseDecimal | boolean | Show decimals on Y-axis labels | No | false | 114 | | yAxisShortLabel | boolean | Shorten yAxis labels with K, M, B for thousand<->billion, etc | No | false | 115 | 116 | ## TODO 117 | - [ ] Code cleanup 118 | - [X] Multi-line chart 119 | - [ ] Horizontal line chart 120 | - [ ] Scatter chart 121 | 122 | -------------------------------------------------------------------------------- /src/Chart.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 'use strict'; 3 | import React, { Component, PropTypes } from 'react'; 4 | import { LayoutAnimation, StyleSheet, View } from 'react-native'; 5 | import BarChart from './BarChart'; 6 | import LineChart from './LineChart'; 7 | import PieChart from './PieChart'; 8 | import YAxis from './yAxis'; 9 | import XAxis from './xAxis'; 10 | import * as C from './constants'; 11 | 12 | const styles = StyleSheet.create({ 13 | default: {}, 14 | }); 15 | 16 | const getRoundNumber = (value, gridStep) => { 17 | if (value <= 0) return 0; 18 | const logValue = Math.log10(value); 19 | const scale = Math.pow(10, Math.floor(logValue)); 20 | const n = Math.ceil(value / scale * 4); 21 | 22 | let tmp = n % gridStep; 23 | if (tmp !== 0) tmp += (gridStep - tmp); 24 | return n * scale / 4.0; 25 | }; 26 | 27 | 28 | export default class Chart extends Component { 29 | static defaultProps : any = { 30 | data: [[]], 31 | animated: true, 32 | animationDuration: 300, 33 | axisColor: C.BLACK, 34 | axisLabelColor: C.BLACK, 35 | axisLineWidth: 1, 36 | axisTitleColor: C.GREY, 37 | axisTitleFontSize: 16, 38 | chartFontSize: 14, 39 | color: [], 40 | dataPointRadius: 3, 41 | gridColor: C.BLACK, 42 | gridLineWidth: 0.5, 43 | hideHorizontalGridLines: false, 44 | hideVerticalGridLines: false, 45 | horizontalScale: 1, 46 | labelFontSize: 10, 47 | lineWidth: 1, 48 | showAxis: true, 49 | showDataPoint: false, 50 | showGrid: true, 51 | showXAxisLabels: true, 52 | showYAxisLabels: true, 53 | tightBounds: false, 54 | verticalGridStep: 4, 55 | xAxisHeight: 20, 56 | yAxisWidth: 30, 57 | yAxisUseDecimal: false, 58 | yAxisShortLabel: false, 59 | }; 60 | 61 | constructor(props : any) { 62 | super(props); 63 | this.state = { bounds: { min: 0, max: 0 } }; 64 | } 65 | componentDidMount() { 66 | this._computeBounds(); 67 | } 68 | 69 | shouldComponentUpdate(props, state) { 70 | return props !== this.props || state !== this.state; 71 | } 72 | 73 | componentDidUpdate(props : any) { 74 | if (this.props !== props) { 75 | this._computeBounds(); 76 | } 77 | } 78 | 79 | _computeBounds() : any { 80 | let min = Infinity; 81 | let max = -Infinity; 82 | const data = this.props.data || [[]]; 83 | 84 | data.forEach(Graph => { 85 | Graph.forEach(XYPair => { 86 | const number = XYPair[1]; 87 | // Addition for blank spaces in graphs - use '' as y-coord 88 | if (number === '') { 89 | return; 90 | } 91 | 92 | if (number < min) min = number; 93 | if (number > max) max = number; 94 | }); 95 | }); 96 | 97 | let ceilMax = Math.ceil(max); 98 | let floorMin = Math.floor(min); 99 | 100 | if ((ceilMax - floorMin) > this.props.verticalGridStep) { 101 | min = floorMin; 102 | max = ceilMax; 103 | } 104 | 105 | // Exit if we want tight bounds 106 | if (this.props.tightBounds) { 107 | return this.setState({ bounds: { min, max } }); 108 | } 109 | 110 | max = getRoundNumber(max, this.props.verticalGridStep); 111 | if (min < 0) { 112 | let step; 113 | 114 | if (this.props.verticalGridStep > 3) { 115 | step = Math.abs(max - min) / (this.props.verticalGridStep - 1); 116 | } else { 117 | step = Math.max(Math.abs(max - min) / 2, Math.max(Math.abs(min), Math.abs(max))); 118 | } 119 | step = getRoundNumber(step, this.props.verticalGridStep); 120 | let newMin; 121 | let newMax; 122 | 123 | if (Math.abs(min) > Math.abs(max)) { 124 | const m = Math.ceil(Math.abs(min) / step); 125 | newMin = step * m * (min > 0 ? 1 : -1); 126 | newMax = step * (this.props.verticalGridStep - m) * (max > 0 ? 1 : -1); 127 | } else { 128 | const m = Math.ceil(Math.abs(max) / step); 129 | newMax = step * m * (max > 0 ? 1 : -1); 130 | newMin = step * (this.props.verticalGridStep - m) * (min > 0 ? 1 : -1); 131 | } 132 | if (min < newMin) { 133 | newMin -= step; 134 | newMax -= step; 135 | } 136 | if (max > newMax + step) { 137 | newMin += step; 138 | newMax += step; 139 | } 140 | if (max < min) { 141 | const tmp = max; 142 | max = min; 143 | min = tmp; 144 | } 145 | } 146 | return this.setState({ bounds: { max, min } }); 147 | } 148 | 149 | _onContainerLayout = (e : Object) => this.setState({ 150 | containerHeight: e.nativeEvent.layout.height, 151 | containerWidth: e.nativeEvent.layout.width, 152 | }); 153 | 154 | _minVerticalBound() : number { 155 | if (this.props.tightBounds) return this.state.bounds.min; 156 | return (this.state.bounds.min > 0) ? this.state.bounds.min : 0; 157 | } 158 | 159 | _maxVerticalBound() : number { 160 | if (this.props.tightBounds) return this.state.bounds.max; 161 | return (this.state.bounds.max > 0) ? this.state.bounds.max : 0; 162 | } 163 | 164 | render() { 165 | const components = { 'line': LineChart, 'bar': BarChart, 'pie': PieChart }; 166 | const axisAlign = (this.props.type === 'line') ? 'left' : 'center'; 167 | return ( 168 | 169 | {(() => { 170 | const ChartType = components[this.props.type] || BarChart; 171 | if (this.props.showAxis && Chart !== PieChart) { 172 | return ( 173 | 178 | 179 | 180 | 192 | 193 | 201 | 202 | {(() => { 203 | return ( 204 | 205 | 213 | 214 | ); 215 | })()} 216 | 217 | ); 218 | } 219 | return ( 220 | 225 | 233 | 234 | ); 235 | })()} 236 | 237 | ); 238 | } 239 | } 240 | 241 | Chart.propTypes = { 242 | // Shared properties between most types 243 | // data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.array)).isRequired, 244 | type: PropTypes.oneOf(['line', 'bar', 'pie']).isRequired, 245 | highlightColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // TODO 246 | highlightIndices: PropTypes.arrayOf(PropTypes.number), // TODO 247 | onDataPointPress: PropTypes.func, 248 | yAxisUseDecimal: PropTypes.bool, 249 | yAxisShortLabel: PropTypes.bool, 250 | 251 | // Bar chart props 252 | color: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), 253 | cornerRadius: PropTypes.number, 254 | // fillGradient: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), // TODO 255 | widthPercent: PropTypes.number, 256 | 257 | // Line/multi-line chart props 258 | fillColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), // need to adjust for multi-line 259 | dataPointColor: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), 260 | dataPointFillColor: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), 261 | dataPointRadius: PropTypes.number, 262 | // highlightRadius: PropTypes.number, // TODO 263 | lineWidth: PropTypes.number, 264 | showDataPoint: PropTypes.bool, // TODO 265 | 266 | // Pie chart props 267 | // pieCenterRatio: PropTypes.number, // TODO 268 | sliceColors: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), 269 | animationDuration: PropTypes.number, 270 | axisColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 271 | axisLabelColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 272 | axisLineWidth: PropTypes.number, 273 | // axisTitleColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 274 | // axisTitleFontSize: PropTypes.number, 275 | // chartFontSize: PropTypes.number, 276 | // chartTitle: PropTypes.string, 277 | // chartTitleColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 278 | gridColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 279 | gridLineWidth: PropTypes.number, 280 | hideHorizontalGridLines: PropTypes.bool, 281 | hideVerticalGridLines: PropTypes.bool, 282 | // labelFontSize: PropTypes.number, 283 | showAxis: PropTypes.bool, 284 | showGrid: PropTypes.bool, 285 | showXAxisLabels: PropTypes.bool, 286 | showYAxisLabels: PropTypes.bool, 287 | style: PropTypes.any, 288 | tightBounds: PropTypes.bool, 289 | verticalGridStep: PropTypes.number, 290 | horizontalGridStep: PropTypes.number, 291 | // xAxisTitle: PropTypes.string, 292 | xAxisHeight: PropTypes.number, 293 | xAxisTransform: PropTypes.func, 294 | // yAxisTitle: PropTypes.string, 295 | yAxisTransform: PropTypes.func, 296 | yAxisWidth: PropTypes.number, 297 | }; 298 | -------------------------------------------------------------------------------- /dist/LineChart.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(exports,"__esModule",{value:true});var _slicedToArray=function(){function sliceIterator(arr,i){var _arr=[];var _n=true;var _d=false;var _e=undefined;try{for(var _i=arr[typeof Symbol==='function'?Symbol.iterator:'@@iterator'](),_s;!(_n=(_s=_i.next()).done);_n=true){_arr.push(_s.value);if(i&&_arr.length===i)break;}}catch(err){_d=true;_e=err;}finally {try{if(!_n&&_i["return"])_i["return"]();}finally {if(_d)throw _e;}}return _arr;}return function(arr,i){if(Array.isArray(arr)){return arr;}else if((typeof Symbol==='function'?Symbol.iterator:'@@iterator') in Object(arr)){return sliceIterator(arr,i);}else {throw new TypeError("Invalid attempt to destructure non-iterable instance");}};}();var _extends=Object.assign||function(target){for(var i=1;imax)max=number;});});var ceilMax=Math.ceil(max);var floorMin=Math.floor(min);if(ceilMax-floorMin>this.props.verticalGridStep){min=floorMin;max=ceilMax;} // Exit if we want tight bounds 152 | if(this.props.tightBounds){return this.setState({bounds:{min:min,max:max}});}max=getRoundNumber(max,this.props.verticalGridStep);if(min<0){var step=void 0;if(this.props.verticalGridStep>3){step=Math.abs(max-min)/(this.props.verticalGridStep-1);}else {step=Math.max(Math.abs(max-min)/2,Math.max(Math.abs(min),Math.abs(max)));}step=getRoundNumber(step,this.props.verticalGridStep);var newMin=void 0;var newMax=void 0;if(Math.abs(min)>Math.abs(max)){var m=Math.ceil(Math.abs(min)/step);newMin=step*m*(min>0?1:-1);newMax=step*(this.props.verticalGridStep-m)*(max>0?1:-1);}else {var _m=Math.ceil(Math.abs(max)/step);newMax=step*_m*(max>0?1:-1);newMin=step*(this.props.verticalGridStep-_m)*(min>0?1:-1);}if(minnewMax+step){newMin+=step;newMax+=step;}if(max0?this.state.bounds.min:0;}},{key:'_maxVerticalBound',value:function _maxVerticalBound() 156 | 157 | 158 | { 159 | if(this.props.tightBounds)return this.state.bounds.max; 160 | return this.state.bounds.max>0?this.state.bounds.max:0;}},{key:'render',value:function render() 161 | 162 | 163 | {var _this2=this; 164 | var components={'line':_LineChart2.default,'bar':_BarChart2.default,'pie':_PieChart2.default}; 165 | var axisAlign=this.props.type==='line'?'left':'center'; 166 | return ( 167 | _react2.default.createElement(_reactNative.View,{__source:{fileName:_jsxFileName,lineNumber:167}}, 168 | function(){ 169 | var ChartType=components[_this2.props.type]||_BarChart2.default; 170 | if(_this2.props.showAxis&&Chart!==_PieChart2.default){ 171 | return ( 172 | _react2.default.createElement(_reactNative.View,{ 173 | ref:'container', 174 | style:[_this2.props.style||{},{flex:1,flexDirection:'column'}], 175 | onLayout:_this2._onContainerLayout,__source:{fileName:_jsxFileName,lineNumber:172}}, 176 | 177 | _react2.default.createElement(_reactNative.View,{style:[styles.default,{flexDirection:'row'}],__source:{fileName:_jsxFileName,lineNumber:177}}, 178 | _react2.default.createElement(_reactNative.View,{ref:'yAxis',__source:{fileName:_jsxFileName,lineNumber:178}}, 179 | _react2.default.createElement(_yAxis2.default,_extends({}, 180 | _this2.props,{ 181 | data:_this2.props.data, 182 | height:_this2.state.containerHeight-_this2.props.xAxisHeight, 183 | width:_this2.props.yAxisWidth, 184 | minVerticalBound:_this2.state.bounds.min, 185 | containerWidth:_this2.state.containerWidth, 186 | maxVerticalBound:_this2.state.bounds.max, 187 | yAxisUseDecimal:_this2.props.yAxisUseDecimal, 188 | yAxisShortLabel:_this2.props.yAxisShortLabel, 189 | style:{width:_this2.props.yAxisWidth},__source:{fileName:_jsxFileName,lineNumber:179}}))), 190 | 191 | 192 | _react2.default.createElement(ChartType,_extends({}, 193 | _this2.props,{ 194 | data:_this2.props.data, 195 | width:_this2.state.containerWidth-_this2.props.yAxisWidth, 196 | height:_this2.state.containerHeight-_this2.props.xAxisHeight, 197 | minVerticalBound:_this2.state.bounds.min, 198 | maxVerticalBound:_this2.state.bounds.max,__source:{fileName:_jsxFileName,lineNumber:192}}))), 199 | 200 | 201 | function(){ 202 | return ( 203 | _react2.default.createElement(_reactNative.View,{ref:'xAxis',__source:{fileName:_jsxFileName,lineNumber:203}}, 204 | _react2.default.createElement(_xAxis2.default,_extends({}, 205 | _this2.props,{ 206 | width:_this2.state.containerWidth-_this2.props.yAxisWidth, 207 | data:_this2.props.data, 208 | height:_this2.props.xAxisHeight, 209 | align:axisAlign, 210 | style:{marginLeft:_this2.props.yAxisWidth-1},__source:{fileName:_jsxFileName,lineNumber:204}}))));}()));} 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | return ( 219 | _react2.default.createElement(_reactNative.View,{ 220 | ref:'container', 221 | onLayout:_this2._onContainerLayout, 222 | style:[_this2.props.style||{},styles.default],__source:{fileName:_jsxFileName,lineNumber:219}}, 223 | 224 | _react2.default.createElement(ChartType,_extends({}, 225 | _this2.props,{ 226 | data:_this2.props.data, 227 | width:_this2.state.containerWidth, 228 | height:_this2.state.containerHeight, 229 | minVerticalBound:_this2.state.bounds.min, 230 | maxVerticalBound:_this2.state.bounds.max,__source:{fileName:_jsxFileName,lineNumber:224}}))));}()));}}]);return Chart;}(_react.Component);Chart.defaultProps={data:[[]],animated:true,animationDuration:300,axisColor:C.BLACK,axisLabelColor:C.BLACK,axisLineWidth:1,axisTitleColor:C.GREY,axisTitleFontSize:16,chartFontSize:14,dataPointRadius:3,gridColor:C.BLACK,gridLineWidth:0.5,hideHorizontalGridLines:false,hideVerticalGridLines:false,horizontalScale:1,labelFontSize:10,lineWidth:1,showAxis:true,showDataPoint:false,showGrid:true,showXAxisLabels:true,showYAxisLabels:true,tightBounds:false,verticalGridStep:4,xAxisHeight:20,yAxisWidth:30,yAxisUseDecimal:false,yAxisShortLabel:false};exports.default=Chart; 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | Chart.propTypes={ 241 | // Shared properties between most types 242 | // data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.array)).isRequired, 243 | type:_react.PropTypes.oneOf(['line','bar','pie']).isRequired, 244 | highlightColor:_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string]), // TODO 245 | highlightIndices:_react.PropTypes.arrayOf(_react.PropTypes.number), // TODO 246 | onDataPointPress:_react.PropTypes.func, 247 | yAxisUseDecimal:_react.PropTypes.bool, 248 | yAxisShortLabel:_react.PropTypes.bool, 249 | 250 | // Bar chart props 251 | color:_react.PropTypes.arrayOf(_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string])), 252 | cornerRadius:_react.PropTypes.number, 253 | // fillGradient: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])), // TODO 254 | widthPercent:_react.PropTypes.number, 255 | 256 | // Line/multi-line chart props 257 | fillColor:_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string]), // need to adjust for multi-line 258 | dataPointColor:_react.PropTypes.arrayOf(_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string])), 259 | dataPointFillColor:_react.PropTypes.arrayOf(_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string])), 260 | dataPointRadius:_react.PropTypes.number, 261 | // highlightRadius: PropTypes.number, // TODO 262 | lineWidth:_react.PropTypes.number, 263 | showDataPoint:_react.PropTypes.bool, // TODO 264 | 265 | // Pie chart props 266 | // pieCenterRatio: PropTypes.number, // TODO 267 | sliceColors:_react.PropTypes.arrayOf(_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string])), 268 | animationDuration:_react.PropTypes.number, 269 | axisColor:_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string]), 270 | axisLabelColor:_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string]), 271 | axisLineWidth:_react.PropTypes.number, 272 | // axisTitleColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 273 | // axisTitleFontSize: PropTypes.number, 274 | // chartFontSize: PropTypes.number, 275 | // chartTitle: PropTypes.string, 276 | // chartTitleColor: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 277 | gridColor:_react.PropTypes.oneOfType([_react.PropTypes.number,_react.PropTypes.string]), 278 | gridLineWidth:_react.PropTypes.number, 279 | hideHorizontalGridLines:_react.PropTypes.bool, 280 | hideVerticalGridLines:_react.PropTypes.bool, 281 | // labelFontSize: PropTypes.number, 282 | showAxis:_react.PropTypes.bool, 283 | showGrid:_react.PropTypes.bool, 284 | showXAxisLabels:_react.PropTypes.bool, 285 | showYAxisLabels:_react.PropTypes.bool, 286 | style:_react.PropTypes.any, 287 | tightBounds:_react.PropTypes.bool, 288 | verticalGridStep:_react.PropTypes.number, 289 | horizontalGridStep:_react.PropTypes.number, 290 | // xAxisTitle: PropTypes.string, 291 | xAxisHeight:_react.PropTypes.number, 292 | xAxisTransform:_react.PropTypes.func, 293 | // yAxisTitle: PropTypes.string, 294 | yAxisTransform:_react.PropTypes.func, 295 | yAxisWidth:_react.PropTypes.number}; --------------------------------------------------------------------------------