@pond-ts/charts 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +1070 -1
  2. package/dist/AreaChart.d.ts +12 -1
  3. package/dist/AreaChart.js +131 -13
  4. package/dist/BarChart.js +184 -30
  5. package/dist/BarList.d.ts +85 -5
  6. package/dist/BarList.js +25 -4
  7. package/dist/BoxList.d.ts +70 -3
  8. package/dist/BoxList.js +21 -7
  9. package/dist/BoxPlot.d.ts +2 -1
  10. package/dist/BoxPlot.js +101 -9
  11. package/dist/Candlestick.d.ts +13 -1
  12. package/dist/Candlestick.js +89 -3
  13. package/dist/ChartContainer.d.ts +36 -48
  14. package/dist/ChartContainer.js +465 -59
  15. package/dist/ChartRow.d.ts +9 -2
  16. package/dist/ChartRow.js +86 -12
  17. package/dist/HeatMap.d.ts +176 -0
  18. package/dist/HeatMap.js +344 -0
  19. package/dist/Layers.d.ts +5 -1
  20. package/dist/Layers.js +1014 -253
  21. package/dist/Legend.js +8 -4
  22. package/dist/LineChart.d.ts +18 -1
  23. package/dist/LineChart.js +165 -4
  24. package/dist/ListTable.d.ts +30 -3
  25. package/dist/ListTable.js +381 -23
  26. package/dist/ScatterChart.d.ts +3 -2
  27. package/dist/ScatterChart.js +68 -4
  28. package/dist/XAxis.js +40 -22
  29. package/dist/area.d.ts +34 -1
  30. package/dist/area.js +88 -1
  31. package/dist/bars.d.ts +57 -3
  32. package/dist/bars.js +237 -26
  33. package/dist/box.d.ts +2 -2
  34. package/dist/box.js +158 -40
  35. package/dist/brush.d.ts +142 -0
  36. package/dist/brush.js +179 -0
  37. package/dist/child-index.d.ts +27 -0
  38. package/dist/child-index.js +57 -0
  39. package/dist/context.d.ts +859 -33
  40. package/dist/cursors.d.ts +161 -0
  41. package/dist/cursors.js +503 -0
  42. package/dist/decimate.d.ts +78 -1
  43. package/dist/decimate.js +157 -0
  44. package/dist/heat.d.ts +163 -0
  45. package/dist/heat.js +659 -0
  46. package/dist/index.d.ts +11 -2
  47. package/dist/index.js +22 -0
  48. package/dist/line.d.ts +137 -0
  49. package/dist/line.js +328 -0
  50. package/dist/ohlc.d.ts +16 -1
  51. package/dist/ohlc.js +93 -4
  52. package/dist/scatter.d.ts +17 -9
  53. package/dist/scatter.js +221 -33
  54. package/dist/select.d.ts +13 -5
  55. package/dist/select.js +14 -6
  56. package/dist/selection-fixtures.d.ts +174 -0
  57. package/dist/selection-fixtures.js +569 -0
  58. package/dist/selection-stories.d.ts +73 -0
  59. package/dist/selection-stories.js +301 -0
  60. package/dist/selectors.d.ts +316 -0
  61. package/dist/selectors.js +391 -0
  62. package/dist/span.d.ts +122 -0
  63. package/dist/span.js +203 -0
  64. package/dist/sweep.d.ts +154 -0
  65. package/dist/sweep.js +282 -0
  66. package/dist/theme.d.ts +456 -5
  67. package/dist/theme.js +217 -41
  68. package/dist/tracker.d.ts +6 -0
  69. package/dist/tracker.js +6 -0
  70. package/dist/tradingAxis.fixture.d.ts +78 -0
  71. package/dist/tradingAxis.fixture.js +215 -0
  72. package/dist/useChartLegend.js +18 -3
  73. package/package.json +3 -3
package/dist/theme.d.ts CHANGED
@@ -89,6 +89,24 @@ export interface ChartTheme {
89
89
  readonly default: CandleStyle;
90
90
  readonly [semantic: string]: CandleStyle;
91
91
  };
