@umriss-ui/charts 0.43.0 → 0.45.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/docs/llms-full.md CHANGED
@@ -1,4 +1,4 @@
1
- # @umriss-ui/charts 0.43.0
1
+ # @umriss-ui/charts 0.45.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
 
@@ -1057,7 +1057,7 @@ npm install @umriss-ui/charts
1057
1057
 
1058
1058
  The command installs the package alone: React 19 or newer is its only peer and stays the application's own, and the package depends on nothing else. The stylesheet comes with the JavaScript, so there is nothing to import - `@umriss-ui/charts/styles.css` is there for setups that link stylesheets by hand. Its colours, type and sizes are `--uc-` tokens that fall back onto core's; core's Theming page lists them all.
1059
1059
 
1060
- A chart takes its container's width and follows it; give it a `height` (300 px without one). `useChart(rows)` hands out `Chart`, its axes and its series, typed at the row; each reads its value from a row through `value`, a field name or a function - a value that is `null`, `undefined` or not finite is a gap, never a zero.
1060
+ A chart takes its container's width and follows it; give it a `height` (300 px without one). `useChart(rows)` hands out `Chart`, its axes and its series, typed at the row; each reads its value from a row through `value`, a field name or a function - a value that is `null`, `undefined` or not finite is a gap, never a zero. The rows are one object per row in an array (also a series' own `data`); anything else is read as no rows, so the chart says "No data" and warns once in development, and `Array.from` makes rows of an Arrow table or a typed array. `undefined` while the rows load draws empty without a word.
1061
1061
 
1062
1062
  An instant is a `Date`, milliseconds since 1970, or - where the x axis says `time` - the ISO string an API sends ([ADR-0052](https://github.com/romanhaendler/umriss-ui/blob/main/docs/adr/0052-a-chart-reads-its-rows-as-they-come.md)). On a time axis the ticks stand on the viewer's local clock, so a day begins at local midnight. The words are English; German comes from `@umriss-ui/charts/wording/de`.
1063
1063
 
@@ -2134,6 +2134,82 @@ export default function SeriesFromLongRows() {
2134
2134
  }
2135
2135
  ```
2136
2136
 
2137
+ ##### Hand over a picture
2138
+
2139
+ `toPng()` resolves to a PNG of the plot, its axes with their breaks, the limits and the legend, on the chart's own ground, in the scheme in use - without the tooltip or the crosshair, even with the pointer resting on the plot. What happens to it is the application's: here it is shown beside the chart.
2140
+
2141
+ ```tsx
2142
+ import { useEffect, useState } from "react";
2143
+ import { Legend, LimitLine, Tooltip, useChart } from "@umriss-ui/charts";
2144
+
2145
+ const HOUR = 3_600_000;
2146
+ const START = Date.UTC(2026, 2, 17, 6);
2147
+ const READINGS = Array.from({ length: 48 }, (_, i) => ({
2148
+ t: START + i * (HOUR / 4),
2149
+ p95: Math.round(180 + 40 * Math.sin(i / 6) + ((i * 37) % 23)),
2150
+ p50: Math.round(110 + 25 * Math.sin(i / 5) + ((i * 17) % 11)),
2151
+ }));
2152
+ // Two shifts, with the hours between them taken off the axis.
2153
+ const SHIFTS = [
2154
+ { from: START, to: START + 4 * HOUR },
2155
+ { from: START + 8 * HOUR, to: START + 12 * HOUR },
2156
+ ];
2157
+
2158
+ export default function Picture() {
2159
+ const { Chart, XAxis, YAxis, Line, toPng } = useChart(READINGS);
2160
+ const [url, setUrl] = useState<string | null>(null);
2161
+ const [marks, setMarks] = useState(true);
2162
+ const [failed, setFailed] = useState<string | null>(null);
2163
+ // The object URL lives as long as the picture shown.
2164
+ useEffect(() => () => (url === null ? undefined : URL.revokeObjectURL(url)), [url]);
2165
+
2166
+ return (
2167
+ <>
2168
+ <div className="demo-actions">
2169
+ <button type="button" className="demo-button" data-encoding aria-pressed={marks} onClick={() => setMarks(!marks)}>
2170
+ Marks
2171
+ </button>
2172
+ <button
2173
+ type="button"
2174
+ className="demo-button"
2175
+ data-picture
2176
+ onClick={() =>
2177
+ toPng().then(
2178
+ (blob) => {
2179
+ setFailed(null);
2180
+ setUrl(URL.createObjectURL(blob));
2181
+ },
2182
+ (error: Error) => setFailed(error.message),
2183
+ )
2184
+ }
2185
+ >
2186
+ Picture
2187
+ </button>
2188
+ </div>
2189
+ <div className="pair">
2190
+ <div>
2191
+ <p className="pair-caption">the chart</p>
2192
+ <Chart height={240} aria-label="Latency of Refunds" encoding={marks ? "marks" : "color"}>
2193
+ <XAxis value="t" time calendar={SHIFTS} />
2194
+ <YAxis label="ms" />
2195
+ <Line value="p95" name="Refunds p95" />
2196
+ <Line value="p50" name="Refunds p50" />
2197
+ <LimitLine value={240} severity="alarm" label="Too slow" />
2198
+ <Legend />
2199
+ <Tooltip />
2200
+ </Chart>
2201
+ </div>
2202
+ <div>
2203
+ <p className="pair-caption">the picture</p>
2204
+ {url !== null && <img src={url} alt="The picture of the chart" data-role="picture" style={{ width: "100%", height: "auto" }} />}
2205
+ {failed !== null && <p role="alert">{failed}</p>}
2206
+ </div>
2207
+ </div>
2208
+ </>
2209
+ );
2210
+ }
2211
+ ```
2212
+
2137
2213
  #### When to use something else
2138
2214
 
2139
2215
  - A single number with a small trend beside it → `Sparkline` or `Stat` from @umriss-ui/core
@@ -2169,14 +2245,15 @@ Under forced colours the chart draws in the system colours and tells its series
2169
2245
  |---|---|---|---|
2170
2246
  | `ref` | `Ref<HTMLDivElement>` | — | The root element, the `<div>` that `className`, `style`, `id` and the `data-*` attributes reach. |
2171
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) |
2172
- | `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 75 more |
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 79 more |
2173
2249
  | `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) |
2174
2250
  | `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) |
2175
2251
  | `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) |
2176
2252
  | `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) |
