@pond-ts/charts 0.56.2 → 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 (78) hide show
  1. package/CHANGELOG.md +1218 -1
  2. package/dist/AreaChart.d.ts +12 -1
  3. package/dist/AreaChart.js +131 -13
  4. package/dist/BarChart.d.ts +84 -9
  5. package/dist/BarChart.js +295 -40
  6. package/dist/BarList.d.ts +85 -5
  7. package/dist/BarList.js +25 -4
  8. package/dist/BoxList.d.ts +70 -3
  9. package/dist/BoxList.js +21 -7
  10. package/dist/BoxPlot.d.ts +2 -1
  11. package/dist/BoxPlot.js +101 -9
  12. package/dist/Candlestick.d.ts +13 -1
  13. package/dist/Candlestick.js +89 -3
  14. package/dist/ChartContainer.d.ts +79 -48
  15. package/dist/ChartContainer.js +482 -60
  16. package/dist/ChartRow.d.ts +9 -2
  17. package/dist/ChartRow.js +86 -12
  18. package/dist/HeatMap.d.ts +176 -0
  19. package/dist/HeatMap.js +344 -0
  20. package/dist/Layers.d.ts +5 -1
  21. package/dist/Layers.js +1014 -253
  22. package/dist/Legend.js +8 -4
  23. package/dist/LineChart.d.ts +18 -1
  24. package/dist/LineChart.js +165 -4
  25. package/dist/ListTable.d.ts +30 -3
  26. package/dist/ListTable.js +381 -23
  27. package/dist/ScatterChart.d.ts +3 -2
  28. package/dist/ScatterChart.js +68 -4
  29. package/dist/XAxis.js +40 -22
  30. package/dist/YAxis.d.ts +28 -1
  31. package/dist/YAxis.js +24 -2
  32. package/dist/annotations.d.ts +74 -0
  33. package/dist/annotations.js +97 -7
  34. package/dist/area.d.ts +34 -1
  35. package/dist/area.js +88 -1
  36. package/dist/bars.d.ts +178 -5
  37. package/dist/bars.js +504 -46
  38. package/dist/box.d.ts +2 -2
  39. package/dist/box.js +158 -40
  40. package/dist/brush.d.ts +142 -0
  41. package/dist/brush.js +179 -0
  42. package/dist/child-index.d.ts +27 -0
  43. package/dist/child-index.js +57 -0
  44. package/dist/context.d.ts +871 -36
  45. package/dist/cursors.d.ts +161 -0
  46. package/dist/cursors.js +503 -0
  47. package/dist/decimate.d.ts +78 -1
  48. package/dist/decimate.js +157 -0
  49. package/dist/heat.d.ts +163 -0
  50. package/dist/heat.js +659 -0
  51. package/dist/index.d.ts +13 -4
  52. package/dist/index.js +25 -2
  53. package/dist/line.d.ts +137 -0
  54. package/dist/line.js +328 -0
  55. package/dist/ohlc.d.ts +16 -1
  56. package/dist/ohlc.js +93 -4
  57. package/dist/scatter.d.ts +17 -9
  58. package/dist/scatter.js +221 -33
  59. package/dist/select.d.ts +13 -5
  60. package/dist/select.js +14 -6
  61. package/dist/selection-fixtures.d.ts +174 -0
  62. package/dist/selection-fixtures.js +569 -0
  63. package/dist/selection-stories.d.ts +73 -0
  64. package/dist/selection-stories.js +301 -0
  65. package/dist/selectors.d.ts +316 -0
  66. package/dist/selectors.js +391 -0
  67. package/dist/span.d.ts +122 -0
  68. package/dist/span.js +203 -0
  69. package/dist/sweep.d.ts +154 -0
  70. package/dist/sweep.js +282 -0
  71. package/dist/theme.d.ts +517 -11
  72. package/dist/theme.js +220 -39
  73. package/dist/tracker.d.ts +6 -0
  74. package/dist/tracker.js +6 -0
  75. package/dist/tradingAxis.fixture.d.ts +78 -0
  76. package/dist/tradingAxis.fixture.js +215 -0
  77. package/dist/useChartLegend.js +18 -3
  78. package/package.json +3 -3
