@pond-ts/react 0.42.0 → 0.44.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 +164 -1
  2. 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/pjm17971/pond-ts/compare/v0.42.0...HEAD
11
+ [Unreleased]: https://github.com/pjm17971/pond-ts/compare/v0.44.0...HEAD
12
+ [0.44.0]: https://github.com/pjm17971/pond-ts/compare/v0.43.0...v0.44.0
13
+ [0.43.0]: https://github.com/pjm17971/pond-ts/compare/v0.42.0...v0.43.0
12
14
  [0.42.0]: https://github.com/pjm17971/pond-ts/compare/v0.41.0...v0.42.0
13
15
  [0.41.0]: https://github.com/pjm17971/pond-ts/compare/v0.40.0...v0.41.0
14
16
  [0.40.0]: https://github.com/pjm17971/pond-ts/compare/v0.39.0...v0.40.0
@@ -40,6 +42,157 @@ and type-level changes; patch bumps are strictly additive.
40
42
 
41
43
  ## [Unreleased]
42
44
 
45
+ ## [0.44.0] — 2026-07-11
46
+
47
+ The **value-axis charts** release: cross-sectional data (a volatility smile keyed
48
+ by strike) becomes a first-class charting surface. `ValueSeries.fromColumns` is
49
+ the direct columnar door; `<ScatterChart>` and `<BoxPlot>` join `<LineChart>` on
50
+ the value axis; `<BoxPlot>` gains range-only (bid→ask) marks, `offset` pairing,
51
+ and `capWidth`; and the region cursor works on value axes and snaps to histogram
52
+ bins.
53
+
54
+ ### Added
55
+
56
+ - **`<BoxPlot>` finished for the value axis + range-only marks** (`@pond-ts/charts`).
57
+ Four coordinated changes, driven by the volatility smile's per-strike bid/ask IV
58
+ segments (`docs/notes/vol-smile-followups-2026-07.md` §1):
59
+ - **Accepts a `ValueSeries`** (`series.byValue('strike')` /
60
+ `ValueSeries.fromColumns`) — boxes on a value axis, the same instanceof branch
61
+ as `<LineChart>` / `<ScatterChart>`. The box **width** now comes from neighbour
62
+ spacing for a **point** key (a `ValueSeries`, or a point-keyed `TimeSeries`) —
63
+ like bars/candles — instead of collapsing to the 1px floor; an interval-keyed
64
+ `TimeSeries` still uses its `[begin, end)`.
65
+ - **Optional `q1`/`median`/`q3`** — omit `q1`+`q3` for a **range-only** box: a
66
+ whisker-only `lower→upper` segment, no body (a bid→ask IV mark honestly named,
67
+ not a candlestick abuse). Omitting exactly one of `q1`/`q3` throws.
68
+ - **`offset` prop** (`<BoxPlot>` and `<ScatterChart>`) — a **pixel** shift for
69
+ pairing same-key marks (call/put at one strike) side by side, zoom-stable. On
70
+ the scatter it moves the draw **and** the click hit-test together; on the box
71
+ the readout hit-tests in un-shifted data space (keep the offset small).
72
+ - **`capWidth` prop** (`<BoxPlot>`) — the whisker end-cap width in **pixels**
73
+ (else half the box width). A small fixed cap keeps two `offset`-paired marks'
74
+ T-bars from overlapping when the value-axis slot is wide; clamped to the box
75
+ width, `'whisker'` shape only.
76
+ - **Readout labels** carry the series' `as` identity (`iv upper`, `iv median`)
77
+ when set, instead of bare column names — the `as ?? column` convention
78
+ Line/Scatter already use.
79
+ - **`<ScatterChart>` accepts a `ValueSeries`** (`@pond-ts/charts`) — scatter
80
+ marks on the value axis, the same instanceof-branched adapter as
81
+ `<LineChart>` (the container infers the x kind from the data). The
82
+ data-driven `radius` / `color` encodings work unchanged on a value axis;
83
+ the per-point `label` reads through a new columnar branch (a `ValueSeries`
84
+ has no per-row events) — IV marks keyed by strike with open-interest
85
+ radius is the driving composition (vol smile). New value-axis Storybook
86
+ fan-out (`ValueAxis` / `ValueAxisEncoded` / `ValueAxisSmile` /
87
+ `ValueAxisFlag`) + Linux visual baselines.
88
+
89
+ - **`ValueSeries.fromColumns({ name, schema, columns, sort? })`** (`pond-ts`) —
90
+ the direct columnar door into value-land, for data that is _natively_
91
+ value-keyed (cross-sectional): an options chain keyed by strike, a spectrum
92
+ keyed by frequency. Exact `TimeSeries.fromColumns` contract with the axis in
93
+ place of time — same polymorphic `number[]` / `Float64Array` inputs, same
94
+ zero-copy adoption, same stable opt-in `sort`, same gap rule — the two doors
95
+ share one ingest engine. Previously cross-sectional callers had to launder
96
+ the axis through a fake `time` column (`TimeSeries.fromColumns` +
97
+ `byValue`); that detour is no longer needed.
98
+
99
+ - **The region cursor snaps to a histogram's bins** (`@pond-ts/charts`). On a
100
+ `<BarChart>` histogram, `cursor="region"` now snaps **bar by bar** with no
101
+ `cursorSequence`: hovering highlights the bar under the pointer, a drag extends
102
+ across whole bars, and `onRegionSelect` reports the selected bin range
103
+ `[lo, hi]` at the bar edges. The bar layer publishes its `[begin, end)` spans
104
+ (a new internal `binIntervals` channel) as the region cursor's snap buckets —
105
+ the same machinery a `cursorSequence` drives on a time axis, so it also covers a
106
+ time-axis histogram. Only a **vertical** bar layer on a continuous (time /
107
+ value) x axis publishes bins; a horizontal chart (value on x) and a categorical
108
+ axis stay freeform / excluded. An explicit `cursorSequence` still takes
109
+ precedence. New `HistogramBins` region-cursor story.
110
+
111
+ ### Changed
112
+
113
+ - **BREAKING (`@pond-ts/charts`): the region cursor works on a value x-axis, and
114
+ `onRegionSelect` reports a neutral `[lo, hi]` pair.** The drag-select callback
115
+ fired a `TimeRange`; it now fires `readonly [number, number]` in **axis units** —
116
+ epoch ms on a time axis, the axis value (strike, distance, …) on a value axis —
117
+ mirroring the container's polymorphic `range` input (which never takes the axis
118
+ _kind_ from its value). A time-axis consumer that wants a `TimeRange` builds one
119
+ from the pair (`new TimeRange({ start: lo, end: hi })`). The cursor itself is
120
+ ungated from time-only to any **continuous** x-axis (time **or** value; a
121
+ **category** axis stays excluded — an ordinal-slot select is a different gesture).
122
+ Bucket **snapping** stays time-only (a `cursorSequence` bucket is a time
123
+ interval), so a value axis is always **freeform** (hover line + raw-span drag).
124
+
125
+ ## [0.43.0] — 2026-07-11
126
+
127
+ The **categorical x-axis** release: a first-class ordinal band scale (ticker /
128
+ account / expiry on x — the transpose view of a time series), plus the charts
129
+ **interaction** wave that landed after v0.42.0 was cut — the region cursor and
130
+ its drag-to-select gesture, per-bin band colour, and annotation edges that snap
131
+ to session boundaries.
132
+
133
+ ### Added
134
+
135
+ - **`@pond-ts/charts`: a first-class categorical x-axis.** `<BarChart
136
+ categories={[{ label, value }]}>` draws one bar per category on an ordinal
137
+ **band scale** (the transpose view's "columns on x" — ticker / account / zone
138
+ on x). The container infers `xKind:'category'` and builds a `scaleBand` over the
139
+ labels; `<CategoryAxis>` ticks once per category. Colour per category via
140
+ `binColors`; selection reports the category name. **Negative** category values
141
+ draw below the baseline (the P&L / delta case) — a single-series category bar
142
+ honours its sign. New exports `scaleBand` / `ScaleBand`, `CategoryAxis`,
143
+ `categoryStack` / `CategoryDatum`. Additive — a new x-kind alongside time /
144
+ value; existing charts are unchanged. First slice of the categorical-axis RFC
145
+ (`docs/rfcs/categorical-axis.md`, Phase 1).
146
+ - **`@pond-ts/charts`: `transposeRow`** — read one **row** of a wide `TimeSeries`
147
+ **across** into `{ label, value }[]` for `<BarChart categories>`: the schema's
148
+ numeric columns (a `pivotByGroup` output's per-group columns, a term
149
+ structure's per-expiry columns) become the categories at one instant. Pick the
150
+ row with `at` (`'last'` — the head/live row — by default; `'first'`, an index,
151
+ or `{ time }`); bound / order the set with `columns`.
152
+ - **`@pond-ts/charts`: stable per-column selection identity.** `SelectInfo` gains
153
+ an optional **`mark`** — a stable per-mark identity within a layer. A categorical
154
+ bar reports its **column name** as `mark`, and a controlled `selected` echo /
155
+ the highlight match key on `(id, mark)`, so a pinned selection survives a column
156
+ reorder (the slot index doesn't; the name does). Additive — `mark` is
157
+ `undefined` for a time / value bar (whose sample `key` is already its identity).
158
+ Plus a category-axis **label policy**: a dense axis thins (keeps every k-th) and
159
+ ellipsis-truncates its labels so they stay legible while every bar draws.
160
+ - **`@pond-ts/charts`: region cursor (`cursor="region"`).** A shaded **band**
161
+ highlights the bucket under the pointer, bucketed by a new **`cursorSequence`**
162
+ prop — a `Sequence` (`Sequence.every('15m')`, `Sequence.calendar('week')`)
163
+ realized over the view, or a `BoundedSequence` (a `TradingCalendar`'s
164
+ `sessionSequence()` / `barSequence()`) used as-is. The band maps through the x
165
+ scale, so on a trading-time axis the closed part of a bucket collapses (crops
166
+ to live sessions). Time-axis only (a no-op on a value axis). (#409, #413)
167
+ - **`@pond-ts/charts`: draggable region cursor → one-shot select.** Opt-in
168
+ **`onRegionSelect?: (range: TimeRange) => void`** makes the region cursor
169
+ draggable: the band extends bucket by bucket and fires **once** on release
170
+ with the selected `[start, end)` `TimeRange` (the cursor doesn't keep it —
171
+ typical use is to zoom the view). With **no `cursorSequence`** it degenerates
172
+ to a hover **line** + **freeform** drag. **`regionSelectModifier="shift"`**
173
+ resolves the gesture conflict with `panZoom` (plain drag pans, shift-drag
174
+ selects); omitted, a region-drag preempts pan. (#416)
175
+ - **`@pond-ts/charts`: `binColors` — per-bin colour for single-series bars.**
176
+ `<BarChart binColors={[...]}>` colours each bar/band segment individually (one
177
+ colour per bin, in order), the single-series analog of the stacked `colors`
178
+ prop — used by the category axis (colour per category) and any single-series
179
+ band chart. (#408)
180
+ - **`@pond-ts/charts`: annotation edges snap to session boundaries.** When a
181
+ `<ChartContainer>` carries a trading calendar (disjoint x axis), dragging a
182
+ `<Region>` edge (or creating one) snaps to the nearest **session boundary**
183
+ rather than raw wall-clock, so a drawn span aligns with real market sessions.
184
+ (#410)
185
+
186
+ ### Fixed
187
+
188
+ - **`@pond-ts/charts`: region body-move no longer distorts across a session
189
+ boundary.** On a trading-time (discontinuous) axis, dragging a `<Region>`
190
+ annotation by its body now translates it rigidly in pixel space, so the box
191
+ keeps its width as it crosses a collapsed gap (it previously applied one
192
+ value-delta to both edges, which stretched the box in the different
193
+ rate-contexts either side of a session boundary). No-op on a continuous axis.
194
+ (#405)
195
+
43
196
  ## [0.42.0] — 2026-07-10
44
197
 
45
198
  The **trading-calendar** release: a new `@pond-ts/financial` package (its first
@@ -82,6 +235,16 @@ collapses closed-market time (weekends, holidays, overnight, lunch breaks).
82
235
  (`theme.axis.sessionDivider`), aligned with the labels.
83
236
  - `Charts/TradingTimeAxis` stories (weekend/holiday/half-day/intraday,
84
237
  continuous-vs-trading, daily-months, proportional-vs-uniform).
238
+ - **`@pond-ts/charts`: first-class histograms.** `<BarChart>` gains **stacking**
239
+ — a group-by dimension → stacked segments, from a wide series (`columns`), a
240
+ `Map<group, TimeSeries>` (the `partitionBy().aggregate().toMap()` shape), or a
241
+ `byColumn` `bins` array; per-group colour via `colors` or theme roles — and an
242
+ **`orientation`** prop (`'vertical'` default | `'horizontal'`, bars grow right
243
+ with the bins on a y band axis). New readers `stacksFromGroups` /
244
+ `stacksFromColumns` / `stacksFromBins` plus `StackedBarSeries` / `BinRecord` /
245
+ `Orientation` types. All data generation composes from existing operators
246
+ (`aggregate` / `byColumn` / `partitionBy`); no core changes. Guide: How-to
247
+ guides → Histograms. (#401)
85
248
  - **`@pond-ts/charts`:** selection now has a stable series identity. `SelectInfo`
86
249
  carries an `id`, and `BarChart` / `ScatterChart` take an optional `id` prop —
87
250
  the series identity used for selection + hover. An `id` **gates interactivity**:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/react",
3
- "version": "0.42.0",
3
+ "version": "0.44.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.42.0",
36
+ "pond-ts": "^0.44.0",
37
37
  "react": "^18.0.0 || ^19.0.0"
38
38
  },
39
39
  "devDependencies": {