2177
- | `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) |
2253
+ | `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
+ | `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 |
2178
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), [Hold the tile length in control (Scenarios)](#hold-the-tile-length-in-control) |
2179
- | `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 75 more |
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 79 more |
2180
2257
 
2181
2258
  ###### Events
2182
2259
 
@@ -2184,12 +2261,6 @@ Under forced colours the chart draws in the system colours and tells its series
2184
2261
  |---|---|---|---|
2185
2262
  | `onPerf` | `(perf: ``ChartPerf``) => void` | — | Instrumentation for the benchmark page; not needed otherwise.<br>Shown in: [Measure a million points (Benchmark)](#benchmark), [Draw a week of seconds (Benchmark)](#benchmark) |
2186
2263
 
2187
- ###### Accessibility
2188
-
2189
- | Prop | Type | Default | Description |
2190
- |---|---|---|---|
2191
- | `ariaLabel` | `string` | — | *Deprecated* since 0.43 - use `aria-label`. Accessible name of the plot. Without a name, `aria-label` or `aria-labelledby`, the chart warns in development. |
2192
-
2193
2264
  ###### Styling
2194
2265
 
2195
2266
  | Prop | Type | Default | Description |
@@ -2230,6 +2301,9 @@ Everything the charts put out as text.
2230
2301
  | `percent` *required* | `(value: string) => string` | — | A share in a normalised stack, its number already formatted. |
2231
2302
  | `walkHelp` *required* | `string` | — | The keys that walk the chart. |
2232
2303
  | `selectHelp` *required* | `string` | — | The key that reports the Active point - only where `onSelect` is given. |
2304
+ | `rangeStarted` *required* | `(from: string) => string` | — | Said when the keyboard sets the start of a range; the x value is written in the axis' format. |
2305
+ | `rangeMade` *required* | `(from: string, to: string) => string` | — | Said when the keyboard sets the end of a range. |
2306
+ | `rangeHelp` *required* | `string` | — | The key that marks a range - only where `onRange` is given and a tooltip stands. |
2233
2307
  | `zoomHelp` *required* | `string` | — | The keys that zoom and pan it - only where the chart can be zoomed. |
2234
2308
  | `legendHelp` *required* | `string` | — | The keys of the legend's entries - only where it has one to press. |
2235
2309
  | `allShown` *required* | `string` | — | Read out where hiding would leave nothing to see, and every series is shown instead. |
@@ -2264,9 +2338,9 @@ Measurements of the instrumentation for the benchmark page.
2264
2338
  #### Known limits
2265
2339
 
2266
2340
  - 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`.
2267
- - No pie, donut, radar or candlestick, no smoothing, animation or export, no WebGL ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md)).
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.45.0/docs/adr/0032-what-umriss-is-not.md)).
2268
2342
 
2269
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
2343
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
2270
2344
 
2271
2345
  ### Axis
2272
2346
 
@@ -2928,6 +3002,50 @@ export default function Categories() {
2928
3002
  }
2929
3003
  ```
2930
3004
 
3005
+ ##### Name the time zone of the instants
3006
+
3007
+ `timeZone` draws the trend in that zone for a reader anywhere: ticks on its hours, labels, tooltip and data table in its time. On 25 October Berlin's clocks go back, and the doubled hour takes its real room on the axis.
3008
+
3009
+ ```tsx
3010
+ import { Tooltip, useChart } from "@umriss-ui/charts";
3011
+
3012
+ /* Data from the energy world, written out here so the example runs on its own. */
3013
+
3014
+ interface Reading {
3015
+ t: number;
3016
+ /** Power fed into the grid, in kW. */
3017
+ kw: number;
3018
+ }
3019
+
3020
+ /** A solar plant's feed-in every hour from 25 October 2026, 00:00 UTC to 17:00
3021
+ UTC - the day Berlin's clocks go back, so that 02:00 stands there twice. */
3022
+ const FEED_IN: readonly Reading[] = Array.from({ length: 18 }, (_, hour) => ({
3023
+ t: Date.UTC(2026, 9, 25, hour),
3024
+ kw: Math.round(Math.max(0, Math.sin((Math.PI * (hour - 5)) / 11)) * 840),
3025
+ }));
3026
+
3027
+ function Trend({ timeZone, name }: { timeZone: string; name: string }) {
3028
+ const { Chart, XAxis, YAxis, Line } = useChart(FEED_IN);
3029
+ return (
3030
+ <Chart height={220} timeZone={timeZone} aria-label={`Feed-in on 25 October 2026 in ${name}`}>
3031
+ <XAxis value="t" time tickCount={9} label={name} />
3032
+ <YAxis label="kW" />
3033
+ <Line value="kw" name="Feed-in" />
3034
+ <Tooltip mode="x" />
3035
+ </Chart>
3036
+ );
3037
+ }
3038
+
3039
+ export default function TimeZone() {
3040
+ return (
3041
+ <div style={{ display: "grid", gap: 8 }}>
3042
+ <Trend timeZone="Europe/Berlin" name="Berlin" />
3043
+ <Trend timeZone="UTC" name="UTC" />
3044
+ </div>
3045
+ );
3046
+ }
3047
+ ```
3048
+
2931
3049
  #### Keyboard
2932
3050
 
2933
3051
  The keys of [Chart](#chart-1) apply here.
@@ -2938,16 +3056,16 @@ The keys of [Chart](#chart-1) apply here.
2938
3056
 
2939
3057
  | Prop | Type | Default | Description |
2940
3058
  |---|---|---|---|
2941
- | `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 75 more |
2942
- | `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 11 more |
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 79 more |
3060
+ | `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 |
2943
3061
  | `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) |
2944
- | `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), [Draw a first chart (Installation)](#installation), [Size a chart by its container (Installation)](#installation) and 36 more |
2945
- | `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) |
2946
- | `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 6 more |
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), [Draw a first chart (Installation)](#installation) and 39 more |
3063
+ | `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 7 more |
2947
3065
  | `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) |
2948
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 7 more |
2949
- | `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 67 more |
2950
- | `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), [Mix series kinds (Chart)](#chart-1) and 8 more |
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 71 more |
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 9 more |
2951
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 21 more |
2952
3070
  | `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) |