package/dist/bars.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { BarSeries, StackedBarSeries } from './data.js';
2
2
  import type { Scale } from './line.js';
3
3
  import type { BarStyle } from './theme.js';
4
- import type { LayerDrawStats } from './context.js';
4
+ import type { LayerDrawStats, SpanSelection } from './context.js';
5
5
  import { type DecimateOption } from './decimate.js';
6
6
  /**
7
7
  * Bar growth direction — the histogram orientation. `'vertical'` bars grow **up**
@@ -56,6 +56,84 @@ export declare function resolveBarBaseline(yScale: Scale): number;
56
56
  * separates columns visually without carving a dead channel out of the target.
57
57
  */
58
58
  export declare function barRect(cs: BarSeries, i: number, xScale: Scale, yScale: Scale, baseline: number, gapPx: number, minWidthPx: number): [x0: number, x1: number, yTop: number, yBottom: number] | null;
59
+ /**
60
+ * The value-space span `[lo, hi]` of **threshold band `k`** along a bar running
61
+ * from `base` to `v`, or `null` when the bar doesn't reach that band.
62
+ *
63
+ * A threshold ladder colours one bar **along its length** — neutral up to the
64
+ * first threshold, then warning, then alarm — so a long bar shows how far
65
+ * through the ladder it travelled rather than only which band it ended in. With
66
+ * `thresholds = [t0, t1]` there are three bands: `[0, t0)`, `[t0, t1)`,
67
+ * `[t1, ∞)`. Band `k` spans magnitudes `[thresholds[k-1] ?? 0, thresholds[k] ??
68
+ * ∞)`, each end clipped to the bar's own magnitude — so a bar that stops inside
69
+ * band 1 yields a truncated band 1 and `null` for band 2.
70
+ *
71
+ * **Breakpoints are absolute data values, not offsets from the baseline** — a
72
+ * `thresholds={[1, 2]}` ladder means "warning above 1, alarm above 2" in the
73
+ * axis's own units, which is what a threshold means everywhere else. They are
74
+ * matched on the **magnitude** and applied to whichever side of zero the bar
75
+ * is on, so a bar hanging below the baseline walks the same ladder downward
76
+ * and a ±3.5 diverging scale bands symmetrically without the caller supplying
77
+ * negative breakpoints. (An asymmetric ladder would need signed breakpoints;
78
+ * deferred until a consumer pulls — see [PND-BANDBAR2].)
79
+ *
80
+ * The painted span is then **clipped to what the bar actually draws**, which
81
+ * is what makes a domain that excludes zero behave: with `<YAxis min={10}>` a
82
+ * bar rests on 10, so a `[1, 2]` ladder leaves it entirely in the top band
83
+ * rather than banding at 11 and 12. Measuring the ladder from the *resolved
84
+ * baseline* instead would silently shift every breakpoint by the axis floor —
85
+ * exactly the class of quiet wrongness this feature exists to remove.
86
+ *
87
+ * Note this is **draw-only geometry**. Hit-testing still treats the bar as one
88
+ * target ({@link barSlotRect} / {@link barAt}), which is the whole reason this
89
+ * is a mark rather than the N-layer overpaint recipe it replaces: one bar keeps
90
+ * one hit region, one stable `mark`, and one legend row.
91
+ *
92
+ * `thresholds` is assumed ascending and finite — {@link normalizeThresholds}
93
+ * enforces that once at the prop boundary rather than per bar per frame.
94
+ */
95
+ export declare function bandSpan(base: number, v: number, thresholds: readonly number[], k: number): [lo: number, hi: number] | null;
96
+ /**
97
+ * A resolved threshold ladder: ascending `thresholds` (from
98
+ * {@link normalizeThresholds}) paired with the `colors` each band draws in,
99
+ * `colors[k]` for the band above `thresholds[k - 1]`. Assembled by `BarChart`
100
+ * from `<BarChart bandColors>` → {@link BarStyle.bands}, so — like
101
+ * {@link StackStyle} — the draw layer stays theme-free and unit-testable.
102
+ *
103
+ * `colors` is guaranteed `thresholds.length + 1` long by the time it reaches a
104
+ * draw path; a short ladder is resolved (and warned about) at the boundary.
105
+ */
106
+ export interface BandLadder {
107
+ readonly thresholds: readonly number[];
108
+ readonly colors: readonly string[];
109
+ }
110
+ /**
111
+ * Validate + freeze a caller's threshold ladder once, at the prop boundary.
112
+ * Returns the ascending, strictly-positive, finite breakpoints — or `null` when
113
+ * there is no usable ladder left, so the caller keeps the flat path.
114
+ *
115
+ * Sorting rather than rejecting an out-of-order ladder is deliberate — the
116
+ * bands are defined by their boundaries, so `[2, 1]` and `[1, 2]` describe the
117
+ * same three bands and there is no second reading to guess at.
118
+ *
119
+ * Three kinds of entry are **dropped**:
120
+ *
121
+ * - **non-finite** — would swallow every band above it;
122
+ * - **negative** — the ladder is walked on the *magnitude* and mirrored onto
123
+ * whichever side of zero the bar is on, so a negative breakpoint has no
124
+ * meaning. Left in, `[-2, -1]` silently clipped every lower band away and
125
+ * painted the whole bar in the final colour — a one-colour bar that looks
126
+ * deliberate (Codex adversarial review). Signed breakpoints are the
127
+ * asymmetric-ladder feature deferred in [PND-BANDBAR2], not this;
128
+ * - **zero** — band 0 already starts at zero, so a `0` breakpoint describes an
129
+ * empty band and shifts every colour by one.
130
+ *
131
+ * Dropping rather than throwing matches how the rest of this prop behaves
132
+ * (a short colour ladder degrades, it doesn't fail), and `BarChart` dev-warns
133
+ * whenever normalization removed anything — a silently-ignored breakpoint is
134
+ * the failure mode this whole feature exists to avoid.
135
+ */
136
+ export declare function normalizeThresholds(thresholds: readonly number[] | undefined): readonly number[] | null;
59
137
  /**
60
138
  * The narrowed selection / hover identity a **single-series** bar matches
61
139
  * against: the layer's series `id`, the sample's `key` (its `begin`), and — when
@@ -68,6 +146,21 @@ export interface BarMark {
68
146
  readonly key: number;
69
147
  readonly mark?: string;
70
148
  }
149
+ /**
150
+ * Stroke a selected mark's outline **inside** its ink rect. Canvas strokes
151
+ * centre on the path, so a plain `strokeRect(x0, yTop, …)` paints
152
+ * `lineWidth / 2` *outside* the rect — which, with the default
153
+ * `outlineWidth: 1.5` against the default `gap: 1`, is enough to bridge the
154
+ * whole inter-bar gap from both sides: a swept run of selected bars fused
155
+ * into one unreadable block (you could not count them). Insetting the path by
156
+ * half the line width keeps every stroked pixel within the bar's own ink, so
157
+ * adjacent selected marks stay separated by exactly the gap the resting bars
158
+ * show. A mark too thin to contain its outline (either dimension
159
+ * `<= lineWidth`) skips the stroke — the highlight fill is already the
160
+ * signal, and an outline wider than the bar would only smear into the
161
+ * neighbours this exists to keep distinct.
162
+ */
163
+ export declare function strokeSelectedOutline(ctx: CanvasRenderingContext2D, x0: number, x1: number, yTop: number, yBottom: number, lineWidth: number): void;
71
164
  /**
72
165
  * Fill one rectangle per bar in `cs`, each spanning its key's `[begin, end]`
73
166
  * (inset by `gapPx`) from the resolved `baseline` to the value.
@@ -120,7 +213,7 @@ export interface BarMark {
120
213
  * repaint them one flat colour; per-bar-coloured layers draw every visible bar.
121
214
  * Returns {@link LayerDrawStats} for `onDrawStats`.
122
215
  */
