rn-dashed-circular-indicator
Version:
A simple fully customizable Dashed Circular indicator for React Native
158 lines (133 loc) • 4.84 kB
JavaScript
import React from "react";
import PropTypes from "prop-types";
import { Path, Svg, Circle, Text } from "react-native-svg";
import { StyleSheet, View } from "react-native";
/* ------------------------ CONSTANTS ------------------------ */
const DEFAULT_RADIUS = 60;
const MAX_VALUE = 10;
const Direction = {
CLOCKSWISE: -1,
ANTI_CLOCKWISE: 1,
};
export const DashedCircularIndicator = React.memo(
({
maxValue = MAX_VALUE,
selectedValue = 0,
radius = DEFAULT_RADIUS,
strokeWidth = radius / 10,
label = "",
activeStrokeColor = "#05a168",
inactiveStrokeColor = "#ddd",
backgroundColor = "#fff",
textColor = '#000',
labelFontSize = Math.floor(radius / 3),
valueFontSize = Math.floor(radius / 2.5),
withGradient = false,
anticlockwise = false,
initialAngularDisplacement = 0,
containerStyle
}) => {
// ---- PIE calculation funciton --------
const generatePie = (value) => {
const x = radius - Math.cos((2 * Math.PI) / (100 / value)) * radius;
const y = radius + Math.sin((2 * Math.PI) / (100 / value)) * radius;
const long = value <= 50 ? 0 : 1;
const d = `M${radius},${radius} L${radius},${0}, A${radius},${radius} 0 ${long},1 ${y},${x} Z`;
return d;
};
//---- PIE Area calculation --------
const calculatePieValue = (numberOfBars) => {
const angle = 360 / numberOfBars;
const pieValue = Math.floor(angle / 4);
return pieValue < 1 ? 1 : Math.floor(angle / 4);
};
// ---- PIE render funciton --------
const renderPie = (i) => {
const DIRECTION = anticlockwise ? Direction.ANTI_CLOCKWISE : Direction.CLOCKSWISE;
//Rotation Calculation
const primaryRotationAngle = (maxValue - 1) * (360 / maxValue);
const rotationAngle =
DIRECTION * initialAngularDisplacement +
-1 * DIRECTION * primaryRotationAngle +
i * DIRECTION * primaryRotationAngle;
const rotationTransformation = `rotate(${rotationAngle}, ${radius}, ${radius})`;
const pieValue = calculatePieValue(maxValue);
const dValue = generatePie(pieValue);
const activeColor = withGradient
? shadeColor(activeStrokeColor, (i + 1) * maxValue/50)
: activeStrokeColor;
const fillColor = i <= selectedValue ? activeColor : inactiveStrokeColor;
return <Path key={i} d={dValue} fill={fillColor} transform={rotationTransformation} />;
};
// ---- Creates a circle by combining the Pie(s) --------
const renderOuterCircle = () => {
return [...Array(maxValue + 1)].map((e, i) => renderPie(i));
};
const labelView = (
<Text
fill={textColor}
fontSize={labelFontSize}
x={radius}
y={radius + labelFontSize}
textAnchor='middle'
>
{label}
</Text>
);
const textValueY = !!label ? radius : radius + valueFontSize / 3;
// -------- MAIN Render --------
return (
<View style={[styles.container, containerStyle]}>
<Svg width={radius * 2} height={radius * 2}>
{renderOuterCircle()}
{/* This is the overlay circle */}
<Circle r={radius - strokeWidth} cx={radius} cy={radius} fill={backgroundColor} />
<Text
fill={textColor}
fontSize={valueFontSize}
fontWeight='bold'
x={radius}
y={textValueY}
textAnchor='middle'
>
{selectedValue}
</Text>
{!!label.length && labelView}
</Svg>
</View>
);
}
);
DashedCircularIndicator.propTypes = {
maxValue: PropTypes.number,
selectedValue: PropTypes.number,
radius: PropTypes.number,
strokeWidth: PropTypes.number,
label: PropTypes.string,
activeStrokeColor: PropTypes.string,
inactiveStrokeColor: PropTypes.string,
labelFontSize: PropTypes.number,
valueFontSize: PropTypes.number,
withGradient: PropTypes.bool,
anticlockwise: PropTypes.bool,
initialAngularDisplacement: PropTypes.number,
};
const styles = StyleSheet.create({
container: { padding: 10 },
});
// ------------------------ UTILITY FUNCTION ------------------------
function shadeColor(color, percent) {
var R = parseInt(color.substring(1, 3), 16);
var G = parseInt(color.substring(3, 5), 16);
var B = parseInt(color.substring(5, 7), 16);
R = parseInt((R * (100 + percent)) / 100);
G = parseInt((G * (100 + percent)) / 100);
B = parseInt((B * (100 + percent)) / 100);
R = R < 255 ? R : 255;
G = G < 255 ? G : 255;
B = B < 255 ? B : 255;
var RR = R.toString(16).length === 1 ? "0" + R.toString(16) : R.toString(16);
var GG = G.toString(16).length === 1 ? "0" + G.toString(16) : G.toString(16);
var BB = B.toString(16).length === 1 ? "0" + B.toString(16) : B.toString(16);
return "#" + RR + GG + BB;
}