92
+ /**
93
+ * Map from a heat map's semantic identifier to its **interaction states**
94
+ * ({@link HeatMap}). Optional throughout; with no `heat` slot a live cell
95
+ * takes the pre-states treatment (one outline per cell, in
96
+ * `bar.highlight`).
97
+ *
98
+ * **Only the states, and deliberately so.** A heat map's *geometry* is
99
+ * bar-family — the slot gap, the minimum bin width, the outline weight —
100
+ * and it reads all of it from `bar[semantic] ?? bar.default`, which is
101
+ * right: a cell is a bar's slot with colour instead of height. What it
102
+ * cannot share is state styling, because a bar's fill is free and a cell's
103
+ * fill **is the datum** (see {@link HeatStates}). So the split is between
104
+ * the two things, not an accident of where the tokens landed.
105
+ */
106
+ readonly heat?: {
107
+ readonly default: HeatStates;
108
+ readonly [semantic: string]: HeatStates;
109
+ };
92
110
  /**
93
111
  * Map from a bar's semantic identifier to its style — the fill, the
94
112
  * selected-bar highlight, and the slot gap / minimum width ({@link BarChart}).
@@ -143,6 +161,29 @@ export interface ChartTheme {
143
161
  };
144
162
  /** Crosshair / tracker stroke colour. Falls back to {@link axis.label} if unset. */
145
163
  readonly cursor?: string;
164
+ /**
165
+ * The **drag band** — the live region the shared brush paints while a drag
166
+ * is in flight (`<RangeCursor>`'s band and `<MultiSelector>`'s sweep are the
167
+ * same pixels; see `renderBrushBand`). `fill` washes the covered span and
168
+ * `edge` hairlines its two boundaries at 1px, so the band has a readable
169
+ * start/end while the gesture is still live.
170
+ *
171
+ * **Optional, and back-compatible when omitted:** with no `brush` the band
172
+ * falls back to the cursor ink at 0.12 with no edges — byte-for-byte what
173
+ * every theme drew before this token existed. `defaultTheme` opts in with
174
+ * the *selection* blue at 7%: the band is about to become a selection, so it
175
+ * should be the same hue as one (whereas the resting bars are teal).
176
+ */
177
+ readonly brush?: {
178
+ /**
179
+ * The band's wash. Drawn at **full element opacity**, so carry the alpha
180
+ * in the colour (`rgba(…, 0.07)`) — a solid hex here paints over the marks
181
+ * the band is supposed to be previewing.
182
+ */
183
+ readonly fill: string;
184
+ /** Stroke for the band's 1px start/end edges. Omit for a fill-only band. */
185
+ readonly edge?: string;
186
+ };
146
187
  /**
147
188
  * Readout chip background (the `flag` / `inline` tracker modes). The value text
148
189
  * is the series colour; this is the panel behind it. Falls back to the plot
@@ -177,6 +218,18 @@ export interface ChartTheme {
177
218
  */