123
- export declare function drawBars(ctx: CanvasRenderingContext2D, cs: BarSeries, xScale: Scale, yScale: Scale, style: BarStyle, baseline: number, gapPx: number, seriesId: string | undefined, selection: BarMark | null, hovered: BarMark | null, decimate?: DecimateOption, binFills?: readonly (string | undefined)[]): LayerDrawStats;
216
+ export declare function drawBars(ctx: CanvasRenderingContext2D, cs: BarSeries, xScale: Scale, yScale: Scale, style: BarStyle, baseline: number, gapPx: number, seriesId: string | undefined, selection: readonly BarMark[], hovered: readonly BarMark[], decimate?: DecimateOption, binFills?: readonly (string | undefined)[], banding?: BandLadder, spans?: readonly SpanSelection[]): LayerDrawStats;
124
217
  /**
125
218
  * The index of the bar whose key span `[begin, end]` contains `time` — the bar
126
219
  * **under the cursor** — or `-1` if `time` falls in no bar's span. This is the
@@ -222,6 +315,80 @@ export interface StackStyle {
222
315
  * falls back to the group fill.
223
316
  */
224
317
  readonly binFills?: readonly (string | undefined)[];
318
+ /**
319
+ * The **selected** segment's fill, and `hover` the pointer-over one — the
320
+ * three-step `fill → hover → highlight` emphasis {@link BarStyle} has always
321
+ * carried and this path used to ignore ([PND-CATEMPH]).
322
+ *
323
+ * **Only applied when there is no meaning-carrying colour to destroy**, i.e.
324
+ * when {@link binFills} is unset. A per-bin-coloured bar keeps its own colour
325
+ * and pops {@link emphasisOpacity} instead — swapping a zone-coloured or
326
+ * direction-coloured bar to one highlight hue would erase what the colour
327
+ * encodes, which is the one *design* exclusion rather than a path accident.
328
+ *
329
+ * The friction this closes wasn't the behaviour, which is defensible: it was
330
+ * that `theme.bar.hover` / `.highlight` were typed, settable, documented as
331
+ * the emphasis channel, and silently did nothing on the most common
332
+ * categorical chart. A theme author set them, saw no change, and had no way
333
+ * to tell whether they were wrong about the colour or about the mechanism.
334
+ */
335
+ readonly highlight?: string;
336
+ /** See {@link highlight}. Falls back to `highlight` when unset. */
337
+ readonly hover?: string;
338
+ /**
339
+ * Stroke for the selected segment's outline. Defaults to the segment's own
340
+ * resolved fill (the shipped behaviour). Set it to give the category path a
341
+ * themed selection cue that works even where the fill can't change — the
342
+ * `binFills` case, where the alpha pop is otherwise the only signal.
343
+ */
344
+ readonly selectedOutline?: string;
345
+ /**
346
+ * The alpha a hovered / selected segment pops to. **Default `1`** (the
347
+ * shipped behaviour). Lower it for a subtler emphasis on a dense stack —
348
+ * previously the pop was hard-coded and the only tunable was the resting
349
+ * {@link opacity}, so a theme could not adjust the *difference* between
350
+ * resting and live, only the floor.
351
+ */
352
+ readonly emphasisOpacity?: number;
353
+ /**
354
+ * Fill for a segment **not** in a non-empty selection set — the themed
355
+ * de-emphasis ([PND-MULTISEL], RFC A2.3). Unset ⇒ nothing dims, so this is
356
+ * opt-in and back-compatible. Unlike {@link highlight}, this **does** apply
357
+ * over {@link binFills}: dimming is about recession, not identity, so a
358
+ * zone-coloured bar can recede without its colour changing meaning — the
359
+ * dimmed value simply replaces it while it is out of the set.
360
+ */
361
+ readonly dimmed?: string;
362
+ /**
363
+ * Per-group {@link dimmed}, parallel to {@link fills} — the resolved
364
+ * `theme.bar.default.groupsDimmed` ramp. Set only for a **group-ramped**
365
+ * stack; `undefined` falls back to the flat {@link dimmed}.
366
+ *
367
+ * A stack dimmed to one colour stops being a stack — the segment boundaries
368
+ * vanish and the receded bins read as solid blocks, which is exactly the
369
+ * structure a selection wants to keep visible for comparison.
370
+ */
371
+ readonly dimmedFills?: readonly string[];
372
+ /**
373
+ * Per-group {@link hover}, parallel to {@link fills} — the resolved
374
+ * `theme.bar.default.groupsHover` ramp. Set only for a **group-ramped**
375
+ * stack; `undefined` falls back to the flat {@link hover}.
376
+ *
377
+ * A single hover colour repaints the pointed-at segment in a hue belonging
378
+ * to another group, so hovering erases the ramp exactly where the reader is
379
+ * looking — and block-scoped hover erases the whole bin.
380
+ */
381
+ readonly hoverFills?: readonly string[];
382
+ /**
383
+ * Whether {@link fills} carry **group identity** (a resolved group ramp)
384
+ * rather than being one repeated role colour.
385
+ *
386
+ * When they do, a selected segment keeps its own fill and the outline plus
387
+ * the receded neighbours are the cue — the same exclusion {@link binFills}
388
+ * already gets, and for the same reason: replacing a meaning-carrying colour
389
+ * with the flat `highlight` destroys the thing the selection is *about*.
390
+ */
391
+ readonly groupColored?: boolean;
225
392
  }
