@umriss-ui/charts 0.45.0 → 0.47.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 +30 -2
- package/dist/charts.css +1 -1
- package/dist/charts.js +2961 -2819
- package/dist/de.d.ts +3 -0
- package/dist/index.d.ts +55 -6
- package/dist/wording/de.js +1 -0
- package/docs/llms-full.md +994 -103
- 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.47.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
|
|
|
@@ -2210,6 +2210,74 @@ export default function Picture() {
|
|
|
2210
2210
|
}
|
|
2211
2211
|
```
|
|
2212
2212
|
|
|
2213
|
+
##### Columns instead of rows
|
|
2214
|
+
|
|
2215
|
+
A course of 100,000 readings in two `Float64Array` columns, with no object per row: the rows are an index array, and the axis and the line read `column[i]` at each index. The rows are memoised on the columns, so a new course is read again; a function compared by its text would keep the old values. A column from Arrow comes as `getChild(name).toArray()`. Columns are no input of [`useChart`](#api-index-1) itself, because a hit, `onSelect`, the tooltip and the data table are per row ([ADR-0048](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0048-charts-are-bound-to-their-rows-by-the-hook.md)).
|
|
2216
|
+
|
|
2217
|
+
```tsx
|
|
2218
|
+
import { useMemo, useState } from "react";
|
|
2219
|
+
import { Button } from "@umriss-ui/core";
|
|
2220
|
+
import { DataTable, Tooltip, bin, useChart } from "@umriss-ui/charts";
|
|
2221
|
+
|
|
2222
|
+
const COUNT = 100_000;
|
|
2223
|
+
|
|
2224
|
+
/** A small LCG - the same numbers on every computer. */
|
|
2225
|
+
function random(seed: number): () => number {
|
|
2226
|
+
let s = seed >>> 0;
|
|
2227
|
+
return () => {
|
|
2228
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
2229
|
+
return s / 4294967296;
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
/** One reading a minute, and the pressure read at it, as two columns. */
|
|
2234
|
+
function course(seed: number): { minute: Float64Array; pressure: Float64Array } {
|
|
2235
|
+
const r = random(seed);
|
|
2236
|
+
const minute = new Float64Array(COUNT);
|
|
2237
|
+
const pressure = new Float64Array(COUNT);
|
|
2238
|
+
for (let i = 0; i < COUNT; i++) {
|
|
2239
|
+
minute[i] = i;
|
|
2240
|
+
pressure[i] = 6 + 0.8 * Math.sin(i / 500) + (r() - 0.5) * 0.6;
|
|
2241
|
+
}
|
|
2242
|
+
return { minute, pressure };
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
export default function ColumnsInsteadOfRows() {
|
|
2246
|
+
const [seed, setSeed] = useState(2026);
|
|
2247
|
+
const columns = useMemo(() => course(seed), [seed]);
|
|
2248
|
+
// One index per reading: the rows are read again whenever the columns are new.
|
|
2249
|
+
const rows = useMemo(() => Array.from({ length: columns.pressure.length }, (_, i) => i), [columns]);
|
|
2250
|
+
const counts = useMemo(() => bin(columns.pressure, { count: 20 }), [columns]);
|
|
2251
|
+
|
|
2252
|
+
const { Chart, XAxis, YAxis, Line } = useChart(rows);
|
|
2253
|
+
const { Chart: Histogram, XAxis: CountXAxis, YAxis: CountYAxis, Bar } = useChart(counts);
|
|
2254
|
+
return (
|
|
2255
|
+
<>
|
|
2256
|
+
<div className="pair">
|
|
2257
|
+
<div>
|
|
2258
|
+
<Chart height={260} aria-label="Pressure of 100,000 readings, one a minute">
|
|
2259
|
+
<XAxis value={(_, i) => columns.minute[i]} label="Minute" />
|
|
2260
|
+
<YAxis label="bar" />
|
|
2261
|
+
<Line value={(_, i) => columns.pressure[i]} name="Pressure" />
|
|
2262
|
+
<Tooltip mode="x" />
|
|
2263
|
+
<DataTable />
|
|
2264
|
+
</Chart>
|
|
2265
|
+
</div>
|
|
2266
|
+
<div>
|
|
2267
|
+
<Histogram height={260} aria-label="How often each pressure was read, in 20 bins">
|
|
2268
|
+
<CountXAxis value="x" label="bar" />
|
|
2269
|
+
<CountYAxis label="readings" />
|
|
2270
|
+
<Bar value="count" name="Readings" barWidth={0.94} />
|
|
2271
|
+
<Tooltip mode="x" />
|
|
2272
|
+
</Histogram>
|
|
2273
|
+
</div>
|
|
2274
|
+
</div>
|
|
2275
|
+
<Button onClick={() => setSeed((s) => s + 1)}>New course</Button>
|
|
2276
|
+
</>
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
```
|
|
2280
|
+
|
|
2213
2281
|
#### When to use something else
|
|
2214
2282
|
|
|
2215
2283
|
- A single number with a small trend beside it → `Sparkline` or `Stat` from @umriss-ui/core
|
|
@@ -2244,16 +2312,16 @@ Under forced colours the chart draws in the system colours and tells its series
|
|
|
2244
2312
|
| Prop | Type | Default | Description |
|
|
2245
2313
|
|---|---|---|---|
|
|
2246
2314
|
| `ref` | `Ref<HTMLDivElement>` | — | The root element, the `<div>` that `className`, `style`, `id` and the `data-*` attributes reach. |
|
|
2247
|
-
| `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) |
|
|
2248
|
-
| `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-1), [Say there is nothing to show](#chart-1), [Read it by keyboard and screen reader](#chart-1) and
|
|
2315
|
+
| `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), [A legend beside the plot (Legend)](#legend) |
|
|
2316
|
+
| `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-1), [Say there is nothing to show](#chart-1), [Read it by keyboard and screen reader](#chart-1) and 93 more |
|
|
2249
2317
|
| `padding` | `number \| Partial<``Padding``>` | — | Outer spacing of the plot area in CSS pixels.<br>Shown in: [Fix the domain and name the ticks (Axis)](#axis) |
|
|
2250
2318
|
| `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-1) |
|
|
2251
2319
|
| `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-1) |
|
|
2252
2320
|
| `wording` | `Partial<``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. Its `locale` also formats every number and every date or time label the chart makes itself (`en-GB`, `de-DE`; the browser's language is not read) - a `tickFormat` or `format` of your own stays in charge of its axis or series. German: `GERMAN_CHARTS_WORDING` from `@umriss-ui/charts/wording/de`.<br>Shown in: [Read it by keyboard and screen reader](#chart-1) |
|
|
2253
2321
|
| `timeZone` | `string` | the reader's zone | An IANA time zone (`"Europe/Berlin"`) for every time axis of the chart, a calendar axis included: ticks stand on its hours and midnights, and the labels, tooltip, readout, data table and summary write its time, in the wording's `locale`. Where the reader's zone is another, the axis title ends in the zone's name (after mount). An hour a clock change doubles is ticked twice and its labels carry the offset; the hour it removes has no tick. A name Intl does not know throws its `RangeError`. An application on `@umriss-ui/core` passes `useFormats().timeZone`.<br>Shown in: [Name the time zone of the instants (Axis)](#axis) |
|
|
2254
2322
|
| `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-1), [Mark every kind](#chart-1), [Mark bands, limits and cells](#chart-1) and 1 more |
|
|
2255
|
-
| `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 sending each other their view (`useChart`'s `onViewChange` and `setView`).<br>Shown in: [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Watch latency against its objective (Scenarios)](#watch-latency-against-its-objective)
|
|
2256
|
-
| `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-1), [Say there is nothing to show](#chart-1), [Read it by keyboard and screen reader](#chart-1) and
|
|
2323
|
+
| `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 sending each other their view (`useChart`'s `onViewChange` and `setView`).<br>Shown in: [Small multiples on one y domain (Axis)](#axis), [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Watch latency against its objective (Scenarios)](#watch-latency-against-its-objective) and 1 more |
|
|
2324
|
+
| `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-1), [Say there is nothing to show](#chart-1), [Read it by keyboard and screen reader](#chart-1) and 93 more |
|
|
2257
2325
|
|
|
2258
2326
|
###### Events
|
|
2259
2327
|
|
|
@@ -2324,6 +2392,7 @@ Everything the charts put out as text.
|
|
|
2324
2392
|
| `count` *required* | `string` | — | |
|
|
2325
2393
|
| `outlierList` *required* | `(values: readonly string[], more: number) => string` | — | |
|
|
2326
2394
|
| `downsampled` *required* | `(readings: number) => string` | — | Added to the caption where the table shows the downsampled course: how many readings its rows stand for. |
|
|
2395
|
+
| `levelRange` *required* | `(from: string, to: string) => string` | — | Added to the name of a matrix' legend entry that has a colour bar: the range its colours span, both ends written in the matrix' format. The bar's own values are hidden from assistive technology. |
|
|
2327
2396
|
|
|
2328
2397
|
###### `ChartPerf`
|
|
2329
2398
|
|
|
@@ -2338,9 +2407,9 @@ Measurements of the instrumentation for the benchmark page.
|
|
|
2338
2407
|
#### Known limits
|
|
2339
2408
|
|
|
2340
2409
|
- No error state of its own: where the rows could not be loaded, show an `Alert` from @umriss-ui/core. Waiting for them is `loading`.
|
|
2341
|
-
- No pie, donut, radar or candlestick, no smoothing, animation or export, no WebGL ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
2410
|
+
- No pie, donut, radar or candlestick, no smoothing, animation or export, no WebGL ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md)). The share of a whole is bars on a percent axis: the Pareto page, "Shares, not counts".
|
|
2342
2411
|
|
|
2343
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
2412
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
2344
2413
|
|
|
2345
2414
|
### Axis
|
|
2346
2415
|
|
|
@@ -3046,6 +3115,204 @@ export default function TimeZone() {
|
|
|
3046
3115
|
}
|
|
3047
3116
|
```
|
|
3048
3117
|
|
|
3118
|
+
##### Small multiples on one y domain
|
|
3119
|
+
|
|
3120
|
+
Six charts share a `syncId` and the same `domain` on every `YAxis`. Equal domains give equal tick labels, so the y bands are equally wide and the plot areas line up; axes that fit their own data would start each plot at another x. Which domain is common is the application's decision, not a prop.
|
|
3121
|
+
|
|
3122
|
+
```tsx
|
|
3123
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
3124
|
+
|
|
3125
|
+
/* Data from the operations world, written out here so the example runs on its own. */
|
|
3126
|
+
|
|
3127
|
+
/** A small LCG - the same numbers on every computer. */
|
|
3128
|
+
function random(seed: number): () => number {
|
|
3129
|
+
let s = seed >>> 0;
|
|
3130
|
+
return () => {
|
|
3131
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
3132
|
+
return s / 4294967296;
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
interface Load {
|
|
3137
|
+
hour: number;
|
|
3138
|
+
/** Processor load, in per cent. */
|
|
3139
|
+
cpu: number;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
/** A day of hourly load per service: busy by day, each at a level of its own. */
|
|
3143
|
+
const SERVICES = ["Checkout", "Billing", "Sign-in", "Search", "Image service", "Notifications"].map((name, index) => {
|
|
3144
|
+
const r = random(40 + index);
|
|
3145
|
+
const level = 25 + index * 11;
|
|
3146
|
+
const rows: Load[] = Array.from({ length: 24 }, (_, hour) => ({
|
|
3147
|
+
hour,
|
|
3148
|
+
cpu: Math.min(100, Math.round(level * (0.5 + 0.5 * Math.max(0, Math.sin((Math.PI * (hour - 5)) / 16))) + r() * 8)),
|
|
3149
|
+
}));
|
|
3150
|
+
return { name, rows };
|
|
3151
|
+
});
|
|
3152
|
+
|
|
3153
|
+
function Small({ name, rows }: { name: string; rows: readonly Load[] }) {
|
|
3154
|
+
const { Chart, XAxis, YAxis, Line } = useChart(rows);
|
|
3155
|
+
return (
|
|
3156
|
+
<Chart height={140} syncId="services" aria-label={`${name}, processor load over a day`}>
|
|
3157
|
+
<XAxis value="hour" tickCount={4} />
|
|
3158
|
+
<YAxis domain={[0, 100]} tickCount={3} label={`${name} %`} />
|
|
3159
|
+
<Line value="cpu" name={name} />
|
|
3160
|
+
<Tooltip mode="x" />
|
|
3161
|
+
</Chart>
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
export default function SmallMultiples() {
|
|
3166
|
+
return (
|
|
3167
|
+
<div data-role="small-multiples" style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: 8 }}>
|
|
3168
|
+
{SERVICES.map((service) => (
|
|
3169
|
+
<Small key={service.name} {...service} />
|
|
3170
|
+
))}
|
|
3171
|
+
</div>
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
```
|
|
3175
|
+
|
|
3176
|
+
##### Values over decades
|
|
3177
|
+
|
|
3178
|
+
The chart's scales are straight: a log scale would break the affine mapping its axes rely on ([ADR-0001](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0001-affine-scale-contract.md), [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md)). So the value is `Math.log10` of the reading, `ticks` stand at 1 to 9 times every power of ten, `tickFormat` labels only the whole powers, and the series' `format` writes `10 ** v`, so that the tooltip shows the reading and not its logarithm.
|
|
3179
|
+
|
|
3180
|
+
```tsx
|
|
3181
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
3182
|
+
|
|
3183
|
+
/* Data from the operations world, written out here so the example runs on its own. */
|
|
3184
|
+
|
|
3185
|
+
interface Burst {
|
|
3186
|
+
hour: number;
|
|
3187
|
+
/** The reading: log lines per minute. */
|
|
3188
|
+
reading: number;
|
|
3189
|
+
/** The reading's logarithm to base 10: what the chart is given. */
|
|
3190
|
+
v: number;
|
|
3191
|
+
}
|
|
3192
|
+
|
|
3193
|
+
/** Log lines per minute over two days: a quiet system, then an incident that
|
|
3194
|
+
grows by decades and dies away again. */
|
|
3195
|
+
const BURST: readonly Burst[] = Array.from({ length: 49 }, (_, hour) => {
|
|
3196
|
+
const reading = Math.max(1, Math.round(10 ** (2.2 + 3.6 * Math.sin((Math.PI * hour) / 40) ** 3 * (hour < 28 ? 1 : 1 - (hour - 28) / 30))));
|
|
3197
|
+
return { hour, reading, v: Math.log10(reading) };
|
|
3198
|
+
});
|
|
3199
|
+
|
|
3200
|
+
/** log10(m * 10^k) for m from 1 to 9 in each of six decades, and the top. */
|
|
3201
|
+
const TICKS = [...Array.from({ length: 6 }, (_, k) => Array.from({ length: 9 }, (_, m) => Math.log10((m + 1) * 10 ** k))).flat(), 6];
|
|
3202
|
+
|
|
3203
|
+
const isDecade = (v: number) => Math.abs(v - Math.round(v)) < 1e-9;
|
|
3204
|
+
|
|
3205
|
+
export default function Decades() {
|
|
3206
|
+
const { Chart, XAxis, YAxis, Line } = useChart(BURST);
|
|
3207
|
+
return (
|
|
3208
|
+
<Chart height={280} aria-label="Log lines per minute over two days, on a scale in decades">
|
|
3209
|
+
<XAxis value="hour" label="Hours" />
|
|
3210
|
+
<YAxis domain={[0, 6]} ticks={TICKS} tickFormat={(v) => (isDecade(v) ? `10^${Math.round(v)}` : "")} label="Lines per minute" />
|
|
3211
|
+
<Line value="v" name="Lines per minute" format={(v) => Math.round(10 ** v).toLocaleString("en")} />
|
|
3212
|
+
<Tooltip mode="x" />
|
|
3213
|
+
</Chart>
|
|
3214
|
+
);
|
|
3215
|
+
}
|
|
3216
|
+
```
|
|
3217
|
+
|
|
3218
|
+
##### A trend with pens
|
|
3219
|
+
|
|
3220
|
+
Three pens stand on one time axis, each on a scale of its own with its unit as the label; yesterday's course of one pen is one more dashed `Line`. The pen list is core's `Checkbox` bound to `hidden` and `toggleSeries` (an application with the table binds a `Table` the same way). A pen is a tag of the application's historian, which the chart does not know: it draws the rows it is given.
|
|
3221
|
+
|
|
3222
|
+
```tsx
|
|
3223
|
+
import { Checkbox, Text } from "@umriss-ui/core";
|
|
3224
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
3225
|
+
|
|
3226
|
+
/* Data from the operations world, written out here so the example runs on its own. */
|
|
3227
|
+
|
|
3228
|
+
/** A small LCG - the same numbers on every computer. */
|
|
3229
|
+
function random(seed: number): () => number {
|
|
3230
|
+
let s = seed >>> 0;
|
|
3231
|
+
return () => {
|
|
3232
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
3233
|
+
return s / 4294967296;
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
const MINUTE = 60_000;
|
|
3238
|
+
|
|
3239
|
+
const DAY = 24 * 60 * MINUTE;
|
|
3240
|
+
|
|
3241
|
+
/** Today, 06:00 to 14:00 local, a reading every ten minutes. */
|
|
3242
|
+
const START = new Date(2026, 2, 17, 6).getTime();
|
|
3243
|
+
|
|
3244
|
+
interface Reading {
|
|
3245
|
+
t: number;
|
|
3246
|
+
temperature: number;
|
|
3247
|
+
pressure: number;
|
|
3248
|
+
flow: number;
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
function course(seed: number): Reading[] {
|
|
3252
|
+
const r = random(seed);
|
|
3253
|
+
return Array.from({ length: 49 }, (_, i) => ({
|
|
3254
|
+
t: START + i * 10 * MINUTE,
|
|
3255
|
+
temperature: Math.round((1180 + 25 * Math.sin(i / 7) + (r() - 0.5) * 8) * 10) / 10,
|
|
3256
|
+
pressure: Math.round((4.2 + 0.5 * Math.sin(i / 11 + 1) + (r() - 0.5) * 0.12) * 100) / 100,
|
|
3257
|
+
flow: Math.round(310 + 60 * Math.sin(i / 9 + 2) + (r() - 0.5) * 14),
|
|
3258
|
+
}));
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
const TODAY = course(11);
|
|
3262
|
+
|
|
3263
|
+
/** Yesterday's temperature as the historian returns it, then shifted by a day to stand on today's axis. */
|
|
3264
|
+
const YESTERDAY = course(12).map((row) => ({ t: row.t - DAY, temperature: row.temperature }));
|
|
3265
|
+
|
|
3266
|
+
const YESTERDAY_ON_TODAY = YESTERDAY.map((row) => ({ ...row, t: row.t + DAY }));
|
|
3267
|
+
|
|
3268
|
+
const PENS = [
|
|
3269
|
+
{ name: "Furnace temperature", key: "temperature", unit: "°C", digits: 0 },
|
|
3270
|
+
{ name: "Steam pressure", key: "pressure", unit: "bar", digits: 2 },
|
|
3271
|
+
{ name: "Cooling flow", key: "flow", unit: "m³/h", digits: 0 },
|
|
3272
|
+
] as const;
|
|
3273
|
+
|
|
3274
|
+
const YESTERDAY_NAME = "Furnace temperature, yesterday";
|
|
3275
|
+
|
|
3276
|
+
export default function Pens() {
|
|
3277
|
+
const { Chart, XAxis, YAxis, Line, hidden, toggleSeries } = useChart(TODAY);
|
|
3278
|
+
const last = TODAY[TODAY.length - 1] as Reading;
|
|
3279
|
+
return (
|
|
3280
|
+
<div style={{ display: "grid", gap: 8 }}>
|
|
3281
|
+
<Chart height={300} aria-label="Furnace temperature, steam pressure and cooling flow today, and yesterday's temperature">
|
|
3282
|
+
<XAxis value="t" time />
|
|
3283
|
+
<YAxis domain={[1100, 1250]} label="°C" />
|
|
3284
|
+
<YAxis id="bar" domain={[3, 6]} label="bar" />
|
|
3285
|
+
<YAxis id="flow" position="right" domain={[200, 400]} label="m³/h" />
|
|
3286
|
+
<Line value="temperature" name="Furnace temperature" markers="never" />
|
|
3287
|
+
<Line value="temperature" data={YESTERDAY_ON_TODAY} name={YESTERDAY_NAME} dash={[5, 4]} color="var(--uc-series-1)" markers="never" />
|
|
3288
|
+
<Line value="pressure" yAxisId="bar" name="Steam pressure" markers="never" />
|
|
3289
|
+
<Line value="flow" yAxisId="flow" name="Cooling flow" markers="never" />
|
|
3290
|
+
<Tooltip mode="x" />
|
|
3291
|
+
</Chart>
|
|
3292
|
+
<div style={{ display: "grid", gap: 4, maxWidth: 420 }}>
|
|
3293
|
+
{PENS.map((pen) => (
|
|
3294
|
+
<Checkbox
|
|
3295
|
+
key={pen.name}
|
|
3296
|
+
checked={!hidden.includes(pen.name)}
|
|
3297
|
+
onChange={() => {
|
|
3298
|
+
toggleSeries(pen.name);
|
|
3299
|
+
if (pen.key === "temperature") toggleSeries(YESTERDAY_NAME);
|
|
3300
|
+
}}
|
|
3301
|
+
label={
|
|
3302
|
+
<span style={{ display: "flex", gap: 8, minWidth: 380, justifyContent: "space-between" }}>
|
|
3303
|
+
<Text as="span">{pen.name}, {pen.unit}</Text>
|
|
3304
|
+
<Text as="span" mono>{last[pen.key].toFixed(pen.digits)}</Text>
|
|
3305
|
+
</span>
|
|
3306
|
+
}
|
|
3307
|
+
/>
|
|
3308
|
+
))}
|
|
3309
|
+
<Text as="p" size="sm" tone="muted" data-role="hidden">{hidden.join(", ")}</Text>
|
|
3310
|
+
</div>
|
|
3311
|
+
</div>
|
|
3312
|
+
);
|
|
3313
|
+
}
|
|
3314
|
+
```
|
|
3315
|
+
|
|
3049
3316
|
#### Keyboard
|
|
3050
3317
|
|
|
3051
3318
|
The keys of [Chart](#chart-1) apply here.
|
|
@@ -3056,19 +3323,19 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
3056
3323
|
|
|
3057
3324
|
| Prop | Type | Default | Description |
|
|
3058
3325
|
|---|---|---|---|
|
|
3059
|
-
| `value` *required* | `AxisField``<T> \| ((d: T, index: number) => ``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), [Add axes for other magnitudes](#axis), [Leave out the hours nobody works](#axis) and
|
|
3326
|
+
| `value` *required* | `AxisField``<T> \| ((d: T, index: number) => ``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), [Add axes for other magnitudes](#axis), [Leave out the hours nobody works](#axis) and 93 more |
|
|
3060
3327
|
| `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), [Read it by keyboard and screen reader (Chart)](#chart-1), [Gestures and keys (Zoom and pan)](#zoom-and-pan) and 12 more |
|
|
3061
3328
|
| `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), [Bind every kind of series to a second axis](#axis) |
|
|
3062
|
-
| `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), [Name the time zone of the instants](#axis), [
|
|
3329
|
+
| `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), [Name the time zone of the instants](#axis), [A trend with pens](#axis) and 42 more |
|
|
3063
3330
|
| `calendar` | `readonly ``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), [Hand over a picture (Chart)](#chart-1) |
|
|
3064
|
-
| `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`, `setView`, `setDomain`). The plain wheel always scrolls the page.<br>Shown in: [Read it by keyboard and screen reader (Chart)](#chart-1), [Gestures and keys (Zoom and pan)](#zoom-and-pan), [Limit the zoom (Zoom and pan)](#zoom-and-pan) and
|
|
3331
|
+
| `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`, `setView`, `setDomain`). The plain wheel always scrolls the page.<br>Shown in: [Read it by keyboard and screen reader (Chart)](#chart-1), [Gestures and keys (Zoom and pan)](#zoom-and-pan), [Limit the zoom (Zoom and pan)](#zoom-and-pan) and 8 more |
|
|
3065
3332
|
| `zoomLimits` | `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) |
|
|
3066
|
-
| `id` | `string` | `"x"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes](#axis), [Align a second axis to the grid](#axis), [Bind every kind of series to a second axis](#axis) and
|
|
3067
|
-
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#axis), [Add axes for other magnitudes](#axis), [Leave out the hours nobody works](#axis) and
|
|
3068
|
-
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Name the time zone of the instants](#axis) and
|
|
3069
|
-
| `tickFormat` | `ValueFormat` | — | 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. Or `NumberOptions`: Intl's options in the wording's locale, compared by value; the step's places stay unless the options set digits.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Bind every kind of series to a second axis](#axis) and
|
|
3333
|
+
| `id` | `string` | `"x"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes](#axis), [Align a second axis to the grid](#axis), [Bind every kind of series to a second axis](#axis) and 8 more |
|
|
3334
|
+
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#axis), [Add axes for other magnitudes](#axis), [Leave out the hours nobody works](#axis) and 85 more |
|
|
3335
|
+
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Name the time zone of the instants](#axis) and 10 more |
|
|
3336
|
+
| `tickFormat` | `ValueFormat` | — | 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. Or `NumberOptions`: Intl's options in the wording's locale, compared by value; the step's places stay unless the options set digits.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Bind every kind of series to a second axis](#axis) and 24 more |
|
|
3070
3337
|
| `grid` | `boolean` | only on the first axis of its orientation | Grid lines.<br>Shown in: [One vehicle through the day (StateBand)](#stateband), [Give each one a lane (StateBand)](#stateband), [See where the vans stand idle (Scenarios)](#see-where-the-vans-stand-idle) |
|
|
3071
|
-
| `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), [Mark bands, limits and cells (Chart)](#chart-1)
|
|
3338
|
+
| `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), [Values over decades](#axis), [Mark bands, limits and cells (Chart)](#chart-1) and 9 more |
|
|
3072
3339
|
| `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) |
|
|
3073
3340
|
| `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), [Tell series apart without colour (Chart)](#chart-1), [Legend above or below (Legend)](#legend) and 1 more |
|
|
3074
3341
|
|
|
@@ -3076,15 +3343,15 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
3076
3343
|
|
|
3077
3344
|
| Prop | Type | Default | Description |
|
|
3078
3345
|
|---|---|---|---|
|
|
3079
|
-
| `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), [Bind every kind of series to a second axis](#axis), [
|
|
3346
|
+
| `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), [Bind every kind of series to a second axis](#axis), [Small multiples on one y domain](#axis) and 15 more |
|
|
3080
3347
|
| `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) |
|
|
3081
|
-
| `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), [Align a second axis to the grid](#axis), [Bind every kind of series to a second axis](#axis) and
|
|
3082
|
-
| `id` | `string` | `"y"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes](#axis), [Align a second axis to the grid](#axis), [Bind every kind of series to a second axis](#axis) and
|
|
3083
|
-
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#axis), [Add axes for other magnitudes](#axis), [Leave out the hours nobody works](#axis) and
|
|
3084
|
-
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Name the time zone of the instants](#axis) and
|
|
3085
|
-
| `tickFormat` | `ValueFormat` | — | 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. Or `NumberOptions`: Intl's options in the wording's locale, compared by value; the step's places stay unless the options set digits.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Bind every kind of series to a second axis](#axis) and
|
|
3348
|
+
| `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), [Align a second axis to the grid](#axis), [Bind every kind of series to a second axis](#axis) and 8 more |
|
|
3349
|
+
| `id` | `string` | `"y"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes](#axis), [Align a second axis to the grid](#axis), [Bind every kind of series to a second axis](#axis) and 8 more |
|
|
3350
|
+
| `label` | `string` | — | Axis title.<br>Shown in: [Fix the domain and name the ticks](#axis), [Add axes for other magnitudes](#axis), [Leave out the hours nobody works](#axis) and 85 more |
|
|
3351
|
+
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Name the time zone of the instants](#axis) and 10 more |
|
|
3352
|
+
| `tickFormat` | `ValueFormat` | — | 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. Or `NumberOptions`: Intl's options in the wording's locale, compared by value; the step's places stay unless the options set digits.<br>Shown in: [Fix the domain and name the ticks](#axis), [Leave out the hours nobody works](#axis), [Bind every kind of series to a second axis](#axis) and 24 more |
|
|
3086
3353
|
| `grid` | `boolean` | only on the first axis of its orientation | Grid lines.<br>Shown in: [One vehicle through the day (StateBand)](#stateband), [Give each one a lane (StateBand)](#stateband), [See where the vans stand idle (Scenarios)](#see-where-the-vans-stand-idle) |
|
|
3087
|
-
| `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), [Mark bands, limits and cells (Chart)](#chart-1)
|
|
3354
|
+
| `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), [Values over decades](#axis), [Mark bands, limits and cells (Chart)](#chart-1) and 9 more |
|
|
3088
3355
|
| `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) |
|
|
3089
3356
|
| `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), [Tell series apart without colour (Chart)](#chart-1), [Legend above or below (Legend)](#legend) and 1 more |
|
|
3090
3357
|
|
|
@@ -3155,14 +3422,14 @@ nothing.
|
|
|
3155
3422
|
|
|
3156
3423
|
| Prop | Type | Default | Description |
|
|
3157
3424
|
|---|---|---|---|
|
|
3158
|
-
| `decimals` | `number` | — |
|
|
3425
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
3159
3426
|
|
|
3160
3427
|
#### Known limits
|
|
3161
3428
|
|
|
3162
|
-
- No logarithmic and no band scale: compute the logarithm in `value`; a category is a place on the linear scale ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
3429
|
+
- No logarithmic and no band scale: compute the logarithm in `value`; a category is a place on the linear scale ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md), [ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)).
|
|
3163
3430
|
- umriss shows the reader's time ([ADR-0060](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0060-validation-and-a-layout-editor-are-never-time-zones-not-yet.md)).
|
|
3164
3431
|
|
|
3165
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
3432
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
3166
3433
|
|
|
3167
3434
|
### Zoom and pan
|
|
3168
3435
|
|
|
@@ -3740,6 +4007,81 @@ function Course({ zoomed, onRange }: { zoomed: ChartRange | null; onRange: (rang
|
|
|
3740
4007
|
}
|
|
3741
4008
|
```
|
|
3742
4009
|
|
|
4010
|
+
##### Follow the live edge, with pause
|
|
4011
|
+
|
|
4012
|
+
`setView` moves the span and is not reported, so the chart's own following is never heard as the reader's; `setDomain` is reported, which is why it fought a wheel zoom. A zoom or a pan stops the following through `onViewChange`, and "Live" puts the view back on the newest reading.
|
|
4013
|
+
|
|
4014
|
+
```tsx
|
|
4015
|
+
import { Button } from "@umriss-ui/core";
|
|
4016
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4017
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
4018
|
+
|
|
4019
|
+
/* A reading a second, derived from the clock: the value of a second never
|
|
4020
|
+
changes, so the rows that arrived before stay where they are. */
|
|
4021
|
+
|
|
4022
|
+
/** A small LCG - the same numbers on every computer. */
|
|
4023
|
+
function random(seed: number): () => number {
|
|
4024
|
+
let s = seed >>> 0;
|
|
4025
|
+
return () => {
|
|
4026
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
4027
|
+
return s / 4294967296;
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
const SECOND = 1_000;
|
|
4032
|
+
const HOUR = 60 * 60 * SECOND;
|
|
4033
|
+
const WINDOW = 2 * HOUR;
|
|
4034
|
+
|
|
4035
|
+
/** The seed of a second, mixed so that neighbouring seconds draw unrelated
|
|
4036
|
+
numbers: the generator alone is a sawtooth from one second to the next. */
|
|
4037
|
+
function mix(n: number): number {
|
|
4038
|
+
let x = Math.imul(n ^ (n >>> 16), 0x45d9f3b);
|
|
4039
|
+
x = Math.imul(x ^ (x >>> 16), 0x45d9f3b);
|
|
4040
|
+
return (x ^ (x >>> 16)) >>> 0;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
/** The p95 latency of a second, in ms: a slow drift and a little noise. */
|
|
4044
|
+
function reading(second: number): number {
|
|
4045
|
+
const r = random(mix(second));
|
|
4046
|
+
return Math.round(240 + 40 * Math.sin(second / 900) + 12 * (r() - 0.5));
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
/** Every second of the two hours that end now. */
|
|
4050
|
+
function lastWindow(now: number): { t: number; p95: number }[] {
|
|
4051
|
+
const end = Math.floor(now / SECOND);
|
|
4052
|
+
const first = end - WINDOW / SECOND + 1;
|
|
4053
|
+
return Array.from({ length: WINDOW / SECOND }, (_, i) => ({ t: (first + i) * SECOND, p95: reading(first + i) }));
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
export default function FollowTheLiveEdge() {
|
|
4057
|
+
const [now, setNow] = useState(() => Date.now());
|
|
4058
|
+
const [following, setFollowing] = useState(true);
|
|
4059
|
+
useEffect(() => {
|
|
4060
|
+
const id = setInterval(() => setNow(Date.now()), SECOND);
|
|
4061
|
+
return () => clearInterval(id);
|
|
4062
|
+
}, []);
|
|
4063
|
+
const rows = useMemo(() => lastWindow(now), [now]);
|
|
4064
|
+
const { Chart, XAxis, YAxis, Line, setView } = useChart(rows, {
|
|
4065
|
+
// Only the reader's changes reach this: `setView` below is never reported.
|
|
4066
|
+
onViewChange: () => setFollowing(false),
|
|
4067
|
+
});
|
|
4068
|
+
useEffect(() => {
|
|
4069
|
+
if (following) setView({ domains: { x: [now - HOUR, now] } });
|
|
4070
|
+
}, [now, following, setView]);
|
|
4071
|
+
return (
|
|
4072
|
+
<>
|
|
4073
|
+
<Chart height={260} aria-label="Latency over the last hour, following the newest reading">
|
|
4074
|
+
<XAxis value="t" time zoomable />
|
|
4075
|
+
<YAxis label="ms" />
|
|
4076
|
+
<Line value="p95" name="p95" />
|
|
4077
|
+
<Tooltip mode="x" />
|
|
4078
|
+
</Chart>
|
|
4079
|
+
<Button onClick={() => setFollowing((on) => !on)}>{following ? "Pause" : "Live"}</Button>
|
|
4080
|
+
</>
|
|
4081
|
+
);
|
|
4082
|
+
}
|
|
4083
|
+
```
|
|
4084
|
+
|
|
3743
4085
|
#### Keyboard
|
|
3744
4086
|
|
|
3745
4087
|
| Key | Action |
|
|
@@ -3755,19 +4097,19 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
3755
4097
|
|
|
3756
4098
|
| Prop | Type | Default | Description |
|
|
3757
4099
|
|---|---|---|---|
|
|
3758
|
-
| `value` *required* | `AxisField``<T> \| ((d: T, index: number) => ``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), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and
|
|
4100
|
+
| `value` *required* | `AxisField``<T> \| ((d: T, index: number) => ``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), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and 93 more |
|
|
3759
4101
|
| `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), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and 12 more |
|
|
3760
4102
|
| `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), [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
3761
|
-
| `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), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and
|
|
4103
|
+
| `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), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and 42 more |
|
|
3762
4104
|
| `calendar` | `readonly ``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), [Hand over a picture (Chart)](#chart-1) |
|
|
3763
|
-
| `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`, `setView`, `setDomain`). The plain wheel always scrolls the page.<br>Shown in: [Gestures and keys](#zoom-and-pan), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and
|
|
4105
|
+
| `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`, `setView`, `setDomain`). The plain wheel always scrolls the page.<br>Shown in: [Gestures and keys](#zoom-and-pan), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and 8 more |
|
|
3764
4106
|
| `zoomLimits` | `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) |
|
|
3765
|
-
| `id` | `string` | `"x"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes (Axis)](#axis), [Align a second axis to the grid (Axis)](#axis), [Bind every kind of series to a second axis (Axis)](#axis) and
|
|
3766
|
-
| `label` | `string` | — | Axis title.<br>Shown in: [Gestures and keys](#zoom-and-pan), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and
|
|
3767
|
-
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Keep charts in step](#zoom-and-pan), [Fix the domain and name the ticks (Axis)](#axis), [Leave out the hours nobody works (Axis)](#axis) and
|
|
3768
|
-
| `tickFormat` | `ValueFormat` | — | 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. Or `NumberOptions`: Intl's options in the wording's locale, compared by value; the step's places stay unless the options set digits.<br>Shown in: [Fix the domain and name the ticks (Axis)](#axis), [Leave out the hours nobody works (Axis)](#axis), [Bind every kind of series to a second axis (Axis)](#axis) and
|
|
4107
|
+
| `id` | `string` | `"x"` | Axis id, through which series bind themselves.<br>Shown in: [Add axes for other magnitudes (Axis)](#axis), [Align a second axis to the grid (Axis)](#axis), [Bind every kind of series to a second axis (Axis)](#axis) and 8 more |
|
|
4108
|
+
| `label` | `string` | — | Axis title.<br>Shown in: [Gestures and keys](#zoom-and-pan), [Limit the zoom](#zoom-and-pan), [Fit the y axis to what is visible](#zoom-and-pan) and 85 more |
|
|
4109
|
+
| `tickCount` | `number` | — | Target value; the 1-2-5 algorithm may deviate.<br>Shown in: [Keep charts in step](#zoom-and-pan), [Fix the domain and name the ticks (Axis)](#axis), [Leave out the hours nobody works (Axis)](#axis) and 10 more |
|
|
4110
|
+
| `tickFormat` | `ValueFormat` | — | 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. Or `NumberOptions`: Intl's options in the wording's locale, compared by value; the step's places stay unless the options set digits.<br>Shown in: [Fix the domain and name the ticks (Axis)](#axis), [Leave out the hours nobody works (Axis)](#axis), [Bind every kind of series to a second axis (Axis)](#axis) and 24 more |
|
|
3769
4111
|
| `grid` | `boolean` | only on the first axis of its orientation | Grid lines.<br>Shown in: [One vehicle through the day (StateBand)](#stateband), [Give each one a lane (StateBand)](#stateband), [See where the vans stand idle (Scenarios)](#see-where-the-vans-stand-idle) |
|
|
3770
|
-
| `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), [
|
|
4112
|
+
| `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), [Values over decades (Axis)](#axis), [Mark bands, limits and cells (Chart)](#chart-1) and 9 more |
|
|
3771
4113
|
| `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) |
|
|
3772
4114
|
| `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), [Tell series apart without colour (Chart)](#chart-1), [Legend above or below (Legend)](#legend) and 1 more |
|
|
3773
4115
|
|
|
@@ -3781,7 +4123,7 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
3781
4123
|
|
|
3782
4124
|
| Prop | Type | Default | Description |
|
|
3783
4125
|
|---|---|---|---|
|
|
3784
|
-
| `onViewChange` | `(view: `[`ChartView`](#view)`) => void` | — | Every change the reader makes, once, always the whole view - the one to keep, or to hand another chart's `setView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with, nor for one `setView` applies.<br>Shown in: [Keep charts in step](#zoom-and-pan), [Read it by keyboard and screen reader (Chart)](#chart-1) |
|
|
4126
|
+
| `onViewChange` | `(view: `[`ChartView`](#view)`) => void` | — | Every change the reader makes, once, always the whole view - the one to keep, or to hand another chart's `setView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with, nor for one `setView` applies.<br>Shown in: [Keep charts in step](#zoom-and-pan), [Follow the live edge, with pause](#zoom-and-pan), [Read it by keyboard and screen reader (Chart)](#chart-1) |
|
|
3785
4127
|
| `onSelect` | `(hit: ``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), [Drill down from a year to its days (Tooltip)](#tooltip) |
|
|
3786
4128
|
| `onRange` | `(range: ``ChartRange``<V>) => void` | — | The reader marked a stretch of an x axis: Alt with a drag where the chart zooms, a plain drag where it does not. Called once, on release - once per x axis -, with `from` not after `to`, in the axis' own values: numbers, milliseconds on a time axis, wall-clock instants on a calendar axis, and on a category axis the first and last category covered, as strings - say so in the handler's parameter, `ChartRange<string>`. A band covers the stretch while the pointer is down; the chart keeps no range and draws none afterwards, and it does not travel over `syncId`. A press that travels 4 px or less is a click and reaches `onSelect`; Escape and a cancelled pointer report nothing.<br>Shown in: [Mark a range](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
|
|
3787
4129
|
|
|
@@ -3877,7 +4219,7 @@ nothing.
|
|
|
3877
4219
|
|
|
3878
4220
|
| Prop | Type | Default | Description |
|
|
3879
4221
|
|---|---|---|---|
|
|
3880
|
-
| `decimals` | `number` | — |
|
|
4222
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
3881
4223
|
|
|
3882
4224
|
###### `TooltipPoint<T = unknown>`
|
|
3883
4225
|
|
|
@@ -3891,6 +4233,7 @@ One hit per series in the tooltip.
|
|
|
3891
4233
|
| `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), [Choose a hit (Tooltip)](#tooltip) |
|
|
3892
4234
|
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself (Tooltip)](#tooltip), [Drill down from a year to its days (Tooltip)](#tooltip) |
|
|
3893
4235
|
| `index` *required* | `number` | — | |
|
|
4236
|
+
| `baseline` | `number` | — | The lower edge, where the series has a baseline of its own - a `Bar` or an `Area` outside a stack; `yValue` is the upper one. A stack's foot is no reading and never stands here. A field of its own, as `level` and `box` have ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). |
|
|
3894
4237
|
| `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. |
|
|
3895
4238
|
| `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. |
|
|
3896
4239
|
| `box` | `BoxNumbers`` & Partial<``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)). |
|
|
@@ -3921,7 +4264,7 @@ The optional numbers of a box by name.
|
|
|
3921
4264
|
|
|
3922
4265
|
- No zoom along y, no rectangle zoom and no navigator: the x span is the one zoom there is.
|
|
3923
4266
|
|
|
3924
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
4267
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
3925
4268
|
|
|
3926
4269
|
### View
|
|
3927
4270
|
|
|
@@ -4116,7 +4459,7 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
4116
4459
|
|
|
4117
4460
|
| Prop | Type | Default | Description |
|
|
4118
4461
|
|---|---|---|---|
|
|
4119
|
-
| `onViewChange` | `(view: ``ChartView``) => void` | — | Every change the reader makes, once, always the whole view - the one to keep, or to hand another chart's `setView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with, nor for one `setView` applies.<br>Shown in: [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Read it by keyboard and screen reader (Chart)](#chart-1) |
|
|
4462
|
+
| `onViewChange` | `(view: ``ChartView``) => void` | — | Every change the reader makes, once, always the whole view - the one to keep, or to hand another chart's `setView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with, nor for one `setView` applies.<br>Shown in: [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Follow the live edge, with pause (Zoom and pan)](#zoom-and-pan), [Read it by keyboard and screen reader (Chart)](#chart-1) |
|
|
4120
4463
|
| `onSelect` | `(hit: ``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), [Drill down from a year to its days (Tooltip)](#tooltip) |
|
|
4121
4464
|
| `onRange` | `(range: ``ChartRange``<V>) => void` | — | The reader marked a stretch of an x axis: Alt with a drag where the chart zooms, a plain drag where it does not. Called once, on release - once per x axis -, with `from` not after `to`, in the axis' own values: numbers, milliseconds on a time axis, wall-clock instants on a calendar axis, and on a category axis the first and last category covered, as strings - say so in the handler's parameter, `ChartRange<string>`. A band covers the stretch while the pointer is down; the chart keeps no range and draws none afterwards, and it does not travel over `syncId`. A press that travels 4 px or less is a click and reaches `onSelect`; Escape and a cancelled pointer report nothing.<br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
|
|
4122
4465
|
|
|
@@ -4124,7 +4467,7 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
4124
4467
|
|
|
4125
4468
|
| Prop | Type | Default | Description |
|
|
4126
4469
|
|---|---|---|---|
|
|
4127
|
-
| `domains` | `AxisSpans` | — | The span each zoomable x axis shows, by axis `id` - a lone x axis is `"x"`. An axis absent here shows its own `domain`. An id that names no zoomable x axis falls out.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#zoom-and-pan), [Mark a range (Zoom and pan)](#zoom-and-pan) |
|
|
4470
|
+
| `domains` | `AxisSpans` | — | The span each zoomable x axis shows, by axis `id` - a lone x axis is `"x"`. An axis absent here shows its own `domain`. An id that names no zoomable x axis falls out.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#zoom-and-pan), [Mark a range (Zoom and pan)](#zoom-and-pan), [Follow the live edge, with pause (Zoom and pan)](#zoom-and-pan) |
|
|
4128
4471
|
| `hidden` | `readonly string[]` | — | The hidden series, by `name`; a series without one cannot be hidden. A name no series carries falls out.<br>Shown in: [Hide a series from the legend (Legend)](#legend) |
|
|
4129
4472
|
|
|
4130
4473
|
##### Types on this page
|
|
@@ -4174,6 +4517,7 @@ One hit per series in the tooltip.
|
|
|
4174
4517
|
| `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), [Choose a hit (Tooltip)](#tooltip) |
|
|
4175
4518
|
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself (Tooltip)](#tooltip), [Drill down from a year to its days (Tooltip)](#tooltip) |
|
|
4176
4519
|
| `index` *required* | `number` | — | |
|
|
4520
|
+
| `baseline` | `number` | — | The lower edge, where the series has a baseline of its own - a `Bar` or an `Area` outside a stack; `yValue` is the upper one. A stack's foot is no reading and never stands here. A field of its own, as `level` and `box` have ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). |
|
|
4177
4521
|
| `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. |
|
|
4178
4522
|
| `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. |
|
|
4179
4523
|
| `box` | `BoxNumbers`` & Partial<``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)). |
|
|
@@ -4204,7 +4548,7 @@ The optional numbers of a box by name.
|
|
|
4204
4548
|
|
|
4205
4549
|
- 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.
|
|
4206
4550
|
|
|
4207
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
4551
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
4208
4552
|
|
|
4209
4553
|
## Series
|
|
4210
4554
|
|
|
@@ -4657,6 +5001,72 @@ export default function BurnDown() {
|
|
|
4657
5001
|
}
|
|
4658
5002
|
```
|
|
4659
5003
|
|
|
5004
|
+
##### A silence is a gap
|
|
5005
|
+
|
|
5006
|
+
`maxGap` is the longest step a line is drawn through; the half hour the historian sent nothing is a hole, not a steady climb.
|
|
5007
|
+
|
|
5008
|
+
```tsx
|
|
5009
|
+
import { Text } from "@umriss-ui/core";
|
|
5010
|
+
import { MINUTE, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5011
|
+
|
|
5012
|
+
/* A historian logs a temperature every minute; it sent nothing from 10:12 to 10:47. */
|
|
5013
|
+
const at = (hours: number, minutes: number) => new Date(2026, 2, 17, hours, minutes).getTime();
|
|
5014
|
+
const READINGS: { t: number; temperature: number }[] = [];
|
|
5015
|
+
for (let t = at(9, 30); t <= at(11, 30); t += MINUTE) {
|
|
5016
|
+
if (t > at(10, 12) && t < at(10, 47)) continue;
|
|
5017
|
+
READINGS.push({ t, temperature: Math.round((74 + 4 * Math.sin(t / (9 * MINUTE)) + (t > at(10, 47) ? 3 : 0)) * 10) / 10 });
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
function Bridged() {
|
|
5021
|
+
const { Chart, XAxis, YAxis, Line } = useChart(READINGS);
|
|
5022
|
+
return (
|
|
5023
|
+
<Chart height={200} aria-label="Reactor temperature, the outage bridged by a straight stroke">
|
|
5024
|
+
<XAxis value="t" time />
|
|
5025
|
+
<YAxis label="°C" domain={[68, 84]} />
|
|
5026
|
+
<Line value="temperature" name="Temperature" markers="never" />
|
|
5027
|
+
<Tooltip />
|
|
5028
|
+
</Chart>
|
|
5029
|
+
);
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
function Broken() {
|
|
5033
|
+
const { Chart, XAxis, YAxis, Line } = useChart(READINGS);
|
|
5034
|
+
return (
|
|
5035
|
+
<Chart height={200} aria-label="Reactor temperature, the outage left as a gap">
|
|
5036
|
+
<XAxis value="t" time />
|
|
5037
|
+
<YAxis label="°C" domain={[68, 84]} />
|
|
5038
|
+
<Line value="temperature" name="Temperature" markers="never" maxGap={5 * MINUTE} />
|
|
5039
|
+
<Tooltip />
|
|
5040
|
+
</Chart>
|
|
5041
|
+
);
|
|
5042
|
+
}
|
|
5043
|
+
|
|
5044
|
+
function Filled() {
|
|
5045
|
+
const { Chart, XAxis, YAxis, Area } = useChart(READINGS);
|
|
5046
|
+
return (
|
|
5047
|
+
<Chart height={200} aria-label="Reactor temperature as an area, closed on both sides of the outage">
|
|
5048
|
+
<XAxis value="t" time />
|
|
5049
|
+
<YAxis label="°C" domain={[60, 84]} />
|
|
5050
|
+
<Area value="temperature" name="Temperature" maxGap={5 * MINUTE} />
|
|
5051
|
+
<Tooltip />
|
|
5052
|
+
</Chart>
|
|
5053
|
+
);
|
|
5054
|
+
}
|
|
5055
|
+
|
|
5056
|
+
export default function Outage() {
|
|
5057
|
+
return (
|
|
5058
|
+
<>
|
|
5059
|
+
<Text as="p">Without maxGap</Text>
|
|
5060
|
+
<Bridged />
|
|
5061
|
+
<Text as="p">With maxGap of five minutes</Text>
|
|
5062
|
+
<Broken />
|
|
5063
|
+
<Text as="p">An area closes on both sides of the gap</Text>
|
|
5064
|
+
<Filled />
|
|
5065
|
+
</>
|
|
5066
|
+
);
|
|
5067
|
+
}
|
|
5068
|
+
```
|
|
5069
|
+
|
|
4660
5070
|
#### When to use something else
|
|
4661
5071
|
|
|
4662
5072
|
- A quantity read from its foot → Area
|
|
@@ -4672,18 +5082,19 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
4672
5082
|
|
|
4673
5083
|
| Prop | Type | Default | Description |
|
|
4674
5084
|
|---|---|---|---|
|
|
4675
|
-
| `value` *required* | `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), [Several series and a gap](#line), [Hold a value until it changes](#line) and
|
|
5085
|
+
| `value` *required* | `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), [Several series and a gap](#line), [Hold a value until it changes](#line) and 58 more |
|
|
4676
5086
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Add axes for other magnitudes (Axis)](#axis), [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
4677
|
-
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Hold a value until it changes](#line), [Add axes for other magnitudes (Axis)](#axis), [Align a second axis to the grid (Axis)](#axis) and
|
|
4678
|
-
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Several series and a gap](#line), [Hold a value until it changes](#line), [Tell series apart without colour (Chart)](#chart-1) and
|
|
4679
|
-
| `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), [Several series and a gap](#line), [Hold a value until it changes](#line) and
|
|
4680
|
-
| `format` | `ValueFormat` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it, as a function or `NumberOptions`.<br>Shown in: [Write each value in its unit (Tooltip)](#tooltip), [Show the values as a table (Tooltip)](#tooltip)
|
|
4681
|
-
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value.<br>Shown in: [Compare progress with a plan](#line), [Fix the domain and name the ticks (Axis)](#axis), [
|
|
5087
|
+
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Hold a value until it changes](#line), [Add axes for other magnitudes (Axis)](#axis), [Align a second axis to the grid (Axis)](#axis) and 5 more |
|
|
5088
|
+
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Several series and a gap](#line), [Hold a value until it changes](#line), [Tell series apart without colour (Chart)](#chart-1) and 9 more |
|
|
5089
|
+
| `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), [Several series and a gap](#line), [Hold a value until it changes](#line) and 58 more |
|
|
5090
|
+
| `format` | `ValueFormat` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it, as a function or `NumberOptions`.<br>Shown in: [Values over decades (Axis)](#axis), [Write each value in its unit (Tooltip)](#tooltip), [Show the values as a table (Tooltip)](#tooltip) and 2 more |
|
|
5091
|
+
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value.<br>Shown in: [Compare progress with a plan](#line), [Fix the domain and name the ticks (Axis)](#axis), [A trend with pens (Axis)](#axis) and 2 more |
|
|
4682
5092
|
| `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)](#compare-the-budget-with-the-forecast) |
|
|
4683
|
-
| `strokeWidth` | `number` | `1.5` | Line width in CSS pixels.<br>Shown in: [Hold a value until it changes](#line), [Compare progress with a plan](#line), [Mix series kinds (Chart)](#chart-1) and
|
|
4684
|
-
| `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), [Compare progress with a plan](#line), [Add axes for other magnitudes (Axis)](#axis) and
|
|
4685
|
-
| `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), [Draw a series per group of long rows (Chart)](#chart-1)
|
|
5093
|
+
| `strokeWidth` | `number` | `1.5` | Line width in CSS pixels.<br>Shown in: [Hold a value until it changes](#line), [Compare progress with a plan](#line), [Mix series kinds (Chart)](#chart-1) and 10 more |
|
|
5094
|
+
| `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), [Compare progress with a plan](#line), [Add axes for other magnitudes (Axis)](#axis) and 2 more |
|
|
5095
|
+
| `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), [A silence is a gap](#line), [Draw a series per group of long rows (Chart)](#chart-1) and 7 more |
|
|
4686
5096
|
| `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) |
|
|
5097
|
+
| `maxGap` | `number` | no limit, a stretch without rows is bridged | The longest step between two readings that is still drawn through, in the x axis' units: milliseconds on a time axis, working time on a calendar axis, places on a category axis. A longer step is a gap: the line breaks and a `step` hold ends `maxGap` after its reading. A pointer farther than this from the nearest reading finds no hit on the series.<br>Shown in: [A silence is a gap](#line) |
|
|
4687
5098
|
|
|
4688
5099
|
##### Types on this page
|
|
4689
5100
|
|
|
@@ -4738,13 +5149,13 @@ nothing.
|
|
|
4738
5149
|
|
|
4739
5150
|
| Prop | Type | Default | Description |
|
|
4740
5151
|
|---|---|---|---|
|
|
4741
|
-
| `decimals` | `number` | — |
|
|
5152
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
4742
5153
|
|
|
4743
5154
|
#### Known limits
|
|
4744
5155
|
|
|
4745
|
-
- No smoothing: a curve between two readings would invent values nobody measured ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
5156
|
+
- No smoothing: a curve between two readings would invent values nobody measured ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md)).
|
|
4746
5157
|
|
|
4747
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
5158
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
4748
5159
|
|
|
4749
5160
|
### Area
|
|
4750
5161
|
|
|
@@ -5148,12 +5559,12 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
5148
5559
|
|
|
5149
5560
|
| Prop | Type | Default | Description |
|
|
5150
5561
|
|---|---|---|---|
|
|
5151
|
-
| `value` *required* | `Value``<T>` | — | Upper edge - a number field of the row or a function of it; null/undefined/NaN/±Infinity means a gap.<br>Shown in: [Fill down to zero](#area), [Fill a range between two edges](#area), [Stack areas](#area) and
|
|
5152
|
-
| `baseline` | `Value``<T>` | a fixed baseline at 0 | Lower edge - a number field of the row or a function of it. In a `stack` the stack below is the lower edge, and this is not read.<br>Shown in: [Fill a range between two edges](#area), [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1) |
|
|
5562
|
+
| `value` *required* | `Value``<T>` | — | Upper edge - a number field of the row or a function of it; null/undefined/NaN/±Infinity means a gap.<br>Shown in: [Fill down to zero](#area), [Fill a range between two edges](#area), [Stack areas](#area) and 7 more |
|
|
5563
|
+
| `baseline` | `Value``<T>` | a fixed baseline at 0 | Lower edge - a number field of the row or a function of it. In a `stack` the stack below is the lower edge, and this is not read.<br>Shown in: [Fill a range between two edges](#area), [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1) and 1 more |
|
|
5153
5564
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5154
5565
|
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5155
|
-
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Fill a range between two edges](#area), [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5156
|
-
| `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: [Fill down to zero](#area), [Fill a range between two edges](#area), [Stack areas](#area) and
|
|
5566
|
+
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Fill a range between two edges](#area), [Bind every kind of series to a second axis (Axis)](#axis), [A fit line with its band (Scatter)](#scatter) |
|
|
5567
|
+
| `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: [Fill down to zero](#area), [Fill a range between two edges](#area), [Stack areas](#area) and 7 more |
|
|
5157
5568
|
| `format` | `ValueFormat` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it, as a function or `NumberOptions`.<br>Shown in: [Show shares of the whole over time](#area) |
|
|
5158
5569
|
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value. |
|
|
5159
5570
|
| `tone` | `"ok" \| "warning" \| "alarm"` | — | A role instead of a colour value; the theme resolves it. `color` beats it. |
|
|
@@ -5162,6 +5573,7 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
5162
5573
|
| `fillOpacity` | `number` | `0.18` | Opacity of the fill, from 0 to 1; the outline stays fully opaque.<br>Shown in: [Fill down to zero](#area), [Stack areas](#area), [Show shares of the whole over time](#area) |
|
|
5163
5574
|
| `strokeWidth` | `number` | `1.5` | Width of the outline along the upper edge in CSS pixels; 0 leaves it out.<br>Shown in: [Fill down to zero](#area), [Fill a range between two edges](#area), [Stack areas](#area) and 2 more |
|
|
5164
5575
|
| `dash` | `readonly number[]` | a solid outline | Dash pattern of the outline as a run of lengths in CSS pixels. The fill stays whole.<br>Shown in: [Fill a range between two edges](#area) |
|
|
5576
|
+
| `maxGap` | `number` | no limit, a stretch without rows is bridged | The longest step between two readings that is still filled through, in the x axis' units: milliseconds on a time axis, working time on a calendar axis, places on a category axis. A longer step is a gap: the area closes on both sides of it. A pointer farther than this from the nearest reading finds no hit. In a `stack` this is not read.<br>Shown in: [A silence is a gap (Line)](#line) |
|
|
5165
5577
|
|
|
5166
5578
|
##### Types on this page
|
|
5167
5579
|
|
|
@@ -5216,7 +5628,7 @@ nothing.
|
|
|
5216
5628
|
|
|
5217
5629
|
| Prop | Type | Default | Description |
|
|
5218
5630
|
|---|---|---|---|
|
|
5219
|
-
| `decimals` | `number` | — |
|
|
5631
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
5220
5632
|
|
|
5221
5633
|
### Bar
|
|
5222
5634
|
|
|
@@ -5226,7 +5638,7 @@ Bars along the x axis (a bar or column chart): one per category, grouped side by
|
|
|
5226
5638
|
import { useChart, bin } from "@umriss-ui/charts";
|
|
5227
5639
|
```
|
|
5228
5640
|
|
|
5229
|
-
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.
|
|
5641
|
+
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; a `baseline` floats it from one value to another, as in a waterfall. `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.
|
|
5230
5642
|
|
|
5231
5643
|
#### Examples
|
|
5232
5644
|
|
|
@@ -5483,6 +5895,141 @@ export default function MarkARangeOfBars() {
|
|
|
5483
5895
|
}
|
|
5484
5896
|
```
|
|
5485
5897
|
|
|
5898
|
+
##### A waterfall
|
|
5899
|
+
|
|
5900
|
+
Keep a running sum beside the changes, give `value` where each step ends and `baseline` where it began: the start and end totals stand on zero, the steps float, and no helper series exists to leak into the tooltip.
|
|
5901
|
+
|
|
5902
|
+
```tsx
|
|
5903
|
+
import { DataTable, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5904
|
+
|
|
5905
|
+
/* The revenue bridge of a half year in €k: the opening total, what moved it, the closing total. */
|
|
5906
|
+
const CHANGES = [
|
|
5907
|
+
{ step: "New customers", change: 310 },
|
|
5908
|
+
{ step: "Upsell", change: 140 },
|
|
5909
|
+
{ step: "Churn", change: -220 },
|
|
5910
|
+
{ step: "Price cut", change: -90 },
|
|
5911
|
+
{ step: "Renewals", change: 180 },
|
|
5912
|
+
];
|
|
5913
|
+
|
|
5914
|
+
const START = 1200;
|
|
5915
|
+
|
|
5916
|
+
/** The running sum: each step begins where the one before ended. */
|
|
5917
|
+
const BRIDGE = (() => {
|
|
5918
|
+
let sum = START;
|
|
5919
|
+
const steps = CHANGES.map(({ step, change }) => {
|
|
5920
|
+
const from = sum;
|
|
5921
|
+
sum += change;
|
|
5922
|
+
return { step, from, to: sum };
|
|
5923
|
+
});
|
|
5924
|
+
return [{ step: "Start", from: 0, to: START }, ...steps, { step: "End", from: 0, to: sum }];
|
|
5925
|
+
})();
|
|
5926
|
+
|
|
5927
|
+
export default function Waterfall() {
|
|
5928
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(BRIDGE);
|
|
5929
|
+
return (
|
|
5930
|
+
<Chart height={280} aria-label="Revenue from the opening to the closing total, step by step">
|
|
5931
|
+
<XAxis value="step" label="Step" />
|
|
5932
|
+
<YAxis label="€k" />
|
|
5933
|
+
<Bar value="to" baseline="from" name="Revenue" />
|
|
5934
|
+
<Tooltip mode="x" />
|
|
5935
|
+
<DataTable />
|
|
5936
|
+
</Chart>
|
|
5937
|
+
);
|
|
5938
|
+
}
|
|
5939
|
+
```
|
|
5940
|
+
|
|
5941
|
+
##### Error bars
|
|
5942
|
+
|
|
5943
|
+
A mean per batch with its 95 % interval: a thin floating `Bar` runs from the lower bound to the upper, a `Scatter` marks the mean on it. A confidence band along a course is an `Area` with a `baseline`, and range bars, a minimum to a maximum per hour, are the same bar.
|
|
5944
|
+
|
|
5945
|
+
```tsx
|
|
5946
|
+
import { DataTable, Tooltip, useChart } from "@umriss-ui/charts";
|
|
5947
|
+
|
|
5948
|
+
/* Eight readings of the output in mm per batch, as the line sends them. */
|
|
5949
|
+
const BATCHES = [
|
|
5950
|
+
[50.2, 49.1, 51.0, 50.4, 48.8, 49.9, 50.7, 49.5],
|
|
5951
|
+
[51.3, 52.0, 50.6, 51.8, 50.9, 52.4, 51.1, 51.6],
|
|
5952
|
+
[48.9, 49.6, 49.2, 48.4, 50.1, 49.0, 48.7, 49.8],
|
|
5953
|
+
[50.8, 51.5, 50.3, 51.0, 52.1, 50.5, 51.2, 50.9],
|
|
5954
|
+
[47.6, 48.3, 48.9, 47.9, 49.2, 48.1, 47.4, 48.6],
|
|
5955
|
+
[50.0, 49.4, 50.6, 49.8, 50.3, 49.1, 50.9, 49.7],
|
|
5956
|
+
[52.6, 51.9, 52.8, 53.4, 52.2, 51.7, 53.1, 52.5],
|
|
5957
|
+
[49.3, 50.1, 48.8, 49.6, 50.4, 49.0, 49.9, 50.2],
|
|
5958
|
+
[50.5, 51.2, 49.8, 50.9, 51.6, 50.1, 51.0, 50.3],
|
|
5959
|
+
[48.2, 47.8, 49.1, 48.5, 47.6, 48.9, 48.0, 48.4],
|
|
5960
|
+
[51.9, 51.1, 52.3, 51.4, 50.8, 52.0, 51.6, 51.2],
|
|
5961
|
+
[49.7, 50.5, 49.2, 50.0, 49.5, 50.8, 49.9, 50.2],
|
|
5962
|
+
];
|
|
5963
|
+
|
|
5964
|
+
/* The two-sided 95 % point of Student's t with seven degrees of freedom: eight readings per batch. */
|
|
5965
|
+
const T95 = 2.365;
|
|
5966
|
+
|
|
5967
|
+
/** The mean of a batch and its interval: the mean plus and minus t times the standard error. */
|
|
5968
|
+
function interval(batch: number, readings: readonly number[]) {
|
|
5969
|
+
const n = readings.length;
|
|
5970
|
+
const mean = readings.reduce((sum, x) => sum + x, 0) / n;
|
|
5971
|
+
const variance = readings.reduce((sum, x) => sum + (x - mean) ** 2, 0) / (n - 1);
|
|
5972
|
+
const half = T95 * Math.sqrt(variance / n);
|
|
5973
|
+
return { batch, mean, lower: mean - half, upper: mean + half };
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5976
|
+
const STATS = BATCHES.map((readings, i) => interval(i + 1, readings));
|
|
5977
|
+
|
|
5978
|
+
export default function ErrorBars() {
|
|
5979
|
+
const { Chart, XAxis, YAxis, Bar, Scatter } = useChart(STATS);
|
|
5980
|
+
return (
|
|
5981
|
+
<Chart height={280} aria-label="Mean output per batch with its 95 % interval">
|
|
5982
|
+
<XAxis value="batch" label="Batch" />
|
|
5983
|
+
<YAxis label="mm" />
|
|
5984
|
+
<Bar value="upper" baseline="lower" barWidth={0.08} name="95 % interval" />
|
|
5985
|
+
<Scatter value="mean" name="Mean" />
|
|
5986
|
+
<Tooltip mode="x" />
|
|
5987
|
+
<DataTable />
|
|
5988
|
+
</Chart>
|
|
5989
|
+
);
|
|
5990
|
+
}
|
|
5991
|
+
```
|
|
5992
|
+
|
|
5993
|
+
##### Per month
|
|
5994
|
+
|
|
5995
|
+
Daily readings summed into months by the application's own `perMonth()`, and drawn as one bar per month on `Date` rows. The chart draws the sums it is given: whether a bar is a sum, a count or a mean, and what it counts, is the application's to decide, not the chart's.
|
|
5996
|
+
|
|
5997
|
+
```tsx
|
|
5998
|
+
import { useMemo } from "react";
|
|
5999
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
6000
|
+
|
|
6001
|
+
/* Units shipped per day, first half of 2026, as the warehouse exports them. */
|
|
6002
|
+
const DAYS = Array.from({ length: 181 }, (_, i) => ({
|
|
6003
|
+
day: new Date(2026, 0, 1 + i),
|
|
6004
|
+
units: Math.round(40 + 12 * Math.sin(i / 9) + ((i * 37) % 11)),
|
|
6005
|
+
}));
|
|
6006
|
+
|
|
6007
|
+
/** Each month's sum, placed at the month's first instant. */
|
|
6008
|
+
function perMonth(days: readonly { day: Date; units: number }[]): { month: Date; units: number }[] {
|
|
6009
|
+
const sums = new Map<number, { month: Date; units: number }>();
|
|
6010
|
+
for (const { day, units } of days) {
|
|
6011
|
+
const month = new Date(day.getFullYear(), day.getMonth(), 1);
|
|
6012
|
+
const row = sums.get(month.getTime()) ?? { month, units: 0 };
|
|
6013
|
+
row.units += units;
|
|
6014
|
+
sums.set(month.getTime(), row);
|
|
6015
|
+
}
|
|
6016
|
+
return [...sums.values()];
|
|
6017
|
+
}
|
|
6018
|
+
|
|
6019
|
+
export default function PerMonth() {
|
|
6020
|
+
const months = useMemo(() => perMonth(DAYS), []);
|
|
6021
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(months);
|
|
6022
|
+
return (
|
|
6023
|
+
<Chart height={260} aria-label="Units shipped per month in the first half of 2026">
|
|
6024
|
+
<XAxis value="month" />
|
|
6025
|
+
<YAxis label="Units" />
|
|
6026
|
+
<Bar value="units" name="Units" />
|
|
6027
|
+
<Tooltip mode="x" />
|
|
6028
|
+
</Chart>
|
|
6029
|
+
);
|
|
6030
|
+
}
|
|
6031
|
+
```
|
|
6032
|
+
|
|
5486
6033
|
#### When to use something else
|
|
5487
6034
|
|
|
5488
6035
|
- Causes ranked by how much they contribute → Pareto
|
|
@@ -5497,17 +6044,18 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
5497
6044
|
|
|
5498
6045
|
| Prop | Type | Default | Description |
|
|
5499
6046
|
|---|---|---|---|
|
|
5500
|
-
| `value` *required* | `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), [Group bars side by side](#bar), [Show bars above and below zero](#bar) and
|
|
6047
|
+
| `value` *required* | `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), [Group bars side by side](#bar), [Show bars above and below zero](#bar) and 20 more |
|
|
6048
|
+
| `baseline` | `Value``<T>` | a fixed baseline at 0 | Lower edge - a number field of the row or a function of it: the bar floats from here to `value`, either way up. A gap in either edge leaves the bar out. In a `stack` the stack below is the lower edge, and this is not read.<br>Shown in: [A waterfall](#bar), [Error bars](#bar) |
|
|
5501
6049
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5502
6050
|
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5503
6051
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5504
|
-
| `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), [Group bars side by side](#bar), [Show bars above and below zero](#bar) and
|
|
6052
|
+
| `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), [Group bars side by side](#bar), [Show bars above and below zero](#bar) and 20 more |
|
|
5505
6053
|
| `format` | `ValueFormat` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it, as a function or `NumberOptions`.<br>Shown in: [Show shares of a whole](#bar), [Compare the budget with the forecast (Scenarios)](#compare-the-budget-with-the-forecast) |
|
|
5506
6054
|
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value.<br>Shown in: [Group bars side by side](#bar), [See where the vans stand idle (Scenarios)](#see-where-the-vans-stand-idle), [Compare the budget with the forecast (Scenarios)](#compare-the-budget-with-the-forecast) |
|
|
5507
6055
|
| `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)](#compare-the-budget-with-the-forecast) |
|
|
5508
6056
|
| `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), [Show shares of a whole](#bar), [Read a stack's total in the table (Tooltip)](#tooltip) and 1 more |
|
|
5509
6057
|
| `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) |
|
|
5510
|
-
| `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), [Count readings into a histogram](#bar), [
|
|
6058
|
+
| `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), [Count readings into a histogram](#bar), [Error bars](#bar) and 6 more |
|
|
5511
6059
|
|
|
5512
6060
|
##### Types on this page
|
|
5513
6061
|
|
|
@@ -5562,13 +6110,13 @@ nothing.
|
|
|
5562
6110
|
|
|
5563
6111
|
| Prop | Type | Default | Description |
|
|
5564
6112
|
|---|---|---|---|
|
|
5565
|
-
| `decimals` | `number` | — |
|
|
6113
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
5566
6114
|
|
|
5567
6115
|
#### Known limits
|
|
5568
6116
|
|
|
5569
6117
|
- Horizontal bars are the next kind to come ([ADR-0059](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0059-the-set-of-chart-kinds-is-open.md)); until then a `Matrix` or a `Table` ranks by name.
|
|
5570
6118
|
|
|
5571
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
6119
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
5572
6120
|
|
|
5573
6121
|
### Scatter
|
|
5574
6122
|
|
|
@@ -5818,6 +6366,138 @@ export default function AgainstATarget() {
|
|
|
5818
6366
|
}
|
|
5819
6367
|
```
|
|
5820
6368
|
|
|
6369
|
+
##### A fit line with its band
|
|
6370
|
+
|
|
6371
|
+
A regression is a model the analyst chooses - which fit, which band - so it is the application's code, not the chart's: here an ordinary least-squares line, and its 95 % band of the fitted mean from the standard error at each x, by the normal approximation (1.96 standard errors). The chart draws the rows it is given.
|
|
6372
|
+
|
|
6373
|
+
```tsx
|
|
6374
|
+
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
6375
|
+
|
|
6376
|
+
/* Data from the data scientist's world, written out here so the example runs on its own. */
|
|
6377
|
+
/** A small LCG - the same numbers on every computer. */
|
|
6378
|
+
function random(seed: number): () => number {
|
|
6379
|
+
let s = seed >>> 0;
|
|
6380
|
+
return () => {
|
|
6381
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
6382
|
+
return s / 4294967296;
|
|
6383
|
+
};
|
|
6384
|
+
}
|
|
6385
|
+
|
|
6386
|
+
interface Reading {
|
|
6387
|
+
x: number;
|
|
6388
|
+
y: number;
|
|
6389
|
+
}
|
|
6390
|
+
|
|
6391
|
+
/** Sixty measurements along a rising course, with a bell-shaped scatter about it (Box-Muller). */
|
|
6392
|
+
const READINGS: readonly Reading[] = (() => {
|
|
6393
|
+
const r = random(7);
|
|
6394
|
+
return Array.from({ length: 60 }, () => {
|
|
6395
|
+
const x = Math.round(r() * 100) / 10;
|
|
6396
|
+
const noise = Math.sqrt(-2 * Math.log(Math.max(r(), 1e-12))) * Math.cos(2 * Math.PI * r());
|
|
6397
|
+
return { x, y: Math.round((2 + 0.8 * x + 1.2 * noise) * 100) / 100 };
|
|
6398
|
+
});
|
|
6399
|
+
})();
|
|
6400
|
+
|
|
6401
|
+
/* Ordinary least squares, and the standard error of the fitted mean at each x. */
|
|
6402
|
+
const n = READINGS.length;
|
|
6403
|
+
const mx = READINGS.reduce((sum, d) => sum + d.x, 0) / n;
|
|
6404
|
+
const my = READINGS.reduce((sum, d) => sum + d.y, 0) / n;
|
|
6405
|
+
const sxx = READINGS.reduce((sum, d) => sum + (d.x - mx) ** 2, 0);
|
|
6406
|
+
const slope = READINGS.reduce((sum, d) => sum + (d.x - mx) * (d.y - my), 0) / sxx;
|
|
6407
|
+
const intercept = my - slope * mx;
|
|
6408
|
+
const sigma = Math.sqrt(READINGS.reduce((sum, d) => sum + (d.y - intercept - slope * d.x) ** 2, 0) / (n - 2));
|
|
6409
|
+
/** 95 % under the normal approximation. */
|
|
6410
|
+
const Z = 1.96;
|
|
6411
|
+
|
|
6412
|
+
interface Fit {
|
|
6413
|
+
x: number;
|
|
6414
|
+
fit: number;
|
|
6415
|
+
lower: number;
|
|
6416
|
+
upper: number;
|
|
6417
|
+
}
|
|
6418
|
+
|
|
6419
|
+
const GRID: readonly Fit[] = Array.from({ length: 41 }, (_, i) => {
|
|
6420
|
+
const x = (i / 40) * 10;
|
|
6421
|
+
const fit = intercept + slope * x;
|
|
6422
|
+
const se = sigma * Math.sqrt(1 / n + (x - mx) ** 2 / sxx);
|
|
6423
|
+
return { x, fit, lower: fit - Z * se, upper: fit + Z * se };
|
|
6424
|
+
});
|
|
6425
|
+
|
|
6426
|
+
export default function FitLineWithBand() {
|
|
6427
|
+
const { Chart, XAxis, YAxis, Scatter, Line, Area } = useChart(READINGS);
|
|
6428
|
+
return (
|
|
6429
|
+
<Chart height={300} aria-label="Measurements with their least-squares fit and 95 per cent band">
|
|
6430
|
+
<XAxis value="x" label="x" />
|
|
6431
|
+
<YAxis label="y" />
|
|
6432
|
+
<Area data={GRID} value="upper" baseline="lower" name="95 % band" />
|
|
6433
|
+
<Scatter value="y" name="Measurement" radius={2.5} />
|
|
6434
|
+
<Line data={GRID} value="fit" name="Fit" strokeWidth={1.75} markers="never" />
|
|
6435
|
+
<Legend placement="top" />
|
|
6436
|
+
<Tooltip mode="nearest" />
|
|
6437
|
+
</Chart>
|
|
6438
|
+
);
|
|
6439
|
+
}
|
|
6440
|
+
```
|
|
6441
|
+
|
|
6442
|
+
##### Where the points crowd
|
|
6443
|
+
|
|
6444
|
+
Count the points into cells and draw the counts as a `Matrix`, a 2D histogram as [`bin`](#api-index-1) is for `Bar` ([ADR-0054](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0054-a-histogram-is-a-helper-for-bar-not-a-chart-kind.md)); a cell without points has no row and is not drawn, so the ground is zero. Point opacity is not offered: a scatter is one path and one fill, so translucency does not add up on it, and a fill per point breaks the million-point budget.
|
|
6445
|
+
|
|
6446
|
+
```tsx
|
|
6447
|
+
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
6448
|
+
|
|
6449
|
+
/* Data from the data scientist's world, written out here so the example runs on its own. */
|
|
6450
|
+
/** A small LCG - the same numbers on every computer. */
|
|
6451
|
+
function random(seed: number): () => number {
|
|
6452
|
+
let s = seed >>> 0;
|
|
6453
|
+
return () => {
|
|
6454
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
6455
|
+
return s / 4294967296;
|
|
6456
|
+
};
|
|
6457
|
+
}
|
|
6458
|
+
|
|
6459
|
+
/** A bell-shaped draw from two uniforms (Box-Muller). */
|
|
6460
|
+
const draw = random(9);
|
|
6461
|
+
const gauss = () => Math.sqrt(-2 * Math.log(Math.max(draw(), 1e-12))) * Math.cos(2 * Math.PI * draw());
|
|
6462
|
+
|
|
6463
|
+
/** Two hundred thousand points in two clusters, counted into a grid of 50 by 50 cells as they come. */
|
|
6464
|
+
const CELLS = 50;
|
|
6465
|
+
const SIZE = 10 / CELLS;
|
|
6466
|
+
const COUNTS = new Uint32Array(CELLS * CELLS);
|
|
6467
|
+
for (let k = 0; k < 200_000; k++) {
|
|
6468
|
+
const [cx, cy, sd] = k % 5 < 3 ? [3, 4, 1.2] : [7, 6.5, 1.6];
|
|
6469
|
+
const i = Math.floor((cx + sd * gauss()) / SIZE);
|
|
6470
|
+
const j = Math.floor((cy + sd * gauss()) / SIZE);
|
|
6471
|
+
if (i >= 0 && i < CELLS && j >= 0 && j < CELLS) COUNTS[j * CELLS + i]! += 1;
|
|
6472
|
+
}
|
|
6473
|
+
|
|
6474
|
+
interface Cell {
|
|
6475
|
+
/** The cell's centre: its column and its row. */
|
|
6476
|
+
x: number;
|
|
6477
|
+
y: number;
|
|
6478
|
+
/** The points in the cell. */
|
|
6479
|
+
count: number;
|
|
6480
|
+
}
|
|
6481
|
+
|
|
6482
|
+
/** One row per cell with points; a cell without any has no row and is not drawn. */
|
|
6483
|
+
const CELL_ROWS: readonly Cell[] = Array.from(COUNTS).flatMap((count, k) =>
|
|
6484
|
+
count === 0 ? [] : [{ x: ((k % CELLS) + 0.5) * SIZE, y: (Math.floor(k / CELLS) + 0.5) * SIZE, count }],
|
|
6485
|
+
);
|
|
6486
|
+
|
|
6487
|
+
export default function WherePointsCrowd() {
|
|
6488
|
+
const { Chart, XAxis, YAxis, Matrix } = useChart(CELL_ROWS);
|
|
6489
|
+
return (
|
|
6490
|
+
<Chart height={320} aria-label="Two hundred thousand points counted into cells, the counts as a gradient">
|
|
6491
|
+
<XAxis value="x" label="x" />
|
|
6492
|
+
<YAxis />
|
|
6493
|
+
<Matrix value="y" level="count" name="Points per cell" format={{ decimals: 0 }} />
|
|
6494
|
+
<Legend />
|
|
6495
|
+
<Tooltip mode="nearest" />
|
|
6496
|
+
</Chart>
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
6499
|
+
```
|
|
6500
|
+
|
|
5821
6501
|
#### Keyboard
|
|
5822
6502
|
|
|
5823
6503
|
The keys of [Chart](#chart-1) apply here.
|
|
@@ -5828,15 +6508,15 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
5828
6508
|
|
|
5829
6509
|
| Prop | Type | Default | Description |
|
|
5830
6510
|
|---|---|---|---|
|
|
5831
|
-
| `value` *required* | `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: [Plot single measurements](#scatter), [Mark samples that miss a target](#scatter), [
|
|
6511
|
+
| `value` *required* | `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: [Plot single measurements](#scatter), [Mark samples that miss a target](#scatter), [A fit line with its band](#scatter) and 5 more |
|
|
5832
6512
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5833
6513
|
| `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5834
6514
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
|
|
5835
|
-
| `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: [Plot single measurements](#scatter), [Mark samples that miss a target](#scatter), [
|
|
6515
|
+
| `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: [Plot single measurements](#scatter), [Mark samples that miss a target](#scatter), [A fit line with its band](#scatter) and 5 more |
|
|
5836
6516
|
| `format` | `ValueFormat` | the y axis' `tickFormat`, else the built-in number format | The value as the tooltip writes it, as a function or `NumberOptions`. |
|
|
5837
6517
|
| `color` | `string` | the palette's `--uc-series-N` | Any CSS colour value. |
|
|
5838
6518
|
| `tone` | `"ok" \| "warning" \| "alarm"` | — | A role instead of a colour value; the theme resolves it.<br>Shown in: [Mark samples that miss a target](#scatter) |
|
|
5839
|
-
| `radius` | `number` | `3` | Point radius in CSS pixels.<br>Shown in: [Plot single measurements](#scatter), [Mark samples that miss a target](#scatter) |
|
|
6519
|
+
| `radius` | `number` | `3` | Point radius in CSS pixels.<br>Shown in: [Plot single measurements](#scatter), [Mark samples that miss a target](#scatter), [A fit line with its band](#scatter) |
|
|
5840
6520
|
|
|
5841
6521
|
##### Types on this page
|
|
5842
6522
|
|
|
@@ -5891,7 +6571,7 @@ nothing.
|
|
|
5891
6571
|
|
|
5892
6572
|
| Prop | Type | Default | Description |
|
|
5893
6573
|
|---|---|---|---|
|
|
5894
|
-
| `decimals` | `number` | — |
|
|
6574
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
5895
6575
|
|
|
5896
6576
|
### BoxPlot
|
|
5897
6577
|
|
|
@@ -6473,13 +7153,13 @@ nothing.
|
|
|
6473
7153
|
|
|
6474
7154
|
| Prop | Type | Default | Description |
|
|
6475
7155
|
|---|---|---|---|
|
|
6476
|
-
| `decimals` | `number` | — |
|
|
7156
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
6477
7157
|
|
|
6478
7158
|
#### Known limits
|
|
6479
7159
|
|
|
6480
7160
|
- No horizontal boxes, no violin or jittered points, no box width by count: a density is smoothing, a jitter a picture that does not repeat.
|
|
6481
7161
|
|
|
6482
|
-
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.
|
|
7162
|
+
What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.47.0/docs/adr/0032-what-umriss-is-not.md).
|
|
6483
7163
|
|
|
6484
7164
|
### StateBand
|
|
6485
7165
|
|
|
@@ -7227,10 +7907,10 @@ export default function ByLimits() {
|
|
|
7227
7907
|
|
|
7228
7908
|
##### Colour cells along a gradient
|
|
7229
7909
|
|
|
7230
|
-
Without `coloring` the cells run along `DEFAULT_GRADIENT` from the lowest value to the highest; a cell without a value stays a hole.
|
|
7910
|
+
Without `coloring` the cells run along `DEFAULT_GRADIENT` from the lowest value to the highest; a cell without a value stays a hole. The legend shows the steps as a colour bar, the range's ends written in `format`.
|
|
7231
7911
|
|
|
7232
7912
|
```tsx
|
|
7233
|
-
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
7913
|
+
import { Legend, Tooltip, useChart } from "@umriss-ui/charts";
|
|
7234
7914
|
|
|
7235
7915
|
/* Data from the operations world, written out here so the example runs on its own. */
|
|
7236
7916
|
/** A small LCG - the same numbers on every computer. */
|
|
@@ -7297,13 +7977,110 @@ export default function Gradient() {
|
|
|
7297
7977
|
<Chart height={280} aria-label="Successful requests per service and hour yesterday, as a gradient">
|
|
7298
7978
|
<XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
7299
7979
|
<YAxis />
|
|
7300
|
-
<Matrix value="service" level="success" name="Success rate" />
|
|
7980
|
+
<Matrix value="service" level="success" name="Success rate" format={{ decimals: 1 }} />
|
|
7981
|
+
<Legend />
|
|
7301
7982
|
<Tooltip mode="nearest" />
|
|
7302
7983
|
</Chart>
|
|
7303
7984
|
);
|
|
7304
7985
|
}
|
|
7305
7986
|
```
|
|
7306
7987
|
|
|
7988
|
+
##### A correlation matrix, read
|
|
7989
|
+
|
|
7990
|
+
With `labels` each cell writes its level in the matrix' `format`, in the text colour over a halo of the ground colour, so it reads on the lightest and the darkest stop alike. A cell too small for its text is left without one: the second matrix is dense, and where the chart is narrow its cells stay bare - the tooltip still names every one.
|
|
7991
|
+
|
|
7992
|
+
```tsx
|
|
7993
|
+
import { Tooltip, useChart } from "@umriss-ui/charts";
|
|
7994
|
+
|
|
7995
|
+
/* Data from the data scientist's world, written out here so the example runs on its own. */
|
|
7996
|
+
/** A small LCG - the same numbers on every computer. */
|
|
7997
|
+
function random(seed: number): () => number {
|
|
7998
|
+
let s = seed >>> 0;
|
|
7999
|
+
return () => {
|
|
8000
|
+
s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
|
|
8001
|
+
return s / 4294967296;
|
|
8002
|
+
};
|
|
8003
|
+
}
|
|
8004
|
+
|
|
8005
|
+
const VARIABLES = ["Age", "Income", "Tenure", "Visits", "Basket", "Returns", "Support", "Churn", "Score"] as const;
|
|
8006
|
+
|
|
8007
|
+
interface Pair {
|
|
8008
|
+
/** The column: the variable's place in `VARIABLES`. */
|
|
8009
|
+
column: number;
|
|
8010
|
+
/** The row: the variable's name. */
|
|
8011
|
+
variable: string;
|
|
8012
|
+
/** Their correlation, from -1 to 1. */
|
|
8013
|
+
r: number;
|
|
8014
|
+
}
|
|
8015
|
+
|
|
8016
|
+
/** The correlations of nine variables: the cosine between direction vectors in
|
|
8017
|
+
a small space, which is a valid correlation matrix by construction. */
|
|
8018
|
+
const CORRELATIONS: readonly Pair[] = (() => {
|
|
8019
|
+
const draw = random(11);
|
|
8020
|
+
const vectors = VARIABLES.map(() => [draw() - 0.45, draw() - 0.5, draw() - 0.5]);
|
|
8021
|
+
const unit = vectors.map((v) => {
|
|
8022
|
+
const length = Math.hypot(...v);
|
|
8023
|
+
return v.map((c) => c / length);
|
|
8024
|
+
});
|
|
8025
|
+
const pairs: Pair[] = [];
|
|
8026
|
+
VARIABLES.forEach((variable, row) => {
|
|
8027
|
+
VARIABLES.forEach((_, column) => {
|
|
8028
|
+
const dot = (unit[row] as number[]).reduce((sum, c, k) => sum + c * (unit[column] as number[])[k]!, 0);
|
|
8029
|
+
pairs.push({ column, variable, r: Math.round(Math.max(-1, Math.min(1, dot)) * 100) / 100 });
|
|
8030
|
+
});
|
|
8031
|
+
});
|
|
8032
|
+
return pairs;
|
|
8033
|
+
})();
|
|
8034
|
+
|
|
8035
|
+
/** Nine stops from blue through white to red, one per ninth of [-1, 1]. */
|
|
8036
|
+
const DIVERGING = ["#2166ac", "#4393c3", "#92c5de", "#d1e5f0", "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b"];
|
|
8037
|
+
|
|
8038
|
+
interface Load {
|
|
8039
|
+
hour: number;
|
|
8040
|
+
service: string;
|
|
8041
|
+
/** The share of capacity in use, 0 to 1. */
|
|
8042
|
+
load: number;
|
|
8043
|
+
}
|
|
8044
|
+
|
|
8045
|
+
/** A load per service and hour: dense on purpose, 24 columns of nine rows. */
|
|
8046
|
+
const LOAD: readonly Load[] = (() => {
|
|
8047
|
+
const draw = random(5);
|
|
8048
|
+
const cells: Load[] = [];
|
|
8049
|
+
VARIABLES.forEach((service) => {
|
|
8050
|
+
for (let hour = 0; hour < 24; hour++) cells.push({ hour, service, load: Math.round(draw() * 100) / 100 });
|
|
8051
|
+
});
|
|
8052
|
+
return cells;
|
|
8053
|
+
})();
|
|
8054
|
+
|
|
8055
|
+
export default function CorrelationRead() {
|
|
8056
|
+
const first = useChart(CORRELATIONS);
|
|
8057
|
+
const second = useChart(LOAD);
|
|
8058
|
+
return (
|
|
8059
|
+
<>
|
|
8060
|
+
<first.Chart height={380} aria-label="Correlation of nine variables, each cell written out">
|
|
8061
|
+
<first.XAxis value="column" ticks={VARIABLES.map((_, k) => k)} tickFormat={(v) => VARIABLES[v] ?? ""} />
|
|
8062
|
+
<first.YAxis />
|
|
8063
|
+
<first.Matrix
|
|
8064
|
+
value="variable"
|
|
8065
|
+
level="r"
|
|
8066
|
+
coloring={{ kind: "gradient", stops: DIVERGING, range: [-1, 1] }}
|
|
8067
|
+
name="Correlation"
|
|
8068
|
+
format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }}
|
|
8069
|
+
labels
|
|
8070
|
+
/>
|
|
8071
|
+
<Tooltip mode="nearest" />
|
|
8072
|
+
</first.Chart>
|
|
8073
|
+
<second.Chart height={300} aria-label="Load per service and hour, labels where the cells have room">
|
|
8074
|
+
<second.XAxis value="hour" ticks={[0, 6, 12, 18]} tickFormat={(v) => `${v}:00`} label="Hour" />
|
|
8075
|
+
<second.YAxis />
|
|
8076
|
+
<second.Matrix value="service" level="load" name="Load" labels />
|
|
8077
|
+
<Tooltip mode="nearest" />
|
|
8078
|
+
</second.Chart>
|
|
8079
|
+
</>
|
|
8080
|
+
);
|
|
8081
|
+
}
|
|
8082
|
+
```
|
|
8083
|
+
|
|
7307
8084
|
#### Keyboard
|
|
7308
8085
|
|
|
7309
8086
|
The keys of [Chart](#chart-1) apply here.
|
|
@@ -7314,14 +8091,15 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
7314
8091
|
|
|
7315
8092
|
| Prop | Type | Default | Description |
|
|
7316
8093
|
|---|---|---|---|
|
|
7317
|
-
| `value` *required* | `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), [Colour cells along a gradient](#matrix), [
|
|
7318
|
-
| `level` *required* | `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), [Colour cells along a gradient](#matrix), [
|
|
7319
|
-
| `coloring` | `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), [Mark bands, limits and cells (Chart)](#chart-1) |
|
|
8094
|
+
| `value` *required* | `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), [Colour cells along a gradient](#matrix), [A correlation matrix, read](#matrix) and 3 more |
|
|
8095
|
+
| `level` *required* | `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), [Colour cells along a gradient](#matrix), [A correlation matrix, read](#matrix) and 3 more |
|
|
8096
|
+
| `coloring` | `MatrixColoring` | `{ kind: "gradient", stops: DEFAULT_GRADIENT }` | How the level colours a cell: by assessment against limits, or across a gradient. A gradient's legend entry shows a colour bar: its steps, with the range's two ends written under them in `format` and the inner edges where they fit. An assessment keeps its three colours.<br>Shown in: [Colour cells by their limits](#matrix), [A correlation matrix, read](#matrix), [Mark bands, limits and cells (Chart)](#chart-1) |
|
|
7320
8097
|
| `xAxisId` | `string` | `"x"` | Binding to an x axis. |
|
|
7321
8098
|
| `yAxisId` | `string` | `"y"` | Binding to a y axis. |
|
|
7322
8099
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data. |
|
|
7323
|
-
| `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: [Colour cells by their limits](#matrix), [Colour cells along a gradient](#matrix), [
|
|
7324
|
-
| `format` | `ValueFormat` | the built-in number format | The level as the tooltip writes it, as a function or `NumberOptions`. The y axis' `tickFormat` writes the row, not the level.<br>Shown in: [Colour cells by their limits](#matrix) |
|
|
8100
|
+
| `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: [Colour cells by their limits](#matrix), [Colour cells along a gradient](#matrix), [A correlation matrix, read](#matrix) and 3 more |
|
|
8101
|
+
| `format` | `ValueFormat` | the built-in number format | The level as the tooltip writes it, as a function or `NumberOptions`. The y axis' `tickFormat` writes the row, not the level.<br>Shown in: [Colour cells by their limits](#matrix), [Colour cells along a gradient](#matrix), [A correlation matrix, read](#matrix) and 1 more |
|
|
8102
|
+
| `labels` | `boolean` | `false` | Each cell writes its level in `format`, in the tick size, in the text colour over a halo of the ground colour - legible on every stop, in both themes and in forced colours. A cell too small for its text is left without one, never truncated; a hole has none. Off by default: a dense heat map would be all clipped text.<br>Shown in: [A correlation matrix, read](#matrix) |
|
|
7325
8103
|
|
|
7326
8104
|
##### Types on this page
|
|
7327
8105
|
|
|
@@ -7439,7 +8217,7 @@ nothing.
|
|
|
7439
8217
|
|
|
7440
8218
|
| Prop | Type | Default | Description |
|
|
7441
8219
|
|---|---|---|---|
|
|
7442
|
-
| `decimals` | `number` | — |
|
|
8220
|
+
| `decimals` | `number` | — | <br>Shown in: [Colour cells along a gradient](#matrix), [Where the points crowd (Scatter)](#scatter) |
|
|
7443
8221
|
|
|
7444
8222
|
###### `Limit`
|
|
7445
8223
|
|
|
@@ -9106,7 +9884,7 @@ nothing.
|
|
|
9106
9884
|
|
|
9107
9885
|
| Prop | Type | Default | Description |
|
|
9108
9886
|
|---|---|---|---|
|
|
9109
|
-
| `decimals` | `number` | — |
|
|
9887
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
9110
9888
|
|
|
9111
9889
|
###### `RuleName`
|
|
9112
9890
|
|
|
@@ -9174,6 +9952,42 @@ export default function ParetoExample() {
|
|
|
9174
9952
|
}
|
|
9175
9953
|
```
|
|
9176
9954
|
|
|
9955
|
+
##### Shares, not counts
|
|
9956
|
+
|
|
9957
|
+
A share reads better as sorted bars than as a pie ([ADR-0059](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0059-the-set-of-chart-kinds-is-open.md)): `pareto()` gives each entry its `share` from 0 to 1, and a percent axis says it - no cumulative line and no second axis.
|
|
9958
|
+
|
|
9959
|
+
```tsx
|
|
9960
|
+
import { useMemo } from "react";
|
|
9961
|
+
import { Tooltip, pareto, useChart } from "@umriss-ui/charts";
|
|
9962
|
+
|
|
9963
|
+
/* Why deliveries failed last month, in no particular order. */
|
|
9964
|
+
const FAILED_DELIVERIES = [
|
|
9965
|
+
{ name: "Damaged in transit", value: 21 },
|
|
9966
|
+
{ name: "Nobody home", value: 312 },
|
|
9967
|
+
{ name: "Address incomplete", value: 96 },
|
|
9968
|
+
{ name: "Refused", value: 34 },
|
|
9969
|
+
{ name: "Business closed", value: 58 },
|
|
9970
|
+
{ name: "No access code", value: 147 },
|
|
9971
|
+
{ name: "Wrong address", value: 12 },
|
|
9972
|
+
{ name: "Parcel not found", value: 8 },
|
|
9973
|
+
{ name: "Weather", value: 6 },
|
|
9974
|
+
{ name: "Vehicle breakdown", value: 4 },
|
|
9975
|
+
];
|
|
9976
|
+
|
|
9977
|
+
export default function SharesNotCounts() {
|
|
9978
|
+
const { entries } = useMemo(() => pareto(FAILED_DELIVERIES, { collectRank: 6, remainderName: "Other" }), []);
|
|
9979
|
+
const { Chart, XAxis, YAxis, Bar } = useChart(entries);
|
|
9980
|
+
return (
|
|
9981
|
+
<Chart height={300} aria-label="Share of failed deliveries by cause, sorted">
|
|
9982
|
+
<XAxis value="name" />
|
|
9983
|
+
<YAxis label="Share of failed deliveries" tickFormat={{ style: "percent", maximumFractionDigits: 0 }} />
|
|
9984
|
+
<Bar value="share" name="Share" barWidth={0.72} />
|
|
9985
|
+
<Tooltip mode="x" />
|
|
9986
|
+
</Chart>
|
|
9987
|
+
);
|
|
9988
|
+
}
|
|
9989
|
+
```
|
|
9990
|
+
|
|
9177
9991
|
#### Keyboard
|
|
9178
9992
|
|
|
9179
9993
|
The keys of [Chart](#chart-1) apply here.
|
|
@@ -9253,7 +10067,7 @@ From `LegendProps` — the full table stands on [Legend](#legend).
|
|
|
9253
10067
|
|
|
9254
10068
|
| Prop | Type | Default | Description |
|
|
9255
10069
|
|---|---|---|---|
|
|
9256
|
-
| [`placement`](#legend) | `"top" \| "bottom"` | `"top"` | Which side of the plot area the legend stands on. |
|
|
10070
|
+
| [`placement`](#legend) | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Which side of the plot area the legend stands on. `"left"` and `"right"` set the entries in a column beside the plot, as wide as the longest name up to a quarter of the chart, scrolling where they do not fit; in a chart narrower than 480 px they stand above the plot, as `"top"`. |
|
|
9257
10071
|
|
|
9258
10072
|
## Around the chart
|
|
9259
10073
|
|
|
@@ -10065,7 +10879,7 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
10065
10879
|
|
|
10066
10880
|
| Prop | Type | Default | Description |
|
|
10067
10881
|
|---|---|---|---|
|
|
10068
|
-
| `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), [Write the tooltip yourself](#tooltip), [Write each value in its unit](#tooltip) and
|
|
10882
|
+
| `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), [Write the tooltip yourself](#tooltip), [Write each value in its unit](#tooltip) and 77 more |
|
|
10069
10883
|
| `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) |
|
|
10070
10884
|
| `render` | `(hit: ``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), [Fix the domain and name the ticks (Axis)](#axis) |
|
|
10071
10885
|
|
|
@@ -10079,7 +10893,7 @@ The keys of [Chart](#chart-1) apply here.
|
|
|
10079
10893
|
|
|
10080
10894
|
| Prop | Type | Default | Description |
|
|
10081
10895
|
|---|---|---|---|
|
|
10082
|
-
| `onViewChange` | `(view: `[`ChartView`](#view)`) => void` | — | Every change the reader makes, once, always the whole view - the one to keep, or to hand another chart's `setView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with, nor for one `setView` applies.<br>Shown in: [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Read it by keyboard and screen reader (Chart)](#chart-1) |
|
|
10896
|
+
| `onViewChange` | `(view: `[`ChartView`](#view)`) => void` | — | Every change the reader makes, once, always the whole view - the one to keep, or to hand another chart's `setView`. A pan or zoom is reported at most once per frame. Not called for the view the chart starts with, nor for one `setView` applies.<br>Shown in: [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Follow the live edge, with pause (Zoom and pan)](#zoom-and-pan), [Read it by keyboard and screen reader (Chart)](#chart-1) |
|
|
10083
10897
|
| `onSelect` | `(hit: ``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), [Drill down from a year to its days](#tooltip) |
|
|
10084
10898
|
| `onRange` | `(range: ``ChartRange``<V>) => void` | — | The reader marked a stretch of an x axis: Alt with a drag where the chart zooms, a plain drag where it does not. Called once, on release - once per x axis -, with `from` not after `to`, in the axis' own values: numbers, milliseconds on a time axis, wall-clock instants on a calendar axis, and on a category axis the first and last category covered, as strings - say so in the handler's parameter, `ChartRange<string>`. A band covers the stretch while the pointer is down; the chart keeps no range and draws none afterwards, and it does not travel over `syncId`. A press that travels 4 px or less is a click and reaches `onSelect`; Escape and a cancelled pointer report nothing.<br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
|
|
10085
10899
|
|
|
@@ -10122,6 +10936,7 @@ One hit per series in the tooltip.
|
|
|
10122
10936
|
| `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), [Fix the domain and name the ticks (Axis)](#axis) |
|
|
10123
10937
|
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself](#tooltip), [Drill down from a year to its days](#tooltip) |
|
|
10124
10938
|
| `index` *required* | `number` | — | |
|
|
10939
|
+
| `baseline` | `number` | — | The lower edge, where the series has a baseline of its own - a `Bar` or an `Area` outside a stack; `yValue` is the upper one. A stack's foot is no reading and never stands here. A field of its own, as `level` and `box` have ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). |
|
|
10125
10940
|
| `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. |
|
|
10126
10941
|
| `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. |
|
|
10127
10942
|
| `box` | `BoxNumbers`` & Partial<``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)). |
|
|
@@ -10393,6 +11208,72 @@ export default function LegendPlacement() {
|
|
|
10393
11208
|
}
|
|
10394
11209
|
```
|
|
10395
11210
|
|
|
11211
|
+
##### A legend beside the plot
|
|
11212
|
+
|
|
11213
|
+
`placement="right"` or `"left"` sets the names in a column beside the plot - as wide as the longest one up to a quarter of the chart, a longer name wrapping under its own text. Where the chart is narrower than 480 px, as the last one is, the legend stands above the plot as `"top"` does.
|
|
11214
|
+
|
|
11215
|
+
```tsx
|
|
11216
|
+
import { Legend, useChart } from "@umriss-ui/charts";
|
|
11217
|
+
|
|
11218
|
+
/* Data from the operations world, written out here so the example runs on its own. */
|
|
11219
|
+
interface Reading {
|
|
11220
|
+
hour: number;
|
|
11221
|
+
units: number;
|
|
11222
|
+
}
|
|
11223
|
+
|
|
11224
|
+
const LINES = [
|
|
11225
|
+
"Press 1",
|
|
11226
|
+
"Press 2",
|
|
11227
|
+
"Welding cell, body shop north",
|
|
11228
|
+
"Paint booth",
|
|
11229
|
+
"Assembly A",
|
|
11230
|
+
"Assembly B",
|
|
11231
|
+
"Final inspection and packing",
|
|
11232
|
+
"Dispatch",
|
|
11233
|
+
];
|
|
11234
|
+
|
|
11235
|
+
/* Once, outside the component: a series' `data` is compared by reference. Units per hour of every line over a
|
|
11236
|
+
shift, a fixed wave of its own for each. */
|
|
11237
|
+
const BY_LINE = LINES.map((line, l) => ({
|
|
11238
|
+
line,
|
|
11239
|
+
rows: Array.from({ length: 8 }, (_, hour): Reading => ({ hour, units: Math.round(60 + l * 9 + 14 * Math.sin((hour + l) * 0.9)) })),
|
|
11240
|
+
}));
|
|
11241
|
+
const READINGS: readonly Reading[] = BY_LINE.flatMap(({ rows }) => rows);
|
|
11242
|
+
|
|
11243
|
+
function Shift({ placement, width }: { placement: "left" | "right"; width?: number }) {
|
|
11244
|
+
const { Chart, XAxis, YAxis, Line } = useChart(READINGS);
|
|
11245
|
+
return (
|
|
11246
|
+
<Chart height={280} width={width} aria-label={`Units per hour of eight lines, legend ${placement}`}>
|
|
11247
|
+
<XAxis value="hour" />
|
|
11248
|
+
<YAxis label="units" />
|
|
11249
|
+
{BY_LINE.map(({ line, rows }) => (
|
|
11250
|
+
<Line key={line} data={rows} value="units" name={line} />
|
|
11251
|
+
))}
|
|
11252
|
+
<Legend placement={placement} />
|
|
11253
|
+
</Chart>
|
|
11254
|
+
);
|
|
11255
|
+
}
|
|
11256
|
+
|
|
11257
|
+
export default function BesideThePlot() {
|
|
11258
|
+
return (
|
|
11259
|
+
<div className="pair" style={{ gridTemplateColumns: "minmax(0, 1fr)" }}>
|
|
11260
|
+
<div>
|
|
11261
|
+
<p className="pair-caption">placement="right"</p>
|
|
11262
|
+
<Shift placement="right" />
|
|
11263
|
+
</div>
|
|
11264
|
+
<div>
|
|
11265
|
+
<p className="pair-caption">placement="left"</p>
|
|
11266
|
+
<Shift placement="left" />
|
|
11267
|
+
</div>
|
|
11268
|
+
<div>
|
|
11269
|
+
<p className="pair-caption">placement="right", 400 px wide - above the plot</p>
|
|
11270
|
+
<Shift placement="right" width={400} />
|
|
11271
|
+
</div>
|
|
11272
|
+
</div>
|
|
11273
|
+
);
|
|
11274
|
+
}
|
|
11275
|
+
```
|
|
11276
|
+
|
|
10396
11277
|
#### Keyboard
|
|
10397
11278
|
|
|
10398
11279
|
| Key | Action |
|
|
@@ -10412,7 +11293,7 @@ Every entry of a named series is a button whose `aria-pressed` says whether its
|
|
|
10412
11293
|
|
|
10413
11294
|
| Prop | Type | Default | Description |
|
|
10414
11295
|
|---|---|---|---|
|
|
10415
|
-
| `placement` | `"top" \| "bottom"` | `"top"` | Which side of the plot area the legend stands on
|
|
11296
|
+
| `placement` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Which side of the plot area the legend stands on. `"left"` and `"right"` set the entries in a column beside the plot, as wide as the longest name up to a quarter of the chart, scrolling where they do not fit; in a chart narrower than 480 px they stand above the plot, as `"top"`.<br>Shown in: [Legend above or below](#legend), [A legend beside the plot](#legend), [Mix series kinds (Chart)](#chart-1) and 30 more |
|
|
10416
11297
|
|
|
10417
11298
|
### Benchmark
|
|
10418
11299
|
|
|
@@ -10908,7 +11789,7 @@ From `LegendProps` — the full table stands on [Legend](#legend).
|
|
|
10908
11789
|
|
|
10909
11790
|
| Prop | Type | Default | Description |
|
|
10910
11791
|
|---|---|---|---|
|
|
10911
|
-
| [`placement`](#legend) | `"top" \| "bottom"` | `"top"` | Which side of the plot area the legend stands on. |
|
|
11792
|
+
| [`placement`](#legend) | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Which side of the plot area the legend stands on. `"left"` and `"right"` set the entries in a column beside the plot, as wide as the longest name up to a quarter of the chart, scrolling where they do not fit; in a chart narrower than 480 px they stand above the plot, as `"top"`. |
|
|
10912
11793
|
|
|
10913
11794
|
## API index
|
|
10914
11795
|
|
|
@@ -10937,13 +11818,13 @@ at once. Place it inside a `Chart`.
|
|
|
10937
11818
|
function DataTable(): ReactNode;
|
|
10938
11819
|
```
|
|
10939
11820
|
|
|
10940
|
-
Used on [Chart](#chart-1), [BoxPlot](#boxplot) and [Tooltip](#tooltip).
|
|
11821
|
+
Used on [Chart](#chart-1), [Bar](#bar), [BoxPlot](#boxplot) and [Tooltip](#tooltip).
|
|
10941
11822
|
|
|
10942
11823
|
##### `Legend`
|
|
10943
11824
|
|
|
10944
11825
|
[Legend](#legend) – What the colours mean (a key), and a switch for every series: a click hides it and shows it again, a double click shows only it (a solo).
|
|
10945
11826
|
|
|
10946
|
-
Used on [Scenarios](#scenarios), [Chart](#chart-1), [Axis](#axis), [View](#view), [Line](#line), [Area](#area), [Bar](#bar), [Scatter](#scatter), [BoxPlot](#boxplot), [StateBand](#stateband), [LimitLine](#limitline), [Pareto](#pareto), [Tooltip](#tooltip), [Legend](#legend) and [Benchmark](#benchmark).
|
|
11827
|
+
Used on [Scenarios](#scenarios), [Chart](#chart-1), [Axis](#axis), [View](#view), [Line](#line), [Area](#area), [Bar](#bar), [Scatter](#scatter), [BoxPlot](#boxplot), [StateBand](#stateband), [Matrix](#matrix), [LimitLine](#limitline), [Pareto](#pareto), [Tooltip](#tooltip), [Legend](#legend) and [Benchmark](#benchmark).
|
|
10947
11828
|
|
|
10948
11829
|
##### `LimitBand`
|
|
10949
11830
|
|
|
@@ -11016,15 +11897,16 @@ Counts values into bins of equal width.
|
|
|
11016
11897
|
A value on an edge belongs to the bin that edge starts - `[from, to)` - but
|
|
11017
11898
|
the last bin is closed, so the greatest value of a domain is counted and not
|
|
11018
11899
|
lost. Values that are not finite (`NaN`, ±Infinity, `null`, `undefined`)
|
|
11019
|
-
are skipped, as a gap is on a chart.
|
|
11900
|
+
are skipped, as a gap is on a chart. The values may be a typed array
|
|
11901
|
+
(`Float64Array`, `Int32Array`): they are read by index. Without values there are no bins; with
|
|
11020
11902
|
values there is at least one, even when they are all equal.
|
|
11021
11903
|
|
|
11022
11904
|
```ts
|
|
11023
|
-
function bin(values:
|
|
11905
|
+
function bin(values: ArrayLike<number | null | undefined>, options?: BinOptions): Bin[];
|
|
11024
11906
|
function bin<T>(rows: readonly T[], value: (row: T) => number | null | undefined, options?: BinOptions): Bin[];
|
|
11025
11907
|
```
|
|
11026
11908
|
|
|
11027
|
-
Used on [Bar](#bar).
|
|
11909
|
+
Used on [Chart](#chart-1), [Bar](#bar) and [Scatter](#scatter).
|
|
11028
11910
|
|
|
11029
11911
|
##### `breaks`
|
|
11030
11912
|
|
|
@@ -11346,6 +12228,8 @@ One second in milliseconds, the unit of a time axis.
|
|
|
11346
12228
|
const SECOND = 1000;
|
|
11347
12229
|
```
|
|
11348
12230
|
|
|
12231
|
+
Used on [Zoom and pan](#zoom-and-pan).
|
|
12232
|
+
|
|
11349
12233
|
#### Types
|
|
11350
12234
|
|
|
11351
12235
|
##### `AreaProps`
|
|
@@ -11363,6 +12247,7 @@ An `Area` as the chart holds it once registered.
|
|
|
11363
12247
|
| `fillOpacity` *required* | `number` | — | Opacity of the fill; the outline stays fully opaque. |
|
|
11364
12248
|
| `strokeWidth` *required* | `number` | — | |
|
|
11365
12249
|
| `dash` | `readonly number[]` | — | Dash pattern of the outline. |
|
|
12250
|
+
| `maxGap` | `number` | — | A step between readings longer than this is a gap; not read in a stack. |
|
|
11366
12251
|
| `accessor` *required* | `ValueFunction``<T>` | — | Y value; null/undefined/NaN/±Infinity means a gap. From `SeriesBase`. |
|
|
11367
12252
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data. From `SeriesBase`. |
|
|
11368
12253
|
| `xAxisId` *required* | `string` | — | From `SeriesBase`. |
|
|
@@ -11472,6 +12357,7 @@ A `Bar` as the chart holds it once registered.
|
|
|
11472
12357
|
| Prop | Type | Default | Description |
|
|
11473
12358
|
|---|---|---|---|
|
|
11474
12359
|
| `kind` *required* | `"bar"` | — | |
|
|
12360
|
+
| `baseline` | `ValueFunction``<T>` | — | Lower edge; without a value the fixed baseline 0. |
|
|
11475
12361
|
| `barWidth` *required* | `number` | — | Width as a fraction of the step; the group shares this fraction. |
|
|
11476
12362
|
| `accessor` *required* | `ValueFunction``<T>` | — | Y value; null/undefined/NaN/±Infinity means a gap. From `SeriesBase`. |
|
|
11477
12363
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data. From `SeriesBase`. |
|
|
@@ -11502,7 +12388,7 @@ The settings of `bin`.
|
|
|
11502
12388
|
| Prop | Type | Default | Description |
|
|
11503
12389
|
|---|---|---|---|
|
|
11504
12390
|
| `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) |
|
|
11505
|
-
| `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
|
|
12391
|
+
| `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.<br>Shown in: [Columns instead of rows (Chart)](#chart-1) |
|
|
11506
12392
|
| `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. |
|
|
11507
12393
|
|
|
11508
12394
|
##### `BoxChannels`
|
|
@@ -11598,22 +12484,22 @@ is typed by that `data` instead.
|
|
|
11598
12484
|
|
|
11599
12485
|
| Prop | Type | Default | Description |
|
|
11600
12486
|
|---|---|---|---|
|
|
11601
|
-
| `Chart` *required* | `(props: `[`ChartProps`](#chart-1)`) => 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), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and
|
|
11602
|
-
| `XAxis` *required* | `<T = Z>(props: `[`XAxisProps`](#axis)`<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), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and
|
|
11603
|
-
| `YAxis` *required* | `(props: `[`YAxisProps`](#axis)`) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#installation), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and
|
|
11604
|
-
| `Line` *required* | `<T = Z>(props: `[`LineProps`](#line)`<T>) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#installation), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and
|
|
11605
|
-
| `Area` *required* | `<T = Z>(props: `[`AreaProps`](#area)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1), [Bind every kind of series to a second axis (Axis)](#axis) and
|
|
11606
|
-
| `Bar` *required* | `<T = Z>(props: `[`BarProps`](#bar)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1), [
|
|
11607
|
-
| `Scatter` *required* | `<T = Z>(props: `[`ScatterProps`](#scatter)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1), [Bind every kind of series to a second axis (Axis)](#axis) and
|
|
12487
|
+
| `Chart` *required* | `(props: `[`ChartProps`](#chart-1)`) => 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), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and 93 more |
|
|
12488
|
+
| `XAxis` *required* | `<T = Z>(props: `[`XAxisProps`](#axis)`<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), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and 93 more |
|
|
12489
|
+
| `YAxis` *required* | `(props: `[`YAxisProps`](#axis)`) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#installation), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and 93 more |
|
|
12490
|
+
| `Line` *required* | `<T = Z>(props: `[`LineProps`](#line)`<T>) => null` | — | <br>Shown in: [Draw a first chart (Installation)](#installation), [Size a chart by its container (Installation)](#installation), [Mix series kinds (Chart)](#chart-1) and 58 more |
|
|
12491
|
+
| `Area` *required* | `<T = Z>(props: `[`AreaProps`](#area)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1), [Bind every kind of series to a second axis (Axis)](#axis) and 7 more |
|
|
12492
|
+
| `Bar` *required* | `<T = Z>(props: `[`BarProps`](#bar)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1), [Columns instead of rows (Chart)](#chart-1) and 20 more |
|
|
12493
|
+
| `Scatter` *required* | `<T = Z>(props: `[`ScatterProps`](#scatter)`<T>) => null` | — | <br>Shown in: [Mix series kinds (Chart)](#chart-1), [Mark every kind (Chart)](#chart-1), [Bind every kind of series to a second axis (Axis)](#axis) and 5 more |
|
|
11608
12494
|
| `StateBand` *required* | `<T = Z>(props: `[`StateBandProps`](#stateband)`<T> & ``OneLane``) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#chart-1), [One vehicle through the day (StateBand)](#stateband), [Lay a state under a course (StateBand)](#stateband) and 4 more |
|
|
11609
|
-
| `Matrix` *required* | `<T = Z>(props: `[`MatrixProps`](#matrix)`<T>) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#chart-1), [
|
|
12495
|
+
| `Matrix` *required* | `<T = Z>(props: `[`MatrixProps`](#matrix)`<T>) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#chart-1), [Where the points crowd (Scatter)](#scatter), [Colour cells by their limits (Matrix)](#matrix) and 3 more |
|
|
11610
12496
|
| `BoxPlot` *required* | `<T = Z>(props: `[`BoxPlotProps`](#boxplot)`<T>) => null` | — | <br>Shown in: [One box per service (BoxPlot)](#boxplot), [A box per hour on a time axis (BoxPlot)](#boxplot), [With outliers (BoxPlot)](#boxplot) and 4 more |
|
|
11611
12497
|
| `view` *required* | [`ChartView`](#view) | — | How the reader is looking at the chart.<br>Shown in: [Keep and restore a view (View)](#view) |
|
|
11612
12498
|
| `domains` *required* | `AxisSpans` | — | The span each zoomable x axis shows, by axis `id`; an axis absent shows its own `domain`. |
|
|
11613
|
-
| `setView` *required* | `(view: `[`ChartView`](#view)`) => void` | — | Goes to a whole view; what it leaves out is shown whole again. Not reported through `onViewChange`: two charts kept in step by `onViewChange: (v) => other.setView(v)` in both directions do not answer each other. A view with the content already shown changes nothing.<br>Shown in: [Read it by keyboard and screen reader (Chart)](#chart-1), [Keep charts in step (Zoom and pan)](#zoom-and-pan), [
|
|
12499
|
+
| `setView` *required* | `(view: `[`ChartView`](#view)`) => void` | — | Goes to a whole view; what it leaves out is shown whole again. Not reported through `onViewChange`: two charts kept in step by `onViewChange: (v) => other.setView(v)` in both directions do not answer each other. A view with the content already shown changes nothing.<br>Shown in: [Read it by keyboard and screen reader (Chart)](#chart-1), [Keep charts in step (Zoom and pan)](#zoom-and-pan), [Follow the live edge, with pause (Zoom and pan)](#zoom-and-pan) and 1 more |
|
|
11614
12500
|
| `setDomain` *required* | `(axisId: string, span: readonly [number, number] \| null) => void` | — | Puts a span in view on a zoomable x axis - a lone one is `"x"`; `null` shows the axis' own `domain` again.<br>Shown in: [Measure a million points (Benchmark)](#benchmark) |
|
|
11615
|
-
| `hidden` *required* | `readonly string[]` | — | The hidden series, by `name
|
|
11616
|
-
| `toggleSeries` *required* | `(name: string) => void` | — | Hides the series `name`, or shows it where it is hidden - as a click on its legend entry does. Hiding the last series visible shows all
|
|
12501
|
+
| `hidden` *required* | `readonly string[]` | — | The hidden series, by `name`.<br>Shown in: [A trend with pens (Axis)](#axis) |
|
|
12502
|
+
| `toggleSeries` *required* | `(name: string) => void` | — | Hides the series `name`, or shows it where it is hidden - as a click on its legend entry does. Hiding the last series visible shows all.<br>Shown in: [A trend with pens (Axis)](#axis) |
|
|
11617
12503
|
| `showOnly` *required* | `(name: string) => void` | — | Hides every series but `name`; all are shown where none carries it.<br>Shown in: [Hide a series from the legend (Legend)](#legend) |
|
|
11618
12504
|
| `showAllSeries` *required* | `() => void` | — | Shows every series again.<br>Shown in: [Hide a series from the legend (Legend)](#legend) |
|
|
11619
12505
|
| `toPng` *required* | `() => Promise<Blob>` | — | A PNG of the chart as it stands: the plot with its grid, series and limits, the axes with ticks, labels and titles, and the legend, on the chart's own ground in the colour scheme in use, at the chart's size on screen and at least twice its CSS pixels. Not in it: the tooltip, the crosshair and hover marks, the "Show all" button, the data key and its table, the screen-reader texts, and any title. Where the picture goes - a download, the clipboard, an upload - is the application's. Rejects with an `Error` when the chart is not on screen or has no size.<br>Shown in: [Hand over a picture (Chart)](#chart-1) |
|
|
@@ -11692,6 +12578,7 @@ Everything the charts put out as text.
|
|
|
11692
12578
|
| `count` *required* | `string` | — | |
|
|
11693
12579
|
| `outlierList` *required* | `(values: readonly string[], more: number) => string` | — | |
|
|
11694
12580
|
| `downsampled` *required* | `(readings: number) => string` | — | Added to the caption where the table shows the downsampled course: how many readings its rows stand for. |
|
|
12581
|
+
| `levelRange` *required* | `(from: string, to: string) => string` | — | Added to the name of a matrix' legend entry that has a colour bar: the range its colours span, both ends written in the matrix' format. The bar's own values are hidden from assistive technology. |
|
|
11695
12582
|
|
|
11696
12583
|
##### `ChartView`
|
|
11697
12584
|
|
|
@@ -11745,7 +12632,7 @@ A `Legend` as the chart holds it once registered.
|
|
|
11745
12632
|
|
|
11746
12633
|
| Prop | Type | Default | Description |
|
|
11747
12634
|
|---|---|---|---|
|
|
11748
|
-
| `placement` *required* | `"top" \| "bottom"` | — | |
|
|
12635
|
+
| `placement` *required* | `"top" \| "bottom" \| "left" \| "right"` | — | |
|
|
11749
12636
|
|
|
11750
12637
|
##### `LegendProps`
|
|
11751
12638
|
|
|
@@ -11868,6 +12755,7 @@ A `Line` as the chart holds it once registered.
|
|
|
11868
12755
|
| `dash` | `readonly number[]` | — | |
|
|
11869
12756
|
| `markers` *required* | `"auto" \| "always" \| "never"` | — | |
|
|
11870
12757
|
| `step` | `boolean` | — | Sample-and-hold: each value holds until the next sample. |
|
|
12758
|
+
| `maxGap` | `number` | — | A step between readings longer than this is a gap. |
|
|
11871
12759
|
| `accessor` *required* | `ValueFunction``<T>` | — | Y value; null/undefined/NaN/±Infinity means a gap. From `SeriesBase`. |
|
|
11872
12760
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data. From `SeriesBase`. |
|
|
11873
12761
|
| `xAxisId` *required* | `string` | — | From `SeriesBase`. |
|
|
@@ -11959,6 +12847,7 @@ series kind whose base accessor reads more than a number.
|
|
|
11959
12847
|
| `kind` *required* | `"matrix"` | — | |
|
|
11960
12848
|
| `level` *required* | `ValueFunction``<T>` | — | The level that decides the colour - the third channel ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). The base accessor yields the row position, not this level. |
|
|
11961
12849
|
| `coloring` *required* | `MatrixColoring` | — | |
|
|
12850
|
+
| `labels` | `boolean` | — | Each cell writes its level. |
|
|
11962
12851
|
| `accessor` *required* | `ValueFunction``<T>` | — | Y value; null/undefined/NaN/±Infinity means a gap. From `SeriesBase`. |
|
|
11963
12852
|
| `data` | `readonly T[]` | — | Series-own data; overrides the container data. From `SeriesBase`. |
|
|
11964
12853
|
| `xAxisId` *required* | `string` | — | From `SeriesBase`. |
|
|
@@ -11989,7 +12878,7 @@ nothing.
|
|
|
11989
12878
|
|
|
11990
12879
|
| Prop | Type | Default | Description |
|
|
11991
12880
|
|---|---|---|---|
|
|
11992
|
-
| `decimals` | `number` | — |
|
|
12881
|
+
| `decimals` | `number` | — | <br>Shown in: [Where the points crowd (Scatter)](#scatter), [Colour cells along a gradient (Matrix)](#matrix) |
|
|
11993
12882
|
|
|
11994
12883
|
##### `OneLane`
|
|
11995
12884
|
|
|
@@ -12051,7 +12940,7 @@ where the cutoff is crossed.
|
|
|
12051
12940
|
|
|
12052
12941
|
| Prop | Type | Default | Description |
|
|
12053
12942
|
|---|---|---|---|
|
|
12054
|
-
| `entries` *required* | `readonly ``ParetoEntry``[]` | — | <br>Shown in: [Sort causes and collect the tail (Pareto)](#pareto) |
|
|
12943
|
+
| `entries` *required* | `readonly ``ParetoEntry``[]` | — | <br>Shown in: [Sort causes and collect the tail (Pareto)](#pareto), [Shares, not counts (Pareto)](#pareto) |
|
|
12055
12944
|
| `total` *required* | `number` | — | Total of the drawn entries - after collecting, the same as before. |
|
|
12056
12945
|
| `cutoffIndex` *required* | `number` | — | Index of the first entry whose cumulative share reaches the cutoff; -1 when it is never reached. |
|
|
12057
12946
|
|
|
@@ -12104,6 +12993,7 @@ chart stands.
|
|
|
12104
12993
|
| `colorOk` *required* | `string` | — | The third outcome of an assessment. "unknown" deliberately has no colour: a missing value becomes a hole like every gap in this library, and a hole carries no colour, because a colour would be a claim. |
|
|
12105
12994
|
| `font` *required* | `string` | — | |
|
|
12106
12995
|
| `fontMono` *required* | `string` | — | |
|
|
12996
|
+
| `sizeTick` *required* | `number` | — | The tick label's size in pixels (`--uc-size-tick`). |
|
|
12107
12997
|
| `series` *required* | `readonly string[]` | — | |
|
|
12108
12998
|
| `forced` *required* | `boolean` | — | The page is in forced colours: every colour above is a system colour, and the chart encodes by marks whatever its `encoding`. |
|
|
12109
12999
|
|
|
@@ -12304,6 +13194,7 @@ One hit per series in the tooltip.
|
|
|
12304
13194
|
| `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), [Choose a hit (Tooltip)](#tooltip) |
|
|
12305
13195
|
| `datum` *required* | `T` | — | <br>Shown in: [Write the tooltip yourself (Tooltip)](#tooltip), [Drill down from a year to its days (Tooltip)](#tooltip) |
|
|
12306
13196
|
| `index` *required* | `number` | — | |
|
|
13197
|
+
| `baseline` | `number` | — | The lower edge, where the series has a baseline of its own - a `Bar` or an `Area` outside a stack; `yValue` is the upper one. A stack's foot is no reading and never stands here. A field of its own, as `level` and `box` have ([ADR-0011](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0011-named-channels-never-overloaded.md)). |
|
|
12307
13198
|
| `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. |
|
|
12308
13199
|
| `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. |
|
|
12309
13200
|
| `box` | `BoxNumbers`` & Partial<``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)). |
|