# Chart

Draws data as a bar, line or area chart with the package's colours, tooltip and legend.

- **Import:** `import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, ChartStyle } from "@booleanpress/ui/chart"`
- **Also install:** `recharts`
- **Page:** <https://ui.booleanpress.com/components/chart> · @booleanpress/ui 0.1.0

## Usage

The chart is Recharts, wrapped so its colours, tooltip and legend match the theme. `ChartContainer` takes a `config` that names each series and gives it a colour; the colour becomes a CSS variable, `--color-<key>`, that the Recharts parts use as `fill` or `stroke`. Install `recharts` in your product: it is a peer dependency.

```tsx
import { Bar, BarChart, XAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@booleanpress/ui/chart"

const config = { sent: { label: "Emails sent", color: "var(--chart-1)" } } satisfies ChartConfig

export function Sent({ data }: { data: { day: string; sent: number }[] }) {
  return (
    <ChartContainer config={config} className="h-56 w-full">
      <BarChart data={data}>
        <XAxis dataKey="day" />
        <ChartTooltip content={<ChartTooltipContent />} />
        <Bar dataKey="sent" fill="var(--color-sent)" />
      </BarChart>
    </ChartContainer>
  )
}
```

Give the container a height (`h-56`) and a width: the chart fills it, and it has no height of its own beyond a 16:9 ratio. Colour a series with `var(--chart-1)` to `var(--chart-5)`, or give `theme: { light, dark }` instead of `color` for a different value per theme. Use `ChartTooltip` with `ChartTooltipContent` and `ChartLegend` with `ChartLegendContent`; both look the series label up in `config`, so `label` is what people read. Turn off Recharts' animation (`isAnimationActive={false}`) when the chart must render at once, such as in a screenshot or a test.

## Examples

### Bar chart

Emails sent per day, with a tooltip that shows the day and the count.

```tsx
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@booleanpress/ui/chart"

const data = [
  { day: "Mon", sent: 412 },
  { day: "Tue", sent: 538 },
  { day: "Wed", sent: 497 },
  { day: "Thu", sent: 603 },
  { day: "Fri", sent: 365 },
  { day: "Sat", sent: 142 },
  { day: "Sun", sent: 118 },
]

const config = { sent: { label: "Emails sent", color: "var(--chart-1)" } } satisfies ChartConfig

export default function ChartBar() {
  return (
    <ChartContainer config={config} className="h-56 w-full max-w-lg">
      <BarChart data={data} margin={{ left: 0, right: 8 }}>
        <CartesianGrid vertical={false} strokeDasharray="3 3" />
        <XAxis dataKey="day" tickLine={false} axisLine={false} tickMargin={8} />
        <YAxis allowDecimals={false} tickLine={false} axisLine={false} width={32} />
        <ChartTooltip isAnimationActive={false} cursor={false} content={<ChartTooltipContent />} />
        <Bar dataKey="sent" fill="var(--color-sent)" radius={4} isAnimationActive={false} />
      </BarChart>
    </ChartContainer>
  )
}
```

### Area chart

Delivered against bounced, each in its own theme colour, with a legend and a tooltip that lists both series.

```tsx
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"
import {
  ChartContainer,
  ChartLegend,
  ChartLegendContent,
  ChartTooltip,
  ChartTooltipContent,
  type ChartConfig,
} from "@booleanpress/ui/chart"

const data = [
  { day: "Mon", delivered: 402, bounced: 10 },
  { day: "Tue", delivered: 521, bounced: 17 },
  { day: "Wed", delivered: 488, bounced: 9 },
  { day: "Thu", delivered: 570, bounced: 33 },
  { day: "Fri", delivered: 358, bounced: 7 },
  { day: "Sat", delivered: 140, bounced: 2 },
  { day: "Sun", delivered: 117, bounced: 1 },
]

const config = {
  delivered: { label: "Delivered", color: "var(--chart-2)" },
  bounced: { label: "Bounced", color: "var(--chart-1)" },
} satisfies ChartConfig

export default function ChartArea() {
  return (
    <ChartContainer config={config} className="h-64 w-full max-w-lg">
      <AreaChart data={data} margin={{ left: 0, right: 8 }}>
        <CartesianGrid vertical={false} strokeDasharray="3 3" />
        <XAxis dataKey="day" tickLine={false} axisLine={false} tickMargin={8} />
        <YAxis allowDecimals={false} tickLine={false} axisLine={false} width={32} />
        <ChartTooltip isAnimationActive={false} content={<ChartTooltipContent indicator="line" />} />
        <ChartLegend content={<ChartLegendContent />} />
        <Area dataKey="delivered" type="monotone" stroke="var(--color-delivered)" fill="var(--color-delivered)" fillOpacity={0.2} isAnimationActive={false} />
        <Area dataKey="bounced" type="monotone" stroke="var(--color-bounced)" fill="var(--color-bounced)" fillOpacity={0.2} isAnimationActive={false} />
      </AreaChart>
    </ChartContainer>
  )
}
```

