@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.
- package/API.md +576 -0
- package/CHANGELOG.md +1213 -1
- package/dist/AreaChart.d.ts +12 -1
- package/dist/AreaChart.js +131 -13
- package/dist/BarChart.d.ts +56 -7
- package/dist/BarChart.js +263 -39
- package/dist/BarList.d.ts +85 -5
- package/dist/BarList.js +25 -4
- package/dist/BoxList.d.ts +70 -3
- package/dist/BoxList.js +21 -7
- package/dist/BoxPlot.d.ts +2 -1
- package/dist/BoxPlot.js +101 -9
- package/dist/Candlestick.d.ts +13 -1
- package/dist/Candlestick.js +89 -3
- package/dist/ChartContainer.d.ts +36 -48
- package/dist/ChartContainer.js +465 -59
- package/dist/ChartRow.d.ts +9 -2
- package/dist/ChartRow.js +176 -14
- package/dist/HeatMap.d.ts +176 -0
- package/dist/HeatMap.js +344 -0
- package/dist/Layers.d.ts +5 -1
- package/dist/Layers.js +1014 -253
- package/dist/Legend.js +8 -4
- package/dist/LineChart.d.ts +18 -1
- package/dist/LineChart.js +165 -4
- package/dist/ListTable.d.ts +30 -3
- package/dist/ListTable.js +381 -23
- package/dist/ScatterChart.d.ts +3 -2
- package/dist/ScatterChart.js +68 -4
- package/dist/XAxis.js +40 -22
- package/dist/YAxis.d.ts +58 -2
- package/dist/YAxis.js +3 -1
- package/dist/area.d.ts +34 -1
- package/dist/area.js +88 -1
- package/dist/bars.d.ts +67 -6
- package/dist/bars.js +250 -35
- package/dist/box.d.ts +2 -2
- package/dist/box.js +158 -40
- package/dist/brush.d.ts +142 -0
- package/dist/brush.js +179 -0
- package/dist/child-index.d.ts +27 -0
- package/dist/child-index.js +57 -0
- package/dist/context.d.ts +870 -39
- package/dist/cursors.d.ts +161 -0
- package/dist/cursors.js +503 -0
- package/dist/data.d.ts +38 -0
- package/dist/data.js +43 -0
- package/dist/decimate.d.ts +78 -1
- package/dist/decimate.js +157 -0
- package/dist/format.d.ts +15 -0
- package/dist/format.js +16 -1
- package/dist/heat.d.ts +163 -0
- package/dist/heat.js +659 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +27 -0
- package/dist/line.d.ts +137 -0
- package/dist/line.js +328 -0
- package/dist/ohlc.d.ts +16 -1
- package/dist/ohlc.js +93 -4
- package/dist/range.d.ts +14 -1
- package/dist/range.js +24 -3
- package/dist/scatter.d.ts +17 -9
- package/dist/scatter.js +221 -33
- package/dist/select.d.ts +13 -5
- package/dist/select.js +14 -6
- package/dist/selection-fixtures.d.ts +174 -0
- package/dist/selection-fixtures.js +569 -0
- package/dist/selection-stories.d.ts +73 -0
- package/dist/selection-stories.js +301 -0
- package/dist/selectors.d.ts +316 -0
- package/dist/selectors.js +391 -0
- package/dist/span.d.ts +122 -0
- package/dist/span.js +203 -0
- package/dist/sweep.d.ts +154 -0
- package/dist/sweep.js +282 -0
- package/dist/theme.d.ts +510 -5
- package/dist/theme.js +217 -41
- package/dist/tracker.d.ts +6 -0
- package/dist/tracker.js +6 -0
- package/dist/tradingAxis.fixture.d.ts +78 -0
- package/dist/tradingAxis.fixture.js +215 -0
- package/dist/useChartLegend.js +18 -3
- package/dist/yticks.d.ts +3 -0
- package/dist/yticks.js +104 -0
- package/package.json +6 -5
package/dist/ChartRow.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
7
|
-
*
|
|
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,
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|