basalt-ui 2.2.0 → 2.3.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/MIGRATING.md +33 -12
  3. package/dist/charts/BandStrip.d.ts +7 -7
  4. package/dist/charts/BandStrip.d.ts.map +1 -1
  5. package/dist/charts/Bars.d.ts +4 -4
  6. package/dist/charts/Bars.d.ts.map +1 -1
  7. package/dist/charts/ChartCard.d.ts +10 -10
  8. package/dist/charts/ChartCard.d.ts.map +1 -1
  9. package/dist/charts/ChartCard.js +2 -1
  10. package/dist/charts/Donut.d.ts +5 -5
  11. package/dist/charts/Donut.d.ts.map +1 -1
  12. package/dist/charts/DualPanel.d.ts +9 -9
  13. package/dist/charts/DualPanel.d.ts.map +1 -1
  14. package/dist/charts/Heatmap.d.ts +15 -15
  15. package/dist/charts/Heatmap.d.ts.map +1 -1
  16. package/dist/charts/MirroredBars.d.ts +9 -9
  17. package/dist/charts/MirroredBars.d.ts.map +1 -1
  18. package/dist/charts/Sparkline.d.ts +6 -6
  19. package/dist/charts/Sparkline.d.ts.map +1 -1
  20. package/dist/charts/StackedArea.d.ts +2 -2
  21. package/dist/charts/StackedArea.d.ts.map +1 -1
  22. package/dist/charts/compile.d.ts +11 -1
  23. package/dist/charts/compile.d.ts.map +1 -1
  24. package/dist/charts/compile.js +51 -2
  25. package/dist/charts/index.d.ts +1 -0
  26. package/dist/charts/index.d.ts.map +1 -1
  27. package/dist/charts/index.js +3 -1
  28. package/dist/charts/types.d.ts +62 -60
  29. package/dist/charts/types.d.ts.map +1 -1
  30. package/llms.txt +2 -2
  31. package/package.json +1 -1
  32. package/src/charts/BandStrip.tsx +12 -10
  33. package/src/charts/Bars.tsx +4 -4
  34. package/src/charts/ChartCard.tsx +17 -11
  35. package/src/charts/Donut.tsx +5 -5
  36. package/src/charts/DualPanel.tsx +13 -6
  37. package/src/charts/Heatmap.tsx +12 -12
  38. package/src/charts/MirroredBars.tsx +8 -8
  39. package/src/charts/Sparkline.tsx +6 -6
  40. package/src/charts/StackedArea.tsx +2 -2
  41. package/src/charts/compile.ts +82 -1
  42. package/src/charts/index.ts +1 -0
  43. package/src/charts/types.ts +68 -56
@@ -35,11 +35,11 @@ export type MirroredBarPane = {
35
35
  * entry throws: the pane would vanish, which reads as a measured zero. */
36
36
  key: string
37
37
  /** The pane's upper bound. `'auto'` (default) is its own visible maximum; a number pins it. */
38
- max?: number | 'auto'
38
+ max?: number | 'auto' | undefined
39
39
  /** When `'auto'`: floors the upper bound, so a quiet window does not amplify noise to full height. */
40
- autoMaxFloor?: number
40
+ autoMaxFloor?: number | undefined
41
41
  /** Tick count for this pane's own axis. Default 3. */
42
- ticks?: number
42
+ ticks?: number | undefined
43
43
  /** Tick and tooltip formatter, in the pane's OWN units. */
44
44
  format: (v: number) => string
45
45
  }
@@ -71,18 +71,18 @@ export type MirroredBarsProps<T, X extends ChartX = ChartX> = Pick<
71
71
  /** Drawn below the baseline. */
72
72
  down: MirroredBarPane
73
73
  /** The up pane's share of the height. Default 0.35: the pair is usually asymmetric. */
74
- upFraction?: number
74
+ upFraction?: number | undefined
75
75
  /** 0..1 of a bar's width that no source sample covers, drawn neutral across both panes. */
76
- getAbsentFraction?: (d: T) => number
76
+ getAbsentFraction?: ((d: T) => number) | undefined
77
77
  /** Per-datum bar opacity: a real but qualified measurement (a short bucket) drawn dimmer. */
