# Bar

> Package `@umriss-ui/charts`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/charts/bar/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/charts/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/charts/llms-full.txt).

Bars along the x axis (a bar or column chart): one per category, grouped side by side, stacked, or as shares of a whole.

```ts
import { useChart, bin } from "@umriss-ui/charts";
```

Demo page: https://romanhaendler.github.io/umriss-ui/charts/bar/

The x axis reads the category from the row - a cost centre, a weekday - and gives each a place, a tick and its name ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)); a number or an instant places a bar as well. A bar's foot is 0, so one series carries both signs. `bin()` counts values into the rows of a histogram: each bin's `from`, `to`, middle `x` and `count`, a value on an edge in the bin that edge starts, the last bin closed, values that are not numbers skipped; a `domain` is taken as given and what lies outside it is left out.

## Examples

### One bar per category

Give the x axis the field that holds the category: each one gets a place, a tick and its name, in the order of the rows ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)).

```tsx
import { Tooltip, useChart } from "@umriss-ui/charts";

/* February's spend per cost centre, as the ledger sends it. */
const SPEND = [
  { costCentre: "Sales", actual: 134_447 },
  { costCentre: "Marketing", actual: 76_588 },
  { costCentre: "Engineering", actual: 183_157 },
  { costCentre: "Design", actual: 51_761 },
  { costCentre: "Customer service", actual: 61_160 },
  { costCentre: "Finance", actual: 46_353 },
  { costCentre: "People", actual: 37_886 },
  { costCentre: "IT", actual: 74_313 },
  { costCentre: "Facilities", actual: 71_848 },
];

export default function PerCategory() {
  const { Chart, XAxis, YAxis, Bar } = useChart(SPEND);
  return (
    <Chart height={260} ariaLabel="Actual spend per cost centre in February">
      <XAxis value="costCentre" />
      <YAxis label="€" />
      <Bar value="actual" name="Actual" />
      <Tooltip mode="x" />
    </Chart>
  );
}
```

### Group bars side by side

Bar series on one x axis stand beside each other; give each the same `barWidth`, and the reference a quiet `color`.

```tsx
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";

/* February's budget and spend per cost centre, as the ledger sends them. */
const FEBRUARY = [
  { costCentre: "Sales", budget: 142_000, actual: 134_447 },
  { costCentre: "Marketing", budget: 68_000, actual: 76_588 },
  { costCentre: "Engineering", budget: 188_000, actual: 183_157 },
  { costCentre: "Design", budget: 54_000, actual: 51_761 },
  { costCentre: "Customer service", budget: 61_000, actual: 61_160 },
  { costCentre: "Finance", budget: 47_000, actual: 46_353 },
  { costCentre: "People", budget: 39_000, actual: 37_886 },
  { costCentre: "IT", budget: 83_000, actual: 74_313 },
  { costCentre: "Facilities", budget: 72_000, actual: 71_848 },
];

export default function Grouped() {
  const { Chart, XAxis, YAxis, Bar } = useChart(FEBRUARY);
  return (
    <Chart height={280} ariaLabel="Budget and actual per cost centre in February">
      <XAxis value="costCentre" />
      <YAxis label="€" />
      {/* A token, so that it follows the colour scheme. */}
      <Bar value="budget" name="Budget" color="var(--uc-color-text)" barWidth={0.7} />
      <Bar value="actual" name="Actual" barWidth={0.7} />
      <Legend placement="top" />
      <Tooltip mode="x" />
    </Chart>
  );
}
```

### Show bars above and below zero

A bar's foot is 0, not the bottom of the axis, so one series carries both signs - here the year's forecast against budget.

```tsx
import { Tooltip, useChart } from "@umriss-ui/charts";

/* The year's forecast less its budget per cost centre, in euros - summed
   where the ledger lives, before the chart. */
const DEVIATION = [
  { costCentre: "Sales", euros: 60 },
  { costCentre: "Marketing", euros: 118_840 },
  { costCentre: "Engineering", euros: 1_440 },
  { costCentre: "Design", euros: 720 },
  { costCentre: "Customer service", euros: 980 },
  { costCentre: "Finance", euros: -1_940 },
  { costCentre: "People", euros: -1_480 },
  { costCentre: "IT", euros: -112_560 },
  { costCentre: "Facilities", euros: -2_240 },
];

const signed = (v: number) => (v > 0 ? `+${(v / 1000).toFixed(0)}k` : `${(v / 1000).toFixed(0)}k`);

export default function Deviation() {
  const { Chart, XAxis, YAxis, Bar } = useChart(DEVIATION);
  return (
    <Chart height={280} ariaLabel="Forecast against budget for the year, per cost centre">
      <XAxis value="costCentre" />
      <YAxis tickFormat={signed} label="€" />
      <Bar value="euros" name="Over budget" />
      <Tooltip mode="x" />
    </Chart>
  );
}
```

### Stack bars

Bars with the same `stack` stand on the ones before them; a missing part stacks as nothing, and the tooltip names what is there. A cause hidden from the legend leaves no gap in the stack.

