# Radar Chart

> A customizable radar chart component with smooth animations and flexible styling for displaying multi-dimensional data.

**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/radar-chart
- Markdown: https://ui.ahmedbna.com/docs/charts/radar-chart.md
- Structured JSON (props, usage, source, examples): https://ui.ahmedbna.com/r/ai/radar-chart.json
- Install payload (source plus every file it imports): https://ui.ahmedbna.com/r/radar-chart.json
- Install: `npx bna-ui add radar-chart`
- npm dependencies: `react-native-reanimated`, `react-native-svg`, `react-native-worklets`
- Registry dependencies: `mode-provider`, `useColorScheme`, `colors`, `useColor`, `globals`
- Preview recording: https://demo.ahmedbna.com/0375-radar-chart-demo.MOV

---

**Example:** A radar chart with smooth animations

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

const sampleData = [
  { label: 'Speed', value: 80 },
  { label: 'Reliability', value: 92 },
  { label: 'Comfort', value: 75 },
  { label: 'Safety', value: 88 },
  { label: 'Efficiency', value: 85 },
  { label: 'Style', value: 70 },
];

export function RadarChartDemo() {
  return (
    <ChartContainer
      title='Product Performance'
      description='Multi-dimensional performance analysis across key metrics'
    >
      <RadarChart
        data={sampleData}
        config={{
          height: 300,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add radar-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/radar-chart.tsx
import { useColor } from '@/hooks/useColor';
import React, { useEffect, useState } from 'react';
import { LayoutChangeEvent, View, ViewStyle } from 'react-native';
import Animated, {
  SharedValue,
  useAnimatedProps,
  useSharedValue,
  withDelay,
  withSpring,
  withTiming,
} from 'react-native-reanimated';
import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg';

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

type AnimatedVertexProps = {
  cx: number;
  cy: number;
  fill: string;
  index: number;
  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 AnimatedVertex = React.memo(
  ({ cx, cy, fill, index, animationProgress }: AnimatedVertexProps) => {
    const pointAnimatedProps = useAnimatedProps(() => ({
      opacity: animationProgress.value,
      r: withDelay(index * 100, withSpring(animationProgress.value * 4)),
    }));

    return (
      <AnimatedCircle
        cx={cx}
        cy={cy}
        fill={fill}
        animatedProps={pointAnimatedProps}
      />
    );
  }
);

interface ChartConfig {
  width?: number;
  height?: number;
  showLabels?: boolean;
  animated?: boolean;
  duration?: number;
  maxValue?: number;
}

interface RadarChartDataPoint {
  label: string;
  value: number;
}

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

export const RadarChart = ({ data, config = {}, style }: Props) => {
  const [containerWidth, setContainerWidth] = useState(300);

  const {
    height = 200,
    showLabels = true,
    animated = true,
    duration = 1000,
    maxValue,
  } = config;

  const chartWidth = containerWidth || config.width || 300;

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

  const animationProgress = useSharedValue(0);

  const handleLayout = (event: LayoutChangeEvent) => {
    const { width: measuredWidth } = event.nativeEvent.layout;
    if (measuredWidth > 0) {
      setContainerWidth(measuredWidth);
    }
  };

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

  if (!data.length) return null;

  const centerX = chartWidth / 2;
  const centerY = height / 2;
  const radius = Math.min(chartWidth, height) / 2 - 40;
  // `??` (not `||`) so an explicit maxValue={0} isn't silently discarded.
  const maxVal = maxValue ?? Math.max(...data.map((d) => d.value));
  if (maxVal === 0) return null;

  // Calculate points for each data point
  const angleStep = (2 * Math.PI) / data.length;
  const points = data.map((item, index) => {
    const angle = index * angleStep - Math.PI / 2; // Start from top
    const distance = (item.value / maxVal) * radius;
    return {
      x: centerX + distance * Math.cos(angle),
      y: centerY + distance * Math.sin(angle),
      labelX: centerX + (radius + 20) * Math.cos(angle),
      labelY: centerY + (radius + 20) * Math.sin(angle),
      label: item.label,
    };
  });

  // Create path for the radar area
  const radarPath =
    points.length > 0
      ? `M${points[0].x},${points[0].y} ` +
        points
          .slice(1)
          .map((p) => `L${p.x},${p.y}`)
          .join(' ') +
        ' Z'
      : '';

  const radarAnimatedProps = useAnimatedProps(() => ({
    opacity: animationProgress.value * 0.3,
  }));

  return (
    <View
      style={[{ width: '100%', height }, style]}
      onLayout={handleLayout}
      accessibilityRole='image'
      accessibilityLabel={`Radar chart with ${data.length} axes, maximum value ${Math.round(maxVal)}`}
    >
      <Svg width={chartWidth} height={height}>
        {/* Grid circles */}
        {[0.2, 0.4, 0.6, 0.8, 1].map((ratio, index) => (
          <Circle
            key={`grid-circle-${index}`}
            cx={centerX}
            cy={centerY}
            r={radius * ratio}
            stroke={mutedColor}
            strokeWidth={0.5}
            fill='none'
            opacity={0.3}
          />
        ))}

        {/* Grid lines */}
        {data.map((_, index) => {
          const angle = index * angleStep - Math.PI / 2;
          const endX = centerX + radius * Math.cos(angle);
          const endY = centerY + radius * Math.sin(angle);

          return (
            <Line
              key={`grid-line-${index}`}
              x1={centerX}
              y1={centerY}
              x2={endX}
              y2={endY}
              stroke={mutedColor}
              strokeWidth={0.5}
              opacity={0.3}
            />
          );
        })}

        {/* Radar area */}
        <AnimatedPath
          d={radarPath}
          fill={primaryColor}
          stroke={primaryColor}
          strokeWidth={2}
          animatedProps={radarAnimatedProps}
        />

        {/* Data points */}
        {points.map((point, index) => (
          <AnimatedVertex
            key={`point-${index}`}
            cx={point.x}
            cy={point.y}
            fill={primaryColor}
            index={index}
            animationProgress={animationProgress}
          />
        ))}

        {/* Labels */}
        {showLabels &&
          points.map((point, index) => (
            <SvgText
              key={`label-${index}`}
              x={point.labelX}
              y={point.labelY}
              textAnchor='middle'
              fontSize={12}
              fill={mutedColor}
              alignmentBaseline='middle'
            >
              {point.label}
            </SvgText>
          ))}
      </Svg>
    </View>
  );
};
```

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

## Usage

```tsx
import { RadarChart } from '@/components/charts/radar-chart';
```

```tsx
const data = [
  { label: 'Speed', value: 80 },
  { label: 'Reliability', value: 92 },
  { label: 'Comfort', value: 75 },
  { label: 'Safety', value: 88 },
  { label: 'Efficiency', value: 85 },
];

<RadarChart
  data={data}
  config={{
    height: 300,
    showLabels: true,
    animated: true,
  }}
/>;
```

## Examples

#### Basic Radar Chart

**Example:** A radar chart with smooth animations

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

const sampleData = [
  { label: 'Speed', value: 80 },
  { label: 'Reliability', value: 92 },
  { label: 'Comfort', value: 75 },
  { label: 'Safety', value: 88 },
  { label: 'Efficiency', value: 85 },
  { label: 'Style', value: 70 },
];

export function RadarChartDemo() {
  return (
    <ChartContainer
      title='Product Performance'
      description='Multi-dimensional performance analysis across key metrics'
    >
      <RadarChart
        data={sampleData}
        config={{
          height: 300,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

#### Sample Radar Chart

**Example:** A sample radar chart

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

const skillsData = [
  { label: 'Frontend', value: 95 },
  { label: 'Backend', value: 82 },
  { label: 'Mobile', value: 78 },
  { label: 'DevOps', value: 65 },
  { label: 'Design', value: 70 },
];

export function RadarChartSample() {
  return (
    <ChartContainer
      title='Skills Assessment'
      description='Developer competency across different technology areas'
    >
      <RadarChart
        data={skillsData}
        config={{
          height: 250,
          showLabels: true,
          animated: true,
          duration: 1200,
          maxValue: 100,
        }}
      />
    </ChartContainer>
  );
}
```

#### Styled Radar Chart

**Example:** A customized radar chart with custom colors and styling

```tsx
// components/demo/charts/radar-chart/radar-chart-styled.tsx
import { RadarChart } from '@/components/charts/radar-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import { useColor } from '@/hooks/useColor';
import React from 'react';

const performanceData = [
  { label: 'Innovation', value: 88 },
  { label: 'Quality', value: 92 },
  { label: 'Delivery', value: 85 },
  { label: 'Customer Satisfaction', value: 90 },
  { label: 'Cost Efficiency', value: 78 },
  { label: 'Team Collaboration', value: 95 },
  { label: 'Process Improvement', value: 82 },
];

export function RadarChartStyled() {
  const accentColor = useColor('accent');

  return (
    <ChartContainer
      title='Team Performance Matrix'
      description='Comprehensive evaluation across key performance indicators'
    >
      <RadarChart
        data={performanceData}
        config={{
          height: 350,
          showLabels: true,
          animated: true,
          duration: 1500,
          maxValue: 100,
        }}
        style={{
          backgroundColor: 'rgba(0, 0, 0, 0.02)',
          borderRadius: 12,
          padding: 16,
        }}
      />
    </ChartContainer>
  );
}
```

#### Large Radar Chart

**Example:** A radar chart with large dataset

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

const comprehensiveData = [
  { label: 'Leadership', value: 85 },
  { label: 'Communication', value: 90 },
  { label: 'Technical Skills', value: 88 },
  { label: 'Problem Solving', value: 92 },
  { label: 'Creativity', value: 78 },
  { label: 'Adaptability', value: 86 },
  { label: 'Time Management', value: 82 },
  { label: 'Teamwork', value: 94 },
  { label: 'Strategic Thinking', value: 80 },
  { label: 'Customer Focus', value: 87 },
];

export function RadarChartLarge() {
  return (
    <ChartContainer
      title='360° Skills Assessment'
      description='Comprehensive evaluation across multiple competency areas'
    >
      <RadarChart
        data={comprehensiveData}
        config={{
          height: 400,
          showLabels: true,
          animated: true,
          duration: 2000,
          maxValue: 100,
        }}
      />
    </ChartContainer>
  );
}
```

## API Reference

### RadarChart

A customizable radar chart component with smooth animations and flexible styling. Perfect for displaying multi-dimensional data with emphasis on comparison across multiple metrics.

| Prop     | Type                    | Default | Description                                   |
| -------- | ----------------------- | ------- | --------------------------------------------- |
| `data`   | `RadarChartDataPoint[]` | -       | 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.      |

### RadarChartDataPoint

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

### ChartConfig

| Prop         | Type      | Default | Description                                          |
| ------------ | --------- | ------- | ---------------------------------------------------- |
| `width`      | `number`  | -       | Fixed width of the chart (auto-sizes if omitted).    |
| `height`     | `number`  | `200`   | Height of the chart.                                 |
| `showLabels` | `boolean` | `true`  | Whether to show labels around the chart.             |
| `animated`   | `boolean` | `true`  | Whether to animate the chart on load.                |
| `duration`   | `number`  | `1000`  | Animation duration in milliseconds.                  |
| `maxValue`   | `number`  | -       | Maximum value for the chart scale (auto if omitted). |

## Features

- **Circular Layout**: Displays data points in a circular radar pattern
- **Smooth Animations**: Built-in animations using React Native Reanimated
- **Responsive Design**: Automatically adapts to container width
- **Grid System**: Circular grid lines and radial guides for easy reading
- **Label Display**: Shows category labels around the perimeter
- **Theme Integration**: Uses theme colors for consistent styling
- **Filled Area**: Highlighted area showing the data profile

## Use Cases

Radar charts are particularly effective for:

- **Performance Analysis**: Comparing multiple performance metrics
- **Skill Assessment**: Visualizing competency across different areas
- **Product Comparison**: Comparing features across multiple products
- **Survey Results**: Displaying multi-dimensional survey responses
- **Sports Analytics**: Showing player statistics across different attributes
- **Quality Metrics**: Displaying quality scores across various dimensions

## Design Considerations

The circular layout of the RadarChart makes it ideal for:

- **Multi-dimensional Data**: Perfect for displaying 3-8 different metrics
- **Pattern Recognition**: Easy to spot strengths and weaknesses
- **Comparative Analysis**: Overlaying multiple data sets for comparison
- **Balance Visualization**: Showing how balanced performance is across metrics

## Accessibility

The RadarChart component includes several accessibility features:

- Semantic SVG structure for screen readers
- Proper contrast ratios for visual elements
- Text labels for all data points
- Supports dynamic text sizing
- Clear visual hierarchy with grid lines

## 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 path calculations for smooth rendering

## Styling

The component integrates with your theme system:

- Uses `primary` color from theme for default area fill and stroke
- Uses `mutedForeground` color for labels and grid lines
- Supports custom styling through style prop
- Consistent opacity and visual hierarchy

## Animation

The chart features smooth entry animations:

- Area fills from 0 opacity to final opacity
- Data points animate with staggered delays
- Configurable animation duration
- Can be disabled for instant rendering
- Uses React Native Reanimated for optimal performance

## Mathematical Considerations

The radar chart uses polar coordinates:

- Angles are calculated based on the number of data points
- Values are normalized to fit within the chart radius
- Grid circles represent percentage increments (20%, 40%, 60%, 80%, 100%)
- Labels are positioned outside the chart area for clarity

## Best Practices

When using radar charts:

- **Limit Data Points**: Use 3-8 metrics for optimal readability
- **Similar Scales**: Ensure all metrics use similar value ranges
- **Meaningful Order**: Arrange metrics in logical order around the circle
- **Clear Labels**: Use concise, descriptive labels
- **Consistent Units**: Use consistent measurement units across metrics
