# Progress Ring Chart

> A customizable circular progress ring component with smooth animations and flexible styling.

**BNA UI** — a React Native / Expo component library.
These components render through `react-native`, not the DOM: there are no HTML
elements, no Tailwind classes and no Radix primitives. Source is copied into your
project and imported through `@/components/ui/*`, `@/components/charts/*`,
`@/hooks/*` and `@/theme/*`. Colours come from the `useColor` hook rather than
hardcoded hex; sizing tokens (`HEIGHT`, `FONT_SIZE`, `BORDER_RADIUS`, `CORNERS`)
come from `@/theme/globals`.

- Docs: https://ui.ahmedbna.com/docs/charts/progress-ring-chart
- Markdown: https://ui.ahmedbna.com/docs/charts/progress-ring-chart.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/progress-ring-chart.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/progress-ring-chart.json
- Install: `npx bna-ui add progress-ring-chart`
- npm dependencies: `react-native-reanimated`, `react-native-svg`, `react-native-worklets`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`, `text`
- Preview recording: https://demo.ahmedbna.com/0371-progress-ring-chart-demo.MOV

---

**Example:** A circular progress ring with smooth animations

```tsx
// components/demo/charts/progress-ring-chart/progress-ring-chart-demo.tsx
import { ProgressRingChart } from '@/components/charts/progress-ring-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

