@pond-ts/react 0.50.0 → 0.52.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 +187 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,9 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
|
|
|
8
8
|
tag, so this file covers them all. Pre-1.0: minor bumps may include new features
|
|
9
9
|
and type-level changes; patch bumps are strictly additive.
|
|
10
10
|
|
|
11
|
-
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.
|
|
11
|
+
[Unreleased]: https://github.com/pond-ts/pond/compare/v0.52.0...HEAD
|
|
12
|
+
[0.52.0]: https://github.com/pond-ts/pond/compare/v0.51.0...v0.52.0
|
|
13
|
+
[0.51.0]: https://github.com/pond-ts/pond/compare/v0.50.0...v0.51.0
|
|
12
14
|
[0.50.0]: https://github.com/pond-ts/pond/compare/v0.49.0...v0.50.0
|
|
13
15
|
[0.49.0]: https://github.com/pond-ts/pond/compare/v0.48.1...v0.49.0
|
|
14
16
|
[0.48.1]: https://github.com/pond-ts/pond/compare/v0.48.0...v0.48.1
|
|
@@ -50,6 +52,190 @@ and type-level changes; patch bumps are strictly additive.
|
|
|
50
52
|
|
|
51
53
|
## [Unreleased]
|
|
52
54
|
|
|
55
|
+
## [0.52.0] — 2026-07-23
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **core / financial:** **Market-scale studies are now typed-array fast**
|
|
60
|
+
(the "SMA/EMA at 1M bars costs hundreds of ms" report). Three cuts along
|
|
61
|
+
the same path, all behaviour-preserving (identical values, warm-ups,
|
|
62
|
+
missing-cell semantics, and rejection errors; every fast path falls back
|
|
63
|
+
to the original sweep when it doesn't apply):
|
|
64
|
+
- **`smooth('ema')` columnar fast path** — on a packed numeric source
|
|
65
|
+
column the EMA recurrence runs straight off the typed buffer into a
|
|
66
|
+
typed result column via trusted construction (key + untouched columns
|
|
67
|
+
pass through zero-copy), replacing the per-row Event/tuple rebuild +
|
|
68
|
+
full-series intake re-pack. 1M rows: **530 ms → 4.4 ms (~120×)**.
|
|
69
|
+
- **`rolling({ count })` numeric fast path** — an all-built-in numeric
|
|
70
|
+
mapping over packed sources feeds the shared incremental reducer states
|
|
71
|
+
directly from the typed buffers and writes snapshots into typed columns
|
|
72
|
+
(no per-row snapshot arrays, no boxed accumulators, no post-pass
|
|
73
|
+
assert/re-pack). 1M rows, `avg`: **135 ms → 32 ms (~4×)**.
|
|
74
|
+
- **financial kernel reads columns, not events** — `rollingColumns` /
|
|
75
|
+
`columnValues` now read study inputs/outputs off the public column API
|
|
76
|
+
instead of materializing `series.events` (an Event + data object per
|
|
77
|
+
row, ~400 ms of pure overhead at 1M rows).
|
|
78
|
+
- End-to-end at 1M bars: `ema()` **603 ms → 2.5 ms (~240×)**, `sma()`
|
|
79
|
+
**569 ms → 56 ms (~10×)**, `bollinger()` **748 ms → 162 ms (~4.6×)**.
|
|
80
|
+
Durable benchmarks: `packages/core/scripts/perf-smooth-ema.mjs`,
|
|
81
|
+
`packages/financial/scripts/perf-studies.mjs`.
|
|
82
|
+
|
|
83
|
+
### Added
|
|
84
|
+
|
|
85
|
+
- **core:** **`TimeSeries.fromArrow(table, options?)` — ingest a decoded Apache
|
|
86
|
+
Arrow `Table`.** pond stays zero-dependency: bring your own Arrow
|
|
87
|
+
(`tableFromIPC(...)`) and hand the `Table` in; the input is duck-typed against
|
|
88
|
+
a small structural surface (`ArrowTableLike` / `ArrowVectorLike` / …, all
|
|
89
|
+
exported). Ingest is the zero-copy path — every `Float64` column's backing
|
|
90
|
+
`Float64Array` is adopted as-is (`Float32`/int columns convert; int64 value
|
|
91
|
+
columns recombine BigInt-free), and the schema is derived from the Arrow
|
|
92
|
+
fields. The time key is converted **BigInt-free**: Arrow's idiomatic int64
|
|
93
|
+
timestamps are recombined from their two int32 halves rather than
|
|
94
|
+
`Number(bigint)` per row — measured **~11× faster** on the time column (0.6ms
|
|
95
|
+
vs 6.8ms at 500k rows; `scripts/perf-from-arrow.mjs`). Options: `time` (key
|
|
96
|
+
column, default the `'time'` field), `timeUnit` (default read from the Arrow
|
|
97
|
+
Arrow type family — a `Timestamp`'s raw-unit int64 is scaled by its
|
|
98
|
+
`TimeUnit`; `Date32`/`Date64` arrive already normalized to epoch-ms and pass
|
|
99
|
+
through; overridable), `columns` (subset, in order), `name`, `sort`. Numeric
|
|
100
|
+
**and string** columns are supported — string columns (Arrow
|
|
101
|
+
`Utf8`) become dict-encoded `StringColumn`s; any other Arrow type
|
|
102
|
+
(list/struct) throws, naming it. A null time key throws; numeric nulls map to
|
|
103
|
+
`NaN` and string nulls to missing.
|
|
104
|
+
- **core:** **`TimeSeries.fromColumns` / `ValueSeries.fromColumns` now accept
|
|
105
|
+
`string` value columns** (previously numeric-only), packed to dict-encoded
|
|
106
|
+
`StringColumn`s (`null`/`undefined` → missing) — the shared columnar-ingress
|
|
107
|
+
engine now dispatches on the schema kind. Other value kinds (`boolean`,
|
|
108
|
+
arrays) still throw.
|
|
109
|
+
- **charts:** **`<ScatterChart decimate>` — dense scatter plots now decimate**
|
|
110
|
+
(PND-MARKDEC scatter half — the last un-decimated mark type). **Default
|
|
111
|
+
`true`.** When the marks are **uniform** (fixed size + colour, no data-driven
|
|
112
|
+
`radius`/`color`), **opaque**, and denser than the pixel grid, overlapping
|
|
113
|
+
marks collapse to one representative per **mark-radius cell** via a 2D
|
|
114
|
+
pixel-**occupancy** sweep. Scatter has no fill, so a line/bar's per-column
|
|
115
|
+
`[min, max]` envelope would erase interior points — the occupancy grid keeps
|
|
116
|
+
one mark per occupied cell instead, which is **visually lossless** for uniform
|
|
117
|
+
opaque marks at that density (same-cell marks overlap). Interaction (hover /
|
|
118
|
+
click / tracker) still reads **every source point**; the per-point selection
|
|
119
|
+
ring + labels are suppressed only on the decimated (dense) path. A
|
|
120
|
+
**translucent** fill (density-encoded — overlap _should_ build up) or a
|
|
121
|
+
data-driven size/colour keeps the full draw. `decimate={false}` draws every
|
|
122
|
+
mark; `{ threshold }` tunes the trigger. The occupancy sweep uses the affine
|
|
123
|
+
fast path for the per-point pixel mapping. Measured (SciChart-suite
|
|
124
|
+
point-update, real browser): **100k 18 → 73 fps (4×)**, and the ladder now
|
|
125
|
+
runs to **10M** points (previously dead by 1M). `drawScatter` now returns
|
|
126
|
+
`LayerDrawStats` (visible via `onDrawStats`).
|
|
127
|
+
|
|
128
|
+
## [0.51.0] — 2026-07-22
|
|
129
|
+
|
|
130
|
+
### Changed
|
|
131
|
+
|
|
132
|
+
- **charts:** **`trackerPosition` is now a _followed_ position, not a hard pin —
|
|
133
|
+
enabling cross-chart cursor sync.** A live local hover wins over
|
|
134
|
+
`trackerPosition`, so the chart under the pointer shows its own cursor while
|
|
135
|
+
any chart without a local pointer follows the controlled time (mapped through
|
|
136
|
+
its own `xScale`, so it's correct across different zooms). This makes
|
|
137
|
+
**multi-chart dashboard cursor sync** fall out of the plain props: give every
|
|
138
|
+
`<ChartContainer>` the same `trackerPosition={sharedTime}` and set `sharedTime`
|
|
139
|
+
from each one's `onTrackerChanged` (clear it to `null` on the group's
|
|
140
|
+
`onPointerLeave`) — no "which chart is active" bookkeeping. **Behaviour
|
|
141
|
+
change:** previously a numeric `trackerPosition` overrode local hover, and
|
|
142
|
+
`trackerPosition={null}` force-hid the cursor; now `null` and `undefined` are
|
|
143
|
+
equivalent ("no controlled position") and a hovered chart always tracks its
|
|
144
|
+
pointer. To force a chart to never show a cursor, use `cursor="none"`. See the
|
|
145
|
+
"Synced cursors across charts" story. No type change (`number | null`).
|
|
146
|
+
- **charts:** **Line / area draw is ~3× faster on stroke-bound frames**
|
|
147
|
+
(PND-AFFINE / PND-GRADX; 2026-07 external-bench profile). When the curve is
|
|
148
|
+
linear and both scales are affine (every y axis is `scaleLinear`; x is
|
|
149
|
+
`scaleLinear` / `scaleTime` / the gap-free default time axis), `drawLine` and
|
|
150
|
+
`drawArea` now map points with an inline `k·v + b` over the typed arrays
|
|
151
|
+
instead of a per-point d3-scale closure + d3-shape generator — a **visually
|
|
152
|
+
identical** draw (guarded by the decimation pixel-identity and per-layer
|
|
153
|
+
visual-regression e2e). A real-gap trading-time axis, or a non-linear curve,
|
|
154
|
+
transparently keeps the exact d3 path. Measured on a JS-only micro-bench
|
|
155
|
+
(`scripts/perf-affine.mjs`): line 1M 60.4 → 19.0 ms (3.2×), area 1M 132 →
|
|
156
|
+
38 ms (3.5×). Separately, the area fill gradient's full-series value extent is
|
|
157
|
+
now memoized per column buffer, so a y-zoom / pan repaint no longer re-walks
|
|
158
|
+
the whole series to find the gradient span. No API change.
|
|
159
|
+
- **charts:** **A y-zoom / y-autorange repaint no longer re-decimates line /
|
|
160
|
+
area layers** (PND-DECKEY; same 2026-07 profile, finding 3). The M4
|
|
161
|
+
decimation output is a pure function of the source data, x-domain, device
|
|
162
|
+
width, threshold, and session breaks — it never reads the y-scale — so it is
|
|
163
|
+
identical across every y-only frame. `drawLine` / `drawArea` now memoize the
|
|
164
|
+
cull+decimate result per source series (one entry, keyed on the x-scale
|
|
165
|
+
object + width + threshold + breaks), so a y-zoom / live y-autorange frame
|
|
166
|
+
reuses the prior polyline instead of re-binning O(N) points; a pan / x-zoom
|
|
167
|
+
mints a fresh x-scale and correctly recomputes. Measured
|
|
168
|
+
(`scripts/perf-deckey.mjs`): the ~5 ms/frame decimation walk at 1M points is
|
|
169
|
+
eliminated on every y-only frame. No API change.
|
|
170
|
+
|
|
171
|
+
### Added
|
|
172
|
+
|
|
173
|
+
- **charts:** **`<BarChart decimate>` — dense column charts now decimate**
|
|
174
|
+
(PND-MARKDEC; 2026-07 profile, finding 4 — "column dead by 5M"). **Default
|
|
175
|
+
`true`**: once the visible **single-series** bars are denser than ~2 per device
|
|
176
|
+
pixel (each slot < ~1px), they're drawn as one per-column **envelope** rect —
|
|
177
|
+
the exact painted union `[min(value, baseline), max(value, baseline)]` of each
|
|
178
|
+
pixel column — instead of every bar, so a 100k–5M-bar column chart stays
|
|
179
|
+
interactive. **Visually lossless** at that density (a perf knob, not a style);
|
|
180
|
+
interaction still reads the source bars (`barAt`), and the per-bar
|
|
181
|
+
selection/hover highlight is suppressed only when decimated (a <1px bar's ring
|
|
182
|
+
isn't visible anyway). `decimate={false}` draws every bar; `{ threshold }`
|
|
183
|
+
tunes the samples-per-pixel factor. No-op for a stacked / multi-group
|
|
184
|
+
histogram (the low-count categorical path). `drawBars` now returns
|
|
185
|
+
`LayerDrawStats` (visible via `onDrawStats`). Measured
|
|
186
|
+
(`scripts/perf-markdec.mjs`, JS-only): the bar draw at 5M points drops
|
|
187
|
+
485 → 26 ms (18.9×), 100k drops 9.7 → 0.9 ms (10.7×) — with the larger
|
|
188
|
+
rasterization win on top, browser-side.
|
|
189
|
+
- **charts:** **`panZoom` is now a three-way mode + a `bounds` extent.**
|
|
190
|
+
`<ChartContainer panZoom>` takes `'none'` / `'pan'` / `'panZoom'` (drag-only
|
|
191
|
+
vs. drag+wheel), with the old boolean kept as shorthand (`true` ⇒ `'panZoom'`,
|
|
192
|
+
`false` ⇒ `'none'`) — so existing charts are unchanged. A new `bounds`
|
|
193
|
+
(`[min, max]`) prop fences pan/zoom to an **outer** extent (panning into an
|
|
194
|
+
edge stops there keeping its span; zoom-out is capped at the whole span), the
|
|
195
|
+
companion
|
|
196
|
+
to the existing `minDuration` zoom-in floor — together they pin the reachable
|
|
197
|
+
window between an inner and outer bound. On a trading-time axis `bounds`
|
|
198
|
+
clamps in wall-clock ms. Purely additive; no type narrowing.
|
|
199
|
+
- **charts:** **Draw-cost + decimation observability** — `<ChartContainer
|
|
200
|
+
onDrawStats>` (PND-DECOBS; dashboard A/B friction, 2026-07-21). Fires a
|
|
201
|
+
`DrawStatsFrame` once per row-canvas repaint (keyed by an opaque `rowKey` for
|
|
202
|
+
multi-row attribution), one `LayerDrawInfo` per layer carrying its `as`,
|
|
203
|
+
measured `drawMs`, and — for a decimating layer (line / area / band / candle /
|
|
204
|
+
box) — `sourceCount` / `drawnCount` / `decimated`.
|
|
205
|
+
Compare `drawnCount` to `sourceCount` to see whether M4 engaged; read `drawMs`
|
|
206
|
+
for per-layer render cost. **Zero-overhead when unused** — the render loop
|
|
207
|
+
skips per-layer timing entirely unless a consumer subscribes. New exports:
|
|
208
|
+
`DrawStatsFrame`, `LayerDrawInfo`.
|
|
209
|
+
|
|
210
|
+
### Fixed
|
|
211
|
+
|
|
212
|
+
- **charts:** hovering no longer repaints the row data canvas on every cursor
|
|
213
|
+
mousemove. The container frame minted a fresh `timeRange` array identity per
|
|
214
|
+
rebuild (and the frame rebuilds per cursor move), which the Layers draw
|
|
215
|
+
callback — depending on `container.timeRange` — read as a domain change:
|
|
216
|
+
each hover frame re-fired the canvas draw effect, including per-layer M4
|
|
217
|
+
re-decimation (measured 105 repaints per 122 mousemove events; with
|
|
218
|
+
`decimate` off, hover fell to ~10 fps). The tuple is now identity-stable on
|
|
219
|
+
its endpoints, restoring the SVG-overlay cursor contract (0 repaints, full
|
|
220
|
+
frame rate while hovering). Found running uPlot's bench protocol against
|
|
221
|
+
pond-charts; guarded by a new hover-sweep perf invariant in
|
|
222
|
+
`e2e/perf-invariants.spec.ts`.
|
|
223
|
+
- **charts:** hovering no longer re-renders cursor-independent components
|
|
224
|
+
(both `YAxis`, `Bar`/`Box`). The cursor position was a `ContainerFrame`
|
|
225
|
+
field, so every mousemove re-identified the whole (~50-field) frame and
|
|
226
|
+
re-rendered **all** its context consumers — even ones that never read the
|
|
227
|
+
cursor. The per-move cursor state (`cursorX`/`cursorY`/`cursorRowKey`) now
|
|
228
|
+
lives in a dedicated `CursorContext`; the frame stays identity-stable across
|
|
229
|
+
a hover, so only the genuine cursor consumers (the `Layers` overlay,
|
|
230
|
+
`XAxis` crosshair pill, `Legend` values) re-render. Measured: 4 → 2 React
|
|
231
|
+
commits per mousemove, ~25% less hover script time on the uPlot-bench
|
|
232
|
+
workload (the win scales with axis/row count). No API change — the split
|
|
233
|
+
types are internal (`PND-HOVCTX`, follow-up to the repaint fix above).
|
|
234
|
+
- **charts:** corrected the `@pond-ts/charts` package-header doc comment, which
|
|
235
|
+
described a "chunked Path2D cache" render stage that was explored and
|
|
236
|
+
**deferred**, never built (it doesn't help the pan case, which re-decimates
|
|
237
|
+
every frame). The stale comment had misled a consumer's perf investigation.
|
|
238
|
+
|
|
53
239
|
## [0.50.0] — 2026-07-21
|
|
54
240
|
|
|
55
241
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pond-ts/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "React hooks for pond-ts live time series",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test:runtime": "vitest run"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"pond-ts": "^0.
|
|
36
|
+
"pond-ts": "^0.52.0",
|
|
37
37
|
"react": "^18.0.0 || ^19.0.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|