@pond-ts/charts 0.54.0 → 0.56.2

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 (51) hide show
  1. package/CHANGELOG.md +451 -1
  2. package/dist/AreaChart.d.ts +47 -22
  3. package/dist/AreaChart.js +24 -1
  4. package/dist/BandChart.d.ts +29 -17
  5. package/dist/BarChart.d.ts +92 -49
  6. package/dist/BarChart.js +65 -8
  7. package/dist/BarList.d.ts +127 -0
  8. package/dist/BarList.js +84 -0
  9. package/dist/BoxList.d.ts +108 -0
  10. package/dist/BoxList.js +125 -0
  11. package/dist/BoxPlot.d.ts +63 -30
  12. package/dist/Candlestick.d.ts +5 -4
  13. package/dist/ChartRow.js +57 -6
  14. package/dist/Layers.js +22 -2
  15. package/dist/LineChart.d.ts +29 -26
  16. package/dist/ListTable.d.ts +51 -0
  17. package/dist/ListTable.js +143 -0
  18. package/dist/ScatterChart.d.ts +27 -17
  19. package/dist/YAxis.d.ts +29 -1
  20. package/dist/YAxis.js +19 -5
  21. package/dist/area.js +46 -15
  22. package/dist/band.js +13 -0
  23. package/dist/bars.d.ts +89 -18
  24. package/dist/bars.js +141 -30
  25. package/dist/column-names.d.ts +74 -0
  26. package/dist/column-names.js +2 -0
  27. package/dist/context.d.ts +40 -2
  28. package/dist/data.d.ts +19 -0
  29. package/dist/data.js +46 -0
  30. package/dist/dev.d.ts +2 -0
  31. package/dist/dev.js +2 -0
  32. package/dist/domain.d.ts +54 -1
  33. package/dist/domain.js +195 -2
  34. package/dist/format.d.ts +20 -0
  35. package/dist/format.js +23 -10
  36. package/dist/gaps.d.ts +33 -0
  37. package/dist/gaps.js +49 -0
  38. package/dist/index.d.ts +19 -13
  39. package/dist/index.js +21 -13
  40. package/dist/line.js +10 -1
  41. package/dist/list-source.d.ts +61 -0
  42. package/dist/list-source.js +5 -0
  43. package/dist/list.d.ts +205 -0
  44. package/dist/list.js +165 -0
  45. package/dist/theme.d.ts +42 -0
  46. package/dist/theme.js +24 -0
  47. package/dist/viewport.d.ts +9 -1
  48. package/dist/viewport.js +40 -4
  49. package/dist/yticks.d.ts +44 -0
  50. package/dist/yticks.js +55 -0
  51. package/package.json +3 -3
@@ -2,55 +2,86 @@ import { ValueSeries } from 'pond-ts';
2
2
  import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
3
3
  import { type BinRecord, type CategoryDatum } from './data.js';
4
4
  import { type Orientation } from './bars.js';
5
+ import type { NumericColumn, ValueNumericColumn } from './column-names.js';
5
6
  import type { DecimateOption } from './decimate.js';