78
- getBarOpacity?: (d: T) => number
78
+ getBarOpacity?: ((d: T) => number) | undefined
79
79
  /**
80
80
  * Collapse adjacent data where one band per datum would be sub-pixel. `merge` is the consumer's
81
81
  * (max, sum, or a rate from summed parts) and must keep `group[0]`'s x, the slot start.
82
82
  */
83
- fold?: { minBandPx?: number; merge: (group: T[]) => T }
83
+ fold?: { minBandPx?: number | undefined; merge: (group: T[]) => T } | undefined
84
84
  /** Default `'leading'`: x is a slot's start. */
85
- cursorResolution?: CartesianChartProps<T, X>['cursorResolution']
85
+ cursorResolution?: CartesianChartProps<T, X>['cursorResolution'] | undefined
86
86
  }
87
87
 
88
88
  const DEFAULT_UP_FRACTION = 0.35
@@ -29,18 +29,18 @@ export type SparklineProps = BasaltProps & {
29
29
  /** Height in px. */
30
30
  height: number
31
31
  /** Width in px. Default: the container's, measured by the host. */
32
- width?: number
32
+ width?: number | undefined
33
33
  /** Default `'line'`. */
34
- mark?: 'line' | 'bar'
34
+ mark?: 'line' | 'bar' | undefined
35
35
  /** A `VX.*` ref or a series token. Default `VX.faint`. */
36
- color?: string
36
+ color?: string | undefined
37
37
  /** Paints the last point in `emphasisColor` — the one reading a trend beside a KPI is for. */
38
- emphasizeLast?: boolean
38
+ emphasizeLast?: boolean | undefined
39
39
  /** Default `VX.accent`. */
40
- emphasisColor?: string
40
+ emphasisColor?: string | undefined
41
41
  /** Pending / error / empty, the same contract as every other kind. The placeholder fills the
42
42
  * sparkline's own `width` × `height`, so a loading tile does not shift when the trend lands. */
43
- state?: ChartState
43
+ state?: ChartState | undefined
44
44
  }
45
45
 
46
46
  type Point = { i: number; v: number | null }
@@ -25,7 +25,7 @@ export type StackedAreaBand<T> = Pick<
25
25
  'key' | 'label' | 'color' | 'getValue' | 'formatValue' | 'tooltip' | 'legend' | 'role' | 'note'
26
26
  > & {
27
27
  /** Default 0.85. */
28
- fillOpacity?: number
28
+ fillOpacity?: number | undefined
29
29
  }
30
30
 
31
31
  export type StackedAreaProps<T, X extends ChartX = ChartX> = Omit<
@@ -35,7 +35,7 @@ export type StackedAreaProps<T, X extends ChartX = ChartX> = Omit<
35
35
  /** The bands, bottom to top. */
36
36
  series: readonly StackedAreaBand<T>[]
37
37
  /** No `scale`: a log axis has no additive zero to stack from. */
38
- y?: Omit<NonNullable<CartesianChartProps<T, X>['y']>, 'scale'>
38
+ y?: Omit<NonNullable<CartesianChartProps<T, X>['y']>, 'scale'> | undefined
39
39
  }
40
40
 
