# Radial Bar Chart

> A customizable radial bar chart component with smooth animations, gradient support, and center value display.

**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/radial-bar-chart
- Markdown: https://ui.ahmedbna.com/docs/charts/radial-bar-chart.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/radial-bar-chart.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/radial-bar-chart.json
- Install: `npx bna-ui add radial-bar-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/0379-radial-bar-chart-demo.MOV

---

**Example:** A radial bar chart with smooth animations and center totals

```tsx
// components/demo/charts/radial-bar-chart/radial-bar-chart-demo.tsx
import { RadialBarChart } from '@/components/charts/radial-bar-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

const sampleData = [
  { label: 'Sales', value: 120 },
  { label: 'Marketing', value: 98 },
  { label: 'Support', value: 86 },
  { label: 'Development', value: 140 },
  { label: 'Design', value: 75 },
];

export function RadialBarChartDemo() {
  return (
    <ChartContainer
      title='Department Performance'
      description='Quarterly performance metrics by department'
    >
      <RadialBarChart
        data={sampleData}
        config={{
          animated: true,
          duration: 1000,
          gradient: false,
        }}
      />
    </ChartContainer>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add radial-bar-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/radial-bar-chart.tsx
import { Text } from '@/components/ui/text';
import { useColor } from '@/hooks/useColor';
import React, { useEffect, useId, useState } from 'react';
import { LayoutChangeEvent, View, ViewStyle } from 'react-native';
import Animated, {
  SharedValue,
  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);

type AnimatedRadialBarProps = {
  cx: number;
  cy: number;
  r: number;
  stroke: string;
  strokeWidth: number;
  circumference: number;
  progressRatio: number;
  transform: string;
  animationProgress: SharedValue<number>;
};

// Per-item hook must live in its own mounted subcomponent, not in the
// parent's .map() body — calling useAnimatedProps per loop iteration
// violates Rules of Hooks the moment data.length changes.
const AnimatedRadialBar = React.memo(
  ({
    cx,
    cy,
    r,
    stroke,
    strokeWidth,
    circumference,
    progressRatio,
    transform,
    animationProgress,
  }: AnimatedRadialBarProps) => {
    const circleAnimatedProps = useAnimatedProps(() => {
      const animatedProgress = animationProgress.value * progressRatio;
      const strokeDashoffset = circumference - animatedProgress * circumference;

      return { strokeDashoffset };
    });

    return (
      <AnimatedCircle
        cx={cx}
        cy={cy}
        r={r}
        stroke={stroke}
        strokeWidth={strokeWidth}
        fill='none'
        strokeLinecap='round'
        strokeDasharray={circumference}
        transform={transform}
        animatedProps={circleAnimatedProps}
      />
    );
  }
);

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

interface ChartDataPoint {
  label: string;
  value: number;
  color?: string;
}

type Props = {
  data: ChartDataPoint[];
  config?: ChartConfig;
  style?: ViewStyle;
};

export const RadialBarChart = ({ data, config = {}, style }: Props) => {
  const [containerSize, setContainerSize] = useState(200);

  const {
    padding = 20,
    animated = true,
    duration = 1000,
    gradient = false,
  } = config;

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

  const animationProgress = useSharedValue(0);
  // Namespaced so multiple same-config charts on one screen don't collide
  // on shared literal gradient ids.
  const gradientIdPrefix = useId();

  const handleLayout = (event: LayoutChangeEvent) => {
    const { width, height } = event.nativeEvent.layout;
    const size = Math.min(width, height);
    if (size > 0) {
      setContainerSize(size);
    }
  };

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

  if (!data.length) return null;

  const maxValue = Math.max(...data.map((d) => d.value));
  if (maxValue === 0) return null;

  const size = containerSize || 200;
  const center = size / 2;
  const maxRadius = (size - padding * 2) / 2;
  const strokeWidth = maxRadius / (data.length + 1);

  const colors = [
    primaryColor,
    useColor('blue'),
    useColor('green'),
    useColor('orange'),
    useColor('purple'),
    useColor('pink'),
  ];

  return (
    <View
      style={[{ width: '100%' }, style]}
      accessibilityRole='image'
      accessibilityLabel={`Radial bar chart with ${data.length} bars, maximum value ${Math.round(maxValue)}`}
    >
      <View
        style={{
          width: '100%',
          height: size,
          alignItems: 'center',
          justifyContent: 'center',
        }}
        onLayout={handleLayout}
      >
        <Svg width={size} height={size}>
          <Defs>
            {gradient &&
              data.map((item, index) => (
                <LinearGradient
                  key={`gradient-${index}`}
                  id={`radialGradient-${gradientIdPrefix}-${index}`}
                  x1='0%'
                  y1='0%'
                  x2='100%'
                  y2='0%'
                >
                  <Stop
                    offset='0%'
                    stopColor={item.color || colors[index % colors.length]}
                    stopOpacity='0.3'
                  />
                  <Stop
                    offset='100%'
                    stopColor={item.color || colors[index % colors.length]}
                    stopOpacity='1'
                  />
                </LinearGradient>
              ))}
          </Defs>

          {data.map((item, index) => {
            const radius = maxRadius - index * strokeWidth - strokeWidth / 2;
            const circumference = 2 * Math.PI * radius;
            const progressRatio = item.value / maxValue;

            return (
              <AnimatedRadialBar
                key={`radial-${index}`}
                cx={center}
                cy={center}
                r={radius}
                stroke={
                  gradient
                    ? `url(#radialGradient-${gradientIdPrefix}-${index})`
                    : item.color || colors[index % colors.length]
                }
                strokeWidth={strokeWidth * 0.8}
                circumference={circumference}
                progressRatio={progressRatio}
                transform={`rotate(-90 ${center} ${center})`}
                animationProgress={animationProgress}
              />
            );
          })}

          {/* Center values */}
          {data.length > 0 && (
            <>
              <SvgText
                x={center}
                y={center - 5}
                textAnchor='middle'
                fontSize={16}
                fill={primaryColor}
                fontWeight='bold'
              >
                {data.reduce((sum, item) => sum + item.value, 0)}
              </SvgText>
              <SvgText
                x={center}
                y={center + 15}
                textAnchor='middle'
                fontSize={12}
                fill={mutedColor}
              >
                Total
              </SvgText>
            </>
          )}
        </Svg>
      </View>

      {/* Legend */}
      <View style={{ marginTop: 15 }}>
        {data.map((item, index) => (
          <View
            key={`legend-${index}`}
            style={{
              flexDirection: 'row',
              alignItems: 'center',
              marginBottom: 8,
            }}
          >
            <View
              style={{
                width: 12,
                height: 12,
                borderRadius: 6,
                backgroundColor: item.color || colors[index % colors.length],
                marginRight: 10,
              }}
            />
            <Text variant='caption'>
              {item.label}: {item.value}
            </Text>
          </View>
        ))}
      </View>
    </View>
  );
};
```

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

## Usage

```tsx
import { RadialBarChart } from '@/components/charts/radial-bar-chart';
```

```tsx
const data = [
  { label: 'Sales', value: 120 },
  { label: 'Marketing', value: 98 },
  { label: 'Support', value: 86 },
  { label: 'Development', value: 140 },
];

<RadialBarChart
  data={data}
  config={{
    animated: true,
    gradient: true,
    duration: 1000,
  }}
/>;
```

## Examples

#### Basic Radial Bar Chart

**Example:** A radial bar chart with smooth animations and center totals

```tsx
// components/demo/charts/radial-bar-chart/radial-bar-chart-demo.tsx
import { RadialBarChart } from '@/components/charts/radial-bar-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

const sampleData = [
  { label: 'Sales', value: 120 },
  { label: 'Marketing', value: 98 },
  { label: 'Support', value: 86 },
  { label: 'Development', value: 140 },
  { label: 'Design', value: 75 },
];

export function RadialBarChartDemo() {
  return (
    <ChartContainer
      title='Department Performance'
      description='Quarterly performance metrics by department'
    >
      <RadialBarChart
        data={sampleData}
        config={{
          animated: true,
          duration: 1000,
          gradient: false,
        }}
      />
    </ChartContainer>
  );
}
```

#### Sample Radial Bar Chart

**Example:** A sample radial bar chart with custom data

```tsx
// components/demo/charts/radial-bar-chart/radial-bar-chart-sample.tsx
import { RadialBarChart } from '@/components/charts/radial-bar-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import { useColor } from '@/hooks/useColor';
import React from 'react';

const sampleData = [
  { label: 'Mobile', value: 45 },
  { label: 'Desktop', value: 38 },
  { label: 'Tablet', value: 17 },
];

export function RadialBarChartSample() {
  const blue = useColor('blue');
  const green = useColor('green');
  const orange = useColor('orange');

  const dataWithColors = sampleData.map((item, index) => ({
    ...item,
    color: [blue, green, orange][index],
  }));

  return (
    <ChartContainer
      title='Device Usage'
      description='User engagement by device type'
    >
      <RadialBarChart
        data={dataWithColors}
        config={{
          animated: true,
          duration: 1200,
          padding: 25,
        }}
      />
    </ChartContainer>
  );
}
```

#### Gradient Radial Bar Chart

**Example:** A radial bar chart with gradient effects

```tsx
// components/demo/charts/radial-bar-chart/radial-bar-chart-gradient.tsx
import { RadialBarChart } from '@/components/charts/radial-bar-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import { useColor } from '@/hooks/useColor';
import React from 'react';

const sampleData = [
  { label: 'Q1 Revenue', value: 85 },
  { label: 'Q2 Revenue', value: 92 },
  { label: 'Q3 Revenue', value: 78 },
  { label: 'Q4 Revenue', value: 96 },
];

export function RadialBarChartGradient() {
  const purple = useColor('purple');
  const pink = useColor('pink');
  const blue = useColor('blue');
  const green = useColor('green');

  const dataWithColors = sampleData.map((item, index) => ({
    ...item,
    color: [purple, pink, blue, green][index],
  }));

  return (
    <ChartContainer
      title='Quarterly Revenue'
      description='Revenue performance with gradient effects'
    >
      <RadialBarChart
        data={dataWithColors}
        config={{
          animated: true,
          duration: 1500,
          gradient: true,
          padding: 30,
        }}
      />
    </ChartContainer>
  );
}
```

#### Large Radial Bar Chart

**Example:** A radial bar chart with large dataset

```tsx
// components/demo/charts/radial-bar-chart/radial-bar-chart-large.tsx
import { RadialBarChart } from '@/components/charts/radial-bar-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import React from 'react';

const largeDataset = [
  { label: 'Product A', value: 156 },
  { label: 'Product B', value: 142 },
  { label: 'Product C', value: 98 },
  { label: 'Product D', value: 124 },
  { label: 'Product E', value: 89 },
  { label: 'Product F', value: 167 },
  { label: 'Product G', value: 78 },
  { label: 'Product H', value: 134 },
];

export function RadialBarChartLarge() {
  return (
    <ChartContainer
      title='Product Performance'
      description='Sales performance across all product lines'
    >
      <RadialBarChart
        data={largeDataset}
        config={{
          animated: true,
          duration: 2000,
          padding: 15,
        }}
      />
    </ChartContainer>
  );
}
```

## API Reference

### RadialBarChart

A customizable radial bar chart component with smooth animations, gradient support, and center value display. Perfect for displaying progress, completion rates, or categorical data in a circular format.

| Prop     | Type               | Default | Description                                   |
| -------- | ------------------ | ------- | --------------------------------------------- |
| `data`   | `ChartDataPoint[]` | -       | Array of data points to display on the chart. |
| `config` | `ChartConfig`      | `{}`    | Configuration object for chart appearance.    |
| `style`  | `ViewStyle`        | -       | Additional styles to apply to the chart.      |

### ChartDataPoint

| Prop    | Type     | Description                        |
| ------- | -------- | ---------------------------------- |
| `label` | `string` | The label for the data point.      |
| `value` | `number` | The value for the data point.      |
| `color` | `string` | Optional custom color for the arc. |

### ChartConfig

| Prop       | Type      | Default | Description                                   |
| ---------- | --------- | ------- | --------------------------------------------- |
| `padding`  | `number`  | `20`    | Padding around the chart.                     |
| `animated` | `boolean` | `true`  | Whether to animate the chart on load.         |
| `duration` | `number`  | `1000`  | Animation duration in milliseconds.           |
| `gradient` | `boolean` | `false` | Whether to use gradient effects for the arcs. |

## Features

- **Circular Layout**: Displays data as concentric circles radiating from center
- **Smooth Animations**: Built-in animations using React Native Reanimated
- **Responsive Design**: Automatically adapts to container size
- **Custom Colors**: Support for individual arc colors
- **Gradient Support**: Optional gradient effects for enhanced visual appeal
- **Center Display**: Shows total value and label in the center
- **Legend**: Automatic legend generation with color indicators
- **Theme Integration**: Uses theme colors for consistent styling

## Use Cases

Radial bar charts are particularly effective for:

- **Progress Tracking**: Displaying completion rates or goal progress
- **Category Comparison**: Comparing values across different categories in a compact format
- **Dashboard Widgets**: Space-efficient data visualization for dashboards
- **Performance Metrics**: Showing KPIs, scores, or ratings in a visually appealing way
- **Budget Allocation**: Visualizing spending distributions
- **Survey Results**: Displaying response distributions in a circular format

## Design Considerations

The radial layout of the RadialBarChart makes it ideal for:

- **Compact Spaces**: Efficient use of space with circular design
- **Multiple Categories**: Clear visual separation with concentric circles
- **Progress Visualization**: Natural representation of completion or progress
- **Aesthetic Appeal**: Visually striking and modern appearance

## Accessibility

The RadialBarChart component includes several accessibility features:

- Semantic SVG structure for screen readers
- Proper contrast ratios for visual elements
- Text labels for both categories and values
- Legend with clear color indicators
- 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
- Responsive layout calculations
- Optimized gradient rendering

## Styling

The component integrates with your theme system:

- Uses `primary` color from theme for default arc color
- Uses `mutedForeground` color for labels and text
- Supports custom colors per data point
- Gradient effects with customizable opacity
- Rounded stroke caps for modern appearance

## Animation

The chart features smooth entry animations:

- Arcs animate from 0 to full progress
- Configurable animation duration
- Can be disabled for instant rendering
- Uses React Native Reanimated for optimal performance
- Synchronized animations across all arcs

## Center Display

The chart includes a center display feature:

- Shows total sum of all values
- Displays "Total" label
- Uses theme colors for consistency
- Automatically scales text size
- Positioned perfectly in the center

## Legend

The automatic legend provides:

- Color-coded indicators for each data point
- Clear labels with values
- Responsive layout
- Consistent spacing and typography
- Theme-integrated styling
