├── .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 | [](https://badge.fury.io/js/react-native-activity-rings)
4 | [](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 |
--------------------------------------------------------------------------------