@perspective-dev/viewer-charts 4.3.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/LICENSE.md +193 -0
- package/dist/cdn/perspective-viewer-charts.js +3 -0
- package/dist/cdn/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/axis/axis-primitives.d.ts +24 -0
- package/dist/esm/axis/bar-axis.d.ts +51 -0
- package/dist/esm/axis/canvas.d.ts +24 -0
- package/dist/esm/axis/categorical-axis-core.d.ts +42 -0
- package/dist/esm/axis/categorical-axis.d.ts +27 -0
- package/dist/esm/axis/facet-chrome.d.ts +13 -0
- package/dist/esm/axis/label-geometry.d.ts +41 -0
- package/dist/esm/axis/legend.d.ts +44 -0
- package/dist/esm/axis/numeric-axis.d.ts +20 -0
- package/dist/esm/charts/candlestick/candlestick-build.d.ts +129 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +10 -0
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +24 -0
- package/dist/esm/charts/candlestick/candlestick.d.ts +144 -0
- package/dist/esm/charts/candlestick/glyphs/draw-candlesticks.d.ts +36 -0
- package/dist/esm/charts/candlestick/glyphs/draw-ohlc.d.ts +33 -0
- package/dist/esm/charts/canvas-types.d.ts +15 -0
- package/dist/esm/charts/cartesian/cartesian-build.d.ts +14 -0
- package/dist/esm/charts/cartesian/cartesian-interact.d.ts +20 -0
- package/dist/esm/charts/cartesian/cartesian-render.d.ts +26 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +239 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +53 -0
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +142 -0
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +23 -0
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +24 -0
- package/dist/esm/charts/cartesian/label-interner.d.ts +21 -0
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +11 -0
- package/dist/esm/charts/chart-base.d.ts +402 -0
- package/dist/esm/charts/chart.d.ts +338 -0
- package/dist/esm/charts/common/band-layout.d.ts +32 -0
- package/dist/esm/charts/common/categorical-y-chart.d.ts +53 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +90 -0
- package/dist/esm/charts/common/chrome-cache.d.ts +18 -0
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +9 -0
- package/dist/esm/charts/common/leaf-color.d.ts +33 -0
- package/dist/esm/charts/common/node-store.d.ts +81 -0
- package/dist/esm/charts/common/tree-chart.d.ts +48 -0
- package/dist/esm/charts/common/tree-chrome.d.ts +31 -0
- package/dist/esm/charts/common/tree-data.d.ts +54 -0
- package/dist/esm/charts/common/visible-extent.d.ts +51 -0
- package/dist/esm/charts/heatmap/heatmap-build.d.ts +86 -0
- package/dist/esm/charts/heatmap/heatmap-interact.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-render.d.ts +19 -0
- package/dist/esm/charts/heatmap/heatmap-y-axis.d.ts +46 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +117 -0
- package/dist/esm/charts/map/map.d.ts +67 -0
- package/dist/esm/charts/registry.d.ts +14 -0
- package/dist/esm/charts/series/glyphs/draw-areas.d.ts +30 -0
- package/dist/esm/charts/series/glyphs/draw-bars.d.ts +15 -0
- package/dist/esm/charts/series/glyphs/draw-lines.d.ts +34 -0
- package/dist/esm/charts/series/glyphs/draw-scatter.d.ts +33 -0
- package/dist/esm/charts/series/series-build.d.ts +228 -0
- package/dist/esm/charts/series/series-interact.d.ts +35 -0
- package/dist/esm/charts/series/series-render.d.ts +41 -0
- package/dist/esm/charts/series/series-type.d.ts +49 -0
- package/dist/esm/charts/series/series.d.ts +317 -0
- package/dist/esm/charts/sunburst/sunburst-interact.d.ts +7 -0
- package/dist/esm/charts/sunburst/sunburst-layout.d.ts +33 -0
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +22 -0
- package/dist/esm/charts/sunburst/sunburst.d.ts +85 -0
- package/dist/esm/charts/treemap/treemap-interact.d.ts +12 -0
- package/dist/esm/charts/treemap/treemap-layout.d.ts +28 -0
- package/dist/esm/charts/treemap/treemap-render.d.ts +18 -0
- package/dist/esm/charts/treemap/treemap.d.ts +74 -0
- package/dist/esm/config.d.ts +27 -0
- package/dist/esm/data/lazy-row.d.ts +32 -0
- package/dist/esm/data/split-groups.d.ts +20 -0
- package/dist/esm/data/view-reader.d.ts +35 -0
- package/dist/esm/event-detail.d.ts +28 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/interaction/hit-test.d.ts +30 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +19 -0
- package/dist/esm/interaction/host-sink-message.d.ts +46 -0
- package/dist/esm/interaction/lazy-tooltip.d.ts +61 -0
- package/dist/esm/interaction/raw-event-forwarder.d.ts +27 -0
- package/dist/esm/interaction/spatial-grid.d.ts +15 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +193 -0
- package/dist/esm/interaction/zoom-controller.d.ts +106 -0
- package/dist/esm/interaction/zoom-router.d.ts +48 -0
- package/dist/esm/layout/facet-grid.d.ts +126 -0
- package/dist/esm/layout/plot-layout.d.ts +104 -0
- package/dist/esm/layout/ticks.d.ts +17 -0
- package/dist/esm/map/mercator.d.ts +102 -0
- package/dist/esm/map/tile-cache.d.ts +38 -0
- package/dist/esm/map/tile-layer.d.ts +66 -0
- package/dist/esm/map/tile-loader.d.ts +52 -0
- package/dist/esm/map/tile-source.d.ts +66 -0
- package/dist/esm/perspective-viewer-charts.js +3 -0
- package/dist/esm/perspective-viewer-charts.js.map +7 -0
- package/dist/esm/plugin/charts.d.ts +40 -0
- package/dist/esm/plugin/plugin.d.ts +95 -0
- package/dist/esm/render/scheduler.d.ts +41 -0
- package/dist/esm/theme/gradient.d.ts +48 -0
- package/dist/esm/theme/palette.d.ts +13 -0
- package/dist/esm/theme/theme-snapshot.d.ts +7 -0
- package/dist/esm/theme/theme.d.ts +53 -0
- package/dist/esm/transport/protocol.d.ts +430 -0
- package/dist/esm/transport/renderer-transport.d.ts +201 -0
- package/dist/esm/utils/css.d.ts +1 -0
- package/dist/esm/utils/font-snapshot.d.ts +50 -0
- package/dist/esm/webgl/buffer-pool.d.ts +62 -0
- package/dist/esm/webgl/context-manager.d.ts +184 -0
- package/dist/esm/webgl/gradient-texture.d.ts +17 -0
- package/dist/esm/webgl/instanced-attrs.d.ts +44 -0
- package/dist/esm/webgl/plot-frame.d.ts +39 -0
- package/dist/esm/webgl/program-cache.d.ts +13 -0
- package/dist/esm/webgl/shader-manifest.d.ts +53 -0
- package/dist/esm/webgl/shader-registry.d.ts +22 -0
- package/dist/esm/worker/boot.d.ts +0 -0
- package/dist/esm/worker/dispatch.d.ts +9 -0
- package/dist/esm/worker/font-loader.d.ts +2 -0
- package/dist/esm/worker/renderer.worker.d.ts +115 -0
- package/dist/esm/worker/session-host.d.ts +26 -0
- package/package.json +47 -0
- package/src/css/perspective-viewer-charts.css +95 -0
- package/src/ts/axis/axis-primitives.ts +125 -0
- package/src/ts/axis/bar-axis.ts +345 -0
- package/src/ts/axis/canvas.ts +64 -0
- package/src/ts/axis/categorical-axis-core.ts +125 -0
- package/src/ts/axis/categorical-axis.ts +716 -0
- package/src/ts/axis/facet-chrome.ts +42 -0
- package/src/ts/axis/label-geometry.ts +188 -0
- package/src/ts/axis/legend.ts +218 -0
- package/src/ts/axis/numeric-axis.ts +353 -0
- package/src/ts/charts/candlestick/candlestick-build.ts +516 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +256 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +387 -0
- package/src/ts/charts/candlestick/candlestick.ts +367 -0
- package/src/ts/charts/candlestick/glyphs/draw-candlesticks.ts +432 -0
- package/src/ts/charts/candlestick/glyphs/draw-ohlc.ts +317 -0
- package/src/ts/charts/canvas-types.ts +30 -0
- package/src/ts/charts/cartesian/cartesian-build.ts +616 -0
- package/src/ts/charts/cartesian/cartesian-interact.ts +355 -0
- package/src/ts/charts/cartesian/cartesian-render.ts +948 -0
- package/src/ts/charts/cartesian/cartesian.ts +469 -0
- package/src/ts/charts/cartesian/glyph.ts +81 -0
- package/src/ts/charts/cartesian/glyphs/density.ts +1263 -0
- package/src/ts/charts/cartesian/glyphs/lines.ts +320 -0
- package/src/ts/charts/cartesian/glyphs/points.ts +239 -0
- package/src/ts/charts/cartesian/label-interner.ts +56 -0
- package/src/ts/charts/cartesian/tooltip-lines.ts +80 -0
- package/src/ts/charts/chart-base.ts +840 -0
- package/src/ts/charts/chart.ts +427 -0
- package/src/ts/charts/common/band-layout.ts +63 -0
- package/src/ts/charts/common/categorical-y-chart.ts +81 -0
- package/src/ts/charts/common/category-axis-resolver.ts +314 -0
- package/src/ts/charts/common/chrome-cache.ts +79 -0
- package/src/ts/charts/common/draw-tooltip-box.ts +84 -0
- package/src/ts/charts/common/leaf-color.ts +92 -0
- package/src/ts/charts/common/node-store.ts +235 -0
- package/src/ts/charts/common/tree-chart.ts +76 -0
- package/src/ts/charts/common/tree-chrome.ts +123 -0
- package/src/ts/charts/common/tree-data.ts +623 -0
- package/src/ts/charts/common/visible-extent.ts +112 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +426 -0
- package/src/ts/charts/heatmap/heatmap-interact.ts +274 -0
- package/src/ts/charts/heatmap/heatmap-render.ts +815 -0
- package/src/ts/charts/heatmap/heatmap-y-axis.ts +351 -0
- package/src/ts/charts/heatmap/heatmap.ts +368 -0
- package/src/ts/charts/map/map.ts +201 -0
- package/src/ts/charts/registry.ts +65 -0
- package/src/ts/charts/series/glyphs/draw-areas.ts +331 -0
- package/src/ts/charts/series/glyphs/draw-bars.ts +113 -0
- package/src/ts/charts/series/glyphs/draw-lines.ts +320 -0
- package/src/ts/charts/series/glyphs/draw-scatter.ts +328 -0
- package/src/ts/charts/series/series-build.ts +848 -0
- package/src/ts/charts/series/series-interact.ts +604 -0
- package/src/ts/charts/series/series-render.ts +1109 -0
- package/src/ts/charts/series/series-type.ts +99 -0
- package/src/ts/charts/series/series.ts +794 -0
- package/src/ts/charts/sunburst/sunburst-interact.ts +460 -0
- package/src/ts/charts/sunburst/sunburst-layout.ts +238 -0
- package/src/ts/charts/sunburst/sunburst-render.ts +887 -0
- package/src/ts/charts/sunburst/sunburst.ts +248 -0
- package/src/ts/charts/treemap/treemap-interact.ts +445 -0
- package/src/ts/charts/treemap/treemap-layout.ts +328 -0
- package/src/ts/charts/treemap/treemap-render.ts +886 -0
- package/src/ts/charts/treemap/treemap.ts +247 -0
- package/src/ts/config.ts +41 -0
- package/src/ts/data/lazy-row.ts +140 -0
- package/src/ts/data/split-groups.ts +97 -0
- package/src/ts/data/view-reader.ts +107 -0
- package/src/ts/event-detail.ts +44 -0
- package/src/ts/index.ts +53 -0
- package/src/ts/interaction/hit-test.ts +106 -0
- package/src/ts/interaction/host-sink-dom.ts +85 -0
- package/src/ts/interaction/host-sink-message.ts +75 -0
- package/src/ts/interaction/lazy-tooltip.ts +102 -0
- package/src/ts/interaction/raw-event-forwarder.ts +175 -0
- package/src/ts/interaction/spatial-grid.ts +100 -0
- package/src/ts/interaction/tooltip-controller.ts +407 -0
- package/src/ts/interaction/zoom-controller.ts +468 -0
- package/src/ts/interaction/zoom-router.ts +230 -0
- package/src/ts/layout/facet-grid.ts +346 -0
- package/src/ts/layout/plot-layout.ts +277 -0
- package/src/ts/layout/ticks.ts +168 -0
- package/src/ts/map/mercator.ts +204 -0
- package/src/ts/map/tile-cache.ts +96 -0
- package/src/ts/map/tile-layer.ts +382 -0
- package/src/ts/map/tile-loader.ts +143 -0
- package/src/ts/map/tile-source.ts +156 -0
- package/src/ts/plugin/charts.ts +286 -0
- package/src/ts/plugin/plugin.ts +668 -0
- package/src/ts/render/scheduler.ts +339 -0
- package/src/ts/shaders/area.frag.glsl +20 -0
- package/src/ts/shaders/area.vert.glsl +19 -0
- package/src/ts/shaders/bar.frag.glsl +25 -0
- package/src/ts/shaders/bar.vert.glsl +60 -0
- package/src/ts/shaders/candlestick-body.frag.glsl +19 -0
- package/src/ts/shaders/candlestick-body.vert.glsl +34 -0
- package/src/ts/shaders/density-extreme.frag.glsl +30 -0
- package/src/ts/shaders/density-mrt.frag.glsl +44 -0
- package/src/ts/shaders/density-mrt.vert.glsl +48 -0
- package/src/ts/shaders/density-resolve.frag.glsl +89 -0
- package/src/ts/shaders/density-resolve.vert.glsl +23 -0
- package/src/ts/shaders/density-splat.frag.glsl +34 -0
- package/src/ts/shaders/density-splat.vert.glsl +52 -0
- package/src/ts/shaders/gridline.frag.glsl +18 -0
- package/src/ts/shaders/gridline.vert.glsl +18 -0
- package/src/ts/shaders/heatmap.frag.glsl +23 -0
- package/src/ts/shaders/heatmap.vert.glsl +42 -0
- package/src/ts/shaders/line-uniform.frag.glsl +26 -0
- package/src/ts/shaders/line-uniform.vert.glsl +54 -0
- package/src/ts/shaders/line.frag.glsl +28 -0
- package/src/ts/shaders/line.vert.glsl +87 -0
- package/src/ts/shaders/scatter.frag.glsl +39 -0
- package/src/ts/shaders/scatter.vert.glsl +67 -0
- package/src/ts/shaders/sunburst-arc.frag.glsl +19 -0
- package/src/ts/shaders/sunburst-arc.vert.glsl +79 -0
- package/src/ts/shaders/tile.frag.glsl +27 -0
- package/src/ts/shaders/tile.vert.glsl +35 -0
- package/src/ts/shaders/treemap.frag.glsl +19 -0
- package/src/ts/shaders/treemap.vert.glsl +25 -0
- package/src/ts/shaders/y-scatter.frag.glsl +30 -0
- package/src/ts/shaders/y-scatter.vert.glsl +31 -0
- package/src/ts/theme/gradient.ts +312 -0
- package/src/ts/theme/palette.ts +64 -0
- package/src/ts/theme/theme-snapshot.ts +66 -0
- package/src/ts/theme/theme.ts +166 -0
- package/src/ts/transport/protocol.ts +497 -0
- package/src/ts/transport/renderer-transport.ts +788 -0
- package/src/ts/utils/css.ts +36 -0
- package/src/ts/utils/font-snapshot.ts +159 -0
- package/src/ts/webgl/buffer-pool.ts +163 -0
- package/src/ts/webgl/context-manager.ts +414 -0
- package/src/ts/webgl/gradient-texture.ts +84 -0
- package/src/ts/webgl/instanced-attrs.ts +139 -0
- package/src/ts/webgl/plot-frame.ts +91 -0
- package/src/ts/webgl/program-cache.ts +46 -0
- package/src/ts/webgl/shader-manifest.ts +148 -0
- package/src/ts/webgl/shader-registry.ts +97 -0
- package/src/ts/worker/boot.ts +22 -0
- package/src/ts/worker/dispatch.ts +99 -0
- package/src/ts/worker/font-loader.ts +89 -0
- package/src/ts/worker/renderer.worker.ts +734 -0
- package/src/ts/worker/session-host.ts +118 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generic "find the value-axis extent among records whose category
|
|
15
|
+
* position falls inside a visible window" helper. Used by Y Bar,
|
|
16
|
+
* X Bar, Candlestick, and any future categorical-axis chart that
|
|
17
|
+
* wants an auto-fit value axis on zoom.
|
|
18
|
+
*
|
|
19
|
+
* Takes a pre-extracted numeric tuple per record via the `extract`
|
|
20
|
+
* callback instead of reading fields directly, so the same code path
|
|
21
|
+
* handles both Bar's `{ catIdx, y0, y1, axis, seriesId }` shape and
|
|
22
|
+
* Candlestick's `{ xCenter, low, high }` shape.
|
|
23
|
+
*
|
|
24
|
+
* TODO(perf): linear scan. Callers that order records by category
|
|
25
|
+
* index could pre-sort once and binary-search the slice to reduce
|
|
26
|
+
* this to O(log N + K_visible). Deferred until profiling shows the
|
|
27
|
+
* scan in the hot path.
|
|
28
|
+
*/
|
|
29
|
+
export interface VisibleExtent {
|
|
30
|
+
min: number;
|
|
31
|
+
max: number;
|
|
32
|
+
hasFit: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface VisibleExtentRecord {
|
|
36
|
+
/**
|
|
37
|
+
* Position on the categorical axis — compared to the visible window.
|
|
38
|
+
*/
|
|
39
|
+
cat: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Low bound of the value-axis extent for this record.
|
|
43
|
+
*/
|
|
44
|
+
lo: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* High bound of the value-axis extent for this record.
|
|
48
|
+
*/
|
|
49
|
+
hi: number;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* True to skip this record (hidden series / wrong axis / etc.).
|
|
53
|
+
*/
|
|
54
|
+
skip: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Walk `items`, filter by `visCatMin <= cat <= visCatMax` (and the
|
|
59
|
+
* caller-supplied `skip` flag), and return min/max over `lo`/`hi`.
|
|
60
|
+
*
|
|
61
|
+
* Returns `hasFit: false` when the window matches no records, so
|
|
62
|
+
* callers can fall back to the base domain.
|
|
63
|
+
*
|
|
64
|
+
* Zero-range guard: if every visible record shares a single value
|
|
65
|
+
* (flat run), pad by `±|value|` so the axis doesn't collapse to a
|
|
66
|
+
* single pixel.
|
|
67
|
+
*/
|
|
68
|
+
export function computeVisibleExtent<T>(
|
|
69
|
+
items: readonly T[],
|
|
70
|
+
visCatMin: number,
|
|
71
|
+
visCatMax: number,
|
|
72
|
+
extract: (item: T, out: VisibleExtentRecord) => void,
|
|
73
|
+
out: VisibleExtent,
|
|
74
|
+
): VisibleExtent {
|
|
75
|
+
let min = Infinity;
|
|
76
|
+
let max = -Infinity;
|
|
77
|
+
|
|
78
|
+
// Reuse a single scratch record across the walk. `extract` mutates
|
|
79
|
+
// it in place — zero allocations per iteration.
|
|
80
|
+
const scratch: VisibleExtentRecord = { cat: 0, lo: 0, hi: 0, skip: false };
|
|
81
|
+
for (let i = 0; i < items.length; i++) {
|
|
82
|
+
scratch.skip = false;
|
|
83
|
+
extract(items[i], scratch);
|
|
84
|
+
if (scratch.skip) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (scratch.cat < visCatMin || scratch.cat > visCatMax) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (scratch.lo < min) {
|
|
93
|
+
min = scratch.lo;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (scratch.hi > max) {
|
|
97
|
+
max = scratch.hi;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const hasFit = isFinite(min) && isFinite(max);
|
|
102
|
+
if (hasFit && min === max) {
|
|
103
|
+
const pad = Math.abs(min) || 1;
|
|
104
|
+
min -= pad;
|
|
105
|
+
max += pad;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
out.min = min;
|
|
109
|
+
out.max = max;
|
|
110
|
+
out.hasFit = hasFit;
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import type { ColumnDataMap } from "../../data/view-reader";
|
|
14
|
+
import type { CategoricalLevel } from "../../axis/categorical-axis";
|
|
15
|
+
import { buildGroupRuns } from "../../axis/categorical-axis-core";
|
|
16
|
+
import {
|
|
17
|
+
resolveAxisMode,
|
|
18
|
+
resolveCategoryAxis,
|
|
19
|
+
resolveNumericCategoryDomain,
|
|
20
|
+
type AxisMode,
|
|
21
|
+
type NumericCategoryDomain,
|
|
22
|
+
} from "../common/category-axis-resolver";
|
|
23
|
+
|
|
24
|
+
export interface HeatmapCell {
|
|
25
|
+
xIdx: number;
|
|
26
|
+
yIdx: number;
|
|
27
|
+
value: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface HeatmapPipelineInput {
|
|
31
|
+
columns: ColumnDataMap;
|
|
32
|
+
numRows: number;
|
|
33
|
+
groupBy: string[];
|
|
34
|
+
splitBy: string[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Source-column types keyed by column name (table.schema() merged
|
|
38
|
+
* with view.expression_schema()). Drives both the X-axis level-type
|
|
39
|
+
* lookup (for non-string row-paths) and the Y-axis numeric-mode
|
|
40
|
+
* decision when there's a single split_by.
|
|
41
|
+
*/
|
|
42
|
+
groupByTypes: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface HeatmapPipelineResult {
|
|
46
|
+
/**
|
|
47
|
+
* Hierarchical row_path levels driving the X axis (outermost-first).
|
|
48
|
+
*/
|
|
49
|
+
xLevels: CategoricalLevel[];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Arrow column names in iteration order; `yIdx === index in this list`.
|
|
53
|
+
*/
|
|
54
|
+
yColumnNames: string[];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Hierarchical Y levels derived by splitting each name on `|`.
|
|
58
|
+
*/
|
|
59
|
+
yLevels: CategoricalLevel[];
|
|
60
|
+
numX: number;
|
|
61
|
+
numY: number;
|
|
62
|
+
rowOffset: number;
|
|
63
|
+
cells: HeatmapCell[];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* O(1) lookup by `yIdx * numX + xIdx`; `null` means no-data.
|
|
67
|
+
*/
|
|
68
|
+
cells2D: (HeatmapCell | null)[];
|
|
69
|
+
colorMin: number;
|
|
70
|
+
colorMax: number;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* X-axis mode. `numeric` fires when the single group_by is
|
|
74
|
+
* date/datetime/integer/float; positions live in `xPositions`
|
|
75
|
+
* and the domain in `xNumericDomain`.
|
|
76
|
+
*/
|
|
77
|
+
xAxisMode: AxisMode;
|
|
78
|
+
yAxisMode: AxisMode;
|
|
79
|
+
xPositions: Float64Array | null;
|
|
80
|
+
yPositions: Float64Array | null;
|
|
81
|
+
xNumericDomain: NumericCategoryDomain | null;
|
|
82
|
+
yNumericDomain: NumericCategoryDomain | null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pure heatmap pipeline. Y indexing maps 1:1 to the arrow column iteration
|
|
87
|
+
* order — `yIdx` is the position of a value column in the ordered
|
|
88
|
+
* `ColumnDataMap` (after skipping `__ROW_PATH_N__` metadata). No
|
|
89
|
+
* aggregate/split reconstruction; the column name *is* the Y label.
|
|
90
|
+
*
|
|
91
|
+
* Externally enforced: only one entry sits in the `Color` slot, so every
|
|
92
|
+
* non-metadata column is a splitwise expansion of that single aggregate.
|
|
93
|
+
*
|
|
94
|
+
* Numeric-axis mode (matching bar/candlestick): when there's exactly one
|
|
95
|
+
* non-string group_by, the X axis switches to a real numeric/date axis
|
|
96
|
+
* with `xPositions[xIdx]` carrying the data-space center. Y mirrors this
|
|
97
|
+
* for a single non-string split_by, parsed best-effort out of the column
|
|
98
|
+
* name leaf segment; on parse failure it falls back to category mode.
|
|
99
|
+
*/
|
|
100
|
+
export function buildHeatmapPipeline(
|
|
101
|
+
input: HeatmapPipelineInput,
|
|
102
|
+
): HeatmapPipelineResult {
|
|
103
|
+
const { columns, numRows, groupBy, splitBy, groupByTypes } = input;
|
|
104
|
+
|
|
105
|
+
const xAxisMode = resolveAxisMode(groupBy, groupByTypes);
|
|
106
|
+
|
|
107
|
+
const empty: HeatmapPipelineResult = {
|
|
108
|
+
xLevels: [],
|
|
109
|
+
yColumnNames: [],
|
|
110
|
+
yLevels: [],
|
|
111
|
+
numX: 0,
|
|
112
|
+
numY: 0,
|
|
113
|
+
rowOffset: 0,
|
|
114
|
+
cells: [],
|
|
115
|
+
cells2D: [],
|
|
116
|
+
colorMin: 0,
|
|
117
|
+
colorMax: 1,
|
|
118
|
+
xAxisMode,
|
|
119
|
+
yAxisMode: { mode: "category" },
|
|
120
|
+
xPositions: null,
|
|
121
|
+
yPositions: null,
|
|
122
|
+
xNumericDomain: null,
|
|
123
|
+
yNumericDomain: null,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const levelTypes = groupBy.map((name) => groupByTypes[name] ?? "string");
|
|
127
|
+
const {
|
|
128
|
+
rowPaths: xLevels,
|
|
129
|
+
numCategories: numX,
|
|
130
|
+
rowOffset,
|
|
131
|
+
} = resolveCategoryAxis(columns, numRows, groupBy.length, levelTypes);
|
|
132
|
+
|
|
133
|
+
// Numeric X domain: sourced from `__ROW_PATH_0__`'s raw values when
|
|
134
|
+
// the single group_by is non-string.
|
|
135
|
+
let xPositions: Float64Array | null = null;
|
|
136
|
+
let xNumericDomain: NumericCategoryDomain | null = null;
|
|
137
|
+
if (xAxisMode.mode === "numeric" && numX > 0) {
|
|
138
|
+
const rp = columns.get("__ROW_PATH_0__");
|
|
139
|
+
const resolved = resolveNumericCategoryDomain(
|
|
140
|
+
rp?.values,
|
|
141
|
+
numX,
|
|
142
|
+
rowOffset,
|
|
143
|
+
groupBy[0] ?? "",
|
|
144
|
+
xAxisMode.numericType === "date" ||
|
|
145
|
+
xAxisMode.numericType === "datetime",
|
|
146
|
+
);
|
|
147
|
+
if (resolved) {
|
|
148
|
+
xPositions = resolved.categoryPositions;
|
|
149
|
+
xNumericDomain = resolved.numericCategoryDomain;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Enumerate Y columns in arrow iteration order, skipping metadata.
|
|
154
|
+
const yColumnNames: string[] = [];
|
|
155
|
+
for (const name of columns.keys()) {
|
|
156
|
+
if (name.startsWith("__")) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const col = columns.get(name);
|
|
161
|
+
if (!col?.values) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
yColumnNames.push(name);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const numY = yColumnNames.length;
|
|
169
|
+
|
|
170
|
+
if (numX === 0 || numY === 0) {
|
|
171
|
+
return { ...empty, xLevels, rowOffset };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Build hierarchical Y levels by splitting each name on `|`, coalescing
|
|
175
|
+
// consecutive equal tokens per level into a shared dictionary entry.
|
|
176
|
+
// Shape mirrors `CategoricalLevel`: one `Int32Array` of dictionary
|
|
177
|
+
// indices (length `numY`) + a string dictionary per level.
|
|
178
|
+
const yLevels = buildYLevelsFromNames(yColumnNames);
|
|
179
|
+
|
|
180
|
+
// Y-numeric mode: only when split_by has exactly one non-string level
|
|
181
|
+
// AND every column name parses into a finite number. The leaf segment
|
|
182
|
+
// is the (split_value, aggregate) `splitVal` token — leading segment
|
|
183
|
+
// when there's a trailing `|aggregate`, or the whole name when there
|
|
184
|
+
// is none.
|
|
185
|
+
const yAxisModeRaw = resolveYAxisMode(splitBy, groupByTypes);
|
|
186
|
+
let yAxisMode: AxisMode = { mode: "category" };
|
|
187
|
+
let yPositions: Float64Array | null = null;
|
|
188
|
+
let yNumericDomain: NumericCategoryDomain | null = null;
|
|
189
|
+
if (yAxisModeRaw.mode === "numeric") {
|
|
190
|
+
const parsed = parseYPositions(yColumnNames, yAxisModeRaw.numericType);
|
|
191
|
+
if (parsed) {
|
|
192
|
+
const resolved = resolveNumericCategoryDomain(
|
|
193
|
+
parsed,
|
|
194
|
+
numY,
|
|
195
|
+
0,
|
|
196
|
+
splitBy[0] ?? "",
|
|
197
|
+
yAxisModeRaw.numericType === "date" ||
|
|
198
|
+
yAxisModeRaw.numericType === "datetime",
|
|
199
|
+
);
|
|
200
|
+
if (resolved) {
|
|
201
|
+
yAxisMode = yAxisModeRaw;
|
|
202
|
+
yPositions = resolved.categoryPositions;
|
|
203
|
+
yNumericDomain = resolved.numericCategoryDomain;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Walk cells. Per-column loop (outer) lets us exploit arrow-contiguous
|
|
209
|
+
// value arrays; validity checks are bit-mask reads.
|
|
210
|
+
const cells: HeatmapCell[] = [];
|
|
211
|
+
const cells2D: (HeatmapCell | null)[] = new Array(numX * numY).fill(null);
|
|
212
|
+
let colorMin = Infinity;
|
|
213
|
+
let colorMax = -Infinity;
|
|
214
|
+
|
|
215
|
+
for (let yIdx = 0; yIdx < numY; yIdx++) {
|
|
216
|
+
const col = columns.get(yColumnNames[yIdx])!;
|
|
217
|
+
const values = col.values!;
|
|
218
|
+
const valid = col.valid;
|
|
219
|
+
for (let xIdx = 0; xIdx < numX; xIdx++) {
|
|
220
|
+
const row = xIdx + rowOffset;
|
|
221
|
+
if (valid) {
|
|
222
|
+
const bit = (valid[row >> 3] >> (row & 7)) & 1;
|
|
223
|
+
if (!bit) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const v = values[row] as number;
|
|
229
|
+
if (!isFinite(v)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const cell: HeatmapCell = { xIdx, yIdx, value: v };
|
|
234
|
+
cells.push(cell);
|
|
235
|
+
cells2D[yIdx * numX + xIdx] = cell;
|
|
236
|
+
if (v < colorMin) {
|
|
237
|
+
colorMin = v;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (v > colorMax) {
|
|
241
|
+
colorMax = v;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!isFinite(colorMin) || !isFinite(colorMax)) {
|
|
247
|
+
colorMin = 0;
|
|
248
|
+
colorMax = 1;
|
|
249
|
+
} else if (colorMin === colorMax) {
|
|
250
|
+
// Degenerate: all equal — nudge so the normalized t is 0 throughout.
|
|
251
|
+
colorMax = colorMin + 1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
xLevels,
|
|
256
|
+
yColumnNames,
|
|
257
|
+
yLevels,
|
|
258
|
+
numX,
|
|
259
|
+
numY,
|
|
260
|
+
rowOffset,
|
|
261
|
+
cells,
|
|
262
|
+
cells2D,
|
|
263
|
+
colorMin,
|
|
264
|
+
colorMax,
|
|
265
|
+
xAxisMode,
|
|
266
|
+
yAxisMode,
|
|
267
|
+
xPositions,
|
|
268
|
+
yPositions,
|
|
269
|
+
xNumericDomain,
|
|
270
|
+
yNumericDomain,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Y-axis mode for heatmap. Only fires when `splitBy.length === 1` and the
|
|
276
|
+
* split column is non-string non-boolean. Multi-split chains stringify
|
|
277
|
+
* each segment so the numeric round-trip is ambiguous; keep them on the
|
|
278
|
+
* categorical path.
|
|
279
|
+
*/
|
|
280
|
+
function resolveYAxisMode(
|
|
281
|
+
splitBy: string[],
|
|
282
|
+
splitByTypes: Record<string, string>,
|
|
283
|
+
): AxisMode {
|
|
284
|
+
if (splitBy.length !== 1) {
|
|
285
|
+
return { mode: "category" };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const t = splitByTypes[splitBy[0]];
|
|
289
|
+
if (t === "date" || t === "datetime" || t === "integer" || t === "float") {
|
|
290
|
+
return { mode: "numeric", numericType: t };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { mode: "category" };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Best-effort parse of the leading `|`-segment of every column name back
|
|
298
|
+
* into a numeric value. Returns `null` if any name fails to parse —
|
|
299
|
+
* caller falls back to category mode.
|
|
300
|
+
*
|
|
301
|
+
* Date/datetime split values are written by the engine as ISO-ish text;
|
|
302
|
+
* `Date.parse` accepts both `YYYY-MM-DD` and `YYYY-MM-DD HH:MM:SS.fff`.
|
|
303
|
+
* Integer/float go through `Number()`.
|
|
304
|
+
*/
|
|
305
|
+
function parseYPositions(
|
|
306
|
+
names: string[],
|
|
307
|
+
numericType: "date" | "datetime" | "integer" | "float",
|
|
308
|
+
): Float64Array | null {
|
|
309
|
+
const positions = new Float64Array(names.length);
|
|
310
|
+
for (let i = 0; i < names.length; i++) {
|
|
311
|
+
const name = names[i];
|
|
312
|
+
const pipeIdx = name.indexOf("|");
|
|
313
|
+
const seg = pipeIdx === -1 ? name : name.slice(0, pipeIdx);
|
|
314
|
+
let v: number;
|
|
315
|
+
if (numericType === "date" || numericType === "datetime") {
|
|
316
|
+
v = Date.parse(seg);
|
|
317
|
+
if (!isFinite(v)) {
|
|
318
|
+
// Engine sometimes emits `YYYY-MM-DD HH:MM:SS.fff` with a
|
|
319
|
+
// space separator that older browsers reject; retry with
|
|
320
|
+
// `T` substitution.
|
|
321
|
+
v = Date.parse(seg.replace(" ", "T"));
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
v = Number(seg);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!isFinite(v)) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
positions[i] = v;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return positions;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Partition a `ColumnDataMap` into one sub-map per user column. Every
|
|
339
|
+
* arrow value column is assigned to the partition whose user column name
|
|
340
|
+
* matches its terminal segment (everything after the last `|`, which
|
|
341
|
+
* equals the whole name when there's no `split_by`). `__ROW_PATH_N__`
|
|
342
|
+
* and `__GROUPING_ID__` metadata columns are copied into every partition
|
|
343
|
+
* since they describe the shared X axis.
|
|
344
|
+
*
|
|
345
|
+
* Used to render one heatmap per user column in a facet grid.
|
|
346
|
+
*/
|
|
347
|
+
export function partitionColumnsPerFacet(
|
|
348
|
+
columns: ColumnDataMap,
|
|
349
|
+
userColumns: string[],
|
|
350
|
+
): Array<{ label: string; columns: ColumnDataMap }> {
|
|
351
|
+
return userColumns.map((userCol) => {
|
|
352
|
+
const partition: ColumnDataMap = new Map();
|
|
353
|
+
for (const [name, col] of columns) {
|
|
354
|
+
if (name.startsWith("__ROW_PATH_") || name === "__GROUPING_ID__") {
|
|
355
|
+
partition.set(name, col);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const pipeIdx = name.lastIndexOf("|");
|
|
360
|
+
const leaf = pipeIdx === -1 ? name : name.slice(pipeIdx + 1);
|
|
361
|
+
if (leaf === userCol) {
|
|
362
|
+
partition.set(name, col);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { label: userCol, columns: partition };
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Split each column name on `|` → hierarchical levels. Outermost segment
|
|
372
|
+
* is index 0; leaf (terminal) segment is `levels.length - 1`. Runs of
|
|
373
|
+
* identical consecutive outer tokens naturally coalesce later during
|
|
374
|
+
* render because the Y axis compares `indices[yIdx]` against neighbours.
|
|
375
|
+
*/
|
|
376
|
+
function buildYLevelsFromNames(names: string[]): CategoricalLevel[] {
|
|
377
|
+
if (names.length === 0) {
|
|
378
|
+
return [];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Find max depth across all names so every Y entry has a value at
|
|
382
|
+
// every level.
|
|
383
|
+
let maxDepth = 0;
|
|
384
|
+
const segments: string[][] = names.map((n) => n.split("|"));
|
|
385
|
+
for (const s of segments) {
|
|
386
|
+
if (s.length > maxDepth) {
|
|
387
|
+
maxDepth = s.length;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (maxDepth === 0) {
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const levels: CategoricalLevel[] = [];
|
|
396
|
+
for (let d = 0; d < maxDepth; d++) {
|
|
397
|
+
const dictionary: string[] = [];
|
|
398
|
+
const dictIndex = new Map<string, number>();
|
|
399
|
+
const indices = new Int32Array(names.length);
|
|
400
|
+
const labels = new Array<string>(names.length);
|
|
401
|
+
let maxLabelChars = 0;
|
|
402
|
+
for (let i = 0; i < names.length; i++) {
|
|
403
|
+
const seg = segments[i][d] ?? "";
|
|
404
|
+
let idx = dictIndex.get(seg);
|
|
405
|
+
if (idx === undefined) {
|
|
406
|
+
idx = dictionary.length;
|
|
407
|
+
dictionary.push(seg);
|
|
408
|
+
dictIndex.set(seg, idx);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
indices[i] = idx;
|
|
412
|
+
labels[i] = seg;
|
|
413
|
+
if (seg.length > maxLabelChars) {
|
|
414
|
+
maxLabelChars = seg.length;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const isLeaf = d === maxDepth - 1;
|
|
419
|
+
const runs = isLeaf
|
|
420
|
+
? []
|
|
421
|
+
: buildGroupRuns(indices, dictionary, 0, names.length);
|
|
422
|
+
levels.push({ labels, runs, maxLabelChars });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return levels;
|
|
426
|
+
}
|