├── .prettierrc ├── .watchmanconfig ├── .eslintrc.json ├── .babelrc ├── .npmignore ├── app.json ├── promo ├── banner.jpg ├── advantages.jpg ├── showcase-1.jpg ├── showcase-qr.jpg ├── single-bar-chart.jpg ├── single-piechart.jpg ├── single-progress.jpg ├── single-line-bezier.jpg ├── single-line-chart.jpg └── single-contribution-graph.jpg ├── src ├── line-chart │ ├── index.ts │ ├── LegendItem.tsx │ └── LineChart.tsx ├── piechart_modified.png ├── Utils.ts ├── contribution-graph │ ├── constants.ts │ ├── DateHelpers.ts │ ├── index.tsx │ └── ContributionGraph.tsx ├── index.ts ├── PieChart.tsx ├── HelperTypes.ts ├── ProgressChart.tsx ├── StackedBarChart.tsx ├── BarChart.tsx └── AbstractChart.tsx ├── .gitignore ├── index.js ├── tsconfig.json ├── .github └── FUNDING.yml ├── contributing.md ├── LICENSE ├── package.json ├── data.js ├── CHANGELOG.md ├── App.js └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app" 3 | } 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"] 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | npm-debug.* 4 | /promo 5 | .babelrc 6 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { "sdkVersion": "37.0.0", "platforms": ["ios", "android"] } 3 | } 4 | -------------------------------------------------------------------------------- /promo/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/banner.jpg -------------------------------------------------------------------------------- /src/line-chart/index.ts: -------------------------------------------------------------------------------- 1 | import LineChart from "./LineChart"; 2 | 3 | export default LineChart; 4 | -------------------------------------------------------------------------------- /promo/advantages.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/advantages.jpg -------------------------------------------------------------------------------- /promo/showcase-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/showcase-1.jpg -------------------------------------------------------------------------------- /promo/showcase-qr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/showcase-qr.jpg -------------------------------------------------------------------------------- /promo/single-bar-chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/single-bar-chart.jpg -------------------------------------------------------------------------------- /promo/single-piechart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/single-piechart.jpg -------------------------------------------------------------------------------- /promo/single-progress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/single-progress.jpg -------------------------------------------------------------------------------- /src/piechart_modified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/src/piechart_modified.png -------------------------------------------------------------------------------- /promo/single-line-bezier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/single-line-bezier.jpg -------------------------------------------------------------------------------- /promo/single-line-chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/single-line-chart.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .expo/ 3 | .DS_Store 4 | npm-debug.* 5 | package-lock.json 6 | .idea 7 | .vscode 8 | yarn.lock 9 | dist -------------------------------------------------------------------------------- /promo/single-contribution-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indiespirit/react-native-chart-kit/HEAD/promo/single-contribution-graph.jpg -------------------------------------------------------------------------------- /src/Utils.ts: -------------------------------------------------------------------------------- 1 | export function mapValue( 2 | x: number, 3 | in_min: number, 4 | in_max: number, 5 | out_min: number, 6 | out_max: number 7 | ) { 8 | return ((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min; 9 | } 10 | -------------------------------------------------------------------------------- /src/contribution-graph/constants.ts: -------------------------------------------------------------------------------- 1 | export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000; 2 | 3 | export const DAYS_IN_WEEK = 7; 4 | 5 | export const MONTH_LABELS = { 6 | 0: "Jan", 7 | 1: "Feb", 8 | 2: "Mar", 9 | 3: "Apr", 10 | 4: "May", 11 | 5: "Jun", 12 | 6: "Jul", 13 | 7: "Aug", 14 | 8: "Sep", 15 | 9: "Oct", 16 | 10: "Nov", 17 | 11: "Dec" 18 | }; 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import AbstractChart from "./AbstractChart"; 2 | import BarChart from "./BarChart"; 3 | import PieChart from "./PieChart"; 4 | import ProgressChart from "./ProgressChart"; 5 | import StackedBarChart from "./StackedBarChart"; 6 | import ContributionGraph from "./contribution-graph"; 7 | import LineChart from "./line-chart"; 8 | 9 | export { 10 | AbstractChart, 11 | BarChart, 12 | LineChart, 13 | PieChart, 14 | ProgressChart, 15 | ContributionGraph, 16 | StackedBarChart 17 | }; 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import AbstractChart from "./src/abstract-chart"; 2 | import LineChart from "./src/line-chart"; 3 | import BarChart from "./src/bar-chart"; 4 | import PieChart from "./src/pie-chart"; 5 | import ProgressChart from "./src/progress-chart"; 6 | import ContributionGraph from "./src/contribution-graph"; 7 | import StackedBarChart from "./src/stackedbar-chart"; 8 | 9 | export { 10 | AbstractChart, 11 | BarChart, 12 | LineChart, 13 | PieChart, 14 | ProgressChart, 15 | ContributionGraph, 16 | StackedBarChart 17 | }; 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es5", 5 | "es6", 6 | "es7", 7 | "es2015", 8 | "es2016", 9 | "es2017", 10 | "es2018", 11 | "esnext" 12 | ], 13 | "target": "es5", 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "rootDir": "./src", 17 | "outDir": "./dist", 18 | "declaration": true, 19 | "declarationMap": true, 20 | "inlineSourceMap": true, 21 | "inlineSources": true, 22 | "esModuleInterop": true, 23 | "noErrorTruncation": true, 24 | "jsx": "react-native" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/contribution-graph/DateHelpers.ts: -------------------------------------------------------------------------------- 1 | // returns a new date shifted a certain number of days (can be negative) 2 | export function shiftDate(date: Date, numDays: number) { 3 | const newDate = new Date(date); 4 | newDate.setDate(newDate.getDate() + numDays); 5 | return newDate; 6 | } 7 | 8 | export function getBeginningTimeForDate(date: Date) { 9 | return new Date(date.getFullYear(), date.getMonth(), date.getDate()); 10 | } 11 | 12 | // obj can be a parseable string, a millisecond timestamp, or a Date object 13 | export function convertToDate(obj: string | number | Date) { 14 | return obj instanceof Date ? obj : new Date(obj); 15 | } 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: hermanya # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to React Native Chart Kit 2 | 3 | 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 4 | 5 | Suggestions and pull requests are highly encouraged! Have a look at the [open issues](https://github.com/indiespirit/react-native-chart-kit/issues). 6 | 7 | ## Workflow 8 | 9 | First clone: 10 | 11 | ```sh 12 | git clone git@github.com:indiespirit/react-native-chart-kit.git 13 | cd react-native-chart-kit 14 | yarn install 15 | ``` 16 | 17 | In order to run it, you are gonna have to flip values for "main" and "_main" in package json. This is nessesary because both npm and expo have a notion of a main file, but for npm it's the file that you run when you import this library in your app; and for expo it's the file that it uses to display the example app. 18 | 19 | Don't forget to flip it back before commiting. 20 | 21 | **After you update fix the package.json** 22 | 23 | ```sh 24 | yarn start # And get you expo app ready on your phone 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /src/contribution-graph/index.tsx: -------------------------------------------------------------------------------- 1 | import { ViewStyle } from "react-native"; 2 | 3 | import { AbstractChartProps } from "../AbstractChart"; 4 | import ContributionGraph, { 5 | ContributionChartValue, 6 | TooltipDataAttrs 7 | } from "./ContributionGraph"; 8 | 9 | export interface ContributionGraphProps extends AbstractChartProps { 10 | values: Array; 11 | endDate: Date; 12 | numDays: number; 13 | width: number; 14 | height: number; 15 | gutterSize?: number; 16 | squareSize?: number; 17 | horizontal?: boolean; 18 | showMonthLabels?: boolean; 19 | showOutOfRangeDays?: boolean; 20 | accessor?: string; 21 | getMonthLabel?: (monthIndex: number) => string; 22 | onDayPress?: ({ count: number, date: Date }) => void; 23 | classForValue?: (value: string) => string; 24 | style?: Partial; 25 | titleForValue?: (value: ContributionChartValue) => string; 26 | tooltipDataAttrs: TooltipDataAttrs; 27 | } 28 | 29 | export type ContributionGraphState = { 30 | maxValue: number; 31 | minValue: number; 32 | valueCache: object; 33 | }; 34 | 35 | export default ContributionGraph; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 indiespirit 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 | -------------------------------------------------------------------------------- /src/line-chart/LegendItem.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Color, Rect, Text, TextProps } from "react-native-svg"; 3 | 4 | const CIRCLE_WIDTH = 16; 5 | const PADDING_LEFT = 4; 6 | const CHARACTER_WIDTH = 6; 7 | 8 | export type LegendItemProps = { 9 | baseLegendItemX: number; 10 | index: number; 11 | legendOffset: number; 12 | legendText: string; 13 | iconColor: Color; 14 | labelProps: TextProps; 15 | }; 16 | 17 | export const LegendItem = (props: LegendItemProps) => { 18 | const { baseLegendItemX, index } = props; 19 | /* half the height of the legend Rect, minus half the height of the circle to align the 20 | circle from its center, rather than its top. */ 21 | const centerAlignedCircle = props.legendOffset / 2 - CIRCLE_WIDTH / 2; 22 | // 65% of the legend container height centers the text in relation to the circles 23 | const centerAlignedText = props.legendOffset * 0.65; 24 | // to center the legendItem on the baseLegendItemX 25 | const textLengthOffset = (props.legendText.length * CHARACTER_WIDTH) / 2; 26 | const legendItemNumber = index + 1; 27 | 28 | return ( 29 | <> 30 | 41 | 48 | {props.legendText} 49 | 50 | 51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-chart-kit", 3 | "version": "6.11.0", 4 | "devDependencies": { 5 | "@types/react-native": "^0.62.13", 6 | "babel-eslint": "10.x", 7 | "babel-plugin-module-resolver": "^3.1.1", 8 | "babel-polyfill": "^6.26.0", 9 | "eslint": "6.x", 10 | "eslint-config-react-app": "^5.0.2", 11 | "eslint-plugin-flowtype": "^4.3.0", 12 | "eslint-plugin-import": "2.x", 13 | "eslint-plugin-jsx-a11y": "6.x", 14 | "eslint-plugin-react": "7.x", 15 | "eslint-plugin-react-hooks": "1.x", 16 | "expo": "^37.0.0", 17 | "husky": "^3.0.7", 18 | "jest-expo": "^37.0.0", 19 | "prettier": "^1.18.2", 20 | "pretty-quick": "^1.11.1", 21 | "react": "16.9.0", 22 | "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", 23 | "react-native-flash-message": "^0.1.10", 24 | "react-native-scrollable-tab-view": "^1.0.0", 25 | "react-native-svg": "11.0.1", 26 | "react-test-renderer": "16.7.0", 27 | "typescript": "^3.9.5" 28 | }, 29 | "_main": "./node_modules/expo/AppEntry.js", 30 | "main": "./dist/index.js", 31 | "typings": "./dist/index.d.ts", 32 | "files": [ 33 | "dist" 34 | ], 35 | "scripts": { 36 | "start": "expo start", 37 | "eject": "expo eject", 38 | "android": "expo start --android", 39 | "ios": "expo start --ios", 40 | "test": "jest", 41 | "build": "tsc", 42 | "dev": "tsc --watch", 43 | "prepublish": "yarn build", 44 | "prepare": "yarn build" 45 | }, 46 | "jest": { 47 | "preset": "jest-expo" 48 | }, 49 | "peerDependencies": { 50 | "react": "> 16.7.0", 51 | "react-native": ">= 0.50.0", 52 | "react-native-svg": "> 6.4.1" 53 | }, 54 | "dependencies": { 55 | "lodash": "^4.17.13", 56 | "paths-js": "^0.4.10", 57 | "point-in-polygon": "^1.0.1" 58 | }, 59 | "husky": { 60 | "hooks": { 61 | "pre-commit": "pretty-quick --staged" 62 | } 63 | }, 64 | "license": "MIT", 65 | "homepage": "https://github.com/indiespirit/react-native-chart-kit", 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/indiespirit/react-native-chart-kit" 69 | }, 70 | "resolutions": { 71 | "@types/react": "16.14.8" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | // Mock data object used for LineChart and BarChart 2 | 3 | const data = { 4 | labels: ["January", "February", "March", "April", "May", "June"], 5 | datasets: [ 6 | { 7 | data: [-50, -20, -2, 86, 71, 100], 8 | color: (opacity = 1) => `rgba(134, 65, 244, ${opacity})` // optional 9 | }, 10 | { 11 | data: [20, 10, 4, 56, 87, 90], 12 | color: (opacity = 1) => `rgba(0, 255, 255, ${opacity})` // optional 13 | }, 14 | { 15 | data: [30, 90, 67, 54, 10, 2] 16 | } 17 | ], 18 | legend: ["Rainy Days", "Sunny Days", "Snowy Days"] // optional 19 | }; 20 | 21 | // Mock data object used for Contribution Graph 22 | 23 | const contributionData = [ 24 | { date: "2016-01-02", count: 1 }, 25 | { date: "2016-01-03", count: 2 }, 26 | { date: "2016-01-04", count: 3 }, 27 | { date: "2016-01-05", count: 4 }, 28 | { date: "2016-01-06", count: 5 }, 29 | { date: "2016-01-30", count: 2 }, 30 | { date: "2016-01-31", count: 3 }, 31 | { date: "2016-03-01", count: 2 }, 32 | { date: "2016-04-02", count: 4 }, 33 | { date: "2016-03-05", count: 2 }, 34 | { date: "2016-02-30", count: 4 } 35 | ]; 36 | 37 | // Mock data object for Pie Chart 38 | 39 | const pieChartData = [ 40 | { 41 | name: "Seoul", 42 | population: 21500000, 43 | color: "rgba(131, 167, 234, 1)", 44 | legendFontColor: "#7F7F7F", 45 | legendFontSize: 15 46 | }, 47 | { 48 | name: "Toronto", 49 | population: 2800000, 50 | color: "#F00", 51 | legendFontColor: "#7F7F7F", 52 | legendFontSize: 15 53 | }, 54 | { 55 | name: "Beijing", 56 | population: 527612, 57 | color: "red", 58 | legendFontColor: "#7F7F7F", 59 | legendFontSize: 15 60 | }, 61 | { 62 | name: "New York", 63 | population: 8538000, 64 | color: "#ffffff", 65 | legendFontColor: "#7F7F7F", 66 | legendFontSize: 15 67 | }, 68 | { 69 | name: "Moscow", 70 | population: 11920000, 71 | color: "rgb(0, 0, 255)", 72 | legendFontColor: "#7F7F7F", 73 | legendFontSize: 15 74 | } 75 | ]; 76 | 77 | // Mock data object for Progress 78 | 79 | const progressChartData = { 80 | labels: ["Swim", "Bike", "Run"], // optional 81 | data: [0.2, 0.5, 0.3] 82 | }; 83 | 84 | const stackedBarGraphData = { 85 | labels: ["Test1", "Test2"], 86 | legend: ["L1", "L2", "L3"], 87 | data: [[60, 60, 60], [30, 30, 60]], 88 | barColors: ["#dfe4ea", "#ced6e0", "#a4b0be"] 89 | }; 90 | 91 | export { 92 | data, 93 | contributionData, 94 | pieChartData, 95 | progressChartData, 96 | stackedBarGraphData 97 | }; 98 | -------------------------------------------------------------------------------- /src/PieChart.tsx: -------------------------------------------------------------------------------- 1 | import Pie from "paths-js/pie"; 2 | import React from "react"; 3 | import { View, ViewStyle } from "react-native"; 4 | import { G, Path, Rect, Svg, Text } from "react-native-svg"; 5 | 6 | import AbstractChart, { AbstractChartProps } from "./AbstractChart"; 7 | 8 | export interface PieChartProps extends AbstractChartProps { 9 | data: Array; 10 | width: number; 11 | height: number; 12 | accessor: string; 13 | backgroundColor: string; 14 | paddingLeft: string; 15 | center?: Array; 16 | absolute?: boolean; 17 | hasLegend?: boolean; 18 | style?: Partial; 19 | avoidFalseZero?: boolean; 20 | } 21 | 22 | type PieChartState = {}; 23 | 24 | class PieChart extends AbstractChart { 25 | render() { 26 | const { 27 | style = {}, 28 | backgroundColor, 29 | absolute = false, 30 | hasLegend = true, 31 | avoidFalseZero = false 32 | } = this.props; 33 | 34 | const { borderRadius = 0 } = style; 35 | 36 | const chart = Pie({ 37 | center: this.props.center || [0, 0], 38 | r: 0, 39 | R: this.props.height / 2.5, 40 | data: this.props.data, 41 | accessor: x => { 42 | return x[this.props.accessor]; 43 | } 44 | }); 45 | 46 | const total = this.props.data.reduce((sum, item) => { 47 | return sum + item[this.props.accessor]; 48 | }, 0); 49 | 50 | const slices = chart.curves.map((c, i) => { 51 | let value: string; 52 | 53 | if (absolute) { 54 | value = c.item[this.props.accessor]; 55 | } else { 56 | if (total === 0) { 57 | value = 0 + "%"; 58 | } else { 59 | const percentage = Math.round( 60 | (100 / total) * c.item[this.props.accessor] 61 | ); 62 | value = Math.round((100 / total) * c.item[this.props.accessor]) + "%"; 63 | if (avoidFalseZero && percentage === 0) { 64 | value = "<1%"; 65 | } else { 66 | value = percentage + "%"; 67 | } 68 | } 69 | } 70 | 71 | return ( 72 | 73 | 74 | {hasLegend ? ( 75 | 88 | ) : null} 89 | {hasLegend ? ( 90 | 101 | {`${value} ${c.item.name}`} 102 | 103 | ) : null} 104 | 105 | ); 106 | }); 107 | 108 | return ( 109 | 117 | 118 | 119 | {this.renderDefs({ 120 | width: this.props.height, 121 | height: this.props.height, 122 | ...this.props.chartConfig 123 | })} 124 | 125 | 132 | 139 | {slices} 140 | 141 | 142 | 143 | ); 144 | } 145 | } 146 | 147 | export default PieChart; 148 | -------------------------------------------------------------------------------- /src/HelperTypes.ts: -------------------------------------------------------------------------------- 1 | import { TextStyle, ViewStyle } from "react-native"; 2 | import { CircleProps, TextProps } from "react-native-svg"; 3 | 4 | export interface Dataset { 5 | /** The data corresponding to the x-axis label. */ 6 | data: number[]; 7 | 8 | /** A function returning the color of the stroke given an input opacity value. */ 9 | color?: (opacity: number) => string; 10 | 11 | /** A function returning array of the colors of the stroke given an input opacity value for each data value. */ 12 | colors?: Array<(opacity: number) => string>; 13 | 14 | /** The width of the stroke. Defaults to 2. */ 15 | strokeWidth?: number; 16 | 17 | /** A boolean indicating whether to render dots for this line */ 18 | withDots?: boolean; 19 | 20 | /** Override of LineChart's withScrollableDot property just for this dataset */ 21 | withScrollableDot?: boolean; 22 | 23 | /** Unique key **/ 24 | key?: string | number; 25 | 26 | /** Stroke Dash Array */ 27 | strokeDashArray?: number[]; 28 | 29 | /** Stroke Dash Offset */ 30 | strokeDashOffset?: number; 31 | } 32 | 33 | export interface ChartData { 34 | /** The x-axis labels */ 35 | labels: string[]; 36 | datasets: Dataset[]; 37 | } 38 | 39 | export interface ChartConfig { 40 | backgroundColor?: string; 41 | /** 42 | * Defines the first color in the linear gradient of a chart's background 43 | */ 44 | backgroundGradientFrom?: string; 45 | /** 46 | * Defines the first color opacity in the linear gradient of a chart's background 47 | */ 48 | backgroundGradientFromOpacity?: number; 49 | /** 50 | * Defines the second color in the linear gradient of a chart's background 51 | */ 52 | backgroundGradientTo?: string; 53 | /** 54 | * Defines the second color opacity in the linear gradient of a chart's background 55 | */ 56 | backgroundGradientToOpacity?: number; 57 | /** 58 | * Defines the previous options to maintain backwards compatibility 59 | */ 60 | fillShadowGradient?: string; 61 | fillShadowGradientOpacity?: number; 62 | /** 63 | * Defines the first color in the linear gradient of the area under data 64 | */ 65 | fillShadowGradientFrom?: string; 66 | /** 67 | * Defines the first color opacity in the linear gradient of the area under data 68 | */ 69 | fillShadowGradientFromOpacity?: number; 70 | /** 71 | * Defines the first color offset in the linear gradient of the area under data 72 | */ 73 | fillShadowGradientFromOffset?: number; 74 | /** 75 | * Defines the second color in the linear gradient of the area under data 76 | */ 77 | fillShadowGradientTo?: string; 78 | /** 79 | * Defines the second color opacity in the linear gradient of the area under data 80 | */ 81 | fillShadowGradientToOpacity?: number; 82 | /** 83 | * Defines the second color offset in the linear gradient of the area under data 84 | */ 85 | fillShadowGradientToOffset?: number; 86 | /** 87 | * Defines the option to use color from dataset to each chart data 88 | */ 89 | useShadowColorFromDataset?: boolean; 90 | /** 91 | * Defines the base color function that is used to calculate colors of labels and sectors used in a chart 92 | */ 93 | color?: (opacity: number, index?: number) => string; 94 | /** 95 | * Defines the function that is used to calculate the color of the labels used in a chart. 96 | */ 97 | labelColor?: (opacity: number) => string; 98 | /** 99 | * Defines the base stroke width in a chart 100 | */ 101 | strokeWidth?: number; 102 | /** 103 | * Defines the percent (0-1) of the available width each bar width in a chart 104 | */ 105 | barPercentage?: number; 106 | barRadius?: number; 107 | /** 108 | * Override styles of the background lines, refer to react-native-svg's Line documentation 109 | */ 110 | propsForBackgroundLines?: object; 111 | /** 112 | * Override styles of the labels, refer to react-native-svg's Text documentation 113 | */ 114 | propsForLabels?: TextProps; 115 | /** 116 | * Override styles of vertical labels, refer to react-native-svg's Text documentation 117 | */ 118 | propsForVerticalLabels?: TextProps; 119 | 120 | /** 121 | * Override styles of horizontal labels, refer to react-native-svg's Text documentation 122 | */ 123 | propsForHorizontalLabels?: TextProps; 124 | /** 125 | * Override styles of the dots, refer to react-native-svg's Text documentation 126 | */ 127 | propsForDots?: CircleProps; 128 | decimalPlaces?: number; 129 | style?: Partial; 130 | 131 | /** 132 | * Define stroke line join type 133 | */ 134 | linejoinType?: "miter" | "bevel" | "round"; 135 | 136 | /** 137 | * Define fill color for scrollable dot 138 | */ 139 | scrollableDotFill?: string; 140 | 141 | /** 142 | * Define stroke color for scrollable dot 143 | */ 144 | scrollableDotStrokeColor?: string; 145 | 146 | /** 147 | * Define stroke width for scrollable dot 148 | */ 149 | scrollableDotStrokeWidth?: number; 150 | 151 | /** 152 | * Define radius for scrollable dot 153 | */ 154 | scrollableDotRadius?: number; 155 | 156 | /** 157 | * Override style for additional info view upper scrollable dot 158 | */ 159 | scrollableInfoViewStyle?: Partial; 160 | 161 | /** 162 | * Override text style for additional info view upper scrollable dot 163 | */ 164 | scrollableInfoTextStyle?: Partial; 165 | scrollableInfoTextDecorator?: (value: number) => string; 166 | 167 | /** 168 | * Set Info View offset 169 | */ 170 | scrollableInfoOffset?: number; 171 | 172 | /** 173 | * Set Info View size 174 | */ 175 | scrollableInfoSize?: Size; 176 | } 177 | 178 | export interface Size { 179 | width: number; 180 | height: number; 181 | } 182 | 183 | export type PartialBy = Omit & Partial>; 184 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v6.11 4 | 5 | - added a prop to customize vertical labels heigh 6 | - use full width when legend is hidden in StackedBarChart 7 | - added custom Y labels in StackedBarChart 8 | 9 | ## v6.8.0 10 | 11 | - use same colors in `ProgressChart` legend if `withCustomBarColorFromData` is set 12 | 13 | ## v6.7.0 14 | 15 | - allowed usage of custom color for each bar on ProgressChart 16 | - allowed usage of custom color for each bar on BarChart 17 | - fixed to display the correct height bar even if changing the "height" of the props in StackedBar 18 | - added indexData prop for renderDotContent 19 | 20 | ## v6.6.0 21 | 22 | - added `propsForVerticalLabels` and `propsForHorizontalLabels` 23 | 24 | ## v6.5.0 25 | 26 | - added `StackedBarChart` `percentile` stacking 27 | 28 | ## v6.4.1 29 | 30 | - added `PieChart` props `avoidFalseZero` 31 | 32 | ## v6.4.0 33 | 34 | - `ProgressChart` updated to include a condition to divide width by 2 for x value 35 | 36 | ## v6.2.0 37 | 38 | - added `withVerticalLines` and `withHorizontalLines` to `LineChart` 39 | 40 | ## v6.1.0 41 | 42 | - added `scrollableInfoTextDecorator` 43 | 44 | ## v6.0.0 45 | 46 | - Typescript rewrite 47 | 48 | ## v5.6.1 49 | 50 | - fixed linear gradient issue due to `react-native-svg` lib update 51 | - added handling for datasets data is null to use last line coordinates 52 | - updated to Expo SDK 37 and add clarification on usage to README.md 53 | 54 | ## v5.6.0 55 | 56 | - added `showValuesOnTopOfBars` prop to `BarChart` 57 | - fixed decimalPlaces being 0 and not applied in `BarChart` 58 | 59 | ## v5.5.0 60 | 61 | - added `useShadowColorFromDataset` to `chartConfig` to make `LineChart` shadow same as line color 62 | 63 | ## v5.4.2 64 | 65 | - fixed decimalPlaces not being sent with barChart 66 | 67 | ## v5.4.0 68 | 69 | - added strokeWidth & radius as props for ProgressChart 70 | 71 | ## v5.3.1 72 | 73 | - TS type fixes 74 | 75 | ## v5.3.0 76 | 77 | - added missing ContributionGraph props 78 | - added `withScrollableDot` to LineChart and a whole bunch of props to `chartConfig`. New feature for Line Chart - scrollable dot. It allows to navigate through chart using gesture and see value at dot's current position. 79 | 80 | ## v5.2.0 81 | 82 | - `propsForDots` added to `ChartConfig` interface 83 | 84 | ## 5.1.1 85 | 86 | - add some safe default values in BarChart's `chartConfig` to avoid potential null pointers 87 | 88 | ## 5.1.0 89 | 90 | - added a withDots property to each dataset in LineChart to disable dots on these lines 91 | - removed `prop-types` 92 | - added `onDayPress` to ContributionGraph 93 | 94 | ## 5.0.0 95 | 96 | - made ContributionGraph opacity distribution even through range between the min and max values 97 | - added `getMonthLabel` to ContributionGraph 98 | - added `yAxisInterval` to LineChart, it allows you to skip vertical lines in the background 99 | - expaned StackedBarChart if it has no legend 100 | 101 | ## 4.5.0 102 | 103 | - removed `.babelrc` from distribution 104 | - made decimalPlaces work for StackedBar Chart 105 | 106 | ## 4.4.0 107 | 108 | - added ability to add custom segments on the Y-Axis 109 | - implemented barRadius config in BarChart 110 | - added showBarTops prop to BarChart 111 | 112 | ## 4.3.0 113 | 114 | - added `barPercentage?: number; hideLegend: boolean;` props to StackedBarChart 115 | - added `barRadius` to chart config 116 | - added `renderDotContent` to LineChart 117 | 118 | ## 4.2.0 119 | 120 | - line chart supports legend 121 | 122 | ## 4.1.0 123 | 124 | - add `hideLegend` to ProgressChart 125 | 126 | ## v4.0.0 127 | 128 | - patched a lot of indirect dependencies 129 | - improved ProgressChartProps types 130 | - added item index to some color calls 131 | - added an optional bottom padding to LineChart 132 | - POTENTIALLY BREAKING for typescript: added some typedefs to "LineChart", "BarChart", and "StackedBarChart". Also added some typedefs for styles. 133 | - corrected the line-chart & progress-chart wrong width calculation 134 | 135 | ## v3.12.0 136 | 137 | - added `formatXLabel`, `formatYLabel`, and `getDotProps` to `LineChart` 138 | 139 | ## v3.11.0 140 | 141 | - added optional props: `xAxisLabel`, `yAxisSuffix`, `yLabelsOffset`, `xLabelsOffset`, and `hidePointsAtIndex` to `LineChart` 142 | - added optional prop `withInnerLines` to `BarChart` 143 | - added optional `fillShadowGradient` color and `fillShadowGradientOpacity` to chart config for customizing the area under the data points in `LinChart` and `BarChart` 144 | 145 | ## v3.10.0 146 | 147 | - added type for chart config 148 | - added props config for Dots in the line chart 149 | 150 | ## v3.9.0 151 | 152 | - added propsForLabels to chartConfig 153 | - added labelColor to chartConfig as a shortcut for propsForLabels / fill 154 | 155 | ## v3.8.0 156 | 157 | - added dot cx, cy in the onDataPointClick functions arguments 158 | - fixed for horizontal label position when there is only one data point and fromZero prop is true 159 | 160 | ## v3.7.0 161 | 162 | - expose paddingTop and paddingRight via the style prop 163 | - style the chart background lines with chartConfig's propsForBackgroundLines 164 | 165 | ## v3.6.0 166 | 167 | - added barPercentage property to chartConfig (by @dchirutac) 168 | - added dot color callback prop (by @stephenc222) 169 | - added bar chart label rotations (by @stephenc222) 170 | 171 | ## v3.5.0 172 | 173 | - added `horizontalLabelRotation` and `verticalLabelRotation` props to `LineChart` 174 | 175 | ## v3.4.0 176 | 177 | - added `chartConfig` `backgroundGradientFromOpacity` and `backgroundGradientToOpacity` 178 | 179 | ## 3.3.0 180 | 181 | - added `index` to `onDataPointClick` 182 | 183 | ## 3.2.0 184 | 185 | - added optional labels for ProgressChart 186 | 187 | ## 3.1.0 188 | 189 | - added withVerticalLabels and withHorizontalLabels to LineChart, BarChart and StackedBarChart 190 | 191 | ## 3.0.0 192 | 193 | - added typescript types 194 | -------------------------------------------------------------------------------- /src/ProgressChart.tsx: -------------------------------------------------------------------------------- 1 | import Pie from "paths-js/pie"; 2 | import React from "react"; 3 | import { View, ViewStyle } from "react-native"; 4 | import { G, Path, Rect, Svg, Text } from "react-native-svg"; 5 | 6 | import AbstractChart, { 7 | AbstractChartConfig, 8 | AbstractChartProps 9 | } from "./AbstractChart"; 10 | 11 | export type ProgressChartData = 12 | | Array 13 | | { labels?: Array; colors?: Array; data: Array }; 14 | 15 | export interface ProgressChartProps extends AbstractChartProps { 16 | data: ProgressChartData; 17 | width: number; 18 | height: number; 19 | center?: Array; 20 | absolute?: boolean; 21 | hasLegend?: boolean; 22 | style?: Partial; 23 | chartConfig?: AbstractChartConfig; 24 | hideLegend?: boolean; 25 | strokeWidth?: number; 26 | radius?: number; 27 | withCustomBarColorFromData?: boolean; 28 | } 29 | 30 | type ProgressChartState = {}; 31 | 32 | class ProgressChart extends AbstractChart< 33 | ProgressChartProps, 34 | ProgressChartState 35 | > { 36 | public static defaultProps = { style: {}, strokeWidth: 16, radius: 32 }; 37 | 38 | render() { 39 | let { 40 | width, 41 | height, 42 | style, 43 | data, 44 | hideLegend, 45 | strokeWidth, 46 | radius 47 | } = this.props; 48 | 49 | const { borderRadius = 0, margin = 0, marginRight = 0 } = style; 50 | 51 | if (Array.isArray(data)) { 52 | data = { 53 | data 54 | }; 55 | } 56 | 57 | const pies = data.data.map((pieData, i) => { 58 | const r = 59 | ((height / 2 - 32) / 60 | (Array.isArray(data) ? data.length : data.data.length)) * 61 | i + 62 | radius; 63 | 64 | return Pie({ 65 | r, 66 | R: r, 67 | center: [0, 0], 68 | data: [pieData, 1 - pieData], 69 | accessor(x: string) { 70 | return x; 71 | } 72 | }); 73 | }); 74 | 75 | const pieBackgrounds = data.data.map((pieData, i) => { 76 | const r = 77 | ((height / 2 - 32) / 78 | (Array.isArray(data) ? data.length : data.data.length)) * 79 | i + 80 | radius; 81 | return Pie({ 82 | r, 83 | R: r, 84 | center: [0, 0], 85 | data: [0.999, 0.001], 86 | accessor(x: string) { 87 | return x; 88 | } 89 | }); 90 | }); 91 | 92 | const withLabel = (i: number) => 93 | (data as any).labels && (data as any).labels[i]; 94 | 95 | const withColor = (i: number) => 96 | (data as any).colors && (data as any).colors[i]; 97 | 98 | const legend = !hideLegend && ( 99 | <> 100 | 101 | {pies.map((_, i) => { 102 | return ( 103 | 123 | ); 124 | })} 125 | 126 | 127 | {pies.map((_, i) => { 128 | return ( 129 | 141 | {withLabel(i) 142 | ? `${(data as any).labels[i]} ${Math.round( 143 | 100 * (data as any).data[i] 144 | )}%` 145 | : `${Math.round(100 * (data as any).data[i])}%`} 146 | 147 | ); 148 | })} 149 | 150 | 151 | ); 152 | 153 | return ( 154 | 162 | 166 | {this.renderDefs({ 167 | width: this.props.height, 168 | height: this.props.height, 169 | ...this.props.chartConfig 170 | })} 171 | 178 | 182 | 183 | {pieBackgrounds.map((pie, i) => { 184 | return ( 185 | 191 | ); 192 | })} 193 | 194 | 195 | {pies.map((pie, i) => { 196 | return ( 197 | 212 | ); 213 | })} 214 | 215 | {legend} 216 | 217 | 218 | 219 | ); 220 | } 221 | } 222 | 223 | export default ProgressChart; 224 | -------------------------------------------------------------------------------- /src/StackedBarChart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, ViewStyle } from "react-native"; 3 | import { G, Rect, Svg, Text } from "react-native-svg"; 4 | 5 | import AbstractChart, { 6 | AbstractChartConfig, 7 | AbstractChartProps, 8 | DEFAULT_X_LABELS_HEIGHT_PERCENTAGE 9 | } from "./AbstractChart"; 10 | 11 | export interface StackedBarChartData { 12 | labels: string[]; 13 | legend: string[]; 14 | data: number[][]; 15 | barColors: string[]; 16 | } 17 | 18 | export interface StackedBarChartProps extends AbstractChartProps { 19 | /** 20 | * E.g. 21 | * ```javascript 22 | * const data = { 23 | * labels: ["Test1", "Test2"], 24 | * legend: ["L1", "L2", "L3"], 25 | * data: [[60, 60, 60], [30, 30, 60]], 26 | * barColors: ["#dfe4ea", "#ced6e0", "#a4b0be"] 27 | * }; 28 | * ``` 29 | */ 30 | data: StackedBarChartData; 31 | width: number; 32 | height: number; 33 | chartConfig: AbstractChartConfig; 34 | hideLegend: boolean; 35 | style?: Partial; 36 | barPercentage?: number; 37 | decimalPlaces?: number; 38 | /** 39 | * Show vertical labels - default: True. 40 | */ 41 | withVerticalLabels?: boolean; 42 | /** 43 | * Show horizontal labels - default: True. 44 | */ 45 | withHorizontalLabels?: boolean; 46 | /** 47 | * The number of horizontal lines 48 | */ 49 | segments?: number; 50 | 51 | percentile?: boolean; 52 | 53 | /** 54 | * Percentage of the chart height, dedicated to vertical labels 55 | * (space below chart) 56 | */ 57 | verticalLabelsHeightPercentage?: number; 58 | 59 | formatYLabel?: (yLabel: string) => string; 60 | } 61 | 62 | type StackedBarChartState = {}; 63 | 64 | class StackedBarChart extends AbstractChart< 65 | StackedBarChartProps, 66 | StackedBarChartState 67 | > { 68 | getBarPercentage = () => { 69 | const { barPercentage = 1 } = this.props.chartConfig; 70 | return barPercentage; 71 | }; 72 | 73 | getBarRadius = (ret: string | any[], x: string | any[]) => { 74 | return this.props.chartConfig.barRadius && ret.length === x.length - 1 75 | ? this.props.chartConfig.barRadius 76 | : 0; 77 | }; 78 | 79 | renderBars = ({ 80 | data, 81 | width, 82 | height, 83 | paddingTop, 84 | paddingRight, 85 | border, 86 | colors, 87 | stackedBar = false, 88 | verticalLabelsHeightPercentage 89 | }: Pick< 90 | Omit, 91 | | "width" 92 | | "height" 93 | | "paddingRight" 94 | | "paddingTop" 95 | | "stackedBar" 96 | | "verticalLabelsHeightPercentage" 97 | > & { 98 | border: number; 99 | colors: string[]; 100 | data: number[][]; 101 | }) => 102 | data.map((x, i) => { 103 | const barWidth = 32 * this.getBarPercentage(); 104 | const ret = []; 105 | let h = 0; 106 | let st = paddingTop; 107 | 108 | let fac = 1; 109 | if (stackedBar) { 110 | fac = 0.7; 111 | } 112 | const sum = this.props.percentile ? x.reduce((a, b) => a + b, 0) : border; 113 | const barsAreaHeight = height * verticalLabelsHeightPercentage; 114 | for (let z = 0; z < x.length; z++) { 115 | h = barsAreaHeight * (x[z] / sum); 116 | const y = barsAreaHeight - h + st; 117 | const xC = 118 | (paddingRight + 119 | (i * (width - paddingRight)) / data.length + 120 | barWidth / 2) * 121 | fac; 122 | 123 | ret.push( 124 | 134 | ); 135 | 136 | if (!this.props.hideLegend) { 137 | ret.push( 138 | 15 ? y + 15 : y + 7} 143 | {...this.getPropsForLabels()} 144 | > 145 | {x[z]} 146 | 147 | ); 148 | } 149 | 150 | st -= h; 151 | } 152 | 153 | return ret; 154 | }); 155 | 156 | renderLegend = ({ 157 | legend, 158 | colors, 159 | width, 160 | height 161 | }: Pick & { 162 | legend: string[]; 163 | colors: string[]; 164 | }) => 165 | legend.map((x, i) => { 166 | return ( 167 | 168 | 177 | 182 | {x} 183 | 184 | 185 | ); 186 | }); 187 | 188 | render() { 189 | const paddingTop = 15; 190 | const paddingRight = 50; 191 | const barWidth = 32 * this.getBarPercentage(); 192 | 193 | const { 194 | width, 195 | height, 196 | style = {}, 197 | data, 198 | withHorizontalLabels = true, 199 | withVerticalLabels = true, 200 | segments = 4, 201 | decimalPlaces, 202 | percentile = false, 203 | verticalLabelsHeightPercentage = DEFAULT_X_LABELS_HEIGHT_PERCENTAGE, 204 | formatYLabel = (yLabel: string) => { 205 | return yLabel; 206 | }, 207 | hideLegend = false 208 | } = this.props; 209 | 210 | const { borderRadius = 0 } = style; 211 | const config = { 212 | width, 213 | height 214 | }; 215 | 216 | let border = 0; 217 | 218 | let max = 0; 219 | for (let i = 0; i < data.data.length; i++) { 220 | const actual = data.data[i].reduce((pv, cv) => pv + cv, 0); 221 | if (actual > max) { 222 | max = actual; 223 | } 224 | } 225 | 226 | if (percentile) { 227 | border = 100; 228 | } else { 229 | border = max; 230 | } 231 | 232 | const showLegend = !hideLegend && data.legend && data.legend.length != 0; 233 | const stackedBar = showLegend; 234 | 235 | return ( 236 | 237 | 238 | {this.renderDefs({ 239 | ...config, 240 | ...this.props.chartConfig 241 | })} 242 | 249 | 250 | {this.renderHorizontalLines({ 251 | ...config, 252 | count: segments, 253 | paddingTop, 254 | verticalLabelsHeightPercentage 255 | })} 256 | 257 | 258 | {withHorizontalLabels 259 | ? this.renderHorizontalLabels({ 260 | ...config, 261 | count: segments, 262 | data: [0, border], 263 | paddingTop, 264 | paddingRight, 265 | decimalPlaces, 266 | verticalLabelsHeightPercentage, 267 | formatYLabel 268 | }) 269 | : null} 270 | 271 | 272 | {withVerticalLabels 273 | ? this.renderVerticalLabels({ 274 | ...config, 275 | labels: data.labels, 276 | paddingRight: paddingRight + 28, 277 | stackedBar, 278 | paddingTop, 279 | horizontalOffset: barWidth, 280 | verticalLabelsHeightPercentage 281 | }) 282 | : null} 283 | 284 | 285 | {this.renderBars({ 286 | ...config, 287 | data: data.data, 288 | border, 289 | colors: this.props.data.barColors, 290 | paddingTop, 291 | paddingRight: paddingRight + 20, 292 | stackedBar, 293 | verticalLabelsHeightPercentage 294 | })} 295 | 296 | {showLegend && 297 | this.renderLegend({ 298 | ...config, 299 | legend: data.legend, 300 | colors: this.props.data.barColors 301 | })} 302 | 303 | 304 | ); 305 | } 306 | } 307 | 308 | export default StackedBarChart; 309 | -------------------------------------------------------------------------------- /src/BarChart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, ViewStyle } from "react-native"; 3 | import { 4 | Defs, 5 | G, 6 | LinearGradient, 7 | Rect, 8 | Stop, 9 | Svg, 10 | Text 11 | } from "react-native-svg"; 12 | 13 | import AbstractChart, { 14 | AbstractChartConfig, 15 | AbstractChartProps 16 | } from "./AbstractChart"; 17 | import { ChartData } from "./HelperTypes"; 18 | 19 | const barWidth = 32; 20 | 21 | export interface BarChartProps extends AbstractChartProps { 22 | data: ChartData; 23 | width: number; 24 | height: number; 25 | fromZero?: boolean; 26 | withInnerLines?: boolean; 27 | yAxisLabel: string; 28 | yAxisSuffix: string; 29 | chartConfig: AbstractChartConfig; 30 | style?: Partial; 31 | horizontalLabelRotation?: number; 32 | verticalLabelRotation?: number; 33 | /** 34 | * Show vertical labels - default: True. 35 | */ 36 | withVerticalLabels?: boolean; 37 | /** 38 | * Show horizontal labels - default: True. 39 | */ 40 | withHorizontalLabels?: boolean; 41 | /** 42 | * The number of horizontal lines 43 | */ 44 | segments?: number; 45 | showBarTops?: boolean; 46 | showValuesOnTopOfBars?: boolean; 47 | withCustomBarColorFromData?: boolean; 48 | flatColor?: boolean; 49 | 50 | } 51 | 52 | type BarChartState = {}; 53 | 54 | class BarChart extends AbstractChart { 55 | getBarPercentage = () => { 56 | const { barPercentage = 1 } = this.props.chartConfig; 57 | return barPercentage; 58 | }; 59 | 60 | renderBars = ({ 61 | data, 62 | width, 63 | height, 64 | paddingTop, 65 | paddingRight, 66 | barRadius, 67 | withCustomBarColorFromData 68 | }: Pick< 69 | Omit, 70 | "width" | "height" | "paddingRight" | "paddingTop" | "barRadius" 71 | > & { 72 | data: number[]; 73 | withCustomBarColorFromData: boolean; 74 | }) => { 75 | const baseHeight = this.calcBaseHeight(data, height); 76 | 77 | return data.map((x, i) => { 78 | const barHeight = this.calcHeight(x, data, height); 79 | const barWidth = 32 * this.getBarPercentage(); 80 | return ( 81 | 0 ? baseHeight - barHeight : baseHeight) / 4) * 3 + 90 | paddingTop 91 | } 92 | rx={barRadius} 93 | width={barWidth} 94 | height={(Math.abs(barHeight) / 4) * 3} 95 | fill={ 96 | withCustomBarColorFromData 97 | ? `url(#customColor_0_${i})` 98 | : "url(#fillShadowGradientFrom)" 99 | } 100 | /> 101 | ); 102 | }); 103 | }; 104 | 105 | renderBarTops = ({ 106 | data, 107 | width, 108 | height, 109 | paddingTop, 110 | paddingRight 111 | }: Pick< 112 | Omit, 113 | "width" | "height" | "paddingRight" | "paddingTop" 114 | > & { 115 | data: number[]; 116 | }) => { 117 | const baseHeight = this.calcBaseHeight(data, height); 118 | 119 | return data.map((x, i) => { 120 | const barHeight = this.calcHeight(x, data, height); 121 | const barWidth = 32 * this.getBarPercentage(); 122 | return ( 123 | 135 | ); 136 | }); 137 | }; 138 | 139 | renderColors = ({ 140 | data, 141 | flatColor 142 | }: Pick & { 143 | flatColor: boolean; 144 | }) => { 145 | return data.map((dataset, index) => ( 146 | 147 | {dataset.colors?.map((color, colorIndex) => { 148 | const highOpacityColor = color(1.0); 149 | const lowOpacityColor = color(0.1); 150 | 151 | return ( 152 | 160 | 161 | {flatColor ? ( 162 | 163 | ) : ( 164 | 165 | )} 166 | 167 | ); 168 | })} 169 | 170 | )); 171 | }; 172 | 173 | renderValuesOnTopOfBars = ({ 174 | data, 175 | width, 176 | height, 177 | paddingTop, 178 | paddingRight 179 | }: Pick< 180 | Omit, 181 | "width" | "height" | "paddingRight" | "paddingTop" 182 | > & { 183 | data: number[]; 184 | }) => { 185 | const baseHeight = this.calcBaseHeight(data, height); 186 | 187 | const renderLabel = (value: number) => { 188 | if(this.props.chartConfig.formatTopBarValue) { 189 | return this.props.chartConfig.formatTopBarValue(value) 190 | } 191 | else { 192 | return value 193 | } 194 | } 195 | return data.map((x, i) => { 196 | const barHeight = this.calcHeight(x, data, height); 197 | const barWidth = 32 * this.getBarPercentage(); 198 | return ( 199 | 212 | {renderLabel(data[i])} 213 | 214 | ); 215 | }); 216 | }; 217 | 218 | render() { 219 | const { 220 | width, 221 | height, 222 | data, 223 | style = {}, 224 | withHorizontalLabels = true, 225 | withVerticalLabels = true, 226 | verticalLabelRotation = 0, 227 | horizontalLabelRotation = 0, 228 | withInnerLines = true, 229 | showBarTops = true, 230 | withCustomBarColorFromData = false, 231 | showValuesOnTopOfBars = false, 232 | flatColor = false, 233 | segments = 4 234 | } = this.props; 235 | 236 | const { borderRadius = 0, paddingTop = 16, paddingRight = 64 } = style; 237 | 238 | const config = { 239 | width, 240 | height, 241 | verticalLabelRotation, 242 | horizontalLabelRotation, 243 | barRadius: 244 | (this.props.chartConfig && this.props.chartConfig.barRadius) || 0, 245 | decimalPlaces: 246 | (this.props.chartConfig && this.props.chartConfig.decimalPlaces) ?? 2, 247 | formatYLabel: 248 | (this.props.chartConfig && this.props.chartConfig.formatYLabel) || 249 | function (label) { 250 | return label; 251 | }, 252 | formatXLabel: 253 | (this.props.chartConfig && this.props.chartConfig.formatXLabel) || 254 | function (label) { 255 | return label; 256 | } 257 | }; 258 | 259 | return ( 260 | 261 | 262 | {this.renderDefs({ 263 | ...config, 264 | ...this.props.chartConfig 265 | })} 266 | {this.renderColors({ 267 | ...this.props.chartConfig, 268 | flatColor: flatColor, 269 | data: this.props.data.datasets 270 | })} 271 | 278 | 279 | {withInnerLines 280 | ? this.renderHorizontalLines({ 281 | ...config, 282 | count: segments, 283 | paddingTop 284 | }) 285 | : null} 286 | 287 | 288 | {withHorizontalLabels 289 | ? this.renderHorizontalLabels({ 290 | ...config, 291 | count: segments, 292 | data: data.datasets[0].data, 293 | paddingTop: paddingTop as number, 294 | paddingRight: paddingRight as number 295 | }) 296 | : null} 297 | 298 | 299 | {withVerticalLabels 300 | ? this.renderVerticalLabels({ 301 | ...config, 302 | labels: data.labels, 303 | paddingRight: paddingRight as number, 304 | paddingTop: paddingTop as number, 305 | horizontalOffset: barWidth * this.getBarPercentage() 306 | }) 307 | : null} 308 | 309 | 310 | {this.renderBars({ 311 | ...config, 312 | data: data.datasets[0].data, 313 | paddingTop: paddingTop as number, 314 | paddingRight: paddingRight as number, 315 | withCustomBarColorFromData: withCustomBarColorFromData 316 | })} 317 | 318 | 319 | {showValuesOnTopOfBars && 320 | this.renderValuesOnTopOfBars({ 321 | ...config, 322 | data: data.datasets[0].data, 323 | paddingTop: paddingTop as number, 324 | paddingRight: paddingRight as number 325 | })} 326 | 327 | 328 | {showBarTops && 329 | this.renderBarTops({ 330 | ...config, 331 | data: data.datasets[0].data, 332 | paddingTop: paddingTop as number, 333 | paddingRight: paddingRight as number 334 | })} 335 | 336 | 337 | 338 | ); 339 | } 340 | } 341 | 342 | export default BarChart; 343 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import "babel-polyfill"; 2 | 3 | import React from "react"; 4 | import { Dimensions, ScrollView, StatusBar, Text } from "react-native"; 5 | import FlashMessage, { showMessage } from "react-native-flash-message"; 6 | import ScrollableTabView from "react-native-scrollable-tab-view"; 7 | 8 | import { 9 | contributionData, 10 | data, 11 | pieChartData, 12 | progressChartData, 13 | stackedBarGraphData 14 | } from "./data"; 15 | import { 16 | BarChart, 17 | ContributionGraph, 18 | LineChart, 19 | PieChart, 20 | ProgressChart, 21 | StackedBarChart 22 | } from "./dist/"; 23 | 24 | // in Expo - swipe left to see the following styling, or create your own 25 | const chartConfigs = [ 26 | { 27 | backgroundColor: "#000000", 28 | backgroundGradientFrom: "#1E2923", 29 | backgroundGradientTo: "#08130D", 30 | color: (opacity = 1) => `rgba(26, 255, 146, ${opacity})`, 31 | style: { 32 | borderRadius: 16 33 | } 34 | }, 35 | { 36 | backgroundColor: "#022173", 37 | backgroundGradientFrom: "#022173", 38 | backgroundGradientTo: "#1b3fa0", 39 | color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, 40 | style: { 41 | borderRadius: 16 42 | }, 43 | propsForBackgroundLines: { 44 | strokeDasharray: "" // solid background lines with no dashes 45 | } 46 | }, 47 | { 48 | backgroundColor: "#ffffff", 49 | backgroundGradientFrom: "#ffffff", 50 | backgroundGradientTo: "#ffffff", 51 | color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})` 52 | }, 53 | { 54 | backgroundColor: "#ffffff", 55 | backgroundGradientFrom: "#ffffff", 56 | backgroundGradientTo: "#ffffff", 57 | color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})` 58 | }, 59 | { 60 | backgroundColor: "#26872a", 61 | backgroundGradientFrom: "#43a047", 62 | backgroundGradientTo: "#66bb6a", 63 | color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, 64 | style: { 65 | borderRadius: 16 66 | } 67 | }, 68 | { 69 | backgroundColor: "#000000", 70 | backgroundGradientFrom: "#000000", 71 | backgroundGradientTo: "#000000", 72 | color: (opacity = 1) => `rgba(${255}, ${255}, ${255}, ${opacity})` 73 | }, 74 | { 75 | backgroundColor: "#0091EA", 76 | backgroundGradientFrom: "#0091EA", 77 | backgroundGradientTo: "#0091EA", 78 | color: (opacity = 1) => `rgba(${255}, ${255}, ${255}, ${opacity})` 79 | }, 80 | { 81 | backgroundColor: "#e26a00", 82 | backgroundGradientFrom: "#fb8c00", 83 | backgroundGradientTo: "#ffa726", 84 | color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, 85 | style: { 86 | borderRadius: 16 87 | } 88 | }, 89 | { 90 | backgroundColor: "#b90602", 91 | backgroundGradientFrom: "#e53935", 92 | backgroundGradientTo: "#ef5350", 93 | color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, 94 | style: { 95 | borderRadius: 16 96 | } 97 | }, 98 | { 99 | backgroundColor: "#ff3e03", 100 | backgroundGradientFrom: "#ff3e03", 101 | backgroundGradientTo: "#ff3e03", 102 | color: (opacity = 1) => `rgba(${0}, ${0}, ${0}, ${opacity})` 103 | } 104 | ]; 105 | 106 | export default class App extends React.Component { 107 | renderTabBar() { 108 | return