@pond-ts/charts 0.55.0 → 0.57.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/CHANGELOG.md +288 -1
- package/dist/AreaChart.d.ts +18 -0
- package/dist/AreaChart.js +24 -1
- package/dist/BarChart.d.ts +84 -9
- package/dist/BarChart.js +113 -12
- package/dist/ChartContainer.d.ts +44 -1
- package/dist/ChartContainer.js +18 -2
- package/dist/ChartRow.js +57 -6
- package/dist/Layers.js +14 -1
- package/dist/YAxis.d.ts +56 -1
- package/dist/YAxis.js +28 -3
- package/dist/annotations.d.ts +74 -0
- package/dist/annotations.js +97 -7
- package/dist/area.js +46 -15
- package/dist/band.js +13 -0
- package/dist/bars.d.ts +139 -4
- package/dist/bars.js +300 -33
- package/dist/context.d.ts +30 -5
- package/dist/dev.d.ts +2 -0
- package/dist/dev.js +2 -0
- package/dist/domain.d.ts +54 -1
- package/dist/domain.js +195 -2
- package/dist/format.d.ts +20 -0
- package/dist/format.js +23 -10
- package/dist/gaps.d.ts +33 -0
- package/dist/gaps.js +49 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -2
- package/dist/line.js +10 -1
- package/dist/theme.d.ts +73 -6
- package/dist/theme.js +5 -0
- package/dist/viewport.d.ts +9 -1
- package/dist/viewport.js +40 -4
- package/dist/yticks.d.ts +44 -0
- package/dist/yticks.js +55 -0
- package/package.json +3 -3
package/dist/BarChart.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { Interval, ValueSeries } from 'pond-ts';
|
|
3
3
|
import { barsFromTimeSeries, barsFromBins, barsFromValueSeries, categoryStack, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
|
|
4
|
-
import { barAt, barExtent, barIndexAtTime, drawBars, drawStacks, resolveBarBaseline, stackAt, stackBinExtent, stackValueExtent, } from './bars.js';
|
|
4
|
+
import { barAt, barExtent, barIndexAtTime, drawBars, drawStacks, normalizeThresholds, resolveBarBaseline, stackAt, stackBinExtent, stackValueExtent, } from './bars.js';
|
|
5
|
+
import { isDev } from './dev.js';
|
|
5
6
|
import { ContainerContext, LayersContext, } from './context.js';
|
|
6
7
|
import { legendLabelFor, useLegendItems, } from './swatch.js';
|
|
7
8
|
import { useSlotKey } from './use-slot-key.js';
|
|
@@ -24,14 +25,21 @@ import { useSlotKey } from './use-slot-key.js';
|
|
|
24
25
|
* domain spans zero, or on the axis floor when an explicit `<YAxis min>` sits
|
|
25
26
|
* above zero (see {@link resolveBarBaseline}).
|
|
26
27
|
*
|
|
27
|
-
* **Baseline (stacked).** A stack is **cumulative from value 0** —
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* An explicit `<YAxis min>` **above** 0 is therefore unsupported
|
|
31
|
-
* would hide the bottom of the cumulative column; only the
|
|
32
|
-
* draws (clipped cleanly at the plot floor, as any bar
|
|
33
|
-
*
|
|
34
|
-
*
|
|
28
|
+
* **Baseline (stacked).** A stack is **cumulative from value 0** — so its value
|
|
29
|
+
* axis **must include 0**. The auto-fit guarantees this:
|
|
30
|
+
* {@link stackValueExtent} returns `[minNegativeTotal, maxPositiveTotal]`, both
|
|
31
|
+
* seeded at `0`. An explicit `<YAxis min>` **above** 0 is therefore unsupported
|
|
32
|
+
* for a stack — it would hide the bottom of the cumulative column; only the
|
|
33
|
+
* portion above the floor draws (clipped cleanly at the plot floor, as any bar
|
|
34
|
+
* below an explicit floor is).
|
|
35
|
+
*
|
|
36
|
+
* **Signed stacks are supported** ([PND-SIGNSTACK]): each bin keeps two running
|
|
37
|
+
* totals, so positive segments stack **up** from the zero line and negative
|
|
38
|
+
* ones stack **down** from it — the signed histogram (net flow by category,
|
|
39
|
+
* inflow/outflow, buy/sell pressure by venue). A **zero** segment is still
|
|
40
|
+
* skipped, having no extent to draw or hit-test. This changed in the
|
|
41
|
+
* threshold-banding wave: negative segments were previously dropped outright
|
|
42
|
+
* and silently, so a mixed-sign series rendered as an all-positive chart.
|
|
35
43
|
*
|
|
36
44
|
* **Interaction (opt-in via `id`).** Hover lights the bar / segment under the
|
|
37
45
|
* cursor (hit-tested by pixel rect, so it works in both orientations); click
|
|
@@ -48,7 +56,7 @@ import { useSlotKey } from './use-slot-key.js';
|
|
|
48
56
|
* </Layers>
|
|
49
57
|
* ```
|
|
50
58
|
*/
|
|
51
|
-
export function BarChart({ series, bins, categories, column, columns, as: semantic, colors, binColors, orientation = 'vertical', ordinal = false, id, axis, gap, decimate = true, legend, index = 0, }) {
|
|
59
|
+
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, }) {
|
|
52
60
|
const container = useContext(ContainerContext);
|
|
53
61
|
if (container === null) {
|
|
54
62
|
throw new Error('<BarChart> must be rendered inside a <ChartContainer>');
|
|
@@ -232,6 +240,87 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
232
240
|
// The stacked path's bar-thickness floor comes from `bar.default` (not the `as`
|
|
233
241
|
// role — `as` is single-series only), matching how `gapPx` sources its default.
|
|
234
242
|
const stackMinWidth = bar.default.minWidth;
|
|
243
|
+
// ── Threshold ladder ([PND-BANDBAR2]) ────────────────────────────────────
|
|
244
|
+
// Resolved once here rather than per bar per frame: normalize the breakpoints
|
|
245
|
+
// (sort, drop non-finite), then pair them with `bandColors` → the role's
|
|
246
|
+
// `BarStyle.bands`. Everything that can go wrong with the pairing is a
|
|
247
|
+
// *silent* wrong-looking chart, so each case dev-warns — this feature exists
|
|
248
|
+
// because a quietly-unbanded bar was the workaround's failure mode.
|
|
249
|
+
// Value-compare the two array props rather than relying on their identity.
|
|
250
|
+
// `thresholds={[1, 2]}` inline is the documented usage and the shape every
|
|
251
|
+
// story and doc example uses — and a fresh array each render would rebuild
|
|
252
|
+
// the ladder, hence the layer `entry` below, hence a `registerLayer` call
|
|
253
|
+
// **every render**. That is a repaint treadmill, not just a noisy warning.
|
|
254
|
+
// The same value-compare-on-registration reasoning `<YAxis ticks>` already
|
|
255
|
+
// applies.
|
|
256
|
+
const thresholdKey = thresholds === undefined ? '' : thresholds.join(',');
|
|
257
|
+
const bandColorKey = bandColors === undefined ? '' : bandColors.join(',');
|
|
258
|
+
const bandLadder = useMemo(() => {
|
|
259
|
+
const steps = normalizeThresholds(thresholds);
|
|
260
|
+
if (steps === null) {
|
|
261
|
+
if (isDev && thresholds !== undefined && thresholds.length > 0) {
|
|
262
|
+
console.warn('<BarChart thresholds>: no usable breakpoints, so no banding was ' +
|
|
263
|
+
'applied — each must be finite and greater than zero. Bars draw ' +
|
|
264
|
+
'in the flat fill.');
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
// Some, but not all, entries dropped. Silently banding on a subset of what
|
|
269
|
+
// the caller wrote is exactly the class of quiet wrongness this feature is
|
|
270
|
+
// meant to remove, so say so.
|
|
271
|
+
if (isDev && thresholds !== undefined && steps.length < thresholds.length) {
|
|
272
|
+
console.warn(`<BarChart thresholds>: dropped ${thresholds.length - steps.length} ` +
|
|
273
|
+
'breakpoint(s) that were not finite and greater than zero. The ' +
|
|
274
|
+
'ladder is walked on the magnitude and mirrored onto whichever side ' +
|
|
275
|
+
'of zero a bar is on, so a negative breakpoint has no meaning; ' +
|
|
276
|
+
`banding on [${steps.join(', ')}].`);
|
|
277
|
+
}
|
|
278
|
+
const want = steps.length + 1;
|
|
279
|
+
const supplied = bandColors ?? singleStyle.bands;
|
|
280
|
+
if (supplied === undefined || supplied.length === 0) {
|
|
281
|
+
if (isDev) {
|
|
282
|
+
console.warn(`<BarChart thresholds>: ${steps.length} breakpoint(s) need ${want} ` +
|
|
283
|
+
'band colours, but neither `bandColors` nor the theme role’s ' +
|
|
284
|
+
'`BarStyle.bands` supplies any. Bars draw in the flat fill.');
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
if (supplied.length < want && isDev) {
|
|
289
|
+
console.warn(`<BarChart thresholds>: ${steps.length} breakpoint(s) need ${want} ` +
|
|
290
|
+
`band colours but only ${supplied.length} were supplied; bands ` +
|
|
291
|
+
'above the last colour fall back to the flat fill.');
|
|
292
|
+
}
|
|
293
|
+
// Pad a short ladder with the flat fill so the draw path can index freely.
|
|
294
|
+
const resolved = supplied.length >= want
|
|
295
|
+
? supplied.slice(0, want)
|
|
296
|
+
: [
|
|
297
|
+
...supplied,
|
|
298
|
+
...Array.from({ length: want - supplied.length }, () => singleStyle.fill),
|
|
299
|
+
];
|
|
300
|
+
return { thresholds: steps, colors: resolved };
|
|
301
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- `thresholdKey` /
|
|
302
|
+
// `bandColorKey` are the value-compared stand-ins for the array props.
|
|
303
|
+
}, [thresholdKey, bandColorKey, singleStyle]);
|
|
304
|
+
// Conflicts between the ladder and the shapes it can't apply to. In an effect
|
|
305
|
+
// so a re-render doesn't re-log; each fires once per genuinely new pairing.
|
|
306
|
+
const multiGroup = shape.kind === 'stacked' && shape.ss.groups.length > 1;
|
|
307
|
+
const hasLadder = bandLadder !== undefined;
|
|
308
|
+
const hasBinColors = binColors !== undefined;
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (!isDev || !hasLadder)
|
|
311
|
+
return;
|
|
312
|
+
if (hasBinColors) {
|
|
313
|
+
console.warn('<BarChart>: `thresholds` and `binColors` are both set. They are two ' +
|
|
314
|
+
'answers to “what colour is this bar”; `binColors` wins as the more ' +
|
|
315
|
+
'specific one, and the threshold bands are ignored.');
|
|
316
|
+
}
|
|
317
|
+
if (multiGroup) {
|
|
318
|
+
console.warn('<BarChart>: `thresholds` is ignored on a multi-group stack — a ' +
|
|
319
|
+
'segment that is already one slice of a total has no defined ' +
|
|
320
|
+
'banding. Threshold bands apply to single-value bars (`series` / ' +
|
|
321
|
+
'`bins` / `categories`), in either orientation.');
|
|
322
|
+
}
|
|
323
|
+
}, [hasLadder, hasBinColors, multiGroup]);
|
|
235
324
|
// Stacked style: per-group fills (colors override → theme role → default),
|
|
236
325
|
// plus the shared opacity / outline from the default bar style. Memoized on the
|
|
237
326
|
// groups + colours so a selection change doesn't rebuild it.
|
|
@@ -243,6 +332,17 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
243
332
|
fills,
|
|
244
333
|
opacity: base.opacity,
|
|
245
334
|
outlineWidth: base.outlineWidth,
|
|
335
|
+
// [PND-CATEMPH] Forward the themed emphasis so the category / horizontal
|
|
336
|
+
// path can read the same `fill → hover → highlight` channel every other
|
|
337
|
+
// bar does, instead of accepting those theme values and ignoring them.
|
|
338
|
+
highlight: base.highlight,
|
|
339
|
+
...(base.hover !== undefined ? { hover: base.hover } : {}),
|
|
340
|
+
...(base.selectedOutline !== undefined
|
|
341
|
+
? { selectedOutline: base.selectedOutline }
|
|
342
|
+
: {}),
|
|
343
|
+
...(base.emphasisOpacity !== undefined
|
|
344
|
+
? { emphasisOpacity: base.emphasisOpacity }
|
|
345
|
+
: {}),
|
|
246
346
|
...(binColors !== undefined ? { binFills: binColors } : {}),
|
|
247
347
|
};
|
|
248
348
|
}, [bar, groups, colors, binColors]);
|
|
@@ -331,7 +431,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
331
431
|
};
|
|
332
432
|
},
|
|
333
433
|
}),
|
|
334
|
-
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale, singleStyle, resolveBarBaseline(yScale), gapPx, id, selection, hover, decimate, binColors),
|
|
434
|
+
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale, singleStyle, resolveBarBaseline(yScale), gapPx, id, selection, hover, decimate, binColors, bandLadder),
|
|
335
435
|
},
|
|
336
436
|
axisId: axis,
|
|
337
437
|
index,
|
|
@@ -391,7 +491,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
391
491
|
};
|
|
392
492
|
},
|
|
393
493
|
}),
|
|
394
|
-
draw: (ctx, xScale, yScale) => drawStacks(ctx, ss, orientation, xScale, yScale, stackStyle, gapPx, stackMinWidth, id, selection, hover),
|
|
494
|
+
draw: (ctx, xScale, yScale) => drawStacks(ctx, ss, orientation, xScale, yScale, stackStyle, gapPx, stackMinWidth, id, selection, hover, bandLadder),
|
|
395
495
|
},
|
|
396
496
|
axisId: axis,
|
|
397
497
|
index,
|
|
@@ -405,6 +505,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
405
505
|
singleStyle,
|
|
406
506
|
stackStyle,
|
|
407
507
|
binColors,
|
|
508
|
+
bandLadder,
|
|
408
509
|
label,
|
|
409
510
|
id,
|
|
410
511
|
gapPx,
|
package/dist/ChartContainer.d.ts
CHANGED
|
@@ -14,6 +14,49 @@ export interface ChartContainerProps {
|
|
|
14
14
|
* the data — so a tuple stays a time domain on a time chart.
|
|
15
15
|
*/
|
|
16
16
|
range?: readonly [number, number] | TimeRange;
|
|
17
|
+
/**
|
|
18
|
+
* **Cap the slot pitch** on a **category** x axis, in CSS pixels
|
|
19
|
+
* ([PND-BANDPACK]). A band scale otherwise spreads its categories across the
|
|
20
|
+
* full plot width, so three categories in a 900px panel become three 300px
|
|
21
|
+
* bars and thirty become thirty 30px ones — the same chart in the same panel
|
|
22
|
+
* reading as two different charts depending on how many categories the data
|
|
23
|
+
* happened to return.
|
|
24
|
+
*
|
|
25
|
+
* That is fine for a static chart with a known domain and wrong for a **live**
|
|
26
|
+
* one: when the category count moves over a session, bar width becomes a
|
|
27
|
+
* meaningless variable that moves on its own, and a reader can't compare the
|
|
28
|
+
* chart to what it looked like a minute ago or to the same chart on another
|
|
29
|
+
* screen. Capping the pitch keeps bar width constant and comparable, and the
|
|
30
|
+
* empty space left over is itself information — it shows the set is small.
|
|
31
|
+
*
|
|
32
|
+
* Omitted ⇒ slots fill the plot (unchanged). When `n × maxBandWidth` exceeds
|
|
33
|
+
* the plot, the cap can't bind and the slots fill as before, so this degrades
|
|
34
|
+
* correctly as categories accumulate. Use {@link bandAlign} to say where the
|
|
35
|
+
* capped block sits.
|
|
36
|
+
*
|
|
37
|
+
* **This caps the slot, not the bar.** `<BarChart gap>` still insets the bar
|
|
38
|
+
* within its slot, and the two compose — one knob for pitch, one for ink,
|
|
39
|
+
* neither doing the other's job. (Inverting `gap` against a measured plot
|
|
40
|
+
* width was the workaround this replaces for the width half; the packing half
|
|
41
|
+
* had no workaround at all.)
|
|
42
|
+
*
|
|
43
|
+
* **Vertical / x-axis categories only.** A `orientation="horizontal"`
|
|
44
|
+
* categorical chart puts its categories on the **y** axis as unit slots,
|
|
45
|
+
* which is a different mechanism and is not capped by this.
|
|
46
|
+
*/
|
|
47
|
+
maxBandWidth?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Where the capped category block sits in the plot when {@link maxBandWidth}
|
|
50
|
+
* binds. **Default `'start'`** — pack from the left, leaving the far side
|
|
51
|
+
* empty. `'center'` and `'end'` place it otherwise.
|
|
52
|
+
*
|
|
53
|
+
* A no-op without `maxBandWidth`, or when the cap doesn't bind: the block
|
|
54
|
+
* fills the plot and there is no slack to place. (There is deliberately no
|
|
55
|
+
* `'fill'` member — "fill" is what *omitting* `maxBandWidth` means, and a
|
|
56
|
+
* `fill` value alongside a pitch cap would be a contradiction rather than a
|
|
57
|
+
* choice.)
|
|
58
|
+
*/
|
|
59
|
+
bandAlign?: 'start' | 'center' | 'end';
|
|
17
60
|
/**
|
|
18
61
|
* A **trading-calendar** discontinuity provider — closed-market time
|
|
19
62
|
* (weekends, holidays, overnight, lunch breaks) collapsed. Supply it to turn
|
|
@@ -419,5 +462,5 @@ export interface ChartContainerProps {
|
|
|
419
462
|
* {@link TimeAxis} at the bottom, aligned under the plots. Y axes are per-row
|
|
420
463
|
* (`<YAxis>`).
|
|
421
464
|
*/
|
|
422
|
-
export declare function ChartContainer({ range, width, rowGap, showAxis, trackerPosition, onTrackerChanged, onDrawStats, selected, onSelect, hovered, onHover, panZoom, bounds, onTimeRangeChange, minDuration, cursor, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime, crosshairSnap, editAnnotations, creating, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap, timeFormat, cursorFormat, origin, theme, discontinuities, calendar, spacing, grid, sessionDividers, children, }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
465
|
+
export declare function ChartContainer({ range, maxBandWidth, bandAlign, width, rowGap, showAxis, trackerPosition, onTrackerChanged, onDrawStats, selected, onSelect, hovered, onHover, panZoom, bounds, onTimeRangeChange, minDuration, cursor, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime, crosshairSnap, editAnnotations, creating, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap, timeFormat, cursorFormat, origin, theme, discontinuities, calendar, spacing, grid, sessionDividers, children, }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
423
466
|
//# sourceMappingURL=ChartContainer.d.ts.map
|
package/dist/ChartContainer.js
CHANGED
|
@@ -45,7 +45,7 @@ function normalizeRange(range) {
|
|
|
45
45
|
* {@link TimeAxis} at the bottom, aligned under the plots. Y axes are per-row
|
|
46
46
|
* (`<YAxis>`).
|
|
47
47
|
*/
|
|
48
|
-
export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trackerPosition, onTrackerChanged, onDrawStats, selected, onSelect, hovered, onHover, panZoom = false, bounds, onTimeRangeChange, minDuration = 1, cursor = DEFAULT_CURSOR_MODE, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime = false, crosshairSnap = true, editAnnotations = false, creating = null, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap = true, timeFormat, cursorFormat, origin, theme, discontinuities, calendar, spacing, grid = true, sessionDividers = 'none', children, }) {
|
|
48
|
+
export function ChartContainer({ range, maxBandWidth, bandAlign = 'start', width, rowGap = 0, showAxis = true, trackerPosition, onTrackerChanged, onDrawStats, selected, onSelect, hovered, onHover, panZoom = false, bounds, onTimeRangeChange, minDuration = 1, cursor = DEFAULT_CURSOR_MODE, cursorSequence, onRegionSelect, regionSelectModifier, cursorTime = false, crosshairSnap = true, editAnnotations = false, creating = null, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap = true, timeFormat, cursorFormat, origin, theme, discontinuities, calendar, spacing, grid = true, sessionDividers = 'none', children, }) {
|
|
49
49
|
// Normalize the `panZoom` mode (boolean shorthand or the three-way string)
|
|
50
50
|
// into the two gesture flags the event surface reads. `true` ⇒ both; `'pan'`
|
|
51
51
|
// ⇒ drag only; `false`/`'none'` ⇒ neither. Zoom implies pan (there is no
|
|
@@ -414,7 +414,21 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
414
414
|
// reads by **name** — `cursorFormat` has nothing to format, so the
|
|
415
415
|
// readout channel stays unset.
|
|
416
416
|
const cats = categories ?? [];
|
|
417
|
-
|
|
417
|
+
// [PND-BANDPACK] Cap the slot pitch, then place the resulting block. With
|
|
418
|
+
// no cap (or one too loose to bind) `packed === plotWidth` and `offset`
|
|
419
|
+
// is 0, so the range is `[0, plotWidth]` exactly as before — the whole
|
|
420
|
+
// feature collapses to the shipped behaviour when unused.
|
|
421
|
+
const n = cats.length;
|
|
422
|
+
const pitch = n > 0 ? plotWidth / n : plotWidth;
|
|
423
|
+
const capped = maxBandWidth !== undefined && maxBandWidth > 0
|
|
424
|
+
? Math.min(pitch, maxBandWidth)
|
|
425
|
+
: pitch;
|
|
426
|
+
const packed = n > 0 ? capped * n : plotWidth;
|
|
427
|
+
const slack = Math.max(0, plotWidth - packed);
|
|
428
|
+
const offset = bandAlign === 'center' ? slack / 2 : bandAlign === 'end' ? slack : 0;
|
|
429
|
+
const s = scaleBand(cats)
|
|
430
|
+
.domain([0, n])
|
|
431
|
+
.range([offset, offset + packed]);
|
|
418
432
|
return {
|
|
419
433
|
xScale: s,
|
|
420
434
|
formatTime: (v) => s.label(v),
|
|
@@ -555,6 +569,8 @@ export function ChartContainer({ range, width, rowGap = 0, showAxis = true, trac
|
|
|
555
569
|
}, [
|
|
556
570
|
resolvedKind,
|
|
557
571
|
categories,
|
|
572
|
+
maxBandWidth,
|
|
573
|
+
bandAlign,
|
|
558
574
|
d0,
|
|
559
575
|
d1,
|
|
560
576
|
plotWidth,
|
package/dist/ChartRow.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Children, cloneElement, isValidElement, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
3
|
-
import { scaleLinear } from 'd3-scale';
|
|
4
|
-
import {
|
|
2
|
+
import { Children, cloneElement, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { scaleLinear, scaleLog } from 'd3-scale';
|
|
4
|
+
import { isDev } from './dev.js';
|
|
5
|
+
import { logAxisWarning, needsExtents, resolveYDomain } from './domain.js';
|
|
5
6
|
import { resolveAxisFormat } from './format.js';
|
|
6
7
|
import { resolveYTickCount } from './yticks.js';
|
|
7
8
|
import { placeAxisSlots } from './slots.js';
|
|
@@ -39,6 +40,7 @@ function axisSpecEqual(a, b) {
|
|
|
39
40
|
return (a.id === b.id &&
|
|
40
41
|
a.side === b.side &&
|
|
41
42
|
a.width === b.width &&
|
|
43
|
+
a.scale === b.scale &&
|
|
42
44
|
// Object.is (not ===) so a degenerate NaN bound compares equal to itself and
|
|
43
45
|
// doesn't re-register every render.
|
|
44
46
|
Object.is(a.min, b.min) &&
|
|
@@ -171,6 +173,7 @@ export function ChartRow({ height, cursor, children }) {
|
|
|
171
173
|
id: IMPLICIT_AXIS_ID,
|
|
172
174
|
side: 'left',
|
|
173
175
|
width: 0,
|
|
176
|
+
scale: 'linear',
|
|
174
177
|
min: undefined,
|
|
175
178
|
max: undefined,
|
|
176
179
|
pad: 0,
|
|
@@ -222,20 +225,68 @@ export function ChartRow({ height, cursor, children }) {
|
|
|
222
225
|
const yScales = useMemo(() => {
|
|
223
226
|
const map = new Map();
|
|
224
227
|
for (const ax of effectiveAxes) {
|
|
225
|
-
const extents = ax
|
|
228
|
+
const extents = needsExtents(ax)
|
|
226
229
|
? layerList
|
|
227
230
|
.filter((entry) => (entry.axisId ?? defaultAxisId) === ax.id)
|
|
228
231
|
.map((entry) => entry.layer.yExtent())
|
|
229
232
|
: [];
|
|
230
|
-
const [lo, hi] = resolveYDomain(ax.min, ax.max, extents, ax.pad);
|
|
233
|
+
const [lo, hi] = resolveYDomain(ax.min, ax.max, extents, ax.pad, ax.scale);
|
|
231
234
|
// Reserve a header band at the top when any axis draws a `'top'` title,
|
|
232
235
|
// so the title clears the top tick + plot (the whole row shifts down
|
|
233
236
|
// uniformly, keeping stacked axes aligned). No top titles ⇒ range top 0,
|
|
234
237
|
// so nothing changes for existing charts.
|
|
235
|
-
|
|
238
|
+
// `scaleLog` and `scaleLinear` share the call/ticks/tickFormat/invert
|
|
239
|
+
// surface every consumer uses (see `YScale`), so choosing between them
|
|
240
|
+
// here is the whole of log support — no draw layer branches on it.
|
|
241
|
+
const base = ax.scale === 'log' ? scaleLog() : scaleLinear();
|
|
242
|
+
map.set(ax.id, base.domain([lo, hi]).range([height, topHeader]));
|
|
236
243
|
}
|
|
237
244
|
return map;
|
|
238
245
|
}, [effectiveAxes, layerList, height, defaultAxisId, topHeader]);
|
|
246
|
+
// Dev-mode diagnostics for a `scale="log"` axis (see `logAxisWarning`). Three
|
|
247
|
+
// things about *where* this sits are load-bearing, each of them a bug the
|
|
248
|
+
// first version shipped:
|
|
249
|
+
//
|
|
250
|
+
// - **An effect, not the scale memo.** Warning from inside `useMemo` is a
|
|
251
|
+
// side effect in a function React may call speculatively — and does call
|
|
252
|
+
// twice under StrictMode.
|
|
253
|
+
// - **Deduplicated by message, in a ref.** The comment on the original said
|
|
254
|
+
// "warn once per offending axis" and nothing implemented it, so a live
|
|
255
|
+
// chart re-warned on every appended sample. Keying on the message (not a
|
|
256
|
+
// bare "already warned" flag) still reports a *different* complaint if the
|
|
257
|
+
// data changes shape.
|
|
258
|
+
// - **`height` is not a dependency.** It is one for the scales, which is why
|
|
259
|
+
// the warning must not ride along: a drag-resize would otherwise emit a
|
|
260
|
+
// line per animation frame.
|
|
261
|
+
//
|
|
262
|
+
// Gated on `isDev` **and** on some axis actually being logarithmic, so a
|
|
263
|
+
// production build and every linear chart skip the extent walk entirely.
|
|
264
|
+
const warnedRef = useRef(new Map());
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
if (!isDev || !effectiveAxes.some((ax) => ax.scale === 'log'))
|
|
267
|
+
return;
|
|
268
|
+
const warned = warnedRef.current;
|
|
269
|
+
for (const ax of effectiveAxes) {
|
|
270
|
+
// A linear axis in the same row has nothing to say and must not pay the
|
|
271
|
+
// O(points) walk below just because a sibling is logarithmic.
|
|
272
|
+
if (ax.scale !== 'log')
|
|
273
|
+
continue;
|
|
274
|
+
// Always walk the extents here, even for a fully-explicit domain the
|
|
275
|
+
// scale memo skips them for: data that cannot be drawn is worth saying so
|
|
276
|
+
// about whether or not it happened to constrain the bounds — and the
|
|
277
|
+
// both-explicit axis was exactly the case the first version stayed silent
|
|
278
|
+
// about.
|
|
279
|
+
const message = logAxisWarning(ax, layerList
|
|
280
|
+
.filter((entry) => (entry.axisId ?? defaultAxisId) === ax.id)
|
|
281
|
+
.map((entry) => entry.layer.yExtent()));
|
|
282
|
+
if (message === null)
|
|
283
|
+
warned.delete(ax.id);
|
|
284
|
+
else if (warned.get(ax.id) !== message) {
|
|
285
|
+
warned.set(ax.id, message);
|
|
286
|
+
console.warn(message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}, [effectiveAxes, layerList, defaultAxisId]);
|
|
239
290
|
// Resolved auto-tick count per axis — explicit `<YAxis tickCount>` else
|
|
240
291
|
// height-derived (see resolveYTickCount). The single source the `<YAxis>`
|
|
241
292
|
// labels, the readout formatter (below), and the `Layers` gridlines all read,
|
package/dist/Layers.js
CHANGED
|
@@ -5,6 +5,7 @@ import { drawGrid, drawDividers, dividerAlphas, thinPixels } from './grid.js';
|
|
|
5
5
|
import { cursorParts, bandRect, regionSpan } from './tracker.js';
|
|
6
6
|
import { resolveSelection } from './select.js';
|
|
7
7
|
import { panRange, zoomRange, panRangeTrading, zoomRangeTrading, } from './viewport.js';
|
|
8
|
+
import { yTickValues } from './yticks.js';
|
|
8
9
|
import { flagChipStyle, flagChipX, axisPillX, axisPillStyle } from './chip.js';
|
|
9
10
|
import { ContainerContext, CursorContext, LayersContext, RowContext, } from './context.js';
|
|
10
11
|
/** Fallback **y**-gridline tick count, used only before the row publishes its
|
|
@@ -116,7 +117,7 @@ export function Layers({ children }) {
|
|
|
116
117
|
// a gridline sits under every `<YAxis>` label and no more.
|
|
117
118
|
const yCount = tickCounts.get(defaultAxisId) ?? GRID_TICKS;
|
|
118
119
|
const yTicks = gridY && !(yIsCategory && explicitY === undefined)
|
|
119
|
-
? (explicitY ?? gridY
|
|
120
|
+
? (explicitY ?? yTickValues(gridY, yCount)).map((t) => gridY(t))
|
|
120
121
|
: [];
|
|
121
122
|
// On a calendar axis the verticals are the FULL grain populations —
|
|
122
123
|
// every day in the month, every month in the year, every aligned
|
|
@@ -267,6 +268,18 @@ export function Layers({ children }) {
|
|
|
267
268
|
// a full replot per mousemove.
|
|
268
269
|
container.timeRange,
|
|
269
270
|
container.reportDrawStats,
|
|
271
|
+
// Read inside `draw`, so they have to invalidate it. Omitting `grid` meant
|
|
272
|
+
// toggling `<ChartContainer grid>` changed nothing until some *other*
|
|
273
|
+
// dep moved — pan the plot a pixel and the gridlines you switched off
|
|
274
|
+
// finally vanished. All primitives, so no per-frame identity churn.
|
|
275
|
+
container.grid,
|
|
276
|
+
container.sessionDividers,
|
|
277
|
+
container.xKind,
|
|
278
|
+
// `container.theme` is read too. It is deliberately NOT listed: it is the
|
|
279
|
+
// caller's prop and an inline object literal would rebuild `draw` every
|
|
280
|
+
// render (a full replot per frame). The values `draw` actually takes from
|
|
281
|
+
// it — `background`, `gridColor`, `gridDash` — are extracted above and
|
|
282
|
+
// listed individually, so a theme swap still invalidates.
|
|
270
283
|
row.rowKey,
|
|
271
284
|
]);
|
|
272
285
|
// Interaction overlay: the cursor marks live on a DOM/SVG overlay above the
|
package/dist/YAxis.d.ts
CHANGED
|
@@ -20,6 +20,34 @@ export interface YAxisProps {
|
|
|
20
20
|
* that has headroom (auto-fit / padded) so it doesn't crowd the top tick.
|
|
21
21
|
*/
|
|
22
22
|
labelPlacement?: 'rotated' | 'top';
|
|
23
|
+
/**
|
|
24
|
+
* Which scale the axis maps its domain through. **Default `'linear'`.**
|
|
25
|
+
*
|
|
26
|
+
* `'log'` gives a base-10 logarithmic axis — for data spanning orders of
|
|
27
|
+
* magnitude, where a linear axis flattens everything below the top decade
|
|
28
|
+
* onto the baseline. Ticks land on the decades, and `format` still formats
|
|
29
|
+
* the **value**, so a readout says `1.2 PB`, not its logarithm.
|
|
30
|
+
*
|
|
31
|
+
* A log domain cannot contain zero or negative numbers — d3 maps them to
|
|
32
|
+
* `NaN`, which has no position on the plot. So:
|
|
33
|
+
*
|
|
34
|
+
* - **Auto-fit ignores non-positive extents** when picking the low end (a
|
|
35
|
+
* `BarChart`, whose extent always reaches zero so its bars can meet their
|
|
36
|
+
* baseline, can therefore share the axis), and rounds the domain out to
|
|
37
|
+
* whole powers of ten.
|
|
38
|
+
* - **An explicit `min`/`max` that is not positive is refused**, and that
|
|
39
|
+
* side auto-fits instead. A positive bound is always honoured exactly; when
|
|
40
|
+
* only one side is given and the domain would invert, the *auto* side moves
|
|
41
|
+
* — the same policy a linear axis follows.
|
|
42
|
+
* - **Layers that fill to a baseline** (`AreaChart`, `BarChart`, a stacked
|
|
43
|
+
* histogram) rest it on the bottom of the domain rather than on zero.
|
|
44
|
+
* - **A value with no position gaps the line**, rather than its neighbours
|
|
45
|
+
* being joined straight across it.
|
|
46
|
+
*
|
|
47
|
+
* A dev-mode warning fires for the cases that are unambiguously a mistake: a
|
|
48
|
+
* refused bound, negative data, or an axis with no positive data at all.
|
|
49
|
+
*/
|
|
50
|
+
scale?: 'linear' | 'log';
|
|
23
51
|
/** Explicit domain bounds; omit to auto-fit the charts linked to this axis. */
|
|
24
52
|
min?: number;
|
|
25
53
|
max?: number;
|
|
@@ -78,6 +106,33 @@ export interface YAxisProps {
|
|
|
78
106
|
boundaryLabels?: boolean;
|
|
79
107
|
/** Gutter width in CSS pixels (default 50). */
|
|
80
108
|
width?: number;
|
|
109
|
+
/**
|
|
110
|
+
* **Keep the scale, draw no gutter.** The axis still registers its domain
|
|
111
|
+
* (`min`/`max`/`scale`/`pad`) and layers still bind to it by `id`, but it
|
|
112
|
+
* renders nothing and reserves **no width** — the plot gets the space.
|
|
113
|
+
*
|
|
114
|
+
* A `<YAxis>` does two jobs: it *holds the scale* and it *renders a gutter*.
|
|
115
|
+
* Without this there was no way to ask for the first without the second, so a
|
|
116
|
+
* chart with a **fixed** domain whose scale is already explained by its
|
|
117
|
+
* chrome (threshold band lines, a legend, a panel header) had two reachable
|
|
118
|
+
* options and needed a third:
|
|
119
|
+
*
|
|
120
|
+
* | | auto domain | explicit domain |
|
|
121
|
+
* |---|---|---|
|
|
122
|
+
* | **gutter** | `<YAxis />` | `<YAxis min max />` |
|
|
123
|
+
* | **no gutter** | omit the axis | ← this prop |
|
|
124
|
+
*
|
|
125
|
+
* Omitting the axis is not the same thing: the row then supplies an implicit
|
|
126
|
+
* auto-domain axis, and the fixed domain is exactly what must not be given
|
|
127
|
+
* up. `width={0}` is not it either — the labels still draw, now over the
|
|
128
|
+
* plot.
|
|
129
|
+
*
|
|
130
|
+
* **Gridlines are unaffected.** They belong to the plot, not the gutter, and
|
|
131
|
+
* `<ChartContainer grid>` already controls them — so a hidden axis can still rule
|
|
132
|
+
* its own gridlines, which is usually what a "the shape matters, the numbers
|
|
133
|
+
* don't" chart wants. Turn them off there if you want neither.
|
|
134
|
+
*/
|
|
135
|
+
hide?: boolean;
|
|
81
136
|
/**
|
|
82
137
|
* This axis instance's colour — tick labels and the axis title take it,
|
|
83
138
|
* overriding the theme's `axis.label` / `axis.title.color`. The multi-axis
|
|
@@ -100,5 +155,5 @@ export interface YAxisProps {
|
|
|
100
155
|
* tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
|
|
101
156
|
* (default: the first axis).
|
|
102
157
|
*/
|
|
103
|
-
export declare function YAxis({ id, side, label, min, max, format, ticks, tickCount, pad, boundaryLabels, width, labelPlacement, color, index, }: YAxisProps): import("react/jsx-runtime").JSX.Element;
|
|
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;
|
|
104
159
|
//# sourceMappingURL=YAxis.d.ts.map
|
package/dist/YAxis.js
CHANGED
|
@@ -3,6 +3,7 @@ import { useContext, useEffect, useMemo } from 'react';
|
|
|
3
3
|
import { ContainerContext, RowContext } from './context.js';
|
|
4
4
|
import { resolveAxisFormat } from './format.js';
|
|
5
5
|
import { useSlotKey } from './use-slot-key.js';
|
|
6
|
+
import { yTickValues } from './yticks.js';
|
|
6
7
|
const DEFAULT_WIDTH = 50;
|
|
7
8
|
/** Fallback tick count before the row has published its resolved count (the
|
|
8
9
|
* first render, pre-registration). The row's height-derived value takes over
|
|
@@ -16,7 +17,7 @@ const DEFAULT_TICK_COUNT = 5;
|
|
|
16
17
|
* tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
|
|
17
18
|
* (default: the first axis).
|
|
18
19
|
*/
|
|
19
|
-
export function YAxis({ id, side = 'left', label, min, max, format, ticks, tickCount, pad = 0, boundaryLabels = true, width = DEFAULT_WIDTH, labelPlacement = 'rotated', color, index = 0, }) {
|
|
20
|
+
export function YAxis({ id, side = 'left', label, scale = 'linear', min, max, format, ticks, tickCount, pad = 0, boundaryLabels = true, width = DEFAULT_WIDTH, hide = false, labelPlacement = 'rotated', color, index = 0, }) {
|
|
20
21
|
const container = useContext(ContainerContext);
|
|
21
22
|
if (container === null) {
|
|
22
23
|
throw new Error('<YAxis> must be rendered inside a <ChartContainer>');
|
|
@@ -28,7 +29,12 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, tickC
|
|
|
28
29
|
const spec = useMemo(() => ({
|
|
29
30
|
id,
|
|
30
31
|
side,
|
|
31
|
-
|
|
32
|
+
// A hidden axis reserves no gutter — the row's slot placement reads this
|
|
33
|
+
// width, so zeroing it here is what gives the space back to the plot.
|
|
34
|
+
// Everything else about the spec is unchanged: the domain still resolves
|
|
35
|
+
// and layers still bind to it, which is the whole point of the prop.
|
|
36
|
+
width: hide ? 0 : width,
|
|
37
|
+
scale,
|
|
32
38
|
min,
|
|
33
39
|
max,
|
|
34
40
|
pad,
|
|
@@ -41,6 +47,8 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, tickC
|
|
|
41
47
|
id,
|
|
42
48
|
side,
|
|
43
49
|
width,
|
|
50
|
+
hide,
|
|
51
|
+
scale,
|
|
44
52
|
min,
|
|
45
53
|
max,
|
|
46
54
|
pad,
|
|
@@ -62,6 +70,23 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, tickC
|
|
|
62
70
|
useEffect(() => {
|
|
63
71
|
registerAxis(slot, spec);
|
|
64
72
|
}, [registerAxis, slot, spec]);
|
|
73
|
+
// `hide`: everything above still runs — the axis is registered, so its scale
|
|
74
|
+
// exists and layers bind to it — and everything below (the gutter chrome)
|
|
75
|
+
// does not. Placed after the last hook so the early return can't change hook
|
|
76
|
+
// order when `hide` is toggled at runtime.
|
|
77
|
+
//
|
|
78
|
+
// It renders an **empty box at the reserved slot width**, not nothing. The
|
|
79
|
+
// container reserves each axis *column* at the widest across rows
|
|
80
|
+
// (`maxSlotWidths`), so a hidden axis sharing a column with a visible one in
|
|
81
|
+
// another row is still allotted that column's width — and drawing nothing
|
|
82
|
+
// there slides this row's plot left, out of line with its siblings and with
|
|
83
|
+
// the shared x-axis. When this axis is alone in its column the reservation is
|
|
84
|
+
// its own `width: 0`, the box is zero-wide, and the plot reclaims the space,
|
|
85
|
+
// which is the point of the prop. Both cases fall out of the same expression.
|
|
86
|
+
if (hide) {
|
|
87
|
+
const hiddenSlot = row.axisSlots.get(slot) ?? 0;
|
|
88
|
+
return hiddenSlot > 0 ? (_jsx("div", { "aria-hidden": "true", style: { flex: `0 0 ${hiddenSlot}px`, height: `${row.height}px` } })) : null;
|
|
89
|
+
}
|
|
65
90
|
const { theme } = container;
|
|
66
91
|
const yScale = row.yScales.get(id);
|
|
67
92
|
// The auto-tick count — the row resolves it (explicit `tickCount` else
|
|
@@ -87,7 +112,7 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, tickC
|
|
|
87
112
|
? ticks.map((t) => ({ value: t.at, label: t.label }))
|
|
88
113
|
: layerCategories !== null
|
|
89
114
|
? layerCategories.map((label, i) => ({ value: i + 0.5, label }))
|
|
90
|
-
: (yScale ? yScale
|
|
115
|
+
: (yScale ? yTickValues(yScale, count) : []).map((t) => ({
|
|
91
116
|
value: t,
|
|
92
117
|
label: fmt(t),
|
|
93
118
|
}));
|
package/dist/annotations.d.ts
CHANGED
|
@@ -161,6 +161,80 @@ export interface BaselineProps {
|
|
|
161
161
|
/** A horizontal line at a y value, scaled against one row axis (RTC's `Baseline`).
|
|
162
162
|
* Its label anchors at the left, at the line's height. */
|
|
163
163
|
export declare function Baseline({ value, axis, label, labelSide, labelPosition, id, selected, selectable, hovered, editing, onChange, indicator, role, }: BaselineProps): import("react/jsx-runtime").JSX.Element | null;
|
|
164
|
+
export interface ZoneProps {
|
|
165
|
+
/** Lower bound in the linked y-axis's units. */
|
|
166
|
+
from: number;
|
|
167
|
+
/** Upper bound in the linked y-axis's units. `from`/`to` may arrive either way
|
|
168
|
+
* round (they're ordered here), and either may be **infinite** for an
|
|
169
|
+
* open-ended band (`to={Infinity}` — the AQI "Hazardous" tail, a
|
|
170
|
+
* `ZoneTime.openEnded` zone): the rect clamps to the plot. */
|
|
171
|
+
to: number;
|
|
172
|
+
/** Which `<YAxis>` (by id) to measure against; omit for the row's default axis. */
|
|
173
|
+
axis?: string;
|
|
174
|
+
/** Chip label, anchored at the band's vertical centre. **Omit for no label** —
|
|
175
|
+
* unlike `<Region>` a zone does *not* auto-label its bounds, because they're
|
|
176
|
+
* already legible on the y axis it spans (a region's x span isn't). The label
|
|
177
|
+
* worth showing is a **name** — `"Good"`, `"Z4 threshold"` — which only the
|
|
178
|
+
* caller has. */
|
|
179
|
+
label?: string;
|
|
180
|
+
/** Which side of the chart the label chip sits. **Default `left`.** */
|
|
181
|
+
labelSide?: 'left' | 'right';
|
|
182
|
+
/** Stable consumer id — a click reports it via `onSelectAnnotation`. Only
|
|
183
|
+
* meaningful with `selectable`. */
|
|
184
|
+
id?: string;
|
|
185
|
+
/** Controlled selection — brightens to the front (level 1). Ignored unless
|
|
186
|
+
* `selectable`. */
|
|
187
|
+
selected?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Whether the band responds to hover + selection. **Default `false`** — the
|
|
190
|
+
* opposite of the rest of the family, and deliberately so: a zone spans the
|
|
191
|
+
* **full plot width**, and a zone *set* tiles the whole y range, so the pointer
|
|
192
|
+
* is always inside one. Interactive by default, they'd light up on every
|
|
193
|
+
* mousemove and their hit rects would swallow the plot's own clicks. A zone is
|
|
194
|
+
* background context first (level 3, pointer-transparent); opt in per band when
|
|
195
|
+
* a band is genuinely a thing to point at.
|
|
196
|
+
*/
|
|
197
|
+
selectable?: boolean;
|
|
198
|
+
/** Theme **role** — colours this band from `theme.annotation.roles[role]` (its
|
|
199
|
+
* `color`, optionally `fillOpacity`), keeping the shared depth ramp. This is
|
|
200
|
+
* how a zone set gets its **semantic palette** (`good` green, `moderate`
|
|
201
|
+
* yellow, …): the scale lives in the theme, not at the call site. Omitted /
|
|
202
|
+
* unknown ⇒ the base annotation colour. */
|
|
203
|
+
role?: string;
|
|
204
|
+
/** Controlled hover (OR'd with pointer hover) — lets a legend row light the
|
|
205
|
+
* band remotely. Ignored unless `selectable`. */
|
|
206
|
+
hovered?: boolean;
|
|
207
|
+
/** Draw the horizontal **boundary lines** at `from`/`to`. **Default `false`** —
|
|
208
|
+
* again the opposite of `<Region>`, because zone sets are usually
|
|
209
|
+
* **contiguous**: every interior boundary is shared by two bands, so edges-on
|
|
210
|
+
* draws each one twice at double opacity. `true` outlines an isolated band (a
|
|
211
|
+
* target range). */
|
|
212
|
+
edges?: boolean;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A shaded band between two **y values**, spanning the full plot width — the
|
|
216
|
+
* y-axis counterpart of `<Region>`. The mark for a **classification of the value
|
|
217
|
+
* axis**: US EPA AQI categories, heart-rate / power zones, a control chart's
|
|
218
|
+
* spec limits, an SLO band.
|
|
219
|
+
*
|
|
220
|
+
* Being a `<Layers>` child it lives in its row and is scaled by that row's y
|
|
221
|
+
* axis — pass `axis` to pick one on a dual-axis row. Like every annotation it
|
|
222
|
+
* paints in the SVG overlay **above** the data canvas, so keep the fill light
|
|
223
|
+
* (the register's `fillOpacity`, ~0.1–0.2) and the trace reads cleanly through
|
|
224
|
+
* it. A zone set is a wash of colour behind the story, not a layer competing
|
|
225
|
+
* with it.
|
|
226
|
+
*
|
|
227
|
+
* Zones are **background context by default** (`selectable={false}`,
|
|
228
|
+
* `edges={false}`, no label) because that is what a tiled zone set is; see
|
|
229
|
+
* {@link ZoneProps.selectable} for why the family's usual defaults invert here.
|
|
230
|
+
* Colour comes from the theme's {@link ZoneProps.role | role} map, so a palette
|
|
231
|
+
* is a theme, not six call-site colours.
|
|
232
|
+
*
|
|
233
|
+
* Unlike the other marks a zone has **no `onChange`** — dragging zone edges
|
|
234
|
+
* (a zone editor) is a real feature but has no consumer yet; the band is
|
|
235
|
+
* declarative until one arrives.
|
|
236
|
+
*/
|
|
237
|
+
export declare function Zone({ from, to, axis, label, labelSide, id, selected, selectable, hovered, role, edges, }: ZoneProps): import("react/jsx-runtime").JSX.Element | null;
|
|
164
238
|
export interface RegionProps {
|
|
165
239
|
/** Start x in axis units (time or value). */
|
|
166
240
|
from: number;
|