41
41
  export function StackedArea<T, X extends ChartX = ChartX>(
@@ -39,6 +39,7 @@ import type {
39
39
  ChartTooltipRow,
40
40
  ChartX,
41
41
  PlotRow,
42
+ ZoneSpec,
42
43
  } from './types'
43
44
 
44
45
  /**
@@ -249,6 +250,86 @@ export function areaGradient(index: number, color: string): ChartGradient {
249
250
  }
250
251
  }
251
252
 
253
+ const areaTint = (color: string): string =>
254
+ `color-mix(in srgb, ${color} var(--vx-area-top), transparent)`
255
+
256
+ /**
257
+ * A series area filled by value zone: a vertical gradient whose stops sit at the zone boundaries.
258
+ * The engine writes `<linearGradient>` in the default `objectBoundingBox` units, so offsets are
259
+ * fractions of the AREA's own box, not of the plot: from the larger of the series' maximum and 0 down
260
+ * to the smaller of its minimum and 0, the baseline the area closes to even when the y domain
261
+ * excludes 0. A boundary at value `v` sits at `(ceiling - v) / (ceiling - floor)`. Hard edges:
262
+ * neighbouring zones meet at one offset; overlapping zones never run backwards. `null` when no zone
263
+ * reaches the area (the caller keeps the plain series-colour fade). Linear axes only.
264
+ */
265
+ export function zoneAreaGradient(
266
+ index: number,
267
+ zones: readonly ZoneSpec[],
268
+ values: readonly (number | null)[],
269
+ ): ChartGradient | null {
270
+ let floor = 0
271
+ let ceiling = 0
272
+ for (const v of values) {
273
+ if (v === null || !Number.isFinite(v)) continue
274
+ if (v < floor) floor = v
275
+ if (v > ceiling) ceiling = v
276
+ }
277
+ const span = ceiling - floor
278
+ if (span <= 0) return null
279
+ const offsetOf = (v: number): number => (ceiling - Math.min(ceiling, Math.max(floor, v))) / span
280
+ const reaching = zones
281
+ .filter((z) => !Number.isNaN(z.from) && !Number.isNaN(z.to) && z.to > floor && z.from < ceiling)
282
+ .toSorted((p, q) => q.to - p.to)
283
+ let previous = 0
284
+ const stops = reaching.flatMap((z) =>
285
+ [offsetOf(z.to), offsetOf(z.from)].map((raw) => {
286
+ previous = Math.max(previous, raw)
287
+ return { offset: previous, color: areaTint(z.fill) }
288
+ }),
289
+ )
290
+ if (stops.length === 0) return null
291
+ return { id: areaGradientId(index), x1: 0, y1: 0, x2: 0, y2: 1, stops }
292
+ }
293
+
294
+ let warnedLogZones = false
295
+
296
+ /** Zone stops are placed linearly in value; a log axis would put them in the wrong place (V2-SPEC
297
+ * § 9.9 precedent): dev throws, production logs once and keeps the series-colour fade. */
298
+ function guardLogZones(onLog: boolean): boolean {
299
+ if (!onLog) return false
300
+ const message = "area: 'zones' cannot use a log axis; use area: true"
301
+ if (process.env.NODE_ENV !== 'production') throw new Error(message)
302
+ // oxlint-disable-next-line no-console -- the production leg of a dev-only throw
303
+ if (!warnedLogZones) console.error(message)
304
+ warnedLogZones = true
305
+ return true
306
+ }
307
+
308
+ /** The gradient resource for a series' `area` fill: zones, a colour ref, or the series colour. */
309
+ function seriesAreaGradient<T, X extends ChartX>(
310
+ s: ChartSeries<T>,
311
+ index: number,
312
+ input: CartesianInput<T, X>,
313
+ axes: Axes<T>,
314
+ ): ChartGradient {
315
+ const plain = areaGradient(
316
+ index,
317
+ typeof s.area === 'string' && s.area !== 'zones' ? s.area : s.color,
318
+ )
319
+ if (s.area !== 'zones') return plain
320
+ const side = axes.onRight(s) ? 'right' : 'left'
321
+ const axisConfig = side === 'right' ? input.y2 : input.y
322
+ if (guardLogZones(axisConfig?.scale === 'log')) return plain
323
+ const zones = (input.zones ?? []).filter((z) => axes.scaleId(z.axisSide) === axes.scaleId(side))
324
+ return (
325
+ zoneAreaGradient(
326
+ index,
327
+ zones,
328
+ input.data.map((d) => s.getValue(d)),
329
+ ) ?? plain
330
+ )
331
+ }
332
+
252
333
  const isBar = <T>(s: ChartSeries<T>): boolean => s.mark === 'bar'
253
334
 
254
335
  /** The two synthetic totals standing in for a stack's bars in its AUTO domain only: the positive
@@ -548,7 +629,7 @@ export function compileCartesian<T, X extends ChartX>(
548
629
  const formatX = (v: ChartValue): string =>
549
630
  input.formatX ? input.formatX(v as X) : defaultFormatX(v)
550
631
  const gradients = input.series.flatMap((s, i) =>
551
- s.area ? [areaGradient(i, typeof s.area === 'string' ? s.area : s.color)] : [],
632
+ s.area ? [seriesAreaGradient(s, i, input, axes)] : [],
552
633
  )
553
634
 
554
635
  return {
@@ -28,6 +28,7 @@ export {
28
28
  } from './MirroredBars'
29
29
  export { ChartCard, type ChartCardProps, type ChartCardSlot } from './ChartCard'
30
30
  export { chartCursor } from './cursor'
31
+ export { chartCurve, type CurveName } from './curves'
31
32
  export type {
32
33
  AxisConfig,
33
34
  CartesianChartProps,
@@ -24,35 +24,44 @@ export type ChartSeries<T> = {
24
24
  * series whose `getValue` is always `null` is a legend-only entry (a zone or reference key). */
25
25
  getValue: (d: T) => number | null
26
26
  /** The legend swatch shape. Default: the kind's own mark (`line` in `Lines`, `bar` in `Bars`). */
27
- mark?: 'line' | 'bar' | 'area'
27
+ mark?: 'line' | 'bar' | 'area' | undefined
28
28
  /** Default `'solid'`; `'dashed'` is `VX.dashArray` on the line, the legend and the tooltip. */
29
- dash?: 'solid' | 'dashed'
29
+ dash?: 'solid' | 'dashed' | undefined
30
30
  /** Interpolation between points. Default `'monotone'`. A piecewise-constant quantity (a price
31
31
  * tier, a config value) is a step: a smoothed curve draws values that never existed. */
32
- curve?: CurveName
33
- strokeWidth?: number
32
+ curve?: CurveName | undefined
33
+ strokeWidth?: number | undefined
34
34
  /** Dims the plotted stroke (a faint companion line). Strokes only. */
35
- strokeOpacity?: number
35
+ strokeOpacity?: number | undefined
36
36
  /** Bars and areas. */
37
- fillOpacity?: number
38
- /** `Lines`: a soft gradient fill under the line — `true` in the series color, or a color ref. */
39
- area?: boolean | string
37
+ fillOpacity?: number | undefined
38
+ /** `Lines`: a soft gradient fill under the line — `true` in the series color, a color ref, or
39
+ * `'zones'`: a vertical gradient coloured by the chart's `zones` on this series' axis, hard stops
40
+ * at the zone boundaries (no zone in reach: the series color). Linear axes only. */
41
+ area?: boolean | 'zones' | (string & {}) | undefined
40
42
  /** Which y axis the series is measured against. `'right'` needs the chart's `y2`. */
41
- axis?: 'left' | 'right'
43
+ axis?: 'left' | 'right' | undefined
42
44
  /** Legend grouping: series → overlay → reference. Default `'series'`. */
43
- role?: 'series' | 'overlay' | 'reference'
45
+ role?: 'series' | 'overlay' | 'reference' | undefined
44
46
  /** `false` keeps it out of the legend (a companion line folded under `parent`). Default `true`. */
45
- legend?: boolean
47
+ legend?: boolean | undefined
46
48
  /** `false` draws and legends it but never lists it as a tooltip row. Default `true`. */
47
- tooltip?: boolean
49
+ tooltip?: boolean | undefined
48
50
  /** A companion (an MA line) names its parent, so a legend toggle and hover keep the pair. */
49
- parent?: string
51
+ parent?: string | undefined
50
52
  /** A short qualifier after the label in the legend and the tooltip row, in muted text. */
51
- note?: string
53
+ note?: string | undefined
52
54
  /** Per-series tooltip value format; receives the datum so a row can read more than the value. */
53
- formatValue?: (v: number, d: T) => string
55
+ formatValue?: ((v: number, d: T) => string) | undefined
54
56
  /** A marker at a point (a PR star, a status dot); `null` for none. */
55
- getMarker?: (d: T) => { color?: string; r?: number; fillOpacity?: number; ring?: boolean } | null
57
+ getMarker?:
58
+ | ((d: T) => {
59
+ color?: string | undefined
60
+ r?: number | undefined
61
+ fillOpacity?: number | undefined
62
+ ring?: boolean | undefined
63
+ } | null)
64
+ | undefined
56
65
  }
