@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.
- package/CHANGELOG.md +451 -1
- package/dist/AreaChart.d.ts +47 -22
- package/dist/AreaChart.js +24 -1
- package/dist/BandChart.d.ts +29 -17
- package/dist/BarChart.d.ts +92 -49
- package/dist/BarChart.js +65 -8
- package/dist/BarList.d.ts +127 -0
- package/dist/BarList.js +84 -0
- package/dist/BoxList.d.ts +108 -0
- package/dist/BoxList.js +125 -0
- package/dist/BoxPlot.d.ts +63 -30
- package/dist/Candlestick.d.ts +5 -4
- package/dist/ChartRow.js +57 -6
- package/dist/Layers.js +22 -2
- package/dist/LineChart.d.ts +29 -26
- package/dist/ListTable.d.ts +51 -0
- package/dist/ListTable.js +143 -0
- package/dist/ScatterChart.d.ts +27 -17
- package/dist/YAxis.d.ts +29 -1
- package/dist/YAxis.js +19 -5
- package/dist/area.js +46 -15
- package/dist/band.js +13 -0
- package/dist/bars.d.ts +89 -18
- package/dist/bars.js +141 -30
- package/dist/column-names.d.ts +74 -0
- package/dist/column-names.js +2 -0
- package/dist/context.d.ts +40 -2
- package/dist/data.d.ts +19 -0
- package/dist/data.js +46 -0
- package/dist/dev.d.ts +2 -0
- package/dist/dev.js +2 -0
- package/dist/domain.d.ts +54 -1
- package/dist/domain.js +195 -2
- package/dist/format.d.ts +20 -0
- package/dist/format.js +23 -10
- package/dist/gaps.d.ts +33 -0
- package/dist/gaps.js +49 -0
- package/dist/index.d.ts +19 -13
- package/dist/index.js +21 -13
- package/dist/line.js +10 -1
- package/dist/list-source.d.ts +61 -0
- package/dist/list-source.js +5 -0
- package/dist/list.d.ts +205 -0
- package/dist/list.js +165 -0
- package/dist/theme.d.ts +42 -0
- package/dist/theme.js +24 -0
- package/dist/viewport.d.ts +9 -1
- package/dist/viewport.js +40 -4
- package/dist/yticks.d.ts +44 -0
- package/dist/yticks.js +55 -0
- package/package.json +3 -3
package/dist/BarChart.d.ts
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
columns
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
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
|
package/dist/BarList.js
ADDED
|
@@ -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
|