178
219
  readonly annotation?: {
179
220
  readonly color: string;
221
+ /**
222
+ * **EXPERIMENTAL ([PND-ANNSNAP]).** Ink for the vertical rules at a swept
223
+ * window's edges — a preview of what promoting the sweep to an annotation
224
+ * would look like. Lighter than {@link color} because these mark a
225
+ * *candidate* range rather than a committed mark. **Omitted ⇒ a light
226
+ * orange fallback.**
227
+ *
228
+ * Not settled: the rules are drawn canvas-side, under the trace ink,
229
+ * whereas a real annotation renders in the SVG overlay above it. If the
230
+ * look is kept, that mismatch has to be resolved rather than papered over.
231
+ */
232
+ readonly spanEdge?: string;
180
233
  readonly fillOpacity: number;
181
234
  readonly depth: readonly [number, number, number];
182
235
  /**
@@ -223,6 +276,58 @@ export interface ChartTheme {
223
276
  readonly border: string;
224
277
  readonly text: string;
225
278
  };
279
+ /**
280
+ * The **row-chart register** — `<BarList>` / `<BoxList>`, whose row states
281
+ * live on chrome the canvas has no equivalent of.
282
+ *
283
+ * A row chart cannot signal state the way a column chart does. Two reasons,
284
+ * and the second is the load-bearing one:
285
+ *
286
+ * - **The row is the target, not the bar.** A vertical bar can be its own
287
+ * hit area because every mark spans the full column width; a row's mark
288
+ * is as short as its value, so a 4% row would be a 30px sliver. The
289
+ * label gutter, the track and the trailing value are one target, and the
290
+ * thing that lights has to be the whole **band**.
291
+ * - **The band carries selection alone.** In a multi-metric row the fill
292
+ * *is* the identity of the metric, so it cannot also carry state — the
293
+ * same channel rule the canvas marks follow. Band + rail must read as
294
+ * selected with no help from the fill, and designing the single-metric
295
+ * case that way too means one treatment covers every row chart.
296
+ *
297
+ * So the two values here are the ones with no canvas counterpart: the row
298
+ * **band** tints. Everything else resolves from tokens that already exist
299
+ * and are per-metric where they should be — a selected fill takes
300
+ * {@link BarStyle.highlight}, a dimmed one {@link BarStyle.dimmed} — so a
301
+ * consumer who themes their bars gets a coherent list without theming it
302
+ * twice.
303
+ *
304
+ * **The rail is deliberately NOT per-metric.** There is one rail per row
305
+ * and a row may carry several metrics, so it cannot resolve through
306
+ * `bar[as]` the way a fill does; it lives here with the bands.
307
+ *
308
+ * **Optional, and back-compatible when omitted:** with no `list` the rows
309
+ * keep exactly the pre-token look — a hover band from `legend.border`, a
310
+ * selection rail from the annotation register, and no dimmed state at all.
311
+ */
312
+ readonly list?: {
313
+ /** Row stripe behind the hovered row — the whole band, gutter to value. */
314
+ readonly hoverBand: string;
315
+ /** The hovered row's 3px inset left edge. Never the selection hue. */
316
+ readonly hoverRail: string;
317
+ /** Row stripe behind a selected row. */
318
+ readonly selectedBand: string;
319
+ /** The selected row's 3px inset left edge. */
320
+ readonly selectedRail: string;
321
+ /**
322
+ * Reference ink — per-row target markers, thresholds, reference ticks.
323
+ *
324
+ * **Reserved away from the selection hue on purpose.** On a bullet row
325
+ * the marker sits *inside* the mark that selection recolors, so a tick
326
+ * in the selection blue is the one collision the rest of the language
327
+ * cannot absorb: you could not tell a target from a selected bar.
328
+ */
329
+ readonly markerInk: string;
330
+ };
226
331
  }
227
332
  /** A resolved line style: stroke colour + width (px). */
