# Scatter Chart

> A customizable scatter plot component with smooth animations and flexible styling for visualizing data relationships.

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

---

**Example:** A scatter plot with smooth animations

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

const sampleData = [
  { x: 10, y: 20, label: 'Point A' },
  { x: 25, y: 35, label: 'Point B' },
  { x: 40, y: 15, label: 'Point C' },
  { x: 55, y: 45, label: 'Point D' },
  { x: 70, y: 30, label: 'Point E' },
  { x: 85, y: 55, label: 'Point F' },
  { x: 30, y: 50, label: 'Point G' },
  { x: 65, y: 25, label: 'Point H' },
];

export function ScatterChartDemo() {
  return (
    <ChartContainer
      title='Performance vs Experience'
      description='Scatter plot showing the relationship between years of experience and performance scores'
    >
      <ScatterPlot
        data={sampleData}
        config={{
          height: 300,
          showGrid: true,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

## Installation

### CLI

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

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

type AnimatedScatterPointProps = {
  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 AnimatedScatterPoint = React.memo(
  ({ cx, cy, fill, index, animationProgress }: AnimatedScatterPointProps) => {
    const pointAnimatedProps = useAnimatedProps(() => ({
      opacity: animationProgress.value,
      r: withDelay(index * 50, withSpring(animationProgress.value * 5)),
    }));

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

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

export type ChartDataPoint = {
  x: number;
  y: number;
  label?: string;
};

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

// Scatter Plot Component
export const ScatterPlot = ({ data, config = {}, style }: Props) => {
  const [containerWidth, setContainerWidth] = useState(300);

  const {
    height = 200,
    padding = 20,
    showGrid = true,
    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 maxX = Math.max(...data.map((d) => d.x));
  const minX = Math.min(...data.map((d) => d.x));
  const maxY = Math.max(...data.map((d) => d.y));
  const minY = Math.min(...data.map((d) => d.y));

  const xRange = maxX - minX || 1;
  const yRange = maxY - minY || 1;

  const innerChartWidth = chartWidth - padding * 2;
  const chartHeight = height - padding * 2;

  // Convert data to screen coordinates
  const points = data.map((point) => ({
    x: padding + ((point.x - minX) / xRange) * innerChartWidth,
    y: padding + ((maxY - point.y) / yRange) * chartHeight,
  }));

  return (
    <View
      style={[{ width: '100%', height }, style]}
      onLayout={handleLayout}
      accessibilityRole='image'
      accessibilityLabel={`Scatter plot with ${data.length} points, x from ${Math.round(minX)} to ${Math.round(maxX)}, y from ${Math.round(minY)} to ${Math.round(maxY)}`}
    >
      <Svg width={chartWidth} height={height}>
        {/* Grid lines */}
        {showGrid && (
          <G>
            {[0, 0.25, 0.5, 0.75, 1].map((ratio, index) => (
              <G key={`grid-${index}`}>
                <Line
                  x1={padding}
                  y1={padding + ratio * chartHeight}
                  x2={chartWidth - padding}
                  y2={padding + ratio * chartHeight}
                  stroke={mutedColor}
                  strokeWidth={0.5}
                  opacity={0.3}
                />
                <Line
                  x1={padding + ratio * innerChartWidth}
                  y1={padding}
                  x2={padding + ratio * innerChartWidth}
                  y2={height - padding}
                  stroke={mutedColor}
                  strokeWidth={0.5}
                  opacity={0.3}
                />
              </G>
            ))}
          </G>
        )}

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

        {/* Axis labels */}
        {showLabels && (
          <G>
            {/* X-axis labels */}
            {[minX, (minX + maxX) / 2, maxX].map((value, index) => (
              <SvgText
                key={`x-label-${index}`}
                x={padding + (index * innerChartWidth) / 2}
                y={height - 5}
                textAnchor='middle'
                fontSize={12}
                fill={mutedColor}
              >
                {Math.round(value)}
              </SvgText>
            ))}
            {/* Y-axis labels */}
            {[maxY, (minY + maxY) / 2, minY].map((value, index) => (
              <SvgText
                key={`y-label-${index}`}
                x={15}
                y={padding + (index * chartHeight) / 2}
                textAnchor='middle'
                fontSize={12}
                fill={mutedColor}
                alignmentBaseline='middle'
              >
                {Math.round(value)}
              </SvgText>
            ))}
          </G>
        )}
      </Svg>
    </View>
  );
};
```

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

## Usage

```tsx
import { ScatterPlot } from '@/components/charts/scatter-chart';
```

```tsx
const data = [
  { x: 10, y: 20, label: 'Point A' },
  { x: 25, y: 35, label: 'Point B' },
  { x: 40, y: 15, label: 'Point C' },
  { x: 55, y: 45, label: 'Point D' },
];

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

## Examples

#### Basic Scatter Chart

**Example:** A scatter plot with smooth animations

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

const sampleData = [
  { x: 10, y: 20, label: 'Point A' },
  { x: 25, y: 35, label: 'Point B' },
  { x: 40, y: 15, label: 'Point C' },
  { x: 55, y: 45, label: 'Point D' },
  { x: 70, y: 30, label: 'Point E' },
  { x: 85, y: 55, label: 'Point F' },
  { x: 30, y: 50, label: 'Point G' },
  { x: 65, y: 25, label: 'Point H' },
];

export function ScatterChartDemo() {
  return (
    <ChartContainer
      title='Performance vs Experience'
      description='Scatter plot showing the relationship between years of experience and performance scores'
    >
      <ScatterPlot
        data={sampleData}
        config={{
          height: 300,
          showGrid: true,
          showLabels: true,
          animated: true,
          duration: 1000,
        }}
      />
    </ChartContainer>
  );
}
```

#### Sample Scatter Chart

**Example:** A sample scatter chart with various data points

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

const sampleData = [
  { x: 5, y: 12, label: 'Alpha' },
  { x: 15, y: 28, label: 'Beta' },
  { x: 35, y: 42, label: 'Gamma' },
  { x: 45, y: 18, label: 'Delta' },
  { x: 25, y: 65, label: 'Epsilon' },
  { x: 55, y: 38, label: 'Zeta' },
  { x: 75, y: 52, label: 'Eta' },
  { x: 65, y: 78, label: 'Theta' },
  { x: 85, y: 25, label: 'Iota' },
  { x: 95, y: 88, label: 'Kappa' },
];

export function ScatterChartSample() {
  return (
    <ChartContainer
      title='Sample Data Distribution'
      description='Sample scatter plot with random data points'
    >
      <ScatterPlot
        data={sampleData}
        config={{
          height: 250,
          showGrid: true,
          showLabels: true,
          animated: true,
          duration: 800,
        }}
      />
    </ChartContainer>
  );
}
```

#### Styled Scatter Chart

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

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

const sampleData = [
  { x: 20, y: 80, label: 'High Performance' },
  { x: 35, y: 65, label: 'Good Performance' },
  { x: 50, y: 70, label: 'Average Performance' },
  { x: 65, y: 45, label: 'Below Average' },
  { x: 80, y: 55, label: 'Improving' },
  { x: 25, y: 90, label: 'Excellent' },
  { x: 75, y: 35, label: 'Needs Work' },
  { x: 60, y: 85, label: 'Outstanding' },
];

export function ScatterChartStyled() {
  return (
    <ChartContainer
      title='Styled Performance Analysis'
      description='Customized scatter plot with enhanced styling'
    >
      <ScatterPlot
        data={sampleData}
        config={{
          height: 320,
          padding: 30,
          showGrid: true,
          showLabels: true,
          animated: true,
          duration: 1200,
        }}
        style={{
          backgroundColor: 'rgba(0, 0, 0, 0.02)',
          borderRadius: 12,
          borderWidth: 1,
          borderColor: 'rgba(0, 0, 0, 0.1)',
        }}
      />
    </ChartContainer>
  );
}
```

#### Large Scatter Chart

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

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

// Generate a larger dataset for demonstration
const generateLargeDataset = () => {
  const data = [];
  for (let i = 0; i < 30; i++) {
    data.push({
      x: Math.random() * 100,
      y: Math.random() * 100,
      label: `Point ${i + 1}`,
    });
  }
  return data;
};

const largeDataset = generateLargeDataset();

export function ScatterChartLarge() {
  return (
    <ChartContainer
      title='Large Dataset Visualization'
      description='Scatter plot with 30 data points showing distribution patterns'
    >
      <ScatterPlot
        data={largeDataset}
        config={{
          height: 400,
          padding: 25,
          showGrid: true,
          showLabels: true,
          animated: true,
          duration: 1500,
        }}
      />
    </ChartContainer>
  );
}
```

## API Reference

### ScatterPlot

A customizable scatter plot component with smooth animations and flexible styling. Perfect for visualizing relationships between two numerical variables and identifying patterns, trends, or outliers in data.

| 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                          |
| ------- | -------- | ------------------------------------ |
| `x`     | `number` | The x-coordinate for the data point. |
| `y`     | `number` | The y-coordinate for the data point. |
| `label` | `string` | Optional label 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.                              |
| `padding`    | `number`  | `20`    | Padding around the chart.                         |
| `showGrid`   | `boolean` | `true`  | Whether to show grid lines.                       |
| `showLabels` | `boolean` | `true`  | Whether to show axis labels.                      |
| `animated`   | `boolean` | `true`  | Whether to animate the chart on load.             |
| `duration`   | `number`  | `800`   | Animation duration in milliseconds.               |

## Features

- **Correlation Analysis**: Visualizes relationships between two numerical variables
- **Smooth Animations**: Built-in animations using React Native Reanimated
- **Responsive Design**: Automatically adapts to container width
- **Grid Lines**: Optional grid lines for better data reading
- **Axis Labels**: Shows min, max, and middle values on both axes
- **Theme Integration**: Uses theme colors for consistent styling
- **Staggered Animation**: Points animate in sequence for visual appeal

## Use Cases

Scatter charts are particularly effective for:

- **Correlation Analysis**: Identifying relationships between variables
- **Trend Identification**: Spotting patterns in data distributions
- **Outlier Detection**: Finding unusual data points
- **Performance Metrics**: Plotting performance vs. effort, cost vs. benefit
- **Scientific Data**: Displaying experimental results or measurements
- **Market Analysis**: Price vs. volume, risk vs. return analysis

## Design Considerations

The ScatterPlot component is optimized for:

- **Data Exploration**: Interactive visualization of data relationships
- **Pattern Recognition**: Clear visual representation of data clusters
- **Multi-dimensional Analysis**: Two-variable comparison in a single view
- **Statistical Analysis**: Visual correlation and regression analysis

## Accessibility

The ScatterPlot component includes several accessibility features:

- Semantic SVG structure for screen readers
- Proper contrast ratios for visual elements
- Clear axis labels with numerical values
- Grid lines for better data point reference
- Supports dynamic text sizing

## 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 for large datasets

## Styling

The component integrates with your theme system:

- Uses `primary` color from theme for data points
- Uses `mutedForeground` color for grid lines and labels
- Consistent with overall design system
- Customizable point sizes and colors

## Animation

The chart features smooth entry animations:

- Points animate from 0 opacity and size to full visibility
- Staggered animation creates a ripple effect
- Configurable animation duration
- Can be disabled for instant rendering
- Uses React Native Reanimated for optimal performance

## Mathematical Considerations

The scatter plot automatically handles:

- **Axis Scaling**: Automatically calculates appropriate scales for both axes
- **Data Normalization**: Converts data values to screen coordinates
- **Boundary Handling**: Ensures all points fit within the chart area
- **Grid Positioning**: Evenly distributes grid lines across the chart

## Data Interpretation

Scatter plots help identify:

- **Positive Correlation**: Points trending upward from left to right
- **Negative Correlation**: Points trending downward from left to right
- **No Correlation**: Points scattered without clear pattern
- **Outliers**: Points significantly distant from the main cluster
- **Clusters**: Groups of points in specific regions
