@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.
- package/CHANGELOG.md +1070 -1
- package/dist/AreaChart.d.ts +12 -1
- package/dist/AreaChart.js +131 -13
- package/dist/BarChart.js +184 -30
- 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 +86 -12
- 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/area.d.ts +34 -1
- package/dist/area.js +88 -1
- package/dist/bars.d.ts +57 -3
- package/dist/bars.js +237 -26
- 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 +859 -33
- package/dist/cursors.d.ts +161 -0
- package/dist/cursors.js +503 -0
- package/dist/decimate.d.ts +78 -1
- package/dist/decimate.js +157 -0
- package/dist/heat.d.ts +163 -0
- package/dist/heat.js +659 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +22 -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/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 +456 -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/package.json +3 -3
package/dist/decimate.d.ts
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
* `xScale` and breaks its subpath on `NaN`) — decimation is a pre-pass that
|
|
46
46
|
* shrinks the point count, not a second renderer.
|
|
47
47
|
*/
|
|
48
|
-
import type { ChartSeries, BandSeries, OhlcSeries, BoxSeries, BarSeries } from './data.js';
|
|
48
|
+
import type { ChartSeries, BandSeries, OhlcSeries, BoxSeries, BarSeries, StackedBarSeries } from './data.js';
|
|
49
49
|
import type { Scale } from './line.js';
|
|
50
50
|
/**
|
|
51
51
|
* A line layer's M4-decimation control (`<LineChart decimate>`). **Default
|
|
@@ -305,6 +305,83 @@ export interface BarColumnEnvelope {
|
|
|
305
305
|
* `resolveBarBaseline`), so the union is honest about the zero line.
|
|
306
306
|
*/
|
|
307
307
|
export declare function decimateBars(cs: BarSeries, xScale: Scale, ctx: CanvasRenderingContext2D, baseline: number, k?: number, visibleCount?: number): BarColumnEnvelope | null;
|
|
308
|
+
/**
|
|
309
|
+
* Decimate a heat-map grid to **one cell per pixel column per row**, each the
|
|
310
|
+
* **mean** of the source cells that fall in it ([PND-HEATMAP]).
|
|
311
|
+
*
|
|
312
|
+
* **Why the mean, and why a heat map can decimate where a coloured bar cannot.**
|
|
313
|
+
* {@link decimateBars} deliberately gives up when `binFills` is set: its
|
|
314
|
+
* reduction is a *geometric* union (`[min, max]` per column), and one rect
|
|
315
|
+
* spanning many differently-coloured bars has no honest colour. A heat map is
|
|
316
|
+
* not in that bind, because its reduction is not geometric. Cells do not form a
|
|
317
|
+
* silhouette — they **composite**: each one covers its own patch of the column,
|
|
318
|
+
* so what the eye receives from a column holding N cells is their area-weighted
|
|
319
|
+
* average. Taking the mean of the *values* and letting the existing ramp colour
|
|
320
|
+
* it is therefore not a statistical choice imposed on the reader; it is what the
|
|
321
|
+
* full-resolution draw already resolves to at this size. Banding the mean also
|
|
322
|
+
* keeps the result **inside the ramp**, so it stays a colour the legend defines
|
|
323
|
+
* (averaging the colours instead would invent off-ramp shades, and would need
|
|
324
|
+
* linear-light care to avoid the usual downsampling darkening).
|
|
325
|
+
*
|
|
326
|
+
* What this **replaces** is worse than it: undecimated, sub-pixel cells are
|
|
327
|
+
* widened to `minWidth` about their midpoints, so they overlap and later draws
|
|
328
|
+
* overpaint earlier ones. The column already showed one cell out of N — chosen
|
|
329
|
+
* by loop order. The mean is both cheaper and more honest than that.
|
|
330
|
+
*
|
|
331
|
+
* **What it loses:** a lone extreme cell among N averages away, exactly as it
|
|
332
|
+
* would in any image downsample. A reader hunting rare spikes should bin the
|
|
333
|
+
* *series* coarsely with `aggregate` and a reducer that says so (`max`), which
|
|
334
|
+
* is the tool that makes the claim explicit and is unaffected by this.
|
|
335
|
+
*
|
|
336
|
+
* Gates on the **visible** cell columns (`visibleCount`), like the bar path.
|
|
337
|
+
* Returns `null` when decimation doesn't apply — below the density threshold, a
|
|
338
|
+
* domainless or non-invertible scale, or no canvas width — and the caller then
|
|
339
|
+
* draws every visible cell. The result carries **no `marks`**: a pixel column
|
|
340
|
+
* aggregates many source bins and so has no stable per-bin identity, which is
|
|
341
|
+
* also why the caller suppresses the live-cell outline while decimated and
|
|
342
|
+
* keeps hit-testing against the source grid ({@link decimateBars} does the same).
|
|
343
|
+
*/
|
|
344
|
+
export declare function decimateHeat(ss: StackedBarSeries, binScale: Scale, ctx: CanvasRenderingContext2D, k?: number, vStart?: number, vEnd?: number,
|
|
345
|
+
/**
|
|
346
|
+
* The bin axis' extent, when it is **not** the x axis. A horizontal heat map
|
|
347
|
+
* puts its bins on y, where the shared helpers do not apply: `scaleRangeWidth`
|
|
348
|
+
* reads `range()[last]`, which is `0` for the usual inverted y range, and
|
|
349
|
+
* `deviceBucketCount` reads the canvas' *width*. Omitted ⇒ the x-axis
|
|
350
|
+
* defaults, which is every other caller.
|
|
351
|
+
*/
|
|
352
|
+
axis?: {
|
|
353
|
+
deviceCount: number;
|
|
354
|
+
spanCss: number;
|
|
355
|
+
}): StackedBarSeries | null;
|
|
356
|
+
/**
|
|
357
|
+
* The **y half** of heat-map decimation: collapse runs of `stride` rows into one,
|
|
358
|
+
* each the mean of the rows it covers ([PND-HEATMAP]).
|
|
359
|
+
*
|
|
360
|
+
* The x half ({@link decimateHeat}) is not enough on its own, and a gene
|
|
361
|
+
* expression matrix is the case that proves it: 10,000 genes x 8 samples is
|
|
362
|
+
* **16.7 rows per pixel row** and only 8 bins, so the column decimator declines
|
|
363
|
+
* and every one of the 80,000 cells is drawn to show ~4,800 distinguishable
|
|
364
|
+
* ones. Whichever axis is oversampled, the argument is the same — rows sharing a
|
|
365
|
+
* pixel row composite, so what the eye receives is their mean.
|
|
366
|
+
*
|
|
367
|
+
* **A fixed integer stride, not a pixel-edge walk.** Source rows are *unit
|
|
368
|
+
* slots* (`[g, g+1]`, which is what lets `binCategories` label them), so they
|
|
369
|
+
* are already uniform in row-index space; a run of `stride` of them is exactly
|
|
370
|
+
* `[r·stride, (r+1)·stride]`. That keeps the y coordinate space **unchanged** —
|
|
371
|
+
* which is load-bearing, because `<YAxis>` scales over `[0, G]` and explicit
|
|
372
|
+
* `{ at, label }` ticks are in those units. Rewriting the row bands the way the
|
|
373
|
+
* x half rewrites bin spans would silently slide every axis label.
|
|
374
|
+
*
|
|
375
|
+
* Returns `null` below the gate (`stride < k` — fewer than `k` rows per device
|
|
376
|
+
* row, where drawing every row is honest and the reduction would not pay), so
|
|
377
|
+
* the caller draws the source rows. The final run is short when `rows` is not a
|
|
378
|
+
* multiple of `stride`; it averages what is there.
|
|
379
|
+
*/
|
|
380
|
+
export declare function decimateHeatRows(values: Float64Array, bins: number, rows: number, deviceRows: number, k?: number): {
|
|
381
|
+
values: Float64Array;
|
|
382
|
+
rows: number;
|
|
383
|
+
stride: number;
|
|
384
|
+
} | null;
|
|
308
385
|
/**
|
|
309
386
|
* Decimate a **uniform** scatter to one representative mark per occupied
|
|
310
387
|
* **pixel cell** ([PND-MARKDEC] scatter half) — the marks analog of
|
package/dist/decimate.js
CHANGED
|
@@ -607,6 +607,163 @@ export function decimateBars(cs, xScale, ctx, baseline, k = 2, visibleCount = cs
|
|
|
607
607
|
}
|
|
608
608
|
return { begin, end, lo, hi, length: W };
|
|
609
609
|
}
|
|
610
|
+
/**
|
|
611
|
+
* Decimate a heat-map grid to **one cell per pixel column per row**, each the
|
|
612
|
+
* **mean** of the source cells that fall in it ([PND-HEATMAP]).
|
|
613
|
+
*
|
|
614
|
+
* **Why the mean, and why a heat map can decimate where a coloured bar cannot.**
|
|
615
|
+
* {@link decimateBars} deliberately gives up when `binFills` is set: its
|
|
616
|
+
* reduction is a *geometric* union (`[min, max]` per column), and one rect
|
|
617
|
+
* spanning many differently-coloured bars has no honest colour. A heat map is
|
|
618
|
+
* not in that bind, because its reduction is not geometric. Cells do not form a
|
|
619
|
+
* silhouette — they **composite**: each one covers its own patch of the column,
|
|
620
|
+
* so what the eye receives from a column holding N cells is their area-weighted
|
|
621
|
+
* average. Taking the mean of the *values* and letting the existing ramp colour
|
|
622
|
+
* it is therefore not a statistical choice imposed on the reader; it is what the
|
|
623
|
+
* full-resolution draw already resolves to at this size. Banding the mean also
|
|
624
|
+
* keeps the result **inside the ramp**, so it stays a colour the legend defines
|
|
625
|
+
* (averaging the colours instead would invent off-ramp shades, and would need
|
|
626
|
+
* linear-light care to avoid the usual downsampling darkening).
|
|
627
|
+
*
|
|
628
|
+
* What this **replaces** is worse than it: undecimated, sub-pixel cells are
|
|
629
|
+
* widened to `minWidth` about their midpoints, so they overlap and later draws
|
|
630
|
+
* overpaint earlier ones. The column already showed one cell out of N — chosen
|
|
631
|
+
* by loop order. The mean is both cheaper and more honest than that.
|
|
632
|
+
*
|
|
633
|
+
* **What it loses:** a lone extreme cell among N averages away, exactly as it
|
|
634
|
+
* would in any image downsample. A reader hunting rare spikes should bin the
|
|
635
|
+
* *series* coarsely with `aggregate` and a reducer that says so (`max`), which
|
|
636
|
+
* is the tool that makes the claim explicit and is unaffected by this.
|
|
637
|
+
*
|
|
638
|
+
* Gates on the **visible** cell columns (`visibleCount`), like the bar path.
|
|
639
|
+
* Returns `null` when decimation doesn't apply — below the density threshold, a
|
|
640
|
+
* domainless or non-invertible scale, or no canvas width — and the caller then
|
|
641
|
+
* draws every visible cell. The result carries **no `marks`**: a pixel column
|
|
642
|
+
* aggregates many source bins and so has no stable per-bin identity, which is
|
|
643
|
+
* also why the caller suppresses the live-cell outline while decimated and
|
|
644
|
+
* keeps hit-testing against the source grid ({@link decimateBars} does the same).
|
|
645
|
+
*/
|
|
646
|
+
export function decimateHeat(ss, binScale, ctx, k = 2, vStart = 0, vEnd = ss.length,
|
|
647
|
+
/**
|
|
648
|
+
* The bin axis' extent, when it is **not** the x axis. A horizontal heat map
|
|
649
|
+
* puts its bins on y, where the shared helpers do not apply: `scaleRangeWidth`
|
|
650
|
+
* reads `range()[last]`, which is `0` for the usual inverted y range, and
|
|
651
|
+
* `deviceBucketCount` reads the canvas' *width*. Omitted ⇒ the x-axis
|
|
652
|
+
* defaults, which is every other caller.
|
|
653
|
+
*/
|
|
654
|
+
axis) {
|
|
655
|
+
const visibleCount = vEnd - vStart;
|
|
656
|
+
if (axis === undefined
|
|
657
|
+
? !shouldDecimateCount(visibleCount, ctx, k)
|
|
658
|
+
: visibleCount < k * axis.deviceCount)
|
|
659
|
+
return null;
|
|
660
|
+
const dom = scaleDomain(binScale);
|
|
661
|
+
if (dom === null || dom[1] <= dom[0])
|
|
662
|
+
return null;
|
|
663
|
+
const invert = scaleInvert(binScale);
|
|
664
|
+
const plotWidthCss = axis?.spanCss ?? scaleRangeWidth(binScale);
|
|
665
|
+
if (invert === null || plotWidthCss === null || plotWidthCss <= 0)
|
|
666
|
+
return null;
|
|
667
|
+
const W = axis?.deviceCount ?? deviceBucketCount(ctx);
|
|
668
|
+
if (W <= 0)
|
|
669
|
+
return null;
|
|
670
|
+
const raw = pixelEdges(invert, plotWidthCss, W);
|
|
671
|
+
// `pixelEdges` walks pixels ascending, so on an **inverted** axis — the usual
|
|
672
|
+
// y range `[h, 0]`, which a horizontal heat map's bin axis uses — the key-space
|
|
673
|
+
// edges come back descending. The sweep below and the emitted `begin`/`end`
|
|
674
|
+
// both want ascending key order, and the draw maps back through the scale
|
|
675
|
+
// anyway, so normalize here rather than special-casing two directions.
|
|
676
|
+
const edges = raw[0] <= raw[W] ? raw : raw.slice().reverse();
|
|
677
|
+
const G = ss.groups.length;
|
|
678
|
+
const sum = new Float64Array(W * G);
|
|
679
|
+
const count = new Float64Array(W * G);
|
|
680
|
+
// `ss.begin` is ascending and so are `edges`, so the column advances
|
|
681
|
+
// monotonically — one O(V + W) sweep rather than a search per bin.
|
|
682
|
+
let col = 0;
|
|
683
|
+
for (let b = vStart; b < vEnd; b += 1) {
|
|
684
|
+
const key = ss.begin[b];
|
|
685
|
+
while (col < W - 1 && key >= edges[col + 1])
|
|
686
|
+
col += 1;
|
|
687
|
+
const src = b * G;
|
|
688
|
+
const dst = col * G;
|
|
689
|
+
for (let g = 0; g < G; g += 1) {
|
|
690
|
+
const v = ss.values[src + g];
|
|
691
|
+
// A hole contributes nothing rather than dragging the mean to zero; a
|
|
692
|
+
// column of nothing but holes stays a hole.
|
|
693
|
+
if (!Number.isFinite(v))
|
|
694
|
+
continue;
|
|
695
|
+
sum[dst + g] += v;
|
|
696
|
+
count[dst + g] += 1;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const begin = new Float64Array(W);
|
|
700
|
+
const end = new Float64Array(W);
|
|
701
|
+
const values = new Float64Array(W * G);
|
|
702
|
+
for (let c = 0; c < W; c += 1) {
|
|
703
|
+
begin[c] = edges[c];
|
|
704
|
+
end[c] = edges[c + 1];
|
|
705
|
+
const dst = c * G;
|
|
706
|
+
for (let g = 0; g < G; g += 1) {
|
|
707
|
+
const n = count[dst + g];
|
|
708
|
+
values[dst + g] = n > 0 ? sum[dst + g] / n : NaN;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return { begin, end, values, groups: ss.groups, length: W };
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* The **y half** of heat-map decimation: collapse runs of `stride` rows into one,
|
|
715
|
+
* each the mean of the rows it covers ([PND-HEATMAP]).
|
|
716
|
+
*
|
|
717
|
+
* The x half ({@link decimateHeat}) is not enough on its own, and a gene
|
|
718
|
+
* expression matrix is the case that proves it: 10,000 genes x 8 samples is
|
|
719
|
+
* **16.7 rows per pixel row** and only 8 bins, so the column decimator declines
|
|
720
|
+
* and every one of the 80,000 cells is drawn to show ~4,800 distinguishable
|
|
721
|
+
* ones. Whichever axis is oversampled, the argument is the same — rows sharing a
|
|
722
|
+
* pixel row composite, so what the eye receives is their mean.
|
|
723
|
+
*
|
|
724
|
+
* **A fixed integer stride, not a pixel-edge walk.** Source rows are *unit
|
|
725
|
+
* slots* (`[g, g+1]`, which is what lets `binCategories` label them), so they
|
|
726
|
+
* are already uniform in row-index space; a run of `stride` of them is exactly
|
|
727
|
+
* `[r·stride, (r+1)·stride]`. That keeps the y coordinate space **unchanged** —
|
|
728
|
+
* which is load-bearing, because `<YAxis>` scales over `[0, G]` and explicit
|
|
729
|
+
* `{ at, label }` ticks are in those units. Rewriting the row bands the way the
|
|
730
|
+
* x half rewrites bin spans would silently slide every axis label.
|
|
731
|
+
*
|
|
732
|
+
* Returns `null` below the gate (`stride < k` — fewer than `k` rows per device
|
|
733
|
+
* row, where drawing every row is honest and the reduction would not pay), so
|
|
734
|
+
* the caller draws the source rows. The final run is short when `rows` is not a
|
|
735
|
+
* multiple of `stride`; it averages what is there.
|
|
736
|
+
*/
|
|
737
|
+
export function decimateHeatRows(values, bins, rows, deviceRows, k = 2) {
|
|
738
|
+
if (!(deviceRows > 0) || !(rows > 0))
|
|
739
|
+
return null;
|
|
740
|
+
const stride = Math.ceil(rows / deviceRows);
|
|
741
|
+
if (stride < k)
|
|
742
|
+
return null;
|
|
743
|
+
const out = Math.ceil(rows / stride);
|
|
744
|
+
const reduced = new Float64Array(bins * out);
|
|
745
|
+
for (let b = 0; b < bins; b += 1) {
|
|
746
|
+
const src = b * rows;
|
|
747
|
+
const dst = b * out;
|
|
748
|
+
for (let r = 0; r < out; r += 1) {
|
|
749
|
+
const g0 = r * stride;
|
|
750
|
+
const g1 = Math.min(g0 + stride, rows);
|
|
751
|
+
let sum = 0;
|
|
752
|
+
let n = 0;
|
|
753
|
+
for (let g = g0; g < g1; g += 1) {
|
|
754
|
+
const v = values[src + g];
|
|
755
|
+
// A hole contributes nothing rather than pulling the mean toward zero;
|
|
756
|
+
// a run of nothing but holes stays a hole.
|
|
757
|
+
if (!Number.isFinite(v))
|
|
758
|
+
continue;
|
|
759
|
+
sum += v;
|
|
760
|
+
n += 1;
|
|
761
|
+
}
|
|
762
|
+
reduced[dst + r] = n > 0 ? sum / n : NaN;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return { values: reduced, rows: out, stride };
|
|
766
|
+
}
|
|
610
767
|
/**
|
|
611
768
|
* Decimate a **uniform** scatter to one representative mark per occupied
|
|
612
769
|
* **pixel cell** ([PND-MARKDEC] scatter half) — the marks analog of
|
package/dist/heat.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { StackedBarSeries } from './data.js';
|
|
2
|
+
import type { Scale } from './line.js';
|
|
3
|
+
import type { Orientation, StackMark } from './bars.js';
|
|
4
|
+
import type { SpanSelection } from './context.js';
|
|
5
|
+
import type { HeatStates } from './theme.js';
|
|
6
|
+
import { type DecimateOption } from './decimate.js';
|
|
7
|
+
/**
|
|
8
|
+
* Heat-map geometry: a grid of cells, each filled by the colour its **value**
|
|
9
|
+
* maps to. Bins run along x, the series' **columns** run down y, and colour
|
|
10
|
+
* carries the aggregate.
|
|
11
|
+
*
|
|
12
|
+
* **Why this reuses {@link StackedBarSeries}.** That type is already exactly a
|
|
13
|
+
* heat map's data: `[begin, end]` spans per bin, a named second dimension in
|
|
14
|
+
* `groups`, and a row-major `length × groups.length` grid of `values`. So a heat
|
|
15
|
+
* map needs **no reader of its own** — `stacksFromColumns(series, columns)`
|
|
16
|
+
* produces all four shapes pond can express today:
|
|
17
|
+
*
|
|
18
|
+
* | source | columns | x axis |
|
|
19
|
+
* | --- | --- | --- |
|
|
20
|
+
* | `TimeSeries` | one | time intervals — a stripe |
|
|
21
|
+
* | `TimeSeries` | many | time intervals — a grid |
|
|
22
|
+
* | `ValueSeries` | one | value intervals — a bin stripe |
|
|
23
|
+
* | `ValueSeries` | many | value intervals — a grid |
|
|
24
|
+
*
|
|
25
|
+
* The stripe is just `groups.length === 1`, so there is one draw path, not two.
|
|
26
|
+
*
|
|
27
|
+
* **What that buys on x.** Because the spans are the ordinary bin spans, the
|
|
28
|
+
* whole of pond's binning machinery applies unchanged — `aggregate` over a
|
|
29
|
+
* trading calendar with sessions, `Sequence.calendar` day/week/month buckets,
|
|
30
|
+
* `byColumn` value bands. The heat map inherits all of it by not having an
|
|
31
|
+
* opinion.
|
|
32
|
+
*
|
|
33
|
+
* **What it costs on y.** The y dimension **must be columns**. A month-of-year
|
|
34
|
+
* grid means building a column per month; a per-city grid means a column per
|
|
35
|
+
* city (`pivotByGroup`'s long→wide output, or `partitionBy` reshaped). That is
|
|
36
|
+
* a real constraint, and a deliberate one: it keeps the second dimension in the
|
|
37
|
+
* data model, where pond's own reshaping operators can produce it, instead of
|
|
38
|
+
* inventing a chart-level pivot.
|
|
39
|
+
*/
|
|
40
|
+
/** Cell styling. Colour is data and comes from the caller's ramp, so this is
|
|
41
|
+
* only the geometry and the live-cell treatment. */
|
|
42
|
+
/**
|
|
43
|
+
* How value maps onto the ramp's bands.
|
|
44
|
+
*
|
|
45
|
+
* `'linear'` splits the domain into equal-width bands. `'log'` splits it into
|
|
46
|
+
* equal-*ratio* bands, which is what a quantity spanning orders of magnitude
|
|
47
|
+
* needs: US measles incidence runs from ~2,900 per 100k before the vaccine to
|
|
48
|
+
* under 1 after it, and linear banding over eight colours puts everything below
|
|
49
|
+
* ~360 in one band — the whole post-1965 record, which is the half the chart
|
|
50
|
+
* exists to show.
|
|
51
|
+
*/
|
|
52
|
+
export type HeatScale = 'linear' | 'log';
|
|
53
|
+
/** How a cell with no value is drawn. */
|
|
54
|
+
export type HeatNoData = 'blank' | 'hatch';
|
|
55
|
+
export interface HeatStyle {
|
|
56
|
+
/** Alpha for a resting cell. A live cell pops to 1, as bars do. */
|
|
57
|
+
readonly opacity: number;
|
|
58
|
+
/** Outline colour for the selected cell. */
|
|
59
|
+
readonly highlight: string;
|
|
60
|
+
/** Selected-cell stroke width in px. */
|
|
61
|
+
readonly outlineWidth: number;
|
|
62
|
+
/** Px inset around each cell, in both axes. `0` tiles them flush. */
|
|
63
|
+
readonly gap: number;
|
|
64
|
+
/** Px floor on a cell's width, so a thin bin stays visible. */
|
|
65
|
+
readonly minWidth: number;
|
|
66
|
+
/** Stroke for the `'hatch'` no-data fill — the theme's grid colour, so it
|
|
67
|
+
* reads as chart furniture rather than as a value. */
|
|
68
|
+
readonly gridColor: string;
|
|
69
|
+
/**
|
|
70
|
+
* The **interaction states** ({@link HeatStates}), from `theme.heat`. Unset
|
|
71
|
+
* ⇒ the pre-states treatment exactly: a live cell gets one outline of its
|
|
72
|
+
* own in {@link highlight}, `outlineWidth` for hover and twice that for
|
|
73
|
+
* selection, and nothing recedes.
|
|
74
|
+
*/
|
|
75
|
+
readonly states?: HeatStates;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Map a value onto a **banded** ramp: `colors` split `[lo, hi]` into equal
|
|
79
|
+
* steps and a value takes the colour of the band it falls in.
|
|
80
|
+
*
|
|
81
|
+
* Banded rather than interpolated on purpose. It is what the climate-stripes
|
|
82
|
+
* card does today (its `anomalyStep` buckets into the ramp's length, which this
|
|
83
|
+
* replaces), it is the conventional reading for stripes and calendar heat maps,
|
|
84
|
+
* and a banded scale is honest about resolution in a way a smooth gradient is
|
|
85
|
+
* not — you can count the steps and read a cell against a legend. With nine or
|
|
86
|
+
* more stops it is visually indistinguishable from a gradient anyway.
|
|
87
|
+
*
|
|
88
|
+
* A non-finite value, or an empty ramp, yields `undefined` — the caller decides
|
|
89
|
+
* whether that is a skipped cell or a fallback fill.
|
|
90
|
+
*/
|
|
91
|
+
export declare function bandedColor(value: number, colors: readonly string[], lo: number, hi: number, scale?: HeatScale): string | undefined;
|
|
92
|
+
/**
|
|
93
|
+
* The `[min, max]` of the finite values across **every** cell — the colour
|
|
94
|
+
* domain when the caller does not pin one. `null` when nothing is finite.
|
|
95
|
+
*
|
|
96
|
+
* Deliberately **not** widened to include `0`, unlike `barExtent`: a bar's
|
|
97
|
+
* height is measured from a baseline so zero must be in the domain, but a
|
|
98
|
+
* cell's colour is measured against the data's own range. Widening would waste
|
|
99
|
+
* half the ramp on an all-positive grid.
|
|
100
|
+
*
|
|
101
|
+
* Note this spans the **whole grid**, not each row — every row is read against
|
|
102
|
+
* one scale, which is what makes rows comparable to each other.
|
|
103
|
+
*/
|
|
104
|
+
export declare function heatValueExtent(ss: StackedBarSeries): [number, number] | null;
|
|
105
|
+
/**
|
|
106
|
+
* The pixel rect of the cell at bin `b`, row `g` — `[x0, x1, yTop, yBottom]`,
|
|
107
|
+
* ascending on both axes — or `null` for a gap (non-finite value), which draws
|
|
108
|
+
* nothing and owns no hit region so a hole in the record reads as a hole.
|
|
109
|
+
*
|
|
110
|
+
* x comes from the bin's own span via {@link barSpanPx}, shared with bars so
|
|
111
|
+
* cells and bars tile identically. y is the row's **unit slot** `[g, g+1]`
|
|
112
|
+
* through the y scale, which is why the layer reports `yExtent` as `[0, G]` and
|
|
113
|
+
* labels rows via `binCategories` at each slot centre.
|
|
114
|
+
*
|
|
115
|
+
* **Row order follows the y scale**, so with the usual inverted pixel range row
|
|
116
|
+
* `0` sits at the *bottom*. That matches the existing band-axis convention
|
|
117
|
+
* (a horizontal histogram's first bin is its lowest), and a caller who wants
|
|
118
|
+
* the first column at the top reverses the column list.
|
|
119
|
+
*/
|
|
120
|
+
export declare function cellRect(ss: StackedBarSeries, b: number, g: number, xScale: Scale, yScale: Scale, gapPx: number, minWidthPx: number, orientation?: Orientation): [x0: number, x1: number, yTop: number, yBottom: number] | null;
|
|
121
|
+
/**
|
|
122
|
+
* Fill one rectangle per cell, coloured by `colorAt(b, g)`. A gap is skipped.
|
|
123
|
+
*
|
|
124
|
+
* A live cell keeps its **own** colour. The colour is never swapped for a
|
|
125
|
+
* highlight, because that colour *is* the datum — replacing it would erase the
|
|
126
|
+
* reading the chart exists to give.
|
|
127
|
+
*
|
|
128
|
+
* That rules out the bar layers' usual affordance too. A bar says "live" by
|
|
129
|
+
* popping from `opacity` to 1, which on a heat map is both invisible (a ramp is
|
|
130
|
+
* normally drawn at full opacity already) and, where it isn't, actively
|
|
131
|
+
* misleading — dimming a cell shifts where the reader places it on the colour
|
|
132
|
+
* scale. So a live cell is marked by an **outline** instead: `outlineWidth` for
|
|
133
|
+
* hover, twice that for selection, both in `style.highlight`. The alpha pop is
|
|
134
|
+
* kept as well, so a theme that does draw cells translucent still behaves like
|
|
135
|
+
* its bars.
|
|
136
|
+
*
|
|
137
|
+
* Hover and selection share one colour deliberately — whether they should
|
|
138
|
+
* diverge is the open question in #577, and this layer should not pre-empt it.
|
|
139
|
+
*
|
|
140
|
+
* Both `selection` and `hovered` are **sets**: `ContainerFrame.selected` has
|
|
141
|
+
* been one since [PND-MULTISEL] and `hovered` since RFC A4.3, so **every** cell
|
|
142
|
+
* a member names lights — a pinned group of cells, or a drag-sweep hovering
|
|
143
|
+
* several at once, all read back rather than only the set's first member. A cell
|
|
144
|
+
* in **both** sets reads as selected (selected outranks hovered, the precedence
|
|
145
|
+
* `drawBars` / `drawStacks` / `drawBox` share) and takes one outline, never two.
|
|
146
|
+
*
|
|
147
|
+
* O(visible × G) after viewport culling on the bin axis, plus O(|set|) per
|
|
148
|
+
* visible **bin** (not per cell — see {@link binLabelsInto}) and only when a set
|
|
149
|
+
* names this layer at all, so a resting draw costs exactly what it did.
|
|
150
|
+
*/
|
|
151
|
+
export declare function drawHeat(ctx: CanvasRenderingContext2D, ss: StackedBarSeries, xScale: Scale, yScale: Scale, style: HeatStyle, colorOf: (value: number) => string | undefined, seriesId: string | undefined, selection?: readonly StackMark[], hovered?: readonly StackMark[], decimate?: DecimateOption, orientation?: Orientation, noData?: HeatNoData, spans?: readonly SpanSelection[]): void;
|
|
152
|
+
/**
|
|
153
|
+
* Hit-test plot-pixel `(px, py)` against the grid — the first cell whose rect
|
|
154
|
+
* contains the point, or `null`. Returns `[bin, row, begin, rowName, value]`.
|
|
155
|
+
*
|
|
156
|
+
* The **value** is the whole point of the layer. A constant-height bar carries
|
|
157
|
+
* none, which is why the climate-stripes card looks its number up out-of-band;
|
|
158
|
+
* a cell answers directly, and so can the cursor.
|
|
159
|
+
*
|
|
160
|
+
* O(N × G), as `stackAt` is: bin and row counts are view-scale, clicks are rare.
|
|
161
|
+
*/
|
|
162
|
+
export declare function heatAt(ss: StackedBarSeries, px: number, py: number, xScale: Scale, yScale: Scale, gapPx: number, minWidthPx: number, orientation?: Orientation): [bin: number, row: number, begin: number, name: string, value: number] | null;
|
|
163
|
+
//# sourceMappingURL=heat.d.ts.map
|