@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/context.d.ts
CHANGED
|
@@ -541,6 +541,17 @@ export interface RowLayer {
|
|
|
541
541
|
* must agree on this list (a mix is an error), the same way {@link xKind} must.
|
|
542
542
|
*/
|
|
543
543
|
xCategories?(): readonly string[] | null;
|
|
544
|
+
/**
|
|
545
|
+
* A **horizontal** categorical source's ordered category names — the same
|
|
546
|
+
* list {@link xCategories} carries for a vertical one, but for the axis it
|
|
547
|
+
* lands on when the bars grow right: the **y** axis ([PND-HCAT]).
|
|
548
|
+
*
|
|
549
|
+
* The y axis stays a linear scale over the layer's unit slots (`[i, i+1]`),
|
|
550
|
+
* so this only supplies *labels*: a `<YAxis>` in the row with no explicit
|
|
551
|
+
* `ticks` derives one tick per category at the slot centre (`i + 0.5`).
|
|
552
|
+
* That hand-built tick list was the friction the gallery funnel documented.
|
|
553
|
+
*/
|
|
554
|
+
binCategories?(): readonly string[] | null;
|
|
544
555
|
/**
|
|
545
556
|
* A bar/histogram layer's bar `[begin, end)` spans, as pond `Interval`s — the
|
|
546
557
|
* **region cursor's snap buckets**. When present (and no `cursorSequence` is
|
|
@@ -601,6 +612,15 @@ export interface TrackerSample {
|
|
|
601
612
|
* quote) emits `"<as> <role>"` composites (`iv lower`, `SPY high`) when its
|
|
602
613
|
* `as` is set, else the raw column / role word. */
|
|
603
614
|
readonly label: string;
|
|
615
|
+
/**
|
|
616
|
+
* Optional **source value for the off-chart readout**, when the layer plots a
|
|
617
|
+
* *derived* column but a `readout` column names the raw value (see
|
|
618
|
+
* `LineChart`/`AreaChart` `readout`). `value` stays the plotted number — so
|
|
619
|
+
* the in-chart cursor dot is unchanged — while an off-chart consumer shows
|
|
620
|
+
* `readout ?? value`. `undefined` when the layer has no `readout` column (the
|
|
621
|
+
* common case: the plotted value *is* the value to show).
|
|
622
|
+
*/
|
|
623
|
+
readonly readout?: number;
|
|
604
624
|
}
|
|
605
625
|
/** One line of a {@link CursorFlag} — a labelled, coloured value. */
|
|
606
626
|
export interface CursorFlagLine {
|
|
@@ -633,6 +653,17 @@ export interface TrackerSource {
|
|
|
633
653
|
xExtent(): readonly [number, number] | null;
|
|
634
654
|
/** A `'category'` source's ordered category names (see {@link RowLayer.xCategories}). */
|
|
635
655
|
xCategories?(): readonly string[] | null;
|
|
656
|
+
/**
|
|
657
|
+
* A **horizontal** categorical source's ordered category names — the same
|
|
658
|
+
* list {@link xCategories} carries for a vertical one, but for the axis it
|
|
659
|
+
* lands on when the bars grow right: the **y** axis ([PND-HCAT]).
|
|
660
|
+
*
|
|
661
|
+
* The y axis stays a linear scale over the layer's unit slots (`[i, i+1]`),
|
|
662
|
+
* so this only supplies *labels*: a `<YAxis>` in the row with no explicit
|
|
663
|
+
* `ticks` derives one tick per category at the slot centre (`i + 0.5`).
|
|
664
|
+
* That hand-built tick list was the friction the gallery funnel documented.
|
|
665
|
+
*/
|
|
666
|
+
binCategories?(): readonly string[] | null;
|
|
636
667
|
/** A bar/histogram source's bar `[begin, end)` spans (see {@link RowLayer.binIntervals}). */
|
|
637
668
|
binIntervals?(): readonly Interval[] | null;
|
|
638
669
|
}
|
|
@@ -667,13 +698,25 @@ export interface SelectInfo {
|
|
|
667
698
|
/** Display label (`as` ?? column ?? id) — labels the selection in a readout. */
|
|
668
699
|
readonly label: string;
|
|
669
700
|
/**
|
|
670
|
-
* An optional **stable per-mark identity within the layer** —
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
701
|
+
* An optional **stable per-mark identity within the layer** — every mark
|
|
702
|
+
* shares the layer's `id`, so this is the handle that picks one *within* it.
|
|
703
|
+
* When a selection carries it, the highlight match + controlled `selected`
|
|
704
|
+
* echo key on `(id, mark)` instead of the sample `key`, so a pin survives a
|
|
705
|
+
* reorder / data update that renumbers the slot.
|
|
706
|
+
*
|
|
707
|
+
* Two layers report one today:
|
|
708
|
+
*
|
|
709
|
+
* - A **categorical** bar reports its *column name* — the slot index is not
|
|
710
|
+
* stable across a reorder, the name is.
|
|
711
|
+
* - A **single-series** bar (`<BarChart series column>`) reports its own axis
|
|
712
|
+
* key, stringified. On a **point-keyed** series the sample `key` is *not*
|
|
713
|
+
* its identity: the bar span is synthesized from neighbour spacing, so
|
|
714
|
+
* `key` is a derived edge (`t - halfGap`) rather than the sample's own
|
|
715
|
+
* time. See `BarSeries.marks`.
|
|
716
|
+
*
|
|
717
|
+
* `undefined` for every other mark (scatter, box, candle), whose sample `key`
|
|
718
|
+
* *is* its identity. A selection without a `mark` still matches on `key`
|
|
719
|
+
* everywhere — the mark is an additional channel, not a replacement.
|
|
677
720
|
*/
|
|
678
721
|
readonly mark?: string;
|
|
679
722
|
}
|
package/dist/data.d.ts
CHANGED
|
@@ -121,6 +121,35 @@ export interface BarSeries {
|
|
|
121
121
|
readonly end: Float64Array;
|
|
122
122
|
readonly y: Float64Array;
|
|
123
123
|
readonly length: number;
|
|
124
|
+
/**
|
|
125
|
+
* Optional **stable per-bar identity** — `marks[i]` names bar `i`. The
|
|
126
|
+
* single-series sibling of {@link StackedBarSeries.marks}: when present, the
|
|
127
|
+
* draw / hit-test / selection can key on this name instead of the bar's
|
|
128
|
+
* `begin` **edge**.
|
|
129
|
+
*
|
|
130
|
+
* The readers ({@link barsFromTimeSeries} / {@link barsFromValueSeries}) fill
|
|
131
|
+
* it with the **sample's own axis key** — `String(key[i])`, the timestamp or
|
|
132
|
+
* axis value the row is keyed on. That is the identity a caller already
|
|
133
|
+
* owns, and for a **point-keyed** series it is *not* `begin[i]`: there the
|
|
134
|
+
* span is synthesized, so `begin[i]` is a derived edge (`key - prevGap/2`,
|
|
135
|
+
* see {@link neighbourSpans}) and pinning a selection by key meant
|
|
136
|
+
* re-deriving the neighbour spacing. `undefined` on a hand-built view.
|
|
137
|
+
*
|
|
138
|
+
* Built **lazily** on first read and then memoized (~9 ms per 100k bars, on
|
|
139
|
+
* top of a ~0.8 ms reader). Who pays, precisely:
|
|
140
|
+
*
|
|
141
|
+
* - A **non-interactive** layer (no `id`, so no `hitTest`) never reads them.
|
|
142
|
+
* - An **interactive** one hit-tests on every *pointer move*, and that echo
|
|
143
|
+
* reads the hovered bar's mark — so the first move that lands on a bar
|
|
144
|
+
* materializes the array, once per data identity, on the input path
|
|
145
|
+
* (11.1 ms vs 1.7 ms for a warm 100k-bar hover).
|
|
146
|
+
*
|
|
147
|
+
* So this is not free for an interactive chart; it is bounded and paid once,
|
|
148
|
+
* where an eager array would cost every chart on every data update. At
|
|
149
|
+
* realistic bar counts it is under a millisecond either way. See
|
|
150
|
+
* `scripts/perf-barmarks.mjs`.
|
|
151
|
+
*/
|
|
152
|
+
readonly marks?: readonly string[];
|
|
124
153
|
}
|
|
125
154
|
/**
|
|
126
155
|
* A chart-ready view of a **stacked / histogram** bar series — the multi-segment
|
|
@@ -156,6 +185,27 @@ export interface StackedBarSeries {
|
|
|
156
185
|
*/
|
|
157
186
|
readonly marks?: readonly string[];
|
|
158
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Assert `column` names an existing **numeric** column of `series`, returning
|
|
190
|
+
* it. The single source of the reader's two errors, so a caller that reads a
|
|
191
|
+
* column **per event** rather than buffering it — the time-axis `readout` path
|
|
192
|
+
* in `LineChart` / `AreaChart` — rejects a bad name identically to one that
|
|
193
|
+
* materializes. Without it a mistyped `readout` throws on a `ValueSeries` (via
|
|
194
|
+
* {@link readValueColumn}) but silently produced no readout on a `TimeSeries`,
|
|
195
|
+
* where the per-event `get()` just returns `undefined`.
|
|
196
|
+
*
|
|
197
|
+
* The `undefined` guard is runtime-necessary even though it reads as dead code:
|
|
198
|
+
* `column()` returns `undefined` for an unknown name at runtime, but core's
|
|
199
|
+
* public overload currently types the result as non-`undefined` (see F-3 in the
|
|
200
|
+
* M1 friction note). Keep it — the "throws on unknown column" tests exercise it.
|
|
201
|
+
*
|
|
202
|
+
* @throws RangeError if `column` does not exist.
|
|
203
|
+
* @throws TypeError if `column` is not a numeric column.
|
|
204
|
+
*/
|
|
205
|
+
export declare function assertNumericColumn<S extends SeriesSchema>(series: TimeSeries<S>, column: string): {
|
|
206
|
+
kind: string;
|
|
207
|
+
read(i: number): number | undefined;
|
|
208
|
+
};
|
|
159
209
|
/**
|
|
160
210
|
* The quantile column names a {@link boxFromTimeSeries} / {@link boxFromValueSeries}
|
|
161
211
|
* reads. `lower`/`upper` (the whisker reach) are required; `q1`/`q3` (the box
|
|
@@ -306,6 +356,10 @@ export declare function ohlcFromTimeSeries<S extends SeriesSchema>(series: TimeS
|
|
|
306
356
|
* interval-keyed series (e.g. an `aggregate`/`window` rollup) draws its true
|
|
307
357
|
* bucket spans. Detected by `keyColumn().kind === 'time'`.
|
|
308
358
|
*
|
|
359
|
+
* Each bar also carries its **own key** as a stable {@link BarSeries.marks}
|
|
360
|
+
* identity, so a selection can be pinned on the sample rather than on the span
|
|
361
|
+
* this derived for it.
|
|
362
|
+
*
|
|
309
363
|
* @throws RangeError if `column` does not exist.
|
|
310
364
|
* @throws TypeError if `column` is not a numeric column.
|
|
311
365
|
*/
|
|
@@ -326,6 +380,10 @@ export declare function barsFromTimeSeries<S extends SeriesSchema>(series: TimeS
|
|
|
326
380
|
* (a slight drift from a true segment edge — fine for the bar look; key an
|
|
327
381
|
* interval/timeRange `TimeSeries` instead if exact edges matter).
|
|
328
382
|
*
|
|
383
|
+
* Each bar also carries its **axis value** as a stable {@link BarSeries.marks}
|
|
384
|
+
* identity — the centre it is drawn around, not the derived edge — so a
|
|
385
|
+
* selection can be pinned without re-deriving the neighbour spacing.
|
|
386
|
+
*
|
|
329
387
|
* @throws RangeError if `column` does not exist.
|
|
330
388
|
* @throws TypeError if `column` is not a numeric column.
|
|
331
389
|
*/
|
|
@@ -389,6 +447,25 @@ export interface StacksFromBinsOptions {
|
|
|
389
447
|
*/
|
|
390
448
|
readonly ordinal?: boolean;
|
|
391
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Build a {@link BarSeries} from **`byColumn` bin records** — the single-series
|
|
452
|
+
* sibling of {@link stacksFromBins}, for a histogram drawing **one** aggregate
|
|
453
|
+
* field.
|
|
454
|
+
*
|
|
455
|
+
* Why it exists ([PND-BARSEM]): a one-column histogram *is* a single-series
|
|
456
|
+
* bar chart — same mark, same geometry — but routing it through the stacked
|
|
457
|
+
* reader made its capabilities depend on which prop produced it (whole-slot
|
|
458
|
+
* hit-testing, the hover colour, the cursor readout and per-bar decimation all
|
|
459
|
+
* live on the single path). Reading it as a `BarSeries` lets the chart decide
|
|
460
|
+
* by what it *draws* rather than by how it was fed.
|
|
461
|
+
*
|
|
462
|
+
* `column` names the aggregate field; slots are the bins' numeric
|
|
463
|
+
* `[start, end]` edges, or uniform unit slots under `{ ordinal: true }` —
|
|
464
|
+
* identical to {@link stacksFromBins}. A missing / non-finite aggregate reads
|
|
465
|
+
* as a gap (`NaN`), and each bar carries its slot index as a stable
|
|
466
|
+
* {@link BarSeries.marks} identity (bin records have no key of their own).
|
|
467
|
+
*/
|
|
468
|
+
export declare function barsFromBins(bins: readonly BinRecord[], column: string, options?: StacksFromBinsOptions): BarSeries;
|
|
392
469
|
/**
|
|
393
470
|
* Build a {@link StackedBarSeries} from **`byColumn` bin records** — the array of
|
|
394
471
|
* `{ start, end, …aggregates }` a value-band aggregation returns
|
package/dist/data.js
CHANGED
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
import { ValueSeries } from 'pond-ts';
|
|
2
|
+
/**
|
|
3
|
+
* Assert `column` names an existing **numeric** column of `series`, returning
|
|
4
|
+
* it. The single source of the reader's two errors, so a caller that reads a
|
|
5
|
+
* column **per event** rather than buffering it — the time-axis `readout` path
|
|
6
|
+
* in `LineChart` / `AreaChart` — rejects a bad name identically to one that
|
|
7
|
+
* materializes. Without it a mistyped `readout` throws on a `ValueSeries` (via
|
|
8
|
+
* {@link readValueColumn}) but silently produced no readout on a `TimeSeries`,
|
|
9
|
+
* where the per-event `get()` just returns `undefined`.
|
|
10
|
+
*
|
|
11
|
+
* The `undefined` guard is runtime-necessary even though it reads as dead code:
|
|
12
|
+
* `column()` returns `undefined` for an unknown name at runtime, but core's
|
|
13
|
+
* public overload currently types the result as non-`undefined` (see F-3 in the
|
|
14
|
+
* M1 friction note). Keep it — the "throws on unknown column" tests exercise it.
|
|
15
|
+
*
|
|
16
|
+
* @throws RangeError if `column` does not exist.
|
|
17
|
+
* @throws TypeError if `column` is not a numeric column.
|
|
18
|
+
*/
|
|
19
|
+
export function assertNumericColumn(series, column) {
|
|
20
|
+
const col = series.column(column);
|
|
21
|
+
if (col === undefined) {
|
|
22
|
+
throw new RangeError(`unknown column '${column}'`);
|
|
23
|
+
}
|
|
24
|
+
if (col.kind !== 'number') {
|
|
25
|
+
throw new TypeError(`column '${column}' must be numeric (got '${col.kind}')`);
|
|
26
|
+
}
|
|
27
|
+
return col;
|
|
28
|
+
}
|
|
2
29
|
/**
|
|
3
30
|
* Read a numeric column into a `Float64Array`, missing cells as `NaN`.
|
|
4
31
|
*
|
|
@@ -15,17 +42,7 @@ import { ValueSeries } from 'pond-ts';
|
|
|
15
42
|
* @throws TypeError if `column` is not a numeric column.
|
|
16
43
|
*/
|
|
17
44
|
function readNumericColumn(series, column) {
|
|
18
|
-
|
|
19
|
-
// `undefined` for an unknown name at runtime, but core's public overload
|
|
20
|
-
// currently types the result as non-`undefined` (see F-3 in the M1 friction
|
|
21
|
-
// note). Keep the guard — the "throws on unknown column" test exercises it.
|
|
22
|
-
const col = series.column(column);
|
|
23
|
-
if (col === undefined) {
|
|
24
|
-
throw new RangeError(`unknown column '${column}'`);
|
|
25
|
-
}
|
|
26
|
-
if (col.kind !== 'number') {
|
|
27
|
-
throw new TypeError(`column '${column}' must be numeric (got '${col.kind}')`);
|
|
28
|
-
}
|
|
45
|
+
const col = assertNumericColumn(series, column);
|
|
29
46
|
const length = series.length;
|
|
30
47
|
const out = new Float64Array(length);
|
|
31
48
|
for (let i = 0; i < length; i += 1) {
|
|
@@ -302,6 +319,43 @@ function neighbourSpans(axis, n) {
|
|
|
302
319
|
}
|
|
303
320
|
return { begin, end };
|
|
304
321
|
}
|
|
322
|
+
/**
|
|
323
|
+
* Attach the lazy stable per-bar identity to a bar view — see
|
|
324
|
+
* {@link BarSeries.marks}. `keys` is the **sample's own** axis buffer (the key
|
|
325
|
+
* column's `begin` for a `TimeSeries`, `axisValues()` for a `ValueSeries`), not
|
|
326
|
+
* the possibly-derived bar span, and must already be trimmed to `bars.length`.
|
|
327
|
+
*
|
|
328
|
+
* The strings are built on first read and then memoized. That is why this is a
|
|
329
|
+
* getter rather than an eager array: 100k bars is ~9 ms of string allocation on
|
|
330
|
+
* top of a ~0.8 ms reader, and eager would charge it to every chart on every
|
|
331
|
+
* data update, for a channel a non-interactive one never uses at all. An
|
|
332
|
+
* interactive chart *does* pay it, once per data identity, on its first hover
|
|
333
|
+
* over a bar — see {@link BarSeries.marks}. `scripts/perf-barmarks.mjs` pins
|
|
334
|
+
* both halves.
|
|
335
|
+
*
|
|
336
|
+
* The getter is deliberately **enumerable**, so a `{...bs}` spread carries the
|
|
337
|
+
* marks through (materializing them) rather than silently dropping them — a
|
|
338
|
+
* perf surprise beats a correctness one. Nothing in the package spreads a
|
|
339
|
+
* `BarSeries` today; this is for outside callers. If one ever appears **in the
|
|
340
|
+
* draw path**, it would force materialization on every frame — but
|
|
341
|
+
* `perf-barmarks.mjs`'s `marks untouched` row is measured on exactly that, so
|
|
342
|
+
* it would show up as a reader regression rather than pass silently.
|
|
343
|
+
*/
|
|
344
|
+
function withKeyMarks(bars, keys) {
|
|
345
|
+
let marks;
|
|
346
|
+
return {
|
|
347
|
+
...bars,
|
|
348
|
+
get marks() {
|
|
349
|
+
if (marks === undefined) {
|
|
350
|
+
const out = new Array(bars.length);
|
|
351
|
+
for (let i = 0; i < bars.length; i += 1)
|
|
352
|
+
out[i] = String(keys[i]);
|
|
353
|
+
marks = out;
|
|
354
|
+
}
|
|
355
|
+
return marks;
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
305
359
|
/**
|
|
306
360
|
* Build a {@link BarSeries} from a pond `TimeSeries` — one bar per event, the
|
|
307
361
|
* key's `[begin, end]` as the x-span and `column` as the height.
|
|
@@ -320,6 +374,10 @@ function neighbourSpans(axis, n) {
|
|
|
320
374
|
* interval-keyed series (e.g. an `aggregate`/`window` rollup) draws its true
|
|
321
375
|
* bucket spans. Detected by `keyColumn().kind === 'time'`.
|
|
322
376
|
*
|
|
377
|
+
* Each bar also carries its **own key** as a stable {@link BarSeries.marks}
|
|
378
|
+
* identity, so a selection can be pinned on the sample rather than on the span
|
|
379
|
+
* this derived for it.
|
|
380
|
+
*
|
|
323
381
|
* @throws RangeError if `column` does not exist.
|
|
324
382
|
* @throws TypeError if `column` is not a numeric column.
|
|
325
383
|
*/
|
|
@@ -327,15 +385,15 @@ export function barsFromTimeSeries(series, column) {
|
|
|
327
385
|
const y = readNumericColumn(series, column);
|
|
328
386
|
const n = series.length;
|
|
329
387
|
const kind = series.keyColumn().kind;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
return { begin, end, y, length: n };
|
|
388
|
+
// Interval / timeRange: the key's own endpoints are the bar span. Point key
|
|
389
|
+
// (begin === end): synthesize a span from neighbour spacing so the bars have
|
|
390
|
+
// width (see neighbourSpans).
|
|
391
|
+
const { begin, end } = kind !== 'time'
|
|
392
|
+
? keyBeginEnd(series)
|
|
393
|
+
: neighbourSpans(series.keyColumn().begin, n);
|
|
394
|
+
// The marks key on the event's own timestamp — which for a point key is the
|
|
395
|
+
// bar's *centre*, not the `begin` edge derived above (see BarSeries.marks).
|
|
396
|
+
return withKeyMarks({ begin, end, y, length: n }, timeAxis(series));
|
|
339
397
|
}
|
|
340
398
|
/**
|
|
341
399
|
* Build a {@link BarSeries} from a pond `ValueSeries` — the value-axis sibling
|
|
@@ -353,6 +411,10 @@ export function barsFromTimeSeries(series, column) {
|
|
|
353
411
|
* (a slight drift from a true segment edge — fine for the bar look; key an
|
|
354
412
|
* interval/timeRange `TimeSeries` instead if exact edges matter).
|
|
355
413
|
*
|
|
414
|
+
* Each bar also carries its **axis value** as a stable {@link BarSeries.marks}
|
|
415
|
+
* identity — the centre it is drawn around, not the derived edge — so a
|
|
416
|
+
* selection can be pinned without re-deriving the neighbour spacing.
|
|
417
|
+
*
|
|
356
418
|
* @throws RangeError if `column` does not exist.
|
|
357
419
|
* @throws TypeError if `column` is not a numeric column.
|
|
358
420
|
*/
|
|
@@ -361,8 +423,9 @@ export function barsFromValueSeries(series, column) {
|
|
|
361
423
|
const n = series.length;
|
|
362
424
|
// axisValues() is the monotonic key buffer (zero-copy); neighbourSpans reads it
|
|
363
425
|
// and allocates fresh span buffers (never mutates the source).
|
|
364
|
-
const
|
|
365
|
-
|
|
426
|
+
const axis = series.axisValues();
|
|
427
|
+
const { begin, end } = neighbourSpans(axis, n);
|
|
428
|
+
return withKeyMarks({ begin, end, y, length: n }, axis);
|
|
366
429
|
}
|
|
367
430
|
/**
|
|
368
431
|
* The per-bin `[begin, end]` slots for a `TimeSeries`, key-shape aware — the same
|
|
@@ -472,6 +535,52 @@ export function stacksFromColumns(series, columns) {
|
|
|
472
535
|
}
|
|
473
536
|
return { begin, end, groups: columns, values, length: n };
|
|
474
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Build a {@link BarSeries} from **`byColumn` bin records** — the single-series
|
|
540
|
+
* sibling of {@link stacksFromBins}, for a histogram drawing **one** aggregate
|
|
541
|
+
* field.
|
|
542
|
+
*
|
|
543
|
+
* Why it exists ([PND-BARSEM]): a one-column histogram *is* a single-series
|
|
544
|
+
* bar chart — same mark, same geometry — but routing it through the stacked
|
|
545
|
+
* reader made its capabilities depend on which prop produced it (whole-slot
|
|
546
|
+
* hit-testing, the hover colour, the cursor readout and per-bar decimation all
|
|
547
|
+
* live on the single path). Reading it as a `BarSeries` lets the chart decide
|
|
548
|
+
* by what it *draws* rather than by how it was fed.
|
|
549
|
+
*
|
|
550
|
+
* `column` names the aggregate field; slots are the bins' numeric
|
|
551
|
+
* `[start, end]` edges, or uniform unit slots under `{ ordinal: true }` —
|
|
552
|
+
* identical to {@link stacksFromBins}. A missing / non-finite aggregate reads
|
|
553
|
+
* as a gap (`NaN`), and each bar carries its slot index as a stable
|
|
554
|
+
* {@link BarSeries.marks} identity (bin records have no key of their own).
|
|
555
|
+
*/
|
|
556
|
+
export function barsFromBins(bins, column, options = {}) {
|
|
557
|
+
const n = bins.length;
|
|
558
|
+
const begin = new Float64Array(n);
|
|
559
|
+
const end = new Float64Array(n);
|
|
560
|
+
const y = new Float64Array(n);
|
|
561
|
+
// The bin's own **start value** is its identity — the same axis-key
|
|
562
|
+
// convention every other reader uses, and stable under `ordinal` (which only
|
|
563
|
+
// changes the drawn slot, not which bin a row is). A slot *index* would be
|
|
564
|
+
// positional and would renumber if the bin set changed. Materialized through
|
|
565
|
+
// the shared lazy getter, so a non-interactive layer never pays for the
|
|
566
|
+
// strings (see {@link BarSeries.marks}).
|
|
567
|
+
const keys = new Float64Array(n);
|
|
568
|
+
for (let i = 0; i < n; i += 1) {
|
|
569
|
+
const bin = bins[i];
|
|
570
|
+
if (options.ordinal) {
|
|
571
|
+
begin[i] = i;
|
|
572
|
+
end[i] = i + 1;
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
begin[i] = bin.start;
|
|
576
|
+
end[i] = bin.end;
|
|
577
|
+
}
|
|
578
|
+
keys[i] = bin.start;
|
|
579
|
+
const v = bin[column];
|
|
580
|
+
y[i] = typeof v === 'number' && Number.isFinite(v) ? v : NaN;
|
|
581
|
+
}
|
|
582
|
+
return withKeyMarks({ begin, end, y, length: n }, keys);
|
|
583
|
+
}
|
|
475
584
|
/**
|
|
476
585
|
* Build a {@link StackedBarSeries} from **`byColumn` bin records** — the array of
|
|
477
586
|
* `{ start, end, …aggregates }` a value-band aggregation returns
|
package/dist/decimate.js
CHANGED
|
@@ -636,11 +636,11 @@ export function decimateBars(cs, xScale, ctx, baseline, k = 2, visibleCount = cs
|
|
|
636
636
|
export function decimateScatter(cs, xScale, yScale, cellPx, vStart = 0, vEnd = cs.length) {
|
|
637
637
|
const cell = cellPx > 0 ? cellPx : 1;
|
|
638
638
|
// Affine fast path ([PND-AFFINE]) for the per-point pixel mapping the sweep
|
|
639
|
-
// needs — an inline `
|
|
640
|
-
// closure per point (each axis independently; a non-affine
|
|
641
|
-
// real-gap trading x, falls back to the exact scale call).
|
|
642
|
-
// sweep would re-introduce the per-point d3-scale cost the
|
|
643
|
-
// shed, making the decimation's own cost dominate.
|
|
639
|
+
// needs — an inline rebased `(v − v0)·k + p0` over the typed arrays instead
|
|
640
|
+
// of a d3-scale closure per point (each axis independently; a non-affine
|
|
641
|
+
// axis, e.g. a real-gap trading x, falls back to the exact scale call).
|
|
642
|
+
// Without this the sweep would re-introduce the per-point d3-scale cost the
|
|
643
|
+
// line/area paths shed, making the decimation's own cost dominate.
|
|
644
644
|
const ax = affineOf(xScale);
|
|
645
645
|
const ay = affineOf(yScale);
|
|
646
646
|
const outX = [];
|
|
@@ -652,13 +652,13 @@ export function decimateScatter(cs, xScale, yScale, cellPx, vStart = 0, vEnd = c
|
|
|
652
652
|
if (!Number.isFinite(y))
|
|
653
653
|
continue; // gap — no mark
|
|
654
654
|
const xv = cs.x[i];
|
|
655
|
-
const px = ax !== null ? ax.
|
|
655
|
+
const px = ax !== null ? (xv - ax.v0) * ax.k + ax.p0 : xScale(xv);
|
|
656
656
|
const col = Math.floor(px / cell);
|
|
657
657
|
if (col !== curCol) {
|
|
658
658
|
rows.clear();
|
|
659
659
|
curCol = col;
|
|
660
660
|
}
|
|
661
|
-
const py = ay !== null ? ay.
|
|
661
|
+
const py = ay !== null ? (y - ay.v0) * ay.k + ay.p0 : yScale(y);
|
|
662
662
|
const row = Math.floor(py / cell);
|
|
663
663
|
if (!rows.has(row)) {
|
|
664
664
|
rows.add(row);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
* `@pond-ts/charts` — the visualization end of pond.
|
|
3
3
|
*
|
|
4
4
|
* Canvas-rendered, streaming-first time-series charts with a
|
|
5
|
-
* react-timeseries-charts-style declarative layout
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* react-timeseries-charts-style declarative layout: compose
|
|
6
|
+
* `<ChartContainer>` → `<ChartRow>` → `<Layers>` → draw layers
|
|
7
|
+
* (line/area/band/scatter/bar/box/candle), plus the standalone DOM row lists
|
|
8
|
+
* ({@link BarList} / {@link BoxList}). **The data contract is the pond series
|
|
9
|
+
* itself** — every layer takes a `TimeSeries` / `ValueSeries` (or a
|
|
10
|
+
* partition `Map`, `byColumn` bins, category records) directly and shapes
|
|
11
|
+
* internally; an adapter you must call when starting from a series is an API
|
|
12
|
+
* failure (`docs/notes/charts-api-review-2026-08.md`). The exported `from*`
|
|
13
|
+
* view builders are **interop escape hatches** for non-pond data only.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* {@link fromTimeSeries}. Axes, themes, the variance band, and interactions
|
|
16
|
-
* land in M2–M4. {@link Canvas} is the low-level DPR-aware primitive the rows
|
|
17
|
-
* sit on.
|
|
15
|
+
* Architecture (typed-array store → decimator → canvas renderer → React
|
|
16
|
+
* shell): `docs/rfcs/charts.md`; roadmap: `PLAN.md`. {@link Canvas} is the
|
|
17
|
+
* low-level DPR-aware primitive the rows sit on.
|
|
18
18
|
*
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
@@ -49,6 +49,12 @@ export type { BarChartProps } from './BarChart.js';
|
|
|
49
49
|
export { Candlestick } from './Candlestick.js';
|
|
50
50
|
export type { CandlestickProps } from './Candlestick.js';
|
|
51
51
|
export type { CandleVariant, ColorBy } from './ohlc.js';
|
|
52
|
+
export { BarList } from './BarList.js';
|
|
53
|
+
export type { BarListProps } from './BarList.js';
|
|
54
|
+
export { BoxList } from './BoxList.js';
|
|
55
|
+
export type { BoxListProps } from './BoxList.js';
|
|
56
|
+
export { listRowsFromTimeSeries, listRowsFromValueSeries } from './list.js';
|
|
57
|
+
export type { ListRow, ListValue, ListCellSpec, ListMarker, ListSortDirection, ListRowsOptions, BarListColumn, BoxListColumn, } from './list.js';
|
|
52
58
|
export { Legend } from './Legend.js';
|
|
53
59
|
export type { LegendProps, LegendPlacement } from './Legend.js';
|
|
54
60
|
export type { SwatchSpec, LegendItemInput } from './swatch.js';
|
|
@@ -63,7 +69,7 @@ export type { RegionProps, BaselineProps, MarkerProps } from './annotations.js';
|
|
|
63
69
|
export type { AnnotationKind, CreateSpec } from './context.js';
|
|
64
70
|
export { YAxisIndicator, createLiveValue } from './indicators.js';
|
|
65
71
|
export type { YAxisIndicatorProps, LiveValue } from './indicators.js';
|
|
66
|
-
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, ohlcFromTimeSeries, stacksFromGroups, stacksFromColumns, stacksFromBins, categoryStack, transposeRow, } from './data.js';
|
|
72
|
+
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, barsFromBins, ohlcFromTimeSeries, stacksFromGroups, stacksFromColumns, stacksFromBins, categoryStack, transposeRow, } from './data.js';
|
|
67
73
|
export type { ChartSeries, BandSeries, BoxSeries, BoxColumns, BarSeries, OhlcSeries, OhlcColumns, StackedBarSeries, BinRecord, StacksFromBinsOptions, CategoryDatum, RowAt, TransposeRowOptions, } from './data.js';
|
|
68
74
|
export type { Orientation } from './bars.js';
|
|
69
75
|
export type { RadiusEncoding, ColorEncoding } from './encoding.js';
|
package/dist/index.js
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
* `@pond-ts/charts` — the visualization end of pond.
|
|
3
3
|
*
|
|
4
4
|
* Canvas-rendered, streaming-first time-series charts with a
|
|
5
|
-
* react-timeseries-charts-style declarative layout
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* react-timeseries-charts-style declarative layout: compose
|
|
6
|
+
* `<ChartContainer>` → `<ChartRow>` → `<Layers>` → draw layers
|
|
7
|
+
* (line/area/band/scatter/bar/box/candle), plus the standalone DOM row lists
|
|
8
|
+
* ({@link BarList} / {@link BoxList}). **The data contract is the pond series
|
|
9
|
+
* itself** — every layer takes a `TimeSeries` / `ValueSeries` (or a
|
|
10
|
+
* partition `Map`, `byColumn` bins, category records) directly and shapes
|
|
11
|
+
* internally; an adapter you must call when starting from a series is an API
|
|
12
|
+
* failure (`docs/notes/charts-api-review-2026-08.md`). The exported `from*`
|
|
13
|
+
* view builders are **interop escape hatches** for non-pond data only.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* {@link fromTimeSeries}. Axes, themes, the variance band, and interactions
|
|
16
|
-
* land in M2–M4. {@link Canvas} is the low-level DPR-aware primitive the rows
|
|
17
|
-
* sit on.
|
|
15
|
+
* Architecture (typed-array store → decimator → canvas renderer → React
|
|
16
|
+
* shell): `docs/rfcs/charts.md`; roadmap: `PLAN.md`. {@link Canvas} is the
|
|
17
|
+
* low-level DPR-aware primitive the rows sit on.
|
|
18
18
|
*
|
|
19
19
|
* @packageDocumentation
|
|
20
20
|
*/
|
|
@@ -33,6 +33,14 @@ export { ScatterChart } from './ScatterChart.js';
|
|
|
33
33
|
export { BoxPlot } from './BoxPlot.js';
|
|
34
34
|
export { BarChart } from './BarChart.js';
|
|
35
35
|
export { Candlestick } from './Candlestick.js';
|
|
36
|
+
// The list family — DOM-rendered *ranked row lists* (the react-timeseries-charts
|
|
37
|
+
// `HorizontalBarChart` shape, reconceived as a table): one row per entity, a
|
|
38
|
+
// proportional bar / five-number box line per configured column, data cells,
|
|
39
|
+
// custom sort, per-row expander. Standalone — no <ChartContainer> (the in-plot
|
|
40
|
+
// horizontal bars remain `<BarChart orientation="horizontal">`).
|
|
41
|
+
export { BarList } from './BarList.js';
|
|
42
|
+
export { BoxList } from './BoxList.js';
|
|
43
|
+
export { listRowsFromTimeSeries, listRowsFromValueSeries } from './list.js';
|
|
36
44
|
// The series key: rows enumerate the registered layers' resolved styles.
|
|
37
45
|
export { Legend } from './Legend.js';
|
|
38
46
|
// The headless legend — the same rows + hover/select sync as data, for
|
|
@@ -48,7 +56,7 @@ export { Region, Baseline, Marker } from './annotations.js';
|
|
|
48
56
|
// Axis indicators — a value pill pinned to an axis edge (the ChartIQ live tag).
|
|
49
57
|
// `createLiveValue` is the high-frequency, isolated-repaint update path.
|
|
50
58
|
export { YAxisIndicator, createLiveValue } from './indicators.js';
|
|
51
|
-
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, ohlcFromTimeSeries,
|
|
59
|
+
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, barsFromBins, ohlcFromTimeSeries,
|
|
52
60
|
// Stacked / histogram readers — assemble a StackedBarSeries from pond's own
|
|
53
61
|
// aggregation output: a Map of grouped series, a wide series, or byColumn bins.
|
|
54
62
|
stacksFromGroups, stacksFromColumns, stacksFromBins,
|
package/dist/line.js
CHANGED
|
@@ -26,8 +26,8 @@ export function strokeAffinePolyline(ctx, xs, ys, ax, ay) {
|
|
|
26
26
|
penDown = false;
|
|
27
27
|
continue;
|
|
28
28
|
}
|
|
29
|
-
const px = ax.
|
|
30
|
-
const py = ay.
|
|
29
|
+
const px = (xs[j] - ax.v0) * ax.k + ax.p0;
|
|
30
|
+
const py = (v - ay.v0) * ay.k + ay.p0;
|
|
31
31
|
if (penDown)
|
|
32
32
|
ctx.lineTo(px, py);
|
|
33
33
|
else {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list family's **source union** — `rows` XOR `series`, shared by
|
|
3
|
+
* {@link BarList} and {@link BoxList} ([PND-CHARTAPI]).
|
|
4
|
+
*
|
|
5
|
+
* Two things this fixes, both flagged on #590's review and deferred to here
|
|
6
|
+
* so one union pattern serves the layers and the lists alike:
|
|
7
|
+
*
|
|
8
|
+
* 1. **The doors were both optional**, so `<BarList columns={…} />` with
|
|
9
|
+
* neither source, and `rows` + `series` together, type-checked and threw at
|
|
10
|
+
* render. Now each is a compile error.
|
|
11
|
+
* 2. **The generic row type could lie.** `R` is inferred from the *callbacks*
|
|
12
|
+
* (`sort`, cell `render`, `renderExpanded`, `onRowClick`), so merely
|
|
13
|
+
* annotating one of them while passing `series` inferred a custom `R` that
|
|
14
|
+
* the series door cannot honour — it produces plain {@link ListRow}s. The
|
|
15
|
+
* series member pins `R` to `ListRow`, so the annotation now fails to
|
|
16
|
+
* compile instead of lying at runtime.
|
|
17
|
+
*/
|
|
18
|
+
import type { ReactNode } from 'react';
|
|
19
|
+
import type { SeriesSchema, TimeSeries, ValueSeries, ValueSeriesSchema } from 'pond-ts';
|
|
20
|
+
import type { ListRow, ListRowsOptions } from './list.js';
|
|
21
|
+
/**
|
|
22
|
+
* The **record door**: entity rows built by hand or from partition facts.
|
|
23
|
+
* `R` may extend {@link ListRow} with extra fields, which then flow into
|
|
24
|
+
* every callback fully typed.
|
|
25
|
+
*/
|
|
26
|
+
export interface ListRowsSource<R extends ListRow> {
|
|
27
|
+
readonly rows: readonly R[];
|
|
28
|
+
readonly series?: never;
|
|
29
|
+
readonly label?: never;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The **series door**: one row per event, read internally — starting from a
|
|
33
|
+
* pond series there is no shaping step. Rows are plain {@link ListRow}s (the
|
|
34
|
+
* reader cannot know a caller's custom row shape), so this member does not
|
|
35
|
+
* carry `R`.
|
|
36
|
+
*/
|
|
37
|
+
export interface ListSeriesSource<S extends SeriesSchema, VS extends ValueSeriesSchema> {
|
|
38
|
+
readonly series: TimeSeries<S> | ValueSeries<VS>;
|
|
39
|
+
readonly rows?: never;
|
|
40
|
+
/**
|
|
41
|
+
* The built-in label cell per row — from the row's ordinal and its axis key
|
|
42
|
+
* (epoch ms / axis value). **Omitted ⇒ the stringified key renders.**
|
|
43
|
+
*/
|
|
44
|
+
readonly label?: ListRowsOptions['label'];
|
|
45
|
+
}
|
|
46
|
+
/** `rows` XOR `series` — exactly one door, enforced at compile time. */
|
|
47
|
+
export type ListSource<R extends ListRow, S extends SeriesSchema, VS extends ValueSeriesSchema> = ListRowsSource<R> | ListSeriesSource<S, VS>;
|
|
48
|
+
/**
|
|
49
|
+
* The row type a given source yields: a caller's `R` through the record door,
|
|
50
|
+
* plain {@link ListRow} through the series door. Callback props resolve
|
|
51
|
+
* against this, which is what stops the series door from claiming a custom
|
|
52
|
+
* row shape it cannot produce.
|
|
53
|
+
*/
|
|
54
|
+
export type RowOf<Src> = Src extends {
|
|
55
|
+
rows: readonly (infer T)[];
|
|
56
|
+
} ? T extends ListRow ? T : ListRow : ListRow;
|
|
57
|
+
/** Narrowing helper for the components' runtime read of the union. */
|
|
58
|
+
export declare function isSeriesSource<R extends ListRow, S extends SeriesSchema, VS extends ValueSeriesSchema>(src: ListSource<R, S, VS>): src is ListSeriesSource<S, VS>;
|
|
59
|
+
/** Re-exported for the components' prop docs. */
|
|
60
|
+
export type ListLabel = (i: number, key: number) => ReactNode;
|
|
61
|
+
//# sourceMappingURL=list-source.d.ts.map
|