pond-ts 0.39.0 → 0.40.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 +41 -14
- package/dist/batch/time-series.d.ts +22 -2
- package/dist/batch/time-series.js +62 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,8 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`, and
|
|
|
8
8
|
them all. Pre-1.0: minor bumps may include new features and type-level changes;
|
|
9
9
|
patch bumps are strictly additive.
|
|
10
10
|
|
|
11
|
-
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.
|
|
11
|
+
[Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.40.0...HEAD
|
|
12
|
+
[0.40.0]: https://github.com/pjm17971/pond-ts/compare/v0.39.0...v0.40.0
|
|
12
13
|
[0.39.0]: https://github.com/pjm17971/pond-ts/compare/v0.38.0...v0.39.0
|
|
13
14
|
[0.38.0]: https://github.com/pjm17971/pond-ts/compare/v0.37.0...v0.38.0
|
|
14
15
|
[0.37.0]: https://github.com/pjm17971/pond-ts/compare/v0.36.0...v0.37.0
|
|
@@ -37,21 +38,34 @@ patch bumps are strictly additive.
|
|
|
37
38
|
|
|
38
39
|
## [Unreleased]
|
|
39
40
|
|
|
41
|
+
## [0.40.0] — 2026-07-05
|
|
42
|
+
|
|
43
|
+
A **core + charts** release from the estela `DataChart`-port friction wave.
|
|
44
|
+
`@pond-ts/react` and `@pond-ts/fit` carry no code changes — republished in
|
|
45
|
+
lock-step (peer ranges widen to `^0.40.0`).
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- `pond-ts`: **`TimeSeries.fromColumns({ sort })`** — an opt-in `sort?: boolean`
|
|
50
|
+
(default `false`) that stable-sorts a columnar payload by key before
|
|
51
|
+
construction, the columnar counterpart of `fromJSON`'s `sort`. The default path
|
|
52
|
+
is unchanged: a decreasing key still throws (a backwards key on the trusted fast
|
|
53
|
+
door is a corruption signal, not silently accepted), and the `Float64Array`
|
|
54
|
+
zero-copy adoption is preserved when `sort` is unset. (#344)
|
|
55
|
+
- `@pond-ts/charts`: **controlled bar hover** — `<ChartContainer hovered
|
|
56
|
+
onHover>`, the transient-hover analog of the existing `selected` / `onSelect`
|
|
57
|
+
pair, keyed by the same `SelectInfo`. Pin a lit `<BarChart>` bar from a legend
|
|
58
|
+
or list row (`hovered`), or mirror a bar-originated hover out-of-band
|
|
59
|
+
(`onHover`); omit both for today's uncontrolled behavior. (#343)
|
|
60
|
+
|
|
40
61
|
### Fixed
|
|
41
62
|
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
mark's `onSelectAnnotation`. The plot now **defers** its pan pointer-capture
|
|
49
|
-
until the pointer actually moves past the drag slop, so a click (no drag) leaves
|
|
50
|
-
the pointer on the mark and its select fires, while a press-drag still pans
|
|
51
|
-
through and the tracker still hides once the pan commits. Resolves the
|
|
52
|
-
browser-dependent finding deferred from #308; adds
|
|
53
|
-
`e2e/annotations-panzoom.spec.ts`, the first real-pointer-event behavior e2e for
|
|
54
|
-
the annotation layer. (#309)
|
|
63
|
+
- `@pond-ts/charts`: **axis and layer registration are value-equality-guarded** —
|
|
64
|
+
a fresh-but-value-equal `ticks` / `format` / `byValue()`-projected `series`
|
|
65
|
+
reference no longer re-registers the axis/layer, fixing a "Maximum update depth
|
|
66
|
+
exceeded" loop on frequently re-rendering (scrub-driven) charts. The layer and
|
|
67
|
+
axis docs gain a memoize note for `format` / `series` (an inline `format`
|
|
68
|
+
closure still must be hoisted — a closure can't be value-compared). (#342)
|
|
55
69
|
|
|
56
70
|
## [0.39.0] — 2026-07-03
|
|
57
71
|
|
|
@@ -89,6 +103,19 @@ carry no code changes — republished in lock-step (peer ranges widen to `^0.39.
|
|
|
89
103
|
|
|
90
104
|
- `@pond-ts/charts`: the crosshair x-axis pill and marker pills read the axis's
|
|
91
105
|
own formatter (a value-axis / off-boundary time no longer shows a raw number).
|
|
106
|
+
- **Charts — click-to-select an annotation now works while `panZoom` is on.** A
|
|
107
|
+
_selectable but non-editable_ `<Region>` / `<Marker>` (one with no `onChange`)
|
|
108
|
+
lets its press bubble to the plot so a drag can pan _through_ it. The plot
|
|
109
|
+
captured the pointer on press to start the pan, and the browser then retargeted
|
|
110
|
+
the resulting `click` onto the plot (Pointer Events spec: a captured pointer's
|
|
111
|
+
compatibility mouse events fire on the capture target) — silently dropping the
|
|
112
|
+
mark's `onSelectAnnotation`. The plot now **defers** its pan pointer-capture
|
|
113
|
+
until the pointer actually moves past the drag slop, so a click (no drag) leaves
|
|
114
|
+
the pointer on the mark and its select fires, while a press-drag still pans
|
|
115
|
+
through and the tracker still hides once the pan commits. Resolves the
|
|
116
|
+
browser-dependent finding deferred from #308; adds
|
|
117
|
+
`e2e/annotations-panzoom.spec.ts`, the first real-pointer-event behavior e2e for
|
|
118
|
+
the annotation layer. (#309)
|
|
92
119
|
|
|
93
120
|
## [0.38.0] — 2026-07-03
|
|
94
121
|
|
|
@@ -112,7 +112,19 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
112
112
|
* the fast path): the resulting series' columns alias the caller's buffers.
|
|
113
113
|
* Mutating an adopted buffer after construction mutates the series. Pass a
|
|
114
114
|
* fresh buffer (e.g. straight off a decode) if this matters; `number[]`
|
|
115
|
-
* columns are always copied.
|
|
115
|
+
* columns are always copied. (**`sort` disables the adoption** — a reorder
|
|
116
|
+
* needs its own buffers, so every column is copied when sorting.)
|
|
117
|
+
*
|
|
118
|
+
* **Ordering.** Keys must be **non-decreasing** — bisect-based operators (crop,
|
|
119
|
+
* `atTime`, range queries) rely on it — so an out-of-order columnar input
|
|
120
|
+
* throws by default (a backwards key on the *trusted* fast door is a corruption
|
|
121
|
+
* signal, not something to silently accept). Pass **`sort: true`** to sort the
|
|
122
|
+
* rows by key before construction instead — the columnar analog of
|
|
123
|
+
* {@link TimeSeries.fromJSON}'s `sort`, paying `O(n log n)` + a copy only when
|
|
124
|
+
* asked. The sort is **stable**, so rows sharing a key keep their input order.
|
|
125
|
+
* (To flatten a genuine backwards blip to a plateau — a different, lossy choice
|
|
126
|
+
* — clamp the key column yourself before ingest; the library won't mutate data
|
|
127
|
+
* for you.)
|
|
116
128
|
*
|
|
117
129
|
* **v1 scope:** a `time`-kind key + `number` value columns (the market-data /
|
|
118
130
|
* chart wire case). Other key kinds (`interval` / `timeRange`) and non-numeric
|
|
@@ -120,12 +132,20 @@ export declare class TimeSeries<S extends SeriesSchema> {
|
|
|
120
132
|
*
|
|
121
133
|
* @throws ValidationError on a missing column, a length mismatch, an
|
|
122
134
|
* unsupported kind, a non-finite timestamp key, or an out-of-order
|
|
123
|
-
* (decreasing) timestamp — keys must be non-decreasing,
|
|
135
|
+
* (decreasing) timestamp when `sort` is not set — keys must be non-decreasing,
|
|
136
|
+
* same as `fromJSON`.
|
|
124
137
|
*/
|
|
125
138
|
static fromColumns<S extends SeriesSchema>(input: {
|
|
126
139
|
name: string;
|
|
127
140
|
schema: S;
|
|
128
141
|
columns: Record<string, ReadonlyArray<number | null | undefined> | Float64Array>;
|
|
142
|
+
/**
|
|
143
|
+
* Sort the rows by key before construction (off by default), for a columnar
|
|
144
|
+
* payload whose rows aren't guaranteed ordered — the counterpart of
|
|
145
|
+
* `fromJSON`'s `sort`. Stable; disables the `Float64Array` zero-copy adoption
|
|
146
|
+
* (columns are reordered into fresh buffers).
|
|
147
|
+
*/
|
|
148
|
+
sort?: boolean;
|
|
129
149
|
}): TimeSeries<S>;
|
|
130
150
|
/**
|
|
131
151
|
* Example: `TimeSeries.fromEvents(events, { schema, name })`.
|
|
@@ -511,7 +511,19 @@ export class TimeSeries {
|
|
|
511
511
|
* the fast path): the resulting series' columns alias the caller's buffers.
|
|
512
512
|
* Mutating an adopted buffer after construction mutates the series. Pass a
|
|
513
513
|
* fresh buffer (e.g. straight off a decode) if this matters; `number[]`
|
|
514
|
-
* columns are always copied.
|
|
514
|
+
* columns are always copied. (**`sort` disables the adoption** — a reorder
|
|
515
|
+
* needs its own buffers, so every column is copied when sorting.)
|
|
516
|
+
*
|
|
517
|
+
* **Ordering.** Keys must be **non-decreasing** — bisect-based operators (crop,
|
|
518
|
+
* `atTime`, range queries) rely on it — so an out-of-order columnar input
|
|
519
|
+
* throws by default (a backwards key on the *trusted* fast door is a corruption
|
|
520
|
+
* signal, not something to silently accept). Pass **`sort: true`** to sort the
|
|
521
|
+
* rows by key before construction instead — the columnar analog of
|
|
522
|
+
* {@link TimeSeries.fromJSON}'s `sort`, paying `O(n log n)` + a copy only when
|
|
523
|
+
* asked. The sort is **stable**, so rows sharing a key keep their input order.
|
|
524
|
+
* (To flatten a genuine backwards blip to a plateau — a different, lossy choice
|
|
525
|
+
* — clamp the key column yourself before ingest; the library won't mutate data
|
|
526
|
+
* for you.)
|
|
515
527
|
*
|
|
516
528
|
* **v1 scope:** a `time`-kind key + `number` value columns (the market-data /
|
|
517
529
|
* chart wire case). Other key kinds (`interval` / `timeRange`) and non-numeric
|
|
@@ -519,10 +531,11 @@ export class TimeSeries {
|
|
|
519
531
|
*
|
|
520
532
|
* @throws ValidationError on a missing column, a length mismatch, an
|
|
521
533
|
* unsupported kind, a non-finite timestamp key, or an out-of-order
|
|
522
|
-
* (decreasing) timestamp — keys must be non-decreasing,
|
|
534
|
+
* (decreasing) timestamp when `sort` is not set — keys must be non-decreasing,
|
|
535
|
+
* same as `fromJSON`.
|
|
523
536
|
*/
|
|
524
537
|
static fromColumns(input) {
|
|
525
|
-
const { name, schema, columns } = input;
|
|
538
|
+
const { name, schema, columns, sort = false } = input;
|
|
526
539
|
// Key column (schema[0]). v1: point-in-time (`time`) keys only.
|
|
527
540
|
const keyDef = schema[0];
|
|
528
541
|
if (keyDef === undefined) {
|
|
@@ -540,18 +553,38 @@ export class TimeSeries {
|
|
|
540
553
|
// generic iterable-protocol path even for a plain array, ~15-20x slower
|
|
541
554
|
// than a preallocated-buffer copy at 100k-element scale — measured, not
|
|
542
555
|
// theoretical (see the pond-columnar-ingest spike's ingest regression).
|
|
543
|
-
let
|
|
556
|
+
let rawBegin;
|
|
544
557
|
if (keyRaw instanceof Float64Array) {
|
|
545
|
-
|
|
558
|
+
rawBegin = keyRaw;
|
|
546
559
|
}
|
|
547
560
|
else {
|
|
548
|
-
|
|
561
|
+
rawBegin = new Float64Array(keyRaw.length);
|
|
549
562
|
for (let j = 0; j < keyRaw.length; j += 1) {
|
|
550
563
|
const v = keyRaw[j];
|
|
551
|
-
|
|
564
|
+
rawBegin[j] = v == null ? NaN : Number(v);
|
|
552
565
|
}
|
|
553
566
|
}
|
|
554
|
-
const count =
|
|
567
|
+
const count = rawBegin.length;
|
|
568
|
+
// `sort: true` — reorder every column by ascending key before construction.
|
|
569
|
+
// Compute the row permutation once (a stable sort of the index array; V8's
|
|
570
|
+
// Array.sort is stable, so equal keys keep input order, matching fromJSON's
|
|
571
|
+
// stable intake), then remap the key + each value column through it below.
|
|
572
|
+
// `order` stays null on the (default) trusted fast path, so no allocation /
|
|
573
|
+
// copy is paid unless asked. A non-finite key is left for `TimeKeyColumn` to
|
|
574
|
+
// reject — sorting can't make it valid.
|
|
575
|
+
let begin;
|
|
576
|
+
let order = null;
|
|
577
|
+
if (sort) {
|
|
578
|
+
const idx = Array.from({ length: count }, (_, i) => i);
|
|
579
|
+
idx.sort((a, b) => rawBegin[a] - rawBegin[b]);
|
|
580
|
+
order = Uint32Array.from(idx);
|
|
581
|
+
begin = new Float64Array(count);
|
|
582
|
+
for (let j = 0; j < count; j += 1)
|
|
583
|
+
begin[j] = rawBegin[order[j]];
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
begin = rawBegin;
|
|
587
|
+
}
|
|
555
588
|
// Throws on any non-finite timestamp.
|
|
556
589
|
const keys = new TimeKeyColumn(begin, count);
|
|
557
590
|
// Enforce the non-decreasing-key invariant that `fromJSON`'s
|
|
@@ -560,11 +593,13 @@ export class TimeSeries {
|
|
|
560
593
|
// bisect-based operators (crop, `atTime`, range queries) rely on it, so an
|
|
561
594
|
// unsorted columnar input must fail loudly here rather than build a silently
|
|
562
595
|
// broken series. One O(N) scan over already-finite values — negligible next
|
|
563
|
-
// to decode.
|
|
596
|
+
// to decode. (When `sort` is set the keys are now non-decreasing, so this is
|
|
597
|
+
// a cheap post-condition check rather than a rejection.)
|
|
564
598
|
for (let j = 1; j < count; j += 1) {
|
|
565
599
|
if (begin[j] < begin[j - 1]) {
|
|
566
600
|
throw new ValidationError(`fromColumns: key column '${keyDef.name}' is out of order at index ${j} ` +
|
|
567
|
-
`(${begin[j]} < ${begin[j - 1]}) — timestamps must be non-decreasing;
|
|
601
|
+
`(${begin[j]} < ${begin[j - 1]}) — timestamps must be non-decreasing; ` +
|
|
602
|
+
`pass { sort: true } or pre-sort the columns`);
|
|
568
603
|
}
|
|
569
604
|
}
|
|
570
605
|
// Value columns — packed directly (missing-aware) from the arrays.
|
|
@@ -594,7 +629,23 @@ export class TimeSeries {
|
|
|
594
629
|
// Manual loop, not `Float64Array.from(arr, mapFn)` — see the key-column
|
|
595
630
|
// comment above; the cost applies identically here.
|
|
596
631
|
let values;
|
|
597
|
-
if (
|
|
632
|
+
if (order !== null) {
|
|
633
|
+
// Sorting: reorder into a fresh buffer through the key permutation (no
|
|
634
|
+
// zero-copy adoption — the rows are being moved). Same missing rule
|
|
635
|
+
// (`null`/`undefined` → NaN) applied while remapping.
|
|
636
|
+
values = new Float64Array(count);
|
|
637
|
+
if (raw instanceof Float64Array) {
|
|
638
|
+
for (let j = 0; j < count; j += 1)
|
|
639
|
+
values[j] = raw[order[j]];
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
for (let j = 0; j < count; j += 1) {
|
|
643
|
+
const v = raw[order[j]];
|
|
644
|
+
values[j] = v == null ? NaN : v;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
else if (raw instanceof Float64Array) {
|
|
598
649
|
values = raw;
|
|
599
650
|
}
|
|
600
651
|
else {
|