```tsx
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";

/* Minutes late across all tours per working day, by cause - two weeks, 2 to
   13 March. On the first Wednesday the loading log was not kept: a gap, not
   a zero. On the second Thursday a closed bridge takes the day. */
const DELAYS = [
  { day: "Mon 2", traffic: 61, loading: 32, access: 10 },
  { day: "Tue 3", traffic: 47, loading: 46, access: 27 },
  { day: "Wed 4", traffic: 49, loading: null, access: 22 },
  { day: "Thu 5", traffic: 40, loading: 44, access: 7 },
  { day: "Fri 6", traffic: 48, loading: 44, access: 27 },
  { day: "Mon 9", traffic: 67, loading: 41, access: 8 },
  { day: "Tue 10", traffic: 58, loading: 50, access: 13 },
  { day: "Wed 11", traffic: 58, loading: 18, access: 14 },
  { day: "Thu 12", traffic: 168, loading: 26, access: 10 },
  { day: "Fri 13", traffic: 57, loading: 18, access: 18 },
];

export default function Stacked() {
  const { Chart, XAxis, YAxis, Bar } = useChart(DELAYS);

  return (
    <Chart height={280} ariaLabel="Minutes late per working day, by cause">
      <XAxis value="day" />
      <YAxis label="Minutes late" />
      <Bar value="traffic" name="Traffic" stack="delay" />
      <Bar value="loading" name="Loading" stack="delay" />
      <Bar value="access" name="No access" stack="delay" />
      <Legend placement="top" />
      <Tooltip mode="x" />
    </Chart>
  );
}
```

### Show shares of a whole

`normalize` makes each stack sum to 100 %, so the question moves from how long to of what; `format` still writes minutes.

```tsx
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";

/* Minutes late across all tours per working day, by cause - two weeks, 2 to
   13 March. On the first Wednesday the loading log was not kept: a gap, not
   a zero. On the second Thursday a closed bridge takes the day. */
const DELAYS = [
  { day: "Mon 2", traffic: 61, loading: 32, access: 10 },
  { day: "Tue 3", traffic: 47, loading: 46, access: 27 },
  { day: "Wed 4", traffic: 49, loading: null, access: 22 },
  { day: "Thu 5", traffic: 40, loading: 44, access: 7 },
  { day: "Fri 6", traffic: 48, loading: 44, access: 27 },
  { day: "Mon 9", traffic: 67, loading: 41, access: 8 },
  { day: "Tue 10", traffic: 58, loading: 50, access: 13 },
  { day: "Wed 11", traffic: 58, loading: 18, access: 14 },
  { day: "Thu 12", traffic: 168, loading: 26, access: 10 },
  { day: "Fri 13", traffic: 57, loading: 18, access: 18 },
];

const minutes = (v: number) => `${v} min`;

export default function Percent() {
  const { Chart, XAxis, YAxis, Bar } = useChart(DELAYS);
  return (
    <Chart height={280} ariaLabel="Share of each cause in the minutes late per working day">
      <XAxis value="day" />
      <YAxis />
      <Bar value="traffic" name="Traffic" stack="delay" normalize format={minutes} />
      <Bar value="loading" name="Loading" stack="delay" normalize format={minutes} />
      <Bar value="access" name="No access" stack="delay" normalize format={minutes} />
      <Legend placement="top" />
      <Tooltip mode="x" />
    </Chart>
  );
}
```

### Count readings into a histogram

`bin()` counts values into bins of one width on a tidy grid and returns each bin's bounds and count; a `Bar` on the numeric x axis draws them at `x`.

```tsx
import { useMemo } from "react";
import { Tooltip, bin, useChart } from "@umriss-ui/charts";

/* How long 40 support tickets took to answer, in minutes, as the helpdesk exports them. */
const MINUTES_TO_ANSWER = [
  4, 7, 9, 11, 12, 12, 14, 15, 15, 17, 18, 19, 21, 22, 22, 23, 24, 26, 27, 28,
  29, 31, 33, 34, 36, 38, 41, 43, 47, 52, 55, 58, 63, 71, 78, 84, 96, 112, 135, 171,
];

export default function Histogram() {
  const bins = useMemo(() => bin(MINUTES_TO_ANSWER, { width: 20 }), []);
  const { Chart, XAxis, YAxis, Bar } = useChart(bins);
  return (
    <Chart height={260} ariaLabel="Minutes to answer 40 support tickets, counted in bins of 20 minutes">
      <XAxis value="x" label="minutes to answer" />
      <YAxis label="tickets" />
      <Bar value="count" name="Tickets" barWidth={0.94} />
      <Tooltip mode="x" />
    </Chart>
  );
}
```

## When to use something else

- Causes ranked by how much they contribute → Pareto

## Keyboard

