# Bar Chart

> A customizable bar chart component with smooth animations and interactive features.

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

---

**Example:** A basic bar chart with smooth animations and rounded corners

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

const sampleData = [
  { label: 'Jan', value: 65, color: '#3b82f6' },
  { label: 'Feb', value: 78, color: '#ef4444' },
  { label: 'Mar', value: 52, color: '#10b981' },
  { label: 'Apr', value: 91, color: '#f59e0b' },
  { label: 'May', value: 73, color: '#8b5cf6' },
  { label: 'Jun', value: 85, color: '#06b6d4' },
];

export function BarChartDemo() {
  return (
    <ChartContainer
      title='Monthly Sales'
      description='Product sales performance by month'
    >
      <BarChart
        data={sampleData}
        config={{
          height: 220,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add 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/bar-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,
  withTiming,
} from 'react-native-reanimated';
import Svg, { G, Line, Rect, Text as SvgText } from 'react-native-svg';

// Animated SVG Components
const AnimatedRect = Animated.createAnimatedComponent(Rect);

type AnimatedBarProps = {
  x: number;
  width: number;
  barHeight: number;
  bottomY: number;
  fill: 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 AnimatedBar = React.memo(
  ({
    x,
    width,
    barHeight,
    bottomY,
    fill,
    animationProgress,
  }: AnimatedBarProps) => {
    const barAnimatedProps = useAnimatedProps(() => ({
      height: animationProgress.value * barHeight,
      y: bottomY - animationProgress.value * barHeight,
    }));

    return (
      <AnimatedRect
        x={x}
        width={width}
        fill={fill}
        rx={4}
        animatedProps={barAnimatedProps}
      />
    );
  }
);

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

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

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

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

  const {
    height = 200,
    padding = 20,
    showGrid = false,
    showLabels = true,
    animated = true,
    duration = 800,
  } = config;

  // Use measured width or fallback to config width or default
  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 maxValue = Math.max(...data.map((d) => d.value));
  if (maxValue === 0) return null;

  const innerChartWidth = chartWidth - padding * 2;
  const chartHeight = height - padding * 2;
  const barWidth = (innerChartWidth / data.length) * 0.8;
  const barSpacing = (innerChartWidth / data.length) * 0.2;

  return (
    <View
      style={[{ width: '100%', height }, style]}
      onLayout={handleLayout}
      accessibilityRole='image'
      accessibilityLabel={`Bar chart with ${data.length} bars, maximum value ${Math.round(maxValue)}`}
    >
      <Svg width={chartWidth} height={height}>
        {/* Grid lines */}
        {showGrid && (
          <G>
            {[0, 0.25, 0.5, 0.75, 1].map((ratio, index) => (
              <Line
                key={`grid-${index}`}
                x1={padding}
                y1={padding + ratio * chartHeight}
                x2={chartWidth - padding}
                y2={padding + ratio * chartHeight}
                stroke={mutedColor}
                strokeWidth={0.5}
                opacity={0.3}
              />
            ))}
          </G>
        )}

        {data.map((item, index) => {
          const barHeight = (item.value / maxValue) * chartHeight;
          const x = padding + index * (barWidth + barSpacing) + barSpacing / 2;
          const y = height - padding - barHeight;

          return (
            <G key={`bar-${index}`}>
              <AnimatedBar
                x={x}
                width={barWidth}
                barHeight={barHeight}
                bottomY={height - padding}
                fill={item.color || primaryColor}
                animationProgress={animationProgress}
              />

              {showLabels && (
                <>
                  <SvgText
                    x={x + barWidth / 2}
                    y={height - 5}
                    textAnchor='middle'
                    fontSize={12}
                    fill={mutedColor}
                  >
                    {item.label}
                  </SvgText>
                  <SvgText
                    x={x + barWidth / 2}
                    y={y - 5}
                    textAnchor='middle'
                    fontSize={11}
                    fill={mutedColor}
                    fontWeight='600'
                  >
                    {item.value}
                  </SvgText>
                </>
              )}
            </G>
          );
        })}
      </Svg>
    </View>
  );
};
```

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

## Usage

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

```tsx
const data = [
  { label: 'Jan', value: 100, color: '#3b82f6' },
  { label: 'Feb', value: 120, color: '#ef4444' },
  { label: 'Mar', value: 90, color: '#10b981' },
  { label: 'Apr', value: 140, color: '#f59e0b' },
];

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

## Examples

#### Basic Bar Chart

**Example:** A basic bar chart with smooth animations and rounded corners

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

const sampleData = [
  { label: 'Jan', value: 65, color: '#3b82f6' },
  { label: 'Feb', value: 78, color: '#ef4444' },
  { label: 'Mar', value: 52, color: '#10b981' },
  { label: 'Apr', value: 91, color: '#f59e0b' },
  { label: 'May', value: 73, color: '#8b5cf6' },
  { label: 'Jun', value: 85, color: '#06b6d4' },
];

export function BarChartDemo() {
  return (
    <ChartContainer
      title='Monthly Sales'
      description='Product sales performance by month'
    >
      <BarChart
        data={sampleData}
        config={{
          height: 220,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

#### Sample Bar Chart

**Example:** A sample bar chart with custom colors

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

const sampleData = [
  { label: 'Product A', value: 120, color: '#3b82f6' },
  { label: 'Product B', value: 98, color: '#ef4444' },
  { label: 'Product C', value: 86, color: '#10b981' },
  { label: 'Product D', value: 74, color: '#f59e0b' },
  { label: 'Product E', value: 65, color: '#8b5cf6' },
];

export function BarChartSample() {
  return (
    <ChartContainer
      title='Product Performance'
      description='Sales performance by product category'
    >
      <BarChart
        data={sampleData}
        config={{
          height: 250,
          showLabels: true,
          animated: true,
          duration: 1200,
        }}
      />
    </ChartContainer>
  );
}
```

#### Minimal Bar Chart

**Example:** A minimal bar chart without labels

```tsx
// components/demo/charts/bar-chart/bar-chart-minimal.tsx
import { BarChart } from '@/components/charts/bar-chart';
import React from 'react';

const sampleData = [
  { label: 'A', value: 30 },
  { label: 'B', value: 50 },
  { label: 'C', value: 25 },
  { label: 'D', value: 70 },
  { label: 'E', value: 45 },
  { label: 'F', value: 60 },
];

export function BarChartMinimal() {
  return (
    <BarChart
      data={sampleData}
      config={{
        height: 150,
        showLabels: false,
        animated: true,
        duration: 600,
        padding: 10,
      }}
    />
  );
}
```

## API Reference

### BarChart

A customizable bar chart component with smooth animations and rounded corners.

| 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 bar.             |
| `value` | `number` | The value of the bar.              |
| `color` | `string` | Optional custom color for the bar. |

### ChartConfig

| Prop         | Type      | Default | Description                                       |
| ------------ | --------- | ------- | ------------------------------------------------- |
| `width`      | `number`  | -       | Fixed width of the chart (auto-sizes if omitted). |
| `height`     | `number`  | `200`   | Height of the chart.                              |
| `padding`    | `number`  | `20`    | Padding around the chart.                         |
| `showGrid`   | `boolean` | `false` | Whether to show grid lines.                       |
| `showLabels` | `boolean` | `true`  | Whether to show labels on bars.                   |
| `animated`   | `boolean` | `true`  | Whether to animate the chart on load.             |
| `duration`   | `number`  | `800`   | Animation duration in milliseconds.               |

## Features

- **Smooth Animations**: Built-in animations using React Native Reanimated
- **Rounded Corners**: Customizable corner radius for modern appearance
- **Responsive Design**: Automatically adapts to container width
- **Custom Colors**: Individual bar colors or color scale support
- **Value Labels**: Optional value display on top of bars
- **Auto-scaling**: Automatic calculation of bar heights and spacing
- **Theme Integration**: Uses theme colors for consistent styling

## Accessibility

The BarChart component is built with accessibility in mind:

- Semantic SVG structure for screen readers
- Proper contrast ratios for visual elements
- Touch targets meet minimum size requirements
- Supports dynamic text sizing
- Clear visual hierarchy with labels and values

## 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
- Optimized bar spacing calculations