export function ProgressRingChartDemo() {
  return (
    <ChartContainer
      title='Goal Progress'
      description='Track your progress towards your goals'
    >
      <ProgressRingChart
        progress={75}
        size={120}
        strokeWidth={8}
        config={{
          animated: true,
          duration: 1000,
          gradient: false,
        }}
        showLabel={true}
        label='Completion Rate'
      />
    </ChartContainer>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add progress-ring-chart
```

### Manual

**1.** Install the required dependencies.

```bash
npm install react-native-svg react-native-reanimated react-native-worklets
```

**2.** Copy and paste the following code into your project.

```tsx
// components/charts/progress-ring-chart.tsx
import { Text } from '@/components/ui/text';
import { useColor } from '@/hooks/useColor';
import { useEffect, useId } from 'react';
import { View, ViewStyle } from 'react-native';
import Animated, {
  useAnimatedProps,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import Svg, {
  Circle,
  Defs,
  LinearGradient,
  Stop,
  Text as SvgText,
} from 'react-native-svg';

// Animated SVG Components
const AnimatedCircle = Animated.createAnimatedComponent(Circle);

interface ChartConfig {
  animated?: boolean;
  duration?: number;
  gradient?: boolean;
}

type Props = {
  progress: number; // 0-100
  size?: number;
  strokeWidth?: number;
  config?: ChartConfig;
  style?: ViewStyle;
  showLabel?: boolean;
  label?: string;
  centerText?: string;
};

export const ProgressRingChart = ({
  progress,
  size = 120,
  strokeWidth = 8,
  config = {},
  style,
  showLabel = true,
  label,
  centerText,
}: Props) => {
  const { animated = true, duration = 1000, gradient = false } = config;

  const primaryColor = useColor('primary');
  const mutedColor = useColor('mutedForeground');

  const animationProgress = useSharedValue(0);
  // Namespaced so multiple same-config rings on one screen don't collide on
  // a shared literal gradient id.
  const gradientId = `progressGradient-${useId()}`;

  const clampedProgress = Math.max(0, Math.min(100, progress));

  useEffect(() => {
    if (animated) {
      animationProgress.value = withTiming(1, { duration });
    } else {
      animationProgress.value = 1;
    }
  }, [clampedProgress, animated, duration]);

  const radius = (size - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const center = size / 2;

  const progressAnimatedProps = useAnimatedProps(() => {
    const animatedProgress = animationProgress.value * (clampedProgress / 100);
    const strokeDashoffset = circumference - animatedProgress * circumference;

    return {
      strokeDashoffset,
    };
  });

  return (
    <View
      style={[{ alignItems: 'center' }, style]}
      accessible
      accessibilityRole='progressbar'
      accessibilityValue={{ min: 0, max: 100, now: clampedProgress }}
      accessibilityLabel={label}
    >
      {showLabel && label && (
        <Text
          variant='caption'
          style={{ color: mutedColor, fontWeight: '600', marginBottom: 4 }}
        >
          {label}
        </Text>
      )}

      <Svg width={size} height={size}>
        <Defs>
          {gradient && (
            <LinearGradient id={gradientId} x1='0%' y1='0%' x2='100%' y2='0%'>
              <Stop offset='0%' stopColor={primaryColor} stopOpacity='0.3' />
              <Stop offset='100%' stopColor={primaryColor} stopOpacity='1' />
            </LinearGradient>
          )}
        </Defs>

        {/* Background circle */}
        <Circle
          cx={center}
          cy={center}
          r={radius}
          stroke={mutedColor}
          strokeWidth={strokeWidth}
          fill='none'
          opacity={0.2}
        />

        {/* Progress circle */}
        <AnimatedCircle
          cx={center}
          cy={center}
          r={radius}
          stroke={gradient ? `url(#${gradientId})` : primaryColor}
          strokeWidth={strokeWidth}
          fill='none'
          strokeLinecap='round'
          strokeDasharray={circumference}
          transform={`rotate(-90 ${center} ${center})`}
          animatedProps={progressAnimatedProps}
        />

        {/* Center text */}
        {centerText && (
          <SvgText
            x={center}
            y={center + 6}
            textAnchor='middle'
            fontSize={18}
            fill={primaryColor}
            fontWeight='bold'
          >
            {centerText}
          </SvgText>
        )}

        {/* Progress percentage */}
        {!centerText && (
          <SvgText
            x={center}
            y={center + 6}
            textAnchor='middle'
            fontSize={16}
            fill={primaryColor}
            fontWeight='600'
          >
            {Math.round(clampedProgress)}%
          </SvgText>
        )}
      </Svg>
    </View>
  );
};
```

**3.** Update the import paths to match your project setup.

## Usage

```tsx
import { ProgressRingChart } from '@/components/charts/progress-ring-chart';
```

```tsx
<ProgressRingChart
  progress={75}
  size={120}
  strokeWidth={8}
  config={{
    animated: true,
    duration: 1000,
    gradient: true,
  }}
/>
```

## Examples

#### Basic Progress Ring

**Example:** A circular progress ring with smooth animations

```tsx
// components/demo/charts/progress-ring-chart/progress-ring-chart-demo.tsx
import { ProgressRingChart } from '@/components/charts/progress-ring-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

export function ProgressRingChartDemo() {
  return (
    <ChartContainer
      title='Goal Progress'
      description='Track your progress towards your goals'
    >
      <ProgressRingChart
        progress={75}
        size={120}
        strokeWidth={8}
        config={{
          animated: true,
          duration: 1000,
          gradient: false,
        }}
        showLabel={true}
        label='Completion Rate'
      />
    </ChartContainer>
  );
}
```

#### Sample Progress Ring

**Example:** A sample progress ring chart

```tsx
// components/demo/charts/progress-ring-chart/progress-ring-chart-sample.tsx
import { ProgressRingChart } from '@/components/charts/progress-ring-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

export function ProgressRingChartSample() {
  return (
    <ChartContainer
      title='Daily Steps'
      description='Track your daily step count'
    >
      <ProgressRingChart
        progress={68}
        size={140}
        strokeWidth={10}
        config={{
          animated: true,
          duration: 1500,
          gradient: false,
        }}
        showLabel={true}
        label='Daily Goal'
        centerText='6,800'
      />
    </ChartContainer>
  );
}
```

#### Styled Progress Ring

**Example:** A customized progress ring with gradient and custom styling

```tsx
// components/demo/charts/progress-ring-chart/progress-ring-chart-styled.tsx
import { ProgressRingChart } from '@/components/charts/progress-ring-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

export function ProgressRingChartStyled() {
  return (
    <ChartContainer
      title='Project Progress'
      description='Development milestone completion with gradient styling'
    >
      <ProgressRingChart
        progress={92}
        size={160}
        strokeWidth={12}
        config={{
          animated: true,
          duration: 2000,
          gradient: true,
        }}
        showLabel={true}
        label='Sprint Progress'
        centerText='92%'
      />
    </ChartContainer>
  );
}
```

#### Large Progress Ring

**Example:** A large progress ring with center text

```tsx
// components/demo/charts/progress-ring-chart/progress-ring-chart-large.tsx
import { ProgressRingChart } from '@/components/charts/progress-ring-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

export function ProgressRingChartLarge() {
  return (
    <ChartContainer
      title='Annual Revenue Target'
      description='Track progress towards annual revenue goals'
    >
      <ProgressRingChart
        progress={87}
        size={200}
        strokeWidth={16}
        config={{
          animated: true,
          duration: 2500,
          gradient: true,
        }}
        showLabel={true}
        label='Revenue Target'
        centerText='$2.6M'
      />
    </ChartContainer>
  );
}
```

## API Reference

### ProgressRingChart

A customizable circular progress ring component with smooth animations and flexible styling. Perfect for displaying progress, completion rates, or any percentage-based data.

| Prop          | Type          | Default | Description                                |
| ------------- | ------------- | ------- | ------------------------------------------ |
| `progress`    | `number`      | -       | Progress value from 0 to 100.              |
| `size`        | `number`      | `120`   | Size of the progress ring in pixels.       |
| `strokeWidth` | `number`      | `8`     | Width of the progress ring stroke.         |
| `config`      | `ChartConfig` | `{}`    | Configuration object for chart appearance. |
| `style`       | `ViewStyle`   | -       | Additional styles to apply to the chart.   |
| `showLabel`   | `boolean`     | `true`  | Whether to show the label above the ring.  |
| `label`       | `string`      | -       | Label text to display above the ring.      |
| `centerText`  | `string`      | -       | Custom text to display in the center.      |

### ChartConfig

| Prop       | Type      | Default | Description                                  |
| ---------- | --------- | ------- | -------------------------------------------- |
| `animated` | `boolean` | `true`  | Whether to animate the chart on load.        |
| `duration` | `number`  | `1000`  | Animation duration in milliseconds.          |
| `gradient` | `boolean` | `false` | Whether to use gradient colors for the ring. |

## Features

- **Circular Design**: Clean circular progress indicator
- **Smooth Animations**: Built-in animations using React Native Reanimated
- **Gradient Support**: Optional gradient colors for enhanced visual appeal
- **Center Text**: Display custom text or percentage in the center
- **Label Support**: Optional label above the progress ring
- **Theme Integration**: Uses theme colors for consistent styling
- **Responsive**: Automatically adapts to different sizes

## Use Cases

Progress ring charts are particularly effective for:

- **Progress Tracking**: Displaying completion rates, loading progress
- **Performance Metrics**: Showing KPIs, scores, or achievements
- **Goal Tracking**: Visualizing progress towards targets
- **Health Metrics**: Displaying fitness goals, step counters
- **Dashboard Widgets**: Compact progress indicators for dashboards

## Design Considerations

The circular design of the ProgressRingChart makes it ideal for:

- **Compact Displays**: Efficient use of space with circular design
- **Dashboard Widgets**: Perfect for small dashboard components
- **Mobile Interfaces**: Works well on small screens
- **Visual Hierarchy**: Draws attention to important metrics
- **Progress Visualization**: Intuitive representation of completion

## Accessibility

The ProgressRingChart component includes several accessibility features:

- Semantic SVG structure for screen readers
- Proper contrast ratios for visual elements
- Text labels for progress values
- Supports dynamic text sizing
- Keyboard navigation support

## Performance

The component is optimized for performance:

- Uses React Native Reanimated for smooth 60fps animations
- Efficient SVG rendering with minimal re-renders
- Automatic cleanup of animation values
- Lightweight circular path calculations

## Styling

The component integrates with your theme system:

- Uses `primary` color from theme for default ring color
- Uses `mutedForeground` color for labels and text
- Supports gradient colors for enhanced visual appeal
- Rounded stroke caps for modern appearance

## Animation

The chart features smooth entry animations:

- Ring animates from 0% to target progress
- Configurable animation duration
- Can be disabled for instant rendering
- Uses React Native Reanimated for optimal performance

## Customization

The progress ring can be customized in various ways:

- **Size**: Adjust the overall size of the ring
- **Stroke Width**: Control the thickness of the ring
- **Colors**: Use theme colors or custom gradient
- **Center Content**: Display percentage or custom text
- **Labels**: Add descriptive labels above the ring