57
66
 
58
67
  /** One y axis. */
@@ -66,23 +75,24 @@ export type AxisConfig<T> = {
66
75
  | readonly [number, number]
67
76
  | 'auto'
68
77
  | ((data: readonly T[], visible: readonly ChartSeries<T>[]) => readonly [number, number])
78
+ | undefined
69
79
  /** When `'auto'`: the raw upper bound is at least this; padding applies after. */
70
- autoMaxFloor?: number
80
+ autoMaxFloor?: number | undefined
71
81
  /** When `'auto'`: the lower bound is at most this. Default `0` (a zero baseline); `Infinity` pads
72
82
  * down from the data minimum instead. Ignored on a log axis. */
73
- autoMinCeil?: number
83
+ autoMinCeil?: number | undefined
74
84
  /** When `'auto'`: padding multiplier away from the data. Default `1.1`. */
75
- autoPad?: number
85
+ autoPad?: number | undefined
76
86
  /** A tick count, or the exact tick values. Default: one per ~48px of plot height, ≤ 5. */
77
- ticks?: number | readonly number[]
87
+ ticks?: number | readonly number[] | undefined
78
88
  /** Tick and tooltip formatter. Default: compact numbers (`12.5k`). */
79
- format?: (v: number) => string
89
+ format?: ((v: number) => string) | undefined
80
90
  /** Horizontal grid rules. Default: on for `y`, off for `y2`. */
81
- grid?: boolean
91
+ grid?: boolean | undefined
82
92
  /** Round the domain outward to nice ticks. Default `false` (a fixed domain stays fixed). */
83
- nice?: boolean
93
+ nice?: boolean | undefined
84
94
  /** Default `'linear'`. A log axis floors at the smallest positive visible value. */
85
- scale?: 'linear' | 'log'
95
+ scale?: 'linear' | 'log' | undefined
86
96
  }
