# Column Chart

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

---

**Example:** A horizontal bar chart with smooth animations

```tsx
// components/demo/charts/column-chart/column-chart-demo.tsx
import { ColumnChart } from '@/components/charts/column-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 },
  { label: 'HR', value: 65 },
];

export function ColumnChartDemo() {
  return (
    <ChartContainer
      title='Department Performance'
      description='Quarterly performance metrics by department'
    >
      <ColumnChart
        data={sampleData}
        config={{
          height: 300,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

## Installation

### CLI

```bash
npx bna-ui add column-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/column-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, Rect, Text as SvgText } from 'react-native-svg';

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

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

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

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

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

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

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

  const {
    height = 200,
    padding = 20,
    showLabels = true,
    animated = true,
    duration = 800,
  } = 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 maxValue = Math.max(...data.map((d) => d.value));
  if (maxValue === 0) return null;

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

  return (
    <View
      style={[{ width: '100%', height }, style]}
      onLayout={handleLayout}
      accessibilityRole='image'
      accessibilityLabel={`Column chart with ${data.length} bars, maximum value ${Math.round(maxValue)}`}
    >
      <Svg width={chartWidth} height={height}>
        {data.map((item, index) => {
          const barWidth = (item.value / maxValue) * innerChartWidth;
          const x = padding;
          const y = padding + index * (barHeight + barSpacing) + barSpacing / 2;

          return (
            <G key={`bar-${index}`}>
              <AnimatedColumn
                x={x}
                y={y}
                barHeight={barHeight}
                barWidth={barWidth}
                fill={item.color || primaryColor}
                animationProgress={animationProgress}
              />

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

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

## Usage

```tsx
import { ColumnChart } from '@/components/charts/column-chart';
```

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

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

## Examples

#### Basic Column Chart

**Example:** A horizontal bar chart with smooth animations

```tsx
// components/demo/charts/column-chart/column-chart-demo.tsx
import { ColumnChart } from '@/components/charts/column-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 },
  { label: 'HR', value: 65 },
];

export function ColumnChartDemo() {
  return (
    <ChartContainer
      title='Department Performance'
      description='Quarterly performance metrics by department'
    >
      <ColumnChart
        data={sampleData}
        config={{
          height: 300,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

#### Sample Column Chart

**Example:** A sample column chart

```tsx
// components/demo/charts/column-chart/column-chart-sample.tsx
import { ColumnChart } from '@/components/charts/column-chart';
import { ChartContainer } from '@/components/charts/chart-container';
import { useColor } from '@/hooks/useColor';
import React, { useState } from 'react';
import { Pressable, Text, View } from 'react-native';

const sampleData = [
  { label: 'Q1 2024', value: 850 },
  { label: 'Q2 2024', value: 920 },
  { label: 'Q3 2024', value: 1100 },
  { label: 'Q4 2024', value: 1250 },
];

export function ColumnChartSample() {
  const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
  const primaryColor = useColor('primary');
  const mutedColor = useColor('muted');

  const enhancedData = sampleData.map((item, index) => ({
    ...item,
    color: selectedIndex === index ? primaryColor : mutedColor,
  }));

  return (
    <ChartContainer
      title='Interactive Revenue Chart'
      description='Tap on quarters to highlight them'
    >
      <ColumnChart
        data={enhancedData}
        config={{
          height: 250,
          showLabels: true,
          animated: true,
          duration: 600,
        }}
      />
      <View
        style={{
          marginTop: 16,
          flexDirection: 'row',
          flexWrap: 'wrap',
          gap: 8,
        }}
      >
        {sampleData.map((item, index) => (
          <Pressable
            key={index}
            onPress={() =>
              setSelectedIndex(selectedIndex === index ? null : index)
            }
            style={{
              padding: 8,
              backgroundColor:
                selectedIndex === index ? primaryColor : mutedColor,
              borderRadius: 6,
              minWidth: 60,
              alignItems: 'center',
            }}
          >
            <Text
              style={{
                color: selectedIndex === index ? 'white' : 'gray',
                fontSize: 12,
                fontWeight: '500',
              }}
            >
              {item.label}
            </Text>
          </Pressable>
        ))}
      </View>
    </ChartContainer>
  );
}
```

#### Styled Column Chart

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

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

const sampleData = [
  { label: 'Mobile', value: 45, color: '#3b82f6' },
  { label: 'Desktop', value: 35, color: '#10b981' },
  { label: 'Tablet', value: 15, color: '#f59e0b' },
  { label: 'Smart TV', value: 8, color: '#ef4444' },
  { label: 'Wearable', value: 3, color: '#8b5cf6' },
];

export function ColumnChartStyled() {
  return (
    <ChartContainer
      title='Device Usage Statistics'
      description='User engagement by device type with custom colors'
    >
      <ColumnChart
        data={sampleData}
        config={{
          height: 280,
          padding: 24,
          showLabels: true,
          animated: true,
          duration: 1200,
        }}
        style={{
          backgroundColor: 'rgba(0, 0, 0, 0.02)',
          borderRadius: 12,
          padding: 16,
        }}
      />
    </ChartContainer>
  );
}
```

#### Large Column Chart

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

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

const largeSampleData = [
  { label: 'E-commerce', value: 2840, color: '#ff0066' },
  { label: 'AI', value: 2440, color: '#ff9900' },
  { label: 'Healthcare', value: 2150, color: '#00e6cc' },
  { label: 'Education', value: 1920, color: '#0099ff' },
  { label: 'Finance', value: 1780, color: '#ffcc00' },
  { label: 'Real Estate', value: 1650, color: '#9933ff' },
  { label: 'Travel', value: 1420, color: '#ff0080' },
  { label: 'Food & Dining', value: 1380, color: '#00cc66' },
  { label: 'Entertainment', value: 1250, color: '#ff6600' },
  { label: 'Sports', value: 1180, color: '#3399ff' },
  { label: 'Technology', value: 1050, color: '#cc66ff' },
  { label: 'Fashion', value: 980, color: '#ff3030' },
  { label: 'Automotive', value: 875, color: '#ff9900' },
  { label: 'Home & Garden', value: 720, color: '#0066ff' },
  { label: 'Beauty', value: 650, color: '#ff3366' },
  { label: 'Pets', value: 580, color: '#00ffcc' },
];

export function ColumnChartLarge() {
  return (
    <ChartContainer
      title='Industry Revenue Analysis'
      description='Annual revenue by industry sector (in millions)'
    >
      <ColumnChart
        data={largeSampleData}
        config={{
          height: 500,
          padding: 20,
          showLabels: true,
          animated: true,
          duration: 4000,
        }}
      />
    </ChartContainer>
  );
}
```

## API Reference

### ColumnChart

A customizable horizontal bar chart component with smooth animations and flexible styling. Perfect for displaying categorical data with emphasis on comparison between categories.

| 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 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.                         |
| `showLabels` | `boolean` | `true`  | Whether to show labels for bars.                  |
| `animated`   | `boolean` | `true`  | Whether to animate the chart on load.             |
| `duration`   | `number`  | `800`   | Animation duration in milliseconds.               |

## Features

- **Horizontal Layout**: Displays bars horizontally for better label readability
- **Smooth Animations**: Built-in animations using React Native Reanimated
- **Responsive Design**: Automatically adapts to container width
- **Custom Colors**: Support for individual bar colors
- **Label Display**: Shows both category labels and values
- **Theme Integration**: Uses theme colors for consistent styling
- **Rounded Corners**: Aesthetic rounded bar corners

## Use Cases

Column charts are particularly effective for:

- **Category Comparison**: Comparing values across different categories
- **Performance Metrics**: Displaying KPIs, scores, or ratings
- **Survey Results**: Showing response distributions
- **Budget Allocation**: Visualizing spending across departments
- **Progress Tracking**: Displaying completion rates or achievements

## Design Considerations

The horizontal layout of the ColumnChart makes it ideal for:

- **Long Category Names**: Labels are displayed to the left of bars, allowing for longer text
- **Small Screens**: Horizontal bars work better on mobile devices
- **Multiple Categories**: Easier to scan through many categories vertically
- **Value Comparison**: Horizontal alignment makes it easier to compare bar lengths

## Accessibility

The ColumnChart component includes several accessibility features:

- Semantic SVG structure for screen readers
- Proper contrast ratios for visual elements
- Text labels for both categories and 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
- Responsive layout calculations

## Styling

The component integrates with your theme system:

- Uses `primary` color from theme for default bar color
- Uses `mutedForeground` color for labels and text
- Supports custom colors per data point
- Rounded corners with consistent border radius

## Animation

The chart features smooth entry animations:

- Bars animate from 0 width to full width
- Configurable animation duration
- Can be disabled for instant rendering
- Uses React Native Reanimated for optimal performance
