@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
package/dist/list.d.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The **list family**'s data model + pure row logic — the shared substrate of
|
|
3
|
+
* {@link BarList} and {@link BoxList}, the DOM-rendered *ranked row lists*
|
|
4
|
+
* (react-timeseries-charts' `HorizontalBarChart`, the esnet "traffic by
|
|
5
|
+
* interface" table, an activity's per-split bars).
|
|
6
|
+
*
|
|
7
|
+
* A list is a **table, not a plot**: rows are entities (interfaces, splits,
|
|
8
|
+
* symbols), each row carries a label, optional data cells, and one glyph line
|
|
9
|
+
* per configured column — a value bar ({@link BarList}) or a five-number
|
|
10
|
+
* distribution box ({@link BoxList}). That is why this family renders DOM
|
|
11
|
+
* rather than registering canvas layers in a `<ChartContainer>`: the defining
|
|
12
|
+
* features (link labels, arbitrary cells, a per-row expander, custom sort) are
|
|
13
|
+
* table semantics a band-scaled plot can't carry. The in-plot horizontal bars
|
|
14
|
+
* remain `<BarChart orientation="horizontal">` — that's the histogram; this is
|
|
15
|
+
* the table.
|
|
16
|
+
*
|
|
17
|
+
* Everything here is pure (no React rendering): row/column/cell types, the
|
|
18
|
+
* shared value scale, and sorting. The components consume these so the two
|
|
19
|
+
* sisters can never disagree on semantics.
|
|
20
|
+
*/
|
|
21
|
+
import type { ReactNode } from 'react';
|
|
22
|
+
import type { SeriesSchema, TimeSeries, ValueSeries, ValueSeriesSchema } from 'pond-ts';
|
|
23
|
+
/**
|
|
24
|
+
* One cell value of a {@link ListRow}: a finite number renders (bar length,
|
|
25
|
+
* box quantile, sortable), a string rides along for data cells and
|
|
26
|
+
* lexicographic sort, and `undefined` / non-finite is a **gap** (no glyph,
|
|
27
|
+
* sorts last). The tolerant union keeps one `values` record serving glyphs,
|
|
28
|
+
* cells, and sort at once.
|
|
29
|
+
*/
|
|
30
|
+
export type ListValue = number | string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* One row of a {@link BarList} / {@link BoxList}.
|
|
33
|
+
*
|
|
34
|
+
* - `key` is the row's **stable identity** — selection, expansion, and React
|
|
35
|
+
* keys all pin to it, so it must be unique in the list (a duplicate key
|
|
36
|
+
* would expand/select both rows at once).
|
|
37
|
+
* - `label` is the built-in first cell's content (a plain string, or a link /
|
|
38
|
+
* any node). **Omitted ⇒ the `key` renders.**
|
|
39
|
+
* - `values` is the row's flat data record. Glyph columns and `sortBy` read it
|
|
40
|
+
* by name; data cells may read it or any extra field a consumer adds (both
|
|
41
|
+
* components are generic over `R extends ListRow`, so custom fields ride
|
|
42
|
+
* through to `render` / `renderExpanded` fully typed).
|
|
43
|
+
*/
|
|
44
|
+
export interface ListRow {
|
|
45
|
+
readonly key: string;
|
|
46
|
+
readonly label?: ReactNode;
|
|
47
|
+
readonly values: Readonly<Record<string, ListValue>>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One **bar line** of a {@link BarList} row — laid out top→bottom in `columns`
|
|
51
|
+
* order within each row (the esnet to-site / from-site pairing is two of
|
|
52
|
+
* these).
|
|
53
|
+
*/
|
|
54
|
+
export interface BarListColumn {
|
|
55
|
+
/** Name of the {@link ListRow.values} entry holding this bar's length. A
|
|
56
|
+
* missing / non-numeric value is a gap — the track renders empty. */
|
|
57
|
+
readonly column: string;
|
|
58
|
+
/**
|
|
59
|
+
* The bar's semantic identifier — what the data _is_. The theme maps it to a
|
|
60
|
+
* `BarStyle` (`theme.bar[as] ?? theme.bar.default`), the same single
|
|
61
|
+
* styling channel `<BarChart>` uses. **Omitted ⇒ the `default` bar style.**
|
|
62
|
+
*/
|
|
63
|
+
readonly as?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* One **box line** of a {@link BoxList} row — a five-number distribution
|
|
67
|
+
* summary plus an optional *current value* tick, each field naming an entry of
|
|
68
|
+
* {@link ListRow.values}. The quantile vocabulary (`lower`/`q1`/`median`/`q3`/
|
|
69
|
+
* `upper`, `q1`+`q3` both-or-neither for a range-only box) mirrors the canvas
|
|
70
|
+
* `<BoxPlot>` exactly, so moving between the plot and the list renames
|
|
71
|
+
* nothing.
|
|
72
|
+
*/
|
|
73
|
+
export interface BoxListColumn {
|
|
74
|
+
/** `values` entry for the lower whisker end (e.g. a `p5` / `min` fact). Required. */
|
|
75
|
+
readonly lower: string;
|
|
76
|
+
/** `values` entry for the box bottom (Q1). Omit with `q3` for a range-only box. */
|
|
77
|
+
readonly q1?: string;
|
|
78
|
+
/** `values` entry for the median line. Omit for no centre line. */
|
|
79
|
+
readonly median?: string;
|
|
80
|
+
/** `values` entry for the box top (Q3). Omit with `q1` for a range-only box. */
|
|
81
|
+
readonly q3?: string;
|
|
82
|
+
/** `values` entry for the upper whisker end (e.g. a `p95` / `max` fact). Required. */
|
|
83
|
+
readonly upper: string;
|
|
84
|
+
/**
|
|
85
|
+
* `values` entry for the **current-value tick** — the dark now-marker over
|
|
86
|
+
* the distribution (the esnet look: range band + tick + printed value).
|
|
87
|
+
* Omit for a distribution-only box.
|
|
88
|
+
*/
|
|
89
|
+
readonly value?: string;
|
|
90
|
+
/** Semantic identifier → `theme.box[as] ?? theme.box.default`. */
|
|
91
|
+
readonly as?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Formats the current value's **inline label**, printed just right of the
|
|
94
|
+
* tick (`"150Gbps"`). **Omitted ⇒ no label** (the tick still draws). Only
|
|
95
|
+
* read when `value` is set.
|
|
96
|
+
*/
|
|
97
|
+
readonly format?: (value: number) => string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* One **data cell** column, rendered before or after the glyph cell (the
|
|
101
|
+
* split's `15.3 mph`, an interface's type tag). `render` receives the whole
|
|
102
|
+
* row — including any consumer-added fields beyond {@link ListRow} — and
|
|
103
|
+
* returns any node; the table gives every cell of a spec its own shrink-to-fit
|
|
104
|
+
* table column, so cells align down the list.
|
|
105
|
+
*/
|
|
106
|
+
export interface ListCellSpec<R extends ListRow = ListRow> {
|
|
107
|
+
/** Stable identity for the cell column (React key). */
|
|
108
|
+
readonly key: string;
|
|
109
|
+
/** Horizontal text alignment. **Omitted ⇒ `'left'`.** */
|
|
110
|
+
readonly align?: 'left' | 'right' | 'center';
|
|
111
|
+
/** The cell's content for one row. */
|
|
112
|
+
readonly render: (row: R) => ReactNode;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* A **reference marker** on the list's shared value scale — a labelled
|
|
116
|
+
* vertical dotted rule through every row (an SLA threshold, a capacity line,
|
|
117
|
+
* the fleet average). The list-family counterpart of the canvas `<Marker>` /
|
|
118
|
+
* `<Baseline>`: user-authored reference, so it draws in the **annotation
|
|
119
|
+
* (marks) register**, never a data hue.
|
|
120
|
+
*
|
|
121
|
+
* `value` is in data units on the shared scale. When the domain is
|
|
122
|
+
* auto-fitted, marker values **join the fit** — a threshold above the data
|
|
123
|
+
* max widens the scale rather than clamping to the edge (an explicit
|
|
124
|
+
* `domain` prop still wins, and then an out-of-domain marker clamps).
|
|
125
|
+
*/
|
|
126
|
+
export interface ListMarker {
|
|
127
|
+
/** Position in data units on the shared scale. */
|
|
128
|
+
readonly value: number;
|
|
129
|
+
/** Printed above the list, centred on the rule. Omit for a bare rule. */
|
|
130
|
+
readonly label?: string;
|
|
131
|
+
}
|
|
132
|
+
/** Row order: `'desc'` puts the largest value at the top (the ranked-list
|
|
133
|
+
* default), `'asc'` the smallest. */
|
|
134
|
+
export type ListSortDirection = 'asc' | 'desc';
|
|
135
|
+
/**
|
|
136
|
+
* Sort rows for display. Precedence: a `custom` comparator wins outright;
|
|
137
|
+
* else `sortBy` names the {@link ListRow.values} entry that drives the order
|
|
138
|
+
* (with several glyph columns, this is how you decide which one ranks the
|
|
139
|
+
* list); else the input order stands (the splits case — chronological rows).
|
|
140
|
+
*
|
|
141
|
+
* Value semantics under `sortBy`: numbers order numerically, strings
|
|
142
|
+
* lexicographically (`localeCompare`); when the two kinds meet, numbers come
|
|
143
|
+
* first; a missing / non-finite value sorts **last regardless of direction**
|
|
144
|
+
* (a dead interface stays at the bottom whether you rank best-first or
|
|
145
|
+
* worst-first). The sort is stable, so ties keep input order.
|
|
146
|
+
*/
|
|
147
|
+
export declare function sortListRows<R extends ListRow>(rows: readonly R[], sortBy: string | undefined, direction: ListSortDirection, custom?: (a: R, b: R) => number): readonly R[];
|
|
148
|
+
/**
|
|
149
|
+
* The shared value scale's domain: every glyph line of every row maps through
|
|
150
|
+
* **one** `[min, max]` so lengths compare across the whole list (the point of
|
|
151
|
+
* a ranked list). Resolved from the data — `keys` names every `values` entry
|
|
152
|
+
* that lands on the scale (bar columns; box lower/upper/value) — as
|
|
153
|
+
* `[min(0, data min), data max]`: bars grow from zero, but a below-zero
|
|
154
|
+
* whisker still fits. `extra` values (reference {@link ListMarker}s) **join
|
|
155
|
+
* the auto fit**, so a threshold above the data max widens the scale instead
|
|
156
|
+
* of clamping to the right edge. An explicit `domain` prop overrides both
|
|
157
|
+
* ends (and ignores `extra`). An empty / all-missing list resolves `[0, 1]`
|
|
158
|
+
* so the mapping stays finite.
|
|
159
|
+
*/
|
|
160
|
+
export declare function resolveListDomain(rows: readonly ListRow[], keys: readonly string[], explicit?: readonly [number, number], extra?: readonly number[]): readonly [number, number];
|
|
161
|
+
/**
|
|
162
|
+
* Map one value onto the shared scale as a **fraction of the track width**,
|
|
163
|
+
* clamped to `[0, 1]` (an out-of-domain value pins to an edge rather than
|
|
164
|
+
* escaping the row). `null` for a gap (missing / non-numeric / non-finite) —
|
|
165
|
+
* the caller draws nothing. A degenerate `min === max` domain maps everything
|
|
166
|
+
* to `0` (nothing to proportion against).
|
|
167
|
+
*/
|
|
168
|
+
export declare function listFraction(value: ListValue, domain: readonly [number, number]): number | null;
|
|
169
|
+
/**
|
|
170
|
+
* Reject a half-specified box body — `q1`/`q3` are both-or-neither, the same
|
|
171
|
+
* contract as the canvas `<BoxPlot>` / `BoxColumns` (a box needs two body
|
|
172
|
+
* edges or none). Throws on exactly one.
|
|
173
|
+
*/
|
|
174
|
+
export declare function validateBoxListColumn(column: BoxListColumn): void;
|
|
175
|
+
/** Options for {@link listRowsFromTimeSeries} / {@link listRowsFromValueSeries}. */
|
|
176
|
+
export interface ListRowsOptions {
|
|
177
|
+
/**
|
|
178
|
+
* The built-in label cell's content per row, from the row's ordinal position
|
|
179
|
+
* and its axis key (epoch ms for a `TimeSeries` — an interval key passes its
|
|
180
|
+
* `begin` — or the axis value for a `ValueSeries`). **Omitted ⇒ no label**,
|
|
181
|
+
* so the cell falls back to the row `key` (the stringified axis key);
|
|
182
|
+
* real-world lists almost always want this (`(i) => \`${i + 1}\`` for
|
|
183
|
+
* splits, a date formatter for daily rows).
|
|
184
|
+
*/
|
|
185
|
+
readonly label?: (i: number, key: number) => ReactNode;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Read a series into {@link ListRow}s — **one row per event**, every value
|
|
189
|
+
* column (numeric *and* string) landing in `values` under its own name. The
|
|
190
|
+
* split-table reader: an `aggregate` per-split rollup feeds straight in, glyph
|
|
191
|
+
* columns pick the columns to draw, cells format the rest. The row `key` is
|
|
192
|
+
* the stringified axis key (an interval key's `begin`), which is unique in a
|
|
193
|
+
* series by construction.
|
|
194
|
+
*
|
|
195
|
+
* Reads per event via `event.get()` (row counts here are table-sized; the
|
|
196
|
+
* typed-array fast paths stay with the canvas readers).
|
|
197
|
+
*/
|
|
198
|
+
export declare function listRowsFromTimeSeries<S extends SeriesSchema>(series: TimeSeries<S>, options?: ListRowsOptions): ListRow[];
|
|
199
|
+
/**
|
|
200
|
+
* The value-axis sibling of {@link listRowsFromTimeSeries} — one row per axis
|
|
201
|
+
* key (`series.byValue('dist')` per-km splits). The row `key` is the
|
|
202
|
+
* stringified axis value.
|
|
203
|
+
*/
|
|
204
|
+
export declare function listRowsFromValueSeries<VS extends ValueSeriesSchema>(series: ValueSeries<VS>, options?: ListRowsOptions): ListRow[];
|
|
205
|
+
//# sourceMappingURL=list.d.ts.map
|
package/dist/list.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sort rows for display. Precedence: a `custom` comparator wins outright;
|
|
3
|
+
* else `sortBy` names the {@link ListRow.values} entry that drives the order
|
|
4
|
+
* (with several glyph columns, this is how you decide which one ranks the
|
|
5
|
+
* list); else the input order stands (the splits case — chronological rows).
|
|
6
|
+
*
|
|
7
|
+
* Value semantics under `sortBy`: numbers order numerically, strings
|
|
8
|
+
* lexicographically (`localeCompare`); when the two kinds meet, numbers come
|
|
9
|
+
* first; a missing / non-finite value sorts **last regardless of direction**
|
|
10
|
+
* (a dead interface stays at the bottom whether you rank best-first or
|
|
11
|
+
* worst-first). The sort is stable, so ties keep input order.
|
|
12
|
+
*/
|
|
13
|
+
export function sortListRows(rows, sortBy, direction, custom) {
|
|
14
|
+
if (custom !== undefined)
|
|
15
|
+
return [...rows].sort(custom);
|
|
16
|
+
if (sortBy === undefined)
|
|
17
|
+
return rows;
|
|
18
|
+
const dir = direction === 'asc' ? 1 : -1;
|
|
19
|
+
return [...rows].sort((a, b) => {
|
|
20
|
+
const av = a.values[sortBy];
|
|
21
|
+
const bv = b.values[sortBy];
|
|
22
|
+
const aNum = typeof av === 'number' && Number.isFinite(av);
|
|
23
|
+
const bNum = typeof bv === 'number' && Number.isFinite(bv);
|
|
24
|
+
const aStr = typeof av === 'string';
|
|
25
|
+
const bStr = typeof bv === 'string';
|
|
26
|
+
// Missing sorts last in BOTH directions — absence is not a small value.
|
|
27
|
+
const aMissing = !aNum && !aStr;
|
|
28
|
+
const bMissing = !bNum && !bStr;
|
|
29
|
+
if (aMissing || bMissing)
|
|
30
|
+
return aMissing === bMissing ? 0 : aMissing ? 1 : -1;
|
|
31
|
+
if (aNum && bNum)
|
|
32
|
+
return dir * (av - bv);
|
|
33
|
+
if (aStr && bStr)
|
|
34
|
+
return dir * av.localeCompare(bv);
|
|
35
|
+
// Mixed kinds: numbers rank before strings, direction-independent (a
|
|
36
|
+
// deliberate tie-break, not an ordering claim across kinds).
|
|
37
|
+
return aNum ? -1 : 1;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The shared value scale's domain: every glyph line of every row maps through
|
|
42
|
+
* **one** `[min, max]` so lengths compare across the whole list (the point of
|
|
43
|
+
* a ranked list). Resolved from the data — `keys` names every `values` entry
|
|
44
|
+
* that lands on the scale (bar columns; box lower/upper/value) — as
|
|
45
|
+
* `[min(0, data min), data max]`: bars grow from zero, but a below-zero
|
|
46
|
+
* whisker still fits. `extra` values (reference {@link ListMarker}s) **join
|
|
47
|
+
* the auto fit**, so a threshold above the data max widens the scale instead
|
|
48
|
+
* of clamping to the right edge. An explicit `domain` prop overrides both
|
|
49
|
+
* ends (and ignores `extra`). An empty / all-missing list resolves `[0, 1]`
|
|
50
|
+
* so the mapping stays finite.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveListDomain(rows, keys, explicit, extra) {
|
|
53
|
+
if (explicit !== undefined)
|
|
54
|
+
return explicit;
|
|
55
|
+
let min = Infinity;
|
|
56
|
+
let max = -Infinity;
|
|
57
|
+
const take = (v) => {
|
|
58
|
+
if (typeof v === 'number' && Number.isFinite(v)) {
|
|
59
|
+
if (v < min)
|
|
60
|
+
min = v;
|
|
61
|
+
if (v > max)
|
|
62
|
+
max = v;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
for (const row of rows) {
|
|
66
|
+
for (const key of keys)
|
|
67
|
+
take(row.values[key]);
|
|
68
|
+
}
|
|
69
|
+
if (extra !== undefined)
|
|
70
|
+
for (const v of extra)
|
|
71
|
+
take(v);
|
|
72
|
+
if (min === Infinity)
|
|
73
|
+
return [0, 1];
|
|
74
|
+
return [Math.min(0, min), max];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Map one value onto the shared scale as a **fraction of the track width**,
|
|
78
|
+
* clamped to `[0, 1]` (an out-of-domain value pins to an edge rather than
|
|
79
|
+
* escaping the row). `null` for a gap (missing / non-numeric / non-finite) —
|
|
80
|
+
* the caller draws nothing. A degenerate `min === max` domain maps everything
|
|
81
|
+
* to `0` (nothing to proportion against).
|
|
82
|
+
*/
|
|
83
|
+
export function listFraction(value, domain) {
|
|
84
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
85
|
+
return null;
|
|
86
|
+
const [d0, d1] = domain;
|
|
87
|
+
if (d1 === d0)
|
|
88
|
+
return 0;
|
|
89
|
+
const f = (value - d0) / (d1 - d0);
|
|
90
|
+
return f < 0 ? 0 : f > 1 ? 1 : f;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Reject a half-specified box body — `q1`/`q3` are both-or-neither, the same
|
|
94
|
+
* contract as the canvas `<BoxPlot>` / `BoxColumns` (a box needs two body
|
|
95
|
+
* edges or none). Throws on exactly one.
|
|
96
|
+
*/
|
|
97
|
+
export function validateBoxListColumn(column) {
|
|
98
|
+
if ((column.q1 === undefined) !== (column.q3 === undefined)) {
|
|
99
|
+
throw new RangeError(`BoxList: 'q1' and 'q3' are both-or-neither — a box body needs both edges ` +
|
|
100
|
+
`(got q1=${column.q1 ?? 'undefined'}, q3=${column.q3 ?? 'undefined'}). ` +
|
|
101
|
+
`Omit both for a range-only box (a lower→upper band).`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Read a series into {@link ListRow}s — **one row per event**, every value
|
|
106
|
+
* column (numeric *and* string) landing in `values` under its own name. The
|
|
107
|
+
* split-table reader: an `aggregate` per-split rollup feeds straight in, glyph
|
|
108
|
+
* columns pick the columns to draw, cells format the rest. The row `key` is
|
|
109
|
+
* the stringified axis key (an interval key's `begin`), which is unique in a
|
|
110
|
+
* series by construction.
|
|
111
|
+
*
|
|
112
|
+
* Reads per event via `event.get()` (row counts here are table-sized; the
|
|
113
|
+
* typed-array fast paths stay with the canvas readers).
|
|
114
|
+
*/
|
|
115
|
+
export function listRowsFromTimeSeries(series, options = {}) {
|
|
116
|
+
const begin = series.keyColumn().begin;
|
|
117
|
+
return buildListRows(valueColumnNames(series.schema), (name) => columnReader(series.column(name)), series.length, (i) => begin[i], options);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The value-axis sibling of {@link listRowsFromTimeSeries} — one row per axis
|
|
121
|
+
* key (`series.byValue('dist')` per-km splits). The row `key` is the
|
|
122
|
+
* stringified axis value.
|
|
123
|
+
*/
|
|
124
|
+
export function listRowsFromValueSeries(series, options = {}) {
|
|
125
|
+
const axis = series.axisValues();
|
|
126
|
+
return buildListRows(valueColumnNames(series.schema), (name) => columnReader(series.column(name)), series.length, (i) => axis[i], options);
|
|
127
|
+
}
|
|
128
|
+
/** A schema's value column names in order (the key column excluded). */
|
|
129
|
+
function valueColumnNames(schema) {
|
|
130
|
+
const cols = schema;
|
|
131
|
+
return cols.slice(1).map((c) => c.name);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A column handle's per-cell reader. `read(i)` is a method on the column
|
|
135
|
+
* *class* (the same access path `data.ts` uses — the bulk readers are mounted
|
|
136
|
+
* by a side-effect import that bundlers tree-shake away); the cast mirrors
|
|
137
|
+
* `assertNumericColumn`'s. `undefined` for an unknown column name.
|
|
138
|
+
*/
|
|
139
|
+
function columnReader(col) {
|
|
140
|
+
if (col === undefined || col === null)
|
|
141
|
+
return undefined;
|
|
142
|
+
const c = col;
|
|
143
|
+
return (i) => c.read(i);
|
|
144
|
+
}
|
|
145
|
+
/** Shared body of the two readers: walk rows, lift each value column. */
|
|
146
|
+
function buildListRows(names, readerOf, length, keyAt, options) {
|
|
147
|
+
const readers = names.map((name) => ({ name, read: readerOf(name) }));
|
|
148
|
+
const out = [];
|
|
149
|
+
for (let i = 0; i < length; i += 1) {
|
|
150
|
+
const values = {};
|
|
151
|
+
for (const { name, read } of readers) {
|
|
152
|
+
const v = read?.(i);
|
|
153
|
+
values[name] =
|
|
154
|
+
typeof v === 'number' || typeof v === 'string' ? v : undefined;
|
|
155
|
+
}
|
|
156
|
+
const key = keyAt(i);
|
|
157
|
+
out.push({
|
|
158
|
+
key: String(key),
|
|
159
|
+
...(options.label !== undefined ? { label: options.label(i, key) } : {}),
|
|
160
|
+
values,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=list.js.map
|
package/dist/theme.d.ts
CHANGED
|
@@ -330,6 +330,36 @@ export interface BarStyle {
|
|
|
330
330
|
readonly gap: number;
|
|
331
331
|
readonly minWidth: number;
|
|
332
332
|
readonly outlineWidth: number;
|
|
333
|
+
/**
|
|
334
|
+
* Optional distinct **hover** fill, so a bar can read a three-step emphasis —
|
|
335
|
+
* `fill` at rest → `hover` under the pointer → `highlight` (+ outline) when
|
|
336
|
+
* selected. **Omitted ⇒ `highlight`**, which is the shipped behaviour: hover
|
|
337
|
+
* and select share one colour and differ only by the selected bar's outline.
|
|
338
|
+
*
|
|
339
|
+
* The scatter analogue is `outline` vs {@link ScatterStyle.selectedOutline} —
|
|
340
|
+
* bars were the less expressive layer for the same two-state interaction
|
|
341
|
+
* ([#577](https://github.com/pond-ts/pond/issues/577)). This is the *hover*
|
|
342
|
+
* half rather than a rename of `highlight`, so no existing theme changes
|
|
343
|
+
* meaning; a theme that wants the distinction opts in by adding one colour.
|
|
344
|
+
*
|
|
345
|
+
* **Where it applies.** Read by the `drawBars` single-series path, which
|
|
346
|
+
* since [PND-BARSEM] covers every **one-segment vertical** bar however it
|
|
347
|
+
* was fed — a `series` + `column` chart, a one-column `bins` histogram, or
|
|
348
|
+
* a one-entry `columns`. Still not read by:
|
|
349
|
+
*
|
|
350
|
+
* - a genuine **multi-group stack** (`columns` / a `Map` series), whose
|
|
351
|
+
* {@link StackStyle} has no hover channel — segments in one bin would
|
|
352
|
+
* need their own hovered identity;
|
|
353
|
+
* - **`categories`** and **horizontal** charts, which keep the transposed
|
|
354
|
+
* stacked draw path ([PND-HCAT] tracks the categorical half);
|
|
355
|
+
* - **`binColors`** (per-bar colours), which pops each bar's *own* fill for
|
|
356
|
+
* both states so a red/green volume bar keeps its meaning while live —
|
|
357
|
+
* the one *design* exclusion rather than a path consequence.
|
|
358
|
+
*
|
|
359
|
+
* The **decimated** dense-bar pass also draws the flat fill only, as it
|
|
360
|
+
* already did for `highlight`.
|
|
361
|
+
*/
|
|
362
|
+
readonly hover?: string;
|
|
333
363
|
}
|
|
334
364
|
/**
|
|
335
365
|
* The neutral default theme. `default` / `primary` match the M1 `LineChart`
|
package/dist/theme.js
CHANGED
|
@@ -74,6 +74,18 @@ export const defaultTheme = {
|
|
|
74
74
|
whisker: '#aabee9',
|
|
75
75
|
whiskerWidth: 1,
|
|
76
76
|
},
|
|
77
|
+
// The warm accent box — the second series of a paired distribution (an
|
|
78
|
+
// in/out traffic list), mirroring `bar.secondary` / `line.secondary`.
|
|
79
|
+
secondary: {
|
|
80
|
+
fill: '#e8836b',
|
|
81
|
+
fillOpacity: 0.3,
|
|
82
|
+
stroke: '#d65f43',
|
|
83
|
+
strokeWidth: 1.5,
|
|
84
|
+
median: '#b4442a',
|
|
85
|
+
medianWidth: 2,
|
|
86
|
+
whisker: '#f0c2b2',
|
|
87
|
+
whiskerWidth: 1,
|
|
88
|
+
},
|
|
77
89
|
},
|
|
78
90
|
candle: {
|
|
79
91
|
// Neutral / unbranded up-down pair — *not* market green/red (a consumer
|
|
@@ -225,6 +237,18 @@ export const estelaTheme = {
|
|
|
225
237
|
whisker: '#a4e4d9', // --es-reef
|
|
226
238
|
whiskerWidth: 1.5,
|
|
227
239
|
},
|
|
240
|
+
// The warm filament accent — the paired second distribution, mirroring
|
|
241
|
+
// `bar.secondary` / `line.hr` on the dark ground.
|
|
242
|
+
secondary: {
|
|
243
|
+
fill: '#E0B36A', // --es-filament
|
|
244
|
+
fillOpacity: 0.28,
|
|
245
|
+
stroke: '#E0B36A',
|
|
246
|
+
strokeWidth: 1.5,
|
|
247
|
+
median: '#F1FBF9', // --es-foam
|
|
248
|
+
medianWidth: 2,
|
|
249
|
+
whisker: '#EDD5A8',
|
|
250
|
+
whiskerWidth: 1.5,
|
|
251
|
+
},
|
|
228
252
|
},
|
|
229
253
|
candle: {
|
|
230
254
|
// On the dark ground: brand teal rising, warm filament falling — the estela
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pond-ts/charts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Canvas-rendered, streaming-first time-series charts for pond-ts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"perf": "PERF_BENCH=1 playwright test perf.spec.ts --workers=1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@pond-ts/react": "^0.
|
|
42
|
-
"pond-ts": "^0.
|
|
41
|
+
"@pond-ts/react": "^0.55.0",
|
|
42
|
+
"pond-ts": "^0.55.0",
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|