@pond-ts/charts 0.70.0 → 0.71.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/AGENTS.md +3 -1
- package/API.md +30 -31
- package/CHANGELOG.md +111 -1
- package/dist/AreaChart.d.ts +30 -22
- package/dist/AreaChart.js +37 -22
- package/dist/BoxPlot.js +5 -3
- package/dist/Candlestick.d.ts +3 -3
- package/dist/Candlestick.js +5 -5
- package/dist/ChartContainer.d.ts +8 -149
- package/dist/ChartContainer.js +68 -111
- package/dist/ChartRow.d.ts +1 -12
- package/dist/ChartRow.js +15 -30
- package/dist/HeatMap.d.ts +3 -3
- package/dist/HeatMap.js +3 -3
- package/dist/Layers.js +84 -34
- package/dist/XAxis.js +1 -0
- package/dist/area.d.ts +6 -5
- package/dist/area.js +13 -6
- package/dist/axis-events.d.ts +4 -4
- package/dist/brush.d.ts +13 -21
- package/dist/brush.js +22 -36
- package/dist/context.d.ts +81 -96
- package/dist/cursors.d.ts +81 -51
- package/dist/cursors.js +164 -127
- package/dist/elapsed.d.ts +1 -1
- package/dist/elapsed.js +1 -1
- package/dist/format.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/selection-fixtures.d.ts +4 -7
- package/dist/selection-fixtures.js +0 -9
- package/dist/selection-stories.d.ts +4 -6
- package/dist/selection-stories.js +4 -4
- package/dist/selectors.d.ts +2 -3
- package/dist/selectors.js +2 -3
- package/dist/tracker.d.ts +10 -28
- package/dist/tracker.js +17 -40
- package/dist/tradingTimeScale.d.ts +2 -2
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -159,6 +159,7 @@ import {
|
|
|
159
159
|
Layers,
|
|
160
160
|
LineChart,
|
|
161
161
|
YAxis,
|
|
162
|
+
CrosshairCursor,
|
|
162
163
|
} from '@pond-ts/charts';
|
|
163
164
|
|
|
164
165
|
const [live, snap] = useLiveSeries({
|
|
@@ -167,7 +168,8 @@ const [live, snap] = useLiveSeries({
|
|
|
167
168
|
retention: { maxAge: '10m' },
|
|
168
169
|
});
|
|
169
170
|
|
|
170
|
-
<ChartContainer width={800}
|
|
171
|
+
<ChartContainer width={800} panZoom>
|
|
172
|
+
<CrosshairCursor />
|
|
171
173
|
<ChartRow height={240}>
|
|
172
174
|
<YAxis id="ms" />
|
|
173
175
|
<Layers>
|
package/API.md
CHANGED
|
@@ -251,17 +251,17 @@ Types: `UseSnapshotOptions`, `SnapshotSource` (structural — covers
|
|
|
251
251
|
|
|
252
252
|
### Components — layout & axes
|
|
253
253
|
|
|
254
|
-
| Component | Key props
|
|
255
|
-
| --------------------------- |
|
|
256
|
-
| `ChartContainer` | `width`, `range?`, `theme?`, `
|
|
257
|
-
| `ChartRow` | `height
|
|
258
|
-
| `Layers` | children
|
|
259
|
-
| `YAxis` | `id` (req), `side?`, `scale?` (`'linear'` \| `'log'` \| `'symlog'`), `linearWindow?`, `min?`/`max?`, `format?`, `width?`, `hide?`
|
|
260
|
-
| `XAxis` | `side?`, `label?`, `format?`, `ticks?`, `transform?`, `dateStyle?`, `timeZone?` (this strip in another IANA zone)
|
|
261
|
-
| `TimeAxis` / `CategoryAxis` | (XAxis props)
|
|
262
|
-
| `Canvas` | `width`, `height`, `draw`
|
|
263
|
-
| `Selector` | `enabled?` (default `true`), `selected?` (mark \| set), `hovered?`, `onSelect?`, `onHover?`, `children?`
|
|
264
|
-
| `MultiSelector` | `enabled?`, `selected?`, `hovered?`, `sequence?`, `onSelect?`, `onHover?`, `children?`
|
|
254
|
+
| Component | Key props | Purpose | Source |
|
|
255
|
+
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
256
|
+
| `ChartContainer` | `width`, `range?`, `theme?`, `panZoom?`, `xScale?`, `bounds?`, `showAxis?`, `calendar?`, `timeZone?`, `origin?`, `maxBandWidth?`/`bandAlign?`, `onTrackerChanged?`, `onDrawStats?` | Root: shared x-scale, interactions, annotations; `timeZone` renders the time axis in an IANA zone (default: viewer-local; a `calendar.timeZone` supplies the default) | `packages/charts/src/ChartContainer.tsx` |
|
|
257
|
+
| `ChartRow` | `height` | One stacked plot band; owns its y-axes | `packages/charts/src/ChartRow.tsx` |
|
|
258
|
+
| `Layers` | children | Mandatory z-stack inside a row (back-to-front) | `packages/charts/src/Layers.tsx` |
|
|
259
|
+
| `YAxis` | `id` (req), `side?`, `scale?` (`'linear'` \| `'log'` \| `'symlog'`), `linearWindow?`, `min?`/`max?`, `format?`, `width?`, `hide?` | Y-axis gutter; layers bind via their `axis` prop | `packages/charts/src/YAxis.tsx` |
|
|
260
|
+
| `XAxis` | `side?`, `label?`, `format?`, `ticks?`, `transform?`, `dateStyle?`, `timeZone?` (this strip in another IANA zone) | Placeable x-axis strip; kind inferred from data | `packages/charts/src/XAxis.tsx` |
|
|
261
|
+
| `TimeAxis` / `CategoryAxis` | (XAxis props) | Thin `XAxis` presets | `packages/charts/src/TimeAxis.tsx`, `CategoryAxis.tsx` |
|
|
262
|
+
| `Canvas` | `width`, `height`, `draw` | Low-level DPR-aware canvas primitive | `packages/charts/src/Canvas.tsx` |
|
|
263
|
+
| `Selector` | `enabled?` (default `true`), `selected?` (mark \| set), `hovered?`, `onSelect?`, `onHover?`, `children?` | Wraps its scope; mounting enables click-select and owns the state it drives (RFC A10) | `packages/charts/src/selectors.tsx` |
|
|
264
|
+
| `MultiSelector` | `enabled?`, `selected?`, `hovered?`, `sequence?`, `onSelect?`, `onHover?`, `children?` | Sweep-select superset of `Selector`: drag sweeps marks, release reports `(hits, modifiers, spans)` — plural, one per swept layer (RFC A5.2) | `packages/charts/src/selectors.tsx` |
|
|
265
265
|
|
|
266
266
|
### Components — draw layers
|
|
267
267
|
|
|
@@ -303,25 +303,24 @@ schema columns). Because the union splits per series _kind_, a value typed as
|
|
|
303
303
|
|
|
304
304
|
### Components — cursors (mounted presets)
|
|
305
305
|
|
|
306
|
-
|
|
307
|
-
as a child of `<ChartContainer>` (the default for
|
|
308
|
-
`<ChartRow>` (the per-row override). Render-only
|
|
309
|
-
gesture-owning cursor (`Crosshair`/`Range`) per scope. The
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
| `
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `RangeCursor` | `sequence?`, `onDragRelease?`, `enableDrag?`, `dragModifier?` | The hover-time band + the drag: release fires once with a `RangeSpan`, then reverts (`"region"` + `onRegionSelect` successor) | `packages/charts/src/cursors.tsx` |
|
|
306
|
+
Cursor presets (interaction RFC §4/A4.1). **A chart shows a cursor only when
|
|
307
|
+
one is mounted** — mount one as a child of `<ChartContainer>` (the default for
|
|
308
|
+
every row) or inside a `<ChartRow>` (the per-row override). Render-only
|
|
309
|
+
presets stack; one gesture-owning cursor (`Crosshair`/`Range`) per scope. The
|
|
310
|
+
first mounted cursor that sets `format` shapes the chart's readout channel.
|
|
311
|
+
The underlying `CursorSpec` contract stays unpublished (Q3); every drag claim
|
|
312
|
+
on the plot (annotation-create, the range drag, pan) is arbitrated by one
|
|
313
|
+
brush recognizer with a documented precedence (`src/brush.tsx`, RFC
|
|
314
|
+
A1.5/A2.7).
|
|
315
|
+
|
|
316
|
+
| Component | Key props | Purpose | Source |
|
|
317
|
+
| ----------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- |
|
|
318
|
+
| `LineCursor` | `showTime?`, `format?` | The synced vertical line | `packages/charts/src/cursors.tsx` |
|
|
319
|
+
| `PointCursor` | `showTime?`, `format?` | A dot on each series at the cursor | `packages/charts/src/cursors.tsx` |
|
|
320
|
+
| `InlineCursor` | `showTime?`, `format?` | Dots + a value chip beside each | `packages/charts/src/cursors.tsx` |
|
|
321
|
+
| `FlagCursor` | `showTime?`, `format?` | Dots + staffed value flags stacked at the top | `packages/charts/src/cursors.tsx` |
|
|
322
|
+
| `CrosshairCursor` | `snap?`, `showTime?`, `format?`, `onSnap?` | The inspection reticle: dashed cross, centre dot in the snapped series' colour, y value pill, x time pill; `onSnap` reports the snapped series + point | `packages/charts/src/cursors.tsx` |
|
|
323
|
+
| `RangeCursor` | `sequence?`, `onDragRelease?`, `enableDrag?`, `dragModifier?` | The hover-time band + the drag: release fires once with a `RangeSpan`, then reverts; on a category axis the band shades the slot and the drag is off | `packages/charts/src/cursors.tsx` |
|
|
325
324
|
|
|
326
325
|
### Components — standalone row lists (DOM tables, no `<ChartContainer>`)
|
|
327
326
|
|
|
@@ -398,8 +397,8 @@ Series shapes (same file): `ChartSeries`, `BandSeries`, `BoxSeries`,
|
|
|
398
397
|
| `scaleBand` / `ScaleBand` | Ordinal slot scale for the category axis | `packages/charts/src/bandScale.ts` |
|
|
399
398
|
| `GapMode` | `'none' \| 'empty' \| 'dashed' \| 'step' \| 'fade'` (Line/Area `gaps` prop) | `packages/charts/src/gaps.ts` |
|
|
400
399
|
| `DecimateOption` | `<LineChart decimate>` — M4 viewport decimation (`bool \| { threshold }`) | `packages/charts/src/decimate.ts` |
|
|
401
|
-
| `CursorMode` | `'none' \| 'line' \| 'point' \| 'inline' \| 'flag' \| 'crosshair' \| 'region'` | `packages/charts/src/context.ts` |
|
|
402
400
|
| `TrackerInfo` / `TrackerSample` | Hover readout payload (`onTrackerChanged`) | `packages/charts/src/context.ts` |
|
|
401
|
+
| `CursorSnap` | The point a `<CrosshairCursor>` snapped to (`onSnap`): a `TrackerSample` + `axisId` + `formatted` | `packages/charts/src/context.ts` |
|
|
403
402
|
| `AnnotationKind` / `CreateSpec` | Annotation identity + draw-gesture payload (`onCreate`) | `packages/charts/src/context.ts` |
|
|
404
403
|
| `SelectInfo` | Selection/hover payload (`Selector`/`MultiSelector` `onSelect`/`onHover`) | `packages/charts/src/context.ts` |
|
|
405
404
|
| `SelectModifiers` | Keyboard modifiers on a click, 2nd arg to `onSelect` | `packages/charts/src/context.ts` |
|
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,8 @@ 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.71.0...HEAD
|
|
12
|
+
[0.71.0]: https://github.com/pond-ts/pond/compare/v0.70.0...v0.71.0
|
|
12
13
|
[0.70.0]: https://github.com/pond-ts/pond/compare/v0.69.0...v0.70.0
|
|
13
14
|
[0.69.0]: https://github.com/pond-ts/pond/compare/v0.68.0...v0.69.0
|
|
14
15
|
[0.68.0]: https://github.com/pond-ts/pond/compare/v0.67.0...v0.68.0
|
|
@@ -73,6 +74,115 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
73
74
|
|
|
74
75
|
## [Unreleased]
|
|
75
76
|
|
|
77
|
+
## [0.71.0] — 2026-09-24
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- `@pond-ts/charts`: **`format` on `<LineCursor>`, `<PointCursor>`,
|
|
82
|
+
`<InlineCursor>` and `<FlagCursor>`** — the readout format that used to be
|
|
83
|
+
the container's `cursorFormat`, now on whichever cursor you mount (it was
|
|
84
|
+
already on `<CrosshairCursor>`). The chart still has one readout channel:
|
|
85
|
+
the first mounted cursor that sets `format` shapes it.
|
|
86
|
+
|
|
87
|
+
- `@pond-ts/charts`: **`<CrosshairCursor onSnap>`** — tells you what the
|
|
88
|
+
crosshair is snapped to: the series (`label`, `color`, `axisId`) and the
|
|
89
|
+
point (`x`, `value`, `formatted`, plus `readout` when the layer has one), as
|
|
90
|
+
a new exported **`CursorSnap`** type. Fires only when the snapped point
|
|
91
|
+
changes, and with `null` when the pointer leaves. With `snap={false}` the
|
|
92
|
+
reticle follows the pointer rather than a series, so it stays `null`; a
|
|
93
|
+
reticle drawn by a controlled `trackerPosition` with no pointer on the chart
|
|
94
|
+
also reports `null`. Story `Cursors/Crosshair / SnapReadout`.
|
|
95
|
+
|
|
96
|
+
- `@pond-ts/charts`: **`<CrosshairCursor>` snaps to box plots.** Its vertical
|
|
97
|
+
line lands on the centre of the box under the pointer and its horizontal
|
|
98
|
+
line on the quantile nearest the pointer (`upper` / `q3` / `median` / `q1` /
|
|
99
|
+
`lower`), with that value on the y-axis pill and reported by `onSnap`. A box
|
|
100
|
+
used to be skipped by the crosshair entirely: the line sat wherever the
|
|
101
|
+
pointer was and there was no value to read. The flag cursor still shows the
|
|
102
|
+
box's one consolidated flag, and the point / inline / flag cursors still draw
|
|
103
|
+
no per-quantile dots on a box. In a row with a box and a line, the vertical
|
|
104
|
+
line snaps to whichever of the two is drawn on top, the same rule as for two
|
|
105
|
+
lines. Story `Cursors/Crosshair / BoxPlot`.
|
|
106
|
+
|
|
107
|
+
### Changed
|
|
108
|
+
|
|
109
|
+
- `@pond-ts/charts`: the **crosshair's centre dot is drawn in the snapped
|
|
110
|
+
series' colour** rather than the cursor ink, so the reticle shows which line
|
|
111
|
+
it is reading. The free reticle (`snap={false}`) has no series under it and
|
|
112
|
+
keeps the cursor ink.
|
|
113
|
+
|
|
114
|
+
- **charts:** **`<AreaChart>` fills to zero by default.** An omitted `baseline`
|
|
115
|
+
used to rest the fill on the bottom of the plot, which on auto-fit data sits
|
|
116
|
+
just under the lowest value — so a series running 50–90 drew 60 as a sliver
|
|
117
|
+
and 90 as a slab several times its size, and the fill's height said nothing
|
|
118
|
+
about the value. The default is now `0`: zero is pulled into the auto-fit
|
|
119
|
+
domain and each fill is as tall as its value, the usual reading of an area.
|
|
120
|
+
A number still sets another reference level. The old look is one prop away:
|
|
121
|
+
**`baseline="floor"`** rests the fill on the bottom of the plot and adds
|
|
122
|
+
nothing to the domain — for a price or an elevation profile, where starting
|
|
123
|
+
at zero would flatten the shape. **Migration:** an `<AreaChart>` with no
|
|
124
|
+
`baseline` whose data sits far from zero will now show zero on its axis; add
|
|
125
|
+
`baseline="floor"` to keep the previous rendering. Charts that already wrote
|
|
126
|
+
`baseline={0}`, or whose axis already started at zero, are unchanged. On a
|
|
127
|
+
log axis nothing changes: zero has no position there, so it rests on the
|
|
128
|
+
floor and is not pulled into the domain. On an axis pinned above zero
|
|
129
|
+
(`<YAxis min={40}>`) the baseline is clamped to the axis floor, as a bar's is,
|
|
130
|
+
so the fill's fade stays on the plot.
|
|
131
|
+
|
|
132
|
+
### Removed
|
|
133
|
+
|
|
134
|
+
- `@pond-ts/charts` (**breaking**): **the old cursor props are gone — a chart
|
|
135
|
+
shows a cursor only when you mount one.** Removed: `<ChartContainer>`'s
|
|
136
|
+
`cursor`, `cursorTime`, `crosshairSnap`, `cursorFormat`, `cursorSequence`,
|
|
137
|
+
`onRegionSelect` and `regionSelectModifier`, `<ChartRow cursor>`, and the
|
|
138
|
+
`CursorMode` type. They were deprecated in 0.58.0. The behaviour change that
|
|
139
|
+
matters most: a chart with no cursor component used to get a vertical line
|
|
140
|
+
cursor anyway; it now gets **no cursor** (hover still reports through
|
|
141
|
+
`onTrackerChanged`). Closes
|
|
142
|
+
[#647](https://github.com/pond-ts/pond/issues/647). **Migration:**
|
|
143
|
+
|
|
144
|
+
| Before | After |
|
|
145
|
+
| ------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
146
|
+
| no `cursor` prop (the implicit line) | `<LineCursor />` (not on a `<MultiSelector>` row — see below) |
|
|
147
|
+
| `cursor="line" \| "point" \| "inline" \| "flag"` | `<LineCursor />` / `<PointCursor />` / `<InlineCursor />` / `<FlagCursor />` |
|
|
148
|
+
| `cursor="crosshair"` + `crosshairSnap={false}` | `<CrosshairCursor snap={false} />` |
|
|
149
|
+
| `cursorTime` | `showTime` on the cursor |
|
|
150
|
+
| `cursorFormat="…"` | `format="…"` on the cursor |
|
|
151
|
+
| `cursor="region"` + `cursorSequence={seq}` | `<RangeCursor sequence={seq} />` |
|
|
152
|
+
| `onRegionSelect={([a, b]) => …}` | `<RangeCursor onDragRelease={({ x: [a, b] }) => …} />` |
|
|
153
|
+
| `regionSelectModifier="shift"` | `<RangeCursor dragModifier="shift" />` |
|
|
154
|
+
| `cursor="none"` | mount nothing |
|
|
155
|
+
| `<ChartRow cursor="…">` | mount the cursor inside that `<ChartRow>` |
|
|
156
|
+
|
|
157
|
+
Mount the cursor as a child of `<ChartContainer>` for every row, or inside
|
|
158
|
+
one `<ChartRow>` for that row only. A row that should have no cursor while
|
|
159
|
+
its siblings have one: mount the cursors per row instead of at the
|
|
160
|
+
container. **Don't add `<LineCursor />` to a row with a `<MultiSelector>`**:
|
|
161
|
+
there the selector's resting band is the cursor (as it already was under the
|
|
162
|
+
implicit line), and any mounted cursor replaces the band.
|
|
163
|
+
|
|
164
|
+
### Fixed
|
|
165
|
+
|
|
166
|
+
- `@pond-ts/charts`: **`<RangeCursor>` on a category axis now shades the bar
|
|
167
|
+
under the pointer.** It used to draw nothing there, so mounting one left the
|
|
168
|
+
row with no cursor at all. The band covers the whole slot, the way a
|
|
169
|
+
bucketed band covers a bucket on a time axis. The drag stays off on a
|
|
170
|
+
category axis (`onDragRelease` never fires there, and the chart now warns
|
|
171
|
+
in development when it is wired); dragging across bars to get them back is
|
|
172
|
+
what `<MultiSelector>` does. Story `Cursors/Range / CategoryAxis`.
|
|
173
|
+
|
|
174
|
+
- **charts:** **A selectable `<AreaChart>` on a log axis with `baseline={0}`
|
|
175
|
+
counted every point over its x span as a hit.** Zero maps to `NaN` on a log
|
|
176
|
+
scale; the draw already fell back to the axis floor, but the hit test used
|
|
177
|
+
the `NaN` pixel, and a `NaN` bound fails neither range check. It now resolves
|
|
178
|
+
the baseline the same way the draw does.
|
|
179
|
+
- **charts:** **`<AreaChart baseline={0}>` on an auto-fit log axis clipped
|
|
180
|
+
most of its series.** The zero baseline was pulled into the extent, which
|
|
181
|
+
left the log fit with no positive low end, so the domain collapsed around the
|
|
182
|
+
largest value (data from 10 to 1e5 fitted `[1e4, 1e6]`). A baseline at or
|
|
183
|
+
below zero is now left out of a log axis's fit. A layer's `yExtent` receives
|
|
184
|
+
the axis's scale kind for this.
|
|
185
|
+
|
|
76
186
|
## [0.70.0] — 2026-09-18
|
|
77
187
|
|
|
78
188
|
### Added
|
package/dist/AreaChart.d.ts
CHANGED
|
@@ -31,21 +31,27 @@ export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
|
|
|
31
31
|
*/
|
|
32
32
|
axis?: string;
|
|
33
33
|
/**
|
|
34
|
-
* The value the fill rests on — the flat edge opposite the value line.
|
|
35
|
-
* forms:
|
|
34
|
+
* The value the fill rests on — the flat edge opposite the value line.
|
|
36
35
|
*
|
|
37
|
-
* - **Omitted ⇒
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
36
|
+
* - **Omitted ⇒ `0`**: an area measures size from zero, so values above zero
|
|
37
|
+
* fill up and values below it fill down, each side's shade fading toward
|
|
38
|
+
* the zero line. Zero is pulled into the auto-fit domain, so the axis always
|
|
39
|
+
* shows where the fill starts.
|
|
40
|
+
* - **Another number ⇒ that reference level**, with the same behaviour (e.g.
|
|
41
|
+
* `baseline={100}` for an index that fills above and below 100). For the
|
|
42
|
+
* esnet two-colour traffic look, compose two `<AreaChart>`s (an "in" column
|
|
43
|
+
* and an "out" column, distinct `as` roles).
|
|
44
|
+
* - **`'floor'` ⇒ the axis's lower bound** (the bottom of the plot): the fill
|
|
45
|
+
* rests on whatever the axis starts at, and nothing is added to the domain.
|
|
46
|
+
* For a series that never nears zero where zero would flatten the shape — a
|
|
47
|
+
* price, an elevation profile.
|
|
43
48
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
49
|
+
* On a **log** axis zero has no position, so a baseline at or below zero
|
|
50
|
+
* falls back to the axis floor and is not pulled into the domain. On an axis
|
|
51
|
+
* pinned so that it excludes the baseline, the fill rests on the nearest
|
|
52
|
+
* edge (the baseline is clamped into the domain, as a bar's is).
|
|
47
53
|
*/
|
|
48
|
-
baseline?: number;
|
|
54
|
+
baseline?: number | 'floor';
|
|
49
55
|
/**
|
|
50
56
|
* Render-time path interpolation for the outline + fill edge — a view concern
|
|
51
57
|
* (denoise the data with pond's `smooth()` upstream). **Omitted ⇒ `'linear'`**
|
|
@@ -95,8 +101,9 @@ export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS exten
|
|
|
95
101
|
* (`theme.area[as] ?? theme.area.default`), overridden by {@link bandColors}.
|
|
96
102
|
* Breakpoints are data and live here; colour stays in the theme — the same
|
|
97
103
|
* split `<BarChart thresholds>` uses ([PND-BANDBAR2]), and the same
|
|
98
|
-
* magnitude-mirrored reading: values below zero (
|
|
99
|
-
*
|
|
104
|
+
* magnitude-mirrored reading: values below zero (which fill downward from
|
|
105
|
+
* the default zero baseline) walk the same ±ladder without negative
|
|
106
|
+
* breakpoints.
|
|
100
107
|
*
|
|
101
108
|
* **A banded area is still one area.** One hit region, one legend row, one
|
|
102
109
|
* readout identity; hover / selection strengthen the fill and keep the band
|
|
@@ -171,8 +178,9 @@ export type AreaChartProps<S extends SeriesSchema = SeriesSchema, VS extends Val
|
|
|
171
178
|
* it to the call signature, so this reads the bound through a localized,
|
|
172
179
|
* documented shape rather than widening `drawArea`'s contract to d3-scale. */
|
|
173
180
|
/**
|
|
174
|
-
* The area's baseline in **data** units: the caller's `baseline
|
|
175
|
-
* finite position on this axis
|
|
181
|
+
* The area's baseline in **data** units: the caller's `baseline`, clamped into
|
|
182
|
+
* the axis domain, when it has a finite position on this axis; else the axis
|
|
183
|
+
* floor.
|
|
176
184
|
*
|
|
177
185
|
* The fallback is not defensive padding — it's the log case. `baseline={0}` is
|
|
178
186
|
* the natural thing to write and is correct on a linear axis; on a log axis
|
|
@@ -191,15 +199,15 @@ export declare function resolveAreaBaseline(baseline: number | undefined, yScale
|
|
|
191
199
|
* against its `axis`), and renders nothing to the DOM — the row draws it. The
|
|
192
200
|
* fill + outline break at gaps rather than spanning them.
|
|
193
201
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* traffic look composes two layers, each with its own `as` role:
|
|
202
|
+
* The fill rests on `baseline` (see {@link AreaChartProps.baseline}): **`0`
|
|
203
|
+
* by default** (positive up, negative down), any other number for a different
|
|
204
|
+
* reference level, or `'floor'` for the bottom of the plot. The esnet
|
|
205
|
+
* two-colour traffic look composes two layers, each with its own `as` role:
|
|
198
206
|
*
|
|
199
207
|
* ```tsx
|
|
200
208
|
* <Layers>
|
|
201
|
-
* <AreaChart series={s} column="in"
|
|
202
|
-
* <AreaChart series={s} column="out"
|
|
209
|
+
* <AreaChart series={s} column="in" as="in" />
|
|
210
|
+
* <AreaChart series={s} column="out" as="out" />
|
|
203
211
|
* </Layers>
|
|
204
212
|
* ```
|
|
205
213
|
*/
|
package/dist/AreaChart.js
CHANGED
|
@@ -16,8 +16,9 @@ import { useBandLadder } from './use-band-ladder.js';
|
|
|
16
16
|
* it to the call signature, so this reads the bound through a localized,
|
|
17
17
|
* documented shape rather than widening `drawArea`'s contract to d3-scale. */
|
|
18
18
|
/**
|
|
19
|
-
* The area's baseline in **data** units: the caller's `baseline
|
|
20
|
-
* finite position on this axis
|
|
19
|
+
* The area's baseline in **data** units: the caller's `baseline`, clamped into
|
|
20
|
+
* the axis domain, when it has a finite position on this axis; else the axis
|
|
21
|
+
* floor.
|
|
21
22
|
*
|
|
22
23
|
* The fallback is not defensive padding — it's the log case. `baseline={0}` is
|
|
23
24
|
* the natural thing to write and is correct on a linear axis; on a log axis
|
|
@@ -28,14 +29,20 @@ import { useBandLadder } from './use-band-ladder.js';
|
|
|
28
29
|
* layer's meaning ("fill from the bottom") on both scale kinds.
|
|
29
30
|
*/
|
|
30
31
|
export function resolveAreaBaseline(baseline, yScale) {
|
|
31
|
-
const
|
|
32
|
+
const d = yScale.domain?.();
|
|
33
|
+
const floor = d && d.length > 0 ? d[0] : 0;
|
|
32
34
|
if (baseline === undefined)
|
|
33
35
|
return floor;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
if (!Number.isFinite(yScale(baseline)))
|
|
37
|
+
return floor;
|
|
38
|
+
if (!d || d.length === 0)
|
|
39
|
+
return baseline;
|
|
40
|
+
// Clamped into the domain, as a bar's baseline is: under an explicit
|
|
41
|
+
// `<YAxis min={40}>` the default 0 is off the plot, and a fill resting
|
|
42
|
+
// there would anchor its fade (and a gap's `'fade'` connector) off-plot.
|
|
43
|
+
const lo = Math.min(d[0], d[d.length - 1]);
|
|
44
|
+
const hi = Math.max(d[0], d[d.length - 1]);
|
|
45
|
+
return Math.min(hi, Math.max(lo, baseline));
|
|
39
46
|
}
|
|
40
47
|
/**
|
|
41
48
|
* An area draw layer: fills between a value `column` and a `baseline`, with a
|
|
@@ -45,15 +52,15 @@ function domainFloor(yScale) {
|
|
|
45
52
|
* against its `axis`), and renders nothing to the DOM — the row draws it. The
|
|
46
53
|
* fill + outline break at gaps rather than spanning them.
|
|
47
54
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* traffic look composes two layers, each with its own `as` role:
|
|
55
|
+
* The fill rests on `baseline` (see {@link AreaChartProps.baseline}): **`0`
|
|
56
|
+
* by default** (positive up, negative down), any other number for a different
|
|
57
|
+
* reference level, or `'floor'` for the bottom of the plot. The esnet
|
|
58
|
+
* two-colour traffic look composes two layers, each with its own `as` role:
|
|
52
59
|
*
|
|
53
60
|
* ```tsx
|
|
54
61
|
* <Layers>
|
|
55
|
-
* <AreaChart series={s} column="in"
|
|
56
|
-
* <AreaChart series={s} column="out"
|
|
62
|
+
* <AreaChart series={s} column="in" as="in" />
|
|
63
|
+
* <AreaChart series={s} column="out" as="out" />
|
|
57
64
|
* </Layers>
|
|
58
65
|
* ```
|
|
59
66
|
*/
|
|
@@ -66,6 +73,8 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
66
73
|
if (layers === null) {
|
|
67
74
|
throw new Error('<AreaChart> must be rendered inside a <Layers>');
|
|
68
75
|
}
|
|
76
|
+
// `undefined` below means "the axis floor" — the internal helpers' spelling.
|
|
77
|
+
const baseValue = baseline === 'floor' ? undefined : (baseline ?? 0);
|
|
69
78
|
const cs = useMemo(() => series instanceof ValueSeries
|
|
70
79
|
? fromValueSeries(series, column)
|
|
71
80
|
: fromTimeSeries(series, column), [series, column]);
|
|
@@ -141,7 +150,13 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
141
150
|
const entry = useMemo(() => ({
|
|
142
151
|
layer: {
|
|
143
152
|
as: semantic,
|
|
144
|
-
|
|
153
|
+
// A baseline with no position on a log axis (zero, the default) is
|
|
154
|
+
// left out of the fit: the draw rests it on the floor anyway, and an
|
|
155
|
+
// extent of `[0, max]` gives the log fit no positive low end, so it
|
|
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),
|
|
145
160
|
// The container infers the shared x scale's kind from its layers — a
|
|
146
161
|
// ValueSeries plots on a value axis, a TimeSeries on time.
|
|
147
162
|
xKind: series instanceof ValueSeries ? 'value' : 'time',
|
|
@@ -155,7 +170,7 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
155
170
|
sweepAxis: 'x',
|
|
156
171
|
sweepSpanOnly: true,
|
|
157
172
|
hitTest: (px, py, xScale, yScale) => {
|
|
158
|
-
const i = areaHitIndex(cs,
|
|
173
|
+
const i = areaHitIndex(cs, baseValue, px, py, xScale, yScale);
|
|
159
174
|
if (i === null)
|
|
160
175
|
return null;
|
|
161
176
|
return {
|
|
@@ -266,15 +281,15 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
266
281
|
: { ...inStyle, color: style.spanColor, fill: style.spanColor }, 1), true, 0, plotExtentOf(ctx, xScale, yScale).width);
|
|
267
282
|
function drawAreaWith(st) {
|
|
268
283
|
return drawArea(ctx, cs, xScale, yScale, st,
|
|
269
|
-
//
|
|
270
|
-
// scale, so it tracks the auto-fit domain); a
|
|
271
|
-
// verbatim.
|
|
284
|
+
// `'floor'` rests on the axis floor (resolved late from the
|
|
285
|
+
// scale, so it tracks the auto-fit domain); a number (default 0)
|
|
286
|
+
// is used verbatim.
|
|
272
287
|
// A log axis has no position for zero — or anything at or below
|
|
273
288
|
// it — so an explicit out-of-domain `baseline` would scale to
|
|
274
289
|
// `NaN` and poison every coordinate in the fill path. Fall back
|
|
275
|
-
// to the axis floor, which is exactly what
|
|
290
|
+
// to the axis floor, which is exactly what `'floor'`
|
|
276
291
|
// already resolves to.
|
|
277
|
-
resolveAreaBaseline(
|
|
292
|
+
resolveAreaBaseline(baseValue, yScale), curveFactory, gaps, gapConnectorOpacity, decimate, bandLadder);
|
|
278
293
|
}
|
|
279
294
|
},
|
|
280
295
|
},
|
|
@@ -288,7 +303,7 @@ export function AreaChart({ series, column, readout, as: semantic, axis, id, bas
|
|
|
288
303
|
readoutY,
|
|
289
304
|
style,
|
|
290
305
|
label,
|
|
291
|
-
|
|
306
|
+
baseValue,
|
|
292
307
|
curveFactory,
|
|
293
308
|
gaps,
|
|
294
309
|
gapConnectorOpacity,
|
package/dist/BoxPlot.js
CHANGED
|
@@ -139,9 +139,11 @@ export function BoxPlot({ series, lower, q1, median, q3, upper, as: semantic, ax
|
|
|
139
139
|
// The readout reads the box **under the cursor** (boxIndexAtTime — span
|
|
140
140
|
// containment, not nearest-by-begin which flips past a wide box's
|
|
141
141
|
// midpoint), anchored at the box **centre** `(x + xEnd) / 2`. Outside
|
|
142
|
-
// every box → no readout.
|
|
143
|
-
//
|
|
144
|
-
//
|
|
142
|
+
// every box → no readout. Feeds the off-chart readout and the
|
|
143
|
+
// crosshair (its x-snap lands mid-box, its y-snap on the nearest
|
|
144
|
+
// quantile); the in-chart flag is `cursorFlag`. `push` skips a
|
|
145
|
+
// non-finite quantile, so an absent (range-only) q1/q3/median simply
|
|
146
|
+
// doesn't read out.
|
|
145
147
|
if (bx.length === 0)
|
|
146
148
|
return [];
|
|
147
149
|
const i = boxIndexAtTime(bx, x);
|
package/dist/Candlestick.d.ts
CHANGED
|
@@ -111,9 +111,9 @@ export interface CandlestickProps<S extends SeriesSchema> {
|
|
|
111
111
|
* supersedes `BoxPlot shape='solid'` for OHLC (which needed a quantile remap, a
|
|
112
112
|
* body precompute, two overlaid layers for green/red, and a column-name tracker).
|
|
113
113
|
*
|
|
114
|
-
* **Cursor.**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* **Cursor.** A candle **participates in the crosshair snap** (as a box plot
|
|
115
|
+
* does), so the reticle lands on candles; unlike a box plot it has no
|
|
116
|
+
* consolidated `cursorFlag`, so the flag cursor shows a chip per value. The readout keys on `as` and shows `close` by default; pass
|
|
117
117
|
* `showOHLC` for the full four-pill quote.
|
|
118
118
|
*
|
|
119
119
|
* ```tsx
|
package/dist/Candlestick.js
CHANGED
|
@@ -37,9 +37,9 @@ import { spansForLayer } from './span.js';
|
|
|
37
37
|
* supersedes `BoxPlot shape='solid'` for OHLC (which needed a quantile remap, a
|
|
38
38
|
* body precompute, two overlaid layers for green/red, and a column-name tracker).
|
|
39
39
|
*
|
|
40
|
-
* **Cursor.**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* **Cursor.** A candle **participates in the crosshair snap** (as a box plot
|
|
41
|
+
* does), so the reticle lands on candles; unlike a box plot it has no
|
|
42
|
+
* consolidated `cursorFlag`, so the flag cursor shows a chip per value. The readout keys on `as` and shows `close` by default; pass
|
|
43
43
|
* `showOHLC` for the full four-pill quote.
|
|
44
44
|
*
|
|
45
45
|
* ```tsx
|
|
@@ -94,8 +94,8 @@ export function Candlestick({ series, open = 'open', high = 'high', low = 'low',
|
|
|
94
94
|
// The readout reads the candle **under the cursor** (containment span,
|
|
95
95
|
// not nearest-by-begin), anchored at the slot centre. Outside every
|
|
96
96
|
// candle → no readout. No `cursorFlag`: the samples flow through the
|
|
97
|
-
// normal per-series tracker path
|
|
98
|
-
//
|
|
97
|
+
// normal per-series tracker path (BoxPlot's cursorFlag takes its
|
|
98
|
+
// values out of the per-series dots and chips).
|
|
99
99
|
if (ohlc.length === 0)
|
|
100
100
|
return [];
|
|
101
101
|
const i = ohlcIndexAtTime(ohlc, time);
|