Bar

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

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

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); 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).

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.

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.

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.

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.

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.

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

Keyboard

The keys of Chart apply here.

API

BarProps<T>

NameTypeDefaultDescription
valuerequiredValue<T>—Height - a number field of the row or a function of it; null/undefined/NaN/±Infinity means a gap.

Shown in: One bar per category, Group bars side by side, Show bars above and below zero and 13 more

xAxisIdstring"x"Binding to an x axis.

Shown in: Bind every kind of series to a second axis (Axis)

yAxisIdstring"y"Binding to a y axis.

Shown in: Bind every kind of series to a second axis (Axis)

datareadonly T[]—Series-own data; overrides the container data.

Shown in: Bind every kind of series to a second axis (Axis)

namestring"Series n", after its place in the chartThe name in legend and tooltip. Without one a warning stands in DEV - an unnamed series is a colour nobody can look up.

Shown in: One bar per category, Group bars side by side, Show bars above and below zero and 13 more

format(value: number) => stringthe y axis' tickFormat, else the built-in number formatThe value as the tooltip writes it.

Shown in: Show shares of a whole, Compare the budget with the forecast (Scenarios)

colorstringthe palette's --uc-series-NAny CSS colour value.

Shown in: Group bars side by side, See where the vans stand idle (Scenarios), Compare the budget with the forecast (Scenarios)

tone| "ok"
| "warning"
| "alarm"
—A role instead of a colour value; the theme resolves it. color beats it.

Shown in: Compare the budget with the forecast (Scenarios)

stackstring—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.

Shown in: Stack bars, Show shares of a whole, Read a stack's total in the table (Tooltip) and 1 more

normalizeboolean—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.

Shown in: Show shares of a whole

barWidthnumber0.8Width as a fraction of the step; just under 1, so that neighbours do not touch. Several bar series share this fraction.

Shown in: Group bars side by side, Count readings into a histogram, Sort causes and collect the tail (Pareto) and 3 more

Types on this page

Value<T>

A value read from the row (ADR-0048): the name of a number field, compared by its name, or a function, compared by its source text (ValueFunction).

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

NumberField<T>

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

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.

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

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/charts

Getting started

Chart

Series

Limits and alarms

Around the chart

API index