### Text alternative

A chart that is given a name and a table with the same numbers beneath it.

```tsx
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@booleanpress/ui/chart"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@booleanpress/ui/table"

const data = [
  { day: "Mon", sent: 412 },
  { day: "Tue", sent: 538 },
  { day: "Wed", sent: 497 },
  { day: "Thu", sent: 603 },
  { day: "Fri", sent: 365 },
]

const config = { sent: { label: "Emails sent", color: "var(--chart-1)" } } satisfies ChartConfig

export default function ChartTextAlternative() {
  return (
    <figure className="flex w-full max-w-lg flex-col gap-3">
      <ChartContainer config={config} className="h-48 w-full" role="img" aria-label="Bar chart of emails sent per weekday. The table below holds the same numbers.">
        <BarChart data={data} margin={{ left: 0, right: 8 }}>
          <CartesianGrid vertical={false} strokeDasharray="3 3" />
          <XAxis dataKey="day" tickLine={false} axisLine={false} tickMargin={8} />
          <YAxis allowDecimals={false} tickLine={false} axisLine={false} width={32} />
          <ChartTooltip isAnimationActive={false} cursor={false} content={<ChartTooltipContent />} />
          <Bar dataKey="sent" fill="var(--color-sent)" radius={4} isAnimationActive={false} />
        </BarChart>
      </ChartContainer>
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>Day</TableHead>
            <TableHead className="text-end">Emails sent</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {data.map((row) => (
            <TableRow key={row.day}>
              <TableCell>{row.day}</TableCell>
              <TableCell className="text-end tabular-nums">{row.sent}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </figure>
  )
}
```

## Accessibility

**Semantics.** The chart is an SVG. Recharts 3 turns its accessibility layer on by default: the SVG gets `tabindex="0"` and `role="application"`. Nothing in it says what the numbers are.

**Labels.** Give every chart a name and a text alternative. Put the numbers in a table, or a sentence that states the finding, next to the chart (the Text alternative example). `role="img"` with an `aria-label` on the container names the chart as one picture and points to the table, but it makes the SVG's contents presentational, so the keyboard tooltip is no longer exposed. Pick the table: it carries the numbers.

**Focus.** With the accessibility layer on, the chart is a tab stop, and focus shows the tooltip at the first point. Left and Right Arrow move the tooltip between data points, and Enter shows or hides it. The focus ring is the browser's.

**Known limits.**

- A chart's numbers have no text alternative unless you write one. The tooltip and legend are visual; a screen-reader user cannot read the values from the SVG.
- Colour alone separates series. Add the legend, and use the tooltip's `indicator` (`dot`, `line`, `dashed`) or different line styles, so people who cannot tell the colours apart can still tell the series apart.
- The tooltip is a visual overlay shown on hover and from the keyboard. Anything people must read belongs in the table, not only in the tooltip.
- The five series colours are tuned for light and dark themes, not for contrast against each other. Do not rely on them for more than three series.

### Keyboard

| Key | Behaviour |
| --- | --- |
| Tab | Focuses the chart and shows the tooltip at the first data point. |
| Left Arrow + Right Arrow | Moves the tooltip to the previous or next data point, once the chart has focus. The accessibility layer is Recharts' own. |
| Enter | Shows or hides the tooltip at the current data point. |

## API

### ChartContainer

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `config` (required) | `ChartConfig` |  | Names and colours the series by key: `{ sent: { label: "Emails sent", color: "var(--chart-1)" } }`. Use `theme: { light, dark }` in place of `color` for per-theme values, and `icon` to show an icon in the legend and tooltip. |
| `initialDimension` | `{ width: number; height: number; }` | `{ width: 320, height: 200 } as const` | The width and height drawn before the chart is measured (default 320 by 200). Raise it where the chart renders on the server. |