The keys of [Chart](https://romanhaendler.github.io/umriss-ui/charts/chart/#keyboard-chart) apply here.

## API

### `BarProps<T>`

| Prop | Type | Default | Description |
|---|---|---|---|
| `value` *required* | [`Value`](https://romanhaendler.github.io/umriss-ui/charts/bar/#type-Value)`<T>` | — | Height - a number field of the row or a function of it; null/undefined/NaN/±Infinity means a gap.<br>Shown in: [One bar per category](https://romanhaendler.github.io/umriss-ui/charts/bar/#per-category), [Group bars side by side](https://romanhaendler.github.io/umriss-ui/charts/bar/#grouped), [Show bars above and below zero](https://romanhaendler.github.io/umriss-ui/charts/bar/#deviation) and 13 more |
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](https://romanhaendler.github.io/umriss-ui/charts/axis/#every-kind-on-a-second-axis) |
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](https://romanhaendler.github.io/umriss-ui/charts/axis/#every-kind-on-a-second-axis) |
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Bind every kind of series to a second axis (Axis)](https://romanhaendler.github.io/umriss-ui/charts/axis/#every-kind-on-a-second-axis) |
| `name` | `string` | "Series n", after its place in the chart | The name in legend and tooltip. Without one a warning stands in DEV - an unnamed series is a colour nobody can look up.<br>Shown in: [One bar per category](https://romanhaendler.github.io/umriss-ui/charts/bar/#per-category), [Group bars side by side](https://romanhaendler.github.io/umriss-ui/charts/bar/#grouped), [Show bars above and below zero](https://romanhaendler.github.io/umriss-ui/charts/bar/#deviation) and 13 more |
| `format` | `(value: number) => string` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it.<br>Shown in: [Show shares of a whole](https://romanhaendler.github.io/umriss-ui/charts/bar/#percent), [Compare the budget with the forecast (Scenarios)](https://romanhaendler.github.io/umriss-ui/charts/#compare-budget-with-forecast) |
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value.<br>Shown in: [Group bars side by side](https://romanhaendler.github.io/umriss-ui/charts/bar/#grouped), [See where the vans stand idle (Scenarios)](https://romanhaendler.github.io/umriss-ui/charts/#see-where-the-vans-stand-idle), [Compare the budget with the forecast (Scenarios)](https://romanhaendler.github.io/umriss-ui/charts/#compare-budget-with-forecast) |
| `tone` | `"ok" \| "warning" \| "alarm"` | — | A role instead of a colour value; the theme resolves it. `color` beats it.<br>Shown in: [Compare the budget with the forecast (Scenarios)](https://romanhaendler.github.io/umriss-ui/charts/#compare-budget-with-forecast) |
| `stack` | `string` | — | The stack this series stands in: every Bar and Area with the same id on the same x and y axis stands on the ones registered before it. A gap stacks as zero; negative values stack downward from zero. The tooltip names each series' own value and the stack's total.<br>Shown in: [Stack bars](https://romanhaendler.github.io/umriss-ui/charts/bar/#stacked), [Show shares of a whole](https://romanhaendler.github.io/umriss-ui/charts/bar/#percent), [Read a stack's total in the table (Tooltip)](https://romanhaendler.github.io/umriss-ui/charts/tooltip/#total-of-a-stack) and 1 more |
| `normalize` | `boolean` | — | On any member of a stack, every x of the stack sums to 100 %: each value becomes its share, and the y axis reads in percent unless it has a `tickFormat`. The total stays the readings' own sum, written in the series' `format`.<br>Shown in: [Show shares of a whole](https://romanhaendler.github.io/umriss-ui/charts/bar/#percent) |
| `barWidth` | `number` | `0.8` | Width as a fraction of the step; just under 1, so that neighbours do not touch. Several bar series share this fraction.<br>Shown in: [Group bars side by side](https://romanhaendler.github.io/umriss-ui/charts/bar/#grouped), [Count readings into a histogram](https://romanhaendler.github.io/umriss-ui/charts/bar/#histogram), [Sort causes and collect the tail (Pareto)](https://romanhaendler.github.io/umriss-ui/charts/pareto/#pareto) and 3 more |

### Types on this page

#### `Value<T>`

A value read from the row ([ADR-0048](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0048-charts-are-bound-to-their-rows-by-the-hook.md)): the name of a number field, compared
by its name, or a function, compared by its source text (`ValueFunction`).

```ts
type Value<T> = NumberField<T> | ValueFunction<T>;
```

#### `NumberField<T>`

The fields of a row whose value is a number, or absent.

```ts
type NumberField<T> = NoInfer<
  {
    [K in keyof T & string]-?: T[K] extends number | null | undefined ? K : never;
  }[keyof T & string]
>;
```

#### `ValueFunction<T>`

A **value** computed from the row - the function form of `Value`.
Compared by its source text, not its identity - an inline function is new
on every render. Known limit: one that reads a
changed closure variable under the same text is not run again; pass a new
data reference then. A native or bound function is compared by identity.

```ts
type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
```

## Known limits

- Horizontal bars are the next kind to come ([ADR-0059](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0059-the-set-of-chart-kinds-is-open.md)); until then a `Matrix` or a `Table` ranks by name.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
