├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── docs └── screenshot.png ├── index.d.ts ├── index.ts ├── package.json ├── prettier.config.js ├── src ├── ActivityRings.tsx ├── ActivityRingsLegend.tsx ├── PieFactory.ts ├── Rings.tsx └── Themes.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .expo 2 | .DS_Store 3 | .idea 4 | .vscode 5 | 6 | node_modules 7 | dist 8 | 9 | npm-debug.* 10 | yarn-error.* 11 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | src -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Martin Sagastume 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![npm version](https://badge.fury.io/js/react-native-activity-rings.svg)](https://badge.fury.io/js/react-native-activity-rings) 4 | [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 5 | 6 | 7 | A library that provides customizable ring visualization of data, and you can also enable legends for chart representation. 8 |

9 | 10 |

11 | 12 | 13 | --- 14 | 15 | ## Installation 16 | 17 | ``` 18 | yarn add react-native-activity-rings 19 | ``` 20 | 21 | ## Example 22 | 23 | ```javascript 24 | import ActivityRings from "react-native-activity-rings"; 25 | 26 | const BasicExample = () => { 27 | 28 | const activityData = [ 29 | { value: 0.8 }, 30 | { value: 0.6 }, 31 | { value: 0.2 } 32 | ]; 33 | 34 | const activityConfig = { 35 | width: 150, 36 | height: 150 37 | }; 38 | 39 | return ( 40 | 41 | 42 | 43 | ); 44 | } 45 | ``` 46 | 47 | 48 | ## Activity Data 49 | 50 | Define an array of objects with the data for each ring: 51 | 52 | ```javascript 53 | const activityData = [ 54 | { 55 | value: 0.8, // ring will use color from theme 56 | }, 57 | { 58 | label: "ACTIVITY", 59 | value: 0.6, 60 | color: "#cb5f18", 61 | }, 62 | { 63 | label: "RINGS", 64 | value: 0.2, 65 | color: "#86040f", 66 | backgroundColor: "#cccccc" 67 | } 68 | ]; 69 | ``` 70 | 71 | | Property | Type | Description | 72 | | ---------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | 73 | | value | Number | The value used as percentage to render for each ring. value of 1 represents 100% so this means 0.2 represents 20%. Values > 1 will not be considered. | 74 | | label? | string | Label will be used when enabling legend flag alongside the percentage value. | 75 | | color? | string | Hex representation of the color code for the ring. Only compatible with hex values (for now). | 76 | | backgroundColor? | string | Hex representation of the background color code for the ring. Only compatible with hex values (for now). The background color will get 30% opacity. | 77 | 78 | 79 | ## Configuration 80 | 81 | Config options for the ring pie: 82 | 83 | ```javascript 84 | const activityConfig = { 85 | width: 150, 86 | height: 150, 87 | radius: 32, 88 | ringSize: 14, 89 | } 90 | ``` 91 | 92 | | Property | Type | Description | 93 | | -------- | ------ | ------------------------------------------ | 94 | | width | Number | The width of the activity ring component. | 95 | | height | Number | The height of the activity ring component. | 96 | | radius? | Number | Defines the radius of the complete pie. | 97 | | ringSize?| Number | Defines the size of each ring in px. | 98 | 99 | ## Legend 100 | Legend is disabled by default. 101 | 102 | Enable legend on the right side of the rings, this is ideal for chart representation. 103 | Notice that legend will use the *label* value you defined for every ring inside the data object. 104 | 105 | ```typescript 106 | 107 | ``` 108 | 109 | ## Themes 110 | 111 | By default this component comes with `Dark` theme and will work best of course with dark backgrounds. The library also provides a theme for light backgrounds and yes it's pretty obviously named `Light` theme. 112 | 113 | ```typescript 114 | 115 | ``` 116 | 117 | *Please notice that dark is the default theme so you don't actually need to specify it.* 118 | 119 | ```typescript 120 | 121 | ``` 122 | 123 | 124 | ## Not yet supported 125 | 126 | 1. Visualize more than 100 percentage on a ring like the Apple Watch does. 127 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodehunter/react-native-activity-rings/ae3785752914464b5075c7d04ddf1352d681922e/docs/screenshot.png -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import ActivityRings, { ActivityRingsConfig, ActivityRingData, ActivityRingsData } from "./src/ActivityRings"; 2 | 3 | export { ActivityRings, ActivityRingsConfig, ActivityRingData, ActivityRingsData }; 4 | 5 | export default ActivityRings; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-activity-rings", 3 | "version": "1.1.2", 4 | "description": "Activity Rings & Progress Chart for React Native.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist/" 9 | ], 10 | "scripts": { 11 | "dev": "tsc --watch", 12 | "build": "tsc", 13 | "prepare": "tsc && echo 'Finished building NPM package'", 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "prettier": "./node_modules/.bin/prettier --write" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/thecodehunter/react-native-activity-rings.git" 20 | }, 21 | "author": "martin@sagas.tech", 22 | "license": "MIT", 23 | "peerDependencies": { 24 | "react": "> 16.7.0", 25 | "react-native": ">= 0.50.0", 26 | "react-native-svg": "^12.1.0" 27 | }, 28 | "dependencies": { 29 | "paths-js": "^0.4.10" 30 | }, 31 | "devDependencies": { 32 | "@babel/core": "~7.9.0", 33 | "@types/node": "14.0.13", 34 | "@types/react": "^16.9.36", 35 | "@types/react-native": "~0.61.23", 36 | "husky": "^4.2.1", 37 | "lint-staged": "^10.0.7", 38 | "prettier": "^1.19.1", 39 | "react": "^16.13.1", 40 | "react-dom": "^16.13.1", 41 | "react-native": "0.61.5", 42 | "typescript": "^3.9.5", 43 | "react-native-svg": "^12.1.0" 44 | }, 45 | "husky": { 46 | "hooks": { 47 | "pre-commit": "lint-staged" 48 | } 49 | }, 50 | "lint-staged": { 51 | "*.{ts,tsx,js,css}": [ 52 | "yarn prettier" 53 | ] 54 | }, 55 | "bugs": { 56 | "url": "https://github.com/thecodehunter/react-native-activity-rings/issues" 57 | }, 58 | "homepage": "https://github.com/thecodehunter/react-native-activity-rings#readme" 59 | } 60 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | parser: "typescript", 4 | jsxBracketSameLine: true, 5 | arrowParens: "always" 6 | }; 7 | -------------------------------------------------------------------------------- /src/ActivityRings.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Theme, THEMES } from "./Themes"; 3 | import ActivityLegend from "./ActivityRingsLegend"; 4 | import Rings from "./Rings"; 5 | import PieFactory from "./PieFactory"; 6 | import { Svg, G } from "react-native-svg"; 7 | import { View, StyleSheet } from "react-native"; 8 | 9 | export type ActivityRingsData = ActivityRingData[]; 10 | 11 | export type ActivityRingData = { 12 | value: number; 13 | label?: string; 14 | color?: string; 15 | backgroundColor?: string; 16 | }; 17 | 18 | export type ActivityRingsConfig = { 19 | width: number; 20 | height: number; 21 | radius?: number; 22 | ringSize?: number; 23 | }; 24 | 25 | type ActivityRingsProps = { 26 | data: ActivityRingData[]; 27 | config: ActivityRingsConfig; 28 | legend?: boolean; 29 | theme?: Theme; 30 | }; 31 | 32 | const defaultCfg: ActivityRingsConfig = { 33 | width: 150, 34 | height: 150, 35 | ringSize: 14, 36 | radius: 32 37 | }; 38 | 39 | const ActivityRingsBase = ({ data, config, theme, legend }: ActivityRingsProps) => { 40 | const cfg: ActivityRingsConfig = { ...defaultCfg, ...config }; 41 | const backPie = PieFactory.create(data, cfg.height, cfg.radius, [0.999, 0.001]); 42 | const frontPie = PieFactory.create(data, cfg.height, cfg.radius); 43 | const selectedTheme = THEMES[theme || "dark"]; 44 | 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {legend && } 56 | 57 | ); 58 | }; 59 | 60 | ActivityRingsBase.defaultProps = { 61 | data: [], 62 | theme: "dark", 63 | legend: false 64 | }; 65 | 66 | const styles = StyleSheet.create({ 67 | layout: { 68 | flexDirection: "row", 69 | alignItems: "center", 70 | justifyContent: "center" 71 | } 72 | }); 73 | 74 | const ActivityRings = React.memo(ActivityRingsBase); 75 | export default ActivityRings; 76 | -------------------------------------------------------------------------------- /src/ActivityRingsLegend.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Theme, THEMES } from "./Themes"; 3 | import { ActivityRingsData, ActivityRingData } from "./ActivityRings"; 4 | import { View, Text, StyleSheet } from "react-native"; 5 | 6 | interface ActivityLegendsProps { 7 | data: ActivityRingsData; 8 | theme?: Theme; 9 | } 10 | 11 | const ActivityLegendBase = ({ data, theme }: ActivityLegendsProps) => { 12 | const selectedTheme = THEMES[theme || "dark"]; 13 | const textStyle = { 14 | ...styles.text, 15 | color: selectedTheme.LegendColorPercentage 16 | }; 17 | return ( 18 | 19 | {data.map((ring: ActivityRingData, idx: number) => { 20 | const bulletColor = ring.color || selectedTheme.RingColors[idx]; 21 | const bulletStyle = { ...styles.bullets, backgroundColor: bulletColor }; 22 | return ( 23 | 24 | 25 | 26 | {Math.round(ring.value * 100)}% {ring.label} 27 | 28 | 29 | ); 30 | })} 31 | 32 | ); 33 | }; 34 | 35 | const styles = StyleSheet.create({ 36 | container: { 37 | marginLeft: 10 38 | }, 39 | row: { 40 | display: "flex", 41 | flexDirection: "row", 42 | alignItems: "center" 43 | }, 44 | bullets: { 45 | width: 16, 46 | height: 16, 47 | borderRadius: 8 48 | }, 49 | text: { 50 | padding: 7, 51 | margin: 0 52 | } 53 | }); 54 | 55 | const ActivityLegend = React.memo(ActivityLegendBase); 56 | export default ActivityLegend; 57 | -------------------------------------------------------------------------------- /src/PieFactory.ts: -------------------------------------------------------------------------------- 1 | import { ActivityRingData } from "./ActivityRings"; 2 | const Pie = require("paths-js/pie"); 3 | 4 | const PieFactory = { 5 | create: (data: ActivityRingData[], height: number, rad: number, fill?: number[]) => { 6 | return data.map((ring: ActivityRingData, idx: number) => { 7 | const pieData = fill || [ring.value, 1 - ring.value]; 8 | const r = ((height / 2 - rad) / data.length) * (data.length - idx - 1) + rad; 9 | return Pie({ 10 | r, 11 | R: r, 12 | data: pieData, 13 | center: [0, 0], 14 | accessor(x: any) { 15 | return x; 16 | } 17 | }); 18 | }); 19 | } 20 | }; 21 | 22 | export default PieFactory; 23 | -------------------------------------------------------------------------------- /src/Rings.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ActivityRingsData } from "./ActivityRings"; 3 | import { ThemeColors } from "./Themes"; 4 | import { G, Path } from "react-native-svg"; 5 | 6 | type Pie = { 7 | // pie-js does not support TS 8 | curves: any[]; 9 | }; 10 | 11 | type RingsProps = { 12 | size: number; 13 | pie: Pie[]; 14 | data: ActivityRingsData; 15 | theme: ThemeColors; 16 | opacity: boolean; 17 | }; 18 | 19 | const Rings = ({ size, pie, data, theme, opacity }: RingsProps) => { 20 | const alpha = opacity ? "33" : ""; 21 | return ( 22 | 23 | {pie.map((ring: Pie, idx: number) => { 24 | const dataObj = data[idx]; 25 | const color = opacity ? dataObj.backgroundColor || dataObj.color : dataObj.color; 26 | const ringColor = color || theme.RingColors[idx]; 27 | // check decimals between 0 and 1 28 | if (!(dataObj.value > 0 && dataObj.value <= 1)) { 29 | return null; 30 | } 31 | return ( 32 | 40 | ); 41 | })} 42 | 43 | ); 44 | }; 45 | 46 | export default Rings; 47 | -------------------------------------------------------------------------------- /src/Themes.ts: -------------------------------------------------------------------------------- 1 | export enum Colors { 2 | White = "#ffffff", 3 | LightGray = "#cccccc", 4 | Gray = "#323232", 5 | // Light Theme 6 | Green = "#50eba9", 7 | Red = "#E02020", 8 | Canary = "#FAEB3F", 9 | // Dark Theme 10 | Move = "#54f0f7", 11 | Exercise = "#c1ff00", 12 | Stand = "#ef135f" 13 | } 14 | 15 | export type Theme = "dark" | "light"; 16 | 17 | export interface ThemeColors { 18 | LegendColorPercentage: string; 19 | LegendColor: string; 20 | RingColors: string[]; 21 | } 22 | 23 | export const THEMES: { dark: ThemeColors; light: ThemeColors } = { 24 | dark: { 25 | LegendColorPercentage: Colors.White, 26 | LegendColor: Colors.LightGray, 27 | RingColors: [Colors.Stand, Colors.Exercise, Colors.Move] 28 | }, 29 | light: { 30 | LegendColorPercentage: Colors.Gray, 31 | LegendColor: Colors.LightGray, 32 | RingColors: [Colors.Green, Colors.Canary, Colors.Red] 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es6", 5 | "jsx": "react-native", 6 | "lib": ["dom", "esnext"], 7 | "moduleResolution": "node", 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true 10 | }, 11 | "exclude": [ 12 | "node_modules", 13 | "**/*.spec.ts" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------