@pond-ts/charts 0.71.0 → 0.73.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/API.md +5 -4
- package/CHANGELOG.md +334 -5
- package/README.md +11 -0
- package/dist/AreaChart.d.ts +35 -12
- package/dist/AreaChart.js +28 -13
- package/dist/BandChart.d.ts +9 -1
- package/dist/BandChart.js +5 -1
- package/dist/ChartRow.js +2 -2
- package/dist/LineChart.d.ts +25 -1
- package/dist/LineChart.js +5 -1
- package/dist/XAxis.d.ts +70 -3
- package/dist/XAxis.js +187 -35
- package/dist/area.d.ts +1 -13
- package/dist/area.js +65 -69
- package/dist/bars.d.ts +7 -6
- package/dist/bars.js +7 -6
- package/dist/context.d.ts +2 -7
- package/dist/line.d.ts +14 -0
- package/dist/line.js +47 -7
- package/dist/tradingTimeScale.d.ts +7 -1
- package/dist/tradingTimeScale.js +3 -2
- package/dist/x-offset.d.ts +20 -0
- package/dist/x-offset.js +23 -0
- package/package.json +3 -3
package/API.md
CHANGED
|
@@ -118,7 +118,8 @@ Value-axis wire types
|
|
|
118
118
|
- **Temporal range**: `timeRange()`, `overlaps()`, `contains()`,
|
|
119
119
|
`intersection()`, `overlapping(range)`, `containedBy(range)`, `trim(range)`,
|
|
120
120
|
`after()`, `before()`, `within()`, `tail(duration)`
|
|
121
|
-
- **Key-type conversion**: `asTime({ at })`, `asTimeRange()`, `asInterval()
|
|
121
|
+
- **Key-type conversion**: `asTime({ at })`, `asTimeRange()`, `asInterval()`;
|
|
122
|
+
`offsetTime(by)` moves every key by a constant (kind kept)
|
|
122
123
|
- **Filter/slice**: `filter()`, `sample(strategy)`, `slice(begin, end)`
|
|
123
124
|
- **Column reshape**: `select()`, `rename()`, `map()`, `mapColumns()`,
|
|
124
125
|
`withColumn()`, `collapse()`
|
|
@@ -161,8 +162,8 @@ Deliberately small — the ordering-based slice of the algebra, no calendar ops
|
|
|
161
162
|
|
|
162
163
|
| Type group | Names | Source |
|
|
163
164
|
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
164
|
-
| Schema contract | `SeriesSchema`, `RowForSchema`, `EventForSchema`, `EventDataForSchema`, `EventKeyForSchema`, `TimeSeriesInput`, `TimeSeriesJsonInput`
|
|
165
|
-
| Aggregation specs | `AggregateReducer`, `AggregateMap`, `
|
|
165
|
+
| Schema contract | `SeriesSchema`, `RowForSchema`, `EventForSchema`, `EventDataForSchema`, `EventKeyForSchema`, `TimeSeriesInput`, `TimeSeriesJsonInput`, `BatchOrderingMode` | `packages/core/src/schema/index.ts` |
|
|
166
|
+
| Aggregation specs | `AggregateReducer`, `AggregateMap`, `AggregateSchema`, `BinReducerName`, `BinOutput` | `packages/core/src/schema/index.ts`, `packages/core/src/column.ts` |
|
|
166
167
|
| Operation schemas | `RollingSchema`, `RollingAlignment`, `AlignSchema`, `DiffSchema`, `SmoothSchema`, `SmoothMethod`, `FillStrategy`, `FillMapping` | `packages/core/src/schema/index.ts` |
|
|
167
168
|
| Column/data kinds | `Column`, `KeyColumn`, `ColumnKind`, `ScalarKind`, `ScalarValue`, `ColumnValue`, `ArrayValue`, `ValidityBitmap` | `packages/core/src/columnar/` |
|
|
168
169
|
| JSON wire format | `JsonRowFormat`, `JsonRowForSchema`, `JsonObjectRowForSchema`, `JsonValueForKind`, `JsonTimestampInput`, `JsonTimeRangeInput`, `JsonIntervalInput` | `packages/core/src/schema/index.ts` |
|
|
@@ -723,7 +724,7 @@ the default for pipelines known at authoring time — see the package README.
|
|
|
723
724
|
| Port declaration | `port<T>({ equals, defaultValue })`; types `PortSpec`, `PortSpecMap`, `PortValue`, `PortValues` | `packages/process/src/types.ts` |
|
|
724
725
|
| Sources | `source<T>()` → `SourceNode` (`set()`), `fromLive(liveSource)` → `LiveSourceNode` (`dispose()`); `GraphSource` (bind contract — looser than core's `LiveSource`, accepts `LiveAggregation`), `SnapshotSource`, `NoInputs` | `packages/process/src/source.ts` |
|
|
725
726
|
| Graph view | `Graph` (`Graph.from(...roots)`, `nodes`, `order()`, `edges()`, `toJSON()`); types `GraphEdge`, `GraphJson`, `GraphNodeJson`, `GraphEdgeJson` | `packages/process/src/graph.ts` |
|
|
726
|
-
| Range output buffers | `prepareRange(length, keep, prior)` → `RangeOutput` (`values`, `
|
|
727
|
+
| Range output buffers | `prepareRange(length, keep, prior)` → `RangeOutput` (`values`, `set`, `clear`; a gap is `NaN` in `values`) carrying `[0, keep)` forward as one block copy; `sealRange(out, length)` → `Float64Column`. Reached from an op as `ctx.out[n]` | `packages/process/src/column.ts` |
|
|
727
728
|
| Ranged recompute | `graph.setSourceFrom(series, changedFrom)` — declares which row first changed; `graph.recomputes` → `{ ranged, full }`. An op opts in with `OpDef.runRange(ctx)`, receiving `{ from, to, previous, previousView, out }` (type `RangeContext`) — write into `out` and return nothing for the block path alongside the usual context. Requires `lookback`. Falls back to a full `run` whenever anything is missing | `packages/process/src/plan/graph.ts` |
|
|
728
729
|
| Node budget | `bind(series, { registry, budgetBytes })` — engine-wide cap on retained node values, LRU, enforced after each `run`; `graph.retainedBytes` / `graph.evictions` / `graph.enforceBudget()`. Unbounded when omitted. Skips a node whose consumer still holds its outlet | `packages/process/src/plan/graph.ts` |
|
|
729
730
|
| Plan history | `requiredHistory(registry, plan)` → `{ known, rows?, undeclared, byOp }` — the minimum safe tail in rows, folded from per-op `OpDef.lookback`. Sums along nesting, maxes across siblings. `known: false` names ops with no declared lookback rather than defaulting to zero (type `HistoryResult`) | `packages/process/src/plan/history.ts` |
|
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,9 @@ The `@pond-ts` packages — `pond-ts`, `@pond-ts/react`, `@pond-ts/charts`,
|
|
|
8
8
|
under a single `v*` tag, so this file covers them all. Pre-1.0: minor bumps may
|
|
9
9
|
include new features 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.73.0...HEAD
|
|
12
|
+
[0.73.0]: https://github.com/pond-ts/pond/compare/v0.72.0...v0.73.0
|
|
13
|
+
[0.72.0]: https://github.com/pond-ts/pond/compare/v0.71.0...v0.72.0
|
|
12
14
|
[0.71.0]: https://github.com/pond-ts/pond/compare/v0.70.0...v0.71.0
|
|
13
15
|
[0.70.0]: https://github.com/pond-ts/pond/compare/v0.69.0...v0.70.0
|
|
14
16
|
[0.69.0]: https://github.com/pond-ts/pond/compare/v0.68.0...v0.69.0
|
|
@@ -74,6 +76,334 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
74
76
|
|
|
75
77
|
## [Unreleased]
|
|
76
78
|
|
|
79
|
+
## [0.73.0] — 2026-10-12
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- **core**: **`ordering: 'strict' | 'reorder' | 'clamp'` on every loader**
|
|
84
|
+
(`new TimeSeries`, `TimeSeries.fromJSON` / `fromColumns` / `fromArrow`,
|
|
85
|
+
`ValueSeries.fromJSON` / `fromColumns` / `fromArrow`), with the new
|
|
86
|
+
`BatchOrderingMode` type exported. Same option name and vocabulary as
|
|
87
|
+
`LiveSeries`' `ordering` (`'strict'` / `'reorder'`), so one concept has one
|
|
88
|
+
name across batch and live. `'strict'` is the default (unchanged);
|
|
89
|
+
`'reorder'` is what `sort: true` did. **`'clamp'`** is new: rows stay in place and a key
|
|
90
|
+
earlier than its predecessor's takes that key (the running max), so a noisy
|
|
91
|
+
feed's lone backwards timestamp lands on its neighbour instead of failing the
|
|
92
|
+
ingest or being moved. Values are untouched, ties pass through, non-finite
|
|
93
|
+
keys are still rejected, value-column `Float64Array`s stay zero-copy, and a
|
|
94
|
+
caller's key buffer is never mutated. `time` keys and `ValueSeries` axes
|
|
95
|
+
only — a `timeRange` / `interval` key throws under `'clamp'`. [PND-INGEST]
|
|
96
|
+
|
|
97
|
+
- `pond-ts`: **`series.offsetTime(by)` moves every key by a constant** —
|
|
98
|
+
milliseconds or a duration string, negative for earlier
|
|
99
|
+
(`yesterday.offsetTime('1d')` overlays yesterday on today; `'1d'` is a fixed
|
|
100
|
+
24 hours, not a calendar day). The key kind is kept; a `timeRange` /
|
|
101
|
+
`interval` key moves both edges and keeps its labels.
|
|
102
|
+
Value columns are shared, not copied, so the cost is one pass over the key:
|
|
103
|
+
0.26 ms on 97k rows × 118 columns, where rebuilding the series through its
|
|
104
|
+
rows took ~990 ms (`packages/core/scripts/perf-offset-time.mjs`). `0` returns
|
|
105
|
+
the series itself; a non-finite or unparseable offset throws.
|
|
106
|
+
|
|
107
|
+
- **charts**: **`xOffset` on `<LineChart>`, `<AreaChart>` and `<BandChart>`
|
|
108
|
+
draws each row a fixed time after its key**, without touching the series. A
|
|
109
|
+
1-minute bar is keyed at its open but its `close` is the price at its end, so
|
|
110
|
+
`<LineChart column="close" xOffset="1m">` puts the line where the closes
|
|
111
|
+
happened — the session's last close on the close, not a bar early — while a
|
|
112
|
+
`<Candlestick>` from the same series still draws at the key. Previously this
|
|
113
|
+
meant rebuilding the whole series with shifted keys. Everything the layer
|
|
114
|
+
reports is in drawn time: the tracker's nearest row and dot, its auto-fit
|
|
115
|
+
x-extent, `sessionBreaks`, a sweep's span. Time series only; a non-zero
|
|
116
|
+
offset on a `ValueSeries` throws.
|
|
117
|
+
|
|
118
|
+
- **charts**: **`<AreaChart sessionBreaks>`** ends the fill and its outline at
|
|
119
|
+
each trading-axis session close and re-starts them at the next open, as
|
|
120
|
+
`<LineChart>` and `<BandChart>` already did — so the line and area styles of
|
|
121
|
+
one intraday series break alike. `gaps="none"` still interpolates a dropout
|
|
122
|
+
inside a session but never across a break, and no inferred gap connector
|
|
123
|
+
spans one.
|
|
124
|
+
|
|
125
|
+
- **charts:** `TradingTimeScale.flatFormat(count, drawn?)` — `drawn` is the
|
|
126
|
+
subset of `ticks(count)` actually rendered; flat promotions walk only those,
|
|
127
|
+
so a dropped tick's period turn carries to the next drawn one. Omitted, it
|
|
128
|
+
labels every tick as before.
|
|
129
|
+
|
|
130
|
+
- **docs**: **The versioning policy is now published** — a "Versioning"
|
|
131
|
+
section in the root README and each package README, and a
|
|
132
|
+
[Versioning](https://pond-ts.org/docs/reference/versioning) page on the
|
|
133
|
+
docs site. Pre-1.0 a minor may break (always marked **breaking** here with a
|
|
134
|
+
migration note), patches never break, all packages release in lock-step,
|
|
135
|
+
and from 1.0 the project follows semver. The page also defines the public
|
|
136
|
+
API: each package's entry-point exports as mapped in `API.md`, excluding
|
|
137
|
+
anything marked experimental or internal.
|
|
138
|
+
|
|
139
|
+
### Changed
|
|
140
|
+
|
|
141
|
+
- **core** (**breaking**): **A `NaN` in a number column now always means
|
|
142
|
+
missing.** Typed-array intake (`fromColumns`, `withColumn` with a
|
|
143
|
+
`Float64Array`) already turned `NaN` into a gap and row intake already
|
|
144
|
+
rejected it, but an operator's own arithmetic could still write a
|
|
145
|
+
_defined_ `NaN` — `diff` / `rate` / `pctChange` of `Infinity - Infinity`,
|
|
146
|
+
a `cumulative` sum that reaches
|
|
147
|
+
`Infinity` and then meets `-Infinity`, or a `cumulative` custom fold,
|
|
148
|
+
`scan` step or numeric `collapse` reducer that returns `NaN`. Those
|
|
149
|
+
cells are now stored as **missing**: `at(i)` / `get` / `read` return
|
|
150
|
+
`undefined`, `scan()` skips them, and `nullCount()` / `hasMissing()` count
|
|
151
|
+
them. Same in live `diff` / `rate` / `pctChange` / `cumulative` views, in a
|
|
152
|
+
live `map` whose function writes `NaN` into a number field, and in
|
|
153
|
+
`@pond-ts/process` `RangeOutput.set` / `sealRange`. A `cumulative` that hits
|
|
154
|
+
`NaN` keeps its accumulator, so built-in folds read missing from that row on.
|
|
155
|
+
`±Infinity` now follows the same rule — see the next entry. The storage
|
|
156
|
+
switch this unlocked landed in the same release — see "number columns store
|
|
157
|
+
a missing cell as `NaN`" below. **Migration:** code that tested
|
|
158
|
+
`Number.isNaN(v)` on an operator's output should test `v === undefined`.
|
|
159
|
+
|
|
160
|
+
- **core** (**breaking**): **Any non-finite number — `NaN`, `Infinity` or
|
|
161
|
+
`-Infinity` — in a number column means missing.** `Infinity` used to get five
|
|
162
|
+
different answers; it now gets one. A non-finite result is stored as a
|
|
163
|
+
**missing** cell (`undefined` on read, counted by `nullCount()` /
|
|
164
|
+
`hasMissing()`, skipped by `scan()`), so `count() + nullCount() === length`
|
|
165
|
+
always holds. What changed:
|
|
166
|
+
- **No longer throws:** `mapColumns` (a mapper returning `NaN` /
|
|
167
|
+
`±Infinity` — was a `RangeError`), and `rolling` / `aggregate` / `smooth`
|
|
168
|
+
/ `baseline` / `byColumn` / `pivotByGroup` / `arrayAggregate` whose result
|
|
169
|
+
overflows (was a `ValidationError`). Those cells are now missing.
|
|
170
|
+
- **No longer a defined value:** `diff` / `rate` / `pctChange` /
|
|
171
|
+
`cumulative` / `scan` / `collapse` results that overflow to `±Infinity`
|
|
172
|
+
(e.g. `diff` of `MAX - (-MAX)`, a `cumulative` sum past `Number.MAX_VALUE`
|
|
173
|
+
— missing from that row on, as its accumulator stays `Infinity`).
|
|
174
|
+
- **Reducers never answer non-finite:** a reducer result that overflows
|
|
175
|
+
(`sum`, `avg`, `stdev`, or a custom reducer returning `Infinity`) is
|
|
176
|
+
`undefined` — in `reduce()` too, which used to return `Infinity`. A
|
|
177
|
+
rolling `sum` / `avg` / `stdev` (batch, live, and `smooth('movingAverage')`)
|
|
178
|
+
reads missing only while the current window overflows, and reads finite
|
|
179
|
+
again once the large values leave it. A mean is still sum-then-divide, so
|
|
180
|
+
`avg([MAX_VALUE, MAX_VALUE])` reads missing although the mean is finite.
|
|
181
|
+
(The column methods `col.sum()` / `col.mean()` are unchanged.)
|
|
182
|
+
- **Doors:** `withColumn` with a `Float64Array` reads `±Infinity` as a gap,
|
|
183
|
+
as it already read `NaN` (was a `ValidationError`). Row intake
|
|
184
|
+
(`new TimeSeries`, `fromJSON`, `LiveSeries.push`) and `withColumn` with a
|
|
185
|
+
plain array still reject `NaN` and `±Infinity` alike; `fromColumns` /
|
|
186
|
+
`fromArrow` already read both as a gap.
|
|
187
|
+
- **Live:** `diff` / `rate` / `pctChange` / `cumulative` / `map` views and
|
|
188
|
+
every live accumulator (`aggregate`, `rolling`, `reduce`) emit `undefined`
|
|
189
|
+
for a non-finite result, so a library view's `toTimeSeries()` no longer
|
|
190
|
+
throws on an overflow. A user `map` keeps the source schema, so a `map` fn
|
|
191
|
+
that writes a non-finite value reads missing too — and `toTimeSeries()`
|
|
192
|
+
then works if that column is optional, but throws the required-column
|
|
193
|
+
`ValidationError` if it is required. `@pond-ts/process` `RangeOutput.set` / `sealRange` / `packColumn`
|
|
194
|
+
store `±Infinity` as missing too.
|
|
195
|
+
|
|
196
|
+
**Migration:** code that caught a `ValidationError` / `RangeError` from these
|
|
197
|
+
operators to detect overflow, or tested `v === Infinity` on their output,
|
|
198
|
+
should test `v === undefined`. Code relying on
|
|
199
|
+
`withColumn(name, float64Array)` to reject `Infinity` must check it before
|
|
200
|
+
the call.
|
|
201
|
+
|
|
202
|
+
- **core** (**breaking**, mostly internal): **Number columns store a missing
|
|
203
|
+
cell as `NaN` in their own `Float64Array`, and keep no separate validity
|
|
204
|
+
bitmap** ([PND-NANREP]). Now that any non-finite number in a number column
|
|
205
|
+
means missing (entries above), the `NaN` is the only marker the column needs.
|
|
206
|
+
Answers do not change, with one exception: a gappy `sum` / `avg` over a
|
|
207
|
+
sub-range (an `aggregate` bucket of 32+ cells) can differ from before in
|
|
208
|
+
the last digit, because the blocked sum now groups its eight lanes from the
|
|
209
|
+
range start rather than from a multiple of 8 — the same kind of rounding
|
|
210
|
+
difference blocked summation already allows. Speed improves — on 1M rows with 4% gaps, `fromColumns`
|
|
211
|
+
is 70% faster, `reduce` `sum` / `avg` / `min` 30–38%, `diff` / `cumulative` /
|
|
212
|
+
`shift` 14–18%, and `nullCount()` on a fresh column 35%. Dense columns are
|
|
213
|
+
mostly unchanged or faster (`fromColumns` 32%); dense `cumulative` measured
|
|
214
|
+
~11% slower. String, boolean and array columns keep their bitmaps. What you
|
|
215
|
+
can see:
|
|
216
|
+
- **`toFloat64Array()` returns `NaN` at a missing cell** (it used to be
|
|
217
|
+
whatever the buffer held, often `0`). A draw loop now breaks the line at a
|
|
218
|
+
gap with no extra check, and `withColumn(name, col.toFloat64Array())` now
|
|
219
|
+
round-trips gaps.
|
|
220
|
+
- **`fromArrow` copies a `Float64` column that has nulls** (one bulk copy,
|
|
221
|
+
writing `NaN` at each null) instead of sharing Arrow's buffers. Gap-free
|
|
222
|
+
columns are still shared with no copy. `toArrow` builds a number column's
|
|
223
|
+
`nullBitmap` from its `NaN` cells (once, then cached).
|
|
224
|
+
- **`Float64Column` constructor** (trusted, internal) is now
|
|
225
|
+
`(values, length, allFinite = false)` — the `validity` argument is gone,
|
|
226
|
+
and the old four-argument call shape throws a `TypeError`. `allFinite` now means "no
|
|
227
|
+
missing cells". `col.validity` still exists for kind-generic code but is
|
|
228
|
+
built from the `NaN` cells on first read. A column built this way with a
|
|
229
|
+
`NaN` partition value or interval label now reads it as missing (it used
|
|
230
|
+
to be a `NaN` partition / a "not a finite number" error).
|
|
231
|
+
- **`@pond-ts/process`:** `RangeOutput`, `ColumnView` and `ColumnBuffers`
|
|
232
|
+
lose their `bits` field — a gap is `NaN` in `values`. `ColumnBuffers`
|
|
233
|
+
gains `allFinite`. `validityByteCount` is removed (nothing to size). `columnBytes` no longer counts a bitmap for number
|
|
234
|
+
columns.
|
|
235
|
+
|
|
236
|
+
**Migration:** code that read `validity.bits` of a number column, or passed
|
|
237
|
+
`bits` across a worker boundary, should test `Number.isNaN(values[i])`
|
|
238
|
+
instead. Code that relied on `fromArrow` sharing a null-bearing column's
|
|
239
|
+
buffer with the Arrow table should not.
|
|
240
|
+
|
|
241
|
+
- **core**: Out-of-order ingest errors now suggest `{ ordering: 'reorder' }`
|
|
242
|
+
(and `'clamp'` where it applies) instead of `{ sort: true }`. Code matching
|
|
243
|
+
on the old message text needs updating.
|
|
244
|
+
|
|
245
|
+
- **core** (**breaking**): **`select(...keys)` throws on a key that isn't a
|
|
246
|
+
column, and on an array argument.** It used to drop unknown keys silently, so
|
|
247
|
+
a typo — or `select(['a', 'b'])` instead of `select('a', 'b')` — returned an
|
|
248
|
+
empty-payload series with no error. Now an unknown key throws a `RangeError`
|
|
249
|
+
naming it and listing the valid columns, and an array argument throws a
|
|
250
|
+
`TypeError` telling you to spread it (`select(...names)`). Applies to
|
|
251
|
+
`TimeSeries`, `LiveSeries`, `LiveView`, `LiveAggregation` and
|
|
252
|
+
`LiveRollingAggregation` (the live ones throw when the view is built, not on
|
|
253
|
+
the first event). `Event.select` rejects an array argument but still
|
|
254
|
+
tolerates absent keys, since an event payload may omit optional fields.
|
|
255
|
+
Duplicate keys are still accepted and select the column once. **Migration:**
|
|
256
|
+
fix the misspelled column, or spread the array.
|
|
257
|
+
|
|
258
|
+
- **core / react**: **The live column reads are no longer experimental.**
|
|
259
|
+
`LiveView.column()`, `LiveView.keyColumn()`, `LiveView.partitionBy().toMap()`,
|
|
260
|
+
the `LiveColumnGroup` class and `@pond-ts/react`'s `useLiveVersion` shipped in
|
|
261
|
+
v0.19.0 labelled "experimental — may change in 0.19.x". They are now part of
|
|
262
|
+
the stable surface that 1.0 will commit to, with no change to their shape.
|
|
263
|
+
|
|
264
|
+
- **charts** (**breaking**): **An `<AreaChart>`'s `baseline` no longer changes
|
|
265
|
+
the y-axis range.** Since v0.71.0 the default zero baseline (and any numeric baseline)
|
|
266
|
+
was pulled into the auto-fit domain, so an area over data running $100–$200
|
|
267
|
+
drew on a $0–$200 axis, and switching `baseline` rescaled the chart — and
|
|
268
|
+
every other layer sharing its axis. The axis now fits the data, exactly as it
|
|
269
|
+
does for a `<LineChart>` of the same column; `baseline` only moves where the
|
|
270
|
+
fill stops. A baseline outside the axis range rests the fill on the nearest
|
|
271
|
+
edge, so on data that never crosses zero the default now draws the same as
|
|
272
|
+
`baseline="floor"`. **Migration:** where the fill should read from zero — a
|
|
273
|
+
stack, a quantity like throughput — put zero on the axis with
|
|
274
|
+
`<YAxis min={0}>` (`max={0}` for an all-negative series). Areas whose data
|
|
275
|
+
already reaches their baseline, or whose axis is pinned, are unchanged.
|
|
276
|
+
|
|
277
|
+
- `pond-ts` (**breaking**, types only): **`TimeSeries.toJSON()`'s return
|
|
278
|
+
type narrows on `rowFormat`**, matching `LiveSeries.toJSON()`. The default
|
|
279
|
+
/ `'array'` form returns `TimeSeriesJsonOutputArray<S>` and
|
|
280
|
+
`{ rowFormat: 'object' }` returns `TimeSeriesJsonOutputObject<S>`, so
|
|
281
|
+
`result.rows` is typed against your schema with no cast; a `rowFormat`
|
|
282
|
+
typed as `JsonRowFormat` gets the union. It used to return the loose
|
|
283
|
+
`TimeSeriesJsonInput<SeriesSchema>`. **Migration:** drop any
|
|
284
|
+
`as TimeSeriesJsonInput<…>` / `as TimeSeriesJsonOutput…<…>` casts (they
|
|
285
|
+
still compile). Two patterns now fail to compile: annotating an
|
|
286
|
+
**object**-form result as `TimeSeriesJsonInput<SeriesSchema>` — including
|
|
287
|
+
a `JsonRowFormat`-typed (union) result, since it can be the object form —
|
|
288
|
+
(use `TimeSeriesJsonInput<typeof schema>`, or no annotation), and reading a
|
|
289
|
+
column by name off a default (tuple) row, which was always `undefined` at
|
|
290
|
+
runtime. `LiveSeries.toJSON()` also gains the `JsonRowFormat` overload, which
|
|
291
|
+
previously had no matching signature. [PND-TSJSONT]
|
|
292
|
+
|
|
293
|
+
- `pond-ts`: **`TimeSeries.toJSON()` tuple rows are ~2.5–3.5× faster at 100k
|
|
294
|
+
rows** — one array per row, filled in place, instead of a mapped array spread
|
|
295
|
+
into a second one. Tuple rows were slower than object rows; they now match or
|
|
296
|
+
beat them. Output unchanged.
|
|
297
|
+
|
|
298
|
+
- **core** (**breaking**): **Every `on()` overload on `LiveAggregation` and
|
|
299
|
+
`LiveRollingAggregation` now returns an unsubscribe function**, matching
|
|
300
|
+
`LiveSeries.on` / `LiveView.on`. `on('bucket')` and `on('update')` used to
|
|
301
|
+
return `this`, so those listeners could never be removed. **Migration:** a
|
|
302
|
+
chained `live.aggregate(…).on('bucket', fn)` now yields the unsubscribe
|
|
303
|
+
function, not the operator (same for `live.rolling(…).on('update', fn)`) —
|
|
304
|
+
assign the operator first, then subscribe.
|
|
305
|
+
|
|
306
|
+
- **core** (**breaking, type-level**): **`LiveAggregation` listeners
|
|
307
|
+
(`'event'`, `'bucket'`) and `LiveRollingAggregation`'s `'event'` listener
|
|
308
|
+
receive the schema-narrowed `EventForSchema<Out>`** — the same type `at()`
|
|
309
|
+
and the `closed()` series hand out — instead of a widened
|
|
310
|
+
`Event<Interval, Record<string, …>>` / `any`. `e.get('col')` is now typed
|
|
311
|
+
per output column, and naming a column the aggregation doesn't produce is a
|
|
312
|
+
compile error. As a result a `LiveAggregation<S, Out>` is now assignable to
|
|
313
|
+
`LiveSource<Out>` ([PND-LIVESRC]). The same narrowing applies to the
|
|
314
|
+
`'event'` listener of the keyed-form fused `live.rolling({...})`
|
|
315
|
+
(`LiveFusedRolling`), `live.reduce(…)` (`LiveReduce`), and the
|
|
316
|
+
clock-triggered partitioned `rolling(…)` variants
|
|
317
|
+
(`LivePartitionedSyncRolling` / `LivePartitionedFusedRolling`), which
|
|
318
|
+
previously typed it `(event: any)`. Both `LiveAggregation` /
|
|
319
|
+
`LiveRollingAggregation` `on()` methods now throw a
|
|
320
|
+
`TypeError` on an unknown event type instead of silently registering the
|
|
321
|
+
listener.
|
|
322
|
+
|
|
323
|
+
### Deprecated
|
|
324
|
+
|
|
325
|
+
- **core**: **`sort?: boolean`** on the loaders above — use
|
|
326
|
+
`ordering: 'reorder'`. `sort: true` still works for this release and maps to
|
|
327
|
+
`'reorder'`; it will be removed in the next minor. Passing `sort` together with
|
|
328
|
+
a conflicting `ordering` (e.g. `sort: true, ordering: 'clamp'`)
|
|
329
|
+
throws a `ValidationError`.
|
|
330
|
+
|
|
331
|
+
### Removed
|
|
332
|
+
|
|
333
|
+
- **core** (**breaking**): **`LiveAggregation.on('close', fn)` is removed —
|
|
334
|
+
use `on('event', fn)`**, which it was an alias for. Untyped callers that
|
|
335
|
+
still pass `'close'` get a `TypeError` naming the replacement.
|
|
336
|
+
|
|
337
|
+
- `pond-ts` (**breaking**, types only): **the back-compat type alias
|
|
338
|
+
`AggregateOutputMap` is gone — use `AggregateMap`.** It has been an exact
|
|
339
|
+
alias of `AggregateMap` since v0.23.0, when the shorthand and
|
|
340
|
+
`{ from, using }` spec forms were unified into one map type. The internal
|
|
341
|
+
schema aliases `RollingOutputMapSchema` (identical to `RollingSchema`) and
|
|
342
|
+
`AggregateOutputMapResultSchema` (identical to `AggregateSchema`) are removed
|
|
343
|
+
with it; neither was reachable from a public entry point. **Migration:**
|
|
344
|
+
replace `AggregateOutputMap<S>` with `AggregateMap<S>` — no runtime change.
|
|
345
|
+
The fused-rolling duplicate-output error message now says "use a
|
|
346
|
+
`{ from, using }` spec entry" instead of naming the removed type.
|
|
347
|
+
|
|
348
|
+
### Fixed
|
|
349
|
+
|
|
350
|
+
- **charts: time-axis tick labels no longer overprint each other.** The tick
|
|
351
|
+
ladder places every session open unconditionally, so two anchors could land
|
|
352
|
+
closer than a label is wide — a session calendar derived from the bars turns
|
|
353
|
+
one stray pre-market print into a one-minute session whose `Sep 29` open sat
|
|
354
|
+
a pixel left of the real `15:30` open, and a seam could crowd the last clock
|
|
355
|
+
tick before it (`21:00` against the next day's `Sep 23`). A time axis now
|
|
356
|
+
measures its labels and drops the one that would collide — a date / month /
|
|
357
|
+
year turn outlasts a session-open clock label, which outlasts a plain one,
|
|
358
|
+
which outlasts a tick in closed-market time (an elapsed axis no longer labels
|
|
359
|
+
a seam with the shut night); of two colliding turns (or opens) the later is
|
|
360
|
+
drawn — then re-labels the
|
|
361
|
+
ticks it kept, so the dates they show stay correct (a stray New Year's print
|
|
362
|
+
can't take `2027` with it; a Sunday-evening futures open can't caption
|
|
363
|
+
Monday), and puts back any dropped tick that fits once a neighbour's label
|
|
364
|
+
narrowed. A blank label (a custom `format` returning `''`) never blocks one.
|
|
365
|
+
Under `align="auto"` the edge labels anchor by the full tick list, so a drop
|
|
366
|
+
never re-anchors another label. Applies to every `align` mode and to custom
|
|
367
|
+
`format`s; explicit `ticks` and `transform` ticks are untouched.
|
|
368
|
+
|
|
369
|
+
- **charts**: a **decimated** `<LineChart sessionBreaks>` with
|
|
370
|
+
`gaps="none"` interpolated straight across each session break, and
|
|
371
|
+
`dashed` / `step` / `fade` drew a gap connector over it — dense intraday data
|
|
372
|
+
decimates, so this was the common case. Decimation marks the break as a gap
|
|
373
|
+
at the break instant, and the draw now cuts there instead of reading it as a
|
|
374
|
+
dropout.
|
|
375
|
+
|
|
376
|
+
## [0.72.0] — 2026-10-08
|
|
377
|
+
|
|
378
|
+
### Changed
|
|
379
|
+
|
|
380
|
+
- `pond-ts`: **`join` and `joinMany` work on columns now, not events** — 20
|
|
381
|
+
to 190× faster in the cases below, with the same output. `join` used to
|
|
382
|
+
build an event for every row on both sides, merge them row by row and
|
|
383
|
+
rebuild the columns. Now it walks the two key columns once, then either
|
|
384
|
+
adopts each column as is or gathers it in one pass. A side's value columns
|
|
385
|
+
are adopted as is whenever all its rows land in the output once and in order:
|
|
386
|
+
always the left side of a `left` join, the right side of a `right` join, and
|
|
387
|
+
both sides when the keys match one for one (`joinMany` over a shared grid).
|
|
388
|
+
One year of 1-minute bars, ~97.5k rows a side, `type: 'left'` unless noted
|
|
389
|
+
(`packages/core/scripts/perf-join.mjs`):
|
|
390
|
+
|
|
391
|
+
| Value columns per side | Before | After |
|
|
392
|
+
| ------------------------ | -------- | -------- |
|
|
393
|
+
| 59 and 59 | 1,868 ms | 24 ms |
|
|
394
|
+
| 5 and 5 | 168 ms | 4.2 ms |
|
|
395
|
+
| 59 and 2 | 766 ms | 4.1 ms |
|
|
396
|
+
| `joinMany`, 4 × 5, outer | 457 ms | 15–20 ms |
|
|
397
|
+
|
|
398
|
+
A left join now costs about as much as the **other** side is wide. To bring
|
|
399
|
+
across only the columns you read, narrow that side first; `select` and
|
|
400
|
+
`rename` don't copy:
|
|
401
|
+
`bars.join(spy.select('close').rename({ close: 'spy' }), { type: 'left' })`.
|
|
402
|
+
|
|
403
|
+
One visible difference: on a row with no match, `event.data()` now lists the
|
|
404
|
+
other side's fields as `undefined`, as every other operator's events do. It
|
|
405
|
+
used to leave them out. `get()` returns `undefined` either way.
|
|
406
|
+
|
|
77
407
|
## [0.71.0] — 2026-09-24
|
|
78
408
|
|
|
79
409
|
### Added
|
|
@@ -213,8 +543,8 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
213
543
|
|
|
214
544
|
- `@pond-ts/financial`: **`StudyOutput.id` documents the `@pond-ts/process`
|
|
215
545
|
bridge**, and a new `test/catalog-process.test.ts` pins it. The field is a
|
|
216
|
-
|
|
217
|
-
|
|
546
|
+
_financial column suffix_; process's `OutputDef.id` is a _process outlet
|
|
547
|
+
id_; they share a name and are different namespaces. Process names its own
|
|
218
548
|
columns (`specId + OutputDef.id`) and matches an op's return to its outputs
|
|
219
549
|
positionally, so a study's own column names never reach it — which means a
|
|
220
550
|
registry bridging the two chooses its own suffixes, and for the **twelve**
|
|
@@ -227,7 +557,7 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
227
557
|
worked around it by hand. No API change: the descriptors, the studies and
|
|
228
558
|
the guard are all unchanged, and the round-trip test is what stops the two
|
|
229
559
|
packages drifting — `catalog.test.ts` validates a descriptor against its
|
|
230
|
-
|
|
560
|
+
_study_, so it is structurally blind to a cross-package disagreement.
|
|
231
561
|
- `@pond-ts/process`: **`Skipped.spec` echoes the request verbatim**, and its
|
|
232
562
|
`params` / `inputs` are typed `unknown` accordingly. The plan pass normalized
|
|
233
563
|
`params: null` to `{}`, so recomputing an id from the echo produced the
|
|
@@ -247,7 +577,6 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
247
577
|
|
|
248
578
|
(All three reported by Tidal against 0.62.0, after adopting it.)
|
|
249
579
|
|
|
250
|
-
|
|
251
580
|
## [0.69.0] — 2026-09-13
|
|
252
581
|
|
|
253
582
|
### Added
|
package/README.md
CHANGED
|
@@ -69,6 +69,17 @@ Guides, the component reference, and live examples live at
|
|
|
69
69
|
**<https://pond-ts.org/docs/charts/>**. Source and issues:
|
|
70
70
|
[github.com/pond-ts/pond](https://github.com/pond-ts/pond).
|
|
71
71
|
|
|
72
|
+
## Versioning
|
|
73
|
+
|
|
74
|
+
Released in lock-step with `pond-ts` and the other `@pond-ts/*` packages
|
|
75
|
+
under one version — keep them in step. Pre-1.0, a minor release may contain
|
|
76
|
+
breaking changes (each marked **breaking** in the
|
|
77
|
+
[CHANGELOG](https://github.com/pond-ts/pond/blob/main/CHANGELOG.md) with a
|
|
78
|
+
migration note); patch releases never break. From 1.0 the project follows
|
|
79
|
+
semver. See
|
|
80
|
+
[Versioning](https://pond-ts.org/docs/reference/versioning) for what
|
|
81
|
+
counts as the public API.
|
|
82
|
+
|
|
72
83
|
## License
|
|
73
84
|
|
|
74
85
|
MIT
|
package/dist/AreaChart.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
|
4
4
|
import type { DecimateOption } from './decimate.js';
|
|
5
5
|
import { type Curve } from './curve.js';
|
|
6
6
|
import { type GapMode } from './gaps.js';
|
|
7
|
+
import { type XOffset } from './x-offset.js';
|
|
7
8
|
export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
8
9
|
/**
|
|
9
10
|
* The series' semantic identifier — what the data _is_ / how it should read
|
|
@@ -33,23 +34,23 @@ export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
|
|
|
33
34
|
/**
|
|
34
35
|
* The value the fill rests on — the flat edge opposite the value line.
|
|
35
36
|
*
|
|
36
|
-
* - **Omitted ⇒ `0`**:
|
|
37
|
-
*
|
|
38
|
-
* the zero line. Zero is pulled into the auto-fit domain, so the axis always
|
|
39
|
-
* shows where the fill starts.
|
|
37
|
+
* - **Omitted ⇒ `0`**: values above zero fill up and values below it fill
|
|
38
|
+
* down, each side's shade fading toward the zero line.
|
|
40
39
|
* - **Another number ⇒ that reference level**, with the same behaviour (e.g.
|
|
41
40
|
* `baseline={100}` for an index that fills above and below 100). For the
|
|
42
41
|
* esnet two-colour traffic look, compose two `<AreaChart>`s (an "in" column
|
|
43
42
|
* and an "out" column, distinct `as` roles).
|
|
44
43
|
* - **`'floor'` ⇒ the axis's lower bound** (the bottom of the plot): the fill
|
|
45
|
-
* rests on whatever the axis starts at,
|
|
46
|
-
* For a series that never nears zero where zero would flatten the shape — a
|
|
47
|
-
* price, an elevation profile.
|
|
44
|
+
* rests on whatever the axis starts at, even when zero is on screen.
|
|
48
45
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* edge (
|
|
46
|
+
* **The baseline never changes the axis range.** The axis fits the data, as
|
|
47
|
+
* it does for a `<LineChart>`, so switching `baseline` only moves where the
|
|
48
|
+
* fill stops, never the scale. A baseline outside the axis range rests the
|
|
49
|
+
* fill on the nearest edge (it is clamped into the domain, as a bar's is), so
|
|
50
|
+
* on data that never crosses zero the default looks the same as `'floor'`.
|
|
51
|
+
* To show the fill from zero, put zero on the axis: `<YAxis min={0}>`. On a
|
|
52
|
+
* **log** axis zero has no position, so a baseline at or below zero falls
|
|
53
|
+
* back to the axis floor.
|
|
53
54
|
*/
|
|
54
55
|
baseline?: number | 'floor';
|
|
55
56
|
/**
|
|
@@ -71,6 +72,21 @@ export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
|
|
|
71
72
|
* `'dashed'` / `'step'` connector faintness is the theme's `gap.connectorOpacity`.)
|
|
72
73
|
*/
|
|
73
74
|
gaps?: GapMode;
|
|
75
|
+
/**
|
|
76
|
+
* End the fill (and its outline) at each **trading-axis discontinuity** — a
|
|
77
|
+
* session / day / lunch close→open — when the container renders on a
|
|
78
|
+
* trading-time axis (a `discontinuities` / `calendar` provider). **Omitted ⇒
|
|
79
|
+
* `false`**: the area runs from the last pre-close point straight to the next
|
|
80
|
+
* open, filling the collapsed gap. `true` ends the shade at the close and
|
|
81
|
+
* re-starts it at the open, the same break `<LineChart sessionBreaks>` draws,
|
|
82
|
+
* so the line and area styles of one intraday series read the same.
|
|
83
|
+
*
|
|
84
|
+
* A **scale** break, orthogonal to {@link gaps} (a **data** break, a NaN run):
|
|
85
|
+
* `gaps="none"` interpolates across a dropout but never across a session
|
|
86
|
+
* break, and no inferred gap connector spans one. A no-op on a continuous
|
|
87
|
+
* axis (no provider) or a provider without `boundaries`.
|
|
88
|
+
*/
|
|
89
|
+
sessionBreaks?: boolean;
|
|
74
90
|
/**
|
|
75
91
|
* **M4 viewport decimation** (charts decimator wave). **Omitted ⇒ `true`**:
|
|
76
92
|
* once the visible data is denser than ~2 samples per device pixel, the fill +
|
|
@@ -140,6 +156,13 @@ export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
|
|
|
140
156
|
* identity** (`as` ?? `column`). The swatch is the resolved area style.
|
|
141
157
|
*/
|
|
142
158
|
legend?: boolean | string;
|
|
159
|
+
/**
|
|
160
|
+
* Draw each row this long **after its key** — milliseconds or a duration
|
|
161
|
+
* string. **Omitted ⇒ `0`**. Time series only. Same knob as
|
|
162
|
+
* {@link LineChartCommon.xOffset}, so an area and the line it pairs with
|
|
163
|
+
* move together.
|
|
164
|
+
*/
|
|
165
|
+
xOffset?: XOffset;
|
|
143
166
|
/**
|
|
144
167
|
* @internal Declaration position among the `<Layers>` children, injected by
|
|
145
168
|
* `Layers` so z-order follows JSX order. Do not set.
|
|
@@ -211,6 +234,6 @@ export declare function resolveAreaBaseline(baseline: number | undefined, yScale
|
|
|
211
234
|
* </Layers>
|
|
212
235
|
* ```
|
|
213
236
|
*/
|
|
214
|
-
export declare function AreaChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, readout, as: semantic, axis, id, baseline, curve, gaps, decimate, thresholds, bandColors, legend, index, }: AreaChartProps<S, VS>): null;
|
|
237
|
+
export declare function AreaChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series: source, column, readout, as: semantic, axis, id, baseline, curve, gaps, sessionBreaks, decimate, thresholds, bandColors, legend, xOffset, index, }: AreaChartProps<S, VS>): null;
|
|
215
238
|
export {};
|
|
216
239
|
//# sourceMappingURL=AreaChart.d.ts.map
|
package/dist/AreaChart.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { ValueSeries } from 'pond-ts';
|
|
3
3
|
import { assertNumericColumn, fromTimeSeries, fromValueSeries, } from './data.js';
|
|
4
|
-
import {
|
|
5
|
-
import { drawPartitioned, plotExtentOf, strokeSpanEdges, } from './line.js';
|
|
4
|
+
import { areaHitIndex, areaStateStyle, drawArea } from './area.js';
|
|
5
|
+
import { drawPartitioned, plotExtentOf, strokeSpanEdges, yExtent, } from './line.js';
|
|
6
6
|
import { sweepSpan } from './sweep.js';
|
|
7
7
|
import { resolveCurve } from './curve.js';
|
|
8
8
|
import { DEFAULT_GAP_MODE, DEFAULT_GAP_CONNECTOR_OPACITY, } from './gaps.js';
|
|
9
9
|
import { ContainerContext, LayersContext, } from './context.js';
|
|
10
10
|
import { legendLabelFor, useLegendItems, } from './swatch.js';
|
|
11
11
|
import { useSlotKey } from './use-slot-key.js';
|
|
12
|
+
import { useXOffset } from './x-offset.js';
|
|
12
13
|
import { useBandLadder } from './use-band-ladder.js';
|
|
13
14
|
/** Read a d3 linear scale's domain lower bound (the axis floor) from the plain
|
|
14
15
|
* `(value) => pixel` function the row hands to `draw`. The runtime object is a
|
|
@@ -37,13 +38,17 @@ export function resolveAreaBaseline(baseline, yScale) {
|
|
|
37
38
|
return floor;
|
|
38
39
|
if (!d || d.length === 0)
|
|
39
40
|
return baseline;
|
|
40
|
-
// Clamped into the domain, as a bar's baseline is:
|
|
41
|
-
//
|
|
41
|
+
// Clamped into the domain, as a bar's baseline is: the baseline never
|
|
42
|
+
// widens the axis, so on data that never crosses zero (or under an explicit
|
|
43
|
+
// `<YAxis min={40}>`) the default 0 is off the plot, and a fill resting
|
|
42
44
|
// there would anchor its fade (and a gap's `'fade'` connector) off-plot.
|
|
43
45
|
const lo = Math.min(d[0], d[d.length - 1]);
|
|
44
46
|
const hi = Math.max(d[0], d[d.length - 1]);
|
|
45
47
|
return Math.min(hi, Math.max(lo, baseline));
|
|
46
48
|
}
|
|
49
|
+
/** Stable empty boundary list — so `sessionBreaks={false}` keeps a referentially
|
|
50
|
+
* constant array and the layer entry isn't rebuilt every render. */
|
|
51
|
+
const NO_BREAKS = [];
|
|
47
52
|
/**
|
|
48
53
|
* An area draw layer: fills between a value `column` and a `baseline`, with a
|
|
49
54
|
* graded (gradient) shade — opaque at the line, transparent at the baseline —
|
|
@@ -64,7 +69,7 @@ export function resolveAreaBaseline(baseline, yScale) {
|
|
|
64
69
|
* </Layers>
|
|
65
70
|
* ```
|
|
66
71
|
*/
|
|
67
|
-
export function AreaChart({ series, column, readout, as: semantic, axis, id, baseline, curve, gaps = DEFAULT_GAP_MODE, decimate = true, thresholds, bandColors, legend, index = 0, }) {
|
|
72
|
+
export function AreaChart({ series: source, column, readout, as: semantic, axis, id, baseline, curve, gaps = DEFAULT_GAP_MODE, sessionBreaks = false, decimate = true, thresholds, bandColors, legend, xOffset, index = 0, }) {
|
|
68
73
|
const container = useContext(ContainerContext);
|
|
69
74
|
if (container === null) {
|
|
70
75
|
throw new Error('<AreaChart> must be rendered inside a <ChartContainer>');
|
|
@@ -73,11 +78,24 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
73
78
|
if (layers === null) {
|
|
74
79
|
throw new Error('<AreaChart> must be rendered inside a <Layers>');
|
|
75
80
|
}
|
|
81
|
+
// The series as drawn: moved by `xOffset` (the source itself when there is
|
|
82
|
+
// none), so every read below shares one axis.
|
|
83
|
+
const series = useXOffset(source, xOffset, 'AreaChart');
|
|
76
84
|
// `undefined` below means "the axis floor" — the internal helpers' spelling.
|
|
77
85
|
const baseValue = baseline === 'floor' ? undefined : (baseline ?? 0);
|
|
78
86
|
const cs = useMemo(() => series instanceof ValueSeries
|
|
79
87
|
? fromValueSeries(series, column)
|
|
80
88
|
: fromTimeSeries(series, column), [series, column]);
|
|
89
|
+
// Trading-axis session breaks: the collapse instants inside this series' span.
|
|
90
|
+
// Data instants, not pixels — view-independent, so pan/zoom reuse them. The
|
|
91
|
+
// same lookup `<LineChart>` / `<BandChart>` do, so the three break alike.
|
|
92
|
+
const sessionBreakInstants = useMemo(() => {
|
|
93
|
+
const provider = container.discontinuities;
|
|
94
|
+
if (!sessionBreaks || provider?.boundaries === undefined || cs.length < 2) {
|
|
95
|
+
return NO_BREAKS;
|
|
96
|
+
}
|
|
97
|
+
return provider.boundaries(cs.x[0], cs.x[cs.length - 1]);
|
|
98
|
+
}, [sessionBreaks, container.discontinuities, cs]);
|
|
81
99
|
// Readout column values for a value-axis series (time path reads it off the
|
|
82
100
|
// event) — the tracker reports it alongside the plotted fill so an off-chart
|
|
83
101
|
// readout can show a source value. See AreaChartProps.readout.
|
|
@@ -150,13 +168,9 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
150
168
|
const entry = useMemo(() => ({
|
|
151
169
|
layer: {
|
|
152
170
|
as: semantic,
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
// would collapse the domain onto the max and clip the series.
|
|
157
|
-
yExtent: (scale) => areaExtent(cs, scale === 'log' && baseValue !== undefined && baseValue <= 0
|
|
158
|
-
? undefined
|
|
159
|
-
: baseValue),
|
|
171
|
+
// The values alone: the baseline never widens the axis (see
|
|
172
|
+
// `baseline`), so an area fits the same domain a line would.
|
|
173
|
+
yExtent: () => yExtent(cs),
|
|
160
174
|
// The container infers the shared x scale's kind from its layers — a
|
|
161
175
|
// ValueSeries plots on a value axis, a TimeSeries on time.
|
|
162
176
|
xKind: series instanceof ValueSeries ? 'value' : 'time',
|
|
@@ -289,7 +303,7 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
289
303
|
// `NaN` and poison every coordinate in the fill path. Fall back
|
|
290
304
|
// to the axis floor, which is exactly what `'floor'`
|
|
291
305
|
// already resolves to.
|
|
292
|
-
resolveAreaBaseline(baseValue, yScale), curveFactory, gaps, gapConnectorOpacity, decimate, bandLadder);
|
|
306
|
+
resolveAreaBaseline(baseValue, yScale), curveFactory, gaps, gapConnectorOpacity, decimate, bandLadder, sessionBreakInstants);
|
|
293
307
|
}
|
|
294
308
|
},
|
|
295
309
|
},
|
|
@@ -307,6 +321,7 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
307
321
|
curveFactory,
|
|
308
322
|
gaps,
|
|
309
323
|
gapConnectorOpacity,
|
|
324
|
+
sessionBreakInstants,
|
|
310
325
|
decimate,
|
|
311
326
|
bandLadder,
|
|
312
327
|
axis,
|