2953
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), [Compare progress with a plan (Line)](#line) and 7 more |
@@ -2962,8 +3080,8 @@ The keys of [Chart](#chart-1) apply here.
2962
3080
  | `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) |
2963
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 7 more |
2964
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 7 more |
2965
- | `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 67 more |
2966
- | `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), [Mix series kinds (Chart)](#chart-1) and 8 more |
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 71 more |
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 9 more |
2967
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 21 more |
2968
3086
  | `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) |
2969
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), [Compare progress with a plan (Line)](#line) and 7 more |
@@ -3041,10 +3159,10 @@ nothing.
3041
3159
 
3042
3160
  #### Known limits
3043
3161
 
3044
- - 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.43.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)).
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.45.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)).
3045
3163
  - 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)).
3046
3164
 
3047
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
3165
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
3048
3166
 
3049
3167
  ### Zoom and pan
3050
3168
 
@@ -3572,8 +3690,63 @@ function Channel({ channel: c, peers }: { channel: (typeof CHANNELS)[number]; pe
3572
3690
  }
3573
3691
  ```
3574
3692
 
3693
+ ##### Mark a range
3694
+
3695
+ `onRange` hears a stretch the reader marked - Alt with a drag, or Space at one point and Space at another - once, on release, `from` before `to`. The chart keeps none: the range lives in the example's own state, and handing it back as `initialView` is the one line that zooms into it.
3696
+
3697
+ ```tsx
3698
+ import { Button, Text } from "@umriss-ui/core";
3699
+ import { useState } from "react";
3700
+ import { Tooltip, useChart, type ChartRange } from "@umriss-ui/charts";
3701
+
3702
+ const HOUR = 3_600_000;
3703
+ const START = Date.UTC(2026, 2, 16);
3704
+ // Hourly p95 latency of one service over three days, as an API delivers it.
3705
+ const READINGS = Array.from({ length: 72 }, (_, i) => ({
3706
+ t: START + i * HOUR,
3707
+ p95: Math.round(220 + 60 * Math.sin(i / 4) + ((i * 37) % 29) + (i >= 40 && i < 46 ? 150 : 0)),
3708
+ }));
3709
+
3710
+ const when = (ms: number) => new Date(ms).toLocaleString("en-GB", { weekday: "short", hour: "2-digit", minute: "2-digit", timeZone: "UTC" });
3711
+
3712
+ export default function MarkARange() {
3713
+ const [range, setRange] = useState<ChartRange | null>(null);
3714
+ // `initialView` is read once, so a zoom into the range is a new chart that starts there.
3715
+ const [zoomed, setZoomed] = useState<ChartRange | null>(null);
3716
+ return (
3717
+ <>
3718
+ <Course key={zoomed === null ? "all" : `${zoomed.from}-${zoomed.to}`} zoomed={zoomed} onRange={setRange} />
3719
+ <Text as="p">{range === null ? "No range marked yet." : `Marked: ${when(range.from)} to ${when(range.to)} (UTC)`}</Text>
3720
+ <Button disabled={range === null} onClick={() => setZoomed(range)}>
3721
+ Zoom into it
3722
+ </Button>
3723
+ </>
3724
+ );
3725
+ }
3726
+
3727
+ function Course({ zoomed, onRange }: { zoomed: ChartRange | null; onRange: (range: ChartRange) => void }) {
3728
+ const { Chart, XAxis, YAxis, Line } = useChart(READINGS, {
3729
+ onRange,
3730
+ initialView: zoomed === null ? undefined : { domains: { [zoomed.axisId]: [zoomed.from, zoomed.to] } },
3731
+ });
3732
+ return (
3733
+ <Chart height={240} aria-label="Hourly p95 latency over three days">
3734
+ <XAxis value="t" time domain="data" zoomable />
3735
+ <YAxis label="ms" />
3736
+ <Line value="p95" name="p95" />
3737
+ <Tooltip mode="x" />
3738
+ </Chart>
3739
+ );
3740
+ }
3741
+ ```
3742
+
3575
3743
  #### Keyboard
3576
3744
 
3745
+ | Key | Action |
3746
+ |---|---|
3747
+ | `Alt + drag` | Mark a range, where `onRange` is given and the x axis is `zoomable`; a plain drag pans. |
3748
+ | `Space` | Start a range at the active point; Space again ends it, Escape cancels. Needs a `Tooltip`. |
3749
+
3577
3750
  The keys of [Chart](#chart-1) apply here.
3578
3751
 
3579
3752
  #### API
@@ -3582,27 +3755,27 @@ The keys of [Chart](#chart-1) apply here.
3582
3755
 
3583
3756
  | Prop | Type | Default | Description |
3584
3757
  |---|---|---|---|
3585
- | `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 75 more |
3586
- | `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 11 more |
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 79 more |
3759
+ | `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 |
3587
3760
  | `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) |
3588
- | `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 36 more |
3589
- | `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) |
3590
- | `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 6 more |
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 39 more |
3762
+ | `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 7 more |
3591
3764
  | `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) |
3592
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 7 more |
3593
- | `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 67 more |
3594
- | `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 8 more |
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 71 more |
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 9 more |
3595
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 21 more |
3596
3769
  | `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) |
3597
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), [Mark bands, limits and cells (Chart)](#chart-1), [Compare progress with a plan (Line)](#line) and 7 more |
3598
3771
  | `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) |