87
97
 
88
98
  /** A horizontal band on one y axis. `±Infinity` reaches the plot edge. */
@@ -90,7 +100,7 @@ export type ZoneSpec = {
90
100
  from: number
91
101
  to: number
92
102
  fill: string
93
- axisSide?: 'left' | 'right'
103
+ axisSide?: 'left' | 'right' | undefined
94
104
  }
95
105
 
96
106
  /**
@@ -99,18 +109,18 @@ export type ZoneSpec = {
99
109
  * from one hour to another, both hours included).
100
110
  */
101
111
  export type XZoneSpec<X extends ChartX = ChartX> = {
102
- from?: X
103
- to?: X
112
+ from?: X | undefined
113
+ to?: X | undefined
104
114
  fill: string
105
- align?: 'center' | 'edge'
115
+ align?: 'center' | 'edge' | undefined
106
116
  }
107
117
 
108
118
  /** A horizontal reference line — annotation only, no domain role beyond staying inside the plot. */
109
119
  export type RefLine = {
110
120
  value: number
111
121
  color: string
112
- dashed?: boolean
113
- axis?: 'left' | 'right'
122
+ dashed?: boolean | undefined
123
+ axis?: 'left' | 'right' | undefined
114
124
  }
115
125
 
116
126
  /** A fill between the line and a value, on one side of it, tracking the line. */