6
- export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
7
- /**
8
- * The source series. Provide **exactly one** of `series` or `bins`.
9
- *
10
- * - A **`TimeSeries`** (interval / timeRange-keyed is the primary form each
11
- * event's key `[begin, end]` is a bar's x-span; a point-keyed series derives
12
- * its width from neighbour spacing) → single-series bars via `column`, or
13
- * stacked bars from a **wide** series via `columns`.
14
- * - A **`ValueSeries`** (`series.byValue('dist')`) bars against its value axis.
15
- * - A **`ReadonlyMap<group, TimeSeries>`** — one series per stack group, all on
16
- * the same bin grid, the shape
17
- * `series.partitionBy('host', { groups }).aggregate(seq, m).toMap()` returns.
18
- * Stacked bars, `column` names the shared value column, groups = map order.
19
- *
20
- * **Live charts:** `series.byValue(…)` / `.toMap()` mint fresh objects each
21
- * call, so an inline `series={…}` re-registers this layer every render on a
22
- * frequently re-rendering chart, memoize the projection (`useMemo`).
23
- */
24
- series?: TimeSeries<S> | ValueSeries<VS> | ReadonlyMap<string, TimeSeries<S>>;
25
- /**
26
- * `byColumn` **bin records** `Array<{ start, end, …aggregates }>` from a
27
- * value-band aggregation
28
- * (`series.byColumn('power', { width: 20 }, { seconds: })`). The value-axis
29
- * alternative to `series`: `column` / `columns` name the aggregate field(s) to
30
- * draw. Pair with `ordinal` for a category (band) axis.
31
- */
32
- bins?: readonly BinRecord[];
33
- /**
34
- * **Categorical** data — an ordered `{ label, value }[]`, one bar per category
35
- * on a first-class **ordinal category x-axis** (the container infers
36
- * `xKind:'category'` and builds a band scale over the labels). The transpose
37
- * view's "columns on x": each `label` is a category (ticker / account / zone),
38
- * `value` its bar height. Provide **exactly one** of `series` / `bins` /
39
- * `categories`; `categories` takes no `column`/`columns` and is **vertical only**
40
- * (categories on x). Colour per category via `binColors`. (Categorical-axis RFC,
41
- * Phase 1.)
42
- */
43
- categories?: readonly CategoryDatum[];
44
- /** Name of the numeric value column for the bar height (single series). Provide
45
- * `column` **or** `columns`, not both. */
46
- column?: string;
47
- /**
48
- * Stacked-segment columns, **bottom → top** — one segment per name. Use with a
49
- * **wide** `series` (e.g. `pivotByGroup` output) or with `bins`. Mutually
50
- * exclusive with `column`, and invalid with a `Map` series (there the segments
51
- * are the map's groups; use `column`).
52
- */
53
- columns?: readonly string[];
7
+ /**
8
+ * The **mode union** — the legal (source, columns) combinations, each a
9
+ * separate member so an illegal mix fails to compile instead of throwing at
10
+ * render ([PND-CHARTAPI]; the 2026-08 API review's #1 item). Column names are
11
+ * schema-derived where the source is a series (see `column-names.ts`, which
12
+ * also explains why a loosely-typed series still accepts any string).
13
+ *
14
+ * Members, in the order a reader meets them:
15
+ *
16
+ * - **`series` + `column`** — one bar per event. A `TimeSeries`
17
+ * (interval/timeRange-keyed draws true spans; a point key derives width
18
+ * from neighbour spacing), a `ValueSeries` (`series.byValue('dist')`), or a
19
+ * **`ReadonlyMap<group, TimeSeries>`** (the
20
+ * `partitionBy(…).aggregate(…).toMap()` shape) — for a `Map`, `column` names
21
+ * the shared value column and the map's order is the stack order.
22
+ * - **`series` + `columns`** a **wide** series stacked bottom top (e.g.
23
+ * `pivotByGroup` output). Invalid with a `Map` source: there the segments
24
+ * *are* the groups, so use `column`.
25
+ * - **`bins` + `column` / `columns`** — `byColumn` bin records
26
+ * (`Array<{ start, end, …aggregates }>`); the names are **aggregate
27
+ * fields** of the record, not schema columns, so they stay `string`. Pair
28
+ * with `ordinal` for a band axis.
29
+ * - **`categories`** an ordered `{ label, value }[]`, one bar per category.
30
+ * Takes **no** `column`/`columns` (each datum carries its own value).
31
+ * Vertical puts the categories on the ordinal **x** axis (the container's
32
+ * band scale); `orientation="horizontal"` puts them on **y** as unit slots
33
+ * and the value on x, and a `<YAxis>` with no explicit `ticks` labels one
34
+ * per category automatically ([PND-HCAT]).
35
+ *
36
+ * **Live charts:** `series.byValue(…)` / `.toMap()` mint fresh objects each
37
+ * call, so an inline `series={…}` re-registers this layer every render on a
38
+ * frequently re-rendering chart, memoize the projection (`useMemo`).
39
+ */
40
+ type BarChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
41
+ series: TimeSeries<S> | ReadonlyMap<string, TimeSeries<S>>;
42
+ column: NumericColumn<S>;
43
+ columns?: never;
44
+ bins?: never;
45
+ categories?: never;
46
+ } | {
47
+ series: ValueSeries<VS>;
48
+ column: ValueNumericColumn<VS>;
49
+ columns?: never;
50
+ bins?: never;
51
+ categories?: never;
52
+ } | {
53
+ series: TimeSeries<S>;
54
+ columns: readonly NumericColumn<S>[];
55
+ column?: never;
56
+ bins?: never;
57
+ categories?: never;
58
+ } | {
59
+ series: ValueSeries<VS>;
60
+ columns: readonly ValueNumericColumn<VS>[];
61
+ column?: never;
62
+ bins?: never;
63
+ categories?: never;
64
+ } | {
65
+ bins: readonly BinRecord[];
66
+ column: string;
67
+ columns?: never;
68
+ series?: never;
69
+ categories?: never;
70
+ } | {
71
+ bins: readonly BinRecord[];
72
+ columns: readonly string[];
73
+ column?: never;
74
+ series?: never;
75
+ categories?: never;
76
+ } | {
77
+ categories: readonly CategoryDatum[];
78
+ series?: never;
79
+ bins?: never;
80
+ column?: never;
81
+ columns?: never;
82
+ };
83
+ /** The props every {@link BarChartSource} mode shares. */
84
+ export interface BarChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
54
85
  /**
55
86
  * The single series' semantic identifier — what the data _is_. The theme maps
56
87
  * it to a {@link BarStyle} (`theme.bar[as] ?? theme.bar.default`). **Single
@@ -91,7 +122,10 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
91
122
  * - `'vertical'` — bars grow **up** from a value baseline, bins on the **x**
92
123
  * axis (time buckets, value bands). The column / time-histogram look.
93
124
  * - `'horizontal'` — bars grow **right**, bins on the **y** axis (a band axis
94
- * like heart-rate zones). Label the bands with `<YAxis ticks={[{ at, label }]}>`.
125
+ * like heart-rate zones). Label the bands with `<YAxis ticks={[{ at, label }]}>`
126
+ * — or, with `categories`, let the `<YAxis>` derive them ([PND-HCAT]): a
127
+ * horizontal categorical chart hands the axis its names and they land one
128
+ * per slot, so a funnel / ranking needs no hand-built tick list.
95
129
  *
96
130
  * A `'horizontal'` chart puts the **value** on the shared x axis, so its
97
131
  * container's x-kind is `'value'` — it cannot share a `<ChartContainer>` with
@@ -159,6 +193,14 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
159
193
  */
