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.
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
The keys of Chart apply here.
BarProps<T>| Name | Type | Default | Description |
|---|---|---|---|
valuerequired | Value<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 |
xAxisId | string | "x" | Binding to an x axis. |
yAxisId | string | "y" | Binding to a y axis. |
data | readonly T[] | — | Series-own data; overrides the container data. |
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. Shown in: One bar per category, Group bars side by side, Show bars above and below zero and 13 more |
format | (value: number) => string | the y axis' tickFormat, else the built-in number format | The value as the tooltip writes it. Shown in: Show shares of a whole, Compare the budget with the forecast (Scenarios) |
color | string | the palette's --uc-series-N | Any 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" | — | A role instead of a colour value; the theme resolves it. color beats
it. |
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. Shown in: Stack bars, Show shares of a whole, Read a stack's total in the table (Tooltip) 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.Shown in: Show shares of a whole |
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. Shown in: Group bars side by side, Count readings into a histogram, Sort causes and collect the tail (Pareto) and 3 more |
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;Matrix or a Table ranks by name.What umriss deliberately does not build, and why: ADR-0032.