@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,460 @@
|
|
|
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 { SunburstChart } from "./sunburst";
|
|
14
|
+
import { NULL_NODE, ancestorNames } from "../common/node-store";
|
|
15
|
+
import { rebuildBreadcrumbs } from "../common/tree-data";
|
|
16
|
+
import {
|
|
17
|
+
renderSunburstFrame,
|
|
18
|
+
renderSunburstChromeOverlay,
|
|
19
|
+
facetCenterForNode,
|
|
20
|
+
} from "./sunburst-render";
|
|
21
|
+
|
|
22
|
+
export type { BreadcrumbRegion as SunburstBreadcrumbRegion } from "../common/tree-chrome";
|
|
23
|
+
|
|
24
|
+
interface FacetHitContext {
|
|
25
|
+
centerX: number;
|
|
26
|
+
centerY: number;
|
|
27
|
+
drillRoot: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Pre-upload visible range for this facet; undefined in non-facet mode.
|
|
31
|
+
*/
|
|
32
|
+
range?: { start: number; end: number };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the facet under cursor; returns single-plot defaults outside facet mode.
|
|
37
|
+
*/
|
|
38
|
+
function facetUnderCursor(
|
|
39
|
+
chart: SunburstChart,
|
|
40
|
+
mx: number,
|
|
41
|
+
my: number,
|
|
42
|
+
): FacetHitContext | null {
|
|
43
|
+
if (chart._facets.length === 0) {
|
|
44
|
+
return {
|
|
45
|
+
centerX: chart._centerX,
|
|
46
|
+
centerY: chart._centerY,
|
|
47
|
+
drillRoot: chart._currentRootId,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const facet of chart._facets) {
|
|
52
|
+
// Post-upload `instanceStart` / `instanceCount` are scan-index
|
|
53
|
+
// ranges into `_visibleNodeIds` — we need the *pre-upload*
|
|
54
|
+
// range to hit-test all arcs (including zero-width ones we
|
|
55
|
+
// skipped for draw). Walk the IDs and match by drill root
|
|
56
|
+
// ancestry instead.
|
|
57
|
+
const dx = mx - facet.centerX;
|
|
58
|
+
const dy = my - facet.centerY;
|
|
59
|
+
const r = Math.sqrt(dx * dx + dy * dy);
|
|
60
|
+
if (r > facet.maxRadius + 4) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
centerX: facet.centerX,
|
|
66
|
+
centerY: facet.centerY,
|
|
67
|
+
drillRoot: facet.drillRoot,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Walk the ancestor chain from `id` up to (but not including) the
|
|
76
|
+
* synthetic `_rootId`, returning true if any step equals `anc`.
|
|
77
|
+
* Used to filter hit-test candidates to arcs that belong to a given
|
|
78
|
+
* facet's drill subtree.
|
|
79
|
+
*/
|
|
80
|
+
function isDescendantOf(
|
|
81
|
+
chart: SunburstChart,
|
|
82
|
+
id: number,
|
|
83
|
+
anc: number,
|
|
84
|
+
): boolean {
|
|
85
|
+
const store = chart._nodeStore;
|
|
86
|
+
let p = id;
|
|
87
|
+
while (p !== NULL_NODE) {
|
|
88
|
+
if (p === anc) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
p = store.parent[p];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert `(mx, my)` to polar and find the containing visible arc.
|
|
100
|
+
*/
|
|
101
|
+
function polarHitTest(chart: SunburstChart, mx: number, my: number): number {
|
|
102
|
+
const ctx = facetUnderCursor(chart, mx, my);
|
|
103
|
+
if (!ctx) {
|
|
104
|
+
return NULL_NODE;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const store = chart._nodeStore;
|
|
108
|
+
const ids = chart._visibleNodeIds;
|
|
109
|
+
const n = chart._visibleNodeCount;
|
|
110
|
+
if (!ids) {
|
|
111
|
+
return NULL_NODE;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const dx = mx - ctx.centerX;
|
|
115
|
+
const dy = my - ctx.centerY;
|
|
116
|
+
const r = Math.sqrt(dx * dx + dy * dy);
|
|
117
|
+
let theta = Math.atan2(dy, dx);
|
|
118
|
+
if (theta < 0) {
|
|
119
|
+
theta += 2 * Math.PI;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Center-circle hit — drill-up target.
|
|
123
|
+
if (r < store.r1[chart._rootId] + 0.001) {
|
|
124
|
+
if (ctx.drillRoot !== chart._rootId) {
|
|
125
|
+
return ctx.drillRoot;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const faceted = chart._facets.length > 0;
|
|
130
|
+
for (let i = 0; i < n; i++) {
|
|
131
|
+
const id = ids[i];
|
|
132
|
+
if (id === ctx.drillRoot) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (faceted && !isDescendantOf(chart, id, ctx.drillRoot)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const a0 = store.a0[id];
|
|
141
|
+
const a1 = store.a1[id];
|
|
142
|
+
const r0 = store.r0[id];
|
|
143
|
+
const r1 = store.r1[id];
|
|
144
|
+
if (r < r0 || r > r1) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (theta < a0 || theta > a1) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return id;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return NULL_NODE;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function handleSunburstHover(
|
|
159
|
+
chart: SunburstChart,
|
|
160
|
+
mx: number,
|
|
161
|
+
my: number,
|
|
162
|
+
): void {
|
|
163
|
+
if (chart._pinnedNodeId !== NULL_NODE) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Breadcrumb region check first (they sit atop the chart area).
|
|
168
|
+
for (const region of chart._breadcrumbRegions) {
|
|
169
|
+
if (
|
|
170
|
+
mx >= region.x0 &&
|
|
171
|
+
mx <= region.x1 &&
|
|
172
|
+
my >= region.y0 &&
|
|
173
|
+
my <= region.y1
|
|
174
|
+
) {
|
|
175
|
+
chart._tooltip.setCursor("pointer");
|
|
176
|
+
if (chart._hoveredNodeId !== NULL_NODE) {
|
|
177
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
178
|
+
renderSunburstChromeOverlay(chart);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const hit = polarHitTest(chart, mx, my);
|
|
186
|
+
const store = chart._nodeStore;
|
|
187
|
+
chart._tooltip.setCursor(
|
|
188
|
+
hit !== NULL_NODE && store.firstChild[hit] !== NULL_NODE
|
|
189
|
+
? "pointer"
|
|
190
|
+
: "default",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (hit !== chart._hoveredNodeId) {
|
|
194
|
+
chart._hoveredNodeId = hit;
|
|
195
|
+
if (hit !== NULL_NODE) {
|
|
196
|
+
const serial = chart._lazyTooltip.beginHover(hit);
|
|
197
|
+
buildSunburstTooltipLines(chart, hit).then((lines) => {
|
|
198
|
+
if (chart._lazyTooltip.commitHover(serial, lines)) {
|
|
199
|
+
renderSunburstChromeOverlay(chart);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
chart._lazyTooltip.clearHover();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
renderSunburstChromeOverlay(chart);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function handleSunburstClick(
|
|
211
|
+
chart: SunburstChart,
|
|
212
|
+
mx: number,
|
|
213
|
+
my: number,
|
|
214
|
+
): void {
|
|
215
|
+
if (chart._pinnedNodeId !== NULL_NODE) {
|
|
216
|
+
dismissSunburstPinnedTooltip(chart);
|
|
217
|
+
chart.emitUnselect();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Breadcrumb click = drill to that crumb.
|
|
222
|
+
for (const region of chart._breadcrumbRegions) {
|
|
223
|
+
if (
|
|
224
|
+
mx >= region.x0 &&
|
|
225
|
+
mx <= region.x1 &&
|
|
226
|
+
my >= region.y0 &&
|
|
227
|
+
my <= region.y1
|
|
228
|
+
) {
|
|
229
|
+
if (region.nodeId !== chart._currentRootId) {
|
|
230
|
+
drillTo(chart, region.nodeId);
|
|
231
|
+
chart.emitUnselect();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Center-circle click = drill up one level (parent of current root).
|
|
239
|
+
const store = chart._nodeStore;
|
|
240
|
+
const ctx = facetUnderCursor(chart, mx, my);
|
|
241
|
+
if (ctx) {
|
|
242
|
+
const dx = mx - ctx.centerX;
|
|
243
|
+
const dy = my - ctx.centerY;
|
|
244
|
+
const r = Math.sqrt(dx * dx + dy * dy);
|
|
245
|
+
if (r < store.r1[chart._rootId] + 0.001) {
|
|
246
|
+
const parent = store.parent[ctx.drillRoot];
|
|
247
|
+
if (parent !== NULL_NODE && parent !== chart._rootId) {
|
|
248
|
+
drillTo(chart, parent);
|
|
249
|
+
chart.emitUnselect();
|
|
250
|
+
} else if (chart._facets.length > 0) {
|
|
251
|
+
// Already at the facet root: reset this facet's drill.
|
|
252
|
+
const facet = chart._facets.find(
|
|
253
|
+
(f) => f.drillRoot === ctx.drillRoot,
|
|
254
|
+
);
|
|
255
|
+
if (facet) {
|
|
256
|
+
chart._facetDrillRoots.delete(facet.label);
|
|
257
|
+
chart.emitUnselect();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (chart._glManager) {
|
|
261
|
+
renderSunburstFrame(chart, chart._glManager);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const hit = polarHitTest(chart, mx, my);
|
|
270
|
+
if (hit === NULL_NODE) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (store.firstChild[hit] !== NULL_NODE) {
|
|
275
|
+
drillTo(chart, hit);
|
|
276
|
+
void emitSunburstNodeEvent(chart, hit, "branch");
|
|
277
|
+
} else {
|
|
278
|
+
showSunburstPinnedTooltip(chart, hit);
|
|
279
|
+
void emitSunburstNodeEvent(chart, hit, "leaf");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Counterpart to `emitTreemapNodeEvent` for sunburst. Same path-walk
|
|
285
|
+
* semantics — split-by prefix in faceted mode, group-by levels
|
|
286
|
+
* afterward, leaf row idx from `_nodeStore.leafRowIdx`.
|
|
287
|
+
*/
|
|
288
|
+
async function emitSunburstNodeEvent(
|
|
289
|
+
chart: SunburstChart,
|
|
290
|
+
nodeId: number,
|
|
291
|
+
kind: "leaf" | "branch",
|
|
292
|
+
): Promise<void> {
|
|
293
|
+
const store = chart._nodeStore;
|
|
294
|
+
const path = ancestorNames(store, nodeId);
|
|
295
|
+
const isFaceted =
|
|
296
|
+
chart._splitBy.length > 0 && chart._facetConfig.facet_mode === "grid";
|
|
297
|
+
const splitByValues: (string | null)[] = isFaceted
|
|
298
|
+
? path.slice(0, chart._splitBy.length)
|
|
299
|
+
: [];
|
|
300
|
+
const groupByValues: (string | null)[] = isFaceted
|
|
301
|
+
? path.slice(
|
|
302
|
+
chart._splitBy.length,
|
|
303
|
+
chart._splitBy.length + chart._groupBy.length,
|
|
304
|
+
)
|
|
305
|
+
: path.slice(0, chart._groupBy.length);
|
|
306
|
+
|
|
307
|
+
const rowIdx = kind === "leaf" ? (store.leafRowIdx[nodeId] ?? null) : null;
|
|
308
|
+
|
|
309
|
+
await chart.emitClickAndSelect({
|
|
310
|
+
rowIdx: rowIdx != null && rowIdx >= 0 ? rowIdx : null,
|
|
311
|
+
columnName: chart._sizeName,
|
|
312
|
+
groupByValues,
|
|
313
|
+
splitByValues,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Drill the clicked facet (or the whole chart in non-facet mode).
|
|
319
|
+
* Faceted drill walks up to the facet root (top-level child of
|
|
320
|
+
* `_rootId`), records the new drill node under that facet's label,
|
|
321
|
+
* and re-renders.
|
|
322
|
+
*/
|
|
323
|
+
function drillTo(chart: SunburstChart, nodeId: number): void {
|
|
324
|
+
const store = chart._nodeStore;
|
|
325
|
+
if (chart._splitBy.length > 0 && chart._facetConfig.facet_mode === "grid") {
|
|
326
|
+
let p = nodeId;
|
|
327
|
+
while (p !== NULL_NODE && store.parent[p] !== chart._rootId) {
|
|
328
|
+
p = store.parent[p];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (p !== NULL_NODE) {
|
|
332
|
+
chart._facetDrillRoots.set(store.name[p], nodeId);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
336
|
+
if (chart._glManager) {
|
|
337
|
+
renderSunburstFrame(chart, chart._glManager);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
chart._currentRootId = nodeId;
|
|
344
|
+
rebuildBreadcrumbs(chart, nodeId);
|
|
345
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
346
|
+
if (chart._glManager) {
|
|
347
|
+
renderSunburstFrame(chart, chart._glManager);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function showSunburstPinnedTooltip(
|
|
352
|
+
chart: SunburstChart,
|
|
353
|
+
nodeId: number,
|
|
354
|
+
): void {
|
|
355
|
+
chart._tooltip.dismiss();
|
|
356
|
+
chart._pinnedNodeId = nodeId;
|
|
357
|
+
|
|
358
|
+
const store = chart._nodeStore;
|
|
359
|
+
const midA = (store.a0[nodeId] + store.a1[nodeId]) / 2;
|
|
360
|
+
const midR = (store.r0[nodeId] + store.r1[nodeId]) / 2;
|
|
361
|
+
const { centerX, centerY } = facetCenterForNode(chart, nodeId);
|
|
362
|
+
const cx = centerX + Math.cos(midA) * midR;
|
|
363
|
+
const cy = centerY + Math.sin(midA) * midR;
|
|
364
|
+
|
|
365
|
+
// CSS bounds: prefer `glManager` (works in both local and worker
|
|
366
|
+
// modes, since the worker constructs its own context manager).
|
|
367
|
+
const cssWidth = chart._glManager?.cssWidth ?? 0;
|
|
368
|
+
const cssHeight = chart._glManager?.cssHeight ?? 0;
|
|
369
|
+
|
|
370
|
+
// Tooltip columns are fetched lazily from the view — the tree
|
|
371
|
+
// itself only retains ancestor names + aggregated value + color.
|
|
372
|
+
// Stale resolutions are discarded via the `_pinnedNodeId` check.
|
|
373
|
+
buildSunburstTooltipLines(chart, nodeId).then((lines) => {
|
|
374
|
+
if (chart._pinnedNodeId !== nodeId) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (lines.length === 0) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
chart._tooltip.pin(lines, { px: cx, py: cy }, { cssWidth, cssHeight });
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
chart._hoveredNodeId = NULL_NODE;
|
|
386
|
+
renderSunburstChromeOverlay(chart);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function dismissSunburstPinnedTooltip(chart: SunburstChart): void {
|
|
390
|
+
chart._tooltip.dismiss();
|
|
391
|
+
chart._pinnedNodeId = NULL_NODE;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export async function buildSunburstTooltipLines(
|
|
395
|
+
chart: SunburstChart,
|
|
396
|
+
nodeId: number,
|
|
397
|
+
): Promise<string[]> {
|
|
398
|
+
const store = chart._nodeStore;
|
|
399
|
+
const lines: string[] = [];
|
|
400
|
+
|
|
401
|
+
// Ancestor path.
|
|
402
|
+
const pathNames: string[] = [];
|
|
403
|
+
let p = nodeId;
|
|
404
|
+
while (store.parent[p] !== NULL_NODE) {
|
|
405
|
+
pathNames.push(store.name[p]);
|
|
406
|
+
p = store.parent[p];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
pathNames.reverse();
|
|
410
|
+
if (pathNames.length > 0) {
|
|
411
|
+
lines.push(pathNames.join(" › "));
|
|
412
|
+
} else {
|
|
413
|
+
lines.push(store.name[nodeId]);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const sizeFmt = chart.getColumnFormatter(chart._sizeName, "value");
|
|
417
|
+
lines.push(`Value: ${sizeFmt(store.value[nodeId])}`);
|
|
418
|
+
|
|
419
|
+
// Color value (numeric branch): stored on the node at insert
|
|
420
|
+
// time, so it's always available without a view fetch.
|
|
421
|
+
if (chart._colorName && !isNaN(store.colorValue[nodeId])) {
|
|
422
|
+
const colorFmt = chart.getColumnFormatter(chart._colorName, "value");
|
|
423
|
+
lines.push(
|
|
424
|
+
`${chart._colorName}: ${colorFmt(store.colorValue[nodeId])}`,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const rowIdx = store.leafRowIdx[nodeId];
|
|
429
|
+
const isLeaf =
|
|
430
|
+
store.firstChild[nodeId] === NULL_NODE && rowIdx !== NULL_NODE;
|
|
431
|
+
|
|
432
|
+
// Extra tooltip columns fetched on demand — see the treemap
|
|
433
|
+
// counterpart for the same pattern.
|
|
434
|
+
if (isLeaf && chart._lazyRows) {
|
|
435
|
+
const row = await chart._lazyRows.fetchRow(rowIdx);
|
|
436
|
+
for (const [name, value] of row) {
|
|
437
|
+
if (value === null || value === undefined) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (name === chart._colorName && !isNaN(store.colorValue[nodeId])) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (typeof value === "number") {
|
|
446
|
+
lines.push(
|
|
447
|
+
`${name}: ${chart.getColumnFormatter(name, "value")(value)}`,
|
|
448
|
+
);
|
|
449
|
+
} else {
|
|
450
|
+
lines.push(`${name}: ${value}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (store.firstChild[nodeId] !== NULL_NODE) {
|
|
456
|
+
lines.push(`Children: ${store.childCount[nodeId]}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return lines;
|
|
460
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
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 { SunburstChart } from "./sunburst";
|
|
14
|
+
import { NULL_NODE, type NodeStore } from "../common/node-store";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Minimum arc area (in pixels²) below which a subtree stops being
|
|
18
|
+
* subdivided. Keeps the visible count bounded independent of tree
|
|
19
|
+
* size — the core mechanism that lets sunburst scale to 2M nodes.
|
|
20
|
+
*/
|
|
21
|
+
const MIN_VISIBLE_ARC_AREA = 4;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Inner radius: reserved for the current-root drill-up target.
|
|
25
|
+
*/
|
|
26
|
+
const INNER_RING_PX = 30;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Compute the max visible tree depth beneath `currentRootId`.
|
|
30
|
+
* Needed for ring-width calculation.
|
|
31
|
+
*/
|
|
32
|
+
function maxDepthBelow(store: NodeStore, currentRootId: number): number {
|
|
33
|
+
// Iterative DFS, no recursion.
|
|
34
|
+
let maxDepth = 0;
|
|
35
|
+
const baseDepth = store.depth[currentRootId];
|
|
36
|
+
let stack = new Int32Array(128);
|
|
37
|
+
stack[0] = currentRootId;
|
|
38
|
+
let sp = 1;
|
|
39
|
+
while (sp > 0) {
|
|
40
|
+
sp--;
|
|
41
|
+
const id = stack[sp];
|
|
42
|
+
const d = store.depth[id] - baseDepth;
|
|
43
|
+
if (d > maxDepth) {
|
|
44
|
+
maxDepth = d;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (
|
|
48
|
+
let c = store.firstChild[id];
|
|
49
|
+
c !== NULL_NODE;
|
|
50
|
+
c = store.nextSibling[c]
|
|
51
|
+
) {
|
|
52
|
+
if (sp >= stack.length) {
|
|
53
|
+
const bigger = new Int32Array(stack.length * 2);
|
|
54
|
+
bigger.set(stack);
|
|
55
|
+
stack = bigger;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
stack[sp++] = c;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return maxDepth;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Recursive polar partition writing `(a0, a1, r0, r1)` into the store.
|
|
67
|
+
* The root node's own ring is reserved (inner radius = 0, outer =
|
|
68
|
+
* `INNER_RING_PX`) for the drill-up click target; descendants start at
|
|
69
|
+
* `INNER_RING_PX` and extend out.
|
|
70
|
+
*/
|
|
71
|
+
export function partitionSunburst(
|
|
72
|
+
store: NodeStore,
|
|
73
|
+
currentRootId: number,
|
|
74
|
+
maxRadius: number,
|
|
75
|
+
): void {
|
|
76
|
+
const baseDepth = store.depth[currentRootId];
|
|
77
|
+
const maxD = Math.max(1, maxDepthBelow(store, currentRootId));
|
|
78
|
+
const usableRadius = Math.max(0, maxRadius - INNER_RING_PX);
|
|
79
|
+
const ringWidth = usableRadius / maxD;
|
|
80
|
+
|
|
81
|
+
// Root spans full circle; its ring is the inner drill-up circle.
|
|
82
|
+
store.a0[currentRootId] = 0;
|
|
83
|
+
store.a1[currentRootId] = 2 * Math.PI;
|
|
84
|
+
store.r0[currentRootId] = 0;
|
|
85
|
+
store.r1[currentRootId] = INNER_RING_PX;
|
|
86
|
+
|
|
87
|
+
// Recurse into children with stack (same pattern as treemap's
|
|
88
|
+
// squarify scratch).
|
|
89
|
+
partitionChildren(
|
|
90
|
+
store,
|
|
91
|
+
currentRootId,
|
|
92
|
+
0,
|
|
93
|
+
2 * Math.PI,
|
|
94
|
+
INNER_RING_PX,
|
|
95
|
+
ringWidth,
|
|
96
|
+
baseDepth,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function partitionChildren(
|
|
101
|
+
store: NodeStore,
|
|
102
|
+
parentId: number,
|
|
103
|
+
a0: number,
|
|
104
|
+
a1: number,
|
|
105
|
+
parentR1: number,
|
|
106
|
+
ringWidth: number,
|
|
107
|
+
baseDepth: number,
|
|
108
|
+
): void {
|
|
109
|
+
const totalValue = store.value[parentId];
|
|
110
|
+
if (totalValue <= 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const span = a1 - a0;
|
|
115
|
+
|
|
116
|
+
let cursor = a0;
|
|
117
|
+
for (
|
|
118
|
+
let c = store.firstChild[parentId];
|
|
119
|
+
c !== NULL_NODE;
|
|
120
|
+
c = store.nextSibling[c]
|
|
121
|
+
) {
|
|
122
|
+
const v = store.value[c];
|
|
123
|
+
if (v <= 0) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const frac = v / totalValue;
|
|
128
|
+
const childA0 = cursor;
|
|
129
|
+
const childA1 = cursor + span * frac;
|
|
130
|
+
cursor = childA1;
|
|
131
|
+
|
|
132
|
+
const childR0 = parentR1;
|
|
133
|
+
const childR1 = parentR1 + ringWidth;
|
|
134
|
+
store.a0[c] = childA0;
|
|
135
|
+
store.a1[c] = childA1;
|
|
136
|
+
store.r0[c] = childR0;
|
|
137
|
+
store.r1[c] = childR1;
|
|
138
|
+
|
|
139
|
+
// LOD: stop subdividing if this arc's projected pixel area
|
|
140
|
+
// falls below the threshold. Descendants keep stale coords but
|
|
141
|
+
// `collectVisibleArcs` will skip them.
|
|
142
|
+
const arcSpan = childA1 - childA0;
|
|
143
|
+
const midR = (childR0 + childR1) / 2;
|
|
144
|
+
const arcLen = arcSpan * midR; // pixel length along arc
|
|
145
|
+
const area = arcLen * ringWidth;
|
|
146
|
+
if (area < MIN_VISIBLE_ARC_AREA) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (store.firstChild[c] !== NULL_NODE) {
|
|
151
|
+
partitionChildren(
|
|
152
|
+
store,
|
|
153
|
+
c,
|
|
154
|
+
childA0,
|
|
155
|
+
childA1,
|
|
156
|
+
childR1,
|
|
157
|
+
ringWidth,
|
|
158
|
+
baseDepth,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Walk from `startId` depth-first, emitting every descendant whose
|
|
166
|
+
* arc area exceeds `MIN_VISIBLE_ARC_AREA`.
|
|
167
|
+
*
|
|
168
|
+
* The single-facet entry point; faceted rendering uses
|
|
169
|
+
* {@link collectVisibleArcsAppend} to concatenate across facets.
|
|
170
|
+
*/
|
|
171
|
+
export function collectVisibleArcs(
|
|
172
|
+
chart: SunburstChart,
|
|
173
|
+
startId: number,
|
|
174
|
+
): void {
|
|
175
|
+
chart._visibleNodeCount = collectVisibleArcsAppend(chart, startId, 0);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Append visible arcs under `startId` to `_visibleNodeIds` starting at
|
|
180
|
+
* `startOffset`, returning the new length.
|
|
181
|
+
*/
|
|
182
|
+
export function collectVisibleArcsAppend(
|
|
183
|
+
chart: SunburstChart,
|
|
184
|
+
startId: number,
|
|
185
|
+
startOffset: number,
|
|
186
|
+
): number {
|
|
187
|
+
const store = chart._nodeStore;
|
|
188
|
+
const a0 = store.a0;
|
|
189
|
+
const a1 = store.a1;
|
|
190
|
+
const r0 = store.r0;
|
|
191
|
+
const r1 = store.r1;
|
|
192
|
+
const firstChild = store.firstChild;
|
|
193
|
+
const nextSibling = store.nextSibling;
|
|
194
|
+
const value = store.value;
|
|
195
|
+
|
|
196
|
+
if (!chart._visibleNodeIds || chart._visibleNodeIds.length < store.count) {
|
|
197
|
+
chart._visibleNodeIds = new Int32Array(store.count);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const out = chart._visibleNodeIds;
|
|
201
|
+
let outIdx = startOffset;
|
|
202
|
+
|
|
203
|
+
let stack = new Int32Array(128);
|
|
204
|
+
stack[0] = startId;
|
|
205
|
+
let sp = 1;
|
|
206
|
+
|
|
207
|
+
while (sp > 0) {
|
|
208
|
+
sp--;
|
|
209
|
+
const id = stack[sp];
|
|
210
|
+
if (value[id] <= 0) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
out[outIdx++] = id;
|
|
215
|
+
|
|
216
|
+
const arcSpan = a1[id] - a0[id];
|
|
217
|
+
const midR = (r0[id] + r1[id]) / 2;
|
|
218
|
+
const arcLen = arcSpan * midR;
|
|
219
|
+
const ringWidth = r1[id] - r0[id];
|
|
220
|
+
if (arcLen * ringWidth < MIN_VISIBLE_ARC_AREA) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (let c = firstChild[id]; c !== NULL_NODE; c = nextSibling[c]) {
|
|
225
|
+
if (sp >= stack.length) {
|
|
226
|
+
const bigger = new Int32Array(stack.length * 2);
|
|
227
|
+
bigger.set(stack);
|
|
228
|
+
stack = bigger;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
stack[sp++] = c;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return outIdx;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { INNER_RING_PX, MIN_VISIBLE_ARC_AREA };
|