160
194
  index?: number;
161
195
  }
196
+ /**
197
+ * `<BarChart>`'s props: the shared knobs ({@link BarChartCommon}) plus
198
+ * **exactly one** legal source shape ({@link BarChartSource}). Mixing sources
199
+ * (`series` + `bins`) or column forms (`column` + `columns`) is a **compile**
200
+ * error, and a column name that isn't in the series' schema fails to compile
201
+ * too — both were runtime throws before [PND-CHARTAPI].
202
+ */
203
+ export type BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = BarChartCommon<S, VS> & BarChartSource<S, VS>;
162
204
  /**
163
205
  * A bar / histogram draw layer. In its simplest form, one rectangle per event
164
206
  * spanning the key's `[begin, end]` from the axis baseline to a numeric
@@ -203,4 +245,5 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
203
245
  * ```
204
246
  */
205
247
  export declare function BarChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, bins, categories, column, columns, as: semantic, colors, binColors, orientation, ordinal, id, axis, gap, decimate, legend, index, }: BarChartProps<S, VS>): null;
248
+ export {};
206
249
  //# sourceMappingURL=BarChart.d.ts.map
package/dist/BarChart.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useContext, useEffect, useMemo } from 'react';
2
2
  import { Interval, ValueSeries } from 'pond-ts';
3
- import { barsFromTimeSeries, barsFromValueSeries, categoryStack, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
3
+ import { barsFromTimeSeries, barsFromBins, barsFromValueSeries, categoryStack, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
4
4
  import { barAt, barExtent, barIndexAtTime, drawBars, drawStacks, resolveBarBaseline, stackAt, stackBinExtent, stackValueExtent, } from './bars.js';
5
5
  import { ContainerContext, LayersContext, } from './context.js';
6
6
  import { legendLabelFor, useLegendItems, } from './swatch.js';
@@ -69,9 +69,6 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
69
69
  if (column !== undefined || columns !== undefined) {
70
70
  throw new Error('<BarChart categories> takes no `column`/`columns` (each datum carries its own value)');
71
71
  }
72
- if (orientation === 'horizontal') {
73
- throw new Error('<BarChart categories> is vertical only (categories on x); horizontal category axes are not yet supported');
74
- }
75
72
  }
76
73
  const isMap = series instanceof Map;
77
74
  if (isMap && columns !== undefined) {
@@ -82,7 +79,14 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
82
79
  }
83
80
  // The single series' semantic label (its identity for the readout + selection):
84
81
  // the `as` role, else the value column. Used only on the single path.
85
- const label = semantic ?? column ?? id ?? 'value';
82
+ //
83
+ // [PND-BARSEM] normalizes a ONE-ENTRY `columns` onto that path, and its mark
84
+ // is the same mark `column` would name — so the lone entry stands in here,
85
+ // or `columns={['a']}` and `column="a"` would report different `SelectInfo.label`
86
+ // for the identical bar (found in review of #593).
87
+ const soleColumn = column ??
88
+ (columns !== undefined && columns.length === 1 ? columns[0] : undefined);
89
+ const label = semantic ?? soleColumn ?? id ?? 'value';
86
90
  // Build the chart-ready data view. Single-series *vertical* stays on the
87
91
  // original BarSeries path (its pixels are unchanged); everything else — any
88
92
  // stack, any horizontal — builds a StackedBarSeries (G === 1 for a single
@@ -99,6 +103,17 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
99
103
  if (cols === undefined) {
100
104
  throw new Error('<BarChart bins> needs `column` or `columns`');
101
105
  }
106
+ // [PND-BARSEM]: a ONE-column vertical histogram draws the same mark as a
107
+ // `series`+`column` chart, so it takes the same path — and with it the
108
+ // whole-slot hit target, the hover colour, the cursor readout and
109
+ // per-bar decimation. Capabilities follow what is drawn, not which prop
110
+ // produced it. Horizontal keeps the transposed stacked path.
111
+ if (cols.length === 1 && orientation !== 'horizontal') {
112
+ return {
113
+ kind: 'single',
114
+ bs: barsFromBins(bins, cols[0], { ordinal }),
115
+ };
116
+ }
102
117
  return { kind: 'stacked', ss: stacksFromBins(bins, cols, { ordinal }) };
103
118
  }
