@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,248 @@
|
|
|
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 { WebGLContextManager } from "../../webgl/context-manager";
|
|
15
|
+
import { TreeChartBase } from "../common/tree-chart";
|
|
16
|
+
import { NULL_NODE } from "../common/node-store";
|
|
17
|
+
import {
|
|
18
|
+
processTreeChunk,
|
|
19
|
+
finalizeTree,
|
|
20
|
+
resetTreeState,
|
|
21
|
+
} from "../common/tree-data";
|
|
22
|
+
import {
|
|
23
|
+
renderSunburstFrame,
|
|
24
|
+
renderSunburstChromeOverlay,
|
|
25
|
+
} from "./sunburst-render";
|
|
26
|
+
import {
|
|
27
|
+
handleSunburstHover,
|
|
28
|
+
handleSunburstClick,
|
|
29
|
+
dismissSunburstPinnedTooltip,
|
|
30
|
+
type SunburstBreadcrumbRegion,
|
|
31
|
+
} from "./sunburst-interact";
|
|
32
|
+
|
|
33
|
+
export interface SunburstLocations {
|
|
34
|
+
u_center: WebGLUniformLocation | null;
|
|
35
|
+
u_resolution: WebGLUniformLocation | null;
|
|
36
|
+
u_border_px: WebGLUniformLocation | null;
|
|
37
|
+
a_strip_t: number;
|
|
38
|
+
a_side: number;
|
|
39
|
+
a_angles: number;
|
|
40
|
+
a_radii: number;
|
|
41
|
+
a_color: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Sentinel fallback for the Size slot when the user hasn't picked one:
|
|
46
|
+
* use the first non-metadata column in the incoming view.
|
|
47
|
+
*/
|
|
48
|
+
function firstNonMetadataColumn(columns: ColumnDataMap): string {
|
|
49
|
+
for (const k of columns.keys()) {
|
|
50
|
+
if (!k.startsWith("__")) {
|
|
51
|
+
return k;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Sunburst chart. Shares tree storage + streaming pipeline + color
|
|
60
|
+
* mode with `TreeChartBase`; adds polar layout + instanced-arc WebGL
|
|
61
|
+
* rendering + drill / tooltip interactions.
|
|
62
|
+
*
|
|
63
|
+
* Internal option: `_labelRotation` — `"upright"` keeps labels on the
|
|
64
|
+
* left half flipped 180° so they read upright (d3fc behavior);
|
|
65
|
+
* `"radial"` leaves them purely tangent to the arc. Defaults to
|
|
66
|
+
* `"upright"`; toggle here if a call site wants flat radial labels.
|
|
67
|
+
*/
|
|
68
|
+
export class SunburstChart extends TreeChartBase {
|
|
69
|
+
_program: WebGLProgram | null = null;
|
|
70
|
+
_locations: SunburstLocations | null = null;
|
|
71
|
+
_stripBuffer: WebGLBuffer | null = null;
|
|
72
|
+
_instanceBuffer: WebGLBuffer | null = null;
|
|
73
|
+
_instanceCount = 0;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Label orientation mode — see class docstring.
|
|
77
|
+
*/
|
|
78
|
+
_labelRotation: "upright" | "radial" = "upright";
|
|
79
|
+
|
|
80
|
+
// Center / radius state resolved per frame.
|
|
81
|
+
_centerX = 0;
|
|
82
|
+
_centerY = 0;
|
|
83
|
+
_maxRadius = 0;
|
|
84
|
+
|
|
85
|
+
// Interaction
|
|
86
|
+
_hoveredNodeId: number = NULL_NODE;
|
|
87
|
+
_pinnedNodeId: number = NULL_NODE;
|
|
88
|
+
_breadcrumbRegions: SunburstBreadcrumbRegion[] = [];
|
|
89
|
+
|
|
90
|
+
_chromeCache: ImageBitmap | null = null;
|
|
91
|
+
_chromeCacheDirty = true;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* See `TreemapChart._chromeCacheGen` — same race, same fix.
|
|
95
|
+
*/
|
|
96
|
+
_chromeCacheGen = 0;
|
|
97
|
+
|
|
98
|
+
// Faceted state
|
|
99
|
+
_facetGrid: import("../../layout/facet-grid").FacetGrid | null = null;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Per-facet drill roots — mirrors `TreemapChart._facetDrillRoots`.
|
|
103
|
+
*/
|
|
104
|
+
_facetDrillRoots: Map<string, number> = new Map();
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Per-facet rendering state. `index` matches the facet grid cell;
|
|
108
|
+
* `centerX`, `centerY`, `maxRadius` are used for layout + hit test;
|
|
109
|
+
* `drillRoot` is the sub-root the facet is currently showing;
|
|
110
|
+
* `instanceStart`, `instanceCount` index into the shared GPU
|
|
111
|
+
* instance buffer for draw dispatch (these are post-skip values,
|
|
112
|
+
* rewritten by `uploadArcInstances` after zero-width arcs and the
|
|
113
|
+
* drill root are filtered out). `nodeStart`, `nodeCount` are the
|
|
114
|
+
* pre-skip range over `_visibleNodeIds` and are *not* rewritten —
|
|
115
|
+
* canvas chrome (arc-label translate origin) walks this range so
|
|
116
|
+
* each label can be placed around its own facet's center instead
|
|
117
|
+
* of the chart-wide `_centerX/_centerY` (which always point at the
|
|
118
|
+
* first facet).
|
|
119
|
+
*/
|
|
120
|
+
_facets: {
|
|
121
|
+
label: string;
|
|
122
|
+
centerX: number;
|
|
123
|
+
centerY: number;
|
|
124
|
+
maxRadius: number;
|
|
125
|
+
drillRoot: number;
|
|
126
|
+
instanceStart: number;
|
|
127
|
+
instanceCount: number;
|
|
128
|
+
nodeStart: number;
|
|
129
|
+
nodeCount: number;
|
|
130
|
+
}[] = [];
|
|
131
|
+
|
|
132
|
+
protected override tooltipCallbacks() {
|
|
133
|
+
return {
|
|
134
|
+
onHover: (mx: number, my: number) =>
|
|
135
|
+
handleSunburstHover(this, mx, my),
|
|
136
|
+
onLeave: () => {
|
|
137
|
+
if (
|
|
138
|
+
this._hoveredNodeId !== NULL_NODE &&
|
|
139
|
+
this._pinnedNodeId === NULL_NODE
|
|
140
|
+
) {
|
|
141
|
+
this._hoveredNodeId = NULL_NODE;
|
|
142
|
+
renderSunburstChromeOverlay(this);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
onClickPre: (mx: number, my: number) => {
|
|
146
|
+
handleSunburstClick(this, mx, my);
|
|
147
|
+
return true;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async uploadAndRender(
|
|
153
|
+
glManager: WebGLContextManager,
|
|
154
|
+
columns: ColumnDataMap,
|
|
155
|
+
startRow: number,
|
|
156
|
+
_endRow: number,
|
|
157
|
+
): Promise<void> {
|
|
158
|
+
this._glManager = glManager;
|
|
159
|
+
|
|
160
|
+
if (startRow === 0) {
|
|
161
|
+
const slots = this._columnSlots;
|
|
162
|
+
this._sizeName = slots[0] || firstNonMetadataColumn(columns) || "";
|
|
163
|
+
this._colorName = slots[1] || "";
|
|
164
|
+
if (!this._colorName) {
|
|
165
|
+
this._colorMode = "empty";
|
|
166
|
+
} else {
|
|
167
|
+
const t = this._columnTypes[this._colorName];
|
|
168
|
+
const isNumeric =
|
|
169
|
+
t === "float" ||
|
|
170
|
+
t === "integer" ||
|
|
171
|
+
t === "date" ||
|
|
172
|
+
t === "datetime";
|
|
173
|
+
this._colorMode = isNumeric ? "numeric" : "series";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Clear per-draw state tied to the old tree — see
|
|
177
|
+
// `TreemapChart.uploadAndRender` for the same pattern and
|
|
178
|
+
// rationale.
|
|
179
|
+
this._hoveredNodeId = NULL_NODE;
|
|
180
|
+
this._pinnedNodeId = NULL_NODE;
|
|
181
|
+
this._breadcrumbRegions = [];
|
|
182
|
+
this._facetDrillRoots.clear();
|
|
183
|
+
this._facetGrid = null;
|
|
184
|
+
this._facets = [];
|
|
185
|
+
|
|
186
|
+
// Invalidate the instance buffer so a render that fires
|
|
187
|
+
// before the fresh upload draws zero arcs.
|
|
188
|
+
this._instanceCount = 0;
|
|
189
|
+
|
|
190
|
+
// Drop any in-flight hover tooltip promise (see treemap).
|
|
191
|
+
this._lazyTooltip.clearHover();
|
|
192
|
+
this._lazyTooltip.invalidatePin();
|
|
193
|
+
dismissSunburstPinnedTooltip(this);
|
|
194
|
+
this._chromeCache?.close();
|
|
195
|
+
this._chromeCache = null;
|
|
196
|
+
this._chromeCacheDirty = true;
|
|
197
|
+
this._chromeCacheGen++;
|
|
198
|
+
|
|
199
|
+
resetTreeState(this);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
processTreeChunk(this, columns);
|
|
203
|
+
finalizeTree(this);
|
|
204
|
+
if (this._rootId !== NULL_NODE) {
|
|
205
|
+
await this.requestRender(glManager);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_fullRender(glManager: WebGLContextManager): void {
|
|
210
|
+
if (this._rootId === NULL_NODE) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this._glManager = glManager;
|
|
215
|
+
renderSunburstFrame(this, glManager);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected destroyInternal(): void {
|
|
219
|
+
dismissSunburstPinnedTooltip(this);
|
|
220
|
+
this._chromeCache?.close();
|
|
221
|
+
this._chromeCache = null;
|
|
222
|
+
const gl = this._glManager?.gl;
|
|
223
|
+
if (gl) {
|
|
224
|
+
if (this._stripBuffer) {
|
|
225
|
+
gl.deleteBuffer(this._stripBuffer);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (this._instanceBuffer) {
|
|
229
|
+
gl.deleteBuffer(this._instanceBuffer);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this._stripBuffer = null;
|
|
234
|
+
this._instanceBuffer = null;
|
|
235
|
+
this._program = null;
|
|
236
|
+
this._locations = null;
|
|
237
|
+
this._rootId = NULL_NODE;
|
|
238
|
+
this._currentRootId = NULL_NODE;
|
|
239
|
+
this._breadcrumbIds = [];
|
|
240
|
+
this._childLookup.clear();
|
|
241
|
+
this._visibleNodeIds = null;
|
|
242
|
+
this._visibleNodeCount = 0;
|
|
243
|
+
this._breadcrumbRegions = [];
|
|
244
|
+
this._facetGrid = null;
|
|
245
|
+
this._facetDrillRoots.clear();
|
|
246
|
+
this._facets = [];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,445 @@
|
|
|
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 { TreemapChart } from "./treemap";
|
|
14
|
+
import { NULL_NODE, ancestorNames } from "../common/node-store";
|
|
15
|
+
import { PADDING_LABEL, rebuildBreadcrumbs } from "./treemap-layout";
|
|
16
|
+
import {
|
|
17
|
+
renderTreemapFrame,
|
|
18
|
+
renderTreemapChromeOverlay,
|
|
19
|
+
} from "./treemap-render";
|
|
20
|
+
|
|
21
|
+
interface HitResult {
|
|
22
|
+
leafId: number;
|
|
23
|
+
branchId: number;
|
|
24
|
+
inHeader: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Find the smallest leaf AND deepest branch at `(mx, my)`. Walks the
|
|
29
|
+
* (already LOD-filtered) `_visibleNodeIds` — at 2M total nodes this is
|
|
30
|
+
* still a small linear scan because LOD keeps visible count bounded.
|
|
31
|
+
*
|
|
32
|
+
* In faceted mode `chart._visibleRootIds[i]` names the drill root that
|
|
33
|
+
* owns node `i`, so the "skip the root itself" check works regardless
|
|
34
|
+
* of which facet the node belongs to.
|
|
35
|
+
*/
|
|
36
|
+
function hitTest(chart: TreemapChart, mx: number, my: number): HitResult {
|
|
37
|
+
const store = chart._nodeStore;
|
|
38
|
+
const x0 = store.x0;
|
|
39
|
+
const y0 = store.y0;
|
|
40
|
+
const x1 = store.x1;
|
|
41
|
+
const y1 = store.y1;
|
|
42
|
+
const depth = store.depth;
|
|
43
|
+
const firstChild = store.firstChild;
|
|
44
|
+
const ids = chart._visibleNodeIds;
|
|
45
|
+
const n = chart._visibleNodeCount;
|
|
46
|
+
const baseArr = chart._visibleBaseDepths;
|
|
47
|
+
const rootArr = chart._visibleRootIds;
|
|
48
|
+
|
|
49
|
+
let bestLeafId = NULL_NODE;
|
|
50
|
+
let bestLeafArea = Infinity;
|
|
51
|
+
let bestBranchId = NULL_NODE;
|
|
52
|
+
let bestBranchArea = Infinity;
|
|
53
|
+
let labelBranchId = NULL_NODE;
|
|
54
|
+
|
|
55
|
+
if (!ids) {
|
|
56
|
+
return { leafId: NULL_NODE, branchId: NULL_NODE, inHeader: false };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
const id = ids[i];
|
|
61
|
+
const rootId = rootArr ? rootArr[i] : chart._currentRootId;
|
|
62
|
+
if (id === rootId) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!(mx >= x0[id] && mx <= x1[id] && my >= y0[id] && my <= y1[id])) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const area = (x1[id] - x0[id]) * (y1[id] - y0[id]);
|
|
71
|
+
if (firstChild[id] !== NULL_NODE) {
|
|
72
|
+
if (area < bestBranchArea) {
|
|
73
|
+
bestBranchArea = area;
|
|
74
|
+
bestBranchId = id;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const baseDepth = baseArr
|
|
78
|
+
? baseArr[i]
|
|
79
|
+
: depth[chart._currentRootId];
|
|
80
|
+
const relDepth = depth[id] - baseDepth;
|
|
81
|
+
if (relDepth === 1 && my <= y0[id] + PADDING_LABEL) {
|
|
82
|
+
labelBranchId = id;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (relDepth === 2) {
|
|
86
|
+
const nw = x1[id] - x0[id];
|
|
87
|
+
const nh = y1[id] - y0[id];
|
|
88
|
+
if (nw >= 60 && nh >= 30) {
|
|
89
|
+
const cy = y0[id] + nh / 2;
|
|
90
|
+
const cx = x0[id] + nw / 2;
|
|
91
|
+
if (
|
|
92
|
+
Math.abs(my - cy) < 10 &&
|
|
93
|
+
Math.abs(mx - cx) < nw * 0.4
|
|
94
|
+
) {
|
|
95
|
+
labelBranchId = id;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (area < bestLeafArea) {
|
|
101
|
+
bestLeafArea = area;
|
|
102
|
+
bestLeafId = id;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (labelBranchId !== NULL_NODE) {
|
|
108
|
+
return { leafId: NULL_NODE, branchId: labelBranchId, inHeader: true };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
leafId: bestLeafId,
|
|
113
|
+
branchId: bestBranchId,
|
|
114
|
+
inHeader: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function handleTreemapHover(
|
|
119
|
+
chart: TreemapChart,
|
|
120
|
+
mx: number,
|
|
121
|
+
my: number,
|
|
122
|
+
): void {
|
|
123
|
+
if (chart._pinnedNodeId !== NULL_NODE) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const region of chart._breadcrumbRegions) {
|
|
128
|
+
if (
|
|
129
|
+
mx >= region.x0 &&
|
|
130
|
+
mx <= region.x1 &&
|
|
131
|
+
my >= region.y0 &&
|
|
132
|
+
my <= region.y1
|
|
133
|
+
) {
|
|
134
|
+
chart._tooltip.setCursor("pointer");
|
|
135
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
136
|
+
renderTreemapChromeOverlay(chart);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const { leafId, branchId, inHeader } = hitTest(chart, mx, my);
|
|
142
|
+
const best = inHeader ? branchId : leafId !== NULL_NODE ? leafId : branchId;
|
|
143
|
+
|
|
144
|
+
if (best !== chart._hoveredNodeId) {
|
|
145
|
+
chart._hoveredNodeId = best;
|
|
146
|
+
chart._tooltip.setCursor(
|
|
147
|
+
branchId !== NULL_NODE ? "pointer" : "default",
|
|
148
|
+
);
|
|
149
|
+
if (best !== NULL_NODE) {
|
|
150
|
+
// Kick off the lazy tooltip build for hover; re-render
|
|
151
|
+
// the chrome overlay once lines resolve. Stale results
|
|
152
|
+
// (mouse moved elsewhere, new view) are dropped by the
|
|
153
|
+
// controller's serial gate.
|
|
154
|
+
const serial = chart._lazyTooltip.beginHover(best);
|
|
155
|
+
buildTreemapTooltipLines(chart, best).then((lines) => {
|
|
156
|
+
if (chart._lazyTooltip.commitHover(serial, lines)) {
|
|
157
|
+
renderTreemapChromeOverlay(chart);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
chart._lazyTooltip.clearHover();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
renderTreemapChromeOverlay(chart);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function handleTreemapClick(
|
|
169
|
+
chart: TreemapChart,
|
|
170
|
+
mx: number,
|
|
171
|
+
my: number,
|
|
172
|
+
): void {
|
|
173
|
+
if (chart._pinnedNodeId !== NULL_NODE) {
|
|
174
|
+
dismissTreemapPinnedTooltip(chart);
|
|
175
|
+
chart.emitUnselect();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const region of chart._breadcrumbRegions) {
|
|
180
|
+
if (
|
|
181
|
+
mx >= region.x0 &&
|
|
182
|
+
mx <= region.x1 &&
|
|
183
|
+
my >= region.y0 &&
|
|
184
|
+
my <= region.y1
|
|
185
|
+
) {
|
|
186
|
+
if (region.nodeId !== chart._currentRootId) {
|
|
187
|
+
drillTo(chart, region.nodeId);
|
|
188
|
+
// Breadcrumb is chrome — no `perspective-click`. The
|
|
189
|
+
// drill-up pops one or more levels off the host's
|
|
190
|
+
// cached filter stack via `selected: false`.
|
|
191
|
+
chart.emitUnselect();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { leafId, branchId, inHeader } = hitTest(chart, mx, my);
|
|
199
|
+
|
|
200
|
+
if (branchId !== NULL_NODE && inHeader) {
|
|
201
|
+
drillTo(chart, branchId);
|
|
202
|
+
void emitTreemapNodeEvent(chart, branchId, "branch");
|
|
203
|
+
} else if (leafId !== NULL_NODE) {
|
|
204
|
+
showTreemapPinnedTooltip(chart, leafId);
|
|
205
|
+
void emitTreemapNodeEvent(chart, leafId, "leaf");
|
|
206
|
+
} else if (branchId !== NULL_NODE) {
|
|
207
|
+
drillTo(chart, branchId);
|
|
208
|
+
void emitTreemapNodeEvent(chart, branchId, "branch");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Build a click detail from a treemap node id and emit both
|
|
214
|
+
* `perspective-click` and `perspective-global-filter selected:true`.
|
|
215
|
+
*
|
|
216
|
+
* For leaves, the source-view row index is `store.leafRowIdx[id]` and
|
|
217
|
+
* the row payload is populated via `_lazyRows`. For branches, no
|
|
218
|
+
* source row exists (the branch is a rollup), so `rowIdx: null` and
|
|
219
|
+
* the row payload is `{}` — only the filter path is meaningful.
|
|
220
|
+
*
|
|
221
|
+
* The path is walked via `ancestorNames` and split into split-by
|
|
222
|
+
* prefix + group-by levels using `_splitBy.length` as the boundary.
|
|
223
|
+
* Faceted mode (`facet_mode === "grid"` with non-empty `_splitBy`)
|
|
224
|
+
* keeps the depth-0 ancestor name as the split prefix.
|
|
225
|
+
*/
|
|
226
|
+
async function emitTreemapNodeEvent(
|
|
227
|
+
chart: TreemapChart,
|
|
228
|
+
nodeId: number,
|
|
229
|
+
kind: "leaf" | "branch",
|
|
230
|
+
): Promise<void> {
|
|
231
|
+
const store = chart._nodeStore;
|
|
232
|
+
const path = ancestorNames(store, nodeId);
|
|
233
|
+
const isFaceted =
|
|
234
|
+
chart._splitBy.length > 0 && chart._facetConfig.facet_mode === "grid";
|
|
235
|
+
const splitByValues: (string | null)[] = isFaceted
|
|
236
|
+
? path.slice(0, chart._splitBy.length)
|
|
237
|
+
: [];
|
|
238
|
+
const groupByValues: (string | null)[] = isFaceted
|
|
239
|
+
? path.slice(
|
|
240
|
+
chart._splitBy.length,
|
|
241
|
+
chart._splitBy.length + chart._groupBy.length,
|
|
242
|
+
)
|
|
243
|
+
: path.slice(0, chart._groupBy.length);
|
|
244
|
+
|
|
245
|
+
const rowIdx = kind === "leaf" ? (store.leafRowIdx[nodeId] ?? null) : null;
|
|
246
|
+
|
|
247
|
+
await chart.emitClickAndSelect({
|
|
248
|
+
rowIdx: rowIdx != null && rowIdx >= 0 ? rowIdx : null,
|
|
249
|
+
columnName: chart._sizeName,
|
|
250
|
+
groupByValues,
|
|
251
|
+
splitByValues,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function handleTreemapDblClick(
|
|
256
|
+
chart: TreemapChart,
|
|
257
|
+
mx: number,
|
|
258
|
+
my: number,
|
|
259
|
+
): void {
|
|
260
|
+
const wasPinned = chart._pinnedNodeId !== NULL_NODE;
|
|
261
|
+
dismissTreemapPinnedTooltip(chart);
|
|
262
|
+
if (wasPinned) {
|
|
263
|
+
chart.emitUnselect();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { leafId, branchId } = hitTest(chart, mx, my);
|
|
267
|
+
const store = chart._nodeStore;
|
|
268
|
+
let target = branchId;
|
|
269
|
+
if (target === NULL_NODE && leafId !== NULL_NODE) {
|
|
270
|
+
const parent = store.parent[leafId];
|
|
271
|
+
if (parent !== chart._currentRootId && parent !== NULL_NODE) {
|
|
272
|
+
target = parent;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (
|
|
277
|
+
target !== NULL_NODE &&
|
|
278
|
+
target !== chart._currentRootId &&
|
|
279
|
+
store.firstChild[target] !== NULL_NODE
|
|
280
|
+
) {
|
|
281
|
+
drillTo(chart, target);
|
|
282
|
+
void emitTreemapNodeEvent(chart, target, "branch");
|
|
283
|
+
if (leafId !== NULL_NODE && store.firstChild[leafId] === NULL_NODE) {
|
|
284
|
+
showTreemapPinnedTooltip(chart, leafId);
|
|
285
|
+
void emitTreemapNodeEvent(chart, leafId, "leaf");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Drill the current facet (or the whole chart in non-facet mode).
|
|
292
|
+
*
|
|
293
|
+
* In faceted mode, walks up the ancestor chain of `nodeId` until the
|
|
294
|
+
* facet root (a top-level child of `_rootId`) is found, then sets
|
|
295
|
+
* `_facetDrillRoots[facetLabel] = nodeId` so only that facet's
|
|
296
|
+
* subtree re-layouts. Non-facet mode keeps the existing single-
|
|
297
|
+
* `_currentRootId` behavior and rebuilds the breadcrumb trail.
|
|
298
|
+
*/
|
|
299
|
+
function drillTo(chart: TreemapChart, nodeId: number): void {
|
|
300
|
+
const store = chart._nodeStore;
|
|
301
|
+
if (chart._splitBy.length > 0 && chart._facetConfig.facet_mode === "grid") {
|
|
302
|
+
// Walk up to find the facet-root ancestor (top-level child of
|
|
303
|
+
// `_rootId`). Guard against drills that target the synthetic
|
|
304
|
+
// root or a facet root itself — those would un-drill the facet.
|
|
305
|
+
let p = nodeId;
|
|
306
|
+
while (p !== NULL_NODE && store.parent[p] !== chart._rootId) {
|
|
307
|
+
p = store.parent[p];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (p !== NULL_NODE) {
|
|
311
|
+
const label = store.name[p];
|
|
312
|
+
chart._facetDrillRoots.set(label, nodeId);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
316
|
+
if (chart._glManager) {
|
|
317
|
+
renderTreemapFrame(chart, chart._glManager);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
chart._currentRootId = nodeId;
|
|
324
|
+
rebuildBreadcrumbs(chart, nodeId);
|
|
325
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
326
|
+
if (chart._glManager) {
|
|
327
|
+
renderTreemapFrame(chart, chart._glManager);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function showTreemapPinnedTooltip(
|
|
332
|
+
chart: TreemapChart,
|
|
333
|
+
nodeId: number,
|
|
334
|
+
): void {
|
|
335
|
+
chart._tooltip.dismiss();
|
|
336
|
+
chart._pinnedNodeId = nodeId;
|
|
337
|
+
|
|
338
|
+
const store = chart._nodeStore;
|
|
339
|
+
const cx = (store.x0[nodeId] + store.x1[nodeId]) / 2;
|
|
340
|
+
const cy = (store.y0[nodeId] + store.y1[nodeId]) / 2;
|
|
341
|
+
|
|
342
|
+
// CSS bounds: prefer `glManager` (works in both local and worker
|
|
343
|
+
// modes, since the worker constructs its own context manager).
|
|
344
|
+
const cssWidth = chart._glManager?.cssWidth ?? 0;
|
|
345
|
+
const cssHeight = chart._glManager?.cssHeight ?? 0;
|
|
346
|
+
|
|
347
|
+
// Tooltip columns are fetched lazily from the view — the tree
|
|
348
|
+
// itself only retains ancestor names + aggregated value + color.
|
|
349
|
+
// If the user dismisses or re-pins between click and resolve, the
|
|
350
|
+
// `_pinnedNodeId` check discards the stale result.
|
|
351
|
+
buildTreemapTooltipLines(chart, nodeId).then((lines) => {
|
|
352
|
+
if (chart._pinnedNodeId !== nodeId) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (lines.length === 0) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
chart._tooltip.pin(lines, { px: cx, py: cy }, { cssWidth, cssHeight });
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
364
|
+
renderTreemapChromeOverlay(chart);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function dismissTreemapPinnedTooltip(chart: TreemapChart): void {
|
|
368
|
+
chart._tooltip.dismiss();
|
|
369
|
+
chart._pinnedNodeId = NULL_NODE;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Build the tooltip for `nodeId`. The node's own name path + aggregate
|
|
374
|
+
* value are derived from the tree; per-row tooltip columns come from
|
|
375
|
+
* the `leafRowIdx` → column-buffer lookup (no per-node `Map`).
|
|
376
|
+
*/
|
|
377
|
+
export async function buildTreemapTooltipLines(
|
|
378
|
+
chart: TreemapChart,
|
|
379
|
+
nodeId: number,
|
|
380
|
+
): Promise<string[]> {
|
|
381
|
+
const store = chart._nodeStore;
|
|
382
|
+
const lines: string[] = [];
|
|
383
|
+
|
|
384
|
+
// Name path (ancestors, topmost first, excluding synthetic root).
|
|
385
|
+
const pathNames: string[] = [];
|
|
386
|
+
let p = nodeId;
|
|
387
|
+
while (store.parent[p] !== NULL_NODE) {
|
|
388
|
+
pathNames.push(store.name[p]);
|
|
389
|
+
p = store.parent[p];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
pathNames.reverse();
|
|
393
|
+
if (pathNames.length > 0) {
|
|
394
|
+
lines.push(pathNames.join(" \u203A "));
|
|
395
|
+
} else {
|
|
396
|
+
lines.push(store.name[nodeId]);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const sizeFmt = chart.getColumnFormatter(chart._sizeName, "value");
|
|
400
|
+
lines.push(`Value: ${sizeFmt(store.value[nodeId])}`);
|
|
401
|
+
|
|
402
|
+
// Color value (numeric branch): stored on the node at insert
|
|
403
|
+
// time, so it's always available without a view fetch.
|
|
404
|
+
if (chart._colorName && !isNaN(store.colorValue[nodeId])) {
|
|
405
|
+
const colorFmt = chart.getColumnFormatter(chart._colorName, "value");
|
|
406
|
+
lines.push(
|
|
407
|
+
`${chart._colorName}: ${colorFmt(store.colorValue[nodeId])}`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const rowIdx = store.leafRowIdx[nodeId];
|
|
412
|
+
const isLeaf =
|
|
413
|
+
store.firstChild[nodeId] === NULL_NODE && rowIdx !== NULL_NODE;
|
|
414
|
+
|
|
415
|
+
// Extra tooltip columns come from the source view row, fetched on
|
|
416
|
+
// demand via `_lazyRows`. Only leaves correspond to a single view
|
|
417
|
+
// row; branch nodes aggregate rows and don't carry extra columns.
|
|
418
|
+
if (isLeaf && chart._lazyRows) {
|
|
419
|
+
const row = await chart._lazyRows.fetchRow(rowIdx);
|
|
420
|
+
for (const [name, value] of row) {
|
|
421
|
+
if (value === null || value === undefined) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (name === chart._colorName && !isNaN(store.colorValue[nodeId])) {
|
|
426
|
+
// Already emitted from the retained tree state above.
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (typeof value === "number") {
|
|
431
|
+
lines.push(
|
|
432
|
+
`${name}: ${chart.getColumnFormatter(name, "value")(value)}`,
|
|
433
|
+
);
|
|
434
|
+
} else {
|
|
435
|
+
lines.push(`${name}: ${value}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (store.firstChild[nodeId] !== NULL_NODE) {
|
|
441
|
+
lines.push(`Children: ${store.childCount[nodeId]}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return lines;
|
|
445
|
+
}
|