228
333
  export interface LineStyle {
@@ -237,6 +342,41 @@ export interface LineStyle {
237
342
  * observed one at a glance.
238
343
  */
239
344
  readonly dash?: readonly number[];
345
+ /**
346
+ * Stroke width when this trace is the **selected series**, and for the
347
+ * emphasised portion of a swept window. **Omitted ⇒ `width * 2`.**
348
+ *
349
+ * **State on a trace is WEIGHT, not hue** — the channel rule, and the same
350
+ * answer `<Candlestick>` got. A line's **colour is its identity**: it is how
351
+ * a reader tells one series from another in a multi-series plot, so a
352
+ * selected line that turned blue would trade the distinction they need for
353
+ * one they already have from the chrome. Thickening says "this one" without
354
+ * spending the channel that says "which one".
355
+ */
356
+ readonly selectedWidth?: number;
357
+ /** Stroke width on **hover** — the transient echo of `selectedWidth`.
358
+ * **Omitted ⇒ `selectedWidth`**, so hover previews the committed weight. */
359
+ readonly hoverWidth?: number;
360
+ /**
361
+ * Alpha for a trace that is **outside** the active selection — another
362
+ * series is selected, or this trace is outside a swept window.
363
+ * **Omitted ⇒ `0.32`**, the value the rest of the palette recedes to.
364
+ *
365
+ * Alpha rather than a colour, deliberately: a muted line must still read as
366
+ * *which* series it is, so its hue has to survive being pushed back.
367
+ */
368
+ readonly dimmedOpacity?: number;
369
+ /**
370
+ * Ink for the **swept window's** emphasised portion. **Omitted ⇒ the trace
371
+ * keeps its own colour and only thickens.**
372
+ *
373
+ * This is the one place a trace's state may take a hue, and the reason is
374
+ * that a window is a **region of one series** — identity is not in question
375
+ * there, because you can see which line you swept. So the region can read as
376
+ * the same act as the brush band that made it (the selection blue), where a
377
+ * whole-series selection cannot.
378
+ */
379
+ readonly spanColor?: string;
240
380
  }
241
381
  /** A resolved band style: fill colour + opacity (0–1) for the variance envelope. */
242
382
  export interface BandStyle {
@@ -265,6 +405,123 @@ export interface ScatterStyle {
265
405
  readonly selectedWidth: number;
266
406
  /** Colour of the optional per-point text label. */
267
407
  readonly label: string;
408
+ /**
409
+ * The **interaction states** — fill and size per state, and the whole
410
+ * styling channel for a live point when set. Unset ⇒ the pre-states
411
+ * behaviour exactly: every point keeps its resting fill and radius, and a
412
+ * live one is merely re-ringed in `selectedOutline`/`selectedWidth`.
413
+ *
414
+ * A scatter can afford what a candle cannot. A candle's hue *is* its
415
+ * meaning (rising vs falling), so it carries state in weight and alpha
416
+ * alone; a heat cell's colour is its value, so it carries state in chrome.
417
+ * A point's colour encodes nothing by default, so it is free to recolour —
418
+ * and it also has a channel none of the column marks have: **size**.
419
+ *
420
+ * That is why hover and selection split the channels rather than sharing
421
+ * them. See {@link ScatterStates}.
422
+ */
423
+ readonly states?: ScatterStates;
424
+ }
425
+ /**
426
+ * A scatter point's per-state fill and size ({@link ScatterStyle.states}).
427
+ *
428
+ * The radii are given in px against {@link ScatterStyle.radius}, and applied
429
+ * as the **ratio** between them — so a data-driven `radius` encoding still
430
+ * grows and shrinks by the same proportion instead of being flattened to one
431
+ * size the moment a point goes live.
432
+ */
433
+ export interface ScatterStates {
434
+ /**
435
+ * Fill for a **hovered** point — or one under a live drag rect, which is
436
+ * the same state (the sweep lights its covered marks through the plural
437
+ * `hovered`). A brightened form of the resting colour, not a new hue: the
438
+ * preview says "these are the ones", and saying it in the committed colour
439
+ * would make the preview and the commit read alike.
440
+ */
441
+ readonly hover: string;
442
+ /** Hovered radius (px). Hover is the state that spends **size** — it is the
443
+ * channel a lone pointer-over can afford, and a hover does not have to
444
+ * survive being read against a whole field of committed marks. */
445
+ readonly hoverRadius: number;
446
+ /**
447
+ * Fill for a **selected** point. Its radius is deliberately left at
448
+ * {@link ScatterStyle.radius}: selection spends **hue** instead, so
449
+ * committing a sweep does not reflow the cloud under the pointer.
450
+ */
451
+ readonly selected: string;
452
+ /** The ring around a live (hovered or selected) point — what keeps
453
+ * overlapping points countable once a whole swept region shares one fill. */
454
+ readonly halo: string;
455
+ /** Halo width (px); `0` draws none. */
456
+ readonly haloWidth: number;
457
+ /**
458
+ * Radius (px) of a point **outside a non-empty selection**. It shrinks as
459
+ * well as fading because alpha alone at these levels thins a cloud to
460
+ * nearly nothing, and the shape of the unselected field is the thing a
461
+ * scatter's background is *for*.
462
+ */
463
+ readonly dimmedRadius: number;
464
+ /** Alpha of a point outside a non-empty selection. */
465
+ readonly dimmedOpacity: number;
466
+ }
467
+ /**
468
+ * A heat map's **interaction states** ({@link ChartTheme.heat}).
469
+ *
470
+ * A cell has no spare channel at all. A bar's fill is free, so a bar swaps it;
471
+ * a point's colour encodes nothing by default, so a point recolours *and*
472
+ * resizes. A cell's colour **is** its value, and its rect is the grid — so
473
+ * every state here is **chrome added around the cell** or a transform applied
474
+ * uniformly to all of them, and none of them repaints a cell in a colour the
475
+ * ramp could also have produced.
476
+ */
477
+ export interface HeatStates {
478
+ /**
479
+ * The flat overlay painted over a cell **outside a non-empty selection** —
480
+ * carry the alpha in the colour (`rgba(255,255,255,0.62)`), because it is
481
+ * composited over the cell, not applied as one.
482
+ *
483
+ * **A flat overlay, not `globalAlpha`, and the difference is the whole
484
+ * point.** Alpha and value are the same channel on a ramp, so fading a cell
485
+ * slides it along the scale — a dimmed dark cell becomes a resting mid one.
486
+ * An overlay is *uniform and monotonic*: every cell moves by the same
487
+ * transform, so the ramp's **order survives inside the veiled set** and only
488
+ * the cross-set comparison is ambiguous — which is exactly what the
489
+ * {@link perimeter} is there to disambiguate.
490
+ *
491
+ * It is also why this is a colour and not a number: opacity composites with
492
+ * whatever is *behind* the cell (a gridline, a non-white background, another
493
+ * layer), so the same value would veil to different colours in different
494
+ * charts. An overlay is a property of the cell.
495
+ */
496
+ readonly veil: string;
497
+ /**
498
+ * The hovered cell's **double ring**, outer colour first — two concentric
499
+ * rings of {@link ringWidth}, both inside the cell.
500
+ *
501
+ * A single ring cannot work against a ramp: a light ring vanishes at the
502
+ * pale end and a dark one at the dark end, and a cell can be anywhere on the
503
+ * scale. The pair guarantees one of the two reads wherever the cell happens
504
+ * to sit. (The same problem `bar.binFills` has, and a better answer than
505
+ * picking one colour and hoping.)
506
+ */
507
+ readonly hoverRing: readonly [string, string];
508
+ /** Width of each of the two hover rings, in px. */
509
+ readonly ringWidth: number;
510
+ /**
511
+ * The selected region's **perimeter** — one outline around the union of
512
+ * selected cells, not one per cell.
513
+ *
514
+ * Per-cell outlines are what this replaces, and the reason is legible in any
515
+ * screenshot of them: a bordered grid is mostly border, and every interior
516
+ * line says nothing, because it is interior to the selection. Drawn by
517
+ * suppressing each cell edge whose neighbour is also selected, so a
518
+ * selection in several disconnected pieces gets one outline **per piece**,
519
+ * and a hole in the middle of one gets its own — no connectivity pass, and
520
+ * no assumption that a selection is a single rectangle.
521
+ */
522
+ readonly perimeter: string;
523
+ /** Perimeter stroke width, in px. */
524
+ readonly perimeterWidth: number;
268
525
  }
269
526
  /**
270
527
  * A resolved box-and-whisker style ({@link BoxPlot}). The q1→q3 box is a filled
@@ -282,6 +539,68 @@ export interface BoxStyle {
282
539
  readonly medianWidth: number;
283
540
  readonly whisker: string;
284
541
  readonly whiskerWidth: number;
542
+ /**
543
+ * The **tint ladder** — one four-step ladder per interaction state, and the
544
+ * whole styling channel for a box when set. Unset ⇒ the flat
545
+ * `fill`/`stroke`/`median`/`whisker` tokens above, unchanged.
546
+ *
547
+ * Every mark of a box reads its step from the *same* ladder
548
+ * ({@link BoxLadder} documents which step is which), so a state change is a
549
+ * **single palette swap** rather than four independent colour decisions.
550
+ * That is what keeps the quantile read intact across states: the ladder
551
+ * carries its meaning in *lightness*, so moving the whole ladder — brighter
552
+ * teal on hover, blue when committed — leaves every relationship between the
553
+ * marks untouched.
554
+ *
555
+ * Two consequences worth stating, because both are the opposite of what the
556
+ * multi-hue `bar` palette needs:
557
+ *
558
+ * - **Shift the ladder, not one step.** Recolouring only the median, or only
559
+ * the body, breaks the read. All four steps move together and keep their
560
+ * relative lightness spacing.
561
+ * - **Dim without desaturating.** A single-hue ladder has nothing to muddy
562
+ * into, so {@link dimmedOpacity} alone is the receded state — no
563
+ * desaturated companion ladder of the kind `bar.groupsDimmed` needs.
564
+ */
565
+ readonly states?: BoxStates;
566
+ /**
567
+ * Stroke width for a **selected** box's hairlines — the body outline and the
568
+ * whiskers both. Unset ⇒ they keep {@link strokeWidth} / {@link whiskerWidth}.
569
+ *
570
+ * A hairline cannot carry a state in hue alone: at 1px a colour change is
571
+ * nearly invisible, and the whisker is the mark that reaches furthest. A
572
+ * weight change is legible at any width, so selection bumps it (1 → 1.5 on
573
+ * `defaultTheme`) alongside the ladder swap.
574
+ */
575
+ readonly selectedStrokeWidth?: number;
576
+ }
577
+ /**
578
+ * A box's four tint steps, lightest → darkest. Which mark reads which step:
579
+ *
580
+ * | step | box plot | quantile bands |
581
+ * | ---- | --------------- | -------------- |
582
+ * | `0` | body fill / the solid shape's outer bar | outer band |
583
+ * | `1` | the solid shape's inner q1→q3 bar | inner band |
584
+ * | `2` | body stroke + whiskers | — |
585
+ * | `3` | the median rule | median rule |
586
+ *
587
+ * Steps are *positions on one hue's lightness ramp*, not four palette entries —
588
+ * a ladder whose steps don't descend in lightness stops encoding anything.
589
+ */
590
+ export type BoxLadder = readonly [string, string, string, string];
591
+ /** The per-state ladders (see {@link BoxStyle.states}). */
592
+ export interface BoxStates {
593
+ /** Nothing selected anywhere. */
594
+ readonly rest: BoxLadder;
595
+ /** Pointer over this box — a preview of what a click would commit, so it
596
+ * sits between {@link rest} and {@link selected} in strength. */
597
+ readonly hover: BoxLadder;
598
+ /** Committed. Pairs with {@link BoxStyle.selectedStrokeWidth}. */
599
+ readonly selected: BoxLadder;
600
+ /** How far a box **outside** a non-empty selection recedes — the
601
+ * {@link rest} ladder at this alpha. No separate ladder, and deliberately
602
+ * no desaturation (see {@link BoxStyle.states}). */
603
+ readonly dimmedOpacity: number;
285
604
  }
286
605
  /**
287
606
  * A resolved candlestick style ({@link Candlestick}). A candle is unreadable in
@@ -309,6 +628,41 @@ export interface CandleStyle {
309
628
  readonly body: string;
310
629
  readonly wick: string;
311
630
  };
631
+ /**
632
+ * The **interaction state** cues — and note what is *missing* from them.
633
+ *
634
+ * A `bar` swaps its fill and a `box` rotates its whole tint ladder, because
635
+ * on those marks hue is free: the meaning lives in position and in lightness
636
+ * ordering. **A candle's hue is its meaning** — rising vs falling is the
637
+ * first thing anyone reads off it — so a candle introduces *no state colour
638
+ * at all*, not even for its outline. Every cue here is a change of weight or
639
+ * of alpha:
640
+ *
641
+ * - **Live** (hovered *or* selected) — the candle **grows**: its body is
642
+ * stroked in its own colour and its lines thicken to
643
+ * {@link liveWickWidth}, so the mark gets a little heavier and nothing
644
+ * else about it changes. Nothing is recoloured, and nothing is added that
645
+ * the chart doesn't otherwise draw.
646
+ * - **Selected** — the same, plus the rest of the field recedes to
647
+ * {@link dimmedOpacity}. The dimming is what separates a committed
648
+ * selection from a passing hover, since the lit mark looks identical
649
+ * either way.
650
+ *
651
+ * That last point is deliberate but worth knowing: hover and selection are
652
+ * distinguished by what happens to the *other* candles, not by this one.
653
+ *
654
+ * Both optional; unset ⇒ a display-only candle exactly as before.
655
+ */
656
+ readonly dimmedOpacity?: number;
657
+ /**
658
+ * Line weight for a **live** (hovered or selected) candle — its wick, and
659
+ * the stroke around its body that makes the mark grow.
660
+ *
661
+ * Unlike {@link BoxStyle.selectedStrokeWidth}, which only selection triggers,
662
+ * this fires on hover too: a box announces hover by moving its tint ladder,
663
+ * and a candle has no ladder to move.
664
+ */
665
+ readonly liveWickWidth?: number;
312
666
  /** Doji (open === close) — body + wick colours; falls back to `rising` if unset. */
313
667
  readonly neutral?: {
314
668
  readonly body: string;
@@ -343,6 +697,25 @@ export interface AreaStyle {
343
697
  * a stack can be uniformly translucent — just not *graded*.)
344
698
  */
345
699
  readonly flatFill?: boolean;
700
+ /**
701
+ * Interaction state, the area's counterpart of `LineStyle`'s
702
+ * ([PND-TRACESEL]) — and the channels differ because what carries the mark
703
+ * differs. An area's mark is its **fill**, so state is the fill's *strength*
704
+ * plus the edge's weight; a line's mark is a stroke, so state is weight alone.
705
+ *
706
+ * `selectedWidth` thickens the edge, `selectedFillOpacity` strengthens the
707
+ * fill, `dimmedOpacity` recedes the whole shape, and `spanColor` is the one
708
+ * hue a swept **window** may take (identity is not in question inside a
709
+ * single series — see `LineStyle.spanColor`).
710
+ */
711
+ readonly selectedWidth?: number;
712
+ /** Fill opacity when selected. **Omitted ⇒ `min(fillOpacity * 2, 1)`.** */
713
+ readonly selectedFillOpacity?: number;
714
+ /** Alpha for an area outside the active selection. **Omitted ⇒ `0.32`.** */
715
+ readonly dimmedOpacity?: number;
716
+ /** Ink for a swept window's emphasised portion (edge + fill).
717
+ * **Omitted ⇒ the area keeps its own colours and only strengthens.** */
718
+ readonly spanColor?: string;
346
719
  }
347
720
  /**
348
721
  * A resolved bar style: the flat `fill` (scaled by `opacity`, 0–1) plus the
@@ -412,6 +785,50 @@ export interface BarStyle {
412
785
  * already one slice of a total has no defined meaning.
413
786
  */
414
787
  readonly bands?: readonly string[];
788
+ /**
789
+ * The **stack group ramp** — ordered fills for a *multi-group* stack's
790
+ * segments, `groups[0]` for the first (bottom / left) group. Cycles when the
791
+ * stack has more groups than the ramp has entries.
792
+ *
793
+ * Sibling of {@link bands}, and here for the same reason (a `theme.bar`
794
+ * top-level key would collide with a role of that name) — but a different
795
+ * axis: `bands` colours one bar *along its length* against a threshold
796
+ * ladder, this colours *across the groups* of one bin.
797
+ *
798
+ * **Multi-group only.** A ramp exists to tell groups apart, so with one group
799
+ * there is nothing to tell apart and the bar keeps its {@link fill} — which
800
+ * is what keeps every categorical and single-series chart (both of which run
801
+ * the stacked draw path with `G === 1`) exactly as it was.
802
+ *
803
+ * Resolution order per group: `<BarChart colors>` → a theme role named after
804
+ * the group (`bar.web`) → this ramp → {@link fill}. So a named role still
805
+ * wins, and the ramp is the fallback that makes an *unthemed* stack legible
806
+ * instead of painting every segment one colour.
807
+ */
808
+ readonly groups?: readonly string[];
809
+ /**
810
+ * The receded counterpart of {@link groups}, same order and cycling — what a
811
+ * segment fades to when a selection exists elsewhere.
812
+ *
813
+ * Per-group rather than the flat {@link dimmed}, because a stack dimmed to a
814
+ * single colour stops being a stack: the segment boundaries vanish and the
815
+ * unselected columns read as solid blocks. Each entry is its ramp colour
816
+ * desaturated and lightened, so the bin keeps its structure while clearly
817
+ * receding.
818
+ */
819
+ readonly groupsDimmed?: readonly string[];
820
+ /**
821
+ * The **hovered** counterpart of {@link groups}, same order and cycling.
822
+ *
823
+ * Per-group for the reason the flat {@link hover} cannot be: one hover
824
+ * colour repaints whichever segment the pointer is over in a hue belonging
825
+ * to a different group, so pointing at a stack momentarily *erases the
826
+ * ramp* — and under a `<MultiSelector>`, where hover is block-scoped, it
827
+ * erases the whole bin at once. Each entry is its ramp colour brightened
828
+ * (same hue, lighter), the same relationship {@link fill} has to
829
+ * {@link hover}.
830
+ */
831
+ readonly groupsHover?: readonly string[];
415
832
  /**
416
833
  * Stroke for a **selected** bar's outline, where the default is the bar's own
417
834
  * resolved fill. The one selection cue that still works when the fill cannot
@@ -427,13 +844,47 @@ export interface BarStyle {
427
844
  * live — which is the part that reads as emphasis.
428
845
  */
429
846
  readonly emphasisOpacity?: number;
847
+ /**
848
+ * The fill for a bar that is **not** in a non-empty selection set — the
849
+ * "everything else recedes" state a chart used as a filter control needs
850
+ * ([PND-MULTISEL]).
851
+ *
852
+ * **Opt-in by construction:** a theme that sets no `dimmed` dims nothing, so
853
+ * existing charts are untouched (RFC `selection.md` A2.3 — the library never
854
+ * auto-dims; the theme carries the selection-state styling and the library
855
+ * references it by state). Nothing dims while the set is empty either: with
856
+ * no selection there is nothing to recede *from*.
857
+ *
858
+ * It exists because "not in the selection" was otherwise re-invented per
859
+ * component, and drifted immediately — one consumer had three charts using
860
+ * `color-mix` at 22%, 28% and 30% for the same concept, in the same week, for
861
+ * no reason. One theme value fixes that permanently.
862
+ */
863
+ readonly dimmed?: string;
430
864
  }