3599
3772
  | `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 |
3600
3773
 
3601
- ##### `ChartOptions<Z = unknown>`
3774
+ ##### `ChartOptions<Z = unknown, V extends number | string = number>`
3602
3775
 
3603
3776
  | Prop | Type | Default | Description |
3604
3777
  |---|---|---|---|
3605
- | `initialView` | [`ChartView`](#view) | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible](#zoom-and-pan), [Hide a series from the legend (Legend)](#legend) |
3778
+ | `initialView` | [`ChartView`](#view) | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible](#zoom-and-pan), [Mark a range](#zoom-and-pan), [Hide a series from the legend (Legend)](#legend) |
3606
3779
 
3607
3780
  ###### Events
3608
3781
 
@@ -3610,6 +3783,7 @@ The keys of [Chart](#chart-1) apply here.
3610
3783
  |---|---|---|---|
3611
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) |
3612
3785
  | `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
+ | `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) |
3613
3787
 
3614
3788
  ##### Types on this page
3615
3789
 
@@ -3681,6 +3855,19 @@ A custom tooltip's `render` receives it.
3681
3855
  | `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
3682
3856
  | `yPx` *required* | `number` | — | Pixel position of the primary hit. |
3683
3857
 
3858
+ ###### `ChartRange<V extends number | string = number>`
3859
+
3860
+ A stretch of an x axis the reader marked (`onRange`): `from` is not after
3861
+ `to`, both in the axis' own values - numbers on a numeric or time axis
3862
+ (milliseconds). A caller that reads numbers takes the default and needs no
3863
+ cast; `V` is what a category axis widens it to.
3864
+
3865
+ | Prop | Type | Default | Description |
3866
+ |---|---|---|---|
3867
+ | `axisId` *required* | `string` | — | <br>Shown in: [Mark a range](#zoom-and-pan) |
3868
+ | `from` *required* | `V` | — | <br>Shown in: [Mark a range](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
3869
+ | `to` *required* | `V` | — | <br>Shown in: [Mark a range](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
3870
+
3684
3871
  ###### `NumberOptions`
3685
3872
 
3686
3873
  The options of `Intl.NumberFormat`, plus `decimals` for a fixed number of
@@ -3734,7 +3921,7 @@ The optional numbers of a box by name.
3734
3921
 
3735
3922
  - No zoom along y, no rectangle zoom and no navigator: the x span is the one zoom there is.
3736
3923
 
3737
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
3924
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
3738
3925
 
3739
3926
  ### View
3740
3927
 
@@ -3919,11 +4106,11 @@ The keys of [Chart](#chart-1) apply here.
3919
4106
 
3920
4107
  #### API
3921
4108
 
3922
- ##### `ChartOptions<Z = unknown>`
4109
+ ##### `ChartOptions<Z = unknown, V extends number | string = number>`
3923
4110
 
3924
4111
  | Prop | Type | Default | Description |
3925
4112
  |---|---|---|---|
3926
- | `initialView` | `ChartView` | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#zoom-and-pan), [Hide a series from the legend (Legend)](#legend) |
4113
+ | `initialView` | `ChartView` | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#zoom-and-pan), [Mark a range (Zoom and pan)](#zoom-and-pan), [Hide a series from the legend (Legend)](#legend) |
3927
4114
 
3928
4115
  ###### Events
3929
4116
 
@@ -3931,12 +4118,13 @@ The keys of [Chart](#chart-1) apply here.
3931
4118
  |---|---|---|---|
3932
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) |
3933
4120
  | `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
+ | `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) |
3934
4122
 
3935
4123
  ##### `ChartView`
3936
4124
 
3937
4125
  | Prop | Type | Default | Description |
3938
4126
  |---|---|---|---|
3939
- | `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) |
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) |
3940
4128
  | `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) |
3941
4129
 
3942
4130
  ##### Types on this page
@@ -3953,6 +4141,19 @@ A custom tooltip's `render` receives it.
3953
4141
  | `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
3954
4142
  | `yPx` *required* | `number` | — | Pixel position of the primary hit. |
3955
4143
 
4144
+ ###### `ChartRange<V extends number | string = number>`
4145
+
4146
+ A stretch of an x axis the reader marked (`onRange`): `from` is not after
4147
+ `to`, both in the axis' own values - numbers on a numeric or time axis
4148
+ (milliseconds). A caller that reads numbers takes the default and needs no
4149
+ cast; `V` is what a category axis widens it to.
4150
+
4151
+ | Prop | Type | Default | Description |
4152
+ |---|---|---|---|
4153
+ | `axisId` *required* | `string` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan) |
4154
+ | `from` *required* | `V` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
4155
+ | `to` *required* | `V` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
4156
+
3956
4157
  ###### `AxisSpans`
3957
4158
 
3958
4159
  The span each zoomable x axis shows, by axis `id`, in the axis' units.
@@ -4003,7 +4204,7 @@ The optional numbers of a box by name.
4003
4204
 
4004
4205
  - 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.
4005
4206
 
4006
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
4207
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
4007
4208
 
4008
4209
  ## Series
4009
4210
 
@@ -4019,6 +4220,8 @@ import { useChart } from "@umriss-ui/charts";
4019
4220
 
4020
4221
  A missing reading breaks the line; nothing is drawn across a gap. With `step` a value holds until the next one, for things logged only when they change.
4021
4222
 
4223
+ A `value` given as a function is compared by its source text, so one that reads something outside its row (`(d) => d.duration * factor`) keeps its old course when that changes, and the development build warns once. Carry the value in the rows, or give the series a `key` that changes with it.
4224
+
4022
4225
  #### Examples
4023
4226
 
4024
4227
  ##### One series
@@ -4469,11 +4672,11 @@ The keys of [Chart](#chart-1) apply here.
4469
4672
 
4470
4673
  | Prop | Type | Default | Description |
4471
4674
  |---|---|---|---|
4472
- | `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 47 more |
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 50 more |
4473
4676
  | `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) |
4474
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 4 more |
4475
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 6 more |
4476
- | `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 47 more |
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 50 more |
4477
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), [Table a long series (Tooltip)](#tooltip) and 1 more |
4478
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), [Watch latency against its objective (Scenarios)](#watch-latency-against-its-objective) and 1 more |
4479
4682
  | `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) |
@@ -4519,8 +4722,8 @@ type NumberField<T> = NoInfer<
4519
4722
  A **value** computed from the row - the function form of `Value`.
4520
4723
  Compared by its source text, not its identity - an inline function is new
4521
4724
  on every render. Known limit: one that reads a
4522
- changed closure variable under the same text is not run again; pass a new
4523
- data reference then. A native or bound function is compared by identity.
4725
+ changed closure variable under the same text is not run again, and DEV warns;
4726
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
4524
4727
 
4525
4728
  ```ts
4526
4729
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -4539,9 +4742,9 @@ nothing.
4539
4742
 
4540
4743
  #### Known limits
4541
4744
 
4542
- - No smoothing: a curve between two readings would invent values nobody measured ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md)).
4745
+ - No smoothing: a curve between two readings would invent values nobody measured ([ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md)).
4543
4746
 
