@pond-ts/charts 0.58.0 → 0.59.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 +576 -0
- package/CHANGELOG.md +144 -1
- package/dist/BarChart.d.ts +56 -7
- package/dist/BarChart.js +82 -12
- package/dist/ChartRow.js +90 -2
- package/dist/YAxis.d.ts +58 -2
- package/dist/YAxis.js +3 -1
- package/dist/bars.d.ts +10 -3
- package/dist/bars.js +13 -9
- package/dist/context.d.ts +11 -6
- package/dist/data.d.ts +38 -0
- package/dist/data.js +43 -0
- package/dist/format.d.ts +15 -0
- package/dist/format.js +16 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -0
- package/dist/range.d.ts +14 -1
- package/dist/range.js +24 -3
- package/dist/theme.d.ts +58 -4
- package/dist/yticks.d.ts +3 -0
- package/dist/yticks.js +104 -0
- package/package.json +6 -5
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.59.0...HEAD
|
|
12
|
+
[0.59.0]: https://github.com/pond-ts/pond/compare/v0.58.0...v0.59.0
|
|
12
13
|
[0.58.0]: https://github.com/pond-ts/pond/compare/v0.57.0...v0.58.0
|
|
13
14
|
[0.57.0]: https://github.com/pond-ts/pond/compare/v0.56.2...v0.57.0
|
|
14
15
|
[0.56.2]: https://github.com/pond-ts/pond/compare/v0.56.1...v0.56.2
|
|
@@ -61,6 +62,148 @@ include new features and type-level changes; patch bumps are strictly additive.
|
|
|
61
62
|
|
|
62
63
|
## [Unreleased]
|
|
63
64
|
|
|
65
|
+
## [0.59.0] — 2026-08-11
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **charts: `<YAxis scale="symlog">` — linear through zero, logarithmic beyond**
|
|
70
|
+
([PND-SYMLOG]). The third `scale` kind, for a **diverging** measure spanning
|
|
71
|
+
orders of magnitude on both sides of zero. `scale="log"` cannot express that
|
|
72
|
+
domain at all (no zero, no negatives) and `scale="linear"` flattens everything
|
|
73
|
+
outside the top decade onto the axis line — so the small and mid-range values,
|
|
74
|
+
usually the finding, become unreadable.
|
|
75
|
+
|
|
76
|
+
The knee is set by the new **`linearWindow`** prop as a _fraction of the
|
|
77
|
+
domain's largest magnitude_ (default `0.02`): on a ±1M domain the axis is
|
|
78
|
+
linear through ±20k and logarithmic beyond. Relative rather than absolute so
|
|
79
|
+
it survives a domain change with no arithmetic at the call site. Values are
|
|
80
|
+
strictly monotonic across the knee, and zero has a real position. A fraction
|
|
81
|
+
outside `(0, 1]` is unusable as a knee, so the axis draws with the default and
|
|
82
|
+
dev-warns which window is in force.
|
|
83
|
+
|
|
84
|
+
**The tick ladder is pond's, not d3's.** `scaleSymlog` supplies the transform
|
|
85
|
+
but ticks it _linearly_, which puts every label in the top decade and none in
|
|
86
|
+
the linear window the scale exists to open up. `<YAxis scale="symlog">` grids
|
|
87
|
+
zero, ±the knee, and mirrored decades beyond it, thinned to the tick budget
|
|
88
|
+
the same way the log path thins its decades, clipped to the domain. When
|
|
89
|
+
`linearWindow` swallows the domain there is nothing left to grid
|
|
90
|
+
logarithmically, and the axis defers to the linear ticks — which is correct,
|
|
91
|
+
not a fallback: inside the knee, symlog _is_ linear.
|
|
92
|
+
|
|
93
|
+
It removes a workaround whose cost was **silence**: pre-transforming values
|
|
94
|
+
into a ±1 plot space with a linear axis pinned to `[-1, 1]` leaves tick
|
|
95
|
+
positions in plot space while their labels must read in real units, so
|
|
96
|
+
computing the two by different routes yields a chart that confidently labels
|
|
97
|
+
positions it does not occupy — no exception, no visual artifact.
|
|
98
|
+
|
|
99
|
+
**If you are replacing a hand-rolled curve, the shape will shift.** `symlog` is
|
|
100
|
+
the single smooth `sign(x) · log1p(|x / knee|)`, not two joined segments; a
|
|
101
|
+
hand-rolled curve that is exactly linear below the knee and `log10` above is the
|
|
102
|
+
same family with a different shape. Migrating one, a consumer measured small
|
|
103
|
+
values at **roughly half** their former height (on a ±9M domain, 283k moved from
|
|
104
|
+
0.44 to 0.24 of the half-plot above the zero line) with order, tail dominance and
|
|
105
|
+
the several-fold lift over a linear axis all preserved. No `linearWindow` recovers the piecewise shape — the
|
|
106
|
+
difference is the curve, not the knee.
|
|
107
|
+
|
|
108
|
+
- **charts: `<BarChart maxBarWidth>` — cap a bar's ink independently of its slot**
|
|
109
|
+
([PND-BARWIDTH]). Applied after the `gap` inset and centred in the slot, with
|
|
110
|
+
`theme.bar[as].maxWidth` as the fallback (the same relationship `gap` has) and
|
|
111
|
+
uncapped when neither is set.
|
|
112
|
+
|
|
113
|
+
It is the **absolute** half of the width vocabulary. `gap` is _relative_, so
|
|
114
|
+
with it alone bar width is always `slot - gap` and fattens as the plot widens
|
|
115
|
+
— and a fixed ink width is what makes a measure comparable **between** panes,
|
|
116
|
+
since bars that widen with their pane read as different weights of the same
|
|
117
|
+
thing. Neither existing spelling expresses "spread the slots, pin the bar":
|
|
118
|
+
`maxBandWidth = barWidth + gap` pins the bar but stops the slots spreading,
|
|
119
|
+
and `maxBandWidth = slotCap` spreads them but lets the bar grow. The
|
|
120
|
+
workaround was to compute `gap` from the band width you predicted the library
|
|
121
|
+
would pick — a re-derivation of pond's layout arithmetic in consumer code,
|
|
122
|
+
which goes silently wrong the moment that rule changes on either side.
|
|
123
|
+
|
|
124
|
+
Pairs with `<ChartContainer maxBandWidth>` (which caps the **slot**) and
|
|
125
|
+
`minWidth` still wins if the two bounds would invert. **A single-series bar's
|
|
126
|
+
hit target stays its whole slot**, so narrow ink costs nothing in clickability;
|
|
127
|
+
on a **stacked** chart the cap does narrow the target, because a stack must
|
|
128
|
+
hit-test its drawn segment rect to resolve which segment.
|
|
129
|
+
|
|
130
|
+
- **charts: `<BarChart categories columns>` — a first-class stacked category
|
|
131
|
+
chart** ([PND-CATSTACK]). Each datum is `{ label, values }` and `columns`
|
|
132
|
+
names the groups to stack bottom → top, the same relationship
|
|
133
|
+
`series` + `columns` already has. New `categoryStacks` reader and
|
|
134
|
+
`CategoryStackDatum` type; geometry, `marks` and the categorical axis are
|
|
135
|
+
unchanged from the single-value case, so this reaches the shipped
|
|
136
|
+
`drawStacks` path with no new draw code. A missing or non-finite group reads
|
|
137
|
+
as a **gap**, not a zero.
|
|
138
|
+
|
|
139
|
+
**It removes a workaround with three costs**, the third only visible since
|
|
140
|
+
0.58.0: composing the picture from one `categories` layer per _cumulative
|
|
141
|
+
total_ (drawn outermost-first so each overpaints the one beneath) meant a
|
|
142
|
+
hand-assembled legend, label thinning blind to the sibling layers, and — because
|
|
143
|
+
a selection entry keys on `(layer id, mark)` — a controlled set replicated
|
|
144
|
+
across every segment layer, where missing one made a selected bar recede
|
|
145
|
+
**from the waist up**. Because `marks` is indexed by **bin**, one entry naming
|
|
146
|
+
`(id, mark)` now matches every segment of a bar, so that failure is not
|
|
147
|
+
expressible rather than merely fixed.
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
|
|
151
|
+
- **charts: a selected segment of a stack with `colors` no longer collapses to
|
|
152
|
+
the flat `highlight`.** `StackStyle.groupColored` — the "a selected segment
|
|
153
|
+
keeps its own fill" exclusion — was gated on the _theme ramp_ having painted
|
|
154
|
+
the stack, so a call site passing `colors` lost it and both segments of a
|
|
155
|
+
selected bar went one `highlight` blue, losing the segment distinction exactly
|
|
156
|
+
where the reader is looking. The gate's stated reason ("a ramp entry the call
|
|
157
|
+
site overrode is no longer the ramp's colour, so its receded counterpart would
|
|
158
|
+
be wrong") applies to the _derived_ companions `dimmedFills` / `hoverFills`,
|
|
159
|
+
which must invent a per-group colour; `groupColored` derives nothing. It now
|
|
160
|
+
gates on **whether the resolved fills actually differ**, so a `colors` map keeps
|
|
161
|
+
its colours under selection, while a multi-group stack under a theme that gives
|
|
162
|
+
its groups no distinct colours at all (no ramp, no roles, no `colors` — e.g.
|
|
163
|
+
`estelaTheme`) still takes the themed `highlight`, because there is no
|
|
164
|
+
meaning-carrying colour there to preserve and suppressing the highlight would
|
|
165
|
+
leave selection invisible. Found building [PND-CATSTACK], where the old gate made
|
|
166
|
+
the first-class stack render _worse_ under selection than the workaround it
|
|
167
|
+
replaces; the second half was found in review, since every story and test renders
|
|
168
|
+
`defaultTheme`, whose ramp hides the difference.
|
|
169
|
+
|
|
170
|
+
- **all packages: `API.md` now ships inside the npm tarball.** The agent-facing
|
|
171
|
+
map of every public export across the six packages — one line per export with
|
|
172
|
+
its purpose and source path — was repo-only, so an agent working in a
|
|
173
|
+
_consuming_ repo had to crawl `node_modules/*/dist/*.d.ts` or go to the
|
|
174
|
+
network to learn the surface. It is now copied in by each package's existing
|
|
175
|
+
`prepack` (the same mechanism that already ships `README`, `LICENSE` and
|
|
176
|
+
`CHANGELOG`) and listed in `files`. ~69kB per tarball.
|
|
177
|
+
|
|
178
|
+
Every package carries the same **monorepo-wide** copy rather than a
|
|
179
|
+
per-package slice, deliberately: the packages compose, and knowing what is
|
|
180
|
+
next door is most of the value. The header now names its audience and
|
|
181
|
+
resolves repo-relative source paths against GitHub, since inside
|
|
182
|
+
`node_modules` a bare `packages/core/src/…` points nowhere.
|
|
183
|
+
|
|
184
|
+
- **charts: `BarStyle.dimmed`'s precedence over per-bar and per-band colour is
|
|
185
|
+
documented, and pinned.** A consumer migrating onto 0.58.0 read
|
|
186
|
+
`BarStyle.hover`'s documented `binColors` exclusion ("pops each bar's _own_
|
|
187
|
+
fill"), reasonably generalized it to `dimmed`, concluded their `binColors` and
|
|
188
|
+
`thresholds` charts would get no de-emphasis, and was about to hand-dim inside
|
|
189
|
+
their own colour arrays. The opposite is true: **an unselected bar takes
|
|
190
|
+
`dimmed`, discarding its per-bar colour, and a banded bar draws flat rather
|
|
191
|
+
than dimming each band.** The asymmetry is deliberate — emphasis preserves a
|
|
192
|
+
per-bar colour because that colour is what the value means, while a receded
|
|
193
|
+
bar's job is to stop competing over meaning — but only `hover` said anything,
|
|
194
|
+
so generalizing was the natural read. `dimmed` now spells out all three paths
|
|
195
|
+
(`binColors`/`binFills`, bands/thresholds, and the per-group stack fallback
|
|
196
|
+
through `StackStyle.dimmedFills`), `hover` scopes its exclusion to the live
|
|
197
|
+
states, and three tests pin the behaviour.
|
|
198
|
+
|
|
199
|
+
- **charts: the `theme.list` register no longer reads as if it carries
|
|
200
|
+
`dimmed`.** Its doc mentioned `highlight`/`dimmed` while explaining that a
|
|
201
|
+
list resolves glyph state through the bar tokens — accurate, but sitting in a
|
|
202
|
+
sentence about per-metric resolution it read as a field list, and cost the
|
|
203
|
+
same consumer a couple of passes to rule out a missing `list.dimmed`. Now
|
|
204
|
+
states explicitly that those are `BarStyle` tokens resolved via
|
|
205
|
+
`theme.bar[as]`, and that this register carries exactly its five values.
|
|
206
|
+
|
|
64
207
|
## [0.58.0] — 2026-08-10
|
|
65
208
|
|
|
66
209
|
### Added
|
package/dist/BarChart.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValueSeries } from 'pond-ts';
|
|
2
2
|
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
3
|
-
import { type BinRecord, type CategoryDatum } from './data.js';
|
|
3
|
+
import { type BinRecord, type CategoryDatum, type CategoryStackDatum } from './data.js';
|
|
4
4
|
import { type Orientation } from './bars.js';
|
|
5
5
|
import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
6
6
|
import type { DecimateOption } from './decimate.js';
|
|
@@ -43,11 +43,17 @@ import type { DecimateOption } from './decimate.js';
|
|
|
43
43
|
* `<ChartContainer origin>` does not rescue it: it relabels a value axis but
|
|
44
44
|
* does not re-ladder it.
|
|
45
45
|
* - **`categories`** — an ordered `{ label, value }[]`, one bar per category.
|
|
46
|
-
* Takes
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* Takes no `column`. Vertical puts the categories on the ordinal **x** axis
|
|
47
|
+
* (the container's band scale); `orientation="horizontal"` puts them on **y**
|
|
48
|
+
* as unit slots and the value on x, and a `<YAxis>` with no explicit `ticks`
|
|
49
|
+
* labels one per category automatically ([PND-HCAT]).
|
|
50
|
+
* - **`categories` + `columns`** — a **stacked** category chart
|
|
51
|
+
* ([PND-CATSTACK]): each datum is `{ label, values }` and `columns` names the
|
|
52
|
+
* groups to stack, bottom → top. The same relationship `series` + `columns`
|
|
53
|
+
* already has, so a category stack is now a first-class shape rather than one
|
|
54
|
+
* `categories` layer per cumulative total. One layer means one `mark` per
|
|
55
|
+
* bar, so **one selection entry lights the whole bar** and the composed
|
|
56
|
+
* workaround's per-layer replication is unnecessary.
|
|
51
57
|
*
|
|
52
58
|
* **Live charts:** `series.byValue(…)` / `.toMap()` mint fresh objects each
|
|
53
59
|
* call, so an inline `series={…}` re-registers this layer every render — on a
|
|
@@ -95,6 +101,12 @@ type BarChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSerie
|
|
|
95
101
|
bins?: never;
|
|
96
102
|
column?: never;
|
|
97
103
|
columns?: never;
|
|
104
|
+
} | {
|
|
105
|
+
categories: readonly CategoryStackDatum[];
|
|
106
|
+
columns: readonly string[];
|
|
107
|
+
series?: never;
|
|
108
|
+
bins?: never;
|
|
109
|
+
column?: never;
|
|
98
110
|
};
|
|
99
111
|
/** The props every {@link BarChartSource} mode shares. */
|
|
100
112
|
export interface BarChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
@@ -230,6 +242,43 @@ export interface BarChartCommon<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
230
242
|
* would invert collapses to the style's `minWidth`.
|
|
231
243
|
*/
|
|
232
244
|
gap?: number;
|
|
245
|
+
/**
|
|
246
|
+
* Cap on a bar's **ink** width in px — applied after the `gap` inset and
|
|
247
|
+
* centred in its slot ([PND-BARWIDTH]). **Omitted ⇒ the theme's `bar`
|
|
248
|
+
* `maxWidth`, and uncapped if that is unset too** (a bar is `slot - gap`
|
|
249
|
+
* wide, as it always was).
|
|
250
|
+
*
|
|
251
|
+
* This is the *absolute* half of the width vocabulary. `gap` is **relative**,
|
|
252
|
+
* so with it alone bar width is always `slot - gap` and fattens as the plot
|
|
253
|
+
* widens; a fixed ink width is what makes a measure comparable **between**
|
|
254
|
+
* panes, since bars that widen with their pane read as different weights of
|
|
255
|
+
* the same thing.
|
|
256
|
+
*
|
|
257
|
+
* Pairs with `<ChartContainer maxBandWidth>`: that caps the **slot** (how far
|
|
258
|
+
* the bars spread), this caps the **ink** inside whatever slot results. The
|
|
259
|
+
* two are independent, which is the point — neither spelling alone expresses
|
|
260
|
+
* "spread the slots, pin the bar":
|
|
261
|
+
*
|
|
262
|
+
* - `maxBandWidth = barWidth + gap` pins the bar but stops the slots
|
|
263
|
+
* spreading;
|
|
264
|
+
* - `maxBandWidth = slotCap` spreads them but lets the bar grow.
|
|
265
|
+
*
|
|
266
|
+
* `theme.bar[as].minWidth` still wins if the two bounds would invert, so the
|
|
267
|
+
* rect can never flip.
|
|
268
|
+
*
|
|
269
|
+
* **On a stacked chart the cap narrows the hit target too**, because a stack
|
|
270
|
+
* hit-tests its drawn segment rect (it must, to resolve *which* segment).
|
|
271
|
+
* A single-series bar is unaffected: it hit-tests its whole slot, so the ink
|
|
272
|
+
* can be narrow while the target stays full width.
|
|
273
|
+
*
|
|
274
|
+
* That holds in **both orientations**, and costs a deliberate guard to keep:
|
|
275
|
+
* a single-series *horizontal* chart shares the oriented draw/hit path with
|
|
276
|
+
* stacks, so the cap is withheld from its hit rect explicitly (`groups.length >
|
|
277
|
+
* 1`). Vertical charts get it for free — `barSlotRect` takes no cap. Don't
|
|
278
|
+
* "simplify" that guard away: the rule follows from segment disambiguation,
|
|
279
|
+
* which is a property of a stack, not of an axis.
|
|
280
|
+
*/
|
|
281
|
+
maxBarWidth?: number;
|
|
233
282
|
/**
|
|
234
283
|
* **M4 column decimation** (charts decimator wave). **Omitted ⇒ `true`**: once
|
|
235
284
|
* the visible bars are denser than ~2 per device pixel (each slot < ~1px), the
|
|
@@ -319,6 +368,6 @@ export type BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends Valu
|
|
|
319
368
|
* </Layers>
|
|
320
369
|
* ```
|
|
321
370
|
*/
|
|
322
|
-
export declare function BarChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, bins, categories, column, columns, as: semantic, colors, binColors, thresholds, bandColors, orientation, ordinal, id, axis, gap, decimate, legend, index, }: BarChartProps<S, VS>): null;
|
|
371
|
+
export declare function BarChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, bins, categories, column, columns, as: semantic, colors, binColors, thresholds, bandColors, orientation, ordinal, id, axis, gap, maxBarWidth, decimate, legend, index, }: BarChartProps<S, VS>): null;
|
|
323
372
|
export {};
|
|
324
373
|
//# sourceMappingURL=BarChart.d.ts.map
|
package/dist/BarChart.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { Interval, ValueSeries } from 'pond-ts';
|
|
3
|
-
import { barsFromTimeSeries, barsFromBins, barsFromValueSeries, categoryStack, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
|
|
3
|
+
import { barsFromTimeSeries, barsFromBins, barsFromValueSeries, categoryStack, categoryStacks, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
|
|
4
4
|
import { barAt, barExtent, barIndexAtTime, drawBars, drawStacks, normalizeThresholds, resolveBarBaseline, stackAt, stackBinExtent, stackValueExtent, } from './bars.js';
|
|
5
5
|
import { spansForLayer } from './span.js';
|
|
6
6
|
import { isDev } from './dev.js';
|
|
@@ -60,7 +60,7 @@ const EMPTY_MARKS = [];
|
|
|
60
60
|
* </Layers>
|
|
61
61
|
* ```
|
|
62
62
|
*/
|
|
63
|
-
export function BarChart({ series, bins, categories, column, columns, as: semantic, colors, binColors, thresholds, bandColors, orientation = 'vertical', ordinal = false, id, axis, gap, decimate = true, legend, index = 0, }) {
|
|
63
|
+
export function BarChart({ series, bins, categories, column, columns, as: semantic, colors, binColors, thresholds, bandColors, orientation = 'vertical', ordinal = false, id, axis, gap, maxBarWidth, decimate = true, legend, index = 0, }) {
|
|
64
64
|
const container = useContext(ContainerContext);
|
|
65
65
|
if (container === null) {
|
|
66
66
|
throw new Error('<BarChart> must be rendered inside a <ChartContainer>');
|
|
@@ -78,8 +78,11 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
78
78
|
throw new Error('<BarChart> needs exactly one of `series`, `bins`, or `categories`');
|
|
79
79
|
}
|
|
80
80
|
if (categories !== undefined) {
|
|
81
|
-
if (column !== undefined
|
|
82
|
-
throw new Error('<BarChart categories> takes no `column
|
|
81
|
+
if (column !== undefined) {
|
|
82
|
+
throw new Error('<BarChart categories> takes no `column` — a single-value datum carries its own `value`, and a stacked one names its groups with `columns`');
|
|
83
|
+
}
|
|
84
|
+
if (columns !== undefined && columns.length === 0) {
|
|
85
|
+
throw new Error('<BarChart categories> with `columns` needs at least one group name');
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
const isMap = series instanceof Map;
|
|
@@ -105,10 +108,21 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
105
108
|
// horizontal bar) so one oriented draw path covers it.
|
|
106
109
|
const shape = useMemo(() => {
|
|
107
110
|
if (categories !== undefined) {
|
|
108
|
-
// Categorical row-read: one unit-slot bar per category
|
|
109
|
-
//
|
|
111
|
+
// Categorical row-read: one unit-slot bar per category, drawn on the
|
|
112
|
+
// container's band scale. The reused stacked geometry — only the axis
|
|
110
113
|
// (band scale + labels) is new.
|
|
111
|
-
|
|
114
|
+
//
|
|
115
|
+
// With `columns` it is a real multi-group stack ([PND-CATSTACK]): same
|
|
116
|
+
// slots, same `marks`, `G > 1`. Because `marks` is indexed by BIN, one
|
|
117
|
+
// selection entry naming `(id, mark)` matches every segment of a bar —
|
|
118
|
+
// which is the property that made the hand-composed workaround's
|
|
119
|
+
// "recedes from the waist up" bug inexpressible here.
|
|
120
|
+
return {
|
|
121
|
+
kind: 'stacked',
|
|
122
|
+
ss: columns !== undefined
|
|
123
|
+
? categoryStacks(categories, columns)
|
|
124
|
+
: categoryStack(categories),
|
|
125
|
+
};
|
|
112
126
|
}
|
|
113
127
|
if (bins !== undefined) {
|
|
114
128
|
const cols = columns ?? (column !== undefined ? [column] : undefined);
|
|
@@ -250,6 +264,19 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
250
264
|
// The stacked path's bar-thickness floor comes from `bar.default` (not the `as`
|
|
251
265
|
// role — `as` is single-series only), matching how `gapPx` sources its default.
|
|
252
266
|
const stackMinWidth = bar.default.minWidth;
|
|
267
|
+
// Same sourcing as `gapPx`: the prop wins, else the theme token, else
|
|
268
|
+
// uncapped ([PND-BARWIDTH]). `bar.default` rather than the `as` role for the
|
|
269
|
+
// stacked ceiling, for the reason above.
|
|
270
|
+
const maxWidthPx = maxBarWidth ?? bar.default.maxWidth;
|
|
271
|
+
// The single-series draw takes a `BarStyle` straight from the theme, so the
|
|
272
|
+
// prop override is applied by shadowing the token — same relationship `gap`
|
|
273
|
+
// has, and with the prop absent the role's own `maxWidth` (if any) already
|
|
274
|
+
// rides along untouched. Only the INK path gets this: `barSlotRect` (the hit
|
|
275
|
+
// region) stays the whole slot, so a narrow capped bar keeps a full-width
|
|
276
|
+
// target — the deliberate ink/hit split `gapPx` already relies on.
|
|
277
|
+
const singleDrawStyle = useMemo(() => maxBarWidth !== undefined
|
|
278
|
+
? { ...singleStyle, maxWidth: maxBarWidth }
|
|
279
|
+
: singleStyle, [singleStyle, maxBarWidth]);
|
|
253
280
|
// ── Threshold ladder ([PND-BANDBAR2]) ────────────────────────────────────
|
|
254
281
|
// Resolved once here rather than per bar per frame: normalize the breakpoints
|
|
255
282
|
// (sort, drop non-finite), then pair them with `bandColors` → the role's
|
|
@@ -351,11 +378,38 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
351
378
|
(ramp !== undefined ? at(ramp, i) : base.fill));
|
|
352
379
|
// A ramp entry the call site overrode is no longer the ramp's colour, so
|
|
353
380
|
// its receded counterpart would be wrong — the whole ramp only means
|
|
354
|
-
// anything when it is the ramp that painted it.
|
|
381
|
+
// anything when it is the ramp that painted it. This gates the *derived*
|
|
382
|
+
// companions (`dimmedFills` / `hoverFills`), which need a counterpart per
|
|
383
|
+
// group and cannot invent one for an arbitrary call-site colour.
|
|
355
384
|
const ramped = ramp !== undefined && colors === undefined;
|
|
385
|
+
// `groupColored` derives nothing — it only says "a selected segment keeps
|
|
386
|
+
// its own fill rather than taking the flat `highlight`", because the colour
|
|
387
|
+
// is meaning-carrying.
|
|
388
|
+
//
|
|
389
|
+
// So the condition is exactly **"do the fills actually differ"**, read off
|
|
390
|
+
// the resolved `fills` rather than inferred from anything upstream of them.
|
|
391
|
+
// Both cheaper inferences are wrong, and each was shipped in turn:
|
|
392
|
+
//
|
|
393
|
+
// - Gating on `ramped` (the ramp painted it) meant a stack with `colors`
|
|
394
|
+
// collapsed BOTH segments of a selected bar to one `highlight` blue —
|
|
395
|
+
// losing the segment distinction exactly where the reader is looking. A
|
|
396
|
+
// call site naming its groups' colours is *more* deliberate than a
|
|
397
|
+
// fallback ramp, not less. Found building [PND-CATSTACK].
|
|
398
|
+
// - Gating on `groups.length > 1` (my fix for that) is wrong in the other
|
|
399
|
+
// direction: a multi-group stack under a theme with **no** group ramp, no
|
|
400
|
+
// `colors` and no per-group roles resolves every fill to `base.fill`, so
|
|
401
|
+
// claiming the colour carries meaning suppresses the highlight and leaves
|
|
402
|
+
// *nothing* — selection becomes invisible. `estelaTheme` is exactly that
|
|
403
|
+
// theme, and it ships. Found by Layer-2 review, which is the only way it
|
|
404
|
+
// could have been: every story and test renders `defaultTheme`, whose ramp
|
|
405
|
+
// makes the two gates indistinguishable.
|
|
406
|
+
//
|
|
407
|
+
// Reading `fills` also handles the degenerate `colors` map that assigns one
|
|
408
|
+
// colour to every group: nothing is distinguished, so the highlight applies.
|
|
409
|
+
const groupColoured = new Set(fills).size > 1;
|
|
356
410
|
return {
|
|
357
411
|
fills,
|
|
358
|
-
...(
|
|
412
|
+
...(groupColoured ? { groupColored: true } : {}),
|
|
359
413
|
...(ramped && rampDim !== undefined
|
|
360
414
|
? {
|
|
361
415
|
dimmedFills: (groups ?? []).map((_g, i) => at(rampDim, i)),
|
|
@@ -380,9 +434,10 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
380
434
|
? { emphasisOpacity: base.emphasisOpacity }
|
|
381
435
|
: {}),
|
|
382
436
|
...(base.dimmed !== undefined ? { dimmed: base.dimmed } : {}),
|
|
437
|
+
...(maxWidthPx !== undefined ? { maxWidth: maxWidthPx } : {}),
|
|
383
438
|
...(binColors !== undefined ? { binFills: binColors } : {}),
|
|
384
439
|
};
|
|
385
|
-
}, [bar, groups, colors, binColors]);
|
|
440
|
+
}, [bar, groups, colors, binColors, maxWidthPx]);
|
|
386
441
|
// The current selection / hover, narrowed to the identity the highlight match
|
|
387
442
|
// needs. For a stack that's (id, key, label = group); the single path uses just
|
|
388
443
|
// (id, key). Read here so a change re-registers the layer → the canvas repaints.
|
|
@@ -525,7 +580,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
525
580
|
},
|
|
526
581
|
}),
|
|
527
582
|
}),
|
|
528
|
-
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale,
|
|
583
|
+
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale, singleDrawStyle, resolveBarBaseline(yScale), gapPx, id, selection, hover, decimate, binColors, bandLadder, layerSpans),
|
|
529
584
|
},
|
|
530
585
|
axisId: axis,
|
|
531
586
|
index,
|
|
@@ -563,7 +618,20 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
563
618
|
? {}
|
|
564
619
|
: {
|
|
565
620
|
hitTest: (px, py, xScale, yScale) => {
|
|
566
|
-
|
|
621
|
+
// The cap reaches the hit rect ONLY for a real stack, where the
|
|
622
|
+
// rect is what resolves *which segment* was hit and so must be
|
|
623
|
+
// the drawn one. A single-series chart has one segment per slot
|
|
624
|
+
// and nothing to disambiguate, so narrowing its target buys
|
|
625
|
+
// nothing and costs clickability.
|
|
626
|
+
//
|
|
627
|
+
// This path serves single-series **horizontal** charts as well as
|
|
628
|
+
// stacks (see the branch comment above), which is how the prop's
|
|
629
|
+
// documented guarantee — "a single-series bar hit-tests its whole
|
|
630
|
+
// slot" — was true only of vertical ones. Found by Layer-2 review;
|
|
631
|
+
// the fix is to make the guarantee orientation-independent rather
|
|
632
|
+
// than to narrow the claim, since the reason for the split is
|
|
633
|
+
// segment disambiguation and that is a property of the *stack*.
|
|
634
|
+
const hit = stackAt(ss, px, py, orientation, xScale, yScale, gapPx, stackMinWidth, ss.groups.length > 1 ? maxWidthPx : undefined);
|
|
567
635
|
if (hit === null)
|
|
568
636
|
return null;
|
|
569
637
|
const [bi, g, begin, name, value] = hit;
|
|
@@ -656,6 +724,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
656
724
|
categoryLabels,
|
|
657
725
|
orientation,
|
|
658
726
|
singleStyle,
|
|
727
|
+
singleDrawStyle,
|
|
659
728
|
stackStyle,
|
|
660
729
|
binColors,
|
|
661
730
|
bandLadder,
|
|
@@ -664,6 +733,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
664
733
|
gapPx,
|
|
665
734
|
decimate,
|
|
666
735
|
stackMinWidth,
|
|
736
|
+
maxWidthPx,
|
|
667
737
|
selection,
|
|
668
738
|
hover,
|
|
669
739
|
layerSpans,
|
package/dist/ChartRow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Children, Fragment, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
-
import { scaleLinear, scaleLog } from 'd3-scale';
|
|
3
|
+
import { scaleLinear, scaleLog, scaleSymlog } from 'd3-scale';
|
|
4
4
|
import { isDev } from './dev.js';
|
|
5
5
|
import { useIndexedChildren } from './child-index.js';
|
|
6
6
|
import { logAxisWarning, needsExtents, resolveYDomain } from './domain.js';
|
|
@@ -11,6 +11,41 @@ import { useSlotKey } from './use-slot-key.js';
|
|
|
11
11
|
import { LegacyCursor } from './cursors.js';
|
|
12
12
|
import { YAxis } from './YAxis.js';
|
|
13
13
|
import { ContainerContext, RowContext, } from './context.js';
|
|
14
|
+
/**
|
|
15
|
+
* `scale="symlog"`'s default linear window — the knee at **2% of the domain's
|
|
16
|
+
* largest magnitude** ([PND-SYMLOG]). Chosen because it is what the reporting
|
|
17
|
+
* consumer's own transform used (`maxAbs / 50`), and confirmed with them as
|
|
18
|
+
* generalizing: every caller they had passed the data's own max-abs, and none
|
|
19
|
+
* had a case for an absolute constant.
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_LINEAR_WINDOW = 0.02;
|
|
22
|
+
/**
|
|
23
|
+
* `scaleSymlog`'s `constant` (the linear window's half-width in data units) for
|
|
24
|
+
* an axis's domain-relative {@link YAxisProps.linearWindow} ([PND-SYMLOG]).
|
|
25
|
+
*
|
|
26
|
+
* **The clamp is the point.** d3's symlog transform is
|
|
27
|
+
* `sign(x)·log1p(|x / constant|)`, so a `constant` of `0` — or of
|
|
28
|
+
* `Number.MIN_VALUE`, which the first version of this "clamped" to — divides
|
|
29
|
+
* every sample by ~zero, and `(∞ − ∞) / (∞ − ∞)` makes **every mapped pixel
|
|
30
|
+
* `NaN`**: a blank plot, `NaN` gridline coordinates, no error. That is strictly
|
|
31
|
+
* worse than the mistake it was guarding, so an unusable fraction (non-finite,
|
|
32
|
+
* `<= 0`, or `> 1`) falls back to the **default** and dev-warns (see the
|
|
33
|
+
* `linearWindow` diagnostics below) rather than being nudged to a value that
|
|
34
|
+
* technically satisfies `> 0`.
|
|
35
|
+
*
|
|
36
|
+
* A degenerate all-zero domain has no magnitude to take a fraction *of*, so the
|
|
37
|
+
* knee falls back to `1`; the axis is linear across it either way.
|
|
38
|
+
*/
|
|
39
|
+
function symlogConstant(linearWindow, lo, hi) {
|
|
40
|
+
const usable = linearWindow !== undefined &&
|
|
41
|
+
Number.isFinite(linearWindow) &&
|
|
42
|
+
linearWindow > 0 &&
|
|
43
|
+
linearWindow <= 1;
|
|
44
|
+
const fraction = usable ? linearWindow : DEFAULT_LINEAR_WINDOW;
|
|
45
|
+
const maxAbs = Math.max(Math.abs(lo), Math.abs(hi));
|
|
46
|
+
const knee = fraction * maxAbs;
|
|
47
|
+
return Number.isFinite(knee) && knee > 0 ? knee : 1;
|
|
48
|
+
}
|
|
14
49
|
/** Sentinel id for the implicit axis a row gets when no `<YAxis>` is declared. */
|
|
15
50
|
const IMPLICIT_AXIS_ID = '__default__';
|
|
16
51
|
/** Element-wise compare of two optional number arrays (an axis's tick values) —
|
|
@@ -43,6 +78,10 @@ function axisSpecEqual(a, b) {
|
|
|
43
78
|
a.side === b.side &&
|
|
44
79
|
a.width === b.width &&
|
|
45
80
|
a.scale === b.scale &&
|
|
81
|
+
// Easy to forget when adding a scale-shaping field, and the failure is
|
|
82
|
+
// silent: an axis whose `linearWindow` alone changed would be discarded by
|
|
83
|
+
// the guard and keep drawing with the previous knee.
|
|
84
|
+
a.linearWindow === b.linearWindow &&
|
|
46
85
|
// Object.is (not ===) so a degenerate NaN bound compares equal to itself and
|
|
47
86
|
// doesn't re-register every render.
|
|
48
87
|
Object.is(a.min, b.min) &&
|
|
@@ -253,7 +292,16 @@ export function ChartRow({ height, cursor, children }) {
|
|
|
253
292
|
// `scaleLog` and `scaleLinear` share the call/ticks/tickFormat/invert
|
|
254
293
|
// surface every consumer uses (see `YScale`), so choosing between them
|
|
255
294
|
// here is the whole of log support — no draw layer branches on it.
|
|
256
|
-
|
|
295
|
+
// `scaleSymlog` shares the same call/ticks/tickFormat/invert surface, so
|
|
296
|
+
// as with log, choosing it here is the whole of symlog support — no draw
|
|
297
|
+
// layer branches on it. Its `constant` (the linear window's half-width) is
|
|
298
|
+
// resolved from the axis's DOMAIN-RELATIVE fraction: absolute would need
|
|
299
|
+
// recomputing whenever the domain moved ([PND-SYMLOG]).
|
|
300
|
+
const base = ax.scale === 'log'
|
|
301
|
+
? scaleLog()
|
|
302
|
+
: ax.scale === 'symlog'
|
|
303
|
+
? scaleSymlog().constant(symlogConstant(ax.linearWindow, lo, hi))
|
|
304
|
+
: scaleLinear();
|
|
257
305
|
const s = base.domain([lo, hi]).range([height, topHeader]);
|
|
258
306
|
// 2-D pan/zoom is carried as a **pixel** transform (`k`, `ty`) so one
|
|
259
307
|
// gesture serves every axis in the row whatever its units, and all of them
|
|
@@ -319,6 +367,46 @@ export function ChartRow({ height, cursor, children }) {
|
|
|
319
367
|
}
|
|
320
368
|
}
|
|
321
369
|
}, [effectiveAxes, layerList, defaultAxisId]);
|
|
370
|
+
// Dev-mode diagnostics for `linearWindow` ([PND-SYMLOG]). Both cases it covers
|
|
371
|
+
// are *silent* otherwise, which is the whole reason it exists: a
|
|
372
|
+
// `linearWindow` on a linear or log axis is read by nothing, and a fraction
|
|
373
|
+
// outside `(0, 1]` is unusable as a knee (see `symlogConstant`), so the axis
|
|
374
|
+
// silently draws with the **default** window instead of the one asked for.
|
|
375
|
+
// Neither throws and neither looks broken — it just isn't the scale the call
|
|
376
|
+
// site asked for.
|
|
377
|
+
//
|
|
378
|
+
// Same shape as the log diagnostics above: an effect rather than the memo, and
|
|
379
|
+
// deduped in `warnedRef` under a suffixed key so it cannot collide with the
|
|
380
|
+
// log message stored under the bare axis id.
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
if (!isDev)
|
|
383
|
+
return;
|
|
384
|
+
const warned = warnedRef.current;
|
|
385
|
+
for (const ax of effectiveAxes) {
|
|
386
|
+
const key = `${ax.id}:linearWindow`;
|
|
387
|
+
const w = ax.linearWindow;
|
|
388
|
+
let message = null;
|
|
389
|
+
if (w !== undefined && ax.scale !== 'symlog') {
|
|
390
|
+
message =
|
|
391
|
+
`<YAxis id="${ax.id}"> sets linearWindow=${w} but scale is ` +
|
|
392
|
+
`"${ax.scale}" — linearWindow only applies to scale="symlog" and is ` +
|
|
393
|
+
`ignored here.`;
|
|
394
|
+
}
|
|
395
|
+
else if (w !== undefined && (!Number.isFinite(w) || w <= 0 || w > 1)) {
|
|
396
|
+
message =
|
|
397
|
+
`<YAxis id="${ax.id}"> has linearWindow=${w}, outside (0, 1] — the ` +
|
|
398
|
+
`axis is drawing with the default ${DEFAULT_LINEAR_WINDOW} instead. ` +
|
|
399
|
+
`It is a fraction of the domain's largest magnitude, so 0.02 means ` +
|
|
400
|
+
`"linear through 2% of the domain".`;
|
|
401
|
+
}
|
|
402
|
+
if (message === null)
|
|
403
|
+
warned.delete(key);
|
|
404
|
+
else if (warned.get(key) !== message) {
|
|
405
|
+
warned.set(key, message);
|
|
406
|
+
console.warn(message);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}, [effectiveAxes]);
|
|
322
410
|
// Resolved auto-tick count per axis — explicit `<YAxis tickCount>` else
|
|
323
411
|
// height-derived (see resolveYTickCount). The single source the `<YAxis>`
|
|
324
412
|
// labels, the readout formatter (below), and the `Layers` gridlines all read,
|
package/dist/YAxis.d.ts
CHANGED
|
@@ -46,8 +46,64 @@ export interface YAxisProps {
|
|
|
46
46
|
*
|
|
47
47
|
* A dev-mode warning fires for the cases that are unambiguously a mistake: a
|
|
48
48
|
* refused bound, negative data, or an axis with no positive data at all.
|
|
49
|
+
*
|
|
50
|
+
* `'symlog'` is **linear through zero, logarithmic beyond** — for data that
|
|
51
|
+
* spans orders of magnitude *on both sides of zero*, which `'log'` cannot
|
|
52
|
+
* express at all (it admits no zero and no negatives). The linear window is
|
|
53
|
+
* {@link linearWindow}. Because it admits zero, it resolves its domain on the
|
|
54
|
+
* ordinary **linear** path: no positive-only bound refusal, no rounding out to
|
|
55
|
+
* decades, no gapping of non-positive samples.
|
|
56
|
+
*
|
|
57
|
+
* **The axis owns tick placement, and that is the substance of the feature.**
|
|
58
|
+
* d3's symlog supplies the transform but ticks it *linearly*, which on a ±1M
|
|
59
|
+
* domain with a 20k knee labels nothing below the knee — the exact region the
|
|
60
|
+
* scale was chosen to reveal. pond grids it on zero, the knee (±`linearWindow ×
|
|
61
|
+
* maxAbs`) and mirrored decades beyond, thinned by the same rule the log axis
|
|
62
|
+
* uses. See `yticks.ts`.
|
|
63
|
+
*
|
|
64
|
+
* **The curve is `log1p`, not piecewise — read this before replacing a
|
|
65
|
+
* hand-rolled one.** "Linear through zero, logarithmic beyond" describes how the
|
|
66
|
+
* axis *reads*, not two joined segments: `scaleSymlog` is the single smooth
|
|
67
|
+
* `sign(x) · log1p(|x / knee|)`, so there is no exact boundary at which one law
|
|
68
|
+
* stops and the other starts. A common hand-rolled curve *is* piecewise —
|
|
69
|
+
* exactly linear below the knee, `log10` above — and the two are the same family
|
|
70
|
+
* with materially different shape. Swapping one for the other, a reporting
|
|
71
|
+
* consumer measured small values landing at **roughly half** their former height
|
|
72
|
+
* (a ±9M domain: 283k went from 0.44 to 0.24 of the half-plot above the zero
|
|
73
|
+
* line), while order, the dominance of the tail, and a several-fold lift over a
|
|
74
|
+
* linear axis all held — the chart still says the same thing, but it does not
|
|
75
|
+
* say it identically.
|
|
76
|
+
*
|
|
77
|
+
* **No `linearWindow` recovers a piecewise shape.** The same consumer tried: a
|
|
78
|
+
* smaller window fits the large values while overshooting the small ones about
|
|
79
|
+
* 2×, because the difference is the curve, not the knee. If you need the
|
|
80
|
+
* piecewise curve exactly, you need your own transform — which is the thing this
|
|
81
|
+
* scale exists to let you delete, so weigh that before reaching for it.
|
|
82
|
+
*/
|
|
83
|
+
scale?: 'linear' | 'log' | 'symlog';
|
|
84
|
+
/**
|
|
85
|
+
* `scale="symlog"`'s **linear window**, as a fraction of the domain's largest
|
|
86
|
+
* magnitude. **Default `0.02`** — the knee sits at 2% of `maxAbs`, so a ±1M
|
|
87
|
+
* domain is linear through ±20k and logarithmic beyond. Ignored on any other
|
|
88
|
+
* scale.
|
|
89
|
+
*
|
|
90
|
+
* **Domain-relative, not absolute** (d3's own `constant` is absolute). A chart
|
|
91
|
+
* that re-keys to the largest magnitude on every update would otherwise need
|
|
92
|
+
* the constant recomputed each tick, and would drift silently the moment
|
|
93
|
+
* someone forgot — the fraction survives a domain change with no call-site
|
|
94
|
+
* arithmetic at all.
|
|
95
|
+
*
|
|
96
|
+
* Precisely: a fraction of the **resolved domain before any pan/zoom** — the
|
|
97
|
+
* one the axis's `min`/`max`/`pad`/auto-fit produce. A 2-D gesture is carried
|
|
98
|
+
* as a *pixel* transform and the knee is deliberately **not** recomputed from
|
|
99
|
+
* the zoomed window, so zooming moves the plot without moving the boundary
|
|
100
|
+
* between the two régimes underneath it. (Recomputing would make the same
|
|
101
|
+
* datum linear at one zoom level and logarithmic at the next.)
|
|
102
|
+
*
|
|
103
|
+
* A value outside `(0, 1]` cannot be a knee; the axis draws with the default
|
|
104
|
+
* instead and dev-warns which window is in force.
|
|
49
105
|
*/
|
|
50
|
-
|
|
106
|
+
linearWindow?: number;
|
|
51
107
|
/** Explicit domain bounds; omit to auto-fit the charts linked to this axis. */
|
|
52
108
|
min?: number;
|
|
53
109
|
max?: number;
|
|
@@ -155,5 +211,5 @@ export interface YAxisProps {
|
|
|
155
211
|
* tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
|
|
156
212
|
* (default: the first axis).
|
|
157
213
|
*/
|
|
158
|
-
export declare function YAxis({ id, side, label, scale, min, max, format, ticks, tickCount, pad, boundaryLabels, width, hide, labelPlacement, color, index, }: YAxisProps): import("react/jsx-runtime").JSX.Element | null;
|
|
214
|
+
export declare function YAxis({ id, side, label, scale, linearWindow, min, max, format, ticks, tickCount, pad, boundaryLabels, width, hide, labelPlacement, color, index, }: YAxisProps): import("react/jsx-runtime").JSX.Element | null;
|
|
159
215
|
//# sourceMappingURL=YAxis.d.ts.map
|