@pond-ts/charts 0.57.0 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/API.md +576 -0
  2. package/CHANGELOG.md +1213 -1
  3. package/dist/AreaChart.d.ts +12 -1
  4. package/dist/AreaChart.js +131 -13
  5. package/dist/BarChart.d.ts +56 -7
  6. package/dist/BarChart.js +263 -39
  7. package/dist/BarList.d.ts +85 -5
  8. package/dist/BarList.js +25 -4
  9. package/dist/BoxList.d.ts +70 -3
  10. package/dist/BoxList.js +21 -7
  11. package/dist/BoxPlot.d.ts +2 -1
  12. package/dist/BoxPlot.js +101 -9
  13. package/dist/Candlestick.d.ts +13 -1
  14. package/dist/Candlestick.js +89 -3
  15. package/dist/ChartContainer.d.ts +36 -48
  16. package/dist/ChartContainer.js +465 -59
  17. package/dist/ChartRow.d.ts +9 -2
  18. package/dist/ChartRow.js +176 -14
  19. package/dist/HeatMap.d.ts +176 -0
  20. package/dist/HeatMap.js +344 -0
  21. package/dist/Layers.d.ts +5 -1
  22. package/dist/Layers.js +1014 -253
  23. package/dist/Legend.js +8 -4
  24. package/dist/LineChart.d.ts +18 -1
  25. package/dist/LineChart.js +165 -4
  26. package/dist/ListTable.d.ts +30 -3
  27. package/dist/ListTable.js +381 -23
  28. package/dist/ScatterChart.d.ts +3 -2
  29. package/dist/ScatterChart.js +68 -4
  30. package/dist/XAxis.js +40 -22
  31. package/dist/YAxis.d.ts +58 -2
  32. package/dist/YAxis.js +3 -1
  33. package/dist/area.d.ts +34 -1
  34. package/dist/area.js +88 -1
  35. package/dist/bars.d.ts +67 -6
  36. package/dist/bars.js +250 -35
  37. package/dist/box.d.ts +2 -2
  38. package/dist/box.js +158 -40
  39. package/dist/brush.d.ts +142 -0
  40. package/dist/brush.js +179 -0
  41. package/dist/child-index.d.ts +27 -0
  42. package/dist/child-index.js +57 -0
  43. package/dist/context.d.ts +870 -39
  44. package/dist/cursors.d.ts +161 -0
  45. package/dist/cursors.js +503 -0
  46. package/dist/data.d.ts +38 -0
  47. package/dist/data.js +43 -0
  48. package/dist/decimate.d.ts +78 -1
  49. package/dist/decimate.js +157 -0
  50. package/dist/format.d.ts +15 -0
  51. package/dist/format.js +16 -1
  52. package/dist/heat.d.ts +163 -0
  53. package/dist/heat.js +659 -0
  54. package/dist/index.d.ts +13 -4
  55. package/dist/index.js +27 -0
  56. package/dist/line.d.ts +137 -0
  57. package/dist/line.js +328 -0
  58. package/dist/ohlc.d.ts +16 -1
  59. package/dist/ohlc.js +93 -4
  60. package/dist/range.d.ts +14 -1
  61. package/dist/range.js +24 -3
  62. package/dist/scatter.d.ts +17 -9
  63. package/dist/scatter.js +221 -33
  64. package/dist/select.d.ts +13 -5
  65. package/dist/select.js +14 -6
  66. package/dist/selection-fixtures.d.ts +174 -0
  67. package/dist/selection-fixtures.js +569 -0
  68. package/dist/selection-stories.d.ts +73 -0
  69. package/dist/selection-stories.js +301 -0
  70. package/dist/selectors.d.ts +316 -0
  71. package/dist/selectors.js +391 -0
  72. package/dist/span.d.ts +122 -0
  73. package/dist/span.js +203 -0
  74. package/dist/sweep.d.ts +154 -0
  75. package/dist/sweep.js +282 -0
  76. package/dist/theme.d.ts +510 -5
  77. package/dist/theme.js +217 -41
  78. package/dist/tracker.d.ts +6 -0
  79. package/dist/tracker.js +6 -0
  80. package/dist/tradingAxis.fixture.d.ts +78 -0
  81. package/dist/tradingAxis.fixture.js +215 -0
  82. package/dist/useChartLegend.js +18 -3
  83. package/dist/yticks.d.ts +3 -0
  84. package/dist/yticks.js +104 -0
  85. package/package.json +6 -5
