@umriss-ui/charts 0.13.0 → 0.15.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 +100 -0
- package/README.md +13 -5
- package/dist/charts.js +2598 -2263
- package/dist/de.d.ts +8 -0
- package/dist/index.d.ts +177 -44
- package/dist/wording/de.js +4 -2
- package/docs/llms-full.md +877 -896
- 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.15.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() {
|
|
@@ -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` | 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
|
|
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 73 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 73 more |
|
|
2220
2191
|
|
|
2221
2192
|
###### Events
|
|
2222
2193
|
|
|
@@ -2228,7 +2199,7 @@ 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 73 more |
|
|
2232
2203
|
|
|
2233
2204
|
###### Styling
|
|
2234
2205
|
|
|
@@ -2261,10 +2232,12 @@ Everything the charts put out as text.
|
|
|
2261
2232
|
| `seriesList` *required* | `(names: readonly string[]) => string` | — | The summary's visible series, by name. |
|
|
2262
2233
|
| `visibleRange` *required* | `(from: string, to: string) => string` | — | The summary's visible stretch of the x axis, both ends formatted. |
|
|
2263
2234
|
| `seriesExtent` *required* | `(name: string, min: string, max: string) => string` | — | The summary's lowest and highest value of one series in that stretch. |
|
|
2235
|
+
| `limitList` *required* | `(limits: readonly { name: string; value: string }[]) => string` | — | The summary's labelled limits, each with its value in its axis' format (a band's two edges as one value). |
|
|
2264
2236
|
| `readout` *required* | `(x: string, rows: readonly { name: string; value: string; x: string }[]) => string` | — | What is read after a key: the position, then each series with its value (and its own x where it stands on another x axis), the emphasised one first. |
|
|
2265
2237
|
| `stackTotal` *required* | `string` | — | The last row of a stack in the tooltip and the readout: the sum of its members at the point (charts-stacking K4). |
|
|
2266
2238
|
| `percent` *required* | `(value: string) => string` | — | A share in a normalised stack, its number already formatted (K5). |
|
|
2267
2239
|
| `walkHelp` *required* | `string` | — | The keys that walk the chart. |
|
|
2240
|
+
| `selectHelp` *required* | `string` | — | The key that reports the Active point - only where `onSelect` is given. |
|
|
2268
2241
|
| `zoomHelp` *required* | `string` | — | The keys that zoom and pan it - only where the chart can be zoomed. |
|
|
2269
2242
|
| `legendHelp` *required* | `string` | — | The keys of the legend's entries - only where it has one to press. |
|
|
2270
2243
|
| `allShown` *required* | `string` | — | Read out where hiding would leave nothing to see, and every series is shown instead. |
|
|
@@ -2305,7 +2278,7 @@ What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendl
|
|
|
2305
2278
|
|
|
2306
2279
|
### Axis
|
|
2307
2280
|
|
|
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).
|
|
2281
|
+
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
2282
|
|
|
2310
2283
|
```ts
|
|
2311
2284
|
import { useChart, workingCalendar } from "@umriss-ui/charts";
|
|
@@ -2313,7 +2286,7 @@ import { useChart, workingCalendar } from "@umriss-ui/charts";
|
|
|
2313
2286
|
|
|
2314
2287
|
Demo page: https://romanhaendler.github.io/umriss-ui/charts/axis/
|
|
2315
2288
|
|
|
2316
|
-
Every scale is linear.
|
|
2289
|
+
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
2290
|
|
|
2318
2291
|
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
2292
|
|
|
@@ -2513,7 +2486,7 @@ export default function WorkingTime() {
|
|
|
2513
2486
|
|
|
2514
2487
|
##### Plot instants on a time axis
|
|
2515
2488
|
|
|
2516
|
-
|
|
2489
|
+
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
2490
|
|
|
2518
2491
|
```tsx
|
|
2519
2492
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -2560,7 +2533,7 @@ const SERVICES: readonly Service[] = [
|
|
|
2560
2533
|
];
|
|
2561
2534
|
|
|
2562
2535
|
interface MetricPoint {
|
|
2563
|
-
t:
|
|
2536
|
+
t: Date;
|
|
2564
2537
|
/** Median and 95th percentile latency, in ms. */
|
|
2565
2538
|
p50: number;
|
|
2566
2539
|
p95: number;
|
|
@@ -2591,7 +2564,7 @@ function metrics(serviceId: string): MetricPoint[] {
|
|
|
2591
2564
|
const surge = serviceId === "checkout" && t >= at(17, 9, 40) && t < at(17, 10, 10) ? 1.9 : 1;
|
|
2592
2565
|
const p95 = Math.round(base * surge * (1 + r() * 0.08));
|
|
2593
2566
|
points.push({
|
|
2594
|
-
t,
|
|
2567
|
+
t: new Date(t),
|
|
2595
2568
|
p50: Math.round(p95 * (0.42 + r() * 0.06)),
|
|
2596
2569
|
p95,
|
|
2597
2570
|
errorRate: Math.round((0.05 + r() * 0.12 + (surge > 1 ? 2.4 + r() : 0)) * 100) / 100,
|
|
@@ -2607,7 +2580,7 @@ export default function TimeAxis() {
|
|
|
2607
2580
|
const { Chart, XAxis, YAxis, Line } = useChart(CHECKOUT);
|
|
2608
2581
|
return (
|
|
2609
2582
|
<Chart height={260} ariaLabel="Checkout's error rate today">
|
|
2610
|
-
<XAxis value="t"
|
|
2583
|
+
<XAxis value="t" />
|
|
2611
2584
|
<YAxis label="Errors %" />
|
|
2612
2585
|
<Line value="errorRate" name="Error rate" />
|
|
2613
2586
|
<Tooltip mode="x" />
|
|
@@ -2933,6 +2906,38 @@ export default function EveryKind() {
|
|
|
2933
2906
|
}
|
|
2934
2907
|
```
|
|
2935
2908
|
|
|
2909
|
+
##### Fix the order and keep an empty place
|
|
2910
|
+
|
|
2911
|
+
`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.
|
|
2912
|
+
|
|
2913
|
+
```tsx
|
|
2914
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
2915
|
+
|
|
2916
|
+
/* Minutes late per working day, as the tour log sends them: only the days
|
|
2917
|
+
that had a delay, the worst first. */
|
|
2918
|
+
const LATE = [
|
|
2919
|
+
{ day: "thu", minutes: 164 },
|
|
2920
|
+
{ day: "mon", minutes: 71 },
|
|
2921
|
+
{ day: "fri", minutes: 58 },
|
|
2922
|
+
{ day: "tue", minutes: 43 },
|
|
2923
|
+
];
|
|
2924
|
+
|
|
2925
|
+
const WEEK = ["mon", "tue", "wed", "thu", "fri"];
|
|
2926
|
+
const NAMES: Record<string, string> = { mon: "Monday", tue: "Tuesday", wed: "Wednesday", thu: "Thursday", fri: "Friday" };
|
|
2927
|
+
|
|
2928
|
+
export default function Categories() {
|
|
2929
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(LATE);
|
|
2930
|
+
return (
|
|
2931
|
+
<Chart height={260} ariaLabel="Minutes late per working day">
|
|
2932
|
+
<XAxis value="day" categories={WEEK} categoryLabel={(day) => NAMES[day] ?? day} />
|
|
2933
|
+
<YAxis label="Minutes late" />
|
|
2934
|
+
<Bar value="minutes" name="Late" />
|
|
2935
|
+
<Tooltip mode="x" />
|
|
2936
|
+
</Chart>
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
```
|
|
2940
|
+
|
|
2936
2941
|
#### Keyboard
|
|
2937
2942
|
|
|
2938
2943
|
The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
@@ -2943,48 +2948,67 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
2943
2948
|
|
|
2944
2949
|
| Prop | Type | Default | Description |
|
|
2945
2950
|
|---|---|---|---|
|
|
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 |
|
|
2951
|
+
| `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 73 more |
|
|
2952
|
+
| `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
2953
|
| `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
|
|
2954
|
+
| `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
2955
|
| `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
2956
|
| `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
2957
|
| `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
2958
|
| `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
|
|
2959
|
+
| `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 65 more |
|
|
2960
|
+
| `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 |
|
|
2961
|
+
| `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 21 more |
|
|
2957
2962
|
| `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
|
|
2963
|
+
| `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 |
|
|
2964
|
+
| `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) |
|
|
2965
|
+
| `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
2966
|
|
|
2960
2967
|
##### `YAxisProps`
|
|
2961
2968
|
|
|
2962
2969
|
| Prop | Type | Default | Description |
|
|
2963
2970
|
|---|---|---|---|
|
|
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
|
|
2971
|
+
| `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
2972
|
| `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
2973
|
| `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
2974
|
| `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
|
|
2975
|
+
| `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 65 more |
|
|
2976
|
+
| `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 |
|
|
2977
|
+
| `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 21 more |
|
|
2971
2978
|
| `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
|
|
2979
|
+
| `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 |
|
|
2980
|
+
| `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) |
|
|
2981
|
+
| `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
2982
|
|
|
2974
2983
|
##### Types on this page
|
|
2975
2984
|
|
|
2976
|
-
###### `
|
|
2985
|
+
###### `AxisField<T, V = AxisValue>`
|
|
2977
2986
|
|
|
2978
|
-
The fields of a row
|
|
2987
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
2988
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
2989
|
+
never an instant.
|
|
2979
2990
|
|
|
2980
2991
|
```ts
|
|
2981
|
-
type
|
|
2992
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
2982
2993
|
{
|
|
2983
|
-
[K in keyof T & string]-?: T[K] extends
|
|
2994
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
2984
2995
|
}[keyof T & string]
|
|
2985
2996
|
>;
|
|
2986
2997
|
```
|
|
2987
2998
|
|
|
2999
|
+
###### `AxisValue`
|
|
3000
|
+
|
|
3001
|
+
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
|
|
3002
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
3003
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
3004
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
3005
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
3006
|
+
place.
|
|
3007
|
+
|
|
3008
|
+
```ts
|
|
3009
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
3010
|
+
```
|
|
3011
|
+
|
|
2988
3012
|
###### `WorkingInterval`
|
|
2989
3013
|
|
|
2990
3014
|
An interval in which time counts; thought of as half-open, but mappable from
|
|
@@ -3007,7 +3031,7 @@ schedule's shape.
|
|
|
3007
3031
|
|
|
3008
3032
|
#### Known limits
|
|
3009
3033
|
|
|
3010
|
-
- No logarithmic and no
|
|
3034
|
+
- 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
3035
|
|
|
3012
3036
|
What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
|
|
3013
3037
|
|
|
@@ -3541,21 +3565,23 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3541
3565
|
|
|
3542
3566
|
| Prop | Type | Default | Description |
|
|
3543
3567
|
|---|---|---|---|
|
|
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 |
|
|
3568
|
+
| `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 73 more |
|
|
3569
|
+
| `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
3570
|
| `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
|
|
3571
|
+
| `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
3572
|
| `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
3573
|
| `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
3574
|
| `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
3575
|
| `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
|
|
3576
|
+
| `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 65 more |
|
|
3577
|
+
| `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 |
|
|
3578
|
+
| `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 21 more |
|
|
3555
3579
|
| `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
|
|
3580
|
+
| `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 |
|
|
3581
|
+
| `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) |
|
|
3582
|
+
| `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
3583
|
|
|
3558
|
-
##### `ChartOptions
|
|
3584
|
+
##### `ChartOptions<Z = unknown>`
|
|
3559
3585
|
|
|
3560
3586
|
| Prop | Type | Default | Description |
|
|
3561
3587
|
|---|---|---|---|
|
|
@@ -3566,21 +3592,37 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3566
3592
|
| Prop | Type | Default | Description |
|
|
3567
3593
|
|---|---|---|---|
|
|
3568
3594
|
| `onViewChange` | `(view: `[`ChartView`](#/view/type-ChartView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another chart as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with.<br>Shown in: [Keep charts in step](#/zoom-and-pan/cursor-sync), [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader), [Keep and restore a view (View)](#/view/keep-and-restore) |
|
|
3595
|
+
| `onSelect` | `(hit: `[`TooltipHit`](#type-TooltipHit)`<Z>) => void` | — | The reader chose a point: a click on a hit (a tap too), or Enter on the Active point - the same hit the tooltip's `render` receives. Once per click; the end of a drag does not report, and the second click of a double click does not either. The chart holds no selection and draws none: what is chosen is the application's state. Needs a `<Tooltip>`, which makes the hits; the hit does not travel over `syncId`.<br>Shown in: [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3569
3596
|
|
|
3570
3597
|
##### Types on this page
|
|
3571
3598
|
|
|
3572
|
-
###### `
|
|
3599
|
+
###### `AxisField<T, V = AxisValue>`
|
|
3573
3600
|
|
|
3574
|
-
The fields of a row
|
|
3601
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
3602
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
3603
|
+
never an instant.
|
|
3575
3604
|
|
|
3576
3605
|
```ts
|
|
3577
|
-
type
|
|
3606
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
3578
3607
|
{
|
|
3579
|
-
[K in keyof T & string]-?: T[K] extends
|
|
3608
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
3580
3609
|
}[keyof T & string]
|
|
3581
3610
|
>;
|
|
3582
3611
|
```
|
|
3583
3612
|
|
|
3613
|
+
###### `AxisValue`
|
|
3614
|
+
|
|
3615
|
+
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
|
|
3616
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
3617
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
3618
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
3619
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
3620
|
+
place.
|
|
3621
|
+
|
|
3622
|
+
```ts
|
|
3623
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
3624
|
+
```
|
|
3625
|
+
|
|
3584
3626
|
###### `WorkingInterval`
|
|
3585
3627
|
|
|
3586
3628
|
An interval in which time counts; thought of as half-open, but mappable from
|
|
@@ -3601,6 +3643,56 @@ schedule's shape.
|
|
|
3601
3643
|
| `min` *required* | `number` | — | <br>Shown in: [Limit the zoom](#/zoom-and-pan/zoom-limits) |
|
|
3602
3644
|
| `max` *required* | `number` | — | <br>Shown in: [Limit the zoom](#/zoom-and-pan/zoom-limits) |
|
|
3603
3645
|
|
|
3646
|
+
###### `TooltipHit<T = unknown>`
|
|
3647
|
+
|
|
3648
|
+
What the pointer hit: the x value under it and one point per series there.
|
|
3649
|
+
A custom tooltip's `render` receives it.
|
|
3650
|
+
|
|
3651
|
+
| Prop | Type | Default | Description |
|
|
3652
|
+
|---|---|---|---|
|
|
3653
|
+
| `xValue` *required* | `number` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3654
|
+
| `points` *required* | `readonly `[`TooltipPoint`](#type-TooltipPoint)`<T>[]` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3655
|
+
| `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
|
|
3656
|
+
| `yPx` *required* | `number` | — | Pixel position of the primary hit. |
|
|
3657
|
+
|
|
3658
|
+
###### `TooltipPoint<T = unknown>`
|
|
3659
|
+
|
|
3660
|
+
One hit per series in the tooltip.
|
|
3661
|
+
|
|
3662
|
+
| Prop | Type | Default | Description |
|
|
3663
|
+
|---|---|---|---|
|
|
3664
|
+
| `seriesName` *required* | `string` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3665
|
+
| `color` *required* | `string` | — | |
|
|
3666
|
+
| `xValue` *required* | `number` | — | The point's x value on its own x axis - with several x axes it can differ from the hit's. |
|
|
3667
|
+
| `yValue` *required* | `number` | — | The point's y value; in a stack its own value, not its top - in a normalised stack its share in percent.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3668
|
+
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself (Tooltip)](#/tooltip/own-content) |
|
|
3669
|
+
| `index` *required* | `number` | — | |
|
|
3670
|
+
| `level` | `number` | — | Only for the matrix: its level, out of the level channel. It stands here and not in yValue, because yValue is the position on the y axis - for a matrix therefore the row. Putting both into one field would be exactly the overloading [ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md) is written against. |
|
|
3671
|
+
| `segment` | `{ from: number; to: number; label: string }` | — | Only for state series: the section being pointed at. A duration does not stand here - it is the difference of two numbers the entry already carries, and formatting it would presuppose knowing what the x axis means. The caller knows that, not this library. |
|
|
3672
|
+
| `box` | [`BoxNumbers`](#type-BoxNumbers)` & Partial<`[`BoxExtras`](#type-BoxExtras)`` > & { /** As the caller gave them; only where the series has `outliers`. */ outliers?: readonly number[]; } `` | — | Only for a box: its further numbers; yValue is its median. Fields of their own for the reason `level` has one ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). |
|
|
3673
|
+
|
|
3674
|
+
###### `BoxNumbers<V = number>`
|
|
3675
|
+
|
|
3676
|
+
A box's further numbers beside its median, by name.
|
|
3677
|
+
|
|
3678
|
+
| Prop | Type | Default | Description |
|
|
3679
|
+
|---|---|---|---|
|
|
3680
|
+
| `lowerQuartile` *required* | `V` | — | |
|
|
3681
|
+
| `upperQuartile` *required* | `V` | — | |
|
|
3682
|
+
| `lowerWhisker` *required* | `V` | — | |
|
|
3683
|
+
| `upperWhisker` *required* | `V` | — | |
|
|
3684
|
+
|
|
3685
|
+
###### `BoxExtras<V = number>`
|
|
3686
|
+
|
|
3687
|
+
The optional numbers of a box (box-plot 04), by name.
|
|
3688
|
+
|
|
3689
|
+
| Prop | Type | Default | Description |
|
|
3690
|
+
|---|---|---|---|
|
|
3691
|
+
| `mean` *required* | `V` | — | |
|
|
3692
|
+
| `notchLower` *required* | `V` | — | |
|
|
3693
|
+
| `notchUpper` *required* | `V` | — | |
|
|
3694
|
+
| `count` *required* | `V` | — | |
|
|
3695
|
+
|
|
3604
3696
|
#### Known limits
|
|
3605
3697
|
|
|
3606
3698
|
- No zoom along y, no rectangle zoom and no navigator: the x span is the one zoom there is.
|
|
@@ -3793,7 +3885,7 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3793
3885
|
|
|
3794
3886
|
#### API
|
|
3795
3887
|
|
|
3796
|
-
##### `ChartOptions
|
|
3888
|
+
##### `ChartOptions<Z = unknown>`
|
|
3797
3889
|
|
|
3798
3890
|
| Prop | Type | Default | Description |
|
|
3799
3891
|
|---|---|---|---|
|
|
@@ -3804,6 +3896,7 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3804
3896
|
| Prop | Type | Default | Description |
|
|
3805
3897
|
|---|---|---|---|
|
|
3806
3898
|
| `onViewChange` | `(view: `[`ChartView`](#type-ChartView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another chart as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with.<br>Shown in: [Keep and restore a view](#/view/keep-and-restore), [Keep charts in step (Zoom and pan)](#/zoom-and-pan/cursor-sync), [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader) |
|
|
3899
|
+
| `onSelect` | `(hit: `[`TooltipHit`](#type-TooltipHit)`<Z>) => void` | — | The reader chose a point: a click on a hit (a tap too), or Enter on the Active point - the same hit the tooltip's `render` receives. Once per click; the end of a drag does not report, and the second click of a double click does not either. The chart holds no selection and draws none: what is chosen is the application's state. Needs a `<Tooltip>`, which makes the hits; the hit does not travel over `syncId`.<br>Shown in: [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3807
3900
|
|
|
3808
3901
|
##### `ChartView`
|
|
3809
3902
|
|
|
@@ -3814,6 +3907,18 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
3814
3907
|
|
|
3815
3908
|
##### Types on this page
|
|
3816
3909
|
|
|
3910
|
+
###### `TooltipHit<T = unknown>`
|
|
3911
|
+
|
|
3912
|
+
What the pointer hit: the x value under it and one point per series there.
|
|
3913
|
+
A custom tooltip's `render` receives it.
|
|
3914
|
+
|
|
3915
|
+
| Prop | Type | Default | Description |
|
|
3916
|
+
|---|---|---|---|
|
|
3917
|
+
| `xValue` *required* | `number` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3918
|
+
| `points` *required* | `readonly `[`TooltipPoint`](#type-TooltipPoint)`<T>[]` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3919
|
+
| `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
|
|
3920
|
+
| `yPx` *required* | `number` | — | Pixel position of the primary hit. |
|
|
3921
|
+
|
|
3817
3922
|
###### `AxisSpans`
|
|
3818
3923
|
|
|
3819
3924
|
The span each zoomable x axis shows, by axis `id`, in the axis' units.
|
|
@@ -3822,6 +3927,44 @@ The span each zoomable x axis shows, by axis `id`, in the axis' units.
|
|
|
3822
3927
|
type AxisSpans = Readonly<Record<string, readonly [number, number]>>;
|
|
3823
3928
|
```
|
|
3824
3929
|
|
|
3930
|
+
###### `TooltipPoint<T = unknown>`
|
|
3931
|
+
|
|
3932
|
+
One hit per series in the tooltip.
|
|
3933
|
+
|
|
3934
|
+
| Prop | Type | Default | Description |
|
|
3935
|
+
|---|---|---|---|
|
|
3936
|
+
| `seriesName` *required* | `string` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3937
|
+
| `color` *required* | `string` | — | |
|
|
3938
|
+
| `xValue` *required* | `number` | — | The point's x value on its own x axis - with several x axes it can differ from the hit's. |
|
|
3939
|
+
| `yValue` *required* | `number` | — | The point's y value; in a stack its own value, not its top - in a normalised stack its share in percent.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
3940
|
+
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself (Tooltip)](#/tooltip/own-content) |
|
|
3941
|
+
| `index` *required* | `number` | — | |
|
|
3942
|
+
| `level` | `number` | — | Only for the matrix: its level, out of the level channel. It stands here and not in yValue, because yValue is the position on the y axis - for a matrix therefore the row. Putting both into one field would be exactly the overloading [ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md) is written against. |
|
|
3943
|
+
| `segment` | `{ from: number; to: number; label: string }` | — | Only for state series: the section being pointed at. A duration does not stand here - it is the difference of two numbers the entry already carries, and formatting it would presuppose knowing what the x axis means. The caller knows that, not this library. |
|
|
3944
|
+
| `box` | [`BoxNumbers`](#type-BoxNumbers)` & Partial<`[`BoxExtras`](#type-BoxExtras)`` > & { /** As the caller gave them; only where the series has `outliers`. */ outliers?: readonly number[]; } `` | — | Only for a box: its further numbers; yValue is its median. Fields of their own for the reason `level` has one ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). |
|
|
3945
|
+
|
|
3946
|
+
###### `BoxNumbers<V = number>`
|
|
3947
|
+
|
|
3948
|
+
A box's further numbers beside its median, by name.
|
|
3949
|
+
|
|
3950
|
+
| Prop | Type | Default | Description |
|
|
3951
|
+
|---|---|---|---|
|
|
3952
|
+
| `lowerQuartile` *required* | `V` | — | |
|
|
3953
|
+
| `upperQuartile` *required* | `V` | — | |
|
|
3954
|
+
| `lowerWhisker` *required* | `V` | — | |
|
|
3955
|
+
| `upperWhisker` *required* | `V` | — | |
|
|
3956
|
+
|
|
3957
|
+
###### `BoxExtras<V = number>`
|
|
3958
|
+
|
|
3959
|
+
The optional numbers of a box (box-plot 04), by name.
|
|
3960
|
+
|
|
3961
|
+
| Prop | Type | Default | Description |
|
|
3962
|
+
|---|---|---|---|
|
|
3963
|
+
| `mean` *required* | `V` | — | |
|
|
3964
|
+
| `notchLower` *required* | `V` | — | |
|
|
3965
|
+
| `notchUpper` *required* | `V` | — | |
|
|
3966
|
+
| `count` *required* | `V` | — | |
|
|
3967
|
+
|
|
3825
3968
|
#### Known limits
|
|
3826
3969
|
|
|
3827
3970
|
- The active point and the open data table are not part of the view: they are where the reader is, not how the chart is looked at.
|
|
@@ -4294,17 +4437,17 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
4294
4437
|
|
|
4295
4438
|
| Prop | Type | Default | Description |
|
|
4296
4439
|
|---|---|---|---|
|
|
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
|
|
4440
|
+
| `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 47 more |
|
|
4298
4441
|
| `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
4442
|
| `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
|
|
4443
|
+
| `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 |
|
|
4444
|
+
| `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 47 more |
|
|
4302
4445
|
| `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
4446
|
| `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
4447
|
| `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
4448
|
| `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
4449
|
| `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), [
|
|
4450
|
+
| `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
4451
|
| `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
4452
|
|
|
4310
4453
|
##### Types on this page
|
|
@@ -4804,104 +4947,50 @@ type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
|
|
|
4804
4947
|
|
|
4805
4948
|
### Bar
|
|
4806
4949
|
|
|
4807
|
-
Bars
|
|
4950
|
+
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
4951
|
|
|
4809
4952
|
```ts
|
|
4810
|
-
import { useChart } from "@umriss-ui/charts";
|
|
4953
|
+
import { useChart, bin } from "@umriss-ui/charts";
|
|
4811
4954
|
```
|
|
4812
4955
|
|
|
4813
4956
|
Demo page: https://romanhaendler.github.io/umriss-ui/charts/bar/
|
|
4814
4957
|
|
|
4815
|
-
|
|
4958
|
+
The x axis reads the category from the row - a cost centre, a weekday - and gives each a place, a tick and its name ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)); a number or an instant places a bar as well. A bar's foot is 0, so one series carries both signs. `bin()` counts values into the rows of a histogram: each bin's `from`, `to`, middle `x` and `count`, a value on an edge in the bin that edge starts, the last bin closed, values that are not numbers skipped; a `domain` is taken as given and what lies outside it is left out.
|
|
4816
4959
|
|
|
4817
4960
|
#### Examples
|
|
4818
4961
|
|
|
4819
4962
|
##### One bar per category
|
|
4820
4963
|
|
|
4821
|
-
|
|
4964
|
+
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
4965
|
|
|
4823
4966
|
```tsx
|
|
4824
4967
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
4825
4968
|
|
|
4826
|
-
/*
|
|
4969
|
+
/* February's spend per cost centre, as the ledger sends it. */
|
|
4970
|
+
const SPEND = [
|
|
4971
|
+
{ costCentre: "Sales", actual: 134_447 },
|
|
4972
|
+
{ costCentre: "Marketing", actual: 76_588 },
|
|
4973
|
+
{ costCentre: "Engineering", actual: 183_157 },
|
|
4974
|
+
{ costCentre: "Design", actual: 51_761 },
|
|
4975
|
+
{ costCentre: "Customer service", actual: 61_160 },
|
|
4976
|
+
{ costCentre: "Finance", actual: 46_353 },
|
|
4977
|
+
{ costCentre: "People", actual: 37_886 },
|
|
4978
|
+
{ costCentre: "IT", actual: 74_313 },
|
|
4979
|
+
{ costCentre: "Facilities", actual: 71_848 },
|
|
4980
|
+
];
|
|
4827
4981
|
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4982
|
+
export default function PerCategory() {
|
|
4983
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(SPEND);
|
|
4984
|
+
return (
|
|
4985
|
+
<Chart height={260} ariaLabel="Actual spend per cost centre in February">
|
|
4986
|
+
<XAxis value="costCentre" />
|
|
4987
|
+
<YAxis label="€" />
|
|
4988
|
+
<Bar value="actual" name="Actual" />
|
|
4989
|
+
<Tooltip mode="x" />
|
|
4990
|
+
</Chart>
|
|
4991
|
+
);
|
|
4835
4992
|
}
|
|
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 },
|
|
4855
|
-
];
|
|
4856
|
-
|
|
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
|
-
export default function PerCategory() {
|
|
4894
|
-
const { Chart, XAxis, YAxis, Bar } = useChart(FEBRUARY);
|
|
4895
|
-
return (
|
|
4896
|
-
<Chart height={260} ariaLabel="Actual spend per cost centre in February">
|
|
4897
|
-
<XAxis value={place} ticks={COST_CENTRES.map((_, i) => i)} tickFormat={(v) => COST_CENTRES[v]?.name ?? ""} />
|
|
4898
|
-
<YAxis label="€" />
|
|
4899
|
-
<Bar value="actual" name="Actual" />
|
|
4900
|
-
<Tooltip mode="x" />
|
|
4901
|
-
</Chart>
|
|
4902
|
-
);
|
|
4903
|
-
}
|
|
4904
|
-
```
|
|
4993
|
+
```
|
|
4905
4994
|
|
|
4906
4995
|
##### Group bars side by side
|
|
4907
4996
|
|
|
@@ -4910,78 +4999,24 @@ Bar series on one x axis stand beside each other; give each the same `barWidth`,
|
|
|
4910
4999
|
```tsx
|
|
4911
5000
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
4912
5001
|
|
|
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 },
|
|
5002
|
+
/* February's budget and spend per cost centre, as the ledger sends them. */
|
|
5003
|
+
const FEBRUARY = [
|
|
5004
|
+
{ costCentre: "Sales", budget: 142_000, actual: 134_447 },
|
|
5005
|
+
{ costCentre: "Marketing", budget: 68_000, actual: 76_588 },
|
|
5006
|
+
{ costCentre: "Engineering", budget: 188_000, actual: 183_157 },
|
|
5007
|
+
{ costCentre: "Design", budget: 54_000, actual: 51_761 },
|
|
5008
|
+
{ costCentre: "Customer service", budget: 61_000, actual: 61_160 },
|
|
5009
|
+
{ costCentre: "Finance", budget: 47_000, actual: 46_353 },
|
|
5010
|
+
{ costCentre: "People", budget: 39_000, actual: 37_886 },
|
|
5011
|
+
{ costCentre: "IT", budget: 83_000, actual: 74_313 },
|
|
5012
|
+
{ costCentre: "Facilities", budget: 72_000, actual: 71_848 },
|
|
4942
5013
|
];
|
|
4943
5014
|
|
|
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
5015
|
export default function Grouped() {
|
|
4981
5016
|
const { Chart, XAxis, YAxis, Bar } = useChart(FEBRUARY);
|
|
4982
5017
|
return (
|
|
4983
5018
|
<Chart height={280} ariaLabel="Budget and actual per cost centre in February">
|
|
4984
|
-
<XAxis value=
|
|
5019
|
+
<XAxis value="costCentre" />
|
|
4985
5020
|
<YAxis label="€" />
|
|
4986
5021
|
{/* A token, so that it follows the colour scheme. */}
|
|
4987
5022
|
<Bar value="budget" name="Budget" color="var(--uc-color-text)" barWidth={0.7} />
|
|
@@ -5000,88 +5035,27 @@ A bar's foot is 0, not the bottom of the axis, so one series carries both signs
|
|
|
5000
5035
|
```tsx
|
|
5001
5036
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
5002
5037
|
|
|
5003
|
-
/*
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
}
|
|
5012
|
-
}
|
|
5013
|
-
|
|
5014
|
-
|
|
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 },
|
|
5038
|
+
/* The year's forecast less its budget per cost centre, in euros - summed
|
|
5039
|
+
where the ledger lives, before the chart. */
|
|
5040
|
+
const DEVIATION = [
|
|
5041
|
+
{ costCentre: "Sales", euros: 60 },
|
|
5042
|
+
{ costCentre: "Marketing", euros: 118_840 },
|
|
5043
|
+
{ costCentre: "Engineering", euros: 1_440 },
|
|
5044
|
+
{ costCentre: "Design", euros: 720 },
|
|
5045
|
+
{ costCentre: "Customer service", euros: 980 },
|
|
5046
|
+
{ costCentre: "Finance", euros: -1_940 },
|
|
5047
|
+
{ costCentre: "People", euros: -1_480 },
|
|
5048
|
+
{ costCentre: "IT", euros: -112_560 },
|
|
5049
|
+
{ costCentre: "Facilities", euros: -2_240 },
|
|
5032
5050
|
];
|
|
5033
5051
|
|
|
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;
|
|
5070
|
-
}
|
|
5071
|
-
|
|
5072
|
-
const DEVIATION: Deviation[] = COST_CENTRES.map((centre, place) => {
|
|
5073
|
-
const rows = LEDGER.filter((row) => row.costCentre === centre.id);
|
|
5074
|
-
const sum = (pick: (row: (typeof rows)[number]) => number) => rows.reduce((total, row) => total + pick(row), 0);
|
|
5075
|
-
return { place, euros: sum((row) => row.forecast) - sum((row) => row.budget) };
|
|
5076
|
-
});
|
|
5077
|
-
|
|
5078
5052
|
const signed = (v: number) => (v > 0 ? `+${(v / 1000).toFixed(0)}k` : `${(v / 1000).toFixed(0)}k`);
|
|
5079
5053
|
|
|
5080
5054
|
export default function Deviation() {
|
|
5081
5055
|
const { Chart, XAxis, YAxis, Bar } = useChart(DEVIATION);
|
|
5082
5056
|
return (
|
|
5083
5057
|
<Chart height={280} ariaLabel="Forecast against budget for the year, per cost centre">
|
|
5084
|
-
<XAxis value="
|
|
5058
|
+
<XAxis value="costCentre" />
|
|
5085
5059
|
<YAxis tickFormat={signed} label="€" />
|
|
5086
5060
|
<Bar value="euros" name="Over budget" />
|
|
5087
5061
|
<Tooltip mode="x" />
|
|
@@ -5097,48 +5071,28 @@ Bars with the same `stack` stand on the ones before them; a missing part stacks
|
|
|
5097
5071
|
```tsx
|
|
5098
5072
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5099
5073
|
|
|
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
|
-
})();
|
|
5074
|
+
/* Minutes late across all tours per working day, by cause - two weeks, 2 to
|
|
5075
|
+
13 March. On the first Wednesday the loading log was not kept: a gap, not
|
|
5076
|
+
a zero. On the second Thursday a closed bridge takes the day. */
|
|
5077
|
+
const DELAYS = [
|
|
5078
|
+
{ day: "Mon 2", traffic: 61, loading: 32, access: 10 },
|
|
5079
|
+
{ day: "Tue 3", traffic: 47, loading: 46, access: 27 },
|
|
5080
|
+
{ day: "Wed 4", traffic: 49, loading: null, access: 22 },
|
|
5081
|
+
{ day: "Thu 5", traffic: 40, loading: 44, access: 7 },
|
|
5082
|
+
{ day: "Fri 6", traffic: 48, loading: 44, access: 27 },
|
|
5083
|
+
{ day: "Mon 9", traffic: 67, loading: 41, access: 8 },
|
|
5084
|
+
{ day: "Tue 10", traffic: 58, loading: 50, access: 13 },
|
|
5085
|
+
{ day: "Wed 11", traffic: 58, loading: 18, access: 14 },
|
|
5086
|
+
{ day: "Thu 12", traffic: 168, loading: 26, access: 10 },
|
|
5087
|
+
{ day: "Fri 13", traffic: 57, loading: 18, access: 18 },
|
|
5088
|
+
];
|
|
5135
5089
|
|
|
5136
5090
|
export default function Stacked() {
|
|
5137
5091
|
const { Chart, XAxis, YAxis, Bar } = useChart(DELAYS);
|
|
5138
5092
|
|
|
5139
5093
|
return (
|
|
5140
5094
|
<Chart height={280} ariaLabel="Minutes late per working day, by cause">
|
|
5141
|
-
<XAxis value="day"
|
|
5095
|
+
<XAxis value="day" />
|
|
5142
5096
|
<YAxis label="Minutes late" />
|
|
5143
5097
|
<Bar value="traffic" name="Traffic" stack="delay" />
|
|
5144
5098
|
<Bar value="loading" name="Loading" stack="delay" />
|
|
@@ -5157,41 +5111,21 @@ export default function Stacked() {
|
|
|
5157
5111
|
```tsx
|
|
5158
5112
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5159
5113
|
|
|
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
|
-
})();
|
|
5114
|
+
/* Minutes late across all tours per working day, by cause - two weeks, 2 to
|
|
5115
|
+
13 March. On the first Wednesday the loading log was not kept: a gap, not
|
|
5116
|
+
a zero. On the second Thursday a closed bridge takes the day. */
|
|
5117
|
+
const DELAYS = [
|
|
5118
|
+
{ day: "Mon 2", traffic: 61, loading: 32, access: 10 },
|
|
5119
|
+
{ day: "Tue 3", traffic: 47, loading: 46, access: 27 },
|
|
5120
|
+
{ day: "Wed 4", traffic: 49, loading: null, access: 22 },
|
|
5121
|
+
{ day: "Thu 5", traffic: 40, loading: 44, access: 7 },
|
|
5122
|
+
{ day: "Fri 6", traffic: 48, loading: 44, access: 27 },
|
|
5123
|
+
{ day: "Mon 9", traffic: 67, loading: 41, access: 8 },
|
|
5124
|
+
{ day: "Tue 10", traffic: 58, loading: 50, access: 13 },
|
|
5125
|
+
{ day: "Wed 11", traffic: 58, loading: 18, access: 14 },
|
|
5126
|
+
{ day: "Thu 12", traffic: 168, loading: 26, access: 10 },
|
|
5127
|
+
{ day: "Fri 13", traffic: 57, loading: 18, access: 18 },
|
|
5128
|
+
];
|
|
5195
5129
|
|
|
5196
5130
|
const minutes = (v: number) => `${v} min`;
|
|
5197
5131
|
|
|
@@ -5199,7 +5133,7 @@ export default function Percent() {
|
|
|
5199
5133
|
const { Chart, XAxis, YAxis, Bar } = useChart(DELAYS);
|
|
5200
5134
|
return (
|
|
5201
5135
|
<Chart height={280} ariaLabel="Share of each cause in the minutes late per working day">
|
|
5202
|
-
<XAxis value="day"
|
|
5136
|
+
<XAxis value="day" />
|
|
5203
5137
|
<YAxis />
|
|
5204
5138
|
<Bar value="traffic" name="Traffic" stack="delay" normalize format={minutes} />
|
|
5205
5139
|
<Bar value="loading" name="Loading" stack="delay" normalize format={minutes} />
|
|
@@ -5211,6 +5145,34 @@ export default function Percent() {
|
|
|
5211
5145
|
}
|
|
5212
5146
|
```
|
|
5213
5147
|
|
|
5148
|
+
##### Count readings into a histogram
|
|
5149
|
+
|
|
5150
|
+
`bin()` counts values into bins of one width on a tidy grid and returns each bin's bounds and count; a `Bar` on the numeric x axis draws them at `x`.
|
|
5151
|
+
|
|
5152
|
+
```tsx
|
|
5153
|
+
import { useMemo } from "react";
|
|
5154
|
+
import { Tooltip, bin, useChart } from "@umriss-ui/charts";
|
|
5155
|
+
|
|
5156
|
+
/* How long 40 support tickets took to answer, in minutes, as the helpdesk exports them. */
|
|
5157
|
+
const MINUTES_TO_ANSWER = [
|
|
5158
|
+
4, 7, 9, 11, 12, 12, 14, 15, 15, 17, 18, 19, 21, 22, 22, 23, 24, 26, 27, 28,
|
|
5159
|
+
29, 31, 33, 34, 36, 38, 41, 43, 47, 52, 55, 58, 63, 71, 78, 84, 96, 112, 135, 171,
|
|
5160
|
+
];
|
|
5161
|
+
|
|
5162
|
+
export default function Histogram() {
|
|
5163
|
+
const bins = useMemo(() => bin(MINUTES_TO_ANSWER, { width: 20 }), []);
|
|
5164
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(bins);
|
|
5165
|
+
return (
|
|
5166
|
+
<Chart height={260} ariaLabel="Minutes to answer 40 support tickets, counted in bins of 20 minutes">
|
|
5167
|
+
<XAxis value="x" label="minutes to answer" />
|
|
5168
|
+
<YAxis label="tickets" />
|
|
5169
|
+
<Bar value="count" name="Tickets" barWidth={0.94} />
|
|
5170
|
+
<Tooltip mode="x" />
|
|
5171
|
+
</Chart>
|
|
5172
|
+
);
|
|
5173
|
+
}
|
|
5174
|
+
```
|
|
5175
|
+
|
|
5214
5176
|
#### When to use something else
|
|
5215
5177
|
|
|
5216
5178
|
- Causes ranked by how much they contribute → Pareto
|
|
@@ -5225,17 +5187,17 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
5225
5187
|
|
|
5226
5188
|
| Prop | Type | Default | Description |
|
|
5227
5189
|
|---|---|---|---|
|
|
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
|
|
5190
|
+
| `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 12 more |
|
|
5229
5191
|
| `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
5192
|
| `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
5193
|
| `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
|
|
5194
|
+
| `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 12 more |
|
|
5233
5195
|
| `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
5196
|
| `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
5197
|
| `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) |
|
|
5236
5198
|
| `stack` | `string` | — | The stack this series stands in: every Bar and Area with the same id on the same x and y axis stands on the ones registered before it. A gap stacks as zero; negative values stack downward from zero. The tooltip names each series' own value and the stack's total.<br>Shown in: [Stack bars](#/bar/stacked), [Show shares of a whole](#/bar/percent), [Compare the budget with the forecast (Scenarios)](#/scenarios/compare-budget-with-forecast) |
|
|
5237
5199
|
| `normalize` | `boolean` | — | On any member of a stack, every x of the stack sums to 100 %: each value becomes its share, and the y axis reads in percent unless it has a `tickFormat`. The total stays the readings' own sum, written in the series' `format`.<br>Shown in: [Show shares of a whole](#/bar/percent) |
|
|
5238
|
-
| `barWidth` | `number` | `0.8` | Width as a fraction of the step; just under 1, so that neighbours do not touch. Several bar series share this fraction.<br>Shown in: [Group bars side by side](#/bar/grouped), [
|
|
5200
|
+
| `barWidth` | `number` | `0.8` | Width as a fraction of the step; just under 1, so that neighbours do not touch. Several bar series share this fraction.<br>Shown in: [Group bars side by side](#/bar/grouped), [Count readings into a histogram](#/bar/histogram), [Sort causes and collect the tail (Pareto)](#/pareto/pareto) and 3 more |
|
|
5239
5201
|
|
|
5240
5202
|
##### Types on this page
|
|
5241
5203
|
|
|
@@ -5595,13 +5557,13 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/boxplot/
|
|
|
5595
5557
|
|
|
5596
5558
|
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
5559
|
|
|
5598
|
-
A box stands on the
|
|
5560
|
+
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
5561
|
|
|
5600
5562
|
#### Examples
|
|
5601
5563
|
|
|
5602
5564
|
##### One box per service
|
|
5603
5565
|
|
|
5604
|
-
Five numbers per row, computed wherever the caller likes.
|
|
5566
|
+
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
5567
|
|
|
5606
5568
|
```tsx
|
|
5607
5569
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -5626,7 +5588,7 @@ export default function Minimal() {
|
|
|
5626
5588
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(RESPONSE_TIMES);
|
|
5627
5589
|
return (
|
|
5628
5590
|
<Chart height={260} ariaLabel="Response time per service over the last hour">
|
|
5629
|
-
<XAxis value=
|
|
5591
|
+
<XAxis value="service" />
|
|
5630
5592
|
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5631
5593
|
<BoxPlot
|
|
5632
5594
|
name="Response time"
|
|
@@ -5818,7 +5780,7 @@ export default function WithOutliers() {
|
|
|
5818
5780
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(RESPONSE_TIMES);
|
|
5819
5781
|
return (
|
|
5820
5782
|
<Chart height={300} ariaLabel="Response time per service over the last hour, with outliers">
|
|
5821
|
-
<XAxis value=
|
|
5783
|
+
<XAxis value="service" />
|
|
5822
5784
|
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5823
5785
|
<BoxPlot
|
|
5824
5786
|
name="Response time"
|
|
@@ -5866,297 +5828,97 @@ export default function MeanAndNotches() {
|
|
|
5866
5828
|
const { Chart, XAxis, YAxis, BoxPlot } = useChart(BATCHES);
|
|
5867
5829
|
return (
|
|
5868
5830
|
<Chart height={300} ariaLabel="Breaking strength per clay supplier, with mean and notches">
|
|
5869
|
-
<XAxis value=
|
|
5831
|
+
<XAxis value="supplier" />
|
|
5870
5832
|
<YAxis tickFormat={(v) => `${v} N`} />
|
|
5871
5833
|
<BoxPlot
|
|
5872
5834
|
name="Breaking strength"
|
|
5873
5835
|
median="median"
|
|
5874
5836
|
lowerQuartile="q1"
|
|
5875
5837
|
upperQuartile="q3"
|
|
5876
|
-
lowerWhisker="low"
|
|
5877
|
-
upperWhisker="high"
|
|
5878
|
-
mean="mean"
|
|
5879
|
-
notchLower={(d) => d.median - notch(d)}
|
|
5880
|
-
notchUpper={(d) => d.median + notch(d)}
|
|
5881
|
-
count="n"
|
|
5882
|
-
format={(v) => `${Math.round(v)} N`}
|
|
5883
|
-
boxWidth={0.5}
|
|
5884
|
-
/>
|
|
5885
|
-
<Legend />
|
|
5886
|
-
<Tooltip />
|
|
5887
|
-
<DataTable />
|
|
5888
|
-
</Chart>
|
|
5889
|
-
);
|
|
5890
|
-
}
|
|
5891
|
-
```
|
|
5892
|
-
|
|
5893
|
-
##### Before and after, side by side
|
|
5894
|
-
|
|
5895
|
-
Box series on one x axis stand beside each other, as bars do. A click in the legend hides one; it leaves the drawing and the y extent.
|
|
5896
|
-
|
|
5897
|
-
```tsx
|
|
5898
|
-
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5899
|
-
|
|
5900
|
-
interface Spread {
|
|
5901
|
-
service: number;
|
|
5902
|
-
low: number;
|
|
5903
|
-
q1: number;
|
|
5904
|
-
median: number;
|
|
5905
|
-
q3: number;
|
|
5906
|
-
high: number;
|
|
5907
|
-
}
|
|
5908
|
-
|
|
5909
|
-
const SERVICES = ["Checkout", "Billing", "Search", "Images"];
|
|
5910
|
-
|
|
5911
|
-
/* Milliseconds per request, the week before and the week after the release. */
|
|
5912
|
-
const BEFORE: Spread[] = [
|
|
5913
|
-
{ service: 0, low: 112, q1: 138, median: 149, q3: 161, high: 195 },
|
|
5914
|
-
{ service: 1, low: 85, q1: 121, median: 140, q3: 173, high: 238 },
|
|
5915
|
-
{ service: 2, low: 140, q1: 152, median: 158, q3: 165, high: 181 },
|
|
5916
|
-
{ service: 3, low: 99, q1: 126, median: 137, q3: 150, high: 186 },
|
|
5917
|
-
];
|
|
5918
|
-
const AFTER: Spread[] = [
|
|
5919
|
-
{ service: 0, low: 118, q1: 135, median: 143, q3: 152, high: 176 },
|
|
5920
|
-
{ service: 1, low: 101, q1: 119, median: 128, q3: 139, high: 164 },
|
|
5921
|
-
{ service: 2, low: 142, q1: 151, median: 157, q3: 164, high: 179 },
|
|
5922
|
-
{ service: 3, low: 103, q1: 124, median: 135, q3: 146, high: 172 },
|
|
5923
|
-
];
|
|
5924
|
-
|
|
5925
|
-
export default function Grouped() {
|
|
5926
|
-
const { Chart, XAxis, YAxis, BoxPlot } = useChart(BEFORE);
|
|
5927
|
-
return (
|
|
5928
|
-
<Chart height={280} ariaLabel="Response time per service before and after the release">
|
|
5929
|
-
<XAxis value="service" ticks={[0, 1, 2, 3]} tickFormat={(v) => SERVICES[v] ?? ""} />
|
|
5930
|
-
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5931
|
-
{[
|
|
5932
|
-
{ name: "Before", data: BEFORE },
|
|
5933
|
-
{ name: "After", data: AFTER },
|
|
5934
|
-
].map((one) => (
|
|
5935
|
-
<BoxPlot
|
|
5936
|
-
key={one.name}
|
|
5937
|
-
name={one.name}
|
|
5938
|
-
data={one.data}
|
|
5939
|
-
median="median"
|
|
5940
|
-
lowerQuartile="q1"
|
|
5941
|
-
upperQuartile="q3"
|
|
5942
|
-
lowerWhisker="low"
|
|
5943
|
-
upperWhisker="high"
|
|
5944
|
-
/>
|
|
5945
|
-
))}
|
|
5946
|
-
<Legend />
|
|
5947
|
-
<Tooltip />
|
|
5948
|
-
</Chart>
|
|
5949
|
-
);
|
|
5950
|
-
}
|
|
5951
|
-
```
|
|
5952
|
-
|
|
5953
|
-
##### Against the objective
|
|
5954
|
-
|
|
5955
|
-
A `LimitLine` shows what was promised. Whether a box breaks it is your judgement, not the library's: this week's whiskers reach past the objective in every region, so its series carries `tone="alarm"`.
|
|
5956
|
-
|
|
5957
|
-
```tsx
|
|
5958
|
-
import { Legend, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5959
|
-
|
|
5960
|
-
interface Spread {
|
|
5961
|
-
region: number;
|
|
5962
|
-
low: number;
|
|
5963
|
-
q1: number;
|
|
5964
|
-
median: number;
|
|
5965
|
-
q3: number;
|
|
5966
|
-
high: number;
|
|
5967
|
-
}
|
|
5968
|
-
|
|
5969
|
-
const REGIONS = ["Europe", "Americas", "Asia"];
|
|
5970
|
-
|
|
5971
|
-
/* Checkout's 95th percentile per five minutes, in ms, per region and week. */
|
|
5972
|
-
const LAST_WEEK: Spread[] = [
|
|
5973
|
-
{ region: 0, low: 182, q1: 204, median: 216, q3: 229, high: 262 },
|
|
5974
|
-
{ region: 1, low: 191, q1: 213, median: 226, q3: 240, high: 276 },
|
|
5975
|
-
{ region: 2, low: 176, q1: 199, median: 211, q3: 224, high: 258 },
|
|
5976
|
-
];
|
|
5977
|
-
const THIS_WEEK: Spread[] = [
|
|
5978
|
-
{ region: 0, low: 196, q1: 231, median: 249, q3: 268, high: 318 },
|
|
5979
|
-
{ region: 1, low: 205, q1: 242, median: 262, q3: 283, high: 339 },
|
|
5980
|
-
{ region: 2, low: 190, q1: 226, median: 244, q3: 263, high: 311 },
|
|
5981
|
-
];
|
|
5982
|
-
|
|
5983
|
-
export default function AgainstTheObjective() {
|
|
5984
|
-
const { Chart, XAxis, YAxis, BoxPlot } = useChart(LAST_WEEK);
|
|
5985
|
-
return (
|
|
5986
|
-
<Chart height={280} ariaLabel="Checkout 95th percentile per region and week against its objective">
|
|
5987
|
-
<XAxis value="region" ticks={[0, 1, 2]} tickFormat={(v) => REGIONS[v] ?? ""} />
|
|
5988
|
-
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5989
|
-
<BoxPlot
|
|
5990
|
-
name="Last week"
|
|
5991
|
-
median="median"
|
|
5992
|
-
lowerQuartile="q1"
|
|
5993
|
-
upperQuartile="q3"
|
|
5994
|
-
lowerWhisker="low"
|
|
5995
|
-
upperWhisker="high"
|
|
5996
|
-
/>
|
|
5997
|
-
<BoxPlot
|
|
5998
|
-
name="This week"
|
|
5999
|
-
data={THIS_WEEK}
|
|
6000
|
-
tone="alarm"
|
|
6001
|
-
median="median"
|
|
6002
|
-
lowerQuartile="q1"
|
|
6003
|
-
upperQuartile="q3"
|
|
6004
|
-
lowerWhisker="low"
|
|
6005
|
-
upperWhisker="high"
|
|
6006
|
-
/>
|
|
6007
|
-
<LimitLine value={300} severity="alarm" label="Objective" />
|
|
6008
|
-
<Legend />
|
|
6009
|
-
<Tooltip />
|
|
6010
|
-
</Chart>
|
|
6011
|
-
);
|
|
6012
|
-
}
|
|
6013
|
-
```
|
|
6014
|
-
|
|
6015
|
-
##### A box per shift, its medians as a line
|
|
6016
|
-
|
|
6017
|
-
The tiles measured in each of six shifts, one box each, and a `Line` through the medians to read the drift. The tooltip reads box and line together; `DataTable` lists every number as text.
|
|
6018
|
-
|
|
6019
|
-
```tsx
|
|
6020
|
-
import { DataTable, Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
6021
|
-
|
|
6022
|
-
/* Data from the plant world, written out here so the example runs on its own. */
|
|
6023
|
-
|
|
6024
|
-
/** A small LCG - reproducible across runs and platforms. */
|
|
6025
|
-
function random(seed: number): () => number {
|
|
6026
|
-
let s = seed >>> 0;
|
|
6027
|
-
return () => {
|
|
6028
|
-
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
6029
|
-
return s / 4294967296;
|
|
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
|
-
}
|
|
5838
|
+
lowerWhisker="low"
|
|
5839
|
+
upperWhisker="high"
|
|
5840
|
+
mean="mean"
|
|
5841
|
+
notchLower={(d) => d.median - notch(d)}
|
|
5842
|
+
notchUpper={(d) => d.median + notch(d)}
|
|
5843
|
+
count="n"
|
|
5844
|
+
format={(v) => `${Math.round(v)} N`}
|
|
5845
|
+
boxWidth={0.5}
|
|
5846
|
+
/>
|
|
5847
|
+
<Legend />
|
|
5848
|
+
<Tooltip />
|
|
5849
|
+
<DataTable />
|
|
5850
|
+
</Chart>
|
|
5851
|
+
);
|
|
5852
|
+
}
|
|
5853
|
+
```
|
|
6125
5854
|
|
|
6126
|
-
|
|
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;
|
|
5855
|
+
##### Before and after, side by side
|
|
6134
5856
|
|
|
6135
|
-
|
|
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;
|
|
5857
|
+
Box series on one x axis stand beside each other, as bars do. A click in the legend hides one; it leaves the drawing and the y extent.
|
|
6142
5858
|
|
|
6143
|
-
|
|
5859
|
+
```tsx
|
|
5860
|
+
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
6144
5861
|
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
5862
|
+
interface Spread {
|
|
5863
|
+
service: string;
|
|
5864
|
+
low: number;
|
|
5865
|
+
q1: number;
|
|
5866
|
+
median: number;
|
|
5867
|
+
q3: number;
|
|
5868
|
+
high: number;
|
|
5869
|
+
}
|
|
6152
5870
|
|
|
6153
|
-
|
|
5871
|
+
/* Milliseconds per request, the week before and the week after the release. */
|
|
5872
|
+
const BEFORE: Spread[] = [
|
|
5873
|
+
{ service: "Checkout", low: 112, q1: 138, median: 149, q3: 161, high: 195 },
|
|
5874
|
+
{ service: "Billing", low: 85, q1: 121, median: 140, q3: 173, high: 238 },
|
|
5875
|
+
{ service: "Search", low: 140, q1: 152, median: 158, q3: 165, high: 181 },
|
|
5876
|
+
{ service: "Images", low: 99, q1: 126, median: 137, q3: 150, high: 186 },
|
|
5877
|
+
];
|
|
5878
|
+
const AFTER: Spread[] = [
|
|
5879
|
+
{ service: "Checkout", low: 118, q1: 135, median: 143, q3: 152, high: 176 },
|
|
5880
|
+
{ service: "Billing", low: 101, q1: 119, median: 128, q3: 139, high: 164 },
|
|
5881
|
+
{ service: "Search", low: 142, q1: 151, median: 157, q3: 164, high: 179 },
|
|
5882
|
+
{ service: "Images", low: 103, q1: 124, median: 135, q3: 146, high: 172 },
|
|
5883
|
+
];
|
|
5884
|
+
|
|
5885
|
+
export default function Grouped() {
|
|
5886
|
+
const { Chart, XAxis, YAxis, BoxPlot } = useChart(BEFORE);
|
|
5887
|
+
return (
|
|
5888
|
+
<Chart height={280} ariaLabel="Response time per service before and after the release">
|
|
5889
|
+
<XAxis value="service" />
|
|
5890
|
+
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5891
|
+
{[
|
|
5892
|
+
{ name: "Before", data: BEFORE },
|
|
5893
|
+
{ name: "After", data: AFTER },
|
|
5894
|
+
].map((one) => (
|
|
5895
|
+
<BoxPlot
|
|
5896
|
+
key={one.name}
|
|
5897
|
+
name={one.name}
|
|
5898
|
+
data={one.data}
|
|
5899
|
+
median="median"
|
|
5900
|
+
lowerQuartile="q1"
|
|
5901
|
+
upperQuartile="q3"
|
|
5902
|
+
lowerWhisker="low"
|
|
5903
|
+
upperWhisker="high"
|
|
5904
|
+
/>
|
|
5905
|
+
))}
|
|
5906
|
+
<Legend />
|
|
5907
|
+
<Tooltip />
|
|
5908
|
+
</Chart>
|
|
5909
|
+
);
|
|
6154
5910
|
}
|
|
5911
|
+
```
|
|
5912
|
+
|
|
5913
|
+
##### Against the objective
|
|
6155
5914
|
|
|
6156
|
-
|
|
5915
|
+
A `LimitLine` shows what was promised. Whether a box breaks it is your judgement, not the library's: this week's whiskers reach past the objective in every region, so its series carries `tone="alarm"`.
|
|
5916
|
+
|
|
5917
|
+
```tsx
|
|
5918
|
+
import { Legend, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
|
|
6157
5919
|
|
|
6158
|
-
interface
|
|
6159
|
-
|
|
5920
|
+
interface Spread {
|
|
5921
|
+
region: string;
|
|
6160
5922
|
low: number;
|
|
6161
5923
|
q1: number;
|
|
6162
5924
|
median: number;
|
|
@@ -6164,22 +5926,68 @@ interface ShiftBox {
|
|
|
6164
5926
|
high: number;
|
|
6165
5927
|
}
|
|
6166
5928
|
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
5929
|
+
/* Checkout's 95th percentile per five minutes, in ms, per region and week. */
|
|
5930
|
+
const LAST_WEEK: Spread[] = [
|
|
5931
|
+
{ region: "Europe", low: 182, q1: 204, median: 216, q3: 229, high: 262 },
|
|
5932
|
+
{ region: "Americas", low: 191, q1: 213, median: 226, q3: 240, high: 276 },
|
|
5933
|
+
{ region: "Asia", low: 176, q1: 199, median: 211, q3: 224, high: 258 },
|
|
5934
|
+
];
|
|
5935
|
+
const THIS_WEEK: Spread[] = [
|
|
5936
|
+
{ region: "Europe", low: 196, q1: 231, median: 249, q3: 268, high: 318 },
|
|
5937
|
+
{ region: "Americas", low: 205, q1: 242, median: 262, q3: 283, high: 339 },
|
|
5938
|
+
{ region: "Asia", low: 190, q1: 226, median: 244, q3: 263, high: 311 },
|
|
5939
|
+
];
|
|
5940
|
+
|
|
5941
|
+
export default function AgainstTheObjective() {
|
|
5942
|
+
const { Chart, XAxis, YAxis, BoxPlot } = useChart(LAST_WEEK);
|
|
5943
|
+
return (
|
|
5944
|
+
<Chart height={280} ariaLabel="Checkout 95th percentile per region and week against its objective">
|
|
5945
|
+
<XAxis value="region" />
|
|
5946
|
+
<YAxis tickFormat={(v) => `${v} ms`} />
|
|
5947
|
+
<BoxPlot
|
|
5948
|
+
name="Last week"
|
|
5949
|
+
median="median"
|
|
5950
|
+
lowerQuartile="q1"
|
|
5951
|
+
upperQuartile="q3"
|
|
5952
|
+
lowerWhisker="low"
|
|
5953
|
+
upperWhisker="high"
|
|
5954
|
+
/>
|
|
5955
|
+
<BoxPlot
|
|
5956
|
+
name="This week"
|
|
5957
|
+
data={THIS_WEEK}
|
|
5958
|
+
tone="alarm"
|
|
5959
|
+
median="median"
|
|
5960
|
+
lowerQuartile="q1"
|
|
5961
|
+
upperQuartile="q3"
|
|
5962
|
+
lowerWhisker="low"
|
|
5963
|
+
upperWhisker="high"
|
|
5964
|
+
/>
|
|
5965
|
+
<LimitLine value={300} severity="alarm" label="Objective" />
|
|
5966
|
+
<Legend />
|
|
5967
|
+
<Tooltip />
|
|
5968
|
+
</Chart>
|
|
5969
|
+
);
|
|
6172
5970
|
}
|
|
5971
|
+
```
|
|
6173
5972
|
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
5973
|
+
##### A box per shift, its medians as a line
|
|
5974
|
+
|
|
5975
|
+
The tiles measured in each of six shifts, one box each, and a `Line` through the medians to read the drift. The tooltip reads box and line together; `DataTable` lists every number as text.
|
|
5976
|
+
|
|
5977
|
+
```tsx
|
|
5978
|
+
import { DataTable, Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5979
|
+
|
|
5980
|
+
/* Data from the plant world: tile length per shift in millimetres, as the
|
|
5981
|
+
quality database sends it - quartiles and median, the whiskers at the last
|
|
5982
|
+
length within 1.5 IQR. */
|
|
5983
|
+
const BOXES = [
|
|
5984
|
+
{ shift: "Mon early", low: 599.72, q1: 599.855, median: 599.97, q3: 600.1575, high: 600.4 },
|
|
5985
|
+
{ shift: "Mon late", low: 599.53, q1: 599.875, median: 600.025, q3: 600.15, high: 600.38 },
|
|
5986
|
+
{ shift: "Tue early", low: 599.6, q1: 599.88, median: 600.015, q3: 600.1925, high: 600.39 },
|
|
5987
|
+
{ shift: "Tue late", low: 599.59, q1: 599.89, median: 599.975, q3: 600.1475, high: 600.36 },
|
|
5988
|
+
{ shift: "Wed early", low: 599.61, q1: 599.8175, median: 600.01, q3: 600.1775, high: 600.45 },
|
|
5989
|
+
{ shift: "Wed late", low: 599.68, q1: 599.8575, median: 600.035, q3: 600.16, high: 600.53 },
|
|
5990
|
+
];
|
|
6183
5991
|
|
|
6184
5992
|
const mm = (v: number) => `${v.toFixed(2)} mm`;
|
|
6185
5993
|
|
|
@@ -6187,7 +5995,7 @@ export default function Detailed() {
|
|
|
6187
5995
|
const { Chart, XAxis, YAxis, BoxPlot, Line } = useChart(BOXES);
|
|
6188
5996
|
return (
|
|
6189
5997
|
<Chart height={320} ariaLabel="Tile length per shift, with the medians as a line">
|
|
6190
|
-
<XAxis value="shift"
|
|
5998
|
+
<XAxis value="shift" label="Shift" />
|
|
6191
5999
|
<YAxis tickFormat={mm} />
|
|
6192
6000
|
<BoxPlot
|
|
6193
6001
|
name="Tile length"
|
|
@@ -6319,7 +6127,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/stateband/
|
|
|
6319
6127
|
|
|
6320
6128
|
`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
6129
|
|
|
6322
|
-
Several bands share one axis as lanes:
|
|
6130
|
+
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
6131
|
|
|
6324
6132
|
#### Examples
|
|
6325
6133
|
|
|
@@ -6703,7 +6511,7 @@ export default function UnderACourse() {
|
|
|
6703
6511
|
|
|
6704
6512
|
##### Give each one a lane
|
|
6705
6513
|
|
|
6706
|
-
|
|
6514
|
+
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
6515
|
|
|
6708
6516
|
```tsx
|
|
6709
6517
|
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -6869,23 +6677,9 @@ export default function OneLaneEach() {
|
|
|
6869
6677
|
return (
|
|
6870
6678
|
<Chart height={300} ariaLabel="The states of all eight vehicles through the day">
|
|
6871
6679
|
<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
|
-
/>
|
|
6680
|
+
<YAxis grid={false} />
|
|
6681
|
+
{FLEET.map(({ vehicle, day }) => (
|
|
6682
|
+
<StateBand key={vehicle.id} data={day} value="state" states={VEHICLE_STATES} lane={vehicle.plate} name={vehicle.plate} />
|
|
6889
6683
|
))}
|
|
6890
6684
|
<Legend placement="top" />
|
|
6891
6685
|
<Tooltip mode="x" />
|
|
@@ -6910,12 +6704,13 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
6910
6704
|
|---|---|---|---|
|
|
6911
6705
|
| `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
6706
|
| `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` |
|
|
6707
|
+
| `xAxisId` | `string` | — | Binding to an x axis. |
|
|
6708
|
+
| `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
6709
|
| `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
6710
|
| `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
|
-
| `
|
|
6711
|
+
| `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 |
|
|
6712
|
+
| `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) |
|
|
6713
|
+
| `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
6714
|
|
|
6920
6715
|
##### Types on this page
|
|
6921
6716
|
|
|
@@ -6971,13 +6766,13 @@ import { useChart, DEFAULT_GRADIENT } from "@umriss-ui/charts";
|
|
|
6971
6766
|
|
|
6972
6767
|
Demo page: https://romanhaendler.github.io/umriss-ui/charts/matrix/
|
|
6973
6768
|
|
|
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.
|
|
6769
|
+
`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
6770
|
|
|
6976
6771
|
#### Examples
|
|
6977
6772
|
|
|
6978
6773
|
##### Colour cells by their limits
|
|
6979
6774
|
|
|
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. `
|
|
6775
|
+
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
6776
|
|
|
6982
6777
|
```tsx
|
|
6983
6778
|
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -7017,8 +6812,8 @@ const SERVICES: readonly Service[] = [
|
|
|
7017
6812
|
|
|
7018
6813
|
interface SuccessCell {
|
|
7019
6814
|
hour: number;
|
|
7020
|
-
/** The row: the service's
|
|
7021
|
-
service:
|
|
6815
|
+
/** The row: the service's name. */
|
|
6816
|
+
service: string;
|
|
7022
6817
|
/** Requests that succeeded, in per cent; `null` where there were none. */
|
|
7023
6818
|
success: number | null;
|
|
7024
6819
|
}
|
|
@@ -7029,13 +6824,13 @@ interface SuccessCell {
|
|
|
7029
6824
|
const SUCCESS_BY_HOUR: readonly SuccessCell[] = (() => {
|
|
7030
6825
|
const r = random(23);
|
|
7031
6826
|
const cells: SuccessCell[] = [];
|
|
7032
|
-
SERVICES.forEach((one
|
|
6827
|
+
SERVICES.forEach((one) => {
|
|
7033
6828
|
for (let hour = 0; hour < 24; hour++) {
|
|
7034
6829
|
let success: number | null = 99.2 + r() * 0.8;
|
|
7035
6830
|
if (one.id === "webhooks" && hour >= 9 && hour < 14) success = 96 + r() * 1.5;
|
|
7036
6831
|
if (hour === 3) success = 97.4 + r() * 1;
|
|
7037
6832
|
if (one.id === "reports" && hour < 6) success = null;
|
|
7038
|
-
cells.push({ hour, service, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
6833
|
+
cells.push({ hour, service: one.name, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
7039
6834
|
}
|
|
7040
6835
|
});
|
|
7041
6836
|
return cells;
|
|
@@ -7046,7 +6841,7 @@ export default function ByLimits() {
|
|
|
7046
6841
|
return (
|
|
7047
6842
|
<Chart height={280} ariaLabel="Successful requests per service and hour yesterday, by limits">
|
|
7048
6843
|
<XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
7049
|
-
<YAxis
|
|
6844
|
+
<YAxis />
|
|
7050
6845
|
<Matrix
|
|
7051
6846
|
value="service"
|
|
7052
6847
|
level="success"
|
|
@@ -7110,8 +6905,8 @@ const SERVICES: readonly Service[] = [
|
|
|
7110
6905
|
|
|
7111
6906
|
interface SuccessCell {
|
|
7112
6907
|
hour: number;
|
|
7113
|
-
/** The row: the service's
|
|
7114
|
-
service:
|
|
6908
|
+
/** The row: the service's name. */
|
|
6909
|
+
service: string;
|
|
7115
6910
|
/** Requests that succeeded, in per cent; `null` where there were none. */
|
|
7116
6911
|
success: number | null;
|
|
7117
6912
|
}
|
|
@@ -7122,13 +6917,13 @@ interface SuccessCell {
|
|
|
7122
6917
|
const SUCCESS_BY_HOUR: readonly SuccessCell[] = (() => {
|
|
7123
6918
|
const r = random(23);
|
|
7124
6919
|
const cells: SuccessCell[] = [];
|
|
7125
|
-
SERVICES.forEach((one
|
|
6920
|
+
SERVICES.forEach((one) => {
|
|
7126
6921
|
for (let hour = 0; hour < 24; hour++) {
|
|
7127
6922
|
let success: number | null = 99.2 + r() * 0.8;
|
|
7128
6923
|
if (one.id === "webhooks" && hour >= 9 && hour < 14) success = 96 + r() * 1.5;
|
|
7129
6924
|
if (hour === 3) success = 97.4 + r() * 1;
|
|
7130
6925
|
if (one.id === "reports" && hour < 6) success = null;
|
|
7131
|
-
cells.push({ hour, service, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
6926
|
+
cells.push({ hour, service: one.name, success: success === null ? null : Math.round(success * 100) / 100 });
|
|
7132
6927
|
}
|
|
7133
6928
|
});
|
|
7134
6929
|
return cells;
|
|
@@ -7139,7 +6934,7 @@ export default function Gradient() {
|
|
|
7139
6934
|
return (
|
|
7140
6935
|
<Chart height={280} ariaLabel="Successful requests per service and hour yesterday, as a gradient">
|
|
7141
6936
|
<XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
7142
|
-
<YAxis
|
|
6937
|
+
<YAxis />
|
|
7143
6938
|
<Matrix value="service" level="success" name="Success rate" />
|
|
7144
6939
|
<Tooltip mode="nearest" />
|
|
7145
6940
|
</Chart>
|
|
@@ -7157,7 +6952,7 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
7157
6952
|
|
|
7158
6953
|
| Prop | Type | Default | Description |
|
|
7159
6954
|
|---|---|---|---|
|
|
7160
|
-
| `value` *required* | [`
|
|
6955
|
+
| `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
6956
|
| `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
6957
|
| `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
6958
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis. |
|
|
@@ -7168,6 +6963,20 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
7168
6963
|
|
|
7169
6964
|
##### Types on this page
|
|
7170
6965
|
|
|
6966
|
+
###### `AxisField<T, V = AxisValue>`
|
|
6967
|
+
|
|
6968
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
6969
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
6970
|
+
never an instant.
|
|
6971
|
+
|
|
6972
|
+
```ts
|
|
6973
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
6974
|
+
{
|
|
6975
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
6976
|
+
}[keyof T & string]
|
|
6977
|
+
>;
|
|
6978
|
+
```
|
|
6979
|
+
|
|
7171
6980
|
###### `Value<T>`
|
|
7172
6981
|
|
|
7173
6982
|
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 +7008,19 @@ type MatrixColoring =
|
|
|
7199
7008
|
};
|
|
7200
7009
|
```
|
|
7201
7010
|
|
|
7011
|
+
###### `AxisValue`
|
|
7012
|
+
|
|
7013
|
+
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
|
|
7014
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
7015
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
7016
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
7017
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
7018
|
+
place.
|
|
7019
|
+
|
|
7020
|
+
```ts
|
|
7021
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
7022
|
+
```
|
|
7023
|
+
|
|
7202
7024
|
###### `NumberField<T>`
|
|
7203
7025
|
|
|
7204
7026
|
The fields of a row whose value is a number, or absent.
|
|
@@ -7682,9 +7504,8 @@ function batteryDay(vehicleId: string): BatteryPoint[] {
|
|
|
7682
7504
|
const NORTH = VEHICLES.filter((vehicle) => vehicle.depot === "north");
|
|
7683
7505
|
const BATTERY = batteryDay("v2");
|
|
7684
7506
|
|
|
7685
|
-
/* The lanes: one
|
|
7686
|
-
|
|
7687
|
-
const LANES = [...NORTH].reverse().map((vehicle) => ({ vehicle, day: vehicleDay(vehicle.id) }));
|
|
7507
|
+
/* The lanes: one per vehicle, named by its plate. */
|
|
7508
|
+
const LANES = NORTH.map((vehicle) => ({ vehicle, day: vehicleDay(vehicle.id) }));
|
|
7688
7509
|
|
|
7689
7510
|
export default function LimitsAndState() {
|
|
7690
7511
|
const { Chart, XAxis, YAxis, StateBand, Line } = useChart(BATTERY);
|
|
@@ -7692,27 +7513,13 @@ export default function LimitsAndState() {
|
|
|
7692
7513
|
<Chart height={340} ariaLabel="An e-van's charge against its limits, above the states of the North depot's vehicles">
|
|
7693
7514
|
<XAxis value="t" time label="Time" />
|
|
7694
7515
|
<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
|
-
/>
|
|
7516
|
+
{/* The lanes take the lower places; the domain leaves the rest as room for the course. */}
|
|
7517
|
+
<YAxis id="lanes" position="right" domain={[-0.5, 7.5]} />
|
|
7702
7518
|
<LimitBand from={10} to={20} severity="warning" label="Reserve" />
|
|
7703
7519
|
<LimitLine value={20} severity="warning" label="Charge soon" />
|
|
7704
7520
|
<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
|
-
/>
|
|
7521
|
+
{LANES.map(({ vehicle, day }) => (
|
|
7522
|
+
<StateBand key={vehicle.id} data={day} value="state" states={VEHICLE_STATES} yAxisId="lanes" lane={vehicle.plate} name={vehicle.plate} />
|
|
7716
7523
|
))}
|
|
7717
7524
|
<Line value="charge" name="Charge" strokeWidth={1.75} />
|
|
7718
7525
|
<Legend placement="top" />
|
|
@@ -7883,7 +7690,7 @@ export default function KilnZone() {
|
|
|
7883
7690
|
|
|
7884
7691
|
##### Mark moments and windows along time
|
|
7885
7692
|
|
|
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.
|
|
7693
|
+
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
7694
|
|
|
7888
7695
|
```tsx
|
|
7889
7696
|
import { LimitBand, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
|
|
@@ -7977,8 +7784,8 @@ export default function AlongTime() {
|
|
|
7977
7784
|
<Chart height={260} ariaLabel="Checkout's latency across a maintenance window and the start of a release freeze">
|
|
7978
7785
|
<XAxis value="t" time />
|
|
7979
7786
|
<YAxis label="ms" />
|
|
7980
|
-
<LimitBand orientation="x" from={
|
|
7981
|
-
<LimitLine orientation="x" value={
|
|
7787
|
+
<LimitBand orientation="x" from={new Date(2026, 2, 17, 2)} to={new Date(2026, 2, 17, 3)} color="#5b7c99" label="Maintenance" />
|
|
7788
|
+
<LimitLine orientation="x" value={new Date(2026, 2, 17, 8, 30)} color="#7b61c9" label="Release freeze" />
|
|
7982
7789
|
<Line value="p95" name="p95" />
|
|
7983
7790
|
<Tooltip mode="x" />
|
|
7984
7791
|
</Chart>
|
|
@@ -8165,11 +7972,11 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
8165
7972
|
|
|
8166
7973
|
| Prop | Type | Default | Description |
|
|
8167
7974
|
|---|---|---|---|
|
|
8168
|
-
| `value` *required* | `number` | — | Where the line lies, in domain units of its axis
|
|
7975
|
+
| `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 11 more |
|
|
8169
7976
|
| `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
7977
|
| `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
|
-
| `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
|
|
8172
|
-
| `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<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
|
|
7978
|
+
| `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 10 more |
|
|
7979
|
+
| `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<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 11 more |
|
|
8173
7980
|
| `color` | `string` | — | Any CSS colour value, in place of the one the severity yields. A colour handed in instead of a severity puts the rule in the application and the presentation in the library, and the two drift apart as soon as a bound moves - so this is for the case that has no severity, not for taste. The line or band takes it; its label keeps the axis's text colour, which stays legible in both themes, and a short stroke before the text in the line's pattern takes this colour.<br>Shown in: [Mark moments and windows along time](#/limitline/along-time) |
|
|
8174
7981
|
| `inExtent` | `boolean` | `true` | Whether the value widens the axis extent, so that the limit is certainly visible.<br>Shown in: [Sort causes and collect the tail (Pareto)](#/pareto/pareto), [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) |
|
|
8175
7982
|
|
|
@@ -8183,12 +7990,12 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
8183
7990
|
|
|
8184
7991
|
| Prop | Type | Default | Description |
|
|
8185
7992
|
|---|---|---|---|
|
|
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
|
|
7993
|
+
| `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 5 more |
|
|
7994
|
+
| `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 5 more |
|
|
8188
7995
|
| `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
7996
|
| `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
|
-
| `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
|
|
8191
|
-
| `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<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
|
|
7997
|
+
| `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 10 more |
|
|
7998
|
+
| `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<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 11 more |
|
|
8192
7999
|
| `color` | `string` | — | Any CSS colour value, in place of the one the severity yields. A colour handed in instead of a severity puts the rule in the application and the presentation in the library, and the two drift apart as soon as a bound moves - so this is for the case that has no severity, not for taste. The line or band takes it; its label keeps the axis's text colour, which stays legible in both themes, and a short stroke before the text in the line's pattern takes this colour.<br>Shown in: [Mark moments and windows along time](#/limitline/along-time) |
|
|
8193
8000
|
| `inExtent` | `boolean` | `true` | Whether the value widens the axis extent, so that the limit is certainly visible.<br>Shown in: [Sort causes and collect the tail (Pareto)](#/pareto/pareto), [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) |
|
|
8194
8001
|
|
|
@@ -8927,7 +8734,7 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/pareto/
|
|
|
8927
8734
|
|
|
8928
8735
|
##### Sort causes and collect the tail
|
|
8929
8736
|
|
|
8930
|
-
`pareto()` sorts descending, adds up and folds everything past `collectRank` into one entry that stands last; the bars and the line are yours
|
|
8737
|
+
`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
8738
|
|
|
8932
8739
|
```tsx
|
|
8933
8740
|
import { useMemo } from "react";
|
|
@@ -8952,7 +8759,7 @@ export default function ParetoExample() {
|
|
|
8952
8759
|
const { Chart, XAxis, YAxis, Bar, Line } = useChart(entries);
|
|
8953
8760
|
return (
|
|
8954
8761
|
<Chart height={300} ariaLabel="Failed deliveries by cause, sorted, with the cumulative share">
|
|
8955
|
-
<XAxis value="
|
|
8762
|
+
<XAxis value="name" />
|
|
8956
8763
|
<YAxis label="Failed deliveries" />
|
|
8957
8764
|
<YAxis
|
|
8958
8765
|
id="share"
|
|
@@ -8994,11 +8801,10 @@ From `XAxisProps` — the full table stands on [Axis](#/axis/type-XAxisProps).
|
|
|
8994
8801
|
|
|
8995
8802
|
| Prop | Type | Default | Description |
|
|
8996
8803
|
|---|---|---|---|
|
|
8997
|
-
| [`value`](#/axis/XAxisProps-value) *required* | [`
|
|
8804
|
+
| [`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
8805
|
| [`id`](#/axis/XAxisProps-id) | `string` | `"x"` | Axis id, through which series bind themselves. |
|
|
8999
8806
|
| [`label`](#/axis/XAxisProps-label) | `string` | — | Axis title. |
|
|
9000
8807
|
| [`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
8808
|
|
|
9003
8809
|
From `YAxisProps` — the full table stands on [Axis](#/axis/type-YAxisProps).
|
|
9004
8810
|
|
|
@@ -9009,7 +8815,6 @@ From `YAxisProps` — the full table stands on [Axis](#/axis/type-YAxisProps).
|
|
|
9009
8815
|
| [`id`](#/axis/YAxisProps-id) | `string` | `"y"` | Axis id, through which series bind themselves. |
|
|
9010
8816
|
| [`label`](#/axis/YAxisProps-label) | `string` | — | Axis title. |
|
|
9011
8817
|
| [`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
8818
|
|
|
9014
8819
|
From `LineProps` — the full table stands on [Line](#/line/type-LineProps).
|
|
9015
8820
|
|
|
@@ -9033,7 +8838,7 @@ From `LimitLineProps` — the full table stands on [LimitLine](#/limitline/type-
|
|
|
9033
8838
|
|
|
9034
8839
|
| Prop | Type | Default | Description |
|
|
9035
8840
|
|---|---|---|---|
|
|
9036
|
-
| [`value`](#/limitline/LimitLineProps-value) *required* | `number` | — | Where the line lies, in domain units of its axis
|
|
8841
|
+
| [`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
8842
|
| [`axisId`](#/limitline/LimitLineProps-axisId) | `string` | the first axis of the orientation | Axis on which the value lies. |
|
|
9038
8843
|
| [`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
8844
|
| [`label`](#/limitline/LimitLineProps-label) | `string` | — | What the value means. A line at 90 does not say what 90 is. |
|
|
@@ -9076,6 +8881,8 @@ Demo page: https://romanhaendler.github.io/umriss-ui/charts/tooltip/
|
|
|
9076
8881
|
|
|
9077
8882
|
The crosshair snaps to a reading, never between two. The data table's key stands at the end of the legend.
|
|
9078
8883
|
|
|
8884
|
+
`useChart(rows, { onSelect })` hears a click or tap on a hit and Enter on the Active point, with the hit a custom `render` receives - once, never at the end of a drag, and for a double click only on its first click. The chart keeps and draws no selection; it needs a `Tooltip` for its hits and does not send the hit over `syncId`.
|
|
8885
|
+
|
|
9079
8886
|
#### Examples
|
|
9080
8887
|
|
|
9081
8888
|
##### Report every series or the nearest
|
|
@@ -9643,6 +9450,82 @@ export default function WeekAsTable() {
|
|
|
9643
9450
|
}
|
|
9644
9451
|
```
|
|
9645
9452
|
|
|
9453
|
+
##### Read the limits at the hit
|
|
9454
|
+
|
|
9455
|
+
`limits` adds a row for every labelled limit on the hit's y axis, its label and value in the axis' format; a key reads the same out, and the chart's summary names them without it.
|
|
9456
|
+
|
|
9457
|
+
```tsx
|
|
9458
|
+
import { LimitBand, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
|
|
9459
|
+
|
|
9460
|
+
/* Checkout's 95th percentile latency every ten minutes of a morning, in ms, as the monitor exports it. */
|
|
9461
|
+
const LATENCY = [
|
|
9462
|
+
{ time: "09:00", p95: 212 }, { time: "09:10", p95: 224 }, { time: "09:20", p95: 231 },
|
|
9463
|
+
{ time: "09:30", p95: 246 }, { time: "09:40", p95: 318 }, { time: "09:50", p95: 402 },
|
|
9464
|
+
{ time: "10:00", p95: 387 }, { time: "10:10", p95: 291 }, { time: "10:20", p95: 238 },
|
|
9465
|
+
{ time: "10:30", p95: 229 },
|
|
9466
|
+
].map((row, x) => ({ ...row, x }));
|
|
9467
|
+
|
|
9468
|
+
export default function Limits() {
|
|
9469
|
+
const { Chart, XAxis, YAxis, Line } = useChart(LATENCY);
|
|
9470
|
+
return (
|
|
9471
|
+
<Chart height={260} ariaLabel="Checkout's latency against its objective and its warning band">
|
|
9472
|
+
<XAxis value="x" tickFormat={(v) => LATENCY[Math.round(v)]?.time ?? ""} />
|
|
9473
|
+
<YAxis label="ms" tickFormat={(v) => `${v} ms`} />
|
|
9474
|
+
<LimitBand from={250} to={300} severity="warning" label="Warning band" />
|
|
9475
|
+
<LimitLine value={300} severity="alarm" label="Objective" />
|
|
9476
|
+
<Line value="p95" name="p95" />
|
|
9477
|
+
<Tooltip mode="x" limits />
|
|
9478
|
+
</Chart>
|
|
9479
|
+
);
|
|
9480
|
+
}
|
|
9481
|
+
```
|
|
9482
|
+
|
|
9483
|
+
##### Choose a hit
|
|
9484
|
+
|
|
9485
|
+
`onSelect` hears a click on a hit, a tap, or Enter on the Active point, with the same hit a custom tooltip receives. The chart keeps no choice and draws none: it is your state, here shown below the chart.
|
|
9486
|
+
|
|
9487
|
+
```tsx
|
|
9488
|
+
import { useState } from "react";
|
|
9489
|
+
import { Tooltip, useChart, type TooltipHit } from "@umriss-ui/charts";
|
|
9490
|
+
|
|
9491
|
+
/* Orders taken per hour of a shop's day, written out here so the example runs on its own. */
|
|
9492
|
+
const ORDERS = [
|
|
9493
|
+
{ hour: 8, web: 14, phone: 6 }, { hour: 9, web: 22, phone: 9 }, { hour: 10, web: 31, phone: 11 },
|
|
9494
|
+
{ hour: 11, web: 38, phone: 8 }, { hour: 12, web: 52, phone: 14 }, { hour: 13, web: 47, phone: 10 },
|
|
9495
|
+
{ hour: 14, web: 35, phone: 7 }, { hour: 15, web: 29, phone: 9 },
|
|
9496
|
+
];
|
|
9497
|
+
|
|
9498
|
+
type Order = (typeof ORDERS)[number];
|
|
9499
|
+
|
|
9500
|
+
export default function ChooseAHit() {
|
|
9501
|
+
const [chosen, setChosen] = useState<TooltipHit<Order> | null>(null);
|
|
9502
|
+
const { Chart, XAxis, YAxis, Line } = useChart(ORDERS, { onSelect: setChosen });
|
|
9503
|
+
return (
|
|
9504
|
+
<>
|
|
9505
|
+
<Chart height={260} ariaLabel="Orders per hour, by channel">
|
|
9506
|
+
<XAxis value="hour" tickFormat={(v) => `${v}:00`} />
|
|
9507
|
+
<YAxis label="orders" />
|
|
9508
|
+
<Line value="web" name="Web" />
|
|
9509
|
+
<Line value="phone" name="Phone" />
|
|
9510
|
+
<Tooltip mode="x" />
|
|
9511
|
+
</Chart>
|
|
9512
|
+
<dl className="metrics">
|
|
9513
|
+
<div>
|
|
9514
|
+
<dt>Chosen</dt>
|
|
9515
|
+
<dd>{chosen === null ? "nothing yet - click a point, or press Enter on one" : `${chosen.xValue}:00`}</dd>
|
|
9516
|
+
</div>
|
|
9517
|
+
{chosen?.points.map((p) => (
|
|
9518
|
+
<div key={p.seriesName}>
|
|
9519
|
+
<dt>{p.seriesName}</dt>
|
|
9520
|
+
<dd>{p.yValue} orders</dd>
|
|
9521
|
+
</div>
|
|
9522
|
+
))}
|
|
9523
|
+
</dl>
|
|
9524
|
+
</>
|
|
9525
|
+
);
|
|
9526
|
+
}
|
|
9527
|
+
```
|
|
9528
|
+
|
|
9646
9529
|
#### Keyboard
|
|
9647
9530
|
|
|
9648
9531
|
The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
@@ -9653,9 +9536,23 @@ The keys of [Chart](#/chart/keyboard-chart) apply here.
|
|
|
9653
9536
|
|
|
9654
9537
|
| Prop | Type | Default | Description |
|
|
9655
9538
|
|---|---|---|---|
|
|
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
|
|
9539
|
+
| `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 60 more |
|
|
9540
|
+
| `limits` | `boolean` | `false` | Adds a row for every labelled `LimitLine` and `LimitBand` on the hit's y axis - its label and value in that axis' format - to the built-in tooltip, and the same to what a key reads out. Limits are not series and stay out of the tooltip unless asked for.<br>Shown in: [Read the limits at the hit](#/tooltip/limits) |
|
|
9657
9541
|
| `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
9542
|
|
|
9543
|
+
##### `ChartOptions<Z = unknown>`
|
|
9544
|
+
|
|
9545
|
+
| Prop | Type | Default | Description |
|
|
9546
|
+
|---|---|---|---|
|
|
9547
|
+
| `initialView` | [`ChartView`](#/view/type-ChartView) | — | The view to start from, and to go to whenever one differing in content from the last is handed in - the same view again changes nothing, and what it leaves out is reset; one the chart reported itself, handed back late, is its own state coming back and not gone to - two charts in step do not jump back mid-pan. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#/zoom-and-pan/visible-domain), [Keep charts in step (Zoom and pan)](#/zoom-and-pan/cursor-sync), [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader) and 2 more |
|
|
9548
|
+
|
|
9549
|
+
###### Events
|
|
9550
|
+
|
|
9551
|
+
| Prop | Type | Default | Description |
|
|
9552
|
+
|---|---|---|---|
|
|
9553
|
+
| `onViewChange` | `(view: `[`ChartView`](#/view/type-ChartView)`) => void` | — | Every change of the view, once, always the whole view - the one to keep, or to hand another chart as its `initialView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with.<br>Shown in: [Keep charts in step (Zoom and pan)](#/zoom-and-pan/cursor-sync), [Read it by keyboard and screen reader (Chart)](#/chart/keyboard-and-screen-reader), [Keep and restore a view (View)](#/view/keep-and-restore) |
|
|
9554
|
+
| `onSelect` | `(hit: `[`TooltipHit`](#type-TooltipHit)`<Z>) => void` | — | The reader chose a point: a click on a hit (a tap too), or Enter on the Active point - the same hit the tooltip's `render` receives. Once per click; the end of a drag does not report, and the second click of a double click does not either. The chart holds no selection and draws none: what is chosen is the application's state. Needs a `<Tooltip>`, which makes the hits; the hit does not travel over `syncId`.<br>Shown in: [Choose a hit](#/tooltip/choose-a-hit) |
|
|
9555
|
+
|
|
9659
9556
|
##### Types on this page
|
|
9660
9557
|
|
|
9661
9558
|
###### `TooltipHit<T = unknown>`
|
|
@@ -9665,8 +9562,8 @@ A custom tooltip's `render` receives it.
|
|
|
9665
9562
|
|
|
9666
9563
|
| Prop | Type | Default | Description |
|
|
9667
9564
|
|---|---|---|---|
|
|
9668
|
-
| `xValue` *required* | `number` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9669
|
-
| `points` *required* | `readonly `[`TooltipPoint`](#type-TooltipPoint)`<T>[]` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9565
|
+
| `xValue` *required* | `number` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Choose a hit](#/tooltip/choose-a-hit), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9566
|
+
| `points` *required* | `readonly `[`TooltipPoint`](#type-TooltipPoint)`<T>[]` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Choose a hit](#/tooltip/choose-a-hit), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9670
9567
|
| `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
|
|
9671
9568
|
| `yPx` *required* | `number` | — | Pixel position of the primary hit. |
|
|
9672
9569
|
|
|
@@ -9676,10 +9573,10 @@ One hit per series in the tooltip.
|
|
|
9676
9573
|
|
|
9677
9574
|
| Prop | Type | Default | Description |
|
|
9678
9575
|
|---|---|---|---|
|
|
9679
|
-
| `seriesName` *required* | `string` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9576
|
+
| `seriesName` *required* | `string` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content), [Choose a hit](#/tooltip/choose-a-hit), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9680
9577
|
| `color` *required* | `string` | — | |
|
|
9681
9578
|
| `xValue` *required* | `number` | — | The point's x value on its own x axis - with several x axes it can differ from the hit's. |
|
|
9682
|
-
| `yValue` *required* | `number` | — | The point's y value; in a stack its own value, not its top - in a normalised stack its share in percent.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9579
|
+
| `yValue` *required* | `number` | — | The point's y value; in a stack its own value, not its top - in a normalised stack its share in percent.<br>Shown in: [Choose a hit](#/tooltip/choose-a-hit), [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
9683
9580
|
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself](#/tooltip/own-content) |
|
|
9684
9581
|
| `index` *required* | `number` | — | |
|
|
9685
9582
|
| `level` | `number` | — | Only for the matrix: its level, out of the level channel. It stands here and not in yValue, because yValue is the position on the y axis - for a matrix therefore the row. Putting both into one field would be exactly the overloading [ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md) is written against. |
|
|
@@ -9923,13 +9820,14 @@ const LEDGER: readonly LedgerRow[] = COST_CENTRES.flatMap((centre, c) => {
|
|
|
9923
9820
|
|
|
9924
9821
|
const MARKETING = LEDGER.filter((row) => row.costCentre === "CC-1200");
|
|
9925
9822
|
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
9926
|
-
|
|
9823
|
+
/** `"2026-03"` as the axis writes it. */
|
|
9824
|
+
const monthName = (month: string) => MONTH_NAMES[Number(month.slice(5)) - 1] ?? month;
|
|
9927
9825
|
|
|
9928
9826
|
function Marketing({ placement }: { placement?: "bottom" }) {
|
|
9929
9827
|
const { Chart, XAxis, YAxis, Line } = useChart(MARKETING);
|
|
9930
9828
|
return (
|
|
9931
9829
|
<Chart height={220} ariaLabel={`Marketing's budget and forecast, legend ${placement ?? "above"}`}>
|
|
9932
|
-
<XAxis value=
|
|
9830
|
+
<XAxis value="month" categoryLabel={monthName} />
|
|
9933
9831
|
<YAxis label="€" />
|
|
9934
9832
|
<Line value="budget" name="Budget" />
|
|
9935
9833
|
<Line value="forecast" name="Forecast" />
|
|
@@ -9973,7 +9871,7 @@ Every entry of a named series is a button whose `aria-pressed` says whether its
|
|
|
9973
9871
|
|
|
9974
9872
|
| Prop | Type | Default | Description |
|
|
9975
9873
|
|---|---|---|---|
|
|
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
|
|
9874
|
+
| `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
9875
|
|
|
9978
9876
|
### Benchmark
|
|
9979
9877
|
|
|
@@ -10343,9 +10241,9 @@ From `XAxisProps` — the full table stands on [Axis](#/axis/type-XAxisProps).
|
|
|
10343
10241
|
|
|
10344
10242
|
| Prop | Type | Default | Description |
|
|
10345
10243
|
|---|---|---|---|
|
|
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
|
|
10244
|
+
| [`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" />`. |
|
|
10245
|
+
| [`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. |
|
|
10246
|
+
| [`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
10247
|
| [`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
10248
|
| [`id`](#/axis/XAxisProps-id) | `string` | `"x"` | Axis id, through which series bind themselves. |
|
|
10351
10249
|
| [`label`](#/axis/XAxisProps-label) | `string` | — | Axis title. |
|
|
@@ -10435,13 +10333,13 @@ Used on [Scenarios](#/), [Chart](#/chart), [Axis](#/axis), [View](#/view), [Line
|
|
|
10435
10333
|
[LimitLine](#/limitline) – A limit drawn as a band between two values of an axis - a tolerance or a
|
|
10436
10334
|
zone. Otherwise a `LimitLine`.
|
|
10437
10335
|
|
|
10438
|
-
Used on [Scenarios](#/), [Chart](#/chart)
|
|
10336
|
+
Used on [Scenarios](#/), [Chart](#/chart), [LimitLine](#/limitline) and [Tooltip](#/tooltip).
|
|
10439
10337
|
|
|
10440
10338
|
##### `LimitLine`
|
|
10441
10339
|
|
|
10442
10340
|
[LimitLine](#/limitline) – The limits a course is read against (a threshold, a reference line): a line above the series, a band beneath them. Reach for it for objectives, budgets and alert levels.
|
|
10443
10341
|
|
|
10444
|
-
Used on [Scenarios](#/), [Chart](#/chart), [Scatter](#/scatter), [BoxPlot](#/boxplot), [LimitLine](#/limitline), [ControlChart](#/controlchart)
|
|
10342
|
+
Used on [Scenarios](#/), [Chart](#/chart), [Scatter](#/scatter), [BoxPlot](#/boxplot), [LimitLine](#/limitline), [ControlChart](#/controlchart), [Pareto](#/pareto) and [Tooltip](#/tooltip).
|
|
10445
10343
|
|
|
10446
10344
|
##### `Tooltip`
|
|
10447
10345
|
|
|
@@ -10463,7 +10361,7 @@ identity over the renders -, the `view`, its `domains` and `hidden`, and
|
|
|
10463
10361
|
the setters that change them.
|
|
10464
10362
|
|
|
10465
10363
|
```ts
|
|
10466
|
-
function useChart<Z>(rows: readonly Z[], options?: ChartOptions): ChartParts<Z>;
|
|
10364
|
+
function useChart<Z>(rows: readonly Z[], options?: ChartOptions<Z>): ChartParts<Z>;
|
|
10467
10365
|
```
|
|
10468
10366
|
|
|
10469
10367
|
Used on [Scenarios](#/), [Installation](#/installation), [Chart](#/chart), [Axis](#/axis), [Zoom and pan](#/zoom-and-pan), [View](#/view), [Line](#/line), [Area](#/area), [Bar](#/bar), [Scatter](#/scatter), [BoxPlot](#/boxplot), [StateBand](#/stateband), [Matrix](#/matrix), [LimitLine](#/limitline), [ControlChart](#/controlchart), [Pareto](#/pareto), [Tooltip](#/tooltip), [Legend](#/legend) and [Benchmark](#/benchmark).
|
|
@@ -10494,6 +10392,23 @@ duplicate.
|
|
|
10494
10392
|
function assess(value: number | null | undefined, set?: LimitSet): Assessment;
|
|
10495
10393
|
```
|
|
10496
10394
|
|
|
10395
|
+
##### `bin`
|
|
10396
|
+
|
|
10397
|
+
Counts values into bins of equal width.
|
|
10398
|
+
|
|
10399
|
+
A value on an edge belongs to the bin that edge starts - `[from, to)` - but
|
|
10400
|
+
the last bin is closed, so the greatest value of a domain is counted and not
|
|
10401
|
+
lost. Values that are not finite (`NaN`, ±Infinity, `null`, `undefined`)
|
|
10402
|
+
are skipped, as a gap is on a chart. Without values there are no bins; with
|
|
10403
|
+
values there is at least one, even when they are all equal.
|
|
10404
|
+
|
|
10405
|
+
```ts
|
|
10406
|
+
function bin(values: readonly (number | null | undefined)[], options?: BinOptions): Bin[];
|
|
10407
|
+
function bin<T>(rows: readonly T[], value: (row: T) => number | null | undefined, options?: BinOptions): Bin[];
|
|
10408
|
+
```
|
|
10409
|
+
|
|
10410
|
+
Used on [Bar](#/bar).
|
|
10411
|
+
|
|
10497
10412
|
##### `breaks`
|
|
10498
10413
|
|
|
10499
10414
|
The breaks in working time that lie within the extent [from, to] - what the
|
|
@@ -10990,19 +10905,35 @@ An `XAxis` or `YAxis` as the chart holds it once registered.
|
|
|
10990
10905
|
| `id` *required* | `string` | — | |
|
|
10991
10906
|
| `orientation` *required* | [`AxisOrientation`](#type-AxisOrientation)<br>`"x" \| "y"` | — | |
|
|
10992
10907
|
| `position` *required* | [`AxisPosition`](#type-AxisPosition)<br>`"bottom" \| "top" \| "left" \| "right"` | — | |
|
|
10993
|
-
| `accessor` | `(d: T, index: number) =>
|
|
10908
|
+
| `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
10909
|
| `label` | `string` | — | |
|
|
10995
10910
|
| `tickCount` | `number` | — | |
|
|
10996
10911
|
| `tickFormat` | `(v: number) => string` | — | |
|
|
10997
10912
|
| `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
10913
|
| `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
|
-
| `
|
|
10914
|
+
| `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. |
|
|
10915
|
+
| `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)). |
|
|
10916
|
+
| `categoryLabel` | `(category: string) => string` | — | What is written for a category, wherever it is written. |
|
|
10917
|
+
| `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
10918
|
| `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
10919
|
| `alignTicks` | `boolean` | — | y axes only: this axis' ticks on the first y axis' grid. |
|
|
11003
10920
|
| `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
10921
|
| `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
10922
|
|
|
10923
|
+
##### `AxisField<T, V = AxisValue>`
|
|
10924
|
+
|
|
10925
|
+
The fields of a row an axis can read - or, with `V`, those that hold only
|
|
10926
|
+
some of what an axis reads: a matrix' row is a position or a category,
|
|
10927
|
+
never an instant.
|
|
10928
|
+
|
|
10929
|
+
```ts
|
|
10930
|
+
type AxisField<T, V = AxisValue> = NoInfer<
|
|
10931
|
+
{
|
|
10932
|
+
[K in keyof T & string]-?: T[K] extends V ? K : never;
|
|
10933
|
+
}[keyof T & string]
|
|
10934
|
+
>;
|
|
10935
|
+
```
|
|
10936
|
+
|
|
11006
10937
|
##### `AxisOrientation`
|
|
11007
10938
|
|
|
11008
10939
|
Whether an axis or a limit runs along x or along y.
|
|
@@ -11028,6 +10959,19 @@ The span each zoomable x axis shows, by axis `id`, in the axis' units.
|
|
|
11028
10959
|
type AxisSpans = Readonly<Record<string, readonly [number, number]>>;
|
|
11029
10960
|
```
|
|
11030
10961
|
|
|
10962
|
+
##### `AxisValue`
|
|
10963
|
+
|
|
10964
|
+
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
|
|
10965
|
+
instant on a time axis -, a string, which is a category, or a `Date`,
|
|
10966
|
+
which is an instant. The type of the value decides what kind the axis is,
|
|
10967
|
+
never its content: `"2024"` is a category, `2024` a number. On an axis
|
|
10968
|
+
that says `time` a string is an ISO 8601 instant. null/undefined is no
|
|
10969
|
+
place.
|
|
10970
|
+
|
|
10971
|
+
```ts
|
|
10972
|
+
type AxisValue = number | string | Date | null | undefined;
|
|
10973
|
+
```
|
|
10974
|
+
|
|
11031
10975
|
##### `BarProps`
|
|
11032
10976
|
|
|
11033
10977
|
Its table stands on [Bar](#/bar/type-BarProps).
|
|
@@ -11051,6 +10995,27 @@ A `Bar` as the chart holds it once registered.
|
|
|
11051
10995
|
| `stack` | `string` | — | The stack this series stands in: series with the same id, on the same x and y axis, stack in registration order (K1). From `Stackable`. |
|
|
11052
10996
|
| `normalize` | `boolean` | — | On any member, the whole stack sums to 100 at every x (K5). From `Stackable`. |
|
|
11053
10997
|
|
|
10998
|
+
##### `Bin`
|
|
10999
|
+
|
|
11000
|
+
A bin, the way it is drawn.
|
|
11001
|
+
|
|
11002
|
+
| Prop | Type | Default | Description |
|
|
11003
|
+
|---|---|---|---|
|
|
11004
|
+
| `from` *required* | `number` | — | Lower edge; the bin holds it. |
|
|
11005
|
+
| `to` *required* | `number` | — | Upper edge; the bin holds it only if it is the last one. |
|
|
11006
|
+
| `x` *required* | `number` | — | The middle, `(from + to) / 2` - the bar's x. |
|
|
11007
|
+
| `count` *required* | `number` | — | How many values fall in it. |
|
|
11008
|
+
|
|
11009
|
+
##### `BinOptions`
|
|
11010
|
+
|
|
11011
|
+
The settings of `bin`.
|
|
11012
|
+
|
|
11013
|
+
| Prop | Type | Default | Description |
|
|
11014
|
+
|---|---|---|---|
|
|
11015
|
+
| `width` | `number` | — | The width of a bin. Wins over `count`. Without a `domain` the edges are multiples of it.<br>Shown in: [Count readings into a histogram (Bar)](#/bar/histogram) |
|
|
11016
|
+
| `count` | `number` | `10` | About how many bins: the width becomes the next 1-2-5 step, so the number comes out near it and not exactly - with a `domain`, exactly. |
|
|
11017
|
+
| `domain` | `readonly [number, number]` | — | The span the bins cover, the first edge on its start and the last on its end. Values outside it are left out, not clamped into the end bins. |
|
|
11018
|
+
|
|
11054
11019
|
##### `BoxChannels`
|
|
11055
11020
|
|
|
11056
11021
|
The named channels of a box: one number per point, NaN where the median
|
|
@@ -11144,14 +11109,14 @@ is typed by that `data` instead.
|
|
|
11144
11109
|
|
|
11145
11110
|
| Prop | Type | Default | Description |
|
|
11146
11111
|
|---|---|---|---|
|
|
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
|
|
11112
|
+
| `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 73 more |
|
|
11113
|
+
| `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 73 more |
|
|
11114
|
+
| `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 73 more |
|
|
11115
|
+
| `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 47 more |
|
|
11151
11116
|
| `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
|
|
11117
|
+
| `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 12 more |
|
|
11153
11118
|
| `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 |
|
|
11119
|
+
| `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
11120
|
| `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
11121
|
| `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
11122
|
| `view` *required* | [`ChartView`](#/view/type-ChartView) | — | How the reader is looking at the chart. |
|
|
@@ -11189,10 +11154,12 @@ Everything the charts put out as text.
|
|
|
11189
11154
|
| `seriesList` *required* | `(names: readonly string[]) => string` | — | The summary's visible series, by name. |
|
|
11190
11155
|
| `visibleRange` *required* | `(from: string, to: string) => string` | — | The summary's visible stretch of the x axis, both ends formatted. |
|
|
11191
11156
|
| `seriesExtent` *required* | `(name: string, min: string, max: string) => string` | — | The summary's lowest and highest value of one series in that stretch. |
|
|
11157
|
+
| `limitList` *required* | `(limits: readonly { name: string; value: string }[]) => string` | — | The summary's labelled limits, each with its value in its axis' format (a band's two edges as one value). |
|
|
11192
11158
|
| `readout` *required* | `(x: string, rows: readonly { name: string; value: string; x: string }[]) => string` | — | What is read after a key: the position, then each series with its value (and its own x where it stands on another x axis), the emphasised one first. |
|
|
11193
11159
|
| `stackTotal` *required* | `string` | — | The last row of a stack in the tooltip and the readout: the sum of its members at the point (charts-stacking K4). |
|
|
11194
11160
|
| `percent` *required* | `(value: string) => string` | — | A share in a normalised stack, its number already formatted (K5). |
|
|
11195
11161
|
| `walkHelp` *required* | `string` | — | The keys that walk the chart. |
|
|
11162
|
+
| `selectHelp` *required* | `string` | — | The key that reports the Active point - only where `onSelect` is given. |
|
|
11196
11163
|
| `zoomHelp` *required* | `string` | — | The keys that zoom and pan it - only where the chart can be zoomed. |
|
|
11197
11164
|
| `legendHelp` *required* | `string` | — | The keys of the legend's entries - only where it has one to press. |
|
|
11198
11165
|
| `allShown` *required* | `string` | — | Read out where hiding would leave nothing to see, and every series is shown instead. |
|
|
@@ -11471,7 +11438,9 @@ Its table stands on [Matrix](#/matrix/type-MatrixProps).
|
|
|
11471
11438
|
##### `MatrixSeriesConfig<T = unknown>`
|
|
11472
11439
|
|
|
11473
11440
|
A `Matrix` as the chart holds it once registered: its base accessor is the
|
|
11474
|
-
row, `level` the colour.
|
|
11441
|
+
row, `level` the colour. The row may be named: where the accessor yields a
|
|
11442
|
+
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
|
|
11443
|
+
series kind whose base accessor reads more than a number.
|
|
11475
11444
|
|
|
11476
11445
|
| Prop | Type | Default | Description |
|
|
11477
11446
|
|---|---|---|---|
|
|
@@ -11499,6 +11468,16 @@ type NumberField<T> = NoInfer<
|
|
|
11499
11468
|
>;
|
|
11500
11469
|
```
|
|
11501
11470
|
|
|
11471
|
+
##### `OneLane`
|
|
11472
|
+
|
|
11473
|
+
A lane is named or given by its edges: the compiler refuses both at once.
|
|
11474
|
+
Beside the props and not in them, so that `StateBandProps` stays an
|
|
11475
|
+
interface another can extend.
|
|
11476
|
+
|
|
11477
|
+
```ts
|
|
11478
|
+
type OneLane = { lane: string; laneFrom?: undefined; laneTo?: undefined } | { lane?: undefined };
|
|
11479
|
+
```
|
|
11480
|
+
|
|
11502
11481
|
##### `Padding`
|
|
11503
11482
|
|
|
11504
11483
|
Space in CSS pixels on each side of a rectangle.
|
|
@@ -11516,9 +11495,9 @@ An item, the way it is drawn.
|
|
|
11516
11495
|
|
|
11517
11496
|
| Prop | Type | Default | Description |
|
|
11518
11497
|
|---|---|---|---|
|
|
11519
|
-
| `name` *required* | `string` | — |
|
|
11498
|
+
| `name` *required* | `string` | — | |
|
|
11520
11499
|
| `value` *required* | `number` | — | |
|
|
11521
|
-
| `index` *required* | `number` | — |
|
|
11500
|
+
| `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
11501
|
| `share` *required* | `number` | — | Share of the total, 0…1. |
|
|
11523
11502
|
| `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
11503
|
| `remainder` *required* | `boolean` | — | The collected tail. Exactly one entry can be this, and it always stands last. |
|
|
@@ -11754,6 +11733,7 @@ an index into `states`.
|
|
|
11754
11733
|
|---|---|---|---|
|
|
11755
11734
|
| `kind` *required* | `"state"` | — | |
|
|
11756
11735
|
| `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)). |
|
|
11736
|
+
| `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
11737
|
| `laneFrom` | `number` | — | Lower edge of the lane in domain units of the y axis; without a value the beginning of the axis domain. |
|
|
11758
11738
|
| `laneTo` | `number` | — | Upper edge of the lane; without a value the end of the axis domain. |
|
|
11759
11739
|
| `accessor` *required* | [`ValueFunction`](#type-ValueFunction)`<T>` | — | Y value; null/undefined/NaN/±Infinity means a gap. From `SeriesBase`. |
|
|
@@ -11772,6 +11752,7 @@ A `Tooltip` as the chart holds it once registered.
|
|
|
11772
11752
|
| Prop | Type | Default | Description |
|
|
11773
11753
|
|---|---|---|---|
|
|
11774
11754
|
| `mode` *required* | `"x" \| "nearest"` | — | |
|
|
11755
|
+
| `limits` | `boolean` | — | |
|
|
11775
11756
|
| `render` | `(hit: `[`TooltipHit`](#type-TooltipHit)`<T>) => ReactNode` | — | |
|
|
11776
11757
|
|
|
11777
11758
|
##### `TooltipHit<T = unknown>`
|
|
@@ -11781,8 +11762,8 @@ A custom tooltip's `render` receives it.
|
|
|
11781
11762
|
|
|
11782
11763
|
| Prop | Type | Default | Description |
|
|
11783
11764
|
|---|---|---|---|
|
|
11784
|
-
| `xValue` *required* | `number` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content) |
|
|
11785
|
-
| `points` *required* | `readonly `[`TooltipPoint`](#type-TooltipPoint)`<T>[]` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content) |
|
|
11765
|
+
| `xValue` *required* | `number` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
11766
|
+
| `points` *required* | `readonly `[`TooltipPoint`](#type-TooltipPoint)`<T>[]` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
11786
11767
|
| `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
|
|
11787
11768
|
| `yPx` *required* | `number` | — | Pixel position of the primary hit. |
|
|
11788
11769
|
|
|
@@ -11794,10 +11775,10 @@ One hit per series in the tooltip.
|
|
|
11794
11775
|
|
|
11795
11776
|
| Prop | Type | Default | Description |
|
|
11796
11777
|
|---|---|---|---|
|
|
11797
|
-
| `seriesName` *required* | `string` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content) |
|
|
11778
|
+
| `seriesName` *required* | `string` | — | <br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Write the tooltip yourself (Tooltip)](#/tooltip/own-content), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
11798
11779
|
| `color` *required* | `string` | — | |
|
|
11799
11780
|
| `xValue` *required* | `number` | — | The point's x value on its own x axis - with several x axes it can differ from the hit's. |
|
|
11800
|
-
| `yValue` *required* | `number` | — | The point's y value; in a stack its own value, not its top - in a normalised stack its share in percent.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration) |
|
|
11781
|
+
| `yValue` *required* | `number` | — | The point's y value; in a stack its own value, not its top - in a normalised stack its share in percent.<br>Shown in: [Fix the domain and name the ticks (Axis)](#/axis/configuration), [Choose a hit (Tooltip)](#/tooltip/choose-a-hit) |
|
|
11801
11782
|
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself (Tooltip)](#/tooltip/own-content) |
|
|
11802
11783
|
| `index` *required* | `number` | — | |
|
|
11803
11784
|
| `level` | `number` | — | Only for the matrix: its level, out of the level channel. It stands here and not in yValue, because yValue is the position on the y axis - for a matrix therefore the row. Putting both into one field would be exactly the overloading [ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md) is written against. |
|
|
@@ -11906,7 +11887,7 @@ A zone line: k times sigma above and below the centre line.
|
|
|
11906
11887
|
| `upper` *required* | `number` | — | |
|
|
11907
11888
|
| `lower` *required* | `number` | — | |
|
|
11908
11889
|
|
|
11909
|
-
Used on [Scenarios](#/), [Axis](#/axis), [
|
|
11890
|
+
Used on [Scenarios](#/), [Axis](#/axis), [LimitLine](#/limitline) and [ControlChart](#/controlchart).
|
|
11910
11891
|
|
|
11911
11892
|
##### `ZoomLimits`
|
|
11912
11893
|
|