### ChartTooltipContent

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` |  | If true, then Tooltip is always displayed, once an activeIndex is set by mouse over, or programmatically. If false, then Tooltip is never displayed. If undefined, Recharts will control when the Tooltip displays. This includes mouse and keyboard controls. |
| `allowEscapeViewBox` | `AllowInDimension` |  | This option allows the tooltip to extend beyond the viewBox of the chart itself. |
| `animationDuration` | `number` |  | Specifies the duration of animation, the unit of this option is ms. |
| `animationEasing` | `EasingInput` |  | The type of easing function. |
| `axisId` | `AxisId` |  | Tooltip always attaches itself to the "Tooltip" axis. Which axis is it? Depends on the layout: - horizontal layout -> X axis - vertical layout -> Y axis - radial layout -> radial axis - centric layout -> angle axis Tooltip will use the default axis for the layout, unless you specify an axisId. |
| `content` | `(ContentType<ValueType, NameType> & string)` |  | Renders the content of the tooltip. This should return HTML elements, not SVG elements. - If not set, the {@link DefaultTooltipContent } component is used. - If set to a React element, this element will be cloned and extra props will be passed in. - If set to a function, the function will be called and should return HTML elements. |
| `contentStyle` | `CSSProperties` |  | CSS styles to be applied to the wrapper `div` element. |
| `cursor` | `CursorDefinition` |  | If set false, no cursor will be drawn when tooltip is active. If set a object, the option is the configuration of cursor. If set a React element, the option is the custom react element of drawing cursor. |
| `defaultIndex` | `number \| TooltipIndex` |  |  |
| `filterNull` | `boolean` |  | When an item of the payload has value null or undefined, this item won't be displayed. |
| `formatter` | `Formatter<ValueType, NameType> \| ((value: ValueType, name: NameType, item: TooltipPayloadEntry, index: number, payload: TooltipPayload) => ReactNode \| [...])` |  | Function to customize the value in the tooltip. If you return an array, the first entry will be the formatted "value", and the second entry will be the formatted "name" |
| `hideIndicator` | `boolean` | `false` | Hides the marker beside each value. |
| `hideLabel` | `boolean` | `false` | Hides the heading line (the category, such as the day). |
| `includeHidden` | `boolean` |  | If true, the tooltip will display information about hidden series. Defaults to false. Interacting with the hide property of Area, Bar, Line, Scatter. |
| `indicator` | `"line" \| "dot" \| "dashed"` | `dot` | The marker beside each value: `dot` (default), `line` or `dashed`. |
| `isAnimationActive` | `boolean \| "auto"` |  | If set false, animation of tooltip will be disabled. If set "auto", the animation will be disabled in SSR and will respect the user's prefers-reduced-motion system preference for accessibility. |
| `itemSorter` | `"name" \| "value" \| "dataKey" \| ((item: Payload<ValueType, NameType>) => string \| number)` |  | Sorts tooltip items. Defaults to 'name' which means it sorts alphabetically by graphical item `name` property. |
| `itemStyle` | `CSSProperties` |  | Style of individual items inside the tooltip, a `<li>` element. These show the data label (name, or dataKey) and value. If a chart has multiple graphical items then the Tooltip renders multiple item and each of them gets this itemStyle applied. |
| `label` | `ReactNode` |  |  |
| `labelClassName` | `string` |  |  |
| `labelFormatter` | `((label: ReactNode, payload: readonly Payload<ValueType, NameType>[]) => ReactNode) \| ((label: ReactNode, payload: TooltipPayload) => ReactNode)` |  | The formatter function of label in tooltip. |
| `labelKey` | `string` |  | The data key that gives the heading, when it is not the axis key. |
| `labelStyle` | `CSSProperties` |  | "Label" is the tooltip title. Renders once on the top of tooltip and shows categorical axis value. Even if there are multiple graphical items in the chart, only one label gets rendered. Note that "Label" in tooltip is the header, which is different from {@link Legend } where "labelStyle" are the individual items. |
| `nameKey` | `string` |  | The data key that names each row, when it is not the series key. |
| `offset` | `number \| Coordinate` |  | The offset size between the position of tooltip and the mouse cursor position. When a number is provided, the same offset is applied to both x and y axes. When a Coordinate object is provided, you can specify different offsets for each axis (x and y as numbers) |
| `payload` | `readonly Payload<ValueType, TooltipNameType>[]` |  |  |
| `payloadUniqBy` | `UniqueOption<TooltipPayloadEntry>` |  |  |
| `portal` | `HTMLElement \| null` |  | If portal is defined, then Tooltip will use this element as a target for rendering using React Portal: https://react.dev/reference/react-dom/createPortal If this is undefined then Tooltip renders inside the recharts-wrapper element. |
| `position` | `Partial<Coordinate>` |  | If this field is set, the tooltip will be displayed at the specified position regardless of the mouse position. You can set a single field (x or y) and let the other field be calculated automatically based on the mouse position. |
| `reverseDirection` | `AllowInDimension` |  | @defaultValue {"x":false,"y":false} |
| `separator` | `string` |  | The separator between name and value. |
| `shared` | `boolean` |  | Defines whether the tooltip is reacting to the current data point, or to all data points at the current axis coordinate. - `true`: tooltip will appear on top of all bars on an axis tick. - `false`: tooltip will appear on individual bars. Different chart types allow different modes, and have different defaults. |
| `trigger` | `"hover" \| "click"` |  | If `hover` then the Tooltip shows on mouse enter and hides on mouse leave. If `click` then the Tooltip shows after clicking and stays active. |
| `useTranslate3d` | `boolean` |  | @defaultValue false |
| `wrapperClassName` | `string` |  |  |
| `wrapperStyle` | `CSSProperties` |  | CSS styles to be applied to the wrapper `div` element. |

### ChartLegendContent

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"center" \| "left" \| "right"` |  | @deprecated use `position` instead which has more options, is more flexible and has more intuitive default styles. |
| `formatter` | `Formatter` |  | Function to customize how content is serialized before rendering. This should return HTML elements, or strings. |
| `hideIcon` | `boolean` | `false` | Shows the coloured square, not the series icon. |
| `iconSize` | `number` |  | The size of icon in each legend item. |
| `iconType` | `"circle" \| "line" \| "rect" \| "none" \| "cross" \| "diamond" \| "plainline" \| "square" \| "star" \| "triangle" \| "wye"` |  | The type of icon in each legend item. |
| `inactiveColor` | `string` |  | The color of the icon when the item is inactive. |
| `labelStyle` | `CSSProperties` |  | Style of individual items inside the Legend, a `<span>` element. These show the data label (name, or dataKey) and value. If a chart has multiple graphical items then the Legend renders multiple item and each of them gets this itemStyle applied. Pie charts render multiple labels from a single data series. Note that this is different from {@link Tooltip }: - in Tooltip: "labelStyle" styles the title / header - in Tooltip: "itemStyle" styles the individual data points - in Legend: "labelStyle" styles the individual data points Beware of the naming inconsistency! |
| `layout` | `"horizontal" \| "vertical"` |  | The layout of legend items inside the legend container. |
| `nameKey` | `string` |  | The data key that names each entry, when it is not the series key. |
| `onPointerEnterCapture` | `AdaptChildPointerEventHandler<LegendPayload, ReactElement<unknown, string \| JSXElementConstructor<any>>>` |  |  |
| `onPointerLeaveCapture` | `AdaptChildPointerEventHandler<LegendPayload, ReactElement<unknown, string \| JSXElementConstructor<any>>>` |  |  |
| `payload` | `readonly LegendPayload[]` |  | DefaultLegendContent.payload is omitted from Legend props. A custom payload can be passed here if desired, or it can be passed from the Legend "content" callback. |
| `verticalAlign` | `"top" \| "bottom" \| "middle"` | `bottom` | @deprecated use `position` instead which has more options, is more flexible and has more intuitive default styles. |

### ChartStyle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `config` (required) | `ChartConfig` |  |  |
| `id` (required) | `string` |  |  |

**Also exported:** `ChartTooltip`, `Tooltip` from Recharts, re-exported; `ChartLegend`, `Legend` from Recharts, re-exported.

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="chart"` (ChartContainer), and `data-chart`.

## Theming

Series colours come from the `--chart-1` to `--chart-5` tokens of `theme.css`, set per theme. The container also styles Recharts' grid lines, axis text, tooltip cursor and dots with theme tokens, so a chart needs no colour of its own.

| Token | Used for |
| --- | --- |
| `--background` | background |
| `--border` | stroke, border |
| `--foreground` | text |
| `--muted` | fill |
| `--muted-foreground` | fill, text |
