@umriss-ui/charts 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +5 -1
- package/dist/charts.js +1761 -1523
- package/dist/index.d.ts +125 -48
- package/docs/llms-full.md +496 -780
- package/package.json +2 -2
package/docs/llms-full.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @umriss-ui/charts 0.
|
|
1
|
+
# @umriss-ui/charts 0.14.0
|
|
2
2
|
|
|
3
3
|
> React canvas chart library for data-dense dashboards - line, area, bar, box plot, scatter, control and Pareto charts. Few chart kinds, drawn well, fast.
|
|
4
4
|
|
|
@@ -400,9 +400,6 @@ const FLEET = VEHICLES.map((vehicle) => {
|
|
|
400
400
|
return { vehicle, points: [...points, { t: NOW, state: null }], idle, driving: points.at(-1)?.state === DRIVING };
|
|
401
401
|
});
|
|
402
402
|
|
|
403
|
-
/* Lane 0 lies at the bottom, so the first vehicle takes the top lane. */
|
|
404
|
-
const lane = (index: number) => FLEET.length - 1 - index;
|
|
405
|
-
const plateOnLane = (v: number) => FLEET[FLEET.length - 1 - Math.floor(v)]?.vehicle.plate ?? "";
|
|
406
403
|
const depotOf = (id: string) => DEPOTS.find((one) => one.id === id)?.name ?? id;
|
|
407
404
|
|
|
408
405
|
const IDLE_TODAY = FLEET.reduce((sum, one) => sum + one.idle, 0) / 60;
|
|
@@ -413,20 +410,14 @@ function FleetLanes() {
|
|
|
413
410
|
return (
|
|
414
411
|
<Chart height={320} ariaLabel="What each vehicle has done today, lane by lane">
|
|
415
412
|
<XAxis value="t" time domain={DAY} />
|
|
416
|
-
<YAxis
|
|
417
|
-
|
|
418
|
-
ticks={FLEET.map((_, i) => i + 0.5)}
|
|
419
|
-
tickFormat={plateOnLane}
|
|
420
|
-
grid={false}
|
|
421
|
-
/>
|
|
422
|
-
{FLEET.map((one, i) => (
|
|
413
|
+
<YAxis grid={false} />
|
|
414
|
+
{FLEET.map((one) => (
|
|
423
415
|
<StateBand
|
|
424
416
|
key={one.vehicle.id}
|
|
425
417
|
data={one.points}
|
|
426
418
|
value="state"
|
|
427
419
|
states={VEHICLE_STATES}
|
|
428
|
-
|
|
429
|
-
laneTo={lane(i) + 0.88}
|
|
420
|
+
lane={one.vehicle.plate}
|
|
430
421
|
name={`${one.vehicle.plate} (${one.vehicle.type}, ${depotOf(one.vehicle.depot)})`}
|
|
431
422
|
/>
|
|
432
423
|
))}
|
|
@@ -440,11 +431,7 @@ function IdleMinutes() {
|
|
|
440
431
|
const { Chart, XAxis, YAxis, Bar } = useChart(FLEET);
|
|
441
432
|
return (
|
|
442
433
|
<Chart height={200} ariaLabel="Idle minutes per vehicle so far today">
|
|
443
|
-
<XAxis
|
|
444
|
-
value={(_d, i) => i}
|
|
445
|
-
ticks={FLEET.map((_, i) => i)}
|
|
446
|
-
tickFormat={(v) => FLEET[v]?.vehicle.plate ?? ""}
|
|
447
|
-
/>
|
|
434
|
+
<XAxis value={(d) => d.vehicle.plate} />
|
|
448
435
|
<YAxis label="min" tickCount={4} />
|
|
449
436
|
<Bar value="idle" name="Idle" color={VEHICLE_STATES[IDLE]!.color} barWidth={0.6} />
|
|
450
437
|
<Tooltip mode="x" />
|
|
@@ -493,86 +480,23 @@ import { Card, CardBody, CardHeader, Grid, Stack, Stat } from "@umriss-ui/core";
|
|
|
493
480
|
import type { LimitSet } from "@umriss-ui/core";
|
|
494
481
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
495
482
|
|
|
496
|
-
/* Data from the controlling world, written out here so the example runs on its own. */
|
|
497
|
-
/** A small LCG - the same numbers on every computer. */
|
|
498
|
-
function random(seed: number): () => number {
|
|
499
|
-
let s = seed >>> 0;
|
|
500
|
-
return () => {
|
|
501
|
-
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
502
|
-
return s / 4294967296;
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
interface CostCentre {
|
|
507
|
-
id: string;
|
|
508
|
-
name: string;
|
|
509
|
-
owner: string;
|
|
510
|
-
/** The budget of one month. */
|
|
511
|
-
monthlyBudget: number;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const COST_CENTRES: readonly CostCentre[] = [
|
|
515
|
-
{ id: "CC-1100", name: "Sales", owner: "Helen Marsh", monthlyBudget: 142_000 },
|
|
516
|
-
{ id: "CC-1200", name: "Marketing", owner: "Rafael Ortiz", monthlyBudget: 68_000 },
|
|
517
|
-
{ id: "CC-2100", name: "Engineering", owner: "Anika Sørensen", monthlyBudget: 188_000 },
|
|
518
|
-
{ id: "CC-2200", name: "Design", owner: "Paul Whitaker", monthlyBudget: 54_000 },
|
|
519
|
-
{ id: "CC-3100", name: "Customer service", owner: "Grace Obi", monthlyBudget: 61_000 },
|
|
520
|
-
{ id: "CC-4100", name: "Finance", owner: "Martina Vogel", monthlyBudget: 47_000 },
|
|
521
|
-
{ id: "CC-4200", name: "People", owner: "Daniel Frost", monthlyBudget: 39_000 },
|
|
522
|
-
{ id: "CC-4300", name: "IT", owner: "Kenji Arai", monthlyBudget: 83_000 },
|
|
523
|
-
{ id: "CC-4400", name: "Facilities", owner: "Olga Ivanova", monthlyBudget: 72_000 },
|
|
524
|
-
];
|
|
525
|
-
|
|
526
|
-
/** The year's months, as `"2026-01"` … `"2026-12"`. */
|
|
527
|
-
const MONTHS: readonly string[] = Array.from({ length: 12 }, (_, i) => `2026-${String(i + 1).padStart(2, "0")}`);
|
|
528
|
-
|
|
529
|
-
/** The months whose books are closed: they have an actual. */
|
|
530
|
-
const CLOSED_MONTHS = 2;
|
|
531
|
-
|
|
532
|
-
interface LedgerRow {
|
|
533
|
-
costCentre: string;
|
|
534
|
-
month: string;
|
|
535
|
-
budget: number;
|
|
536
|
-
/** `null` while the month is open. */
|
|
537
|
-
actual: number | null;
|
|
538
|
-
forecast: number;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/* Where a cost centre lands against its budget, as a factor: Marketing's
|
|
542
|
-
spring campaign overspends, IT's delayed licences underspend. */
|
|
543
|
-
const TENDENCY: Readonly<Record<string, number>> = { "CC-1200": 1.14, "CC-4300": 0.88 };
|
|
544
|
-
|
|
545
|
-
/** Budget, actual and forecast of every cost centre in every month. */
|
|
546
|
-
const LEDGER: readonly LedgerRow[] = COST_CENTRES.flatMap((centre, c) => {
|
|
547
|
-
const r = random(310 + c);
|
|
548
|
-
const tendency = TENDENCY[centre.id] ?? 1;
|
|
549
|
-
return MONTHS.map((month, m) => {
|
|
550
|
-
/* December pays the bonuses, August is the quiet month. */
|
|
551
|
-
const season = m === 11 ? 1.12 : m === 7 ? 0.9 : 1;
|
|
552
|
-
const budget = Math.round(centre.monthlyBudget * season);
|
|
553
|
-
const forecast = Math.round((budget * (tendency + (r() - 0.5) * 0.06)) / 100) * 100;
|
|
554
|
-
const actual = m < CLOSED_MONTHS ? Math.round(forecast * (1 + (r() - 0.5) * 0.08)) : null;
|
|
555
|
-
return { costCentre: centre.id, month, budget, actual, forecast };
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
|
|
559
483
|
const thousands = (euros: number) => euros / 1000;
|
|
560
|
-
const outlook = (row: LedgerRow) => row.actual ?? row.forecast;
|
|
561
484
|
const euros = (v: number) => `${v.toLocaleString("en-GB", { maximumFractionDigits: 1 })} k€`;
|
|
562
485
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
outlook:
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
486
|
+
/* The year per cost centre, in euros, as the ledger sums it: the budget, and
|
|
487
|
+
the outlook - closed months at their actual, open months at their forecast. */
|
|
488
|
+
const YEAR = [
|
|
489
|
+
{ name: "Sales", budget: 1_706_840, outlook: 1_701_784 },
|
|
490
|
+
{ name: "Marketing", budget: 817_360, outlook: 934_111 },
|
|
491
|
+
{ name: "Engineering", budget: 2_259_760, outlook: 2_258_122 },
|
|
492
|
+
{ name: "Design", budget: 649_080, outlook: 649_484 },
|
|
493
|
+
{ name: "Customer service", budget: 733_220, outlook: 734_416 },
|
|
494
|
+
{ name: "Finance", budget: 564_940, outlook: 563_659 },
|
|
495
|
+
{ name: "People", budget: 468_780, outlook: 468_251 },
|
|
496
|
+
{ name: "IT", budget: 997_660, outlook: 881_804 },
|
|
497
|
+
{ name: "Facilities", budget: 865_440, outlook: 860_691 },
|
|
498
|
+
];
|
|
499
|
+
const gap = (one: { budget: number; outlook: number }) => one.outlook - one.budget;
|
|
576
500
|
|
|
577
501
|
const BUDGET = YEAR.reduce((sum, one) => sum + one.budget, 0);
|
|
578
502
|
const OUTLOOK = YEAR.reduce((sum, one) => sum + one.outlook, 0);
|
|
@@ -586,24 +510,37 @@ const GAP_LIMITS: LimitSet = {
|
|
|
586
510
|
],
|
|
587
511
|
};
|
|
588
512
|
|
|
589
|
-
const MARKETING =
|
|
590
|
-
|
|
591
|
-
|
|
513
|
+
const MARKETING = { id: "CC-1200", owner: "Rafael Ortiz" };
|
|
514
|
+
|
|
515
|
+
/* Marketing's ledger month by month, in euros; `actual` is `null` while the
|
|
516
|
+
month's books are open. */
|
|
517
|
+
const MONTHLY = [
|
|
518
|
+
{ month: "2026-01", budget: 68_000, actual: 76_823, forecast: 76_900 },
|
|
519
|
+
{ month: "2026-02", budget: 68_000, actual: 76_588, forecast: 78_600 },
|
|
520
|
+
{ month: "2026-03", budget: 68_000, actual: null, forecast: 78_200 },
|
|
521
|
+
{ month: "2026-04", budget: 68_000, actual: null, forecast: 76_100 },
|
|
522
|
+
{ month: "2026-05", budget: 68_000, actual: null, forecast: 77_100 },
|
|
523
|
+
{ month: "2026-06", budget: 68_000, actual: null, forecast: 77_800 },
|
|
524
|
+
{ month: "2026-07", budget: 68_000, actual: null, forecast: 78_400 },
|
|
525
|
+
{ month: "2026-08", budget: 61_200, actual: null, forecast: 69_600 },
|
|
526
|
+
{ month: "2026-09", budget: 68_000, actual: null, forecast: 79_200 },
|
|
527
|
+
{ month: "2026-10", budget: 68_000, actual: null, forecast: 78_400 },
|
|
528
|
+
{ month: "2026-11", budget: 68_000, actual: null, forecast: 77_200 },
|
|
529
|
+
{ month: "2026-12", budget: 76_160, actual: null, forecast: 88_700 },
|
|
530
|
+
];
|
|
531
|
+
/** `"2026-03"` as the axis writes it. */
|
|
532
|
+
const monthName = (month: string) => new Date(`${month}-01T00:00`).toLocaleString("en-GB", { month: "short" });
|
|
592
533
|
|
|
593
534
|
function GapPerCentre() {
|
|
594
535
|
const { Chart, XAxis, YAxis, Bar } = useChart(YEAR);
|
|
595
536
|
return (
|
|
596
537
|
<Chart height={240} ariaLabel="Outlook minus budget for the year, per cost centre">
|
|
597
|
-
<XAxis
|
|
598
|
-
value={(_d, i) => i}
|
|
599
|
-
ticks={YEAR.map((_, i) => i)}
|
|
600
|
-
tickFormat={(v) => YEAR[v]?.name ?? ""}
|
|
601
|
-
/>
|
|
538
|
+
<XAxis value="name" />
|
|
602
539
|
<YAxis label="k€" tickFormat={euros} />
|
|
603
540
|
{/* One series per sign, stacked on one another so that each bar stands centred on its cost centre. */}
|
|
604
|
-
<Bar value={(d) => (d
|
|
541
|
+
<Bar value={(d) => (gap(d) > 0 ? thousands(gap(d)) : null)} name="Over budget" tone="alarm" stack="gap" format={euros} />
|
|
605
542
|
<Bar
|
|
606
|
-
value={(d) => (d
|
|
543
|
+
value={(d) => (gap(d) <= 0 ? thousands(gap(d)) : null)}
|
|
607
544
|
name="Under budget"
|
|
608
545
|
color="var(--uc-color-text)"
|
|
609
546
|
stack="gap"
|
|
@@ -619,11 +556,7 @@ function MarketingByMonth() {
|
|
|
619
556
|
const { Chart, XAxis, YAxis, Bar, Line } = useChart(MONTHLY);
|
|
620
557
|
return (
|
|
621
558
|
<Chart height={260} ariaLabel="Marketing's budget, actual and forecast per month">
|
|
622
|
-
<XAxis
|
|
623
|
-
value={(_d, i) => i}
|
|
624
|
-
ticks={MONTHLY.map((_, i) => i)}
|
|
625
|
-
tickFormat={(v) => MONTH_NAMES[v] ?? ""}
|
|
626
|
-
/>
|
|
559
|
+
<XAxis value="month" categoryLabel={monthName} />
|
|
627
560
|
<YAxis label="k€" tickFormat={euros} />
|
|
628
561
|
<Bar value={(d) => thousands(d.budget)} name="Budget" color="var(--uc-color-text)" barWidth={0.7} format={euros} />
|
|
629
562
|
<Bar value={(d) => (d.actual === null ? null : thousands(d.actual))} name="Actual" barWidth={0.7} format={euros} />
|
|
@@ -784,8 +717,8 @@ const BURNT = BURNDOWN[0]!.remaining! - LEFT;
|
|
|
784
717
|
const DAYS_DONE = BOOKED.length - 1;
|
|
785
718
|
const DAYS_LEFT = BURNDOWN.length - BOOKED.length;
|
|
786
719
|
|
|
787
|
-
|
|
788
|
-
const
|
|
720
|
+
/** A working day as the axis names it: a category, so the weekend takes no room. */
|
|
721
|
+
const dayOf = (one: { t: number }) => new Date(one.t).toLocaleDateString("en-GB", { weekday: "short", day: "numeric" });
|
|
789
722
|
|
|
790
723
|
interface Holder {
|
|
791
724
|
name: string;
|
|
@@ -801,12 +734,7 @@ function SprintBurnDown() {
|
|
|
801
734
|
const { Chart, XAxis, YAxis, Line } = useChart(BURNDOWN);
|
|
802
735
|
return (
|
|
803
736
|
<Chart height={280} ariaLabel={`${SPRINT.name}: hours left per working day against an even pace`}>
|
|
804
|
-
<XAxis
|
|
805
|
-
value={(_d, i) => i}
|
|
806
|
-
ticks={BURNDOWN.map((_, i) => i)}
|
|
807
|
-
tickFormat={day}
|
|
808
|
-
label="Working day"
|
|
809
|
-
/>
|
|
737
|
+
<XAxis value={dayOf} label="Working day" />
|
|
810
738
|
<YAxis label="Hours left" domain={[0, BURNDOWN[0]!.ideal]} />
|
|
811
739
|
<Line value="ideal" name="Even pace" color="var(--uc-color-text)" dash={[4, 4]} strokeWidth={1} />
|
|
812
740
|
<Line value="remaining" name="Left" markers="always" strokeWidth={2} />
|
|
@@ -820,11 +748,7 @@ function OpenPerPerson() {
|
|
|
820
748
|
const { Chart, XAxis, YAxis, Bar } = useChart(HOLDERS);
|
|
821
749
|
return (
|
|
822
750
|
<Chart height={200} ariaLabel="Estimated hours of unfinished items per person">
|
|
823
|
-
<XAxis
|
|
824
|
-
value={(_d, i) => i}
|
|
825
|
-
ticks={HOLDERS.map((_, i) => i)}
|
|
826
|
-
tickFormat={(v) => HOLDERS[v]?.name ?? ""}
|
|
827
|
-
/>
|
|
751
|
+
<XAxis value="name" />
|
|
828
752
|
<YAxis label="h" tickCount={4} />
|
|
829
753
|
<Bar value="open" name="Estimate" barWidth={0.6} />
|
|
830
754
|
<Tooltip mode="x" />
|
|
@@ -846,7 +770,7 @@ export default function BurnDown() {
|
|
|
846
770
|
<Stack gap={2}>
|
|
847
771
|
<SprintBurnDown />
|
|
848
772
|
<Text size="sm" tone="muted">
|
|
849
|
-
Ten working days, {
|
|
773
|
+
Ten working days, {dayOf(BURNDOWN[0]!)} to {dayOf(BURNDOWN.at(-1)!)}; the weekend between them is left out.
|
|
850
774
|
</Text>
|
|
851
775
|
</Stack>
|
|
852
776
|
</CardBody>
|
|
@@ -1137,7 +1061,7 @@ The command installs the package alone: React 18 or 19 is its only peer and stay
|
|
|
1137
1061
|
|
|
1138
1062
|
A chart takes its container's width and follows it; give it a `height` (300 px without one). `useChart(rows)` hands out `Chart`, its axes and its series, typed at the row; each reads its value from a row through `value`, a field name or a function - a value that is `null`, `undefined` or not finite is a gap, never a zero.
|
|
1139
1063
|
|
|
1140
|
-
|
|
1064
|
+
An instant is a `Date`, milliseconds since 1970, or - where the x axis says `time` - the ISO string an API sends ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). On a time axis the ticks stand on the viewer's local clock, so a day begins at local midnight. The words are English; German comes from `@umriss-ui/charts/wording/de`.
|
|
1141
1065
|
|
|
1142
1066
|
The charts take German per chart: `wording={GERMAN_CHARTS_WORDING}` from `@umriss-ui/charts/wording/de`, as [the keyboard and screen reader example](#/chart/keyboard-and-screen-reader) shows. They read no language provider, so this demo has no EN/DE switch in its header.
|
|
1143
1067
|
|
|
@@ -1145,31 +1069,24 @@ The charts take German per chart: `wording={GERMAN_CHARTS_WORDING}` from `@umris
|
|
|
1145
1069
|
|
|
1146
1070
|
##### Draw a first chart
|
|
1147
1071
|
|
|
1148
|
-
Hand [`useChart`](#/api/useChart) the rows, then give its `Chart` an axis per direction and a series; the x axis and the series read a row through `value`.
|
|
1072
|
+
Hand [`useChart`](#/api/useChart) the rows as they come, then give its `Chart` an axis per direction and a series; the x axis and the series read a row through `value`. With `time` the axis reads the instants an API sends as text.
|
|
1149
1073
|
|
|
1150
1074
|
```tsx
|
|
1151
1075
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
1152
1076
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
t:
|
|
1156
|
-
signIns:
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
{ t:
|
|
1163
|
-
{ t:
|
|
1164
|
-
{ t:
|
|
1165
|
-
{ t:
|
|
1166
|
-
{ t: at(4), signIns: 205 },
|
|
1167
|
-
{ t: at(5), signIns: 340 },
|
|
1168
|
-
{ t: at(6), signIns: 820 },
|
|
1169
|
-
{ t: at(7), signIns: 1460 },
|
|
1170
|
-
{ t: at(8), signIns: 2130 },
|
|
1171
|
-
{ t: at(9), signIns: 2480 },
|
|
1172
|
-
{ t: at(10), signIns: 2390 },
|
|
1077
|
+
/* Sign-ins per hour, as the API sends them: the hour's start in local time. */
|
|
1078
|
+
const SIGN_INS = [
|
|
1079
|
+
{ t: "2026-03-17T00:00", signIns: 310 },
|
|
1080
|
+
{ t: "2026-03-17T01:00", signIns: 220 },
|
|
1081
|
+
{ t: "2026-03-17T02:00", signIns: 180 },
|
|
1082
|
+
{ t: "2026-03-17T03:00", signIns: 170 },
|
|
1083
|
+
{ t: "2026-03-17T04:00", signIns: 205 },
|
|
1084
|
+
{ t: "2026-03-17T05:00", signIns: 340 },
|
|
1085
|
+
{ t: "2026-03-17T06:00", signIns: 820 },
|
|
1086
|
+
{ t: "2026-03-17T07:00", signIns: 1460 },
|
|
1087
|
+
{ t: "2026-03-17T08:00", signIns: 2130 },
|
|
1088
|
+
{ t: "2026-03-17T09:00", signIns: 2480 },
|
|
1089
|
+
{ t: "2026-03-17T10:00", signIns: 2390 },
|
|
1173
1090
|
];
|
|
1174
1091
|
|
|
1175
1092
|
export default function FirstChart() {
|
|
@@ -1324,7 +1241,7 @@ From `ChartProps` — the full table stands on [Chart](#/chart/type-ChartProps).
|
|
|
1324
1241
|
| Prop | Type | Default | Description |
|
|
1325
1242
|
|---|---|---|---|
|
|
1326
1243
|
| [`width`](#/chart/ChartProps-width) | `number \| "100%"` | `"100%"` | Width in CSS pixels, or `"100%"` for the host's: the chart measures its host and follows it through a ResizeObserver. |
|
|
1327
|
-
| [`height`](#/chart/ChartProps-height) | `number` |
|
|
1244
|
+
| [`height`](#/chart/ChartProps-height) | `number` | fills the frame, at least 300 px | Height in CSS pixels, fixed. Left out, the chart fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 300 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)). |
|
|
1328
1245
|
| [`children`](#/chart/ChartProps-children) | `ReactNode` | — | The axes, series and companions of this chart. They draw nothing themselves: each registers its configuration and the scene draws it, and the order in the JSX is the drawing order. |
|
|
1329
1246
|
|
|
1330
1247
|
###### Accessibility
|
|
@@ -1337,8 +1254,8 @@ From `XAxisProps` — the full table stands on [Axis](#/axis/type-XAxisProps).
|
|
|
1337
1254
|
|
|
1338
1255
|
| Prop | Type | Default | Description |
|
|
1339
1256
|
|---|---|---|---|
|
|
1340
|
-
| [`value`](#/axis/XAxisProps-value) *required* | [`
|
|
1341
|
-
| [`time`](#/axis/XAxisProps-time) | `boolean` | — | The values are instants
|
|
1257
|
+
| [`value`](#/axis/XAxisProps-value) *required* | [`AxisField`](#/axis/type-AxisField)`<T> \| ((d: T, index: number) => `[`AxisValue`](#/axis/type-AxisValue)`)` | — | Where a row lies on this axis: a field of the row, compared by its name, or a function of it, compared by its source text as a series' is (`ValueFunction`) - with the same closure limit. A number is a position, a `Date` an instant, a string a category: every distinct one gets a place of its own, in the order the rows bring them, a tick and its name as the label ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). The type decides, never the content - `"2024"` is a category, `2024` a number - and a row without a value has no place. The axis reads the rows of every series bound to it; where those bring their own `data`, name their row: `<XAxis<HourCount> value="hour" />`. |
|
|
1258
|
+
| [`time`](#/axis/XAxisProps-time) | `boolean` | — | The values are instants: milliseconds since the epoch, or with `time` said ISO 8601 strings as an API sends them, read by the platform's rule - a date with a time and no offset is local time, a date alone is UTC; a string that is none is not drawn. Rows that hold a `Date` put the axis in time without `time`. Ticks on local boundaries from the minute to the month, labels by level in en-GB with a 24-hour clock - `15:00`, `17 Mar`, `Mar 2026` - and the date on the first tick and on the first of a new day (`17 Mar 00:00`); a day tick there carries the year. The tooltip's x value carries the seconds where the readings lie less than a minute apart. Another language is a `tickFormat`, which is handed the instant in milliseconds. `calendar` implies it. |
|
|
1342
1259
|
| [`label`](#/axis/XAxisProps-label) | `string` | — | Axis title. |
|
|
1343
1260
|
|
|
1344
1261
|
From `YAxisProps` — the full table stands on [Axis](#/axis/type-YAxisProps).
|
|
@@ -1701,14 +1618,15 @@ const CENTRES = [
|
|
|
1701
1618
|
{ id: "CC-4400", name: "Facilities" },
|
|
1702
1619
|
];
|
|
1703
1620
|
|
|
1704
|
-
const month = (row: LedgerRow) => Number(row.month.slice(5)) - 1;
|
|
1705
1621
|
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1622
|
+
/** `"2026-03"` as the axis writes it. */
|
|
1623
|
+
const monthName = (month: string) => MONTH_NAMES[Number(month.slice(5)) - 1] ?? month;
|
|
1706
1624
|
|
|
1707
1625
|
function Forecasts({ encoding }: { encoding?: "marks" }) {
|
|
1708
1626
|
const { Chart, XAxis, YAxis, Line } = useChart(LEDGER);
|
|
1709
1627
|
return (
|
|
1710
1628
|
<Chart height={240} ariaLabel="Forecast of four cost centres per month" encoding={encoding}>
|
|
1711
|
-
<XAxis value=
|
|
1629
|
+
<XAxis value="month" categoryLabel={monthName} />
|
|
1712
1630
|
<YAxis label="€" />
|
|
1713
1631
|
{CENTRES.map((c) => (
|
|
1714
1632
|
<Line
|
|
@@ -2022,8 +1940,8 @@ const SERVICES: readonly Service[] = [
|
|
|
2022
1940
|
|
|
2023
1941
|
interface SuccessCell {
|
|
2024
1942
|
hour: number;
|
|
2025
|
-
/** The row: the service's
|
|
2026
|
-
service:
|
|
1943
|
+
/** The row: the service's name. */
|
|
1944
|
+
service: string;
|
|
2027
1945
|
/** Requests that succeeded, in per cent; `null` where there were none. */
|
|
2028
1946
|
success: number | null;
|
|
2029
1947
|
}
|
|
@@ -2034,13 +1952,13 @@ interface SuccessCell {
|
|
|
2034
1952
|
const SUCCESS_BY_HOUR: readonly SuccessCell[] = (() => {
|
|
2035
1953
|
const r = random(23);
|
|
2036
1954
|
const cells: SuccessCell[] = [];
|
|
2037
|
-
SERVICES.forEach((one
|
|
1955
|
+
SERVICES.forEach((one) => {
|
|
2038
1956
|
for (let hour = 0; hour < 24; hour++) {
|
|
2039
1957
|
let success: number | null = 99.2 + r() * 0.8;
|
|
2040
1958
|
if (one.id === "webhooks" && hour >= 9 && hour < 14) success = 96 + r() * 1.5;
|
|
2041
1959
|
if (hour === 3) success = 97.4 + r() * 1;
|
|
2042
1960
|
if (one.id === "reports" && hour < 6) success = null;
|
|
2043
|
-
cells.push({ hour, service, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
1961
|
+
cells.push({ hour, service: one.name, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
2044
1962
|
}
|
|
2045
1963
|
});
|
|
2046
1964
|
return cells;
|
|
@@ -2055,10 +1973,11 @@ function BatteryAndStates() {
|
|
|
2055
1973
|
<Chart height={280} ariaLabel="An e-van's battery against its reserve, above its states" encoding="marks">
|
|
2056
1974
|
<XAxis value="t" time label="Time" tickCount={4} />
|
|
2057
1975
|
<YAxis domain={[-40, 100]} ticks={[0, 20, 40, 60, 80, 100]} label="%" />
|
|
2058
|
-
|
|
1976
|
+
{/* One lane, and four places of room above it for the course. */}
|
|
1977
|
+
<YAxis id="lane" position="right" domain={[-0.5, 4.5]} />
|
|
2059
1978
|
<LimitBand from={10} to={20} severity="warning" label="Reserve" />
|
|
2060
1979
|
<LimitLine value={10} severity="alarm" label="Empty soon" />
|
|
2061
|
-
<StateBand data={STATES} value="state" states={VEHICLE_STATES} yAxisId="lane"
|
|
1980
|
+
<StateBand data={STATES} value="state" states={VEHICLE_STATES} yAxisId="lane" lane="FP 377 K" name="FP 377 K" />
|
|
2062
1981
|
<Line value="charge" name="Charge" strokeWidth={1.75} />
|
|
2063
1982
|
<Legend placement="top" />
|
|
2064
1983
|
<Tooltip mode="x" />
|
|
@@ -2071,7 +1990,7 @@ function SuccessCells() {
|
|
|
2071
1990
|
return (
|
|
2072
1991
|
<Chart height={280} ariaLabel="Successful requests per service and hour, by limits and by marks" encoding="marks">
|
|
2073
1992
|
<XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
2074
|
-
<YAxis
|
|
1993
|
+
<YAxis />
|
|
2075
1994
|
<Matrix
|
|
2076
1995
|
value="service"
|
|
2077
1996
|
level="success"
|
|
@@ -2175,6 +2094,58 @@ export default function Loading() {
|
|
|
2175
2094
|
}
|
|
2176
2095
|
```
|
|
2177
2096
|
|
|
2097
|
+
##### Draw a series per group of long rows
|
|
2098
|
+
|
|
2099
|
+
A chart draws one series per element. Rows that come one per service and hour are grouped before it - a `Set` names the groups, a `filter` picks each one's rows - and each group is a series with its own `data`. Which rows belong together is yours to say.
|
|
2100
|
+
|
|
2101
|
+
```tsx
|
|
2102
|
+
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
2103
|
+
|
|
2104
|
+
/* Failed requests in per cent, per service and hour - long rows, as a query
|
|
2105
|
+
returns them. */
|
|
2106
|
+
const ERRORS = [
|
|
2107
|
+
{ t: "2026-03-17T06:00", service: "Checkout", errors: 0.12 },
|
|
2108
|
+
{ t: "2026-03-17T06:00", service: "Billing", errors: 0.21 },
|
|
2109
|
+
{ t: "2026-03-17T06:00", service: "Search", errors: 0.08 },
|
|
2110
|
+
{ t: "2026-03-17T07:00", service: "Checkout", errors: 0.15 },
|
|
2111
|
+
{ t: "2026-03-17T07:00", service: "Billing", errors: 0.19 },
|
|
2112
|
+
{ t: "2026-03-17T07:00", service: "Search", errors: 0.09 },
|
|
2113
|
+
{ t: "2026-03-17T08:00", service: "Checkout", errors: 0.14 },
|
|
2114
|
+
{ t: "2026-03-17T08:00", service: "Billing", errors: 0.24 },
|
|
2115
|
+
{ t: "2026-03-17T08:00", service: "Search", errors: 0.11 },
|
|
2116
|
+
{ t: "2026-03-17T09:00", service: "Checkout", errors: 1.86 },
|
|
2117
|
+
{ t: "2026-03-17T09:00", service: "Billing", errors: 0.92 },
|
|
2118
|
+
{ t: "2026-03-17T09:00", service: "Search", errors: 0.1 },
|
|
2119
|
+
{ t: "2026-03-17T10:00", service: "Checkout", errors: 2.41 },
|
|
2120
|
+
{ t: "2026-03-17T10:00", service: "Billing", errors: 1.13 },
|
|
2121
|
+
{ t: "2026-03-17T10:00", service: "Search", errors: 0.12 },
|
|
2122
|
+
{ t: "2026-03-17T11:00", service: "Checkout", errors: 0.18 },
|
|
2123
|
+
{ t: "2026-03-17T11:00", service: "Billing", errors: 0.22 },
|
|
2124
|
+
{ t: "2026-03-17T11:00", service: "Search", errors: 0.09 },
|
|
2125
|
+
];
|
|
2126
|
+
|
|
2127
|
+
/* Once, outside the component: a series' `data` is compared by reference. */
|
|
2128
|
+
const BY_SERVICE = [...new Set(ERRORS.map((row) => row.service))].map((service) => ({
|
|
2129
|
+
service,
|
|
2130
|
+
rows: ERRORS.filter((row) => row.service === service),
|
|
2131
|
+
}));
|
|
2132
|
+
|
|
2133
|
+
export default function SeriesFromLongRows() {
|
|
2134
|
+
const { Chart, XAxis, YAxis, Line } = useChart(ERRORS);
|
|
2135
|
+
return (
|
|
2136
|
+
<Chart height={260} ariaLabel="Failed requests per service and hour this morning">
|
|
2137
|
+
<XAxis value="t" time />
|
|
2138
|
+
<YAxis label="Errors %" />
|
|
2139
|
+
{BY_SERVICE.map(({ service, rows }) => (
|
|
2140
|
+
<Line key={service} data={rows} value="errors" name={service} markers="always" />
|
|
2141
|
+
))}
|
|
2142
|
+
<Legend placement="top" />
|
|
2143
|
+
<Tooltip mode="x" />
|
|
2144
|
+
</Chart>
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
```
|
|
2148
|
+
|
|
2178
2149
|
#### When to use something else
|
|
2179
2150
|
|
|
2180
2151
|
- A single number with a small trend beside it → `Sparkline` or `Stat` from @umriss-ui/core
|
|
@@ -2209,14 +2180,14 @@ Under forced colours the chart draws in the system colours and tells its series
|
|
|
2209
2180
|
| Prop | Type | Default | Description |
|
|
2210
2181
|
|---|---|---|---|
|
|
2211
2182
|
| `width` | `number \| "100%"` | `"100%"` | Width in CSS pixels, or `"100%"` for the host's: the chart measures its host and follows it through a ResizeObserver.<br>Shown in: [Size a chart by its container (Installation)](#/installation/in-its-container) |
|
|
2212
|
-
| `height` | `number` |
|
|
2183
|
+
| `height` | `number` | fills the frame, at least 300 px | Height in CSS pixels, fixed. Left out, the chart fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 300 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)).<br>Shown in: [Mix series kinds](#/chart/mixed), [Say there is nothing to show](#/chart/empty), [Read it by keyboard and screen reader](#/chart/keyboard-and-screen-reader) and 70 more |
|
|
2213
2184
|
| `padding` | `number \| Partial<`[`Padding`](#type-Padding)`>` | — | Outer spacing of the plot area in CSS pixels.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
2214
2185
|
| `empty` | `ReactNode` | the wording's `empty` | Shown in the middle of the plot area when no visible series has a point to show - no data, only gaps, or every series hidden. Axes and frame stay. Not while `loading`.<br>Shown in: [Say there is nothing to show](#/chart/empty) |
|
|
2215
2186
|
| `loading` | `boolean` | `false` | The rows are on their way, as the table's `loading` says ([ADR-0042](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0042-a-rows-height-never-follows-what-it-shows.md)). A course already drawn stays: it dims after a moment and takes no pointer until the answer is in. Where there is nothing to show, the empty message waits. The plot is `aria-busy` meanwhile.<br>Shown in: [Show that rows are on their way](#/chart/loading) |
|
|
2216
2187
|
| `wording` | `Partial<`[`ChartsWording`](#type-ChartsWording)`>` | `DEFAULT_CHARTS_WORDING` | The chart's words ([ADR-0031](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0031-the-charts-carry-their-own-wording.md)); entries left out fall back to English. German: `GERMAN_CHARTS_WORDING` from `@umriss-ui/charts/wording/de`.<br>Shown in: [Read it by keyboard and screen reader](#/chart/keyboard-and-screen-reader) |
|
|
2217
2188
|
| `encoding` | `"color" \| "marks"` | `"color"` | How series are told apart. `"color"`: by their colour. `"marks"`: by a dash and a marker shape as well - a line, an area and a scatter -, and by a hatch - a bar, a state, a matrix cell, a limit band -, chosen by the same palette place as the colour, so that a reader who does not see the colours still tells them apart; the legend's chips show the same. A caller's own `dash` wins over the place's.<br>Shown in: [Tell series apart without colour](#/chart/told-apart-without-colour), [Mark every kind](#/chart/marks-on-every-kind), [Mark bands, limits and cells](#/chart/bands-limits-and-cells) |
|
|
2218
2189
|
| `syncId` | `string` | — | Charts with the same id share the pointer's x position, in domain units: each draws its crosshair there, the tooltip stays with the chart under the pointer. Zoom is not shared this way: charts zoom together by handing each other their view (`useChart`'s `initialView` and `onViewChange`).<br>Shown in: [Keep charts in step (Zoom and pan)](#/zoom-and-pan/cursor-sync), [Watch latency against its objective (Scenarios)](#/scenarios/watch-latency-against-its-objective), [Hold the tile length in control (Scenarios)](#/scenarios/hold-the-tile-length-in-control) |
|
|
2219
|
-
| `children` | `ReactNode` | — | The axes, series and companions of this chart. They draw nothing themselves: each registers its configuration and the scene draws it, and the order in the JSX is the drawing order.<br>Shown in: [Mix series kinds](#/chart/mixed), [Say there is nothing to show](#/chart/empty), [Read it by keyboard and screen reader](#/chart/keyboard-and-screen-reader) and
|
|
2190
|
+
| `children` | `ReactNode` | — | The axes, series and companions of this chart. They draw nothing themselves: each registers its configuration and the scene draws it, and the order in the JSX is the drawing order.<br>Shown in: [Mix series kinds](#/chart/mixed), [Say there is nothing to show](#/chart/empty), [Read it by keyboard and screen reader](#/chart/keyboard-and-screen-reader) and 70 more |
|
|
2220
2191
|
|
|
2221
2192
|
###### Events
|
|
2222
2193
|
|
|
@@ -2228,14 +2199,14 @@ Under forced colours the chart draws in the system colours and tells its series
|
|
|
2228
2199
|
|
|
2229
2200
|
| Prop | Type | Default | Description |
|
|
2230
2201
|
|---|---|---|---|
|
|
2231
|
-
| `ariaLabel` | `string` | — | Required in DEV through a warning.<br>Shown in: [Mix series kinds](#/chart/mixed), [Say there is nothing to show](#/chart/empty), [Read it by keyboard and screen reader](#/chart/keyboard-and-screen-reader) and
|
|
2202
|
+
| `ariaLabel` | `string` | — | Required in DEV through a warning.<br>Shown in: [Mix series kinds](#/chart/mixed), [Say there is nothing to show](#/chart/empty), [Read it by keyboard and screen reader](#/chart/keyboard-and-screen-reader) and 70 more |
|
|
2232
2203
|
|
|
2233
2204
|
###### Styling
|
|
2234
2205
|
|
|
2235
2206
|
| Prop | Type | Default | Description |
|
|
2236
2207
|
|---|---|---|---|
|
|
2237
2208
|
| `className` | `string` | — | Goes to the root element, as it does everywhere in this workspace. |
|
|
2238
|
-
| `style` | `CSSProperties` | — | Goes to the root element
|
|
2209
|
+
| `style` | `CSSProperties` | — | Goes to the root element, after the chart's own styles: a `height` or `minHeight` set here wins. The plot area is observed in both directions and the scene follows it. |
|
|
2239
2210
|
|
|
2240
2211
|
##### Types on this page
|
|
2241
2212
|
|
|
@@ -2305,7 +2276,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
2305
2276
|
|
|
2306
2277
|
### Axis
|
|
2307
2278
|
|
|
2308
|
-
What a value is measured against: extent, ticks and labels, several axes per side, time on the local clock, and a working-time axis that leaves out the hours nobody works (a scale).
|
|
2279
|
+
What a value is measured against: extent, ticks and labels, categories, several axes per side, time on the local clock, and a working-time axis that leaves out the hours nobody works (a scale).
|
|
2309
2280
|
|
|
2310
2281
|
```ts
|
|
2311
2282
|
import { useChart, workingCalendar } from "@umriss-ui/charts";
|
|
@@ -2313,7 +2284,7 @@ import { useChart, workingCalendar } from "@umriss-ui/charts";
|
|
|
2313
2284
|
|
|
2314
2285
|
Demo page: https://romanhaendler.github.io/umriss-ui/charts/axis/
|
|
2315
2286
|
|
|
2316
|
-
Every scale is linear.
|
|
2287
|
+
Every scale is linear. An axis reads what its rows hold: a number is a position, a string a category, which gets 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 working calendar maps time before it is drawn and marks every seam it took out ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md)).
|
|
2317
2288
|
|
|
2318
2289
|
An x axis zooms where it is `zoomable`; `domain` is then where it starts. That has a page of its own, [Zoom and pan](#/zoom-and-pan).
|
|
2319
2290
|
|
|
@@ -2513,7 +2484,7 @@ export default function WorkingTime() {
|
|
|
2513
2484
|
|
|
2514
2485
|
##### Plot instants on a time axis
|
|
2515
2486
|
|
|
2516
|
-
|
|
2487
|
+
Rows that hold a `Date` put the axis in time; `time` does the same for milliseconds and for ISO strings. Ticks stand on the local clock, labelled by hour, with the date where a day begins.
|
|
2517
2488
|
|
|
2518
2489
|
```tsx
|
|
2519
2490
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -2560,7 +2531,7 @@ const SERVICES: readonly Service[] = [
|
|
|
2560
2531
|
];
|
|
2561
2532
|
|
|
2562
2533
|
interface MetricPoint {
|
|
2563
|
-
t:
|
|
2534
|
+
t: Date;
|
|
2564
2535
|
/** Median and 95th percentile latency, in ms. */
|
|
2565
2536
|
p50: number;
|
|
2566
2537
|
p95: number;
|
|
@@ -2591,7 +2562,7 @@ function metrics(serviceId: string): MetricPoint[] {
|
|
|
2591
2562
|
const surge = serviceId === "checkout" && t >= at(17, 9, 40) && t < at(17, 10, 10) ? 1.9 : 1;
|
|
2592
2563
|
const p95 = Math.round(base * surge * (1 + r() * 0.08));
|
|
2593
2564
|
points.push({
|
|
2594
|
-
t,
|
|
2565
|
+
t: new Date(t),
|
|
2595
2566
|
p50: Math.round(p95 * (0.42 + r() * 0.06)),
|
|
2596
2567
|
p95,
|
|
2597
2568
|
errorRate: Math.round((0.05 + r() * 0.12 + (surge > 1 ? 2.4 + r() : 0)) * 100) / 100,
|
|
@@ -2607,7 +2578,7 @@ export default function TimeAxis() {
|
|
|
2607
2578
|
const { Chart, XAxis, YAxis, Line } = useChart(CHECKOUT);
|
|
2608
2579
|
return (
|
|
2609
2580
|
<Chart height={260} ariaLabel="Checkout's error rate today">
|
|
2610
|
-
<XAxis value="t"
|
|
2581
|
+
<XAxis value="t" />
|
|
2611
2582
|
<YAxis label="Errors %" />
|
|
2612
2583
|
<Line value="errorRate" name="Error rate" />
|
|
2613
2584
|
<Tooltip mode="x" />
|
|
@@ -2933,6 +2904,38 @@ export default function EveryKind() {
|
|
|
2933
2904
|
}
|
|
2934
2905
|
```
|
|
2935
2906
|
|
|
2907
|
+
##### Fix the order and keep an empty place
|
|
2908
|
+
|
|
2909
|
+
`categories` is the order and the extent: a day without a row keeps its place, and the rows may come as they like. `categoryLabel` writes a name where the rows hold a key.
|
|
2910
|
+
|
|
2911
|
+
```tsx
|
|
2912
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
2913
|
+
|
|
2914
|
+
/* Minutes late per working day, as the tour log sends them: only the days
|
|
2915
|
+
that had a delay, the worst first. */
|
|
2916
|
+
const LATE = [
|
|
2917
|
+
{ day: "thu", minutes: 164 },
|
|
2918
|
+
{ day: "mon", minutes: 71 },
|
|
2919
|
+
{ day: "fri", minutes: 58 },
|
|
2920
|
+
{ day: "tue", minutes: 43 },
|
|
2921
|
+
];
|
|
2922
|
+
|
|
2923
|
+
const WEEK = ["mon", "tue", "wed", "thu", "fri"];
|
|
2924
|
+
const NAMES: Record<string, string> = { mon: "Monday", tue: "Tuesday", wed: "Wednesday", thu: "Thursday", fri: "Friday" };
|
|
2925
|
+
|
|
2926
|
+
export default function Categories() {
|
|
2927
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(LATE);
|
|
2928
|
+
return (
|
|
2929
|
+
<Chart height={260} ariaLabel="Minutes late per working day">
|
|
2930
|
+
<XAxis value="day" categories={WEEK} categoryLabel={(day) => NAMES[day] ?? day} />
|
|
2931
|
+
<YAxis label="Minutes late" />
|
|
2932
|
+
<Bar value="minutes" name="Late" />
|
|
2933
|
+
<Tooltip mode="x" />
|
|
2934
|
+
</Chart>
|
|
2935
|
+
);
|
|
2936
|
+
}
|
|
2937
|
+
```
|
|
2938
|
+
|
|
2936
2939
|
#### Keyboard
|
|
2937
2940
|
|
|
2938
2941
|
The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
@@ -2943,48 +2946,67 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
2943
2946
|
|
|
2944
2947
|
| Prop | Type | Default | Description |
|
|
2945
2948
|
|---|---|---|---|
|
|
2946
|
-
| `value` *required* | [`
|
|
2947
|
-
| `domain` | `"nice" \| "data" \| readonly [number, number]` | `"nice"` | `"nice"` widens the data's extent to ticks - to named `ticks` where there are any, on a time axis to whole units of a step of hours or longer -, `"data"` keeps it, a pair is fixed. On a `zoomable` axis it is the start: what the axis shows while the chart's view names no span for it.<br>Shown in: [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis), [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader), [Gestures and keys (Zoom and pan)](#/zoom-and-pan/gestures-and-keys) and 10 more |
|
|
2949
|
+
| `value` *required* | [`AxisField`](#type-AxisField)`<T> \| ((d: T, index: number) => `[`AxisValue`](#type-AxisValue)`)` | — | Where a row lies on this axis: a field of the row, compared by its name, or a function of it, compared by its source text as a series' is (`ValueFunction`) - with the same closure limit. A number is a position, a `Date` an instant, a string a category: every distinct one gets a place of its own, in the order the rows bring them, a tick and its name as the label ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). The type decides, never the content - `"2024"` is a category, `2024` a number - and a row without a value has no place. The axis reads the rows of every series bound to it; where those bring their own `data`, name their row: `<XAxis<HourCount> value="hour" />`.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Add axes for other magnitudes](#/axis/axes), [Leave out the hours nobody works](#/axis/working-time) and 70 more |
|
|
2950
|
+
| `domain` | `"nice" \| "data" \| readonly [number, number] \| readonly [Date, Date]` | `"nice"` | `"nice"` widens the data's extent to ticks - to named `ticks` where there are any, on a time axis to whole units of a step of hours or longer -, `"data"` keeps it, a pair is fixed: numbers, on a time axis milliseconds or two `Date`s, on a category axis places. On a `zoomable` axis it is the start: what the axis shows while the chart's view names no span for it.<br>Shown in: [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis), [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader), [Gestures and keys (Zoom and pan)](#/zoom-and-pan/gestures-and-keys) and 10 more |
|
|
2948
2951
|
| `position` | `"bottom" \| "top"` | `"bottom"` | Which edge the axis stands at. A second x axis on the opposite edge is how a series counts in a unit of its own.<br>Shown in: [Add axes for other magnitudes](#/axis/axes), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) |
|
|
2949
|
-
| `time` | `boolean` | — | The values are instants
|
|
2952
|
+
| `time` | `boolean` | — | The values are instants: milliseconds since the epoch, or with `time` said ISO 8601 strings as an API sends them, read by the platform's rule - a date with a time and no offset is local time, a date alone is UTC; a string that is none is not drawn. Rows that hold a `Date` put the axis in time without `time`. Ticks on local boundaries from the minute to the month, labels by level in en-GB with a 24-hour clock - `15:00`, `17 Mar`, `Mar 2026` - and the date on the first tick and on the first of a new day (`17 Mar 00:00`); a day tick there carries the year. The tooltip's x value carries the seconds where the readings lie less than a minute apart. Another language is a `tickFormat`, which is handed the instant in milliseconds. `calendar` implies it.<br>Shown in: [Align a second axis to the grid](#/axis/aligned-ticks), [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container) and 36 more |
|
|
2950
2953
|
| `calendar` | `readonly `[`WorkingInterval`](#type-WorkingInterval)`[]` | — | Working calendar: the intervals in which time counts. With it the axis stands in working time - nights and weekends are out, and every removed span gets a break mark. The scale stays affine; the mapping happens in materialisation ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md)).<br>Shown in: [Leave out the hours nobody works](#/axis/working-time) |
|
|
2951
2954
|
| `zoomable` | `boolean` | `false` | Zoom and pan: Ctrl or ⌘ with the wheel - and a pinch - zoom around the pointer, a drag, a horizontal wheel or Shift with the wheel pan, a double click goes back to `domain`; with the focus on the chart, + and − zoom, Shift with ← or → pans and 0 goes back. The span shown is part of the chart's view, by this axis' `id` (`useChart`'s `initialView`, `onViewChange`, `setDomain`). The plain wheel always scrolls the page.<br>Shown in: [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader), [Gestures and keys (Zoom and pan)](#/zoom-and-pan/gestures-and-keys), [Limit the zoom (Zoom and pan)](#/zoom-and-pan/zoom-limits) and 5 more |
|
|
2952
2955
|
| `zoomLimits` | [`ZoomLimits`](#type-ZoomLimits) | at most the data's extent or this axis' own `domain`, whichever is wider, at least three data steps | The narrowest and the widest span zoom may reach, in the axis' units - milliseconds on a time axis.<br>Shown in: [Limit the zoom (Zoom and pan)](#/zoom-and-pan/zoom-limits) |
|
|
2953
2956
|
| `id` | `string` | `"x"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes](#/axis/axes), [Align a second axis to the grid](#/axis/aligned-ticks), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and 7 more |
|
|
2954
|
-
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Add axes for other magnitudes](#/axis/axes), [Leave out the hours nobody works](#/axis/working-time) and
|
|
2955
|
-
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Mix series kinds (Chart)](#/chart/mixed) and
|
|
2956
|
-
| `tickFormat` | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and
|
|
2957
|
+
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Add axes for other magnitudes](#/axis/axes), [Leave out the hours nobody works](#/axis/working-time) and 62 more |
|
|
2958
|
+
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Mix series kinds (Chart)](#/chart/mixed) and 8 more |
|
|
2959
|
+
| `tickFormat` | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and 19 more |
|
|
2957
2960
|
| `grid` | `boolean` | only on the first axis of its orientation | Grid lines.<br>Shown in: [One vehicle through the day (StateBand)](#/stateband/one-vehicle), [Give each one a lane (StateBand)](#/stateband/one-lane-each), [See where the vans stand idle (Scenarios)](#/scenarios/see-where-the-vans-stand-idle) |
|
|
2958
|
-
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm
|
|
2961
|
+
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm, on a numeric axis: an hour axis' named hours, a lane grid. An axis that reads categories needs none - every category has its tick.<br>Shown in: [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [Compare progress with a plan (Line)](#/line/burn-down) and 7 more |
|
|
2962
|
+
| `categories` | `readonly string[]` | — | The categories of the axis, in their order. Without it they stand in the order the rows first bring them. With it the list is the order and the extent: a category no row carries keeps its empty place, and a row whose category is not listed is not drawn - DEV names it. It makes the axis a category axis whatever the rows hold ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)).<br>Shown in: [Fix the order and keep an empty place](#/axis/categories) |
|
|
2963
|
+
| `categoryLabel` | `(category: string) => string` | the category itself | What is written for a category - under its tick, in the tooltip, the readout and the data table - where the rows hold a key and not a name. Two categories may share a label and keep two places. Compared by its source text, as `tickFormat` is.<br>Shown in: [Fix the order and keep an empty place](#/axis/categories), [Tell series apart without colour (Chart)](#/chart/told-apart-without-colour), [Legend above or below (Legend)](#/legend/above-or-below) and 1 more |
|
|
2959
2964
|
|
|
2960
2965
|
##### `YAxisProps`
|
|
2961
2966
|
|
|
2962
2967
|
| Prop | Type | Default | Description |
|
|
2963
2968
|
|---|---|---|---|
|
|
2964
|
-
| `domain` | `"nice" \| "data" \| "visible" \| readonly [number, number]` | `"nice"` | As on the x axis, and `"visible"`: what the series show inside their x axis' domain - a zoomed hour gets the hour's range, not the week's -, widened to ticks as `"nice"` is. Where the x domain is not fixed that is every point.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells) and
|
|
2969
|
+
| `domain` | `"nice" \| "data" \| "visible" \| readonly [number, number]` | `"nice"` | As on the x axis, and `"visible"`: what the series show inside their x axis' domain - a zoomed hour gets the hour's range, not the week's -, widened to ticks as `"nice"` is. Where the x domain is not fixed that is every point.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells) and 10 more |
|
|
2965
2970
|
| `alignTicks` | `boolean` | — | On a further y axis: take the first y axis' tick count and widen this axis' domain until its ticks fall on that one's grid lines, the steps still 1-2-5 - one grid serves both. `tickCount` then does not apply; on the first y axis it changes nothing.<br>Shown in: [Align a second axis to the grid](#/axis/aligned-ticks) |
|
|
2966
2971
|
| `position` | `"left" \| "right"` | `"left"` | Which edge the axis stands at. Several y axes per side are stacked outwards, so that extents of clearly different magnitude stay readable.<br>Shown in: [Add axes for other magnitudes](#/axis/axes), [Align a second axis to the grid](#/axis/aligned-ticks), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and 7 more |
|
|
2967
2972
|
| `id` | `string` | `"y"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes](#/axis/axes), [Align a second axis to the grid](#/axis/aligned-ticks), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and 7 more |
|
|
2968
|
-
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Add axes for other magnitudes](#/axis/axes), [Leave out the hours nobody works](#/axis/working-time) and
|
|
2969
|
-
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Mix series kinds (Chart)](#/chart/mixed) and
|
|
2970
|
-
| `tickFormat` | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and
|
|
2973
|
+
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Add axes for other magnitudes](#/axis/axes), [Leave out the hours nobody works](#/axis/working-time) and 62 more |
|
|
2974
|
+
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Mix series kinds (Chart)](#/chart/mixed) and 8 more |
|
|
2975
|
+
| `tickFormat` | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity.<br>Shown in: [Fix the domain and name the ticks](#/axis/configuration), [Leave out the hours nobody works](#/axis/working-time), [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis) and 19 more |
|
|
2971
2976
|
| `grid` | `boolean` | only on the first axis of its orientation | Grid lines.<br>Shown in: [One vehicle through the day (StateBand)](#/stateband/one-vehicle), [Give each one a lane (StateBand)](#/stateband/one-lane-each), [See where the vans stand idle (Scenarios)](#/scenarios/see-where-the-vans-stand-idle) |
|
|
2972
|
-
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm
|
|
2977
|
+
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm, on a numeric axis: an hour axis' named hours, a lane grid. An axis that reads categories needs none - every category has its tick.<br>Shown in: [Bind every kind of series to a second axis](#/axis/every-kind-on-a-second-axis), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [Compare progress with a plan (Line)](#/line/burn-down) and 7 more |
|
|
2978
|
+
| `categories` | `readonly string[]` | — | The categories of the axis, in their order. Without it they stand in the order the rows first bring them. With it the list is the order and the extent: a category no row carries keeps its empty place, and a row whose category is not listed is not drawn - DEV names it. It makes the axis a category axis whatever the rows hold ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)).<br>Shown in: [Fix the order and keep an empty place](#/axis/categories) |
|
|
2979
|
+
| `categoryLabel` | `(category: string) => string` | the category itself | What is written for a category - under its tick, in the tooltip, the readout and the data table - where the rows hold a key and not a name. Two categories may share a label and keep two places. Compared by its source text, as `tickFormat` is.<br>Shown in: [Fix the order and keep an empty place](#/axis/categories), [Tell series apart without colour (Chart)](#/chart/told-apart-without-colour), [Legend above or below (Legend)](#/legend/above-or-below) and 1 more |
|
|
2973
2980
|
|
|
2974
2981
|
##### Types on this page
|
|
2975
2982
|
|
|
2976
|
-
###### `
|
|
2983
|
+
###### `AxisField<T, V = AxisValue>`
|
|
2977
2984
|
|
|
2978
|
-
The fields of a row
|
|
2985
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
2986
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
2987
|
+
never an instant.
|
|
2979
2988
|
|
|
2980
2989
|
```ts
|
|
2981
|
-
type
|
|
2990
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
2982
2991
|
{
|
|
2983
|
-
[K in keyof T & string]-?: T[K] extends
|
|
2992
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
2984
2993
|
}[keyof T & string]
|
|
2985
2994
|
>;
|
|
2986
2995
|
```
|
|
2987
2996
|
|
|
2997
|
+
###### `AxisValue`
|
|
2998
|
+
|
|
2999
|
+
What an axis reads from a row ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)): a number - a position, or an
|
|
3000
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
3001
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
3002
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
3003
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
3004
|
+
place.
|
|
3005
|
+
|
|
3006
|
+
```ts
|
|
3007
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
3008
|
+
```
|
|
3009
|
+
|
|
2988
3010
|
###### `WorkingInterval`
|
|
2989
3011
|
|
|
2990
3012
|
An interval in which time counts; thought of as half-open, but mappable from
|
|
@@ -3007,7 +3029,7 @@ schedule's shape.
|
|
|
3007
3029
|
|
|
3008
3030
|
#### Known limits
|
|
3009
3031
|
|
|
3010
|
-
- No logarithmic and no
|
|
3032
|
+
- No logarithmic and no band scale: compute the logarithm in `value`; a category is a place on the linear scale ([ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/), [ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)).
|
|
3011
3033
|
|
|
3012
3034
|
What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
|
|
3013
3035
|
|
|
@@ -3541,19 +3563,21 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3541
3563
|
|
|
3542
3564
|
| Prop | Type | Default | Description |
|
|
3543
3565
|
|---|---|---|---|
|
|
3544
|
-
| `value` *required* | [`
|
|
3545
|
-
| `domain` | `"nice" \| "data" \| readonly [number, number]` | `"nice"` | `"nice"` widens the data's extent to ticks - to named `ticks` where there are any, on a time axis to whole units of a step of hours or longer -, `"data"` keeps it, a pair is fixed. On a `zoomable` axis it is the start: what the axis shows while the chart's view names no span for it.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and 10 more |
|
|
3566
|
+
| `value` *required* | [`AxisField`](#type-AxisField)`<T> \| ((d: T, index: number) => `[`AxisValue`](#type-AxisValue)`)` | — | Where a row lies on this axis: a field of the row, compared by its name, or a function of it, compared by its source text as a series' is (`ValueFunction`) - with the same closure limit. A number is a position, a `Date` an instant, a string a category: every distinct one gets a place of its own, in the order the rows bring them, a tick and its name as the label ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). The type decides, never the content - `"2024"` is a category, `2024` a number - and a row without a value has no place. The axis reads the rows of every series bound to it; where those bring their own `data`, name their row: `<XAxis<HourCount> value="hour" />`.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and 70 more |
|
|
3567
|
+
| `domain` | `"nice" \| "data" \| readonly [number, number] \| readonly [Date, Date]` | `"nice"` | `"nice"` widens the data's extent to ticks - to named `ticks` where there are any, on a time axis to whole units of a step of hours or longer -, `"data"` keeps it, a pair is fixed: numbers, on a time axis milliseconds or two `Date`s, on a category axis places. On a `zoomable` axis it is the start: what the axis shows while the chart's view names no span for it.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and 10 more |
|
|
3546
3568
|
| `position` | `"bottom" \| "top"` | `"bottom"` | Which edge the axis stands at. A second x axis on the opposite edge is how a series counts in a unit of its own.<br>Shown in: [Add axes for other magnitudes (Axis)](#/axis/axes), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) |
|
|
3547
|
-
| `time` | `boolean` | — | The values are instants
|
|
3569
|
+
| `time` | `boolean` | — | The values are instants: milliseconds since the epoch, or with `time` said ISO 8601 strings as an API sends them, read by the platform's rule - a date with a time and no offset is local time, a date alone is UTC; a string that is none is not drawn. Rows that hold a `Date` put the axis in time without `time`. Ticks on local boundaries from the minute to the month, labels by level in en-GB with a 24-hour clock - `15:00`, `17 Mar`, `Mar 2026` - and the date on the first tick and on the first of a new day (`17 Mar 00:00`); a day tick there carries the year. The tooltip's x value carries the seconds where the readings lie less than a minute apart. Another language is a `tickFormat`, which is handed the instant in milliseconds. `calendar` implies it.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and 36 more |
|
|
3548
3570
|
| `calendar` | `readonly `[`WorkingInterval`](#type-WorkingInterval)`[]` | — | Working calendar: the intervals in which time counts. With it the axis stands in working time - nights and weekends are out, and every removed span gets a break mark. The scale stays affine; the mapping happens in materialisation ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md)).<br>Shown in: [Leave out the hours nobody works (Axis)](#/axis/working-time) |
|
|
3549
3571
|
| `zoomable` | `boolean` | `false` | Zoom and pan: Ctrl or ⌘ with the wheel - and a pinch - zoom around the pointer, a drag, a horizontal wheel or Shift with the wheel pan, a double click goes back to `domain`; with the focus on the chart, + and − zoom, Shift with ← or → pans and 0 goes back. The span shown is part of the chart's view, by this axis' `id` (`useChart`'s `initialView`, `onViewChange`, `setDomain`). The plain wheel always scrolls the page.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and 5 more |
|
|
3550
3572
|
| `zoomLimits` | [`ZoomLimits`](#type-ZoomLimits) | at most the data's extent or this axis' own `domain`, whichever is wider, at least three data steps | The narrowest and the widest span zoom may reach, in the axis' units - milliseconds on a time axis.<br>Shown in: [Limit the zoom](#/zoom-and-pan/zoom-limits) |
|
|
3551
3573
|
| `id` | `string` | `"x"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes (Axis)](#/axis/axes), [Align a second axis to the grid (Axis)](#/axis/aligned-ticks), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and 7 more |
|
|
3552
|
-
| `label` | `string` | — | Axis title.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and
|
|
3553
|
-
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Keep charts in step](#/zoom-and-pan/cursor-sync), [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Leave out the hours nobody works (Axis)](#/axis/working-time) and
|
|
3554
|
-
| `tickFormat` | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Leave out the hours nobody works (Axis)](#/axis/working-time), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and
|
|
3574
|
+
| `label` | `string` | — | Axis title.<br>Shown in: [Gestures and keys](#/zoom-and-pan/gestures-and-keys), [Limit the zoom](#/zoom-and-pan/zoom-limits), [Fit the y axis to what is visible](#/zoom-and-pan/visible-domain) and 62 more |
|
|
3575
|
+
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Keep charts in step](#/zoom-and-pan/cursor-sync), [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Leave out the hours nobody works (Axis)](#/axis/working-time) and 8 more |
|
|
3576
|
+
| `tickFormat` | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Leave out the hours nobody works (Axis)](#/axis/working-time), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and 19 more |
|
|
3555
3577
|
| `grid` | `boolean` | only on the first axis of its orientation | Grid lines.<br>Shown in: [One vehicle through the day (StateBand)](#/stateband/one-vehicle), [Give each one a lane (StateBand)](#/stateband/one-lane-each), [See where the vans stand idle (Scenarios)](#/scenarios/see-where-the-vans-stand-idle) |
|
|
3556
|
-
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm
|
|
3578
|
+
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm, on a numeric axis: an hour axis' named hours, a lane grid. An axis that reads categories needs none - every category has its tick.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [Compare progress with a plan (Line)](#/line/burn-down) and 7 more |
|
|
3579
|
+
| `categories` | `readonly string[]` | — | The categories of the axis, in their order. Without it they stand in the order the rows first bring them. With it the list is the order and the extent: a category no row carries keeps its empty place, and a row whose category is not listed is not drawn - DEV names it. It makes the axis a category axis whatever the rows hold ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)).<br>Shown in: [Fix the order and keep an empty place (Axis)](#/axis/categories) |
|
|
3580
|
+
| `categoryLabel` | `(category: string) => string` | the category itself | What is written for a category - under its tick, in the tooltip, the readout and the data table - where the rows hold a key and not a name. Two categories may share a label and keep two places. Compared by its source text, as `tickFormat` is.<br>Shown in: [Fix the order and keep an empty place (Axis)](#/axis/categories), [Tell series apart without colour (Chart)](#/chart/told-apart-without-colour), [Legend above or below (Legend)](#/legend/above-or-below) and 1 more |
|
|
3557
3581
|
|
|
3558
3582
|
##### `ChartOptions`
|
|
3559
3583
|
|
|
@@ -3569,18 +3593,33 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3569
3593
|
|
|
3570
3594
|
##### Types on this page
|
|
3571
3595
|
|
|
3572
|
-
###### `
|
|
3596
|
+
###### `AxisField<T, V = AxisValue>`
|
|
3573
3597
|
|
|
3574
|
-
The fields of a row
|
|
3598
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
3599
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
3600
|
+
never an instant.
|
|
3575
3601
|
|
|
3576
3602
|
```ts
|
|
3577
|
-
type
|
|
3603
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
3578
3604
|
{
|
|
3579
|
-
[K in keyof T & string]-?: T[K] extends
|
|
3605
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
3580
3606
|
}[keyof T & string]
|
|
3581
3607
|
>;
|
|
3582
3608
|
```
|
|
3583
3609
|
|
|
3610
|
+
###### `AxisValue`
|
|
3611
|
+
|
|
3612
|
+
What an axis reads from a row ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)): a number - a position, or an
|
|
3613
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
3614
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
3615
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
3616
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
3617
|
+
place.
|
|
3618
|
+
|
|
3619
|
+
```ts
|
|
3620
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
3621
|
+
```
|
|
3622
|
+
|
|
3584
3623
|
###### `WorkingInterval`
|
|
3585
3624
|
|
|
3586
3625
|
An interval in which time counts; thought of as half-open, but mappable from
|
|
@@ -4294,17 +4333,17 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
4294
4333
|
|
|
4295
4334
|
| Prop | Type | Default | Description |
|
|
4296
4335
|
|---|---|---|---|
|
|
4297
|
-
| `value` *required* | [`Value`](#type-Value)`<T>` | — | Y value - a number field of the row or a function of it; null/undefined/NaN/±Infinity means a gap.<br>Shown in: [One series](#/line/one-series), [Several series and a gap](#/line/multi-series), [Hold a value until it changes](#/line/step) and
|
|
4336
|
+
| `value` *required* | [`Value`](#type-Value)`<T>` | — | Y value - a number field of the row or a function of it; null/undefined/NaN/±Infinity means a gap.<br>Shown in: [One series](#/line/one-series), [Several series and a gap](#/line/multi-series), [Hold a value until it changes](#/line/step) and 45 more |
|
|
4298
4337
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Add axes for other magnitudes (Axis)](#/axis/axes), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) |
|
|
4299
4338
|
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Hold a value until it changes](#/line/step), [Add axes for other magnitudes (Axis)](#/axis/axes), [Align a second axis to the grid (Axis)](#/axis/aligned-ticks) and 4 more |
|
|
4300
|
-
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Several series and a gap](#/line/multi-series), [Hold a value until it changes](#/line/step), [Tell series apart without colour (Chart)](#/chart/told-apart-without-colour) and
|
|
4301
|
-
| `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 series](#/line/one-series), [Several series and a gap](#/line/multi-series), [Hold a value until it changes](#/line/step) and
|
|
4339
|
+
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Several series and a gap](#/line/multi-series), [Hold a value until it changes](#/line/step), [Tell series apart without colour (Chart)](#/chart/told-apart-without-colour) and 6 more |
|
|
4340
|
+
| `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 series](#/line/one-series), [Several series and a gap](#/line/multi-series), [Hold a value until it changes](#/line/step) and 45 more |
|
|
4302
4341
|
| `format` | `(value: number) => string` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it.<br>Shown in: [Write each value in its unit (Tooltip)](#/tooltip/value-format), [Show the values as a table (Tooltip)](#/tooltip/data-table), [Table a long series (Tooltip)](#/tooltip/a-week-as-a-table) and 1 more |
|
|
4303
4342
|
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value.<br>Shown in: [Compare progress with a plan](#/line/burn-down), [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Watch latency against its objective (Scenarios)](#/scenarios/watch-latency-against-its-objective) and 1 more |
|
|
4304
4343
|
| `tone` | `"ok" \| "warning" \| "alarm"` | — | A role instead of a colour value; the theme resolves it.<br>Shown in: [Compare the budget with the forecast (Scenarios)](#/scenarios/compare-budget-with-forecast) |
|
|
4305
4344
|
| `strokeWidth` | `number` | `1.5` | Line width in CSS pixels.<br>Shown in: [Hold a value until it changes](#/line/step), [Compare progress with a plan](#/line/burn-down), [Mix series kinds (Chart)](#/chart/mixed) and 9 more |
|
|
4306
4345
|
| `dash` | `readonly number[]` | a solid line | Dash pattern as a run of lengths in CSS pixels.<br>Shown in: [Several series and a gap](#/line/multi-series), [Compare progress with a plan](#/line/burn-down), [Add axes for other magnitudes (Axis)](#/axis/axes) and 1 more |
|
|
4307
|
-
| `markers` | `"auto" \| "always" \| "never"` | `"auto"` | When the individual points are drawn as marks: `auto` every point up to 60 points and above that only a point between two gaps - it has no line to be seen by - `always`, or `never`, not even that one.<br>Shown in: [Compare progress with a plan](#/line/burn-down), [
|
|
4346
|
+
| `markers` | `"auto" \| "always" \| "never"` | `"auto"` | When the individual points are drawn as marks: `auto` every point up to 60 points and above that only a point between two gaps - it has no line to be seen by - `always`, or `never`, not even that one.<br>Shown in: [Compare progress with a plan](#/line/burn-down), [Draw a series per group of long rows (Chart)](#/chart/series-from-long-rows), [Fix the domain and name the ticks (Axis)](#/axis/configuration) and 4 more |
|
|
4308
4347
|
| `step` | `boolean` | — | Sample-and-hold: each value holds as a horizontal until the next sample and jumps there - a target, a digital signal. A gap ends the hold at its x. The tooltip reports the sample the hold began with.<br>Shown in: [Hold a value until it changes](#/line/step) |
|
|
4309
4348
|
|
|
4310
4349
|
##### Types on this page
|
|
@@ -4804,7 +4843,7 @@ type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
|
|
|
4804
4843
|
|
|
4805
4844
|
### Bar
|
|
4806
4845
|
|
|
4807
|
-
Bars
|
|
4846
|
+
Bars along the x axis (a bar or column chart): one per category, grouped side by side, stacked, or as shares of a whole.
|
|
4808
4847
|
|
|
4809
4848
|
```ts
|
|
4810
4849
|
import { useChart } from "@umriss-ui/charts";
|
|
@@ -4812,89 +4851,35 @@ import { useChart } from "@umriss-ui/charts";
|
|
|
4812
4851
|
|
|
4813
4852
|
Demo page: https://romanhaendler.github.io/umriss-ui/charts/bar/
|
|
4814
4853
|
|
|
4815
|
-
|
|
4854
|
+
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.
|
|
4816
4855
|
|
|
4817
4856
|
#### Examples
|
|
4818
4857
|
|
|
4819
4858
|
##### One bar per category
|
|
4820
4859
|
|
|
4821
|
-
|
|
4860
|
+
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)).
|
|
4822
4861
|
|
|
4823
4862
|
```tsx
|
|
4824
4863
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
4825
4864
|
|
|
4826
|
-
/*
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
}
|
|
4835
|
-
}
|
|
4836
|
-
|
|
4837
|
-
interface CostCentre {
|
|
4838
|
-
id: string;
|
|
4839
|
-
name: string;
|
|
4840
|
-
owner: string;
|
|
4841
|
-
/** The budget of one month. */
|
|
4842
|
-
monthlyBudget: number;
|
|
4843
|
-
}
|
|
4844
|
-
|
|
4845
|
-
const COST_CENTRES: readonly CostCentre[] = [
|
|
4846
|
-
{ id: "CC-1100", name: "Sales", owner: "Helen Marsh", monthlyBudget: 142_000 },
|
|
4847
|
-
{ id: "CC-1200", name: "Marketing", owner: "Rafael Ortiz", monthlyBudget: 68_000 },
|
|
4848
|
-
{ id: "CC-2100", name: "Engineering", owner: "Anika Sørensen", monthlyBudget: 188_000 },
|
|
4849
|
-
{ id: "CC-2200", name: "Design", owner: "Paul Whitaker", monthlyBudget: 54_000 },
|
|
4850
|
-
{ id: "CC-3100", name: "Customer service", owner: "Grace Obi", monthlyBudget: 61_000 },
|
|
4851
|
-
{ id: "CC-4100", name: "Finance", owner: "Martina Vogel", monthlyBudget: 47_000 },
|
|
4852
|
-
{ id: "CC-4200", name: "People", owner: "Daniel Frost", monthlyBudget: 39_000 },
|
|
4853
|
-
{ id: "CC-4300", name: "IT", owner: "Kenji Arai", monthlyBudget: 83_000 },
|
|
4854
|
-
{ id: "CC-4400", name: "Facilities", owner: "Olga Ivanova", monthlyBudget: 72_000 },
|
|
4865
|
+
/* February's spend per cost centre, as the ledger sends it. */
|
|
4866
|
+
const SPEND = [
|
|
4867
|
+
{ costCentre: "Sales", actual: 134_447 },
|
|
4868
|
+
{ costCentre: "Marketing", actual: 76_588 },
|
|
4869
|
+
{ costCentre: "Engineering", actual: 183_157 },
|
|
4870
|
+
{ costCentre: "Design", actual: 51_761 },
|
|
4871
|
+
{ costCentre: "Customer service", actual: 61_160 },
|
|
4872
|
+
{ costCentre: "Finance", actual: 46_353 },
|
|
4873
|
+
{ costCentre: "People", actual: 37_886 },
|
|
4874
|
+
{ costCentre: "IT", actual: 74_313 },
|
|
4875
|
+
{ costCentre: "Facilities", actual: 71_848 },
|
|
4855
4876
|
];
|
|
4856
4877
|
|
|
4857
|
-
/** The year's months, as `"2026-01"` … `"2026-12"`. */
|
|
4858
|
-
const MONTHS: readonly string[] = Array.from({ length: 12 }, (_, i) => `2026-${String(i + 1).padStart(2, "0")}`);
|
|
4859
|
-
|
|
4860
|
-
/** The months whose books are closed: they have an actual. */
|
|
4861
|
-
const CLOSED_MONTHS = 2;
|
|
4862
|
-
|
|
4863
|
-
interface LedgerRow {
|
|
4864
|
-
costCentre: string;
|
|
4865
|
-
month: string;
|
|
4866
|
-
budget: number;
|
|
4867
|
-
/** `null` while the month is open. */
|
|
4868
|
-
actual: number | null;
|
|
4869
|
-
forecast: number;
|
|
4870
|
-
}
|
|
4871
|
-
|
|
4872
|
-
/* Where a cost centre lands against its budget, as a factor: Marketing's
|
|
4873
|
-
spring campaign overspends, IT's delayed licences underspend. */
|
|
4874
|
-
const TENDENCY: Readonly<Record<string, number>> = { "CC-1200": 1.14, "CC-4300": 0.88 };
|
|
4875
|
-
|
|
4876
|
-
/** Budget, actual and forecast of every cost centre in every month. */
|
|
4877
|
-
const LEDGER: readonly LedgerRow[] = COST_CENTRES.flatMap((centre, c) => {
|
|
4878
|
-
const r = random(310 + c);
|
|
4879
|
-
const tendency = TENDENCY[centre.id] ?? 1;
|
|
4880
|
-
return MONTHS.map((month, m) => {
|
|
4881
|
-
/* December pays the bonuses, August is the quiet month. */
|
|
4882
|
-
const season = m === 11 ? 1.12 : m === 7 ? 0.9 : 1;
|
|
4883
|
-
const budget = Math.round(centre.monthlyBudget * season);
|
|
4884
|
-
const forecast = Math.round((budget * (tendency + (r() - 0.5) * 0.06)) / 100) * 100;
|
|
4885
|
-
const actual = m < CLOSED_MONTHS ? Math.round(forecast * (1 + (r() - 0.5) * 0.08)) : null;
|
|
4886
|
-
return { costCentre: centre.id, month, budget, actual, forecast };
|
|
4887
|
-
});
|
|
4888
|
-
});
|
|
4889
|
-
|
|
4890
|
-
const FEBRUARY = LEDGER.filter((row) => row.month === "2026-02");
|
|
4891
|
-
const place = (row: LedgerRow) => COST_CENTRES.findIndex((c) => c.id === row.costCentre);
|
|
4892
|
-
|
|
4893
4878
|
export default function PerCategory() {
|
|
4894
|
-
const { Chart, XAxis, YAxis, Bar } = useChart(
|
|
4879
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(SPEND);
|
|
4895
4880
|
return (
|
|
4896
4881
|
<Chart height={260} ariaLabel="Actual spend per cost centre in February">
|
|
4897
|
-
<XAxis value=
|
|
4882
|
+
<XAxis value="costCentre" />
|
|
4898
4883
|
<YAxis label="€" />
|
|
4899
4884
|
<Bar value="actual" name="Actual" />
|
|
4900
4885
|
<Tooltip mode="x" />
|
|
@@ -4910,170 +4895,55 @@ Bar series on one x axis stand beside each other; give each the same `barWidth`,
|
|
|
4910
4895
|
```tsx
|
|
4911
4896
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
4912
4897
|
|
|
4913
|
-
/*
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
}
|
|
4922
|
-
}
|
|
4923
|
-
|
|
4924
|
-
interface CostCentre {
|
|
4925
|
-
id: string;
|
|
4926
|
-
name: string;
|
|
4927
|
-
owner: string;
|
|
4928
|
-
/** The budget of one month. */
|
|
4929
|
-
monthlyBudget: number;
|
|
4930
|
-
}
|
|
4931
|
-
|
|
4932
|
-
const COST_CENTRES: readonly CostCentre[] = [
|
|
4933
|
-
{ id: "CC-1100", name: "Sales", owner: "Helen Marsh", monthlyBudget: 142_000 },
|
|
4934
|
-
{ id: "CC-1200", name: "Marketing", owner: "Rafael Ortiz", monthlyBudget: 68_000 },
|
|
4935
|
-
{ id: "CC-2100", name: "Engineering", owner: "Anika Sørensen", monthlyBudget: 188_000 },
|
|
4936
|
-
{ id: "CC-2200", name: "Design", owner: "Paul Whitaker", monthlyBudget: 54_000 },
|
|
4937
|
-
{ id: "CC-3100", name: "Customer service", owner: "Grace Obi", monthlyBudget: 61_000 },
|
|
4938
|
-
{ id: "CC-4100", name: "Finance", owner: "Martina Vogel", monthlyBudget: 47_000 },
|
|
4939
|
-
{ id: "CC-4200", name: "People", owner: "Daniel Frost", monthlyBudget: 39_000 },
|
|
4940
|
-
{ id: "CC-4300", name: "IT", owner: "Kenji Arai", monthlyBudget: 83_000 },
|
|
4941
|
-
{ id: "CC-4400", name: "Facilities", owner: "Olga Ivanova", monthlyBudget: 72_000 },
|
|
4898
|
+
/* February's budget and spend per cost centre, as the ledger sends them. */
|
|
4899
|
+
const FEBRUARY = [
|
|
4900
|
+
{ costCentre: "Sales", budget: 142_000, actual: 134_447 },
|
|
4901
|
+
{ costCentre: "Marketing", budget: 68_000, actual: 76_588 },
|
|
4902
|
+
{ costCentre: "Engineering", budget: 188_000, actual: 183_157 },
|
|
4903
|
+
{ costCentre: "Design", budget: 54_000, actual: 51_761 },
|
|
4904
|
+
{ costCentre: "Customer service", budget: 61_000, actual: 61_160 },
|
|
4905
|
+
{ costCentre: "Finance", budget: 47_000, actual: 46_353 },
|
|
4906
|
+
{ costCentre: "People", budget: 39_000, actual: 37_886 },
|
|
4907
|
+
{ costCentre: "IT", budget: 83_000, actual: 74_313 },
|
|
4908
|
+
{ costCentre: "Facilities", budget: 72_000, actual: 71_848 },
|
|
4942
4909
|
];
|
|
4943
4910
|
|
|
4944
|
-
/** The year's months, as `"2026-01"` … `"2026-12"`. */
|
|
4945
|
-
const MONTHS: readonly string[] = Array.from({ length: 12 }, (_, i) => `2026-${String(i + 1).padStart(2, "0")}`);
|
|
4946
|
-
|
|
4947
|
-
/** The months whose books are closed: they have an actual. */
|
|
4948
|
-
const CLOSED_MONTHS = 2;
|
|
4949
|
-
|
|
4950
|
-
interface LedgerRow {
|
|
4951
|
-
costCentre: string;
|
|
4952
|
-
month: string;
|
|
4953
|
-
budget: number;
|
|
4954
|
-
/** `null` while the month is open. */
|
|
4955
|
-
actual: number | null;
|
|
4956
|
-
forecast: number;
|
|
4957
|
-
}
|
|
4958
|
-
|
|
4959
|
-
/* Where a cost centre lands against its budget, as a factor: Marketing's
|
|
4960
|
-
spring campaign overspends, IT's delayed licences underspend. */
|
|
4961
|
-
const TENDENCY: Readonly<Record<string, number>> = { "CC-1200": 1.14, "CC-4300": 0.88 };
|
|
4962
|
-
|
|
4963
|
-
/** Budget, actual and forecast of every cost centre in every month. */
|
|
4964
|
-
const LEDGER: readonly LedgerRow[] = COST_CENTRES.flatMap((centre, c) => {
|
|
4965
|
-
const r = random(310 + c);
|
|
4966
|
-
const tendency = TENDENCY[centre.id] ?? 1;
|
|
4967
|
-
return MONTHS.map((month, m) => {
|
|
4968
|
-
/* December pays the bonuses, August is the quiet month. */
|
|
4969
|
-
const season = m === 11 ? 1.12 : m === 7 ? 0.9 : 1;
|
|
4970
|
-
const budget = Math.round(centre.monthlyBudget * season);
|
|
4971
|
-
const forecast = Math.round((budget * (tendency + (r() - 0.5) * 0.06)) / 100) * 100;
|
|
4972
|
-
const actual = m < CLOSED_MONTHS ? Math.round(forecast * (1 + (r() - 0.5) * 0.08)) : null;
|
|
4973
|
-
return { costCentre: centre.id, month, budget, actual, forecast };
|
|
4974
|
-
});
|
|
4975
|
-
});
|
|
4976
|
-
|
|
4977
|
-
const FEBRUARY = LEDGER.filter((row) => row.month === "2026-02");
|
|
4978
|
-
const place = (row: LedgerRow) => COST_CENTRES.findIndex((c) => c.id === row.costCentre);
|
|
4979
|
-
|
|
4980
4911
|
export default function Grouped() {
|
|
4981
4912
|
const { Chart, XAxis, YAxis, Bar } = useChart(FEBRUARY);
|
|
4982
4913
|
return (
|
|
4983
4914
|
<Chart height={280} ariaLabel="Budget and actual per cost centre in February">
|
|
4984
|
-
<XAxis value=
|
|
4915
|
+
<XAxis value="costCentre" />
|
|
4985
4916
|
<YAxis label="€" />
|
|
4986
|
-
{/* A token, so that it follows the colour scheme. */}
|
|
4987
|
-
<Bar value="budget" name="Budget" color="var(--uc-color-text)" barWidth={0.7} />
|
|
4988
|
-
<Bar value="actual" name="Actual" barWidth={0.7} />
|
|
4989
|
-
<Legend placement="top" />
|
|
4990
|
-
<Tooltip mode="x" />
|
|
4991
|
-
</Chart>
|
|
4992
|
-
);
|
|
4993
|
-
}
|
|
4994
|
-
```
|
|
4995
|
-
|
|
4996
|
-
##### Show bars above and below zero
|
|
4997
|
-
|
|
4998
|
-
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.
|
|
4999
|
-
|
|
5000
|
-
```tsx
|
|
5001
|
-
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
5002
|
-
|
|
5003
|
-
/* Data from the controlling world, written out here so the example runs on its own. */
|
|
5004
|
-
|
|
5005
|
-
/** A small LCG - the same numbers on every computer. */
|
|
5006
|
-
function random(seed: number): () => number {
|
|
5007
|
-
let s = seed >>> 0;
|
|
5008
|
-
return () => {
|
|
5009
|
-
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
5010
|
-
return s / 4294967296;
|
|
5011
|
-
};
|
|
5012
|
-
}
|
|
5013
|
-
|
|
5014
|
-
interface CostCentre {
|
|
5015
|
-
id: string;
|
|
5016
|
-
name: string;
|
|
5017
|
-
owner: string;
|
|
5018
|
-
/** The budget of one month. */
|
|
5019
|
-
monthlyBudget: number;
|
|
5020
|
-
}
|
|
5021
|
-
|
|
5022
|
-
const COST_CENTRES: readonly CostCentre[] = [
|
|
5023
|
-
{ id: "CC-1100", name: "Sales", owner: "Helen Marsh", monthlyBudget: 142_000 },
|
|
5024
|
-
{ id: "CC-1200", name: "Marketing", owner: "Rafael Ortiz", monthlyBudget: 68_000 },
|
|
5025
|
-
{ id: "CC-2100", name: "Engineering", owner: "Anika Sørensen", monthlyBudget: 188_000 },
|
|
5026
|
-
{ id: "CC-2200", name: "Design", owner: "Paul Whitaker", monthlyBudget: 54_000 },
|
|
5027
|
-
{ id: "CC-3100", name: "Customer service", owner: "Grace Obi", monthlyBudget: 61_000 },
|
|
5028
|
-
{ id: "CC-4100", name: "Finance", owner: "Martina Vogel", monthlyBudget: 47_000 },
|
|
5029
|
-
{ id: "CC-4200", name: "People", owner: "Daniel Frost", monthlyBudget: 39_000 },
|
|
5030
|
-
{ id: "CC-4300", name: "IT", owner: "Kenji Arai", monthlyBudget: 83_000 },
|
|
5031
|
-
{ id: "CC-4400", name: "Facilities", owner: "Olga Ivanova", monthlyBudget: 72_000 },
|
|
5032
|
-
];
|
|
5033
|
-
|
|
5034
|
-
/** The year's months, as `"2026-01"` … `"2026-12"`. */
|
|
5035
|
-
const MONTHS: readonly string[] = Array.from({ length: 12 }, (_, i) => `2026-${String(i + 1).padStart(2, "0")}`);
|
|
5036
|
-
|
|
5037
|
-
/** The months whose books are closed: they have an actual. */
|
|
5038
|
-
const CLOSED_MONTHS = 2;
|
|
5039
|
-
|
|
5040
|
-
interface LedgerRow {
|
|
5041
|
-
costCentre: string;
|
|
5042
|
-
month: string;
|
|
5043
|
-
budget: number;
|
|
5044
|
-
/** `null` while the month is open. */
|
|
5045
|
-
actual: number | null;
|
|
5046
|
-
forecast: number;
|
|
5047
|
-
}
|
|
5048
|
-
|
|
5049
|
-
/* Where a cost centre lands against its budget, as a factor: Marketing's
|
|
5050
|
-
spring campaign overspends, IT's delayed licences underspend. */
|
|
5051
|
-
const TENDENCY: Readonly<Record<string, number>> = { "CC-1200": 1.14, "CC-4300": 0.88 };
|
|
5052
|
-
|
|
5053
|
-
/** Budget, actual and forecast of every cost centre in every month. */
|
|
5054
|
-
const LEDGER: readonly LedgerRow[] = COST_CENTRES.flatMap((centre, c) => {
|
|
5055
|
-
const r = random(310 + c);
|
|
5056
|
-
const tendency = TENDENCY[centre.id] ?? 1;
|
|
5057
|
-
return MONTHS.map((month, m) => {
|
|
5058
|
-
/* December pays the bonuses, August is the quiet month. */
|
|
5059
|
-
const season = m === 11 ? 1.12 : m === 7 ? 0.9 : 1;
|
|
5060
|
-
const budget = Math.round(centre.monthlyBudget * season);
|
|
5061
|
-
const forecast = Math.round((budget * (tendency + (r() - 0.5) * 0.06)) / 100) * 100;
|
|
5062
|
-
const actual = m < CLOSED_MONTHS ? Math.round(forecast * (1 + (r() - 0.5) * 0.08)) : null;
|
|
5063
|
-
return { costCentre: centre.id, month, budget, actual, forecast };
|
|
5064
|
-
});
|
|
5065
|
-
});
|
|
5066
|
-
|
|
5067
|
-
interface Deviation {
|
|
5068
|
-
place: number;
|
|
5069
|
-
euros: number;
|
|
4917
|
+
{/* A token, so that it follows the colour scheme. */}
|
|
4918
|
+
<Bar value="budget" name="Budget" color="var(--uc-color-text)" barWidth={0.7} />
|
|
4919
|
+
<Bar value="actual" name="Actual" barWidth={0.7} />
|
|
4920
|
+
<Legend placement="top" />
|
|
4921
|
+
<Tooltip mode="x" />
|
|
4922
|
+
</Chart>
|
|
4923
|
+
);
|
|
5070
4924
|
}
|
|
4925
|
+
```
|
|
5071
4926
|
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
4927
|
+
##### Show bars above and below zero
|
|
4928
|
+
|
|
4929
|
+
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.
|
|
4930
|
+
|
|
4931
|
+
```tsx
|
|
4932
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
4933
|
+
|
|
4934
|
+
/* The year's forecast less its budget per cost centre, in euros - summed
|
|
4935
|
+
where the ledger lives, before the chart. */
|
|
4936
|
+
const DEVIATION = [
|
|
4937
|
+
{ costCentre: "Sales", euros: 60 },
|
|
4938
|
+
{ costCentre: "Marketing", euros: 118_840 },
|
|
4939
|
+
{ costCentre: "Engineering", euros: 1_440 },
|
|
4940
|
+
{ costCentre: "Design", euros: 720 },
|
|
4941
|
+
{ costCentre: "Customer service", euros: 980 },
|
|
4942
|
+
{ costCentre: "Finance", euros: -1_940 },
|
|
4943
|
+
{ costCentre: "People", euros: -1_480 },
|
|
4944
|
+
{ costCentre: "IT", euros: -112_560 },
|
|
4945
|
+
{ costCentre: "Facilities", euros: -2_240 },
|
|
4946
|
+
];
|
|
5077
4947
|
|
|
5078
4948
|
const signed = (v: number) => (v > 0 ? `+${(v / 1000).toFixed(0)}k` : `${(v / 1000).toFixed(0)}k`);
|
|
5079
4949
|
|
|
@@ -5081,7 +4951,7 @@ export default function Deviation() {
|
|
|
5081
4951
|
const { Chart, XAxis, YAxis, Bar } = useChart(DEVIATION);
|
|
5082
4952
|
return (
|
|
5083
4953
|
<Chart height={280} ariaLabel="Forecast against budget for the year, per cost centre">
|
|
5084
|
-
<XAxis value="
|
|
4954
|
+
<XAxis value="costCentre" />
|
|
5085
4955
|
<YAxis tickFormat={signed} label="€" />
|
|
5086
4956
|
<Bar value="euros" name="Over budget" />
|
|
5087
4957
|
<Tooltip mode="x" />
|
|
@@ -5097,48 +4967,28 @@ Bars with the same `stack` stand on the ones before them; a missing part stacks
|
|
|
5097
4967
|
```tsx
|
|
5098
4968
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5099
4969
|
|
|
5100
|
-
/*
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
day:
|
|
5114
|
-
|
|
5115
|
-
/** Minutes late across all tours, by cause; `null` where it was not logged. */
|
|
5116
|
-
traffic: number;
|
|
5117
|
-
loading: number | null;
|
|
5118
|
-
access: number;
|
|
5119
|
-
}
|
|
5120
|
-
|
|
5121
|
-
/** Two working weeks, 2 to 13 March. On the first Wednesday the loading log
|
|
5122
|
-
was not kept - a gap, not a zero - and on the second Thursday a closed
|
|
5123
|
-
bridge takes the day. */
|
|
5124
|
-
const DELAYS: readonly DayDelays[] = (() => {
|
|
5125
|
-
const r = random(1204);
|
|
5126
|
-
const names = ["Mon", "Tue", "Wed", "Thu", "Fri"];
|
|
5127
|
-
return Array.from({ length: 10 }, (_, day) => ({
|
|
5128
|
-
day,
|
|
5129
|
-
name: `${names[day % 5]} ${2 + day + 2 * Math.floor(day / 5)}`,
|
|
5130
|
-
traffic: Math.round((day === 8 ? 150 : 40) + r() * 30),
|
|
5131
|
-
loading: day === 2 ? null : Math.round(10 + r() * 45),
|
|
5132
|
-
access: Math.round(5 + r() * 25),
|
|
5133
|
-
}));
|
|
5134
|
-
})();
|
|
4970
|
+
/* Minutes late across all tours per working day, by cause - two weeks, 2 to
|
|
4971
|
+
13 March. On the first Wednesday the loading log was not kept: a gap, not
|
|
4972
|
+
a zero. On the second Thursday a closed bridge takes the day. */
|
|
4973
|
+
const DELAYS = [
|
|
4974
|
+
{ day: "Mon 2", traffic: 61, loading: 32, access: 10 },
|
|
4975
|
+
{ day: "Tue 3", traffic: 47, loading: 46, access: 27 },
|
|
4976
|
+
{ day: "Wed 4", traffic: 49, loading: null, access: 22 },
|
|
4977
|
+
{ day: "Thu 5", traffic: 40, loading: 44, access: 7 },
|
|
4978
|
+
{ day: "Fri 6", traffic: 48, loading: 44, access: 27 },
|
|
4979
|
+
{ day: "Mon 9", traffic: 67, loading: 41, access: 8 },
|
|
4980
|
+
{ day: "Tue 10", traffic: 58, loading: 50, access: 13 },
|
|
4981
|
+
{ day: "Wed 11", traffic: 58, loading: 18, access: 14 },
|
|
4982
|
+
{ day: "Thu 12", traffic: 168, loading: 26, access: 10 },
|
|
4983
|
+
{ day: "Fri 13", traffic: 57, loading: 18, access: 18 },
|
|
4984
|
+
];
|
|
5135
4985
|
|
|
5136
4986
|
export default function Stacked() {
|
|
5137
4987
|
const { Chart, XAxis, YAxis, Bar } = useChart(DELAYS);
|
|
5138
4988
|
|
|
5139
4989
|
return (
|
|
5140
4990
|
<Chart height={280} ariaLabel="Minutes late per working day, by cause">
|
|
5141
|
-
<XAxis value="day"
|
|
4991
|
+
<XAxis value="day" />
|
|
5142
4992
|
<YAxis label="Minutes late" />
|
|
5143
4993
|
<Bar value="traffic" name="Traffic" stack="delay" />
|
|
5144
4994
|
<Bar value="loading" name="Loading" stack="delay" />
|
|
@@ -5157,41 +5007,21 @@ export default function Stacked() {
|
|
|
5157
5007
|
```tsx
|
|
5158
5008
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5159
5009
|
|
|
5160
|
-
/*
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
}
|
|
5169
|
-
}
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
day:
|
|
5174
|
-
|
|
5175
|
-
/** Minutes late across all tours, by cause; `null` where it was not logged. */
|
|
5176
|
-
traffic: number;
|
|
5177
|
-
loading: number | null;
|
|
5178
|
-
access: number;
|
|
5179
|
-
}
|
|
5180
|
-
|
|
5181
|
-
/** Two working weeks, 2 to 13 March. On the first Wednesday the loading log
|
|
5182
|
-
was not kept - a gap, not a zero - and on the second Thursday a closed
|
|
5183
|
-
bridge takes the day. */
|
|
5184
|
-
const DELAYS: readonly DayDelays[] = (() => {
|
|
5185
|
-
const r = random(1204);
|
|
5186
|
-
const names = ["Mon", "Tue", "Wed", "Thu", "Fri"];
|
|
5187
|
-
return Array.from({ length: 10 }, (_, day) => ({
|
|
5188
|
-
day,
|
|
5189
|
-
name: `${names[day % 5]} ${2 + day + 2 * Math.floor(day / 5)}`,
|
|
5190
|
-
traffic: Math.round((day === 8 ? 150 : 40) + r() * 30),
|
|
5191
|
-
loading: day === 2 ? null : Math.round(10 + r() * 45),
|
|
5192
|
-
access: Math.round(5 + r() * 25),
|
|
5193
|
-
}));
|
|
5194
|
-
})();
|
|
5010
|
+
/* Minutes late across all tours per working day, by cause - two weeks, 2 to
|
|
5011
|
+
13 March. On the first Wednesday the loading log was not kept: a gap, not
|
|
5012
|
+
a zero. On the second Thursday a closed bridge takes the day. */
|
|
5013
|
+
const DELAYS = [
|
|
5014
|
+
{ day: "Mon 2", traffic: 61, loading: 32, access: 10 },
|
|
5015
|
+
{ day: "Tue 3", traffic: 47, loading: 46, access: 27 },
|
|
5016
|
+
{ day: "Wed 4", traffic: 49, loading: null, access: 22 },
|
|
5017
|
+
{ day: "Thu 5", traffic: 40, loading: 44, access: 7 },
|
|
5018
|
+
{ day: "Fri 6", traffic: 48, loading: 44, access: 27 },
|
|
5019
|
+
{ day: "Mon 9", traffic: 67, loading: 41, access: 8 },
|
|
5020
|
+
{ day: "Tue 10", traffic: 58, loading: 50, access: 13 },
|
|
5021
|
+
{ day: "Wed 11", traffic: 58, loading: 18, access: 14 },
|
|
5022
|
+
{ day: "Thu 12", traffic: 168, loading: 26, access: 10 },
|
|
5023
|
+
{ day: "Fri 13", traffic: 57, loading: 18, access: 18 },
|
|
5024
|
+
];
|
|
5195
5025
|
|
|
5196
5026
|
const minutes = (v: number) => `${v} min`;
|
|
5197
5027
|
|
|
@@ -5199,7 +5029,7 @@ export default function Percent() {
|
|
|
5199
5029
|
const { Chart, XAxis, YAxis, Bar } = useChart(DELAYS);
|
|
5200
5030
|
return (
|
|
5201
5031
|
<Chart height={280} ariaLabel="Share of each cause in the minutes late per working day">
|
|
5202
|
-
<XAxis value="day"
|
|
5032
|
+
<XAxis value="day" />
|
|
5203
5033
|
<YAxis />
|
|
5204
5034
|
<Bar value="traffic" name="Traffic" stack="delay" normalize format={minutes} />
|
|
5205
5035
|
<Bar value="loading" name="Loading" stack="delay" normalize format={minutes} />
|
|
@@ -5225,11 +5055,11 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
5225
5055
|
|
|
5226
5056
|
| Prop | Type | Default | Description |
|
|
5227
5057
|
|---|---|---|---|
|
|
5228
|
-
| `value` *required* | [`Value`](#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](#/bar/per-category), [Group bars side by side](#/bar/grouped), [Show bars above and below zero](#/bar/deviation) and
|
|
5058
|
+
| `value` *required* | [`Value`](#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](#/bar/per-category), [Group bars side by side](#/bar/grouped), [Show bars above and below zero](#/bar/deviation) and 11 more |
|
|
5229
5059
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) |
|
|
5230
5060
|
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) |
|
|
5231
5061
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) |
|
|
5232
|
-
| `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](#/bar/per-category), [Group bars side by side](#/bar/grouped), [Show bars above and below zero](#/bar/deviation) and
|
|
5062
|
+
| `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](#/bar/per-category), [Group bars side by side](#/bar/grouped), [Show bars above and below zero](#/bar/deviation) and 11 more |
|
|
5233
5063
|
| `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](#/bar/percent), [Compare the budget with the forecast (Scenarios)](#/scenarios/compare-budget-with-forecast) |
|
|
5234
5064
|
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value.<br>Shown in: [Group bars side by side](#/bar/grouped), [See where the vans stand idle (Scenarios)](#/scenarios/see-where-the-vans-stand-idle), [Compare the budget with the forecast (Scenarios)](#/scenarios/compare-budget-with-forecast) |
|
|
5235
5065
|
| `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)](#/scenarios/compare-budget-with-forecast) |
|
|
@@ -5595,13 +5425,13 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/boxplot/
|
|
|
5595
5425
|
|
|
5596
5426
|
Every number is the caller's: `median`, the two quartiles and where each whisker ends. The library computes none of them and names no quartile method or whisker rule, so a box aggregated in a database draws as well as one computed in the browser.
|
|
5597
5427
|
|
|
5598
|
-
A box stands on the
|
|
5428
|
+
A box stands on the x axis as a bar does: a group is a category the axis reads from the row, and a time axis carries a box per hour ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). A missing median is a gap - no box is drawn there.
|
|
5599
5429
|
|
|
5600
5430
|
#### Examples
|
|
5601
5431
|
|
|
5602
5432
|
##### One box per service
|
|
5603
5433
|
|
|
5604
|
-
Five numbers per row, computed wherever the caller likes.
|
|
5434
|
+
Five numbers per row, computed wherever the caller likes. The x axis reads the service's name: each one is a category with a place and a tick of its own.
|
|
5605
5435
|
|
|
5606
5436
|
```tsx
|
|
5607
5437
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -5626,7 +5456,7 @@ export default function Minimal() {
|
|
|
5626
5456
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(RESPONSE_TIMES);
|
|
5627
5457
|
return (
|
|
5628
5458
|
<Chart height={260} ariaLabel="Response time per service over the last hour">
|
|
5629
|
-
<XAxis value=
|
|
5459
|
+
<XAxis value="service" />
|
|
5630
5460
|
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5631
5461
|
<BoxPlot
|
|
5632
5462
|
name="Response time"
|
|
@@ -5818,7 +5648,7 @@ export default function WithOutliers() {
|
|
|
5818
5648
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(RESPONSE_TIMES);
|
|
5819
5649
|
return (
|
|
5820
5650
|
<Chart height={300} ariaLabel="Response time per service over the last hour, with outliers">
|
|
5821
|
-
<XAxis value=
|
|
5651
|
+
<XAxis value="service" />
|
|
5822
5652
|
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5823
5653
|
<BoxPlot
|
|
5824
5654
|
name="Response time"
|
|
@@ -5866,7 +5696,7 @@ export default function MeanAndNotches() {
|
|
|
5866
5696
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(BATCHES);
|
|
5867
5697
|
return (
|
|
5868
5698
|
<Chart height={300} ariaLabel="Breaking strength per clay supplier, with mean and notches">
|
|
5869
|
-
<XAxis value=
|
|
5699
|
+
<XAxis value="supplier" />
|
|
5870
5700
|
<YAxis tickFormat={(v) => `${v} N`} />
|
|
5871
5701
|
<BoxPlot
|
|
5872
5702
|
name="Breaking strength"
|
|
@@ -5898,7 +5728,7 @@ Box series on one x axis stand beside each other, as bars do. A click in the leg
|
|
|
5898
5728
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5899
5729
|
|
|
5900
5730
|
interface Spread {
|
|
5901
|
-
service:
|
|
5731
|
+
service: string;
|
|
5902
5732
|
low: number;
|
|
5903
5733
|
q1: number;
|
|
5904
5734
|
median: number;
|
|
@@ -5906,27 +5736,25 @@ interface Spread {
|
|
|
5906
5736
|
high: number;
|
|
5907
5737
|
}
|
|
5908
5738
|
|
|
5909
|
-
const SERVICES = ["Checkout", "Billing", "Search", "Images"];
|
|
5910
|
-
|
|
5911
5739
|
/* Milliseconds per request, the week before and the week after the release. */
|
|
5912
5740
|
const BEFORE: Spread[] = [
|
|
5913
|
-
{ service:
|
|
5914
|
-
{ service:
|
|
5915
|
-
{ service:
|
|
5916
|
-
{ service:
|
|
5741
|
+
{ service: "Checkout", low: 112, q1: 138, median: 149, q3: 161, high: 195 },
|
|
5742
|
+
{ service: "Billing", low: 85, q1: 121, median: 140, q3: 173, high: 238 },
|
|
5743
|
+
{ service: "Search", low: 140, q1: 152, median: 158, q3: 165, high: 181 },
|
|
5744
|
+
{ service: "Images", low: 99, q1: 126, median: 137, q3: 150, high: 186 },
|
|
5917
5745
|
];
|
|
5918
5746
|
const AFTER: Spread[] = [
|
|
5919
|
-
{ service:
|
|
5920
|
-
{ service:
|
|
5921
|
-
{ service:
|
|
5922
|
-
{ service:
|
|
5747
|
+
{ service: "Checkout", low: 118, q1: 135, median: 143, q3: 152, high: 176 },
|
|
5748
|
+
{ service: "Billing", low: 101, q1: 119, median: 128, q3: 139, high: 164 },
|
|
5749
|
+
{ service: "Search", low: 142, q1: 151, median: 157, q3: 164, high: 179 },
|
|
5750
|
+
{ service: "Images", low: 103, q1: 124, median: 135, q3: 146, high: 172 },
|
|
5923
5751
|
];
|
|
5924
5752
|
|
|
5925
5753
|
export default function Grouped() {
|
|
5926
5754
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(BEFORE);
|
|
5927
5755
|
return (
|
|
5928
5756
|
<Chart height={280} ariaLabel="Response time per service before and after the release">
|
|
5929
|
-
<XAxis value="service"
|
|
5757
|
+
<XAxis value="service" />
|
|
5930
5758
|
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5931
5759
|
{[
|
|
5932
5760
|
{ name: "Before", data: BEFORE },
|
|
@@ -5958,7 +5786,7 @@ A `LimitLine` shows what was promised. Whether a box breaks it is your judgement
|
|
|
5958
5786
|
import { Legend, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5959
5787
|
|
|
5960
5788
|
interface Spread {
|
|
5961
|
-
region:
|
|
5789
|
+
region: string;
|
|
5962
5790
|
low: number;
|
|
5963
5791
|
q1: number;
|
|
5964
5792
|
median: number;
|
|
@@ -5966,25 +5794,23 @@ interface Spread {
|
|
|
5966
5794
|
high: number;
|
|
5967
5795
|
}
|
|
5968
5796
|
|
|
5969
|
-
const REGIONS = ["Europe", "Americas", "Asia"];
|
|
5970
|
-
|
|
5971
5797
|
/* Checkout's 95th percentile per five minutes, in ms, per region and week. */
|
|
5972
5798
|
const LAST_WEEK: Spread[] = [
|
|
5973
|
-
{ region:
|
|
5974
|
-
{ region:
|
|
5975
|
-
{ region:
|
|
5799
|
+
{ region: "Europe", low: 182, q1: 204, median: 216, q3: 229, high: 262 },
|
|
5800
|
+
{ region: "Americas", low: 191, q1: 213, median: 226, q3: 240, high: 276 },
|
|
5801
|
+
{ region: "Asia", low: 176, q1: 199, median: 211, q3: 224, high: 258 },
|
|
5976
5802
|
];
|
|
5977
5803
|
const THIS_WEEK: Spread[] = [
|
|
5978
|
-
{ region:
|
|
5979
|
-
{ region:
|
|
5980
|
-
{ region:
|
|
5804
|
+
{ region: "Europe", low: 196, q1: 231, median: 249, q3: 268, high: 318 },
|
|
5805
|
+
{ region: "Americas", low: 205, q1: 242, median: 262, q3: 283, high: 339 },
|
|
5806
|
+
{ region: "Asia", low: 190, q1: 226, median: 244, q3: 263, high: 311 },
|
|
5981
5807
|
];
|
|
5982
5808
|
|
|
5983
5809
|
export default function AgainstTheObjective() {
|
|
5984
5810
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(LAST_WEEK);
|
|
5985
5811
|
return (
|
|
5986
5812
|
<Chart height={280} ariaLabel="Checkout 95th percentile per region and week against its objective">
|
|
5987
|
-
<XAxis value="region"
|
|
5813
|
+
<XAxis value="region" />
|
|
5988
5814
|
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5989
5815
|
<BoxPlot
|
|
5990
5816
|
name="Last week"
|
|
@@ -6019,167 +5845,17 @@ The tiles measured in each of six shifts, one box each, and a `Line` through the
|
|
|
6019
5845
|
```tsx
|
|
6020
5846
|
import { DataTable, Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
6021
5847
|
|
|
6022
|
-
/* Data from the plant world
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
}
|
|
6031
|
-
}
|
|
6032
|
-
|
|
6033
|
-
/** The early shift, 06:00 to 14:00. */
|
|
6034
|
-
const SHIFT_MINUTES = 480;
|
|
6035
|
-
|
|
6036
|
-
/** The kiln's zone 3, in °C: the numbers every part reads it against, once.
|
|
6037
|
-
Between the two warnings lies the tolerance - a tile fired outside it is
|
|
6038
|
-
scrap; `returnTo` is the alarm's dead band. */
|
|
6039
|
-
const KILN = { target: 1200, tolerance: [1185, 1215], alarm: 1230, returnTo: 1222 } as const;
|
|
6040
|
-
|
|
6041
|
-
/** The kiln fires one tile every quarter of a minute when nothing holds it up -
|
|
6042
|
-
the ideal cycle time of the OEE's performance. */
|
|
6043
|
-
const IDEAL_CYCLE_MINUTES = 0.25;
|
|
6044
|
-
|
|
6045
|
-
/** One minute of the line. */
|
|
6046
|
-
interface Reading {
|
|
6047
|
-
/** Minutes since the start of the shift. */
|
|
6048
|
-
minute: number;
|
|
6049
|
-
/** Zone 3 of the kiln, in °C. */
|
|
6050
|
-
kiln: number;
|
|
6051
|
-
/** The exit pyrometer, in °C - `null` while it sends nothing. */
|
|
6052
|
-
exit: number | null;
|
|
6053
|
-
/** Whether the belt runs. */
|
|
6054
|
-
running: boolean;
|
|
6055
|
-
/** Tiles out of the kiln in this minute. */
|
|
6056
|
-
fired: number;
|
|
6057
|
-
/** Of them, the ones fired inside the tolerance. */
|
|
6058
|
-
good: number;
|
|
6059
|
-
}
|
|
6060
|
-
|
|
6061
|
-
/** A tile taken off the belt and measured - one every ten minutes while it runs. */
|
|
6062
|
-
interface Sample {
|
|
6063
|
-
minute: number;
|
|
6064
|
-
/** The tile's length after firing, in mm (nominal 600). */
|
|
6065
|
-
length: number;
|
|
6066
|
-
}
|
|
6067
|
-
|
|
6068
|
-
/** A batch of tiles through the three stations. */
|
|
6069
|
-
interface Batch {
|
|
6070
|
-
id: string;
|
|
6071
|
-
name: string;
|
|
6072
|
-
/** How many tiles the batch is planned for. */
|
|
6073
|
-
tiles: number;
|
|
6074
|
-
/** Minutes since the start of the shift, per station. */
|
|
6075
|
-
press: readonly [number, number];
|
|
6076
|
-
dryer: readonly [number, number];
|
|
6077
|
-
kiln: readonly [number, number];
|
|
6078
|
-
}
|
|
6079
|
-
|
|
6080
|
-
interface Plant {
|
|
6081
|
-
readings: readonly Reading[];
|
|
6082
|
-
samples: readonly Sample[];
|
|
6083
|
-
batches: readonly Batch[];
|
|
6084
|
-
}
|
|
6085
|
-
|
|
6086
|
-
/* When the exit pyrometer goes silent, and for how long. Fixed rather than
|
|
6087
|
-
seeded, so that a running control room shows its reading turn stale, then
|
|
6088
|
-
lost, and come back. */
|
|
6089
|
-
const SILENT_FROM = 288;
|
|
6090
|
-
|
|
6091
|
-
const SILENT_FOR = 50;
|
|
6092
|
-
|
|
6093
|
-
/** The shift, from a seed. */
|
|
6094
|
-
function plant(seed: number): Plant {
|
|
6095
|
-
const r = random(seed);
|
|
6096
|
-
|
|
6097
|
-
/* The one excursion: a burner overshoots somewhere between 02:30 and 03:30
|
|
6098
|
-
into the shift, for half an hour, peaking near 1242 °C. The operator stops
|
|
6099
|
-
the feed twenty-two minutes in, for eighteen minutes. The noise around it
|
|
6100
|
-
is too small to reach a warning on its own, so this is the only crossing -
|
|
6101
|
-
which is what the tests hold. */
|
|
6102
|
-
const surgeFrom = 150 + Math.floor(r() * 60);
|
|
6103
|
-
const surgeFor = 30;
|
|
6104
|
-
const stopFrom = surgeFrom + 22;
|
|
6105
|
-
const stopFor = 18;
|
|
6106
|
-
|
|
6107
|
-
/* The plan: batches through the kiln one after another, each pressed an
|
|
6108
|
-
hour and dried half an hour before - the first ones during the night
|
|
6109
|
-
shift. Tiles come out of the kiln only while a batch is in it, so the
|
|
6110
|
-
OEE's count and the batches' counts are the same tiles. The plan is a
|
|
6111
|
-
plan: a stop of the belt does not move it. */
|
|
6112
|
-
const batches: Batch[] = [];
|
|
6113
|
-
for (let at = 0, i = 0; at < SHIFT_MINUTES; i++) {
|
|
6114
|
-
const length = 70 + Math.floor(r() * 25);
|
|
6115
|
-
batches.push({
|
|
6116
|
-
id: `b-${4121 + i}`,
|
|
6117
|
-
name: `B-${4121 + i}`,
|
|
6118
|
-
tiles: Math.round(length / IDEAL_CYCLE_MINUTES / 10) * 10,
|
|
6119
|
-
press: [at - 60, at - 60 + length],
|
|
6120
|
-
dryer: [at - 30, at - 30 + length],
|
|
6121
|
-
kiln: [at, at + length],
|
|
6122
|
-
});
|
|
6123
|
-
at += length + 5;
|
|
6124
|
-
}
|
|
6125
|
-
|
|
6126
|
-
const readings: Reading[] = [];
|
|
6127
|
-
const samples: Sample[] = [];
|
|
6128
|
-
let drift = 0;
|
|
6129
|
-
for (let minute = 0; minute < SHIFT_MINUTES; minute++) {
|
|
6130
|
-
drift = 0.9 * drift + (r() - 0.5) * 3;
|
|
6131
|
-
const into = minute - surgeFrom;
|
|
6132
|
-
const surge = into >= 0 && into <= surgeFor ? 42 * Math.sin((Math.PI * into) / surgeFor) : 0;
|
|
6133
|
-
const kiln = Math.round((KILN.target + drift + surge) * 10) / 10;
|
|
6134
|
-
|
|
6135
|
-
const running = !(minute >= stopFrom && minute < stopFrom + stopFor);
|
|
6136
|
-
const loaded = batches.some((batch) => batch.kiln[0] <= minute && minute < batch.kiln[1]);
|
|
6137
|
-
const jam = r() < 0.12;
|
|
6138
|
-
const fired = running && loaded ? (jam ? 3 : 4) : 0;
|
|
6139
|
-
const inTolerance = kiln >= KILN.tolerance[0] && kiln <= KILN.tolerance[1];
|
|
6140
|
-
const silent = minute >= SILENT_FROM && minute < SILENT_FROM + SILENT_FOR;
|
|
6141
|
-
const exit = silent ? null : Math.round((kiln - 380 + (r() - 0.5) * 4) * 10) / 10;
|
|
6142
|
-
|
|
6143
|
-
readings.push({ minute, kiln, exit, running, fired, good: inTolerance ? fired : 0 });
|
|
6144
|
-
|
|
6145
|
-
if (running && minute % 10 === 0) {
|
|
6146
|
-
/* Hotter shrinks more: the length follows the kiln, so the control
|
|
6147
|
-
chart sees the excursion the trend sees. */
|
|
6148
|
-
const length = 600 + (KILN.target - kiln) * 0.05 + (r() - 0.5) * 0.6;
|
|
6149
|
-
samples.push({ minute, length: Math.round(length * 100) / 100 });
|
|
6150
|
-
}
|
|
6151
|
-
}
|
|
6152
|
-
|
|
6153
|
-
return { readings, samples, batches };
|
|
6154
|
-
}
|
|
6155
|
-
|
|
6156
|
-
const SHIFTS = ["Mon early", "Mon late", "Tue early", "Tue late", "Wed early", "Wed late"];
|
|
6157
|
-
|
|
6158
|
-
interface ShiftBox {
|
|
6159
|
-
shift: number;
|
|
6160
|
-
low: number;
|
|
6161
|
-
q1: number;
|
|
6162
|
-
median: number;
|
|
6163
|
-
q3: number;
|
|
6164
|
-
high: number;
|
|
6165
|
-
}
|
|
6166
|
-
|
|
6167
|
-
/** The value at share p of the sorted values, between neighbours. */
|
|
6168
|
-
function quantile(sorted: readonly number[], p: number): number {
|
|
6169
|
-
const at = (sorted.length - 1) * p;
|
|
6170
|
-
const lo = Math.floor(at);
|
|
6171
|
-
return (sorted[lo] as number) + ((sorted[Math.ceil(at)] as number) - (sorted[lo] as number)) * (at - lo);
|
|
6172
|
-
}
|
|
6173
|
-
|
|
6174
|
-
/* One shift per seed; the whiskers end at the last length within 1.5 IQR. */
|
|
6175
|
-
const BOXES: ShiftBox[] = SHIFTS.map((_, shift) => {
|
|
6176
|
-
const sorted = plant(shift + 1).samples.map((s) => s.length).sort((a, b) => a - b);
|
|
6177
|
-
const q1 = quantile(sorted, 0.25);
|
|
6178
|
-
const q3 = quantile(sorted, 0.75);
|
|
6179
|
-
const reach = 1.5 * (q3 - q1);
|
|
6180
|
-
const inside = sorted.filter((v) => v >= q1 - reach && v <= q3 + reach);
|
|
6181
|
-
return { shift, low: inside[0] as number, q1, median: quantile(sorted, 0.5), q3, high: inside[inside.length - 1] as number };
|
|
6182
|
-
});
|
|
5848
|
+
/* Data from the plant world: tile length per shift in millimetres, as the
|
|
5849
|
+
quality database sends it - quartiles and median, the whiskers at the last
|
|
5850
|
+
length within 1.5 IQR. */
|
|
5851
|
+
const BOXES = [
|
|
5852
|
+
{ shift: "Mon early", low: 599.72, q1: 599.855, median: 599.97, q3: 600.1575, high: 600.4 },
|
|
5853
|
+
{ shift: "Mon late", low: 599.53, q1: 599.875, median: 600.025, q3: 600.15, high: 600.38 },
|
|
5854
|
+
{ shift: "Tue early", low: 599.6, q1: 599.88, median: 600.015, q3: 600.1925, high: 600.39 },
|
|
5855
|
+
{ shift: "Tue late", low: 599.59, q1: 599.89, median: 599.975, q3: 600.1475, high: 600.36 },
|
|
5856
|
+
{ shift: "Wed early", low: 599.61, q1: 599.8175, median: 600.01, q3: 600.1775, high: 600.45 },
|
|
5857
|
+
{ shift: "Wed late", low: 599.68, q1: 599.8575, median: 600.035, q3: 600.16, high: 600.53 },
|
|
5858
|
+
];
|
|
6183
5859
|
|
|
6184
5860
|
const mm = (v: number) => `${v.toFixed(2)} mm`;
|
|
6185
5861
|
|
|
@@ -6187,7 +5863,7 @@ export default function Detailed() {
|
|
|
6187
5863
|
const { Chart, XAxis, YAxis, BoxPlot, Line } = useChart(BOXES);
|
|
6188
5864
|
return (
|
|
6189
5865
|
<Chart height={320} ariaLabel="Tile length per shift, with the medians as a line">
|
|
6190
|
-
<XAxis value="shift"
|
|
5866
|
+
<XAxis value="shift" label="Shift" />
|
|
6191
5867
|
<YAxis tickFormat={mm} />
|
|
6192
5868
|
<BoxPlot
|
|
6193
5869
|
name="Tile length"
|
|
@@ -6319,7 +5995,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/stateband/
|
|
|
6319
5995
|
|
|
6320
5996
|
`value` gives a state's place in `states`, which fixes the order, the name and the colour in one place ([ADR-0007](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0007-a-state-is-a-number.md)). Each segment runs to the next reading; a `null` leaves a hole, never a colour for unknown.
|
|
6321
5997
|
|
|
6322
|
-
Several bands share one axis as lanes:
|
|
5998
|
+
Several bands share one axis as lanes: each names its `lane`, and the y axis lists one per name, the first at the top ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). On a numeric y axis `laneFrom` and `laneTo` give a lane its edges instead.
|
|
6323
5999
|
|
|
6324
6000
|
#### Examples
|
|
6325
6001
|
|
|
@@ -6703,7 +6379,7 @@ export default function UnderACourse() {
|
|
|
6703
6379
|
|
|
6704
6380
|
##### Give each one a lane
|
|
6705
6381
|
|
|
6706
|
-
|
|
6382
|
+
Each band names its `lane`, and the y axis lists one per name, the first at the top: the legend explains every state once, however many lanes share them.
|
|
6707
6383
|
|
|
6708
6384
|
```tsx
|
|
6709
6385
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -6869,23 +6545,9 @@ export default function OneLaneEach() {
|
|
|
6869
6545
|
return (
|
|
6870
6546
|
<Chart height={300} ariaLabel="The states of all eight vehicles through the day">
|
|
6871
6547
|
<XAxis value="t" time label="Time" />
|
|
6872
|
-
{
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
ticks={FLEET.map((_, i) => i + 0.45)}
|
|
6876
|
-
tickFormat={(v) => FLEET[FLEET.length - 1 - Math.floor(v)]?.vehicle.plate ?? ""}
|
|
6877
|
-
grid={false}
|
|
6878
|
-
/>
|
|
6879
|
-
{FLEET.map(({ vehicle, day }, i) => (
|
|
6880
|
-
<StateBand
|
|
6881
|
-
key={vehicle.id}
|
|
6882
|
-
data={day}
|
|
6883
|
-
value="state"
|
|
6884
|
-
states={VEHICLE_STATES}
|
|
6885
|
-
laneFrom={FLEET.length - 1 - i}
|
|
6886
|
-
laneTo={FLEET.length - 1 - i + 0.9}
|
|
6887
|
-
name={vehicle.plate}
|
|
6888
|
-
/>
|
|
6548
|
+
<YAxis grid={false} />
|
|
6549
|
+
{FLEET.map(({ vehicle, day }) => (
|
|
6550
|
+
<StateBand key={vehicle.id} data={day} value="state" states={VEHICLE_STATES} lane={vehicle.plate} name={vehicle.plate} />
|
|
6889
6551
|
))}
|
|
6890
6552
|
<Legend placement="top" />
|
|
6891
6553
|
<Tooltip mode="x" />
|
|
@@ -6910,12 +6572,13 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
6910
6572
|
|---|---|---|---|
|
|
6911
6573
|
| `value` *required* | [`Value`](#type-Value)`<T>` | — | Index of the state in `states` - a number field of the row or a function of it; null/undefined/NaN/±Infinity is a gap - and a gap stays a hole, it gets no colour for "unknown". A colour would be a claim about the interval.<br>Shown in: [One vehicle through the day](#/stateband/one-vehicle), [Lay a state under a course](#/stateband/under-a-course), [Give each one a lane](#/stateband/one-lane-each) and 3 more |
|
|
6912
6574
|
| `states` *required* | `readonly `[`StateEntry`](#type-StateEntry)`[]` | — | The closed set of states, in the order of their codes.<br>Shown in: [One vehicle through the day](#/stateband/one-vehicle), [Lay a state under a course](#/stateband/under-a-course), [Give each one a lane](#/stateband/one-lane-each) and 3 more |
|
|
6913
|
-
| `xAxisId` | `string` |
|
|
6914
|
-
| `yAxisId` | `string` |
|
|
6575
|
+
| `xAxisId` | `string` | — | Binding to an x axis. |
|
|
6576
|
+
| `yAxisId` | `string` | — | Binding to a y axis.<br>Shown in: [Lay a state under a course](#/stateband/under-a-course), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [Read limits above the states (LimitLine)](#/limitline/limits-and-state) |
|
|
6915
6577
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Lay a state under a course](#/stateband/under-a-course), [Give each one a lane](#/stateband/one-lane-each), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells) and 2 more |
|
|
6916
6578
|
| `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 vehicle through the day](#/stateband/one-vehicle), [Lay a state under a course](#/stateband/under-a-course), [Give each one a lane](#/stateband/one-lane-each) and 3 more |
|
|
6917
|
-
| `
|
|
6918
|
-
| `
|
|
6579
|
+
| `lane` | `string` | — | The lane by its name: a category of the y axis, whose place the band takes, as wide as a bar is. The y axis then lists a lane per name, the first at the top, in the bands' order or its own `categories` ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). Not together with `laneFrom` or `laneTo`.<br>Shown in: [Give each one a lane](#/stateband/one-lane-each), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [Read limits above the states (LimitLine)](#/limitline/limits-and-state) and 1 more |
|
|
6580
|
+
| `laneFrom` | `number` | the beginning of the axis' domain | Lower edge of the lane in domain units of a numeric y axis.<br>Shown in: [Lay a state under a course](#/stateband/under-a-course) |
|
|
6581
|
+
| `laneTo` | `number` | the end of the axis' domain | Upper edge of the lane in domain units of a numeric y axis.<br>Shown in: [Lay a state under a course](#/stateband/under-a-course) |
|
|
6919
6582
|
|
|
6920
6583
|
##### Types on this page
|
|
6921
6584
|
|
|
@@ -6971,13 +6634,13 @@ import { useChart, DEFAULT_GRADIENT } from "@umriss-ui/charts";
|
|
|
6971
6634
|
|
|
6972
6635
|
Demo page: https://romanhaendler.github.io/umriss-ui/charts/matrix/
|
|
6973
6636
|
|
|
6974
|
-
`value` places a cell's row, as it places every series along y; `level` decides its colour. A cell without a level stays a hole, not a zero. The tooltip carries the level as text, since colour alone carries no number.
|
|
6637
|
+
`value` places a cell's row, as it places every series along y - a string names the row, and the y axis lists one per name, the first at the top ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)); `level` decides its colour. A cell without a level stays a hole, not a zero. The tooltip carries the level as text, since colour alone carries no number.
|
|
6975
6638
|
|
|
6976
6639
|
#### Examples
|
|
6977
6640
|
|
|
6978
6641
|
##### Colour cells by their limits
|
|
6979
6642
|
|
|
6980
|
-
With `coloring` of kind `assessment` a cell takes the tone its limits give it: a bad hour everywhere reads differently from one bad service. `
|
|
6643
|
+
With `coloring` of kind `assessment` a cell takes the tone its limits give it: a bad hour everywhere reads differently from one bad service. `value` names the row - a string is a category, and the y axis lists one per service, the first at the top; `format` writes the level in the tooltip.
|
|
6981
6644
|
|
|
6982
6645
|
```tsx
|
|
6983
6646
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -7017,8 +6680,8 @@ const SERVICES: readonly Service[] = [
|
|
|
7017
6680
|
|
|
7018
6681
|
interface SuccessCell {
|
|
7019
6682
|
hour: number;
|
|
7020
|
-
/** The row: the service's
|
|
7021
|
-
service:
|
|
6683
|
+
/** The row: the service's name. */
|
|
6684
|
+
service: string;
|
|
7022
6685
|
/** Requests that succeeded, in per cent; `null` where there were none. */
|
|
7023
6686
|
success: number | null;
|
|
7024
6687
|
}
|
|
@@ -7029,13 +6692,13 @@ interface SuccessCell {
|
|
|
7029
6692
|
const SUCCESS_BY_HOUR: readonly SuccessCell[] = (() => {
|
|
7030
6693
|
const r = random(23);
|
|
7031
6694
|
const cells: SuccessCell[] = [];
|
|
7032
|
-
SERVICES.forEach((one
|
|
6695
|
+
SERVICES.forEach((one) => {
|
|
7033
6696
|
for (let hour = 0; hour < 24; hour++) {
|
|
7034
6697
|
let success: number | null = 99.2 + r() * 0.8;
|
|
7035
6698
|
if (one.id === "webhooks" && hour >= 9 && hour < 14) success = 96 + r() * 1.5;
|
|
7036
6699
|
if (hour === 3) success = 97.4 + r() * 1;
|
|
7037
6700
|
if (one.id === "reports" && hour < 6) success = null;
|
|
7038
|
-
cells.push({ hour, service, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
6701
|
+
cells.push({ hour, service: one.name, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
7039
6702
|
}
|
|
7040
6703
|
});
|
|
7041
6704
|
return cells;
|
|
@@ -7046,7 +6709,7 @@ export default function ByLimits() {
|
|
|
7046
6709
|
return (
|
|
7047
6710
|
<Chart height={280} ariaLabel="Successful requests per service and hour yesterday, by limits">
|
|
7048
6711
|
<XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
7049
|
-
<YAxis
|
|
6712
|
+
<YAxis />
|
|
7050
6713
|
<Matrix
|
|
7051
6714
|
value="service"
|
|
7052
6715
|
level="success"
|
|
@@ -7110,8 +6773,8 @@ const SERVICES: readonly Service[] = [
|
|
|
7110
6773
|
|
|
7111
6774
|
interface SuccessCell {
|
|
7112
6775
|
hour: number;
|
|
7113
|
-
/** The row: the service's
|
|
7114
|
-
service:
|
|
6776
|
+
/** The row: the service's name. */
|
|
6777
|
+
service: string;
|
|
7115
6778
|
/** Requests that succeeded, in per cent; `null` where there were none. */
|
|
7116
6779
|
success: number | null;
|
|
7117
6780
|
}
|
|
@@ -7122,13 +6785,13 @@ interface SuccessCell {
|
|
|
7122
6785
|
const SUCCESS_BY_HOUR: readonly SuccessCell[] = (() => {
|
|
7123
6786
|
const r = random(23);
|
|
7124
6787
|
const cells: SuccessCell[] = [];
|
|
7125
|
-
SERVICES.forEach((one
|
|
6788
|
+
SERVICES.forEach((one) => {
|
|
7126
6789
|
for (let hour = 0; hour < 24; hour++) {
|
|
7127
6790
|
let success: number | null = 99.2 + r() * 0.8;
|
|
7128
6791
|
if (one.id === "webhooks" && hour >= 9 && hour < 14) success = 96 + r() * 1.5;
|
|
7129
6792
|
if (hour === 3) success = 97.4 + r() * 1;
|
|
7130
6793
|
if (one.id === "reports" && hour < 6) success = null;
|
|
7131
|
-
cells.push({ hour, service, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
6794
|
+
cells.push({ hour, service: one.name, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
7132
6795
|
}
|
|
7133
6796
|
});
|
|
7134
6797
|
return cells;
|
|
@@ -7139,7 +6802,7 @@ export default function Gradient() {
|
|
|
7139
6802
|
return (
|
|
7140
6803
|
<Chart height={280} ariaLabel="Successful requests per service and hour yesterday, as a gradient">
|
|
7141
6804
|
<XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
7142
|
-
<YAxis
|
|
6805
|
+
<YAxis />
|
|
7143
6806
|
<Matrix value="service" level="success" name="Success rate" />
|
|
7144
6807
|
<Tooltip mode="nearest" />
|
|
7145
6808
|
</Chart>
|
|
@@ -7157,7 +6820,7 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
7157
6820
|
|
|
7158
6821
|
| Prop | Type | Default | Description |
|
|
7159
6822
|
|---|---|---|---|
|
|
7160
|
-
| `value` *required* | [`
|
|
6823
|
+
| `value` *required* | [`AxisField`](#type-AxisField)`<T, number \| string \| null \| undefined> \| ((d: T, index: number) => number \| string \| null \| undefined)` | — | The row on the y axis - a field of the data's row or a function of it. A number is a position, as every series' y is; a string is a category: the y axis then has a named row per distinct one, the first at the top, in the order the data bring them or the y axis' `categories` ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). Never an instant: a `Date` is refused.<br>Shown in: [Colour cells by their limits](#/matrix/by-limits), [Colour cells along a gradient](#/matrix/gradient), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells) |
|
|
7161
6824
|
| `level` *required* | [`Value`](#type-Value)`<T>` | — | The level that decides the colour - a number field of the row or a function of it. A missing level is a hole, not a zero.<br>Shown in: [Colour cells by their limits](#/matrix/by-limits), [Colour cells along a gradient](#/matrix/gradient), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells) |
|
|
7162
6825
|
| `coloring` | [`MatrixColoring`](#type-MatrixColoring) | `{ kind: "gradient", stops: DEFAULT_GRADIENT }` | How the level colours a cell: by assessment against limits, or across a gradient.<br>Shown in: [Colour cells by their limits](#/matrix/by-limits), [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells) |
|
|
7163
6826
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis. |
|
|
@@ -7168,6 +6831,20 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
7168
6831
|
|
|
7169
6832
|
##### Types on this page
|
|
7170
6833
|
|
|
6834
|
+
###### `AxisField<T, V = AxisValue>`
|
|
6835
|
+
|
|
6836
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
6837
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
6838
|
+
never an instant.
|
|
6839
|
+
|
|
6840
|
+
```ts
|
|
6841
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
6842
|
+
{
|
|
6843
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
6844
|
+
}[keyof T & string]
|
|
6845
|
+
>;
|
|
6846
|
+
```
|
|
6847
|
+
|
|
7171
6848
|
###### `Value<T>`
|
|
7172
6849
|
|
|
7173
6850
|
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
|
|
@@ -7199,6 +6876,19 @@ type MatrixColoring =
|
|
|
7199
6876
|
};
|
|
7200
6877
|
```
|
|
7201
6878
|
|
|
6879
|
+
###### `AxisValue`
|
|
6880
|
+
|
|
6881
|
+
What an axis reads from a row ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)): a number - a position, or an
|
|
6882
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
6883
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
6884
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
6885
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
6886
|
+
place.
|
|
6887
|
+
|
|
6888
|
+
```ts
|
|
6889
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
6890
|
+
```
|
|
6891
|
+
|
|
7202
6892
|
###### `NumberField<T>`
|
|
7203
6893
|
|
|
7204
6894
|
The fields of a row whose value is a number, or absent.
|
|
@@ -7682,9 +7372,8 @@ function batteryDay(vehicleId: string): BatteryPoint[] {
|
|
|
7682
7372
|
const NORTH = VEHICLES.filter((vehicle) => vehicle.depot === "north");
|
|
7683
7373
|
const BATTERY = batteryDay("v2");
|
|
7684
7374
|
|
|
7685
|
-
/* The lanes: one
|
|
7686
|
-
|
|
7687
|
-
const LANES = [...NORTH].reverse().map((vehicle) => ({ vehicle, day: vehicleDay(vehicle.id) }));
|
|
7375
|
+
/* The lanes: one per vehicle, named by its plate. */
|
|
7376
|
+
const LANES = NORTH.map((vehicle) => ({ vehicle, day: vehicleDay(vehicle.id) }));
|
|
7688
7377
|
|
|
7689
7378
|
export default function LimitsAndState() {
|
|
7690
7379
|
const { Chart, XAxis, YAxis, StateBand, Line } = useChart(BATTERY);
|
|
@@ -7692,27 +7381,13 @@ export default function LimitsAndState() {
|
|
|
7692
7381
|
<Chart height={340} ariaLabel="An e-van's charge against its limits, above the states of the North depot's vehicles">
|
|
7693
7382
|
<XAxis value="t" time label="Time" />
|
|
7694
7383
|
<YAxis domain={[-100, 100]} ticks={[0, 20, 40, 60, 80, 100]} label="Charge %" />
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
position="right"
|
|
7698
|
-
domain={[0, 8]}
|
|
7699
|
-
ticks={LANES.map((_, i) => i + 0.45)}
|
|
7700
|
-
tickFormat={(v) => LANES[Math.floor(v)]?.vehicle.plate ?? ""}
|
|
7701
|
-
/>
|
|
7384
|
+
{/* The lanes take the lower places; the domain leaves the rest as room for the course. */}
|
|
7385
|
+
<YAxis id="lanes" position="right" domain={[-0.5, 7.5]} />
|
|
7702
7386
|
<LimitBand from={10} to={20} severity="warning" label="Reserve" />
|
|
7703
7387
|
<LimitLine value={20} severity="warning" label="Charge soon" />
|
|
7704
7388
|
<LimitLine value={10} severity="alarm" label="Return now" />
|
|
7705
|
-
{LANES.map(({ vehicle, day }
|
|
7706
|
-
<StateBand
|
|
7707
|
-
key={vehicle.id}
|
|
7708
|
-
data={day}
|
|
7709
|
-
value="state"
|
|
7710
|
-
states={VEHICLE_STATES}
|
|
7711
|
-
yAxisId="lanes"
|
|
7712
|
-
laneFrom={i}
|
|
7713
|
-
laneTo={i + 0.9}
|
|
7714
|
-
name={vehicle.plate}
|
|
7715
|
-
/>
|
|
7389
|
+
{LANES.map(({ vehicle, day }) => (
|
|
7390
|
+
<StateBand key={vehicle.id} data={day} value="state" states={VEHICLE_STATES} yAxisId="lanes" lane={vehicle.plate} name={vehicle.plate} />
|
|
7716
7391
|
))}
|
|
7717
7392
|
<Line value="charge" name="Charge" strokeWidth={1.75} />
|
|
7718
7393
|
<Legend placement="top" />
|
|
@@ -7883,7 +7558,7 @@ export default function KilnZone() {
|
|
|
7883
7558
|
|
|
7884
7559
|
##### Mark moments and windows along time
|
|
7885
7560
|
|
|
7886
|
-
With `orientation="x"` a limit lies on the time axis: a line for a moment, a band for a window. Neither carries a severity, so each takes a `color` of its own.
|
|
7561
|
+
With `orientation="x"` a limit lies on the time axis: a line for a moment, a band for a window, each named as a `Date` or in milliseconds. Neither carries a severity, so each takes a `color` of its own.
|
|
7887
7562
|
|
|
7888
7563
|
```tsx
|
|
7889
7564
|
import { LimitBand, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -7977,8 +7652,8 @@ export default function AlongTime() {
|
|
|
7977
7652
|
<Chart height={260} ariaLabel="Checkout's latency across a maintenance window and the start of a release freeze">
|
|
7978
7653
|
<XAxis value="t" time />
|
|
7979
7654
|
<YAxis label="ms" />
|
|
7980
|
-
<LimitBand orientation="x" from={
|
|
7981
|
-
<LimitLine orientation="x" value={
|
|
7655
|
+
<LimitBand orientation="x" from={new Date(2026, 2, 17, 2)} to={new Date(2026, 2, 17, 3)} color="#5b7c99" label="Maintenance" />
|
|
7656
|
+
<LimitLine orientation="x" value={new Date(2026, 2, 17, 8, 30)} color="#7b61c9" label="Release freeze" />
|
|
7982
7657
|
<Line value="p95" name="p95" />
|
|
7983
7658
|
<Tooltip mode="x" />
|
|
7984
7659
|
</Chart>
|
|
@@ -8165,7 +7840,7 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
8165
7840
|
|
|
8166
7841
|
| Prop | Type | Default | Description |
|
|
8167
7842
|
|---|---|---|---|
|
|
8168
|
-
| `value` *required* | `number` | — | Where the line lies, in domain units of its axis
|
|
7843
|
+
| `value` *required* | `number \| Date` | — | Where the line lies, in domain units of its axis; along a time axis an instant, in milliseconds or as a `Date`.<br>Shown in: [Draw a limit](#/limitline/objective), [Lay a band under the series](#/limitline/band), [Read limits above the states](#/limitline/limits-and-state) and 10 more |
|
|
8169
7844
|
| `axisId` | `string` | the first axis of the orientation | Axis on which the value lies.<br>Shown in: [Sort causes and collect the tail (Pareto)](#/pareto/pareto) |
|
|
8170
7845
|
| `orientation` | `"x" \| "y"` | `"y"` | Whether the value lies on the x or on the y axis - a limit is nearly always a value and not a moment.<br>Shown in: [Mark moments and windows along time](#/limitline/along-time) |
|
|
8171
7846
|
| `severity` | [`Severity`](#type-Severity)<br>`"warning" \| "alarm"` | `"alarm"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise.<br>Shown in: [Draw a limit](#/limitline/objective), [Lay a band under the series](#/limitline/band), [Read limits above the states](#/limitline/limits-and-state) and 9 more |
|
|
@@ -8183,8 +7858,8 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
8183
7858
|
|
|
8184
7859
|
| Prop | Type | Default | Description |
|
|
8185
7860
|
|---|---|---|---|
|
|
8186
|
-
| `from` *required* | `number` | — | Lower edge of the band, in domain units of its axis
|
|
8187
|
-
| `to` *required* | `number` | — | Upper edge, in domain units of its axis. A band drawn backwards (`from` above `to`) is drawn between the two all the same - the pair is a range, not a direction.<br>Shown in: [Lay a band under the series](#/limitline/band), [Read limits above the states](#/limitline/limits-and-state), [Watch a kiln zone against its tolerance](#/limitline/kiln-zone) and 4 more |
|
|
7861
|
+
| `from` *required* | `number \| Date` | — | Lower edge of the band, in domain units of its axis; along a time axis an instant, in milliseconds or as a `Date`.<br>Shown in: [Lay a band under the series](#/limitline/band), [Read limits above the states](#/limitline/limits-and-state), [Watch a kiln zone against its tolerance](#/limitline/kiln-zone) and 4 more |
|
|
7862
|
+
| `to` *required* | `number \| Date` | — | Upper edge, in domain units of its axis. A band drawn backwards (`from` above `to`) is drawn between the two all the same - the pair is a range, not a direction.<br>Shown in: [Lay a band under the series](#/limitline/band), [Read limits above the states](#/limitline/limits-and-state), [Watch a kiln zone against its tolerance](#/limitline/kiln-zone) and 4 more |
|
|
8188
7863
|
| `axisId` | `string` | the first axis of the orientation | Axis on which the value lies.<br>Shown in: [Sort causes and collect the tail (Pareto)](#/pareto/pareto) |
|
|
8189
7864
|
| `orientation` | `"x" \| "y"` | `"y"` | Whether the value lies on the x or on the y axis - a limit is nearly always a value and not a moment.<br>Shown in: [Mark moments and windows along time](#/limitline/along-time) |
|
|
8190
7865
|
| `severity` | [`Severity`](#type-Severity)<br>`"warning" \| "alarm"` | `"warning"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise.<br>Shown in: [Draw a limit](#/limitline/objective), [Lay a band under the series](#/limitline/band), [Read limits above the states](#/limitline/limits-and-state) and 9 more |
|
|
@@ -8927,7 +8602,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/pareto/
|
|
|
8927
8602
|
|
|
8928
8603
|
##### Sort causes and collect the tail
|
|
8929
8604
|
|
|
8930
|
-
`pareto()` sorts descending, adds up and folds everything past `collectRank` into one entry that stands last; the bars and the line are yours
|
|
8605
|
+
`pareto()` sorts descending, adds up and folds everything past `collectRank` into one entry that stands last; the bars and the line are yours, and the x axis reads each entry's `name`.
|
|
8931
8606
|
|
|
8932
8607
|
```tsx
|
|
8933
8608
|
import { useMemo } from "react";
|
|
@@ -8952,7 +8627,7 @@ export default function ParetoExample() {
|
|
|
8952
8627
|
const { Chart, XAxis, YAxis, Bar, Line } = useChart(entries);
|
|
8953
8628
|
return (
|
|
8954
8629
|
<Chart height={300} ariaLabel="Failed deliveries by cause, sorted, with the cumulative share">
|
|
8955
|
-
<XAxis value="
|
|
8630
|
+
<XAxis value="name" />
|
|
8956
8631
|
<YAxis label="Failed deliveries" />
|
|
8957
8632
|
<YAxis
|
|
8958
8633
|
id="share"
|
|
@@ -8981,7 +8656,7 @@ From `ChartProps` — the full table stands on [Chart](#/chart/type-ChartProps).
|
|
|
8981
8656
|
|
|
8982
8657
|
| Prop | Type | Default | Description |
|
|
8983
8658
|
|---|---|---|---|
|
|
8984
|
-
| [`height`](#/chart/ChartProps-height) | `number` |
|
|
8659
|
+
| [`height`](#/chart/ChartProps-height) | `number` | fills the frame, at least 300 px | Height in CSS pixels, fixed. Left out, the chart fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 300 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)). |
|
|
8985
8660
|
| [`children`](#/chart/ChartProps-children) | `ReactNode` | — | The axes, series and companions of this chart. They draw nothing themselves: each registers its configuration and the scene draws it, and the order in the JSX is the drawing order. |
|
|
8986
8661
|
|
|
8987
8662
|
###### Accessibility
|
|
@@ -8994,11 +8669,10 @@ From `XAxisProps` — the full table stands on [Axis](#/axis/type-XAxisProps).
|
|
|
8994
8669
|
|
|
8995
8670
|
| Prop | Type | Default | Description |
|
|
8996
8671
|
|---|---|---|---|
|
|
8997
|
-
| [`value`](#/axis/XAxisProps-value) *required* | [`
|
|
8672
|
+
| [`value`](#/axis/XAxisProps-value) *required* | [`AxisField`](#/axis/type-AxisField)`<T> \| ((d: T, index: number) => `[`AxisValue`](#/axis/type-AxisValue)`)` | — | Where a row lies on this axis: a field of the row, compared by its name, or a function of it, compared by its source text as a series' is (`ValueFunction`) - with the same closure limit. A number is a position, a `Date` an instant, a string a category: every distinct one gets a place of its own, in the order the rows bring them, a tick and its name as the label ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). The type decides, never the content - `"2024"` is a category, `2024` a number - and a row without a value has no place. The axis reads the rows of every series bound to it; where those bring their own `data`, name their row: `<XAxis<HourCount> value="hour" />`. |
|
|
8998
8673
|
| [`id`](#/axis/XAxisProps-id) | `string` | `"x"` | Axis id, through which series bind themselves. |
|
|
8999
8674
|
| [`label`](#/axis/XAxisProps-label) | `string` | — | Axis title. |
|
|
9000
8675
|
| [`tickFormat`](#/axis/XAxisProps-tickFormat) | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity. |
|
|
9001
|
-
| [`ticks`](#/axis/XAxisProps-ticks) | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm. For axes whose values are places and not numbers: the lanes of a state stack, the categories of a Pareto. |
|
|
9002
8676
|
|
|
9003
8677
|
From `YAxisProps` — the full table stands on [Axis](#/axis/type-YAxisProps).
|
|
9004
8678
|
|
|
@@ -9009,7 +8683,6 @@ From `YAxisProps` — the full table stands on [Axis](#/axis/type-YAxisProps).
|
|
|
9009
8683
|
| [`id`](#/axis/YAxisProps-id) | `string` | `"y"` | Axis id, through which series bind themselves. |
|
|
9010
8684
|
| [`label`](#/axis/YAxisProps-label) | `string` | — | Axis title. |
|
|
9011
8685
|
| [`tickFormat`](#/axis/YAxisProps-tickFormat) | `(v: number) => string` | — | Label of a tick. Compared by its source text: one that reads a changed closure variable under the same text does not relabel the axis - give it a new text or remount. A bound `Intl.NumberFormat#format` is compared by identity. |
|
|
9012
|
-
| [`ticks`](#/axis/YAxisProps-ticks) | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm. For axes whose values are places and not numbers: the lanes of a state stack, the categories of a Pareto. |
|
|
9013
8686
|
|
|
9014
8687
|
From `LineProps` — the full table stands on [Line](#/line/type-LineProps).
|
|
9015
8688
|
|
|
@@ -9033,7 +8706,7 @@ From `LimitLineProps` — the full table stands on [LimitLine](#/limitline/type-
|
|
|
9033
8706
|
|
|
9034
8707
|
| Prop | Type | Default | Description |
|
|
9035
8708
|
|---|---|---|---|
|
|
9036
|
-
| [`value`](#/limitline/LimitLineProps-value) *required* | `number` | — | Where the line lies, in domain units of its axis
|
|
8709
|
+
| [`value`](#/limitline/LimitLineProps-value) *required* | `number \| Date` | — | Where the line lies, in domain units of its axis; along a time axis an instant, in milliseconds or as a `Date`. |
|
|
9037
8710
|
| [`axisId`](#/limitline/LimitLineProps-axisId) | `string` | the first axis of the orientation | Axis on which the value lies. |
|
|
9038
8711
|
| [`severity`](#/limitline/LimitLineProps-severity) | [`Severity`](#/limitline/type-Severity)<br>`"warning" \| "alarm"` | `"alarm"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise. |
|
|
9039
8712
|
| [`label`](#/limitline/LimitLineProps-label) | `string` | — | What the value means. A line at 90 does not say what 90 is. |
|
|
@@ -9653,7 +9326,7 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
9653
9326
|
|
|
9654
9327
|
| Prop | Type | Default | Description |
|
|
9655
9328
|
|---|---|---|---|
|
|
9656
|
-
| `mode` | `"x" \| "nearest"` | `"x"` | "x": every series at the x position; "nearest": only the nearest point.<br>Shown in: [Report every series or the nearest](#/tooltip/x-or-nearest), [Write the tooltip yourself](#/tooltip/own-content), [Write each value in its unit](#/tooltip/value-format) and
|
|
9329
|
+
| `mode` | `"x" \| "nearest"` | `"x"` | "x": every series at the x position; "nearest": only the nearest point.<br>Shown in: [Report every series or the nearest](#/tooltip/x-or-nearest), [Write the tooltip yourself](#/tooltip/own-content), [Write each value in its unit](#/tooltip/value-format) and 57 more |
|
|
9657
9330
|
| `render` | `(hit: `[`TooltipHit`](#type-TooltipHit)`<T>) => ReactNode` | the built-in tooltip | Render prop for content of one's own. Compared by its source text, like `tickFormat` - with the same closure limit.<br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9658
9331
|
|
|
9659
9332
|
##### Types on this page
|
|
@@ -9923,13 +9596,14 @@ const LEDGER: readonly LedgerRow[] = COST_CENTRES.flatMap((centre, c) => {
|
|
|
9923
9596
|
|
|
9924
9597
|
const MARKETING = LEDGER.filter((row) => row.costCentre === "CC-1200");
|
|
9925
9598
|
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
9926
|
-
|
|
9599
|
+
/** `"2026-03"` as the axis writes it. */
|
|
9600
|
+
const monthName = (month: string) => MONTH_NAMES[Number(month.slice(5)) - 1] ?? month;
|
|
9927
9601
|
|
|
9928
9602
|
function Marketing({ placement }: { placement?: "bottom" }) {
|
|
9929
9603
|
const { Chart, XAxis, YAxis, Line } = useChart(MARKETING);
|
|
9930
9604
|
return (
|
|
9931
9605
|
<Chart height={220} ariaLabel={`Marketing's budget and forecast, legend ${placement ?? "above"}`}>
|
|
9932
|
-
<XAxis value=
|
|
9606
|
+
<XAxis value="month" categoryLabel={monthName} />
|
|
9933
9607
|
<YAxis label="€" />
|
|
9934
9608
|
<Line value="budget" name="Budget" />
|
|
9935
9609
|
<Line value="forecast" name="Forecast" />
|
|
@@ -9973,7 +9647,7 @@ Every entry of a named series is a button whose `aria-pressed` says whether its
|
|
|
9973
9647
|
|
|
9974
9648
|
| Prop | Type | Default | Description |
|
|
9975
9649
|
|---|---|---|---|
|
|
9976
|
-
| `placement` | `"top" \| "bottom"` | `"top"` | Which side of the plot area the legend stands on.<br>Shown in: [Legend above or below](#/legend/above-or-below), [Mix series kinds (Chart)](#/chart/mixed), [Mark every kind (Chart)](#/chart/marks-on-every-kind) and
|
|
9650
|
+
| `placement` | `"top" \| "bottom"` | `"top"` | Which side of the plot area the legend stands on.<br>Shown in: [Legend above or below](#/legend/above-or-below), [Mix series kinds (Chart)](#/chart/mixed), [Mark every kind (Chart)](#/chart/marks-on-every-kind) and 27 more |
|
|
9977
9651
|
|
|
9978
9652
|
### Benchmark
|
|
9979
9653
|
|
|
@@ -10324,7 +9998,7 @@ From `ChartProps` — the full table stands on [Chart](#/chart/type-ChartProps).
|
|
|
10324
9998
|
|
|
10325
9999
|
| Prop | Type | Default | Description |
|
|
10326
10000
|
|---|---|---|---|
|
|
10327
|
-
| [`height`](#/chart/ChartProps-height) | `number` |
|
|
10001
|
+
| [`height`](#/chart/ChartProps-height) | `number` | fills the frame, at least 300 px | Height in CSS pixels, fixed. Left out, the chart fills its frame - a stretched grid cell, a card the grid stretches, a container of definite height - and never goes below 300 px, which is also its height in a frame without one. A different floor goes through `style`'s `minHeight` ([ADR-0050](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0050-a-chart-fills-its-frame-never-below-its-floor.md)). |
|
|
10328
10002
|
| [`children`](#/chart/ChartProps-children) | `ReactNode` | — | The axes, series and companions of this chart. They draw nothing themselves: each registers its configuration and the scene draws it, and the order in the JSX is the drawing order. |
|
|
10329
10003
|
|
|
10330
10004
|
###### Events
|
|
@@ -10343,9 +10017,9 @@ From `XAxisProps` — the full table stands on [Axis](#/axis/type-XAxisProps).
|
|
|
10343
10017
|
|
|
10344
10018
|
| Prop | Type | Default | Description |
|
|
10345
10019
|
|---|---|---|---|
|
|
10346
|
-
| [`value`](#/axis/XAxisProps-value) *required* | [`
|
|
10347
|
-
| [`domain`](#/axis/XAxisProps-domain) | `"nice" \| "data" \| readonly [number, number]` | `"nice"` | `"nice"` widens the data's extent to ticks - to named `ticks` where there are any, on a time axis to whole units of a step of hours or longer -, `"data"` keeps it, a pair is fixed. On a `zoomable` axis it is the start: what the axis shows while the chart's view names no span for it. |
|
|
10348
|
-
| [`time`](#/axis/XAxisProps-time) | `boolean` | — | The values are instants
|
|
10020
|
+
| [`value`](#/axis/XAxisProps-value) *required* | [`AxisField`](#/axis/type-AxisField)`<T> \| ((d: T, index: number) => `[`AxisValue`](#/axis/type-AxisValue)`)` | — | Where a row lies on this axis: a field of the row, compared by its name, or a function of it, compared by its source text as a series' is (`ValueFunction`) - with the same closure limit. A number is a position, a `Date` an instant, a string a category: every distinct one gets a place of its own, in the order the rows bring them, a tick and its name as the label ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). The type decides, never the content - `"2024"` is a category, `2024` a number - and a row without a value has no place. The axis reads the rows of every series bound to it; where those bring their own `data`, name their row: `<XAxis<HourCount> value="hour" />`. |
|
|
10021
|
+
| [`domain`](#/axis/XAxisProps-domain) | `"nice" \| "data" \| readonly [number, number] \| readonly [Date, Date]` | `"nice"` | `"nice"` widens the data's extent to ticks - to named `ticks` where there are any, on a time axis to whole units of a step of hours or longer -, `"data"` keeps it, a pair is fixed: numbers, on a time axis milliseconds or two `Date`s, on a category axis places. On a `zoomable` axis it is the start: what the axis shows while the chart's view names no span for it. |
|
|
10022
|
+
| [`time`](#/axis/XAxisProps-time) | `boolean` | — | The values are instants: milliseconds since the epoch, or with `time` said ISO 8601 strings as an API sends them, read by the platform's rule - a date with a time and no offset is local time, a date alone is UTC; a string that is none is not drawn. Rows that hold a `Date` put the axis in time without `time`. Ticks on local boundaries from the minute to the month, labels by level in en-GB with a 24-hour clock - `15:00`, `17 Mar`, `Mar 2026` - and the date on the first tick and on the first of a new day (`17 Mar 00:00`); a day tick there carries the year. The tooltip's x value carries the seconds where the readings lie less than a minute apart. Another language is a `tickFormat`, which is handed the instant in milliseconds. `calendar` implies it. |
|
|
10349
10023
|
| [`zoomable`](#/axis/XAxisProps-zoomable) | `boolean` | `false` | Zoom and pan: Ctrl or ⌘ with the wheel - and a pinch - zoom around the pointer, a drag, a horizontal wheel or Shift with the wheel pan, a double click goes back to `domain`; with the focus on the chart, + and − zoom, Shift with ← or → pans and 0 goes back. The span shown is part of the chart's view, by this axis' `id` (`useChart`'s `initialView`, `onViewChange`, `setDomain`). The plain wheel always scrolls the page. |
|
|
10350
10024
|
| [`id`](#/axis/XAxisProps-id) | `string` | `"x"` | Axis id, through which series bind themselves. |
|
|
10351
10025
|
| [`label`](#/axis/XAxisProps-label) | `string` | — | Axis title. |
|
|
@@ -10990,19 +10664,35 @@ An `XAxis` or `YAxis` as the chart holds it once registered.
|
|
|
10990
10664
|
| `id` *required* | `string` | — | |
|
|
10991
10665
|
| `orientation` *required* | [`AxisOrientation`](#type-AxisOrientation)<br>`"x" \| "y"` | — | |
|
|
10992
10666
|
| `position` *required* | [`AxisPosition`](#type-AxisPosition)<br>`"bottom" \| "top" \| "left" \| "right"` | — | |
|
|
10993
|
-
| `accessor` | `(d: T, index: number) =>
|
|
10667
|
+
| `accessor` | `(d: T, index: number) => `[`AxisValue`](#type-AxisValue) | — | Where a row lies along the axis; an x axis' only - along y the series place their rows. A number is a position or an instant, a string a category, a `Date` an instant ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). |
|
|
10994
10668
|
| `label` | `string` | — | |
|
|
10995
10669
|
| `tickCount` | `number` | — | |
|
|
10996
10670
|
| `tickFormat` | `(v: number) => string` | — | |
|
|
10997
10671
|
| `domain` *required* | `"nice" \| "data" \| "visible" \| readonly [number, number]` | — | "visible" is a y axis' only: the extent of what its series show inside their x axis' domain. |
|
|
10998
10672
|
| `grid` | `boolean` | only the first registered axis per orientation | Whether the axis draws its grid lines. |
|
|
10999
|
-
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm
|
|
11000
|
-
| `
|
|
10673
|
+
| `ticks` | `readonly number[]` | — | Fixed tick values instead of the 1-2-5 algorithm, on a numeric axis: an hour axis' named hours, a lane grid. |
|
|
10674
|
+
| `categories` | `readonly string[]` | — | The categories of the axis, in their order: the list is the order and the extent, and makes the axis a category axis ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). |
|
|
10675
|
+
| `categoryLabel` | `(category: string) => string` | — | What is written for a category, wherever it is written. |
|
|
10676
|
+
| `time` | `boolean` | — | The values are instants - milliseconds, and where this is said ISO 8601 strings as well: ticks on local boundaries, labels by level. A calendar implies it, and so do rows that bring a `Date` ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). |
|
|
11001
10677
|
| `calendar` | `readonly `[`WorkingInterval`](#type-WorkingInterval)`[]` | — | Working calendar: the intervals in which time counts. With it, materialisation maps wall clock time onto working time before the scale calculates - the scale stays affine ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md)). |
|
|
11002
10678
|
| `alignTicks` | `boolean` | — | y axes only: this axis' ticks on the first y axis' grid. |
|
|
11003
10679
|
| `zoomable` | `boolean` | — | x axes only: wheel, drag, pinch, double click and the zoom keys move the span the chart's view holds for this axis ([ADR-0047](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0047-a-component-holds-its-own-view.md)). |
|
|
11004
10680
|
| `zoomLimits` | [`ZoomLimits`](#type-ZoomLimits) | at most the data's extent, at least three data steps | x axes only: the narrowest and the widest span zoom may reach. |
|
|
11005
10681
|
|
|
10682
|
+
##### `AxisField<T, V = AxisValue>`
|
|
10683
|
+
|
|
10684
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
10685
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
10686
|
+
never an instant.
|
|
10687
|
+
|
|
10688
|
+
```ts
|
|
10689
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
10690
|
+
{
|
|
10691
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
10692
|
+
}[keyof T & string]
|
|
10693
|
+
>;
|
|
10694
|
+
```
|
|
10695
|
+
|
|
11006
10696
|
##### `AxisOrientation`
|
|
11007
10697
|
|
|
11008
10698
|
Whether an axis or a limit runs along x or along y.
|
|
@@ -11028,6 +10718,19 @@ The span each zoomable x axis shows, by axis `id`, in the axis' units.
|
|
|
11028
10718
|
type AxisSpans = Readonly<Record<string, readonly [number, number]>>;
|
|
11029
10719
|
```
|
|
11030
10720
|
|
|
10721
|
+
##### `AxisValue`
|
|
10722
|
+
|
|
10723
|
+
What an axis reads from a row ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)): a number - a position, or an
|
|
10724
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
10725
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
10726
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
10727
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
10728
|
+
place.
|
|
10729
|
+
|
|
10730
|
+
```ts
|
|
10731
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
10732
|
+
```
|
|
10733
|
+
|
|
11031
10734
|
##### `BarProps`
|
|
11032
10735
|
|
|
11033
10736
|
Its table stands on [Bar](#/bar/type-BarProps).
|
|
@@ -11144,14 +10847,14 @@ is typed by that `data` instead.
|
|
|
11144
10847
|
|
|
11145
10848
|
| Prop | Type | Default | Description |
|
|
11146
10849
|
|---|---|---|---|
|
|
11147
|
-
| `Chart` *required* | `(props: `[`ChartProps`](#/chart/type-ChartProps)`) => ReactNode` | — | The chart over the hook's rows. One per `useChart`: it holds the view, and a second chart is a second call.<br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and
|
|
11148
|
-
| `XAxis` *required* | `<T = Z>(props: `[`XAxisProps`](#/axis/type-XAxisProps)`<T>) => null` | — | An x axis reads the rows of every series bound to it: the hook's, or with `<XAxis<Row>>` those the series bring as their own `data`.<br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and
|
|
11149
|
-
| `YAxis` *required* | `(props: `[`YAxisProps`](#/axis/type-YAxisProps)`) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and
|
|
11150
|
-
| `Line` *required* | `<T = Z>(props: `[`LineProps`](#/line/type-LineProps)`<T>) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and
|
|
10850
|
+
| `Chart` *required* | `(props: `[`ChartProps`](#/chart/type-ChartProps)`) => ReactNode` | — | The chart over the hook's rows. One per `useChart`: it holds the view, and a second chart is a second call.<br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and 70 more |
|
|
10851
|
+
| `XAxis` *required* | `<T = Z>(props: `[`XAxisProps`](#/axis/type-XAxisProps)`<T>) => null` | — | An x axis reads the rows of every series bound to it: the hook's, or with `<XAxis<Row>>` those the series bring as their own `data`.<br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and 70 more |
|
|
10852
|
+
| `YAxis` *required* | `(props: `[`YAxisProps`](#/axis/type-YAxisProps)`) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and 70 more |
|
|
10853
|
+
| `Line` *required* | `<T = Z>(props: `[`LineProps`](#/line/type-LineProps)`<T>) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#/installation/first-chart), [Size a chart by its container (Installation)](#/installation/in-its-container), [Mix series kinds (Chart)](#/chart/mixed) and 45 more |
|
|
11151
10854
|
| `Area` *required* | `<T = Z>(props: `[`AreaProps`](#/area/type-AreaProps)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#/chart/mixed), [Mark every kind (Chart)](#/chart/marks-on-every-kind), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and 5 more |
|
|
11152
|
-
| `Bar` *required* | `<T = Z>(props: `[`BarProps`](#/bar/type-BarProps)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#/chart/mixed), [Mark every kind (Chart)](#/chart/marks-on-every-kind), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and
|
|
10855
|
+
| `Bar` *required* | `<T = Z>(props: `[`BarProps`](#/bar/type-BarProps)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#/chart/mixed), [Mark every kind (Chart)](#/chart/marks-on-every-kind), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and 11 more |
|
|
11153
10856
|
| `Scatter` *required* | `<T = Z>(props: `[`ScatterProps`](#/scatter/type-ScatterProps)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#/chart/mixed), [Mark every kind (Chart)](#/chart/marks-on-every-kind), [Bind every kind of series to a second axis (Axis)](#/axis/every-kind-on-a-second-axis) and 2 more |
|
|
11154
|
-
| `StateBand` *required* | `<T = Z>(props: `[`StateBandProps`](#/stateband/type-StateBandProps)`<T>) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [One vehicle through the day (StateBand)](#/stateband/one-vehicle), [Lay a state under a course (StateBand)](#/stateband/under-a-course) and 3 more |
|
|
10857
|
+
| `StateBand` *required* | `<T = Z>(props: `[`StateBandProps`](#/stateband/type-StateBandProps)`<T> & `[`OneLane`](#type-OneLane)`) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [One vehicle through the day (StateBand)](#/stateband/one-vehicle), [Lay a state under a course (StateBand)](#/stateband/under-a-course) and 3 more |
|
|
11155
10858
|
| `Matrix` *required* | `<T = Z>(props: `[`MatrixProps`](#/matrix/type-MatrixProps)`<T>) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#/chart/bands-limits-and-cells), [Colour cells by their limits (Matrix)](#/matrix/by-limits), [Colour cells along a gradient (Matrix)](#/matrix/gradient) |
|
|
11156
10859
|
| `BoxPlot` *required* | `<T = Z>(props: `[`BoxPlotProps`](#/boxplot/type-BoxPlotProps)`<T>) => null` | — | <br>Shown in: [One box per service (BoxPlot)](#/boxplot/minimal), [A box per hour on a time axis (BoxPlot)](#/boxplot/over-time), [With outliers (BoxPlot)](#/boxplot/outliers) and 4 more |
|
|
11157
10860
|
| `view` *required* | [`ChartView`](#/view/type-ChartView) | — | How the reader is looking at the chart. |
|
|
@@ -11471,7 +11174,9 @@ Its table stands on [Matrix](#/matrix/type-MatrixProps).
|
|
|
11471
11174
|
##### `MatrixSeriesConfig<T = unknown>`
|
|
11472
11175
|
|
|
11473
11176
|
A `Matrix` as the chart holds it once registered: its base accessor is the
|
|
11474
|
-
row, `level` the colour.
|
|
11177
|
+
row, `level` the colour. The row may be named: where the accessor yields a
|
|
11178
|
+
string, materialisation gives it its category's place ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)) - the one
|
|
11179
|
+
series kind whose base accessor reads more than a number.
|
|
11475
11180
|
|
|
11476
11181
|
| Prop | Type | Default | Description |
|
|
11477
11182
|
|---|---|---|---|
|
|
@@ -11499,6 +11204,16 @@ type NumberField<T> = NoInfer<
|
|
|
11499
11204
|
>;
|
|
11500
11205
|
```
|
|
11501
11206
|
|
|
11207
|
+
##### `OneLane`
|
|
11208
|
+
|
|
11209
|
+
A lane is named or given by its edges: the compiler refuses both at once.
|
|
11210
|
+
Beside the props and not in them, so that `StateBandProps` stays an
|
|
11211
|
+
interface another can extend.
|
|
11212
|
+
|
|
11213
|
+
```ts
|
|
11214
|
+
type OneLane = { lane: string; laneFrom?: undefined; laneTo?: undefined } | { lane?: undefined };
|
|
11215
|
+
```
|
|
11216
|
+
|
|
11502
11217
|
##### `Padding`
|
|
11503
11218
|
|
|
11504
11219
|
Space in CSS pixels on each side of a rectangle.
|
|
@@ -11516,9 +11231,9 @@ An item, the way it is drawn.
|
|
|
11516
11231
|
|
|
11517
11232
|
| Prop | Type | Default | Description |
|
|
11518
11233
|
|---|---|---|---|
|
|
11519
|
-
| `name` *required* | `string` | — |
|
|
11234
|
+
| `name` *required* | `string` | — | |
|
|
11520
11235
|
| `value` *required* | `number` | — | |
|
|
11521
|
-
| `index` *required* | `number` | — |
|
|
11236
|
+
| `index` *required* | `number` | — | The entry's rank from 0, in the order drawn - its place on an x axis that reads `name`, and an x value for one that reads numbers. |
|
|
11522
11237
|
| `share` *required* | `number` | — | Share of the total, 0…1. |
|
|
11523
11238
|
| `cumulative` *required* | `number` | — | Cumulative share up to and including this entry, 0…1. Exactly 1 at the last entry.<br>Shown in: [Sort causes and collect the tail (Pareto)](#/pareto/pareto) |
|
|
11524
11239
|
| `remainder` *required* | `boolean` | — | The collected tail. Exactly one entry can be this, and it always stands last. |
|
|
@@ -11754,6 +11469,7 @@ an index into `states`.
|
|
|
11754
11469
|
|---|---|---|---|
|
|
11755
11470
|
| `kind` *required* | `"state"` | — | |
|
|
11756
11471
|
| `states` *required* | `readonly `[`StateEntry`](#type-StateEntry)`[]` | — | The closed set of states. The accessor yields the index into it - a number, not a key ([ADR-0007](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0007-a-state-is-a-number.md)). |
|
|
11472
|
+
| `lane` | `string` | — | The lane by its name: a category of the y axis, whose place the band takes ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). |
|
|
11757
11473
|
| `laneFrom` | `number` | — | Lower edge of the lane in domain units of the y axis; without a value the beginning of the axis domain. |
|
|
11758
11474
|
| `laneTo` | `number` | — | Upper edge of the lane; without a value the end of the axis domain. |
|
|
11759
11475
|
| `accessor` *required* | [`ValueFunction`](#type-ValueFunction)`<T>` | — | Y value; null/undefined/NaN/±Infinity means a gap. From `SeriesBase`. |
|
|
@@ -11906,7 +11622,7 @@ A zone line: k times sigma above and below the centre line.
|
|
|
11906
11622
|
| `upper` *required* | `number` | — | |
|
|
11907
11623
|
| `lower` *required* | `number` | — | |
|
|
11908
11624
|
|
|
11909
|
-
Used on [Scenarios](#/), [Axis](#/axis), [
|
|
11625
|
+
Used on [Scenarios](#/), [Axis](#/axis), [LimitLine](#/limitline) and [ControlChart](#/controlchart).
|
|
11910
11626
|
|
|
11911
11627
|
##### `ZoomLimits`
|
|
11912
11628
|
|