# Bar chart

Dithered bars with a solid cap, grouped or stacked, for weekly counts and period comparisons.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

<ComponentPreview src="examples/bar-chart-demo.tsx" size="lg" controls="barChartControls">
  <BarChartDemo />
</ComponentPreview>

## Installation [#installation]

<InstallBlock name="bar-chart" />

## Usage [#usage]

```tsx
import { BarChart } from "@/components/dithercn/bar-chart";

const data = [
  { week: "W36", deploys: 61 },
  { week: "W37", deploys: 68 },
  { week: "W38", deploys: 74 },
];

export function Deploys() {
  return (
    <BarChart
      data={data}
      x="week"
      series={[{ label: "Deploys", value: "deploys" }]}
    />
  );
}
```

Each bar is a dithered body under a solid 2px cap. One bar is drawn solid, the last by default, with its value above it. When the pointer or keyboard focus moves to another bar, that one fills in instead. `x` and each series' `value` name fields of your data, or take a function when you render from a Client Component.

## Examples [#examples]

### Stacked [#stacked]

`layout="stacked"` puts the series on top of each other with a 2px gap between segments, and the axis covers the totals. `highlight="max"` picks the tallest stack.

<ComponentPreview src="examples/bar-chart-stacked.tsx" size="lg">
  <BarChartStacked />
</ComponentPreview>

### Grouped comparison [#grouped-comparison]

Grouped bars sit side by side, one per series. `highlight="none"` leaves every bar dithered until you scrub to one, and `fill="hatched"` gives each bar diagonal lines instead of the gradient.

<ComponentPreview src="examples/bar-chart-grouped.tsx" size="lg">
  <BarChartGrouped />
</ComponentPreview>

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` (required) | `readonly T[]` |  | One entry per bar or group of bars, in order. |
| `x` (required) | `keyof T \| ((datum: T) => string \| number)` |  | The field (or function) holding each bar's category label. |
| `series` (required) | `[ChartSeries<T>, ...ChartSeries<T>[]]` |  | Each series is { label, value, color? }, where value is a numeric field of T or a function. |
| `layout` | `"grouped" \| "stacked" \| "percent"` | `"grouped"` | How several series share a category: side by side, on top of each other, or stacked and scaled to 100%. |
| `fill` | `"gradient" \| "dotted" \| "hatched" \| "solid"` | `"gradient"` | gradient is densest at the top of each bar and thins toward the axis. dotted is a sparse lattice, hatched diagonal lines, solid flat colour. |
| `bloom` | `"off" \| "low" \| "high"` | `"off"` | A soft glow in each series' colour behind its fill. Its strength follows --dither-bloom, lower in light mode. |
| `colors` | `readonly DitherColor[]` | `ink, orange, green, pink, …` | Colours by series position: palette names such as "green" or any CSS colour. A series' own color wins. |
| `interactive` | `boolean` | `true` | Scrubbing, the readout and the legend spotlight. false makes a decorative chart: no focus stop, no readout, just an image with a summary. |
| `animate` | `boolean` | `true` | Entrance motion. Always off under reduced motion. |
| `highlight` | `number \| "last" \| "max" \| "none"` | `"last"` | The bar drawn solid, with its value, while nothing is scrubbed. |
| `height` | `number` | `240` | Height in px, axes included. |
| `level` | `number` | `0.75` | Gradient only: coverage at the top of each bar, from 0 to 1. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | Gradient only: the dither texture. |
| `cell` | `number` | `--dither-cell` | Size of one dither cell in px, rounded to whole device pixels. |
| `axes` | `boolean` | `true` | Show the value and category axes. |
| `tooltip` | `boolean` | `true` | Show the readout while scrubbing. |
| `legend` | `boolean` | `Two or more series` | Show the legend under the chart. Its entries spotlight a series on hover and lock it on click. |
| `format` | `Intl.NumberFormatOptions \| ((value: number) => string)` | `Compact, as in 1.2K` | How values are written on the axis, in the readout and in the table. |

Series colours follow the palette: ink, then orange, green, pink, purple, blue, red and grey. Pass `colors`, or `color` on one series, with a palette name or any CSS colour.

## Notes [#notes]

* The chart is a focusable group with a short summary, and every value is in a visually hidden table. The arrow keys, Home and End move between bars, a polite live region reads out each stop, and Escape clears the readout and any locked series. The scrubbed column gets a soft band and fills in solid.
* Bars rise from the baseline one after another on entrance. With reduced motion they appear in place.
* Bars are capped at 36px wide and snapped to whole pixels, and the dither cell to whole device pixels, so edges and cells stay sharp.