4544
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
4747
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
4545
4748
 
4546
4749
  ### Area
4547
4750
 
@@ -4997,8 +5200,8 @@ type NumberField<T> = NoInfer<
4997
5200
  A **value** computed from the row - the function form of `Value`.
4998
5201
  Compared by its source text, not its identity - an inline function is new
4999
5202
  on every render. Known limit: one that reads a
5000
- changed closure variable under the same text is not run again; pass a new
5001
- data reference then. A native or bound function is compared by identity.
5203
+ changed closure variable under the same text is not run again, and DEV warns;
5204
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
5002
5205
 
5003
5206
  ```ts
5004
5207
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -5241,6 +5444,45 @@ export default function Histogram() {
5241
5444
  }
5242
5445
  ```
5243
5446
 
5447
+ ##### Mark a range of bars
5448
+
5449
+ A category axis does not zoom, so a plain drag marks a range: `from` and `to` are the category names the rows hold, the earlier one first.
5450
+
5451
+ ```tsx
5452
+ import { Text } from "@umriss-ui/core";
5453
+ import { useState } from "react";
5454
+ import { Tooltip, useChart, type ChartRange } from "@umriss-ui/charts";
5455
+
5456
+ // February's spend per cost centre, as the ledger sends it.
5457
+ const SPEND = [
5458
+ { costCentre: "Sales", actual: 134_447 },
5459
+ { costCentre: "Marketing", actual: 76_588 },
5460
+ { costCentre: "Engineering", actual: 183_157 },
5461
+ { costCentre: "Design", actual: 51_761 },
5462
+ { costCentre: "Customer service", actual: 61_160 },
5463
+ { costCentre: "Finance", actual: 46_353 },
5464
+ { costCentre: "People", actual: 37_886 },
5465
+ { costCentre: "IT", actual: 74_313 },
5466
+ { costCentre: "Facilities", actual: 71_848 },
5467
+ ];
5468
+
5469
+ export default function MarkARangeOfBars() {
5470
+ const [range, setRange] = useState<ChartRange<string> | null>(null);
5471
+ const { Chart, XAxis, YAxis, Bar } = useChart(SPEND, { onRange: setRange });
5472
+ return (
5473
+ <>
5474
+ <Chart height={260} aria-label="Actual spend per cost centre in February">
5475
+ <XAxis value="costCentre" />
5476
+ <YAxis label="€" />
5477
+ <Bar value="actual" name="Actual" />
5478
+ <Tooltip mode="x" />
5479
+ </Chart>
5480
+ <Text as="p">{range === null ? "Drag across the bars to mark a range." : `Marked: ${range.from} to ${range.to}`}</Text>
5481
+ </>
5482
+ );
5483
+ }
5484
+ ```
5485
+
5244
5486
  #### When to use something else
5245
5487
 
5246
5488
  - Causes ranked by how much they contribute → Pareto
@@ -5255,11 +5497,11 @@ The keys of [Chart](#chart-1) apply here.
5255
5497
 
5256
5498
  | Prop | Type | Default | Description |
5257
5499
  |---|---|---|---|
5258
- | `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 14 more |
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 15 more |
5259
5501
  | `xAxisId` | `string` | `"x"` | Binding to an x axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
5260
5502
  | `yAxisId` | `string` | `"y"` | Binding to a y axis.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
5261
5503
  | `data` | `readonly T[]` | — | Series-own data; overrides the container data.<br>Shown in: [Bind every kind of series to a second axis (Axis)](#axis) |
5262
- | `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 14 more |
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 15 more |
5263
5505
  | `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) |
5264
5506
  | `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) |
5265
5507
  | `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) |
@@ -5304,8 +5546,8 @@ type NumberField<T> = NoInfer<
5304
5546
  A **value** computed from the row - the function form of `Value`.
5305
5547
  Compared by its source text, not its identity - an inline function is new
5306
5548
  on every render. Known limit: one that reads a
5307
- changed closure variable under the same text is not run again; pass a new
5308
- data reference then. A native or bound function is compared by identity.
5549
+ changed closure variable under the same text is not run again, and DEV warns;
5550
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
5309
5551
 
5310
5552
  ```ts
5311
5553
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -5326,7 +5568,7 @@ nothing.
5326
5568
 
5327
5569
  - 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.
5328
5570
 
5329
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
5571
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
5330
5572
 
5331
5573
  ### Scatter
5332
5574
 
@@ -5633,8 +5875,8 @@ type NumberField<T> = NoInfer<
5633
5875
  A **value** computed from the row - the function form of `Value`.
5634
5876
  Compared by its source text, not its identity - an inline function is new
5635
5877
  on every render. Known limit: one that reads a