431
865
  /**
432
- * The neutral default theme. `default` / `primary` match the M1 `LineChart`
433
- * colour (`#2563eb`) so adopting the theme channel doesn't shift existing
434
- * renders. `primary` / `secondary` / `context` are a built-in generic role
435
- * vocabulary; an unrecognised (e.g. domain-specific) identifier falls back to
436
- * `default`.
866
+ * The neutral default theme. The shared data hue is a cerulean (`#0284c7`)
867
+ * across `line` / `band` / `area` / `scatter` / `box` / candle-rising, chosen
868
+ * to clear the bar palette's *selection* blue (`#3F5BE0`) the original M1
869
+ * royal blue (`#2563eb`) sat ~ΔE 5 from it, so a line drawn over bars read as
870
+ * nearly the selection colour. `primary` / `secondary` / `context` are a
871
+ * built-in generic role vocabulary; an unrecognised (e.g. domain-specific)
872
+ * identifier falls back to `default`.
873
+ *
874
+ * **Spreading this inherits every slot you don't override** — including colours
875
+ * for layers you haven't added yet, so a `{ ...defaultTheme, bar: … }` theme
876
+ * paints this blue the first time someone drops in a `<LineChart>`. That is the
877
+ * intended workflow, not a trap: take the defaults, change the one or two things
878
+ * that are yours. A design system that must own *every* colour should assert on
879
+ * that in its own test rather than catch it in review — walk the resolved theme
880
+ * for values outside your palette ([PND-THEMEBASE]).
881
+ *
882
+ * **The `bar` slot is the exception to "one blue".** Bars carry an interaction
883
+ * state (rest / hover / selected / dimmed), and encoding four states as four
884
+ * shades of one hue is unreadable — so `bar.default` runs its own
885
+ * **interaction-state palette**: teal at rest, blue when selected, brighter
886
+ * teal on hover. See the comment on that slot, and `brush` for the matching
887
+ * drag band.
437
888
  */
438
889
  export declare const defaultTheme: ChartTheme;
439
890
  /**