@@ -134,44 +144,46 @@ export type ChartTooltipRow = {
134
144
  export type ChartTooltipConfig<T, X extends ChartX = ChartX> = {
135
145
  /** The tooltip title. Default: the x value through `formatX`'s long form (a date reads
136
146
  * `Mon Sep 1 2026`). */
137
- formatHeader?: (x: X, d: T) => string
147
+ formatHeader?: ((x: X, d: T) => string) | undefined
138
148
  /** Rows after the per-series rows (a total, a zone badge, a context line). `visible`/`hidden` are
139
149
  * the legend's split, so a row never names a series the plot no longer draws. */
140
- rows?: (
141
- d: T,
142
- ctx: { visible: readonly ChartSeries<T>[]; hidden: readonly ChartSeries<T>[] },
143
- ) => readonly ChartTooltipRow[]
150
+ rows?:
151
+ | ((
152
+ d: T,
153
+ ctx: { visible: readonly ChartSeries<T>[]; hidden: readonly ChartSeries<T>[] },
154
+ ) => readonly ChartTooltipRow[])
155
+ | undefined
144
156
  /** Show this chart's tooltip while it FOLLOWS a sibling's cursor. Default `false`: only the chart
145
157
  * under the pointer or keyboard focus shows a tooltip and a live region. */
146
- onFollow?: boolean
158
+ onFollow?: boolean | undefined
147
159
  }
148
160
 
149
161
  export type ChartLegendConfig = {
150
162
  /** Default `'bottom'`. `'header'` draws the legend at the top of the chart body, spaced as the
151
163
  * enclosing `ChartCard`'s header legend row (in the chart's own markup, so it is in the server
152
164
  * HTML and the header never grows); outside a card it is `'top'`. */
153
- placement?: 'top' | 'bottom' | 'header'
165
+ placement?: 'top' | 'bottom' | 'header' | undefined
154
166
  /** Visually separate roles: series | overlay | reference. */
155
- groups?: boolean
167
+ groups?: boolean | undefined
156
168
  /** Clicking an entry hides that series. Default: on whenever there are two or more entries. */
157
- toggle?: boolean
169
+ toggle?: boolean | undefined
158
170
  }
159
171
 
160
172
  /** A height per container class, `base` below `regular`. A plain number is fixed. */
161
173
  export type ResponsiveChartHeight = {
162
174
  base: number
163
- regular?: number
164
- wide?: number
175
+ regular?: number | undefined
176
+ wide?: number | undefined
165
177
  }
166
178
 
167
179
  /** The three "nothing to draw" states, pending → error → empty. */
168
180
  export type ChartState = {
169
- pending?: boolean
181
+ pending?: boolean | undefined
170
182
  /** Truthy = the query failed; an `Error`'s message is the label. */
171
183
  error?: unknown
172
184
  /** `true`, or the empty label itself. The label is the one way to say WHY (`'No runs match the
173
185
  * filter'` vs `'No runs yet'`): § 9 fixes the state shape, so there is no reason field. */
174
- empty?: boolean | string
186
+ empty?: boolean | string | undefined
175
187
  }
176
188
 
177
189
  /** A row of `plot.rows(key)`: one per datum, `y: null` kept as a gap. */
@@ -215,27 +227,27 @@ export type CartesianChartProps<T, X extends ChartX = ChartX> = BasaltProps & {
215
227
  series: readonly ChartSeries<T>[]
216
228
  /** The plot's marks. Draw `plot.rows(key)` / `plot.visible`, never `series` directly. */
217
229
  marks: (plot: PlotContext<T, X>) => ChartMarks
218
- y?: AxisConfig<T>
230
+ y?: AxisConfig<T> | undefined
219
231
  /** A right axis; series with `axis: 'right'` and marks bound to `plot.y2` read it. */
220
- y2?: AxisConfig<T>
232
+ y2?: AxisConfig<T> | undefined
221
233
  /** A tick count, or the exact x tick values. Default: by container class. */
222
- xTicks?: number | readonly X[]
234
+ xTicks?: number | readonly X[] | undefined
223
235
  /** Tick label for an x value. Default: `DD.MM` for dates, compact numbers, strings verbatim. */
224
- formatX?: (x: X) => string
225
- zones?: readonly ZoneSpec[]
226
- xZones?: readonly XZoneSpec<X>[]
227
- refLines?: readonly RefLine[]
236
+ formatX?: ((x: X) => string) | undefined
237
+ zones?: readonly ZoneSpec[] | undefined
238
+ xZones?: readonly XZoneSpec<X>[] | undefined
239
+ refLines?: readonly RefLine[] | undefined
228
240
  /** `false` hides the legend. */
229
- legend?: ChartLegendConfig | false
241
+ legend?: ChartLegendConfig | false | undefined
230
242
  /** Default: the height law (measured width × class ratio, 160–420px, ≤ 45vh on touch). */
231
- height?: number | ResponsiveChartHeight
232
- state?: ChartState
243
+ height?: number | ResponsiveChartHeight | undefined
244
+ state?: ChartState | undefined
233
245
  /** Joins a named cursor group instead of the page-shared default. */
234
- cursorGroup?: string
246
+ cursorGroup?: string | undefined
235
247
  /** Default `'nearest'`. `'leading'` for a series keyed by a bucket's leading edge (a weekly
236
248
  * value keyed by its Monday). */
237
- cursorResolution?: CursorResolution
238
- tooltip?: ChartTooltipConfig<T, X> | false
249
+ cursorResolution?: CursorResolution | undefined
250
+ tooltip?: ChartTooltipConfig<T, X> | false | undefined
239
251
  }
240
252
 
241
253
  /** The x channel of a `plot.rows()` row, as an accessor (a field name does not type-check against a