226
393
  /** The narrowed selection / hover identity a stacked segment matches against:
227
394
  * the series `id`, the bin's `begin` (its `key`), and the group (its `label`).
@@ -235,14 +402,20 @@ export interface StackMark {
235
402
  }
236
403
  /**
237
404
  * The `[min, max]` extent of the **value (stacked) axis**. For a true multi-group
238
- * stack it is `[0, maxTotal]`, where `maxTotal` is the tallest bin's summed finite
239
- * non-negative segments. For a **single-group** series (`G === 1` — the plain /
405
+ * stack it is `[minNegTotal, maxPosTotal]` each bin's positive segments summed
406
+ * upward and its negative segments summed downward, tracked separately
407
+ * ([PND-SIGNSTACK]). For a **single-group** series (`G === 1` — the plain /
240
408
  * categorical bar case) it spans the values' own `[min, max]`, so a **negative**
241
409
  * bar's floor is in the domain (segments below the baseline stay visible). `0` is
242
410
  * always pulled in so the bars rest on a visible baseline (the bar analog of
243
411
  * {@link barExtent}). An empty / all-gap series returns `[0, 1]` so the axis still
244
412
  * has a usable domain. Feeds the y auto-fit for a vertical histogram, the x
245
413
  * auto-fit for a horizontal one.
414
+ *
415
+ * The negative half is new: this used to sum only positives, matching a draw
416
+ * path that dropped negative segments outright. Both halves changed together —
417
+ * an extent that stopped at `0` below would clip the very segments the draw
418
+ * path now emits.
246
419
  */
247
420
  export declare function stackValueExtent(ss: StackedBarSeries): [number, number];
248
421
  /**
@@ -299,7 +472,7 @@ export declare function segmentRect(ss: StackedBarSeries, b: number, g: number,
299
472
  *
300
473
  * O(N·G) over bins × groups, one fill (+ optional stroke) per drawn segment.
301
474
  */
302
- export declare function drawStacks(ctx: CanvasRenderingContext2D, ss: StackedBarSeries, orientation: Orientation, xScale: Scale, yScale: Scale, style: StackStyle, gapPx: number, minSpanPx: number, seriesId: string | undefined, selection: StackMark | null, hover: StackMark | null): void;
475
+ export declare function drawStacks(ctx: CanvasRenderingContext2D, ss: StackedBarSeries, orientation: Orientation, xScale: Scale, yScale: Scale, style: StackStyle, gapPx: number, minSpanPx: number, seriesId: string | undefined, selection: readonly StackMark[], hover: readonly StackMark[], banding?: BandLadder, spans?: readonly SpanSelection[]): void;
303
476
  /**
304
477
  * Hit-test plot-pixel `(px, py)` against `ss`'s stacked segments — the **first**
305
478
  * segment whose rect contains the point, or `null`. The geometry is