@pond-ts/charts 0.53.1 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1175 -4
- package/dist/AreaChart.d.ts +30 -15
- package/dist/AreaChart.js +46 -5
- package/dist/BandChart.d.ts +29 -17
- package/dist/BarChart.d.ts +109 -57
- package/dist/BarChart.js +82 -12
- 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/Layers.js +8 -1
- package/dist/LineChart.d.ts +30 -16
- package/dist/LineChart.js +46 -5
- package/dist/ListTable.d.ts +51 -0
- package/dist/ListTable.js +143 -0
- package/dist/ScatterChart.d.ts +27 -17
- package/dist/YAxis.js +15 -4
- package/dist/affine.d.ts +35 -14
- package/dist/affine.js +34 -17
- package/dist/area.js +4 -4
- package/dist/bars.d.ts +107 -25
- package/dist/bars.js +204 -39
- package/dist/column-names.d.ts +74 -0
- package/dist/column-names.js +2 -0
- package/dist/context.d.ts +50 -7
- package/dist/data.d.ts +77 -0
- package/dist/data.js +131 -22
- package/dist/decimate.js +7 -7
- package/dist/index.d.ts +19 -13
- package/dist/index.js +21 -13
- package/dist/line.js +2 -2
- 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 +30 -0
- package/dist/theme.js +24 -0
- package/package.json +3 -3
|
@@ -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
|
package/dist/BoxList.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { ValueSeries } from 'pond-ts';
|
|
4
|
+
import { listFraction, listRowsFromTimeSeries, listRowsFromValueSeries, resolveListDomain, sortListRows, validateBoxListColumn, } from './list.js';
|
|
5
|
+
import { isSeriesSource, } from './list-source.js';
|
|
6
|
+
import { ListTable, listInk } from './ListTable.js';
|
|
7
|
+
import { defaultTheme } from './theme.js';
|
|
8
|
+
/**
|
|
9
|
+
* A **distribution row list** — {@link BarList}'s sister, drawing a horizontal
|
|
10
|
+
* five-number box per configured column instead of a value bar: the light
|
|
11
|
+
* `lower→upper` range band, the stronger `q1→q3` body, the `median` line, and
|
|
12
|
+
* an optional **current-value tick** with a printed label (the esnet
|
|
13
|
+
* traffic-by-interface look: where traffic *ranges* vs where it *is now*).
|
|
14
|
+
*
|
|
15
|
+
* Same table contract as {@link BarList} (standalone, cells, sort, expander,
|
|
16
|
+
* one shared scale, gap-aware), same quantile vocabulary as the canvas
|
|
17
|
+
* `<BoxPlot>` (`lower`/`q1`/`median`/`q3`/`upper`, both-or-neither body,
|
|
18
|
+
* quantiles **pre-computed upstream** — `series.reduce` facts; the chart never
|
|
19
|
+
* computes them).
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <BoxList
|
|
23
|
+
* rows={ifaceRows}
|
|
24
|
+
* columns={[
|
|
25
|
+
* { lower: 'in_p5', q1: 'in_p25', median: 'in_p50', q3: 'in_p75',
|
|
26
|
+
* upper: 'in_p95', value: 'in_now', format: fmtBps },
|
|
27
|
+
* { lower: 'out_p5', q1: 'out_p25', median: 'out_p50', q3: 'out_p75',
|
|
28
|
+
* upper: 'out_p95', value: 'out_now', format: fmtBps, as: 'secondary' },
|
|
29
|
+
* ]}
|
|
30
|
+
* sortBy="in_now"
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function BoxList(props) {
|
|
35
|
+
// One normalized view of the union — `isSeriesSource` is the runtime
|
|
36
|
+
// narrowing; the doors are mutually exclusive by construction now.
|
|
37
|
+
const source = props;
|
|
38
|
+
const { rows, series, label, columns, domain, sortBy, sortDirection = 'desc', sort, before, after, renderExpanded, defaultExpanded, onExpandToggle, selected, onRowClick, markers, barHeight = 10, divided, baseline = true, theme = defaultTheme, } = source;
|
|
39
|
+
// A runtime guard for JS consumers and `any`-typed call sites — the
|
|
40
|
+
// props union makes both branches unreachable from typed TS, but a
|
|
41
|
+
// silently-ignored source prop is a worse failure than a throw.
|
|
42
|
+
if (isSeriesSource(props) === (rows !== undefined)) {
|
|
43
|
+
throw new Error('<BoxList>: provide exactly one of `rows` (records) or `series` (one row per event)');
|
|
44
|
+
}
|
|
45
|
+
for (const col of columns)
|
|
46
|
+
validateBoxListColumn(col);
|
|
47
|
+
// The series door reads internally — starting from a pond series there is
|
|
48
|
+
// no shaping step (with `series`, R stays the default ListRow).
|
|
49
|
+
const allRows = useMemo(() => rows ??
|
|
50
|
+
(series instanceof ValueSeries
|
|
51
|
+
? listRowsFromValueSeries(series, label !== undefined ? { label } : {})
|
|
52
|
+
: listRowsFromTimeSeries(series, label !== undefined ? { label } : {})), [rows, series, label]);
|
|
53
|
+
const sorted = useMemo(() => sortListRows(allRows, sortBy, sortDirection, sort), [allRows, sortBy, sortDirection, sort]);
|
|
54
|
+
const scale = useMemo(() => resolveListDomain(allRows, columns.flatMap((c) => c.value !== undefined
|
|
55
|
+
? [c.lower, c.upper, c.value]
|
|
56
|
+
: [c.lower, c.upper]), domain, markers?.map((m) => m.value)), [allRows, columns, domain, markers]);
|
|
57
|
+
const resolvedMarkers = useMemo(() => markers?.map((m) => ({
|
|
58
|
+
frac: listFraction(m.value, scale),
|
|
59
|
+
...(m.label !== undefined ? { label: m.label } : {}),
|
|
60
|
+
})), [markers, scale]);
|
|
61
|
+
return (_jsx(ListTable, { rows: sorted, kind: "box", 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) => (_jsx(BoxLine
|
|
62
|
+
// Index-qualified so two lines over the same quantile names
|
|
63
|
+
// (say, styled differently) never collide.
|
|
64
|
+
, { row: row, col: col, scale: scale, height: barHeight, style: theme.box[col.as ?? 'default'] ?? theme.box.default, ink: listInk(theme), fontSize: theme.font.size }, `${ci} ${col.lower} ${col.upper}`))) })) }));
|
|
65
|
+
}
|
|
66
|
+
/** One horizontal box line: range band → body → median → current tick + label. */
|
|
67
|
+
function BoxLine({ row, col, scale, height, style, ink, fontSize, }) {
|
|
68
|
+
const at = (name) => name === undefined ? null : listFraction(row.values[name], scale);
|
|
69
|
+
const lo = at(col.lower);
|
|
70
|
+
const hi = at(col.upper);
|
|
71
|
+
const q1 = at(col.q1);
|
|
72
|
+
const q3 = at(col.q3);
|
|
73
|
+
const med = at(col.median);
|
|
74
|
+
const tick = at(col.value);
|
|
75
|
+
const raw = col.value !== undefined ? row.values[col.value] : undefined;
|
|
76
|
+
const label = col.format !== undefined && typeof raw === 'number' && Number.isFinite(raw)
|
|
77
|
+
? col.format(raw)
|
|
78
|
+
: null;
|
|
79
|
+
const pct = (f) => `${f * 100}%`;
|
|
80
|
+
// The row keeps its slot height even when everything is missing — a gap
|
|
81
|
+
// reads as an empty line, not a collapsed row.
|
|
82
|
+
return (_jsxs("div", { "data-list-boxline": "", style: { position: 'relative', height: height + 4, margin: '2px 0' }, children: [lo !== null && hi !== null && (_jsx("div", { "data-list-range": "", style: {
|
|
83
|
+
position: 'absolute',
|
|
84
|
+
top: 2,
|
|
85
|
+
bottom: 2,
|
|
86
|
+
left: pct(lo),
|
|
87
|
+
width: pct(Math.max(hi - lo, 0)),
|
|
88
|
+
background: style.whisker,
|
|
89
|
+
opacity: 0.55,
|
|
90
|
+
borderRadius: height / 2,
|
|
91
|
+
} })), q1 !== null && q3 !== null && (_jsx("div", { "data-list-body": "", style: {
|
|
92
|
+
position: 'absolute',
|
|
93
|
+
top: 2,
|
|
94
|
+
bottom: 2,
|
|
95
|
+
left: pct(q1),
|
|
96
|
+
width: pct(Math.max(q3 - q1, 0)),
|
|
97
|
+
background: style.fill,
|
|
98
|
+
opacity: Math.min(style.fillOpacity * 2, 1),
|
|
99
|
+
borderRadius: 1,
|
|
100
|
+
} })), med !== null && (_jsx("div", { "data-list-median": "", style: {
|
|
101
|
+
position: 'absolute',
|
|
102
|
+
top: 2,
|
|
103
|
+
bottom: 2,
|
|
104
|
+
left: `calc(${pct(med)} - ${style.medianWidth / 2}px)`,
|
|
105
|
+
width: style.medianWidth,
|
|
106
|
+
background: style.median,
|
|
107
|
+
} })), tick !== null && (_jsxs(_Fragment, { children: [_jsx("div", { "data-list-tick": "", style: {
|
|
108
|
+
position: 'absolute',
|
|
109
|
+
top: 0,
|
|
110
|
+
bottom: 0,
|
|
111
|
+
left: `calc(${pct(tick)} - 1.5px)`,
|
|
112
|
+
width: 3,
|
|
113
|
+
background: style.stroke,
|
|
114
|
+
borderRadius: 1,
|
|
115
|
+
} }), label !== null && (_jsx("span", { "data-list-value": "", style: {
|
|
116
|
+
position: 'absolute',
|
|
117
|
+
left: `calc(${pct(tick)} + 8px)`,
|
|
118
|
+
top: '50%',
|
|
119
|
+
transform: 'translateY(-50%)',
|
|
120
|
+
whiteSpace: 'nowrap',
|
|
121
|
+
fontSize: fontSize + 1,
|
|
122
|
+
color: ink,
|
|
123
|
+
}, children: label }))] }))] }));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=BoxList.js.map
|
package/dist/BoxPlot.d.ts
CHANGED
|
@@ -1,37 +1,9 @@
|
|
|
1
1
|
import { ValueSeries } from 'pond-ts';
|
|
2
2
|
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
3
|
+
import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
3
4
|
import { type BoxShape } from './box.js';
|
|
4
5
|
import type { DecimateOption } from './decimate.js';
|
|
5
|
-
export interface
|
|
6
|
-
/**
|
|
7
|
-
* The source series. A `TimeSeries` plots against the time axis; a `ValueSeries`
|
|
8
|
-
* (`series.byValue('strike')`, or `ValueSeries.fromColumns` for natively
|
|
9
|
-
* value-keyed data — a per-strike IV distribution) against its value axis — the
|
|
10
|
-
* container infers which from the data, no axis-type prop (mirrors `<LineChart>`
|
|
11
|
-
* / `<ScatterChart>`). The box x-span is the key's `[begin, end)` for an
|
|
12
|
-
* interval-keyed `TimeSeries`, else synthesized from neighbour spacing (a
|
|
13
|
-
* point-keyed `TimeSeries`, or a `ValueSeries`) so the box keeps real width.
|
|
14
|
-
*/
|
|
15
|
-
series: TimeSeries<S> | ValueSeries<VS>;
|
|
16
|
-
/** Name of the numeric column for the lower whisker end (e.g. `p5` / `min`).
|
|
17
|
-
* **Required** — with `upper` it's the whisker reach. */
|
|
18
|
-
lower: string;
|
|
19
|
-
/**
|
|
20
|
-
* Name of the numeric column for the box bottom — first quartile (e.g. `p25`).
|
|
21
|
-
* **Optional:** omit `q1` **and** `q3` together for a **range-only** box — a
|
|
22
|
-
* whisker-only `lower→upper` segment, no body (a bid→ask IV mark). Giving just
|
|
23
|
-
* one of `q1`/`q3` throws.
|
|
24
|
-
*/
|
|
25
|
-
q1?: string;
|
|
26
|
-
/** Name of the numeric column for the median line (e.g. `p50`). **Optional** —
|
|
27
|
-
* omit for no centre line (independent of the box body). */
|
|
28
|
-
median?: string;
|
|
29
|
-
/** Name of the numeric column for the box top — third quartile (e.g. `p75`).
|
|
30
|
-
* **Optional** — omit with `q1` for a range-only box (see `q1`). */
|
|
31
|
-
q3?: string;
|
|
32
|
-
/** Name of the numeric column for the upper whisker end (e.g. `p95` / `max`).
|
|
33
|
-
* **Required** — with `lower` it's the whisker reach. */
|
|
34
|
-
upper: string;
|
|
6
|
+
export interface BoxPlotCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
35
7
|
/**
|
|
36
8
|
* The box series' semantic identifier — what the spread _is_ (e.g. `latency`).
|
|
37
9
|
* The theme maps it to a {@link BoxStyle} (`theme.box[as] ?? theme.box.default`
|
|
@@ -122,6 +94,66 @@ export interface BoxPlotProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
122
94
|
*/
|
|
123
95
|
index?: number;
|
|
124
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* BoxPlot's source + column props, a **union over the series kind** so the
|
|
99
|
+
* column names are checked against the schema that was actually passed
|
|
100
|
+
* ([PND-CHARTAPI]). A single member carrying `NumericColumn<S> |
|
|
101
|
+
* ValueNumericColumn<VS>` would silently widen to `string`: only one of the
|
|
102
|
+
* two generics is ever inferred, and the other falls back (measured in
|
|
103
|
+
* `spikes/charts-type-seam/`). Loosely-typed series still accept any name.
|
|
104
|
+
*/
|
|
105
|
+
type BoxPlotSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
106
|
+
/**
|
|
107
|
+
* The source series. **Live charts:** `series.byValue(…)` mints a
|
|
108
|
+
* *fresh* projection each call, so an inline `series={s.byValue('d')}`
|
|
109
|
+
* re-registers this layer every render — on a frequently re-rendering
|
|
110
|
+
* (e.g. scrub-driven) chart, memoize the projection (`useMemo`) so the
|
|
111
|
+
* layer isn't rebuilt each frame.
|
|
112
|
+
*/
|
|
113
|
+
series: TimeSeries<S>;
|
|
114
|
+
/** Lower whisker end (e.g. `p5` / `min`). **Required** — with `upper`
|
|
115
|
+
* it is the whisker reach. */
|
|
116
|
+
lower: NumericColumn<S>;
|
|
117
|
+
/**
|
|
118
|
+
* Box bottom — first quartile (e.g. `p25`). **Optional:** omit `q1`
|
|
119
|
+
* **and** `q3` together for a **range-only** box (a whisker-only
|
|
120
|
+
* `lower→upper` segment, no body — a bid→ask IV mark). Giving just one
|
|
121
|
+
* of the pair throws.
|
|
122
|
+
*/
|
|
123
|
+
q1?: NumericColumn<S>;
|
|
124
|
+
/** Median line (e.g. `p50`). **Optional** — omit for no centre line
|
|
125
|
+
* (independent of the box body). */
|
|
126
|
+
median?: NumericColumn<S>;
|
|
127
|
+
/** Box top — third quartile (e.g. `p75`). **Optional** — omit with `q1`
|
|
128
|
+
* for a range-only box. */
|
|
129
|
+
q3?: NumericColumn<S>;
|
|
130
|
+
/** Upper whisker end (e.g. `p95` / `max`). **Required** — with `lower`
|
|
131
|
+
* it is the whisker reach. */
|
|
132
|
+
upper: NumericColumn<S>;
|
|
133
|
+
} | {
|
|
134
|
+
series: ValueSeries<VS>;
|
|
135
|
+
/** Lower whisker end (e.g. `p5` / `min`). **Required** — with `upper`
|
|
136
|
+
* it is the whisker reach. */
|
|
137
|
+
lower: ValueNumericColumn<VS>;
|
|
138
|
+
/**
|
|
139
|
+
* Box bottom — first quartile (e.g. `p25`). **Optional:** omit `q1`
|
|
140
|
+
* **and** `q3` together for a **range-only** box (a whisker-only
|
|
141
|
+
* `lower→upper` segment, no body — a bid→ask IV mark). Giving just one
|
|
142
|
+
* of the pair throws.
|
|
143
|
+
*/
|
|
144
|
+
q1?: ValueNumericColumn<VS>;
|
|
145
|
+
/** Median line (e.g. `p50`). **Optional** — omit for no centre line
|
|
146
|
+
* (independent of the box body). */
|
|
147
|
+
median?: ValueNumericColumn<VS>;
|
|
148
|
+
/** Box top — third quartile (e.g. `p75`). **Optional** — omit with `q1`
|
|
149
|
+
* for a range-only box. */
|
|
150
|
+
q3?: ValueNumericColumn<VS>;
|
|
151
|
+
/** Upper whisker end (e.g. `p95` / `max`). **Required** — with `lower`
|
|
152
|
+
* it is the whisker reach. */
|
|
153
|
+
upper: ValueNumericColumn<VS>;
|
|
154
|
+
};
|
|
155
|
+
/** `<BoxPlot>`'s props: the shared knobs plus one series-kind source shape. */
|
|
156
|
+
export type BoxPlotProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = BoxPlotCommon<S, VS> & BoxPlotSource<S, VS>;
|
|
125
157
|
/**
|
|
126
158
|
* A discrete box-and-whisker draw layer — the bar-chart analog of the variance
|
|
127
159
|
* band. Reads **pre-computed quantile columns** of `series` (typically a
|
|
@@ -154,4 +186,5 @@ export interface BoxPlotProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
154
186
|
* ```
|
|
155
187
|
*/
|
|
156
188
|
export declare function BoxPlot<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, lower, q1, median, q3, upper, as: semantic, axis, gap, shape, showMedian, offset, capWidth, id, decimate, legend, index, }: BoxPlotProps<S, VS>): null;
|
|
189
|
+
export {};
|
|
157
190
|
//# sourceMappingURL=BoxPlot.d.ts.map
|
package/dist/Candlestick.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SeriesSchema, TimeSeries } from 'pond-ts';
|
|
2
|
+
import type { NumericColumn } from './column-names.js';
|
|
2
3
|
import type { DecimateOption } from './decimate.js';
|
|
3
4
|
import { type CandleVariant, type ColorBy } from './ohlc.js';
|
|
4
5
|
export interface CandlestickProps<S extends SeriesSchema> {
|
|
@@ -12,13 +13,13 @@ export interface CandlestickProps<S extends SeriesSchema> {
|
|
|
12
13
|
*/
|
|
13
14
|
series: TimeSeries<S>;
|
|
14
15
|
/** Opening-price column. **Omitted ⇒ `'open'`.** */
|
|
15
|
-
open?:
|
|
16
|
+
open?: NumericColumn<S>;
|
|
16
17
|
/** Session-high column. **Omitted ⇒ `'high'`.** */
|
|
17
|
-
high?:
|
|
18
|
+
high?: NumericColumn<S>;
|
|
18
19
|
/** Session-low column. **Omitted ⇒ `'low'`.** */
|
|
19
|
-
low?:
|
|
20
|
+
low?: NumericColumn<S>;
|
|
20
21
|
/** Closing-price column. **Omitted ⇒ `'close'`.** */
|
|
21
|
-
close?:
|
|
22
|
+
close?: NumericColumn<S>;
|
|
22
23
|
/**
|
|
23
24
|
* The series' semantic identifier — what the data _is_ (e.g. a ticker). The
|
|
24
25
|
* theme maps it to a {@link CandleStyle} (`theme.candle[as] ??
|
package/dist/Layers.js
CHANGED
|
@@ -101,6 +101,13 @@ export function Layers({ children }) {
|
|
|
101
101
|
// A category axis draws no vertical gridlines — a line through each bar
|
|
102
102
|
// centre reads as noise; the bars are the structure.
|
|
103
103
|
const xTickVals = container.xKind === 'category' ? [] : xScale.ticks(xTickCount);
|
|
104
|
+
// The same rule on the other axis: a **horizontal** categorical chart
|
|
105
|
+
// ([PND-HCAT]) puts its categories on y, where the `<YAxis>` labels slot
|
|
106
|
+
// *centres* while d3's auto ticks fall on slot *boundaries* — so drawing
|
|
107
|
+
// them would both mismatch the labels and stripe each bar. Suppressed,
|
|
108
|
+
// exactly as the categorical x axis already is.
|
|
109
|
+
const yIsCategory = layers.some((e) => (e.axisId ?? defaultAxisId) === defaultAxisId &&
|
|
110
|
+
(e.layer.binCategories?.() ?? null) !== null);
|
|
104
111
|
// The reference grid — behind the data, opt-out via `grid={false}` for
|
|
105
112
|
// a clean backdrop (session dividers below stay independent of it).
|
|
106
113
|
if (container.grid) {
|
|
@@ -108,7 +115,7 @@ export function Layers({ children }) {
|
|
|
108
115
|
// single source, height-derived or an explicit `<YAxis tickCount>`), so
|
|
109
116
|
// a gridline sits under every `<YAxis>` label and no more.
|
|
110
117
|
const yCount = tickCounts.get(defaultAxisId) ?? GRID_TICKS;
|
|
111
|
-
const yTicks = gridY
|
|
118
|
+
const yTicks = gridY && !(yIsCategory && explicitY === undefined)
|
|
112
119
|
? (explicitY ?? gridY.ticks(yCount)).map((t) => gridY(t))
|
|
113
120
|
: [];
|
|
114
121
|
// On a calendar axis the verticals are the FULL grain populations —
|