pond-ts 0.50.0 → 0.51.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.
Files changed (2) hide show
  1. package/CHANGELOG.md +113 -1
  2. 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`,
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.50.0...HEAD
11
+ [Unreleased]: https://github.com/pond-ts/pond/compare/v0.51.0...HEAD
12
+ [0.51.0]: https://github.com/pond-ts/pond/compare/v0.50.0...v0.51.0
12
13
  [0.50.0]: https://github.com/pond-ts/pond/compare/v0.49.0...v0.50.0
13
14
  [0.49.0]: https://github.com/pond-ts/pond/compare/v0.48.1...v0.49.0
14
15
  [0.48.1]: https://github.com/pond-ts/pond/compare/v0.48.0...v0.48.1
@@ -50,6 +51,117 @@ and type-level changes; patch bumps are strictly additive.
50
51
 
51
52
  ## [Unreleased]
52
53
 
54
+ ## [0.51.0] — 2026-07-22
55
+
56
+ ### Changed
57
+
58
+ - **charts:** **`trackerPosition` is now a _followed_ position, not a hard pin —
59
+ enabling cross-chart cursor sync.** A live local hover wins over
60
+ `trackerPosition`, so the chart under the pointer shows its own cursor while
61
+ any chart without a local pointer follows the controlled time (mapped through
62
+ its own `xScale`, so it's correct across different zooms). This makes
63
+ **multi-chart dashboard cursor sync** fall out of the plain props: give every
64
+ `<ChartContainer>` the same `trackerPosition={sharedTime}` and set `sharedTime`
65
+ from each one's `onTrackerChanged` (clear it to `null` on the group's
66
+ `onPointerLeave`) — no "which chart is active" bookkeeping. **Behaviour
67
+ change:** previously a numeric `trackerPosition` overrode local hover, and
68
+ `trackerPosition={null}` force-hid the cursor; now `null` and `undefined` are
69
+ equivalent ("no controlled position") and a hovered chart always tracks its
70
+ pointer. To force a chart to never show a cursor, use `cursor="none"`. See the
71
+ "Synced cursors across charts" story. No type change (`number | null`).
72
+ - **charts:** **Line / area draw is ~3× faster on stroke-bound frames**
73
+ (PND-AFFINE / PND-GRADX; 2026-07 external-bench profile). When the curve is
74
+ linear and both scales are affine (every y axis is `scaleLinear`; x is
75
+ `scaleLinear` / `scaleTime` / the gap-free default time axis), `drawLine` and
76
+ `drawArea` now map points with an inline `k·v + b` over the typed arrays
77
+ instead of a per-point d3-scale closure + d3-shape generator — a **visually
78
+ identical** draw (guarded by the decimation pixel-identity and per-layer
79
+ visual-regression e2e). A real-gap trading-time axis, or a non-linear curve,
80
+ transparently keeps the exact d3 path. Measured on a JS-only micro-bench
81
+ (`scripts/perf-affine.mjs`): line 1M 60.4 → 19.0 ms (3.2×), area 1M 132 →
82
+ 38 ms (3.5×). Separately, the area fill gradient's full-series value extent is
83
+ now memoized per column buffer, so a y-zoom / pan repaint no longer re-walks
84
+ the whole series to find the gradient span. No API change.
85
+ - **charts:** **A y-zoom / y-autorange repaint no longer re-decimates line /
86
+ area layers** (PND-DECKEY; same 2026-07 profile, finding 3). The M4
87
+ decimation output is a pure function of the source data, x-domain, device
88
+ width, threshold, and session breaks — it never reads the y-scale — so it is
89
+ identical across every y-only frame. `drawLine` / `drawArea` now memoize the
90
+ cull+decimate result per source series (one entry, keyed on the x-scale
91
+ object + width + threshold + breaks), so a y-zoom / live y-autorange frame
92
+ reuses the prior polyline instead of re-binning O(N) points; a pan / x-zoom
93
+ mints a fresh x-scale and correctly recomputes. Measured
94
+ (`scripts/perf-deckey.mjs`): the ~5 ms/frame decimation walk at 1M points is
95
+ eliminated on every y-only frame. No API change.
96
+
97
+ ### Added
98
+
99
+ - **charts:** **`<BarChart decimate>` — dense column charts now decimate**
100
+ (PND-MARKDEC; 2026-07 profile, finding 4 — "column dead by 5M"). **Default
101
+ `true`**: once the visible **single-series** bars are denser than ~2 per device
102
+ pixel (each slot < ~1px), they're drawn as one per-column **envelope** rect —
103
+ the exact painted union `[min(value, baseline), max(value, baseline)]` of each
104
+ pixel column — instead of every bar, so a 100k–5M-bar column chart stays
105
+ interactive. **Visually lossless** at that density (a perf knob, not a style);
106
+ interaction still reads the source bars (`barAt`), and the per-bar
107
+ selection/hover highlight is suppressed only when decimated (a <1px bar's ring
108
+ isn't visible anyway). `decimate={false}` draws every bar; `{ threshold }`
109
+ tunes the samples-per-pixel factor. No-op for a stacked / multi-group
110
+ histogram (the low-count categorical path). `drawBars` now returns
111
+ `LayerDrawStats` (visible via `onDrawStats`). Measured
112
+ (`scripts/perf-markdec.mjs`, JS-only): the bar draw at 5M points drops
113
+ 485 → 26 ms (18.9×), 100k drops 9.7 → 0.9 ms (10.7×) — with the larger
114
+ rasterization win on top, browser-side.
115
+ - **charts:** **`panZoom` is now a three-way mode + a `bounds` extent.**
116
+ `<ChartContainer panZoom>` takes `'none'` / `'pan'` / `'panZoom'` (drag-only
117
+ vs. drag+wheel), with the old boolean kept as shorthand (`true` ⇒ `'panZoom'`,
118
+ `false` ⇒ `'none'`) — so existing charts are unchanged. A new `bounds`
119
+ (`[min, max]`) prop fences pan/zoom to an **outer** extent (panning into an
120
+ edge stops there keeping its span; zoom-out is capped at the whole span), the
121
+ companion
122
+ to the existing `minDuration` zoom-in floor — together they pin the reachable
123
+ window between an inner and outer bound. On a trading-time axis `bounds`
124
+ clamps in wall-clock ms. Purely additive; no type narrowing.
125
+ - **charts:** **Draw-cost + decimation observability** — `<ChartContainer
126
+ onDrawStats>` (PND-DECOBS; dashboard A/B friction, 2026-07-21). Fires a
127
+ `DrawStatsFrame` once per row-canvas repaint (keyed by an opaque `rowKey` for
128
+ multi-row attribution), one `LayerDrawInfo` per layer carrying its `as`,
129
+ measured `drawMs`, and — for a decimating layer (line / area / band / candle /
130
+ box) — `sourceCount` / `drawnCount` / `decimated`.
131
+ Compare `drawnCount` to `sourceCount` to see whether M4 engaged; read `drawMs`
132
+ for per-layer render cost. **Zero-overhead when unused** — the render loop
133
+ skips per-layer timing entirely unless a consumer subscribes. New exports:
134
+ `DrawStatsFrame`, `LayerDrawInfo`.
135
+
136
+ ### Fixed
137
+
138
+ - **charts:** hovering no longer repaints the row data canvas on every cursor
139
+ mousemove. The container frame minted a fresh `timeRange` array identity per
140
+ rebuild (and the frame rebuilds per cursor move), which the Layers draw
141
+ callback — depending on `container.timeRange` — read as a domain change:
142
+ each hover frame re-fired the canvas draw effect, including per-layer M4
143
+ re-decimation (measured 105 repaints per 122 mousemove events; with
144
+ `decimate` off, hover fell to ~10 fps). The tuple is now identity-stable on
145
+ its endpoints, restoring the SVG-overlay cursor contract (0 repaints, full
146
+ frame rate while hovering). Found running uPlot's bench protocol against
147
+ pond-charts; guarded by a new hover-sweep perf invariant in
148
+ `e2e/perf-invariants.spec.ts`.
149
+ - **charts:** hovering no longer re-renders cursor-independent components
150
+ (both `YAxis`, `Bar`/`Box`). The cursor position was a `ContainerFrame`
151
+ field, so every mousemove re-identified the whole (~50-field) frame and
152
+ re-rendered **all** its context consumers — even ones that never read the
153
+ cursor. The per-move cursor state (`cursorX`/`cursorY`/`cursorRowKey`) now
154
+ lives in a dedicated `CursorContext`; the frame stays identity-stable across
155
+ a hover, so only the genuine cursor consumers (the `Layers` overlay,
156
+ `XAxis` crosshair pill, `Legend` values) re-render. Measured: 4 → 2 React
157
+ commits per mousemove, ~25% less hover script time on the uPlot-bench
158
+ workload (the win scales with axis/row count). No API change — the split
159
+ types are internal (`PND-HOVCTX`, follow-up to the repaint fix above).
160
+ - **charts:** corrected the `@pond-ts/charts` package-header doc comment, which
161
+ described a "chunked Path2D cache" render stage that was explored and
162
+ **deferred**, never built (it doesn't help the pan case, which re-decimates
163
+ every frame). The stale comment had misled a consumer's perf investigation.
164
+
53
165
  ## [0.50.0] — 2026-07-21
54
166
 
55
167
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pond-ts",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "description": "TypeScript-first time series primitives",
5
5
  "license": "MIT",
6
6
  "repository": {