5636
- changed closure variable under the same text is not run again; pass a new
5637
- data reference then. A native or bound function is compared by identity.
5878
+ changed closure variable under the same text is not run again, and DEV warns;
5879
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
5638
5880
 
5639
5881
  ```ts
5640
5882
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -6193,8 +6435,8 @@ type NumberField<T> = NoInfer<
6193
6435
  A **value** computed from the row - the function form of `Value`.
6194
6436
  Compared by its source text, not its identity - an inline function is new
6195
6437
  on every render. Known limit: one that reads a
6196
- changed closure variable under the same text is not run again; pass a new
6197
- data reference then. A native or bound function is compared by identity.
6438
+ changed closure variable under the same text is not run again, and DEV warns;
6439
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
6198
6440
 
6199
6441
  ```ts
6200
6442
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -6237,7 +6479,7 @@ nothing.
6237
6479
 
6238
6480
  - 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.
6239
6481
 
6240
- What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.43.0/docs/adr/0032-what-umriss-is-not.md).
6482
+ What umriss deliberately does not build, and why: [ADR-0032](https://github.com/romanhaendler/umriss-ui/blob/charts-v0.45.0/docs/adr/0032-what-umriss-is-not.md).
6241
6483
 
6242
6484
  ### StateBand
6243
6485
 
@@ -6871,8 +7113,8 @@ type NumberField<T> = NoInfer<
6871
7113
  A **value** computed from the row - the function form of `Value`.
6872
7114
  Compared by its source text, not its identity - an inline function is new
6873
7115
  on every render. Known limit: one that reads a
6874
- changed closure variable under the same text is not run again; pass a new
6875
- data reference then. A native or bound function is compared by identity.
7116
+ changed closure variable under the same text is not run again, and DEV warns;
7117
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
6876
7118
 
6877
7119
  ```ts
6878
7120
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -7167,8 +7409,8 @@ type NumberField<T> = NoInfer<
7167
7409
  A **value** computed from the row - the function form of `Value`.
7168
7410
  Compared by its source text, not its identity - an inline function is new
7169
7411
  on every render. Known limit: one that reads a
7170
- changed closure variable under the same text is not run again; pass a new
7171
- data reference then. A native or bound function is compared by identity.
7412
+ changed closure variable under the same text is not run again, and DEV warns;
7413
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
7172
7414
 
7173
7415
  ```ts
7174
7416
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -8110,11 +8352,11 @@ The keys of [Chart](#chart-1) apply here.
8110
8352
 
8111
8353
  | Prop | Type | Default | Description |
8112
8354
  |---|---|---|---|
8113
- | `value` *required* | `number \| Date` | — | Where the line lies, in domain units of its axis; along a time axis an instant, in milliseconds or as a `Date`.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 11 more |
8355
+ | `value` *required* | `number \| Date` | — | Where the line lies, in domain units of its axis; along a time axis an instant, in milliseconds or as a `Date`.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 12 more |
8114
8356
  | `axisId` | `string` | the first axis of the orientation | Axis on which the value lies.<br>Shown in: [Sort causes and collect the tail (Pareto)](#pareto) |
8115
8357
  | `orientation` | `"x" \| "y"` | `"y"` | Whether the value lies on the x or on the y axis - a limit is nearly always a value and not a moment.<br>Shown in: [Mark moments and windows along time](#limitline) |
8116
- | `severity` | `Severity`<br>`"warning" \| "alarm"` | `"alarm"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 10 more |
8117
- | `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 11 more |
8358
+ | `severity` | `Severity`<br>`"warning" \| "alarm"` | `"alarm"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 11 more |
8359
+ | `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 12 more |
8118
8360
  | `color` | `string` | — | Any CSS colour value, in place of the one the severity yields. A colour handed in instead of a severity puts the rule in the application and the presentation in the library, and the two drift apart as soon as a bound moves - so this is for the case that has no severity, not for taste. The line or band takes it; its label keeps the axis's text colour, which stays legible in both themes, and a short stroke before the text in the line's pattern takes this colour.<br>Shown in: [Mark moments and windows along time](#limitline) |
8119
8361
  | `inExtent` | `boolean` | `true` | Whether the value widens the axis extent, so that the limit is certainly visible.<br>Shown in: [Sort causes and collect the tail (Pareto)](#pareto), [Watch latency against its objective (Scenarios)](#watch-latency-against-its-objective), [Hold the tile length in control (Scenarios)](#hold-the-tile-length-in-control) |
8120
8362
 
@@ -8132,8 +8374,8 @@ The keys of [Chart](#chart-1) apply here.
8132
8374
  | `to` *required* | `number \| Date` | — | Upper edge, in domain units of its axis. A band drawn backwards (`from` above `to`) is drawn between the two all the same - the pair is a range, not a direction.<br>Shown in: [Lay a band under the series](#limitline), [Read limits above the states](#limitline), [Watch a kiln zone against its tolerance](#limitline) and 5 more |
8133
8375
  | `axisId` | `string` | the first axis of the orientation | Axis on which the value lies.<br>Shown in: [Sort causes and collect the tail (Pareto)](#pareto) |
8134
8376
  | `orientation` | `"x" \| "y"` | `"y"` | Whether the value lies on the x or on the y axis - a limit is nearly always a value and not a moment.<br>Shown in: [Mark moments and windows along time](#limitline) |
8135
- | `severity` | `Severity`<br>`"warning" \| "alarm"` | `"warning"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 10 more |
8136
- | `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 11 more |
8377
+ | `severity` | `Severity`<br>`"warning" \| "alarm"` | `"warning"` | Two severities, not five: five urgencies are five colours, and five colours on a dense screen are noise.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 11 more |
8378
+ | `label` | `string` | — | What the value means. A line at 90 does not say what 90 is.<br>Shown in: [Draw a limit](#limitline), [Lay a band under the series](#limitline), [Read limits above the states](#limitline) and 12 more |
8137
8379
  | `color` | `string` | — | Any CSS colour value, in place of the one the severity yields. A colour handed in instead of a severity puts the rule in the application and the presentation in the library, and the two drift apart as soon as a bound moves - so this is for the case that has no severity, not for taste. The line or band takes it; its label keeps the axis's text colour, which stays legible in both themes, and a short stroke before the text in the line's pattern takes this colour.<br>Shown in: [Mark moments and windows along time](#limitline) |
8138
8380
  | `inExtent` | `boolean` | `true` | Whether the value widens the axis extent, so that the limit is certainly visible.<br>Shown in: [Sort causes and collect the tail (Pareto)](#pareto), [Watch latency against its objective (Scenarios)](#watch-latency-against-its-objective), [Hold the tile length in control (Scenarios)](#hold-the-tile-length-in-control) |
8139
8381
 
@@ -8848,8 +9090,8 @@ type NumberField<T> = NoInfer<
8848
9090
  A **value** computed from the row - the function form of `Value`.
8849
9091
  Compared by its source text, not its identity - an inline function is new
8850
9092
  on every render. Known limit: one that reads a
8851
- changed closure variable under the same text is not run again; pass a new
8852
- data reference then. A native or bound function is compared by identity.
9093
+ changed closure variable under the same text is not run again, and DEV warns;
9094
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
8853
9095
 
8854
9096
  ```ts
8855
9097
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;
@@ -9823,15 +10065,15 @@ The keys of [Chart](#chart-1) apply here.
9823
10065
 
9824
10066
  | Prop | Type | Default | Description |
9825
10067
  |---|---|---|---|
9826
- | `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 62 more |
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 65 more |
9827
10069
  | `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) |
9828
10070
  | `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) |
9829
10071
 
9830
- ##### `ChartOptions<Z = unknown>`
10072
+ ##### `ChartOptions<Z = unknown, V extends number | string = number>`
9831
10073
 
9832
10074
  | Prop | Type | Default | Description |
9833
10075
  |---|---|---|---|
9834
- | `initialView` | [`ChartView`](#view) | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#zoom-and-pan), [Hide a series from the legend (Legend)](#legend) |
10076
+ | `initialView` | [`ChartView`](#view) | — | The view to start from, read once when the hook first runs; a different value on a later render is ignored, as React ignores a changed `defaultValue`. To go to a view later, call `setView`. The chart remembers none: where a view is kept is the application's decision.<br>Shown in: [Fit the y axis to what is visible (Zoom and pan)](#zoom-and-pan), [Mark a range (Zoom and pan)](#zoom-and-pan), [Hide a series from the legend (Legend)](#legend) |
9835
10077
 
9836
10078
  ###### Events
9837
10079
 
@@ -9839,6 +10081,7 @@ The keys of [Chart](#chart-1) apply here.
9839
10081
  |---|---|---|---|
9840
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) |
9841
10083
  | `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
+ | `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) |
9842
10085
 
9843
10086
  ##### Types on this page
9844
10087
 
@@ -9854,6 +10097,19 @@ A custom tooltip's `render` receives it.
9854
10097
  | `xPx` *required* | `number` | — | Pixel position of the crosshair (snapped to the data point). |
9855
10098
  | `yPx` *required* | `number` | — | Pixel position of the primary hit. |
9856
10099
 
10100
+ ###### `ChartRange<V extends number | string = number>`
10101
+
10102
+ A stretch of an x axis the reader marked (`onRange`): `from` is not after
10103
+ `to`, both in the axis' own values - numbers on a numeric or time axis
10104
+ (milliseconds). A caller that reads numbers takes the default and needs no
10105
+ cast; `V` is what a category axis widens it to.
10106
+
10107
+ | Prop | Type | Default | Description |
10108
+ |---|---|---|---|
10109
+ | `axisId` *required* | `string` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan) |
10110
+ | `from` *required* | `V` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
10111
+ | `to` *required* | `V` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
10112
+
9857
10113
  ###### `TooltipPoint<T = unknown>`
9858
10114
 
9859
10115
  One hit per series in the tooltip.
@@ -10722,7 +10978,7 @@ identity over the renders -, the `view`, its `domains` and `hidden`, and
10722
10978
  the setters that change them.
10723
10979
 
10724
10980
  ```ts
10725
- function useChart<Z>(rows: readonly Z[], options?: ChartOptions<Z>): ChartParts<Z>;
10981
+ function useChart<Z, V extends number | string = number>(rows: readonly Z[], options?: ChartOptions<Z, V>): ChartParts<Z>;
10726
10982
  ```
10727
10983
 
10728
10984
  Used on [Scenarios](#scenarios), [Installation](#installation), [Chart](#chart-1), [Axis](#axis), [Zoom and pan](#zoom-and-pan), [View](#view), [Line](#line), [Area](#area), [Bar](#bar), [Scatter](#scatter), [BoxPlot](#boxplot), [StateBand](#stateband), [Matrix](#matrix), [LimitLine](#limitline), [ControlChart](#controlchart), [Pareto](#pareto), [Tooltip](#tooltip), [Legend](#legend) and [Benchmark](#benchmark).
@@ -10802,6 +11058,14 @@ function controlLimits(values: readonly number[], origin: ControlLimitOrigin): C
10802
11058
 
10803
11059
  Used on [Scenarios](#scenarios), [LimitLine](#limitline) and [ControlChart](#controlchart).
10804
11060
 
11061
+ ##### `inDoubledHour`
11062
+
11063
+ Does the clock show this instant's time twice?
11064
+
11065
+ ```ts
11066
+ function inDoubledHour(t: number, zone?: string): boolean;
11067
+ ```
11068
+
10805
11069
  ##### `invalidateTheme`
10806
11070
 
10807
11071
  Explicit invalidation: devalue the cache, every living chart reads anew and
@@ -10815,10 +11079,11 @@ function invalidateTheme(): void;
10815
11079
 
10816
11080
  The local offset at an instant, as the shift of a grid that is to lie on
10817
11081
  local time: `wallClock = offset + k · step`. Taken at one instant, it holds
10818
- for a grid up to the next clock change.
11082
+ for a grid up to the next clock change. With a `timeZone` (an IANA name)
11083
+ the zone's offset, else the reader's.
10819
11084
 
10820
11085
  ```ts
10821
- function localOffset(instant: number): number;
11086
+ function localOffset(instant: number, timeZone?: string): number;
10822
11087
  ```
10823
11088
 
10824
11089
  ##### `pareto`
@@ -10974,11 +11239,16 @@ If two candidates fall on the same working time - the end of one interval and
10974
11239
  the beginning of the next - the earlier one stays: two labels on the same
10975
11240
  pixel would be one too many.
10976
11241
 
11242
+ With a `timeZone` the grid is the zone's, and `offset` is not read: a step
11243
+ of whole days stands on the zone's midnights, a shorter one on its whole
11244
+ multiples in real time - none in the hour a clock change removes, two in
11245
+ the one it doubles.
11246
+
10977
11247
  Both times come back, because the axis needs both: the working time to
10978
11248
  place the tick, the wall clock time to label it.
10979
11249
 
10980
11250
  ```ts
10981
- function workingTimeTicks(input: CalendarInput, from: number, to: number, step: number, offset?: number): WorkingTimeTick[];
11251
+ function workingTimeTicks(input: CalendarInput, from: number, to: number, step: number, offset?: number, timeZone?: string): WorkingTimeTick[];
10982
11252
  ```
10983
11253
 
10984
11254
  ##### `zones`
@@ -11328,12 +11598,12 @@ is typed by that `data` instead.
11328
11598
 
11329
11599
  | Prop | Type | Default | Description |
11330
11600
  |---|---|---|---|
11331
- | `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 75 more |
11332
- | `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 75 more |
11333
- | `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 75 more |
11334
- | `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 47 more |
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 79 more |
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 79 more |
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 79 more |
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 50 more |
11335
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 5 more |
11336
- | `Bar` *required* | `<T = Z>(props: `[`BarProps`](#bar)`<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 14 more |
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), [Bind every kind of series to a second axis (Axis)](#axis) and 15 more |
11337
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 3 more |
11338
11608
  | `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 |
11339
11609
  | `Matrix` *required* | `<T = Z>(props: `[`MatrixProps`](#matrix)`<T>) => null` | — | <br>Shown in: [Mark bands, limits and cells (Chart)](#chart-1), [Colour cells by their limits (Matrix)](#matrix), [Colour cells along a gradient (Matrix)](#matrix) and 1 more |
@@ -11346,6 +11616,7 @@ is typed by that `data` instead.
11346
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. |
11347
11617
  | `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) |
11348
11618
  | `showAllSeries` *required* | `() => void` | — | Shows every series again.<br>Shown in: [Hide a series from the legend (Legend)](#legend) |
11619
+ | `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) |
11349
11620
 
11350
11621
  Used on [Chart](#chart-1).
11351
11622
 
@@ -11365,6 +11636,21 @@ Used on [Benchmark](#benchmark).
11365
11636
 
11366
11637
  Its table stands on [Chart](#chart-1).
11367
11638
 
11639
+ ##### `ChartRange<V extends number | string = number>`
11640
+
11641
+ A stretch of an x axis the reader marked (`onRange`): `from` is not after
11642
+ `to`, both in the axis' own values - numbers on a numeric or time axis
11643
+ (milliseconds). A caller that reads numbers takes the default and needs no
11644
+ cast; `V` is what a category axis widens it to.
11645
+
11646
+ | Prop | Type | Default | Description |
11647
+ |---|---|---|---|
11648
+ | `axisId` *required* | `string` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan) |
11649
+ | `from` *required* | `V` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
11650
+ | `to` *required* | `V` | — | <br>Shown in: [Mark a range (Zoom and pan)](#zoom-and-pan), [Mark a range of bars (Bar)](#bar) |
11651
+
11652
+ Used on [Zoom and pan](#zoom-and-pan) and [Bar](#bar).
11653
+
11368
11654
  ##### `ChartsWording`
11369
11655
 
11370
11656
  Everything the charts put out as text.
@@ -11383,6 +11669,9 @@ Everything the charts put out as text.
11383
11669
  | `percent` *required* | `(value: string) => string` | — | A share in a normalised stack, its number already formatted. |
11384
11670
  | `walkHelp` *required* | `string` | — | The keys that walk the chart. |
11385
11671
  | `selectHelp` *required* | `string` | — | The key that reports the Active point - only where `onSelect` is given. |
11672
+ | `rangeStarted` *required* | `(from: string) => string` | — | Said when the keyboard sets the start of a range; the x value is written in the axis' format. |
11673
+ | `rangeMade` *required* | `(from: string, to: string) => string` | — | Said when the keyboard sets the end of a range. |
11674
+ | `rangeHelp` *required* | `string` | — | The key that marks a range - only where `onRange` is given and a tooltip stands. |
11386
11675
  | `zoomHelp` *required* | `string` | — | The keys that zoom and pan it - only where the chart can be zoomed. |
11387
11676
  | `legendHelp` *required* | `string` | — | The keys of the legend's entries - only where it has one to press. |
11388
11677
  | `allShown` *required* | `string` | — | Read out where hiding would leave nothing to see, and every series is shown instead. |
@@ -12048,8 +12337,8 @@ Used on [Tooltip](#tooltip).
12048
12337
  A **value** computed from the row - the function form of `Value`.
12049
12338
  Compared by its source text, not its identity - an inline function is new
12050
12339
  on every render. Known limit: one that reads a
12051
- changed closure variable under the same text is not run again; pass a new
12052
- data reference then. A native or bound function is compared by identity.
12340
+ changed closure variable under the same text is not run again, and DEV warns;
12341
+ carry the value in the rows, or give the series a `key` that changes with it. A native or bound function is compared by identity.
12053
12342
 
12054
12343
  ```ts
12055
12344
  type ValueFunction<T> = (d: T, index: number) => number | null | undefined;