@@ -3,8 +3,15 @@ import { type CursorMode } from './context.js';
3
3
  export interface ChartRowProps {
4
4
  /** Row height in CSS pixels. */
5
5
  height: number;
6
- /** Cursor presentation for this row, overriding the container's default
7
- * ({@link ChartContainerProps.cursor}). Omit to inherit. See {@link CursorMode}. */
6
+ /**
7
+ * Cursor presentation for this row, overriding the container's default
8
+ * ({@link ChartContainerProps.cursor}). Omit to inherit. See {@link CursorMode}.
9
+ *
10
+ * @deprecated Mount a cursor component **inside the row** instead
11
+ * (`<ChartRow><CrosshairCursor /> …</ChartRow>`) — the per-row override with
12
+ * the same nearest-mount-wins semantics. Works for one more minor; a mounted
13
+ * cursor in the row overrides this prop.
14
+ */
8
15
  cursor?: CursorMode;
9
16
  children?: ReactNode;
10
17
  }
package/dist/ChartRow.js CHANGED
@@ -1,14 +1,51 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, cloneElement, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
- import { scaleLinear, scaleLog } from 'd3-scale';
2
+ import { Children, Fragment, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
+ import { scaleLinear, scaleLog, scaleSymlog } from 'd3-scale';
4
4
  import { isDev } from './dev.js';
5
+ import { useIndexedChildren } from './child-index.js';
5
6
  import { logAxisWarning, needsExtents, resolveYDomain } from './domain.js';
6
7
  import { resolveAxisFormat } from './format.js';
7
8
  import { resolveYTickCount } from './yticks.js';
8
9
  import { placeAxisSlots } from './slots.js';
9
10
  import { useSlotKey } from './use-slot-key.js';
11
+ import { LegacyCursor } from './cursors.js';
10
12
  import { YAxis } from './YAxis.js';
11
13
  import { ContainerContext, RowContext, } from './context.js';
14
+ /**
15
+ * `scale="symlog"`'s default linear window — the knee at **2% of the domain's
16
+ * largest magnitude** ([PND-SYMLOG]). Chosen because it is what the reporting
17
+ * consumer's own transform used (`maxAbs / 50`), and confirmed with them as
18
+ * generalizing: every caller they had passed the data's own max-abs, and none
19
+ * had a case for an absolute constant.
20
+ */
21
+ const DEFAULT_LINEAR_WINDOW = 0.02;
22
+ /**
23
+ * `scaleSymlog`'s `constant` (the linear window's half-width in data units) for
24
+ * an axis's domain-relative {@link YAxisProps.linearWindow} ([PND-SYMLOG]).
25
+ *
26
+ * **The clamp is the point.** d3's symlog transform is
27
+ * `sign(x)·log1p(|x / constant|)`, so a `constant` of `0` — or of
28
+ * `Number.MIN_VALUE`, which the first version of this "clamped" to — divides
29
+ * every sample by ~zero, and `(∞ − ∞) / (∞ − ∞)` makes **every mapped pixel
30
+ * `NaN`**: a blank plot, `NaN` gridline coordinates, no error. That is strictly
31
+ * worse than the mistake it was guarding, so an unusable fraction (non-finite,
32
+ * `<= 0`, or `> 1`) falls back to the **default** and dev-warns (see the
33
+ * `linearWindow` diagnostics below) rather than being nudged to a value that
34
+ * technically satisfies `> 0`.
35
+ *
36
+ * A degenerate all-zero domain has no magnitude to take a fraction *of*, so the
37
+ * knee falls back to `1`; the axis is linear across it either way.
38
+ */
39
+ function symlogConstant(linearWindow, lo, hi) {
40
+ const usable = linearWindow !== undefined &&
41
+ Number.isFinite(linearWindow) &&
42
+ linearWindow > 0 &&
43
+ linearWindow <= 1;
44
+ const fraction = usable ? linearWindow : DEFAULT_LINEAR_WINDOW;
45
+ const maxAbs = Math.max(Math.abs(lo), Math.abs(hi));
46
+ const knee = fraction * maxAbs;
47
+ return Number.isFinite(knee) && knee > 0 ? knee : 1;
48
+ }
12
49
  /** Sentinel id for the implicit axis a row gets when no `<YAxis>` is declared. */
13
50
  const IMPLICIT_AXIS_ID = '__default__';
14
51
  /** Element-wise compare of two optional number arrays (an axis's tick values) —
@@ -41,6 +78,10 @@ function axisSpecEqual(a, b) {
41
78
  a.side === b.side &&
42
79
  a.width === b.width &&
43
80
  a.scale === b.scale &&
81
+ // Easy to forget when adding a scale-shaping field, and the failure is
82
+ // silent: an axis whose `linearWindow` alone changed would be discarded by
83
+ // the guard and keep drawing with the previous knee.
84
+ a.linearWindow === b.linearWindow &&
44
85
  // Object.is (not ===) so a degenerate NaN bound compares equal to itself and
45
86
  // doesn't re-register every render.
46
87
  Object.is(a.min, b.min) &&
@@ -104,6 +145,18 @@ export function ChartRow({ height, cursor, children }) {
104
145
  const { registerRow } = container;
105
146
  useEffect(() => registerRow(rowKey), [registerRow, rowKey]);
106
147
  const isFirstRow = container.firstRowKey === rowKey;
148
+ // Deprecation notice for the legacy `cursor` prop (dev, once per row): the
149
+ // per-row override is now a cursor component mounted inside the row. The
150
+ // prop keeps working via the shim rendered below.
151
+ const warnedCursorRef = useRef(false);
152
+ useEffect(() => {
153
+ if (!isDev || cursor === undefined || warnedCursorRef.current)
154
+ return;
155
+ warnedCursorRef.current = true;
156
+ console.warn(`[pond-charts] <ChartRow cursor="${cursor}"> is deprecated (it keeps ` +
157
+ 'working this minor, removed next) — mount the cursor component ' +
158
+ 'inside the row instead (docs/rfcs/interaction.md §9).');
159
+ }, [cursor]);
107
160
  // Keyed by a stable per-instance id (Map preserves insertion order; setting an
108
161
  // existing key updates in place). So a re-register on a prop change keeps the
109
162
  // entry's slot — the axis-default (first axis) and layer z-order stay stable
@@ -222,6 +275,7 @@ export function ChartRow({ height, cursor, children }) {
222
275
  // One y-scale per axis. A layer counts toward an axis when its (late-resolved)
223
276
  // axis id matches; `resolveYDomain` handles the auto-fit + empty/flat/inverted
224
277
  // edges. yExtent() is O(points), so only walk the layers when a bound auto-fits.
278
+ const { k: yk, ty: yty } = container.yTransform;
225
279
  const yScales = useMemo(() => {
226
280
  const map = new Map();
227
281
  for (const ax of effectiveAxes) {
@@ -238,11 +292,37 @@ export function ChartRow({ height, cursor, children }) {
238
292
  // `scaleLog` and `scaleLinear` share the call/ticks/tickFormat/invert
239
293
  // surface every consumer uses (see `YScale`), so choosing between them
240
294
  // 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]));
295
+ // `scaleSymlog` shares the same call/ticks/tickFormat/invert surface, so
296
+ // as with log, choosing it here is the whole of symlog support — no draw
297
+ // layer branches on it. Its `constant` (the linear window's half-width) is
298
+ // resolved from the axis's DOMAIN-RELATIVE fraction: absolute would need
299
+ // recomputing whenever the domain moved ([PND-SYMLOG]).
300
+ const base = ax.scale === 'log'
301
+ ? scaleLog()
302
+ : ax.scale === 'symlog'
303
+ ? scaleSymlog().constant(symlogConstant(ax.linearWindow, lo, hi))
304
+ : scaleLinear();
305
+ const s = base.domain([lo, hi]).range([height, topHeader]);
306
+ // 2-D pan/zoom is carried as a **pixel** transform (`k`, `ty`) so one
307
+ // gesture serves every axis in the row whatever its units, and all of them
308
+ // zoom by the same factor — which is what fixes the aspect ratio. But it is
309
+ // applied by narrowing the **domain** to the window that transform makes
310
+ // visible, not by stretching the range.
311
+ //
312
+ // That distinction is not cosmetic. Stretching the range leaves the tick
313
+ // generator working on the FULL domain, so ticks outside the view get
314
+ // clamped onto the plot edge and pile up — 350 and 400 printed on top of
315
+ // each other in the first cut. Narrowing the domain means ticks, padding
316
+ // and every downstream reader see an ordinary axis over the visible
317
+ // window, and none of them need to know a transform exists.
318
+ if (yk !== 1 || yty !== 0) {
319
+ const at = (px) => +s.invert((px - yty) / yk);
320
+ s.domain([at(height), at(topHeader)]);
321
+ }
322
+ map.set(ax.id, s);
243
323
  }
244
324
  return map;
245
- }, [effectiveAxes, layerList, height, defaultAxisId, topHeader]);
325
+ }, [effectiveAxes, layerList, height, defaultAxisId, topHeader, yk, yty]);
246
326
  // Dev-mode diagnostics for a `scale="log"` axis (see `logAxisWarning`). Three
247
327
  // things about *where* this sits are load-bearing, each of them a bug the
248
328
  // first version shipped:
@@ -287,6 +367,46 @@ export function ChartRow({ height, cursor, children }) {
287
367
  }
288
368
  }
289
369
  }, [effectiveAxes, layerList, defaultAxisId]);
370
+ // Dev-mode diagnostics for `linearWindow` ([PND-SYMLOG]). Both cases it covers
371
+ // are *silent* otherwise, which is the whole reason it exists: a
372
+ // `linearWindow` on a linear or log axis is read by nothing, and a fraction
373
+ // outside `(0, 1]` is unusable as a knee (see `symlogConstant`), so the axis
374
+ // silently draws with the **default** window instead of the one asked for.
375
+ // Neither throws and neither looks broken — it just isn't the scale the call
376
+ // site asked for.
377
+ //
378
+ // Same shape as the log diagnostics above: an effect rather than the memo, and
379
+ // deduped in `warnedRef` under a suffixed key so it cannot collide with the
380
+ // log message stored under the bare axis id.
381
+ useEffect(() => {
382
+ if (!isDev)
383
+ return;
384
+ const warned = warnedRef.current;
385
+ for (const ax of effectiveAxes) {
386
+ const key = `${ax.id}:linearWindow`;
387
+ const w = ax.linearWindow;
388
+ let message = null;
389
+ if (w !== undefined && ax.scale !== 'symlog') {
390
+ message =
391
+ `<YAxis id="${ax.id}"> sets linearWindow=${w} but scale is ` +
392
+ `"${ax.scale}" — linearWindow only applies to scale="symlog" and is ` +
393
+ `ignored here.`;
394
+ }
395
+ else if (w !== undefined && (!Number.isFinite(w) || w <= 0 || w > 1)) {
396
+ message =
397
+ `<YAxis id="${ax.id}"> has linearWindow=${w}, outside (0, 1] — the ` +
398
+ `axis is drawing with the default ${DEFAULT_LINEAR_WINDOW} instead. ` +
399
+ `It is a fraction of the domain's largest magnitude, so 0.02 means ` +
400
+ `"linear through 2% of the domain".`;
401
+ }
402
+ if (message === null)
403
+ warned.delete(key);
404
+ else if (warned.get(key) !== message) {
405
+ warned.set(key, message);
406
+ console.warn(message);
407
+ }
408
+ }
409
+ }, [effectiveAxes]);
290
410
  // Resolved auto-tick count per axis — explicit `<YAxis tickCount>` else
291
411
  // height-derived (see resolveYTickCount). The single source the `<YAxis>`
292
412
  // labels, the readout formatter (below), and the `Layers` gridlines all read,
@@ -369,9 +489,14 @@ export function ChartRow({ height, cursor, children }) {
369
489
  // Inject each direct child's JSX position so axes register their declaration
370
490
  // order (the default-axis source). `<Layers>` receives an index too (harmless
371
491
  // — it's not an axis) and injects its own into the draw layers.
372
- const indexedChildren = Children.map(children, (child, index) => isValidElement(child)
373
- ? cloneElement(child, { index })
374
- : child);
492
+ //
493
+ // A fragment child costs more here than it does in `<Layers>`: the axes
494
+ // inside it lose the index *and* the `child.type === YAxis` sort below cannot
495
+ // see through it, so they fall into `plotEls` and render in the middle of the
496
+ // row instead of in a gutter. Hence the same warning on both.
497
+ const indexedChildren = useIndexedChildren(children, '<ChartRow>', 'the axes inside it lose their declaration order (the default-axis pick ' +
498
+ 'and slot order within a side) and are placed in the plot rather than a ' +
499
+ 'gutter, because the side sort cannot see through a fragment');
375
500
  // Place axes by their `side`, not by JSX author position — so a `side="right"`
376
501
  // axis always renders right of the plot (and a left axis left), **consistent
377
502
  // with the side-based gutter reservation above**. (Author position only
@@ -383,20 +508,57 @@ export function ChartRow({ height, cursor, children }) {
383
508
  const leftAxisEls = [];
384
509
  const plotEls = [];
385
510
  const rightAxisEls = [];
511
+ let axisInsideWrapper = false;
386
512
  for (const child of indexedChildren ?? []) {
387
513
  if (isValidElement(child) && child.type === YAxis) {
388
514
  const side = child.props.side ?? 'left';
389
515
  (side === 'right' ? rightAxisEls : leftAxisEls).push(child);
390
516
  }
391
517
  else {
518
+ // A `<Selector>`/`<MultiSelector>` is a legitimate row child now that it
519
+ // wraps its scope (RFC A10.1) — but it must wrap the row's `<Layers>`,
520
+ // NOT its axes: the sort above matches on `child.type`, so an axis
521
+ // nested inside any wrapper is invisible to it and lands in the plot
522
+ // column. The fragment warning cannot catch this one (a selector is a
523
+ // real element, not a fragment), and the failure is silent, so look one
524
+ // level down for the mistake the docs could invite.
525
+ // A fragment is skipped here: `useIndexedChildren` already warns about
526
+ // it and names the same gutter consequence, so checking it too would
527
+ // print two warnings for one mistake.
528
+ if (isDev &&
529
+ isValidElement(child) &&
530
+ child.type !== Fragment &&
531
+ !axisInsideWrapper) {
532
+ const nested = child.props.children;
533
+ if (nested !== undefined) {
534
+ for (const g of Children.toArray(nested)) {
535
+ if (isValidElement(g) && g.type === YAxis) {
536
+ axisInsideWrapper = true;
537
+ break;
538
+ }
539
+ }
540
+ }
541
+ }
392
542
  plotEls.push(child);
393
543
  }
394
544
  }
395
- return (_jsx(RowContext.Provider, { value: frame, children: _jsxs("div", { style: {
396
- display: 'flex',
397
- flexDirection: 'row',
398
- width: `${container.width}px`,
399
- height: `${height}px`,
400
- }, children: [leftPad > 0 && _jsx("div", { style: { flex: `0 0 ${leftPad}px` } }), leftAxisEls, plotEls, rightAxisEls, rightPad > 0 && _jsx("div", { style: { flex: `0 0 ${rightPad}px` } })] }) }));
545
+ const warnedAxisWrapperRef = useRef(false);
546
+ useEffect(() => {
547
+ if (!isDev || !axisInsideWrapper || warnedAxisWrapperRef.current)
548
+ return;
549
+ warnedAxisWrapperRef.current = true;
550
+ console.warn('[pond-charts] a <YAxis> is nested inside another element in this ' +
551
+ '<ChartRow>, so it renders in the plot column instead of a gutter — ' +
552
+ '<ChartRow> places axes by matching its own children, and cannot see ' +
553
+ 'through a wrapper. A row-scoped <Selector>/<MultiSelector> should ' +
554
+ "wrap the row's <Layers>, leaving each <YAxis> a direct child of the " +
555
+ '<ChartRow>.');
556
+ }, [axisInsideWrapper]);
557
+ return (_jsxs(RowContext.Provider, { value: frame, children: [cursor !== undefined && (_jsx(LegacyCursor, { mode: cursor, showTime: container.cursorTime, snap: container.crosshairSnap })), _jsxs("div", { style: {
558
+ display: 'flex',
559
+ flexDirection: 'row',
560
+ width: `${container.width}px`,
561
+ height: `${height}px`,
562
+ }, children: [leftPad > 0 && _jsx("div", { style: { flex: `0 0 ${leftPad}px` } }), leftAxisEls, plotEls, rightAxisEls, rightPad > 0 && _jsx("div", { style: { flex: `0 0 ${rightPad}px` } })] })] }));
401
563
  }
402
564
  //# sourceMappingURL=ChartRow.js.map
@@ -0,0 +1,176 @@
1
+ import { ValueSeries } from 'pond-ts';
2
+ import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
3
+ import type { DecimateOption } from './decimate.js';
4
+ import type { Orientation } from './bars.js';
5
+ import { type HeatNoData, type HeatScale } from './heat.js';
6
+ export interface HeatMapProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
7
+ /**
8
+ * The source series. A **`TimeSeries`** puts time intervals on x, a
9
+ * **`ValueSeries`** puts value intervals on x — inferred, no axis-kind prop,
10
+ * the same rule `<BarChart>` uses.
11
+ *
12
+ * Because the cell spans are the ordinary bin spans, the whole of pond's
13
+ * binning machinery applies unchanged: `aggregate` over a trading calendar
14
+ * with sessions, `Sequence.calendar` day/week/month buckets, `byColumn` value
15
+ * bands. The heat map inherits all of it by having no opinion about x.
16
+ */
17
+ series: TimeSeries<S> | ValueSeries<VS>;
18
+ /**
19
+ * The numeric columns forming the **rows**, bottom → top — one row per
20
+ * column. Give one column for a single-row **stripe**; a stripe is just
21
+ * `columns.length === 1`, drawn by the same path.
22
+ *
23
+ * The y dimension must be columns, which is the layer's one real constraint.
24
+ * A month-of-year grid means a column per month; a per-city grid means a
25
+ * column per city (`pivotByGroup`'s long→wide output, or `partitionBy`
26
+ * reshaped). That keeps the second dimension in the data model, where pond's
27
+ * own reshaping operators can produce it, rather than inventing a
28
+ * chart-level pivot.
29
+ *
30
+ * Row `0` is at the **bottom**, matching the band-axis convention; reverse
31
+ * the list to read top-down.
32
+ */
33
+ columns: readonly string[];
34
+ /**
35
+ * The colour ramp, low → high. The value domain splits into `colors.length`
36
+ * equal bands and a cell takes its band's colour (see {@link bandedColor}).
37
+ * A diverging ramp is just a ramp whose middle is pale.
38
+ */
39
+ colors: readonly string[];
40
+ /**
41
+ * Pin the colour domain as `[lo, hi]`. **Omitted ⇒ the finite extent across
42
+ * the whole grid**, so every row is read against one scale and rows are
43
+ * comparable to each other.
44
+ *
45
+ * Pin it when two charts must be read against each other, or when the window
46
+ * is a slice of a longer record and the colours should not re-mean themselves
47
+ * as it moves — a colour scale has no tick labels to reveal that it moved.
48
+ */
49
+ domain?: readonly [number, number];
50
+ /**
51
+ * Which axis carries the **bins**. `'vertical'` (the default) puts them on
52
+ * **x** with the columns as rows down y; `'horizontal'` transposes — bins run
53
+ * down **y** and the columns become the categories along x.
54
+ *
55
+ * The transpose is cheaper here than for `<BarChart>`, because a heat map has
56
+ * two *position* axes and no value axis: nothing has to change which scale it
57
+ * is measured against, only which one is horizontal on the canvas.
58
+ *
59
+ * Reach for `'horizontal'` when the binned dimension is the long one and the
60
+ * columns are few — a gene-expression matrix (thousands of gene buckets, a
61
+ * handful of samples) is the canonical case, and it is the orientation that
62
+ * literature draws. Note that the bins still come from the **key** axis, so
63
+ * the genes must be the series' rows and the samples its columns; the
64
+ * ordinary binning operators (`byColumn`, `aggregate`) then bucket them.
65
+ */
66
+ orientation?: Orientation;
67
+ /** Semantic identifier — picks geometry defaults off `theme.bar[as]`. */
68
+ as?: string;
69
+ /** Which `<YAxis>` (by `id`) this layer scales against. */
70
+ axis?: string;
71
+ /** Px inset around each cell. **Omitted ⇒ `0`**, tiling flush. */
72
+ gap?: number;
73
+ /**
74
+ * How value maps onto the ramp's bands. **Omitted ⇒ `'linear'`** — equal-width
75
+ * bands across the domain.
76
+ *
77
+ * `'log'` gives equal-*ratio* bands, which is what a quantity spanning orders
78
+ * of magnitude needs. US measles incidence runs from ~2,900 per 100k before
79
+ * the vaccine to under 1 after it; linear banding over eight colours puts
80
+ * everything below ~360 into a single band — the entire post-1965 record,
81
+ * which is the half of that chart carrying the finding.
82
+ *
83
+ * Bands on `log1p` of the offset from the domain's floor, so a value **at**
84
+ * the floor is a real band rather than `-Infinity`. Zero is the case that
85
+ * needs it: an incidence grid is mostly zeros once a disease is eliminated,
86
+ * and those cells are the point.
87
+ */
88
+ scale?: HeatScale;
89
+ /**
90
+ * How a cell with no value is drawn. **Omitted ⇒ `'blank'`** — nothing is
91
+ * painted and the background shows through, which is right when a hole simply
92
+ * means "outside the record".
93
+ *
94
+ * `'hatch'` draws diagonal lines in the theme's grid colour. Reach for it when
95
+ * *missing* and *low* would otherwise be indistinguishable — on a pale ramp
96
+ * "draw nothing" reads as the bottom of the scale, so a state with no
97
+ * surveillance yet looks exactly like a state reporting zero cases. No ramp
98
+ * colour can be mistaken for hatching, which is why it is the convention.
99
+ *
100
+ * Suppressed while decimated: an aggregated cell is not a hole.
101
+ */
102
+ noData?: HeatNoData;
103
+ /**
104
+ * Viewport decimation — **on by default**, and a perf knob rather than a
105
+ * rendering-style one.
106
+ *
107
+ * Once the visible cells are denser than ~2 per device pixel they overlap and
108
+ * overpaint each other, so what you see is already one cell per column picked
109
+ * by draw order. Decimation replaces that with the **mean** per pixel column
110
+ * — what the overdrawn picture resolves to at that size — from `O(W·G)` rects
111
+ * instead of `O(V·G)`. A 20,000-bin grid over an 800px plot goes from ~48ms
112
+ * to a fraction of it.
113
+ *
114
+ * `{ threshold }` moves the cells-per-pixel gate (default `2`). `false` draws
115
+ * every visible cell — reach for it if you are screenshotting at a device
116
+ * pixel ratio the gate can't see, not to "keep the data honest": undecimated
117
+ * at this density is the less honest picture.
118
+ *
119
+ * While decimated, per-cell selection and hover outlines are suppressed (a
120
+ * sub-pixel ring isn't visible anyway) and interaction still reads the source
121
+ * grid.
122
+ */
123
+ decimate?: DecimateOption;
124
+ /**
125
+ * Stable identity — **gates selection + hover**, as every layer's does. Both
126
+ * channels are sets, and **every** cell a member names outlines (bin `key` —
127
+ * or the stable per-bin `mark` — plus the row `label`), so a multi-cell pin or
128
+ * a drag-sweep hover lights all of it. A cell in both reads as selected.
129
+ */
130
+ id?: string;
131
+ /** @internal Declaration position, injected by `Layers`. Do not set. */
132
+ index?: number;
133
+ }
134
+ /**
135
+ * A **heat-map draw layer**: a grid of cells, bins along x and the series'
136
+ * columns down y, colour carrying the aggregate ([PND-HEATMAP]).
137
+ *
138
+ * ```tsx
139
+ * // A stripe — one column.
140
+ * <HeatMap series={hourly} columns={['count']} colors={ramp} id="load" />
141
+ *
142
+ * // A grid — one column per row.
143
+ * <HeatMap series={byCity} columns={['London', 'Paris', 'Berlin']} colors={ramp} />
144
+ * ```
145
+ *
146
+ * **No reader of its own.** It builds on `stacksFromColumns`, whose output is
147
+ * already a heat map's data shape — bin spans, named rows, a row-major value
148
+ * grid. That covers all four shapes pond can express today (`TimeSeries` or
149
+ * `ValueSeries` × one column or many), and the stripe is simply `G === 1`, so
150
+ * there is one draw path rather than two.
151
+ *
152
+ * **The readout is the point.** A cell carries its value, so hover and click
153
+ * report it and the readout pill takes the cell's own colour. The bar-based
154
+ * workaround this replaces cannot: its bars are a constant-height column
155
+ * carrying no value, so the number has to be looked up out-of-band.
156
+ *
157
+ * **Styling.** Colour is data and comes from `colors`, not the theme. Geometry
158
+ * and the selected-cell treatment are borrowed from
159
+ * `theme.bar[as] ?? theme.bar.default` rather than a new `theme.heat` slot:
160
+ * `ChartTheme`'s slots are required, so adding one is breaking for every custom
161
+ * theme, and the M5 "theme tokens optional-with-default" gate has to land
162
+ * first. Borrowing defers that decision instead of pre-empting it.
163
+ *
164
+ * **Pair it with `<ChartContainer cursor="none">`.** The container's default is
165
+ * the shared vertical line, and on a grid that is a *second, weaker* cursor
166
+ * competing with the one that already works: the cell under the pointer takes an
167
+ * outline, which says both axes at once. The line says only x, and a heat map's
168
+ * x position is rarely the question. The pointer's own crosshair shape plus the
169
+ * cell outline is the whole affordance.
170
+ *
171
+ * **Not built:** a grouped two-level x axis, and cell value labels. The former
172
+ * is axis work that would serve bars equally; the latter is small and
173
+ * independent.
174
+ */
175
+ export declare function HeatMap<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, columns, colors, domain, orientation, as: semantic, axis, gap, scale, noData, decimate, id, index, }: HeatMapProps<S, VS>): null;
176
+ //# sourceMappingURL=HeatMap.d.ts.map