104
119
  if (isMap) {
@@ -112,6 +127,18 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
112
127
  }
113
128
  const s = series;
114
129
  if (columns !== undefined) {
130
+ // [PND-BARSEM]: a one-entry `columns` is a single series wearing the
131
+ // stack's clothes — same mark, so the same capabilities (see the `bins`
132
+ // branch above).
133
+ if (columns.length === 1 && orientation !== 'horizontal') {
134
+ const only = columns[0];
135
+ return {
136
+ kind: 'single',
137
+ bs: s instanceof ValueSeries
138
+ ? barsFromValueSeries(s, only)
139
+ : barsFromTimeSeries(s, only),
140
+ };
141
+ }
115
142
  return { kind: 'stacked', ss: stacksFromColumns(s, columns) };
116
143
  }
117
144
  if (column === undefined) {
@@ -178,7 +205,29 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
178
205
  }, [shape, orientation, binAxisKind]);
179
206
  const { bar } = container.theme;
180
207
  // Single-series style: the `as` role → theme bar style (the single channel).
181
- const singleStyle = (semantic !== undefined ? bar[semantic] : undefined) ?? bar.default;
208
+ //
209
+ // A shape [PND-BARSEM] normalized onto this path (a one-column `bins`, a
210
+ // one-entry `columns`) used to resolve its fill through the *stacked*
211
+ // channel — `colors[group] ?? theme.bar[group] ?? default` — so resolving
212
+ // only `as` here would silently drop a caller's `colors` map and the
213
+ // `theme.bar[<column>]` role, changing the bars' colour with no error
214
+ // (found in review of #593). The column name is that shape's group name, so
215
+ // the same three-step lookup is applied, `as` still winning when given.
216
+ const singleStyle = useMemo(() => {
217
+ const byRole = semantic !== undefined ? bar[semantic] : undefined;
218
+ if (byRole !== undefined)
219
+ return byRole;
220
+ if (soleColumn !== undefined) {
221
+ const override = colors?.[soleColumn];
222
+ const byColumn = bar[soleColumn];
223
+ if (override !== undefined) {
224
+ return { ...(byColumn ?? bar.default), fill: override };
225
+ }
226
+ if (byColumn !== undefined)
227
+ return byColumn;
228
+ }
229
+ return bar.default;
230
+ }, [bar, semantic, soleColumn, colors]);
182
231
  const gapPx = gap ?? bar.default.gap;
183
232
  // The stacked path's bar-thickness floor comes from `bar.default` (not the `as`
184
233
  // role — `as` is single-series only), matching how `gapPx` sources its default.
@@ -256,7 +305,10 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
256
305
  : {
257
306
  hitTest: (px, py, xScale, yScale) => {
258
307
  const baseline = resolveBarBaseline(yScale);
259
- const hit = barAt(bs, px, py, xScale, yScale, baseline, gapPx, singleStyle.minWidth);
308
+ // No `gapPx` the hit region is the bar's whole slot (its
309
+ // interval width, full plot height), not the inset rect the
310
+ // gap draws. See barSlotRect.
311
+ const hit = barAt(bs, px, py, xScale, yScale, baseline, singleStyle.minWidth);
260
312
  if (hit === null)
261
313
  return null;
262
314
  const [bi, begin, value] = hit;
@@ -302,8 +354,13 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
302
354
  ...(binBuckets !== null ? { binIntervals: () => binBuckets } : {}),
303
355
  // A categorical chart hands the container its ordered category names — the
304
356
  // ordinal axis domain the shared band scale + label formatter build on.
357
+ // Categorical labels go to the axis the categories actually land on:
358
+ // x for a vertical chart (the container's band scale), y for a
359
+ // horizontal one, where they label the unit slots ([PND-HCAT]).
305
360
  ...(categoryLabels !== null
306
- ? { xCategories: () => categoryLabels }
361
+ ? vertical
362
+ ? { xCategories: () => categoryLabels }
363
+ : { binCategories: () => categoryLabels }
307
364
  : {}),
308
365
  // No x-scrub flag for a stack / horizontal chart — hover + click read it
309
366
  // out instead (the flag is single-series-vertical only).
@@ -0,0 +1,127 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { SeriesSchema, ValueSeriesSchema } from 'pond-ts';
3
+ import { type BarListColumn, type ListCellSpec, type ListMarker, type ListRow, type ListSortDirection } from './list.js';
4
+ import { type ListRowsSource, type ListSeriesSource } from './list-source.js';
5
+ import { type ChartTheme } from './theme.js';
6
+ /** The props both BarList source doors share. */
7
+ export interface BarListCommon<R extends ListRow = ListRow> {
8
+ /**
9
+ * The bar lines, **top→bottom within each row** — each names a `values`
10
+ * entry for its length and optionally a theme role (`as`). Several columns
11
+ * stack as parallel lines per row (the to-site / from-site pairing), all on
12
+ * the **one shared scale**, so lengths compare across lines and rows alike.
13
+ */
14
+ columns: readonly BarListColumn[];
15
+ /**
16
+ * The shared scale's `[min, max]`. **Omitted ⇒ resolved from the data**:
17
+ * `[min(0, data min), data max]` over every bar column of every row. Set it
18
+ * to pin the scale across live updates (a re-sorting traffic list whose max
19
+ * changes every tick) or across sibling lists.
20
+ *
21
+ * Bars are **length-encoded from the domain minimum** — the component
22
+ * assumes non-negative values. A negative value stays in-domain (the auto
23
+ * fit widens below zero) but draws as a short left-anchored bar, not a
24
+ * diverging one; diverging bar lists are out of scope (transform upstream,
25
+ * or use {@link BoxList}, whose marks are positional).
26
+ */
27
+ domain?: readonly [number, number];
28
+ /**
29
+ * Name of the `values` entry that **ranks the list** — with several bar
30
+ * columns, this is the decision of which one drives the order. Missing /
31
+ * non-finite values sort last either direction. **Omitted ⇒ input order**
32
+ * (the chronological splits case).
33
+ */
34
+ sortBy?: string;
35
+ /** `'desc'` (default — largest on top, the ranked-list convention) or `'asc'`. */
36
+ sortDirection?: ListSortDirection;
37
+ /** Full custom comparator — **overrides** `sortBy`/`sortDirection`. */
38
+ sort?: (a: R, b: R) => number;
39
+ /** Data cell columns rendered **between the label and the bars**, in order. */
40
+ before?: readonly ListCellSpec<R>[];
41
+ /** Data cell columns rendered **after the bars**, in order (a split's
42
+ * speed / climb readouts). */
43
+ after?: readonly ListCellSpec<R>[];
44
+ /**
45
+ * A row's expanded detail (any node — a stats grid, a nested chart).
46
+ * **Providing it adds the chevron column**; expansion is per-row,
47
+ * uncontrolled, keyed on `row.key` (so it survives a re-sort), seeded by
48
+ * `defaultExpanded`. Omitted ⇒ no expander UI at all.
49
+ */
50
+ renderExpanded?: (row: R) => ReactNode;
51
+ /** Row keys expanded on first render. */
52
+ defaultExpanded?: readonly string[];
53
+ /** Observe a toggle (`expanded` is the row's **new** state). */
54
+ onExpandToggle?: (key: string, expanded: boolean) => void;
55
+ /**
56
+ * The selected row's `key`, marked with an inset edge in the annotation
57
+ * (marks) register — selection is a user's mark, not data. Consumer-owned
58
+ * state: pair with `onRowClick`. `null` / omitted ⇒ none.
59
+ */
60
+ selected?: string | null;
61
+ /** Row click (rows show hover + pointer affordances only when provided). */
62
+ onRowClick?: (row: R) => void;
63
+ /** Each bar line's height in px. **Omitted ⇒ `8`.** */
64
+ barHeight?: number;
65
+ /** Rule between rows (`theme.axis.grid`). **Omitted ⇒ `true`.** */
66
+ divided?: boolean;
67
+ /**
68
+ * Reference **markers** on the shared scale — each draws a dotted vertical
69
+ * rule through every row (annotation-register ink) with its `label` printed
70
+ * above the list, centred on the rule. An SLA line, a capacity, the fleet
71
+ * average. Marker values **join the auto domain fit** (a threshold above
72
+ * the data max widens the scale); under an explicit `domain` they clamp.
73
+ */
74
+ markers?: readonly ListMarker[];
75
+ /**
76
+ * The vertical **baseline rule** at the scale origin (the glyph cell's left
77
+ * edge, the row dividers' `axis.grid` ink). **Omitted ⇒ `false`** — a bar's track
78
+ * already shows where zero is; opt in when the tracks are visually quiet.
79
+ * (`<BoxList>` defaults it **on**: its lines float at `lower`, so the shared
80
+ * origin is what relates rows to each other.)
81
+ */
82
+ baseline?: boolean;
83
+ /** Styling — the same {@link ChartTheme} the canvas charts read; bars
84
+ * resolve `theme.bar[as]`. **Omitted ⇒ {@link defaultTheme}.** */
85
+ theme?: ChartTheme;
86
+ }
87
+ /**
88
+ * `<BarList>`'s props: the shared knobs ({@link BarListCommon}) plus **exactly
89
+ * one** source door ([PND-CHARTAPI]). Passing both `rows` and `series`, or
90
+ * neither, is now a **compile** error rather than a render-time throw.
91
+ *
92
+ * The two members differ in their row type on purpose. Through `rows`, a
93
+ * caller's `R` flows into every callback. Through `series` the rows are read
94
+ * internally and are plain {@link ListRow}s, so that member pins the callbacks
95
+ * to `ListRow` — annotating a callback with a custom row type while passing
96
+ * `series` no longer compiles, where before it silently lied (#590 review).
97
+ */
98
+ export type BarListProps<R extends ListRow = ListRow, S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = (BarListCommon<R> & ListRowsSource<R>) | (BarListCommon<ListRow> & ListSeriesSource<S, VS>);
99
+ /**
100
+ * A **ranked bar list** — the DOM sister of `<BarChart orientation="horizontal">`,
101
+ * for the table-shaped cases: one row per *entity* (interface, split, symbol),
102
+ * a label cell, one proportional bar line per configured column, optional data
103
+ * cells before/after, optional per-row expander. react-timeseries-charts'
104
+ * `HorizontalBarChart`, reconceived as what it always was: a table.
105
+ *
106
+ * **Standalone** — no `<ChartContainer>`; there is no time axis here. It takes
107
+ * a `theme` directly and renders a plain `<table>` (label cells can be links,
108
+ * cells align by table layout, the expander is a `colSpan` row).
109
+ *
110
+ * - **One shared scale.** Every bar of every row maps through one
111
+ * `[min, max]` (see `domain`), because cross-row comparison is the point.
112
+ * - **Gaps.** A missing / non-numeric value renders an empty track and sorts
113
+ * last — absence reads as absence, never as zero-drawn-long.
114
+ * - **Sorting.** `sortBy` + `sortDirection` for the common case, `sort` for
115
+ * anything else, input order otherwise.
116
+ *
117
+ * ```tsx
118
+ * <BarList
119
+ * rows={listRowsFromTimeSeries(splits, { label: (i) => `${i + 1}` })}
120
+ * columns={[{ column: 'speed' }]}
121
+ * after={[{ key: 'speed', align: 'right', render: (r) => fmtMph(r.values.speed) }]}
122
+ * renderExpanded={(r) => <SplitDetail row={r} />}
123
+ * />
124
+ * ```
125
+ */
126
+ export declare function BarList<R extends ListRow = ListRow, S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>(props: BarListProps<R, S, VS>): import("react/jsx-runtime").JSX.Element;
127
+ //# sourceMappingURL=BarList.d.ts.map
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { ValueSeries } from 'pond-ts';
4
+ import { listFraction, listRowsFromTimeSeries, listRowsFromValueSeries, resolveListDomain, sortListRows, } from './list.js';
5
+ import { isSeriesSource, } from './list-source.js';
6
+ import { ListTable } from './ListTable.js';
7
+ import { defaultTheme } from './theme.js';
8
+ /**
9
+ * A **ranked bar list** — the DOM sister of `<BarChart orientation="horizontal">`,
10
+ * for the table-shaped cases: one row per *entity* (interface, split, symbol),
11
+ * a label cell, one proportional bar line per configured column, optional data
12
+ * cells before/after, optional per-row expander. react-timeseries-charts'
13
+ * `HorizontalBarChart`, reconceived as what it always was: a table.
14
+ *
15
+ * **Standalone** — no `<ChartContainer>`; there is no time axis here. It takes
16
+ * a `theme` directly and renders a plain `<table>` (label cells can be links,
17
+ * cells align by table layout, the expander is a `colSpan` row).
18
+ *
19
+ * - **One shared scale.** Every bar of every row maps through one
20
+ * `[min, max]` (see `domain`), because cross-row comparison is the point.
21
+ * - **Gaps.** A missing / non-numeric value renders an empty track and sorts
22
+ * last — absence reads as absence, never as zero-drawn-long.
23
+ * - **Sorting.** `sortBy` + `sortDirection` for the common case, `sort` for
24
+ * anything else, input order otherwise.
25
+ *
26
+ * ```tsx
27
+ * <BarList
28
+ * rows={listRowsFromTimeSeries(splits, { label: (i) => `${i + 1}` })}
29
+ * columns={[{ column: 'speed' }]}
30
+ * after={[{ key: 'speed', align: 'right', render: (r) => fmtMph(r.values.speed) }]}
31
+ * renderExpanded={(r) => <SplitDetail row={r} />}
32
+ * />
33
+ * ```
34
+ */
35
+ export function BarList(props) {
36
+ // One normalized view of the union — `isSeriesSource` is the runtime
37
+ // narrowing; the doors are mutually exclusive by construction now.
38
+ const source = props;
39
+ const { rows, series, label, columns, domain, sortBy, sortDirection = 'desc', sort, before, after, renderExpanded, defaultExpanded, onExpandToggle, selected, onRowClick, markers, barHeight = 8, divided, baseline, theme = defaultTheme, } = source;
40
+ // A runtime guard for JS consumers and `any`-typed call sites — the
41
+ // props union makes both branches unreachable from typed TS, but a
42
+ // silently-ignored source prop is a worse failure than a throw.
43
+ if (isSeriesSource(props) === (rows !== undefined)) {
44
+ throw new Error('<BarList>: provide exactly one of `rows` (records) or `series` (one row per event)');
45
+ }
46
+ // The series door reads internally — starting from a pond series there is
47
+ // no shaping step (with `series`, R stays the default ListRow).
48
+ const allRows = useMemo(() => rows ??
49
+ (series instanceof ValueSeries
50
+ ? listRowsFromValueSeries(series, label !== undefined ? { label } : {})
51
+ : listRowsFromTimeSeries(series, label !== undefined ? { label } : {})), [rows, series, label]);
52
+ const sorted = useMemo(() => sortListRows(allRows, sortBy, sortDirection, sort), [allRows, sortBy, sortDirection, sort]);
53
+ const scale = useMemo(() => resolveListDomain(allRows, columns.map((c) => c.column), domain, markers?.map((m) => m.value)), [allRows, columns, domain, markers]);
54
+ const resolvedMarkers = useMemo(() => markers?.map((m) => ({
55
+ frac: listFraction(m.value, scale),
56
+ ...(m.label !== undefined ? { label: m.label } : {}),
57
+ })), [markers, scale]);
58
+ return (_jsx(ListTable, { rows: sorted, kind: "bar", markers: resolvedMarkers, before: before, after: after, renderExpanded: renderExpanded, defaultExpanded: defaultExpanded, onExpandToggle: onExpandToggle, selected: selected, onRowClick: onRowClick, divided: divided, baseline: baseline, theme: theme, renderGlyphs: (row) => (_jsx(_Fragment, { children: columns.map((col, ci) => {
59
+ const style = theme.bar[col.as ?? 'default'] ?? theme.bar.default;
60
+ const frac = listFraction(row.values[col.column], scale);
61
+ return (_jsxs("div", { "data-list-track": col.column, style: {
62
+ position: 'relative',
63
+ height: barHeight,
64
+ margin: '3px 0',
65
+ borderRadius: barHeight / 2,
66
+ overflow: 'hidden',
67
+ }, children: [_jsx("div", { style: {
68
+ position: 'absolute',
69
+ inset: 0,
70
+ background: style.fill,
71
+ opacity: 0.15,
72
+ } }), frac !== null && frac > 0 && (_jsx("div", { "data-list-bar": col.column, style: {
73
+ position: 'absolute',
74
+ top: 0,
75
+ bottom: 0,
76
+ left: 0,
77
+ width: `${frac * 100}%`,
78
+ background: style.fill,
79
+ opacity: style.opacity,
80
+ borderRadius: barHeight / 2,
81
+ } }))] }, `${ci} ${col.column}`));
82
+ }) })) }));
83
+ }
84
+ //# sourceMappingURL=BarList.js.map
@@ -0,0 +1,108 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { SeriesSchema, ValueSeriesSchema } from 'pond-ts';
3
+ import { type BoxListColumn, type ListCellSpec, type ListMarker, type ListRow, type ListSortDirection } from './list.js';
4
+ import { type ListRowsSource, type ListSeriesSource } from './list-source.js';
5
+ import { type ChartTheme } from './theme.js';
6
+ /** The props both BoxList source doors share. */
7
+ export interface BoxListCommon<R extends ListRow = ListRow> {
8
+ /**
9
+ * The box lines, **top→bottom within each row** — each names the `values`
10
+ * entries for its five-number summary (`lower`/`upper` required; `q1`+`q3`
11
+ * both-or-neither; `median` optional — the `<BoxPlot>` vocabulary), plus an
12
+ * optional current-value tick (`value`) with an inline `format`ted label.
13
+ * All lines share the **one scale** so distributions compare across the
14
+ * whole list.
15
+ */
16
+ columns: readonly BoxListColumn[];
17
+ /**
18
+ * The shared scale's `[min, max]`. **Omitted ⇒ resolved from the data**
19
+ * over every box's `lower`/`upper`/`value`: `[min(0, data min), data max]`.
20
+ */
21
+ domain?: readonly [number, number];
22
+ /**
23
+ * Name of the `values` entry that ranks the list. The box columns are
24
+ * plain `values` names, so any stat sorts — the current value
25
+ * (`sortBy="in_now"`), a p95, a median — with no stat-picking rule to
26
+ * remember. Missing sorts last. **Omitted ⇒ input order.**
27
+ */
28
+ sortBy?: string;
29
+ /** `'desc'` (default) or `'asc'`. */
30
+ sortDirection?: ListSortDirection;
31
+ /** Full custom comparator — **overrides** `sortBy`/`sortDirection`. */
32
+ sort?: (a: R, b: R) => number;
33
+ /** Data cell columns between the label and the boxes. */
34
+ before?: readonly ListCellSpec<R>[];
35
+ /** Data cell columns after the boxes. */
36
+ after?: readonly ListCellSpec<R>[];
37
+ /** A row's expanded detail; providing it adds the chevron column (see
38
+ * `BarListProps.renderExpanded`). */
39
+ renderExpanded?: (row: R) => ReactNode;
40
+ /** Row keys expanded on first render. */
41
+ defaultExpanded?: readonly string[];
42
+ /** Observe a toggle (`expanded` is the row's new state). */
43
+ onExpandToggle?: (key: string, expanded: boolean) => void;
44
+ /** The selected row's `key` (inset accent edge). Pair with `onRowClick`. */
45
+ selected?: string | null;
46
+ /** Row click (also gates the hover affordance). */
47
+ onRowClick?: (row: R) => void;
48
+ /** Each box line's height in px. **Omitted ⇒ `10`.** */
49
+ barHeight?: number;
50
+ /** Rule between rows. **Omitted ⇒ `true`.** */
51
+ divided?: boolean;
52
+ /**
53
+ * Reference **markers** on the shared scale — a dotted vertical rule
54
+ * through every row with the `label` printed above the list (see
55
+ * `BarListProps.markers`; identical semantics, including joining the auto
56
+ * domain fit).
57
+ */
58
+ markers?: readonly ListMarker[];
59
+ /**
60
+ * The vertical **baseline rule** at the scale origin (the glyph cell's left
61
+ * edge, the row dividers' `axis.grid` ink). **Omitted ⇒ `true`** — box lines float
62
+ * at their `lower` quantile, so the shared origin is what lets the eye
63
+ * relate rows to each other. Pass `false` to drop it.
64
+ */
65
+ baseline?: boolean;
66
+ /** Styling — boxes resolve `theme.box[as]`. **Omitted ⇒ {@link defaultTheme}.** */
67
+ theme?: ChartTheme;
68
+ }
69
+ /**
70
+ * `<BoxList>`'s props: the shared knobs ({@link BoxListCommon}) plus **exactly
71
+ * one** source door ([PND-CHARTAPI]). Passing both `rows` and `series`, or
72
+ * neither, is now a **compile** error rather than a render-time throw.
73
+ *
74
+ * The two members differ in their row type on purpose. Through `rows`, a
75
+ * caller's `R` flows into every callback. Through `series` the rows are read
76
+ * internally and are plain {@link ListRow}s, so that member pins the callbacks
77
+ * to `ListRow` — annotating a callback with a custom row type while passing
78
+ * `series` no longer compiles, where before it silently lied (#590 review).
79
+ */
80
+ export type BoxListProps<R extends ListRow = ListRow, S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = (BoxListCommon<R> & ListRowsSource<R>) | (BoxListCommon<ListRow> & ListSeriesSource<S, VS>);
81
+ /**
82
+ * A **distribution row list** — {@link BarList}'s sister, drawing a horizontal
83
+ * five-number box per configured column instead of a value bar: the light
84
+ * `lower→upper` range band, the stronger `q1→q3` body, the `median` line, and
85
+ * an optional **current-value tick** with a printed label (the esnet
86
+ * traffic-by-interface look: where traffic *ranges* vs where it *is now*).
87
+ *
88
+ * Same table contract as {@link BarList} (standalone, cells, sort, expander,
89
+ * one shared scale, gap-aware), same quantile vocabulary as the canvas
90
+ * `<BoxPlot>` (`lower`/`q1`/`median`/`q3`/`upper`, both-or-neither body,
91
+ * quantiles **pre-computed upstream** — `series.reduce` facts; the chart never
92
+ * computes them).
93
+ *
94
+ * ```tsx
95
+ * <BoxList
96
+ * rows={ifaceRows}
97
+ * columns={[
98
+ * { lower: 'in_p5', q1: 'in_p25', median: 'in_p50', q3: 'in_p75',
99
+ * upper: 'in_p95', value: 'in_now', format: fmtBps },
100
+ * { lower: 'out_p5', q1: 'out_p25', median: 'out_p50', q3: 'out_p75',
101
+ * upper: 'out_p95', value: 'out_now', format: fmtBps, as: 'secondary' },
102
+ * ]}
103
+ * sortBy="in_now"
104
+ * />
105
+ * ```
106
+ */
107
+ export declare function BoxList<R extends ListRow = ListRow, S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>(props: BoxListProps<R, S, VS>): import("react/jsx-runtime").JSX.Element;
108
+ //# sourceMappingURL=BoxList.d.ts.map