@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,623 @@
|
|
|
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
|
+
* Streaming tree pipeline shared by treemap and sunburst. Rows arrive
|
|
15
|
+
* incrementally; each chunk inserts its rows directly into the SOA
|
|
16
|
+
* tree. Per-leaf tooltip columns are fetched lazily on pin via the
|
|
17
|
+
* chart's `_lazyRows`; the tree only retains `leafRowIdx` per leaf
|
|
18
|
+
* (small, O(leaves)) as the handle back to the source view row.
|
|
19
|
+
* After a chunk is processed, `finalizeTree` recomputes `value`
|
|
20
|
+
* bottom-up.
|
|
21
|
+
*
|
|
22
|
+
* Color mode:
|
|
23
|
+
* - `"numeric"` — `readColor` reads the row's numeric value; the
|
|
24
|
+
* render path maps it through the continuous gradient.
|
|
25
|
+
* - `"series"` — `readColor` reads the row's string value from the
|
|
26
|
+
* color column's dictionary, and `seedColorLabels` pre-populates
|
|
27
|
+
* `_uniqueColorLabels` in dictionary-index order. Render picks
|
|
28
|
+
* `palette[dictIdx % paletteSize]`.
|
|
29
|
+
* - `"empty"` — no color column; every leaf gets `palette[0]`.
|
|
30
|
+
*
|
|
31
|
+
* When `_splitBy` is populated, every row is duplicated — one insertion
|
|
32
|
+
* per split prefix, with that prefix pushed as the top-level path
|
|
33
|
+
* segment so the top-level children of the synthetic root become
|
|
34
|
+
* facet roots. The per-prefix `size` / `color` columns (named
|
|
35
|
+
* `${prefix}|${base}`) feed the facet's values. `seedColorLabels`
|
|
36
|
+
* runs once per split so every split's dictionary contributes to the
|
|
37
|
+
* shared legend.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import type { ColumnDataMap, ColumnData } from "../../data/view-reader";
|
|
41
|
+
import { buildSplitGroups } from "../../data/split-groups";
|
|
42
|
+
import { synthesizeStringLevel } from "./category-axis-resolver";
|
|
43
|
+
import { NULL_NODE } from "./node-store";
|
|
44
|
+
import type { TreeChartBase } from "./tree-chart";
|
|
45
|
+
|
|
46
|
+
// Reset
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reset the shared tree state. Called on the first chunk
|
|
50
|
+
* (`startRow === 0`) of each dataset load.
|
|
51
|
+
*/
|
|
52
|
+
export function resetTreeState(chart: TreeChartBase): void {
|
|
53
|
+
chart._nodeStore.reset();
|
|
54
|
+
chart._childLookup.clear();
|
|
55
|
+
|
|
56
|
+
// Allocate the synthetic root (id 0 by convention).
|
|
57
|
+
const rootId = chart._nodeStore.allocate();
|
|
58
|
+
chart._nodeStore.name[rootId] = "Total";
|
|
59
|
+
chart._nodeStore.depth[rootId] = 0;
|
|
60
|
+
chart._nodeStore.parent[rootId] = NULL_NODE;
|
|
61
|
+
chart._childLookup.set(rootId, new Map());
|
|
62
|
+
|
|
63
|
+
chart._rootId = rootId;
|
|
64
|
+
chart._currentRootId = rootId;
|
|
65
|
+
chart._breadcrumbIds = [rootId];
|
|
66
|
+
|
|
67
|
+
chart._rowCount = 0;
|
|
68
|
+
|
|
69
|
+
chart._colorMin = Infinity;
|
|
70
|
+
chart._colorMax = -Infinity;
|
|
71
|
+
chart._uniqueColorLabels.clear();
|
|
72
|
+
|
|
73
|
+
chart._visibleNodeIds = null;
|
|
74
|
+
chart._visibleNodeCount = 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Tree insertion
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Find-or-create a child of `parentId` named `segment`. Uses a per-
|
|
81
|
+
* parent `Map<name, childId>` for O(1) lookup.
|
|
82
|
+
*/
|
|
83
|
+
function childByName(
|
|
84
|
+
chart: TreeChartBase,
|
|
85
|
+
parentId: number,
|
|
86
|
+
segment: string,
|
|
87
|
+
): number {
|
|
88
|
+
let lookup = chart._childLookup.get(parentId);
|
|
89
|
+
if (!lookup) {
|
|
90
|
+
lookup = new Map();
|
|
91
|
+
chart._childLookup.set(parentId, lookup);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const existing = lookup.get(segment);
|
|
95
|
+
if (existing !== undefined) {
|
|
96
|
+
return existing;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const childId = chart._nodeStore.allocate();
|
|
100
|
+
chart._nodeStore.name[childId] = segment;
|
|
101
|
+
chart._nodeStore.appendChild(parentId, childId);
|
|
102
|
+
lookup.set(segment, childId);
|
|
103
|
+
return childId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Insert one row into the tree.
|
|
108
|
+
*/
|
|
109
|
+
function insertRow(
|
|
110
|
+
chart: TreeChartBase,
|
|
111
|
+
rowPath: string[],
|
|
112
|
+
sizeValue: number,
|
|
113
|
+
colorValue: number,
|
|
114
|
+
colorLabel: string,
|
|
115
|
+
rowIdx: number,
|
|
116
|
+
groupByLen: number,
|
|
117
|
+
): void {
|
|
118
|
+
let currentId = chart._rootId;
|
|
119
|
+
const depth = rowPath.length;
|
|
120
|
+
for (let d = 0; d < depth; d++) {
|
|
121
|
+
const childId = childByName(chart, currentId, rowPath[d]);
|
|
122
|
+
|
|
123
|
+
if (d === depth - 1) {
|
|
124
|
+
if (groupByLen === 0 || depth === groupByLen) {
|
|
125
|
+
// Store `|size|` for layout; remember the sign so
|
|
126
|
+
// render can dim negative leaves (matches the area
|
|
127
|
+
// chart's `theme.areaOpacity`).
|
|
128
|
+
chart._nodeStore.size[childId] = Math.abs(sizeValue);
|
|
129
|
+
chart._nodeStore.sizeSign[childId] = sizeValue < 0 ? -1 : 1;
|
|
130
|
+
chart._nodeStore.leafRowIdx[childId] = rowIdx;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!isNaN(colorValue)) {
|
|
134
|
+
chart._nodeStore.colorValue[childId] = colorValue;
|
|
135
|
+
if (colorValue < chart._colorMin) {
|
|
136
|
+
chart._colorMin = colorValue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (colorValue > chart._colorMax) {
|
|
140
|
+
chart._colorMax = colorValue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (colorLabel) {
|
|
145
|
+
chart._nodeStore.colorLabel[childId] = colorLabel;
|
|
146
|
+
if (!chart._uniqueColorLabels.has(colorLabel)) {
|
|
147
|
+
chart._uniqueColorLabels.set(
|
|
148
|
+
colorLabel,
|
|
149
|
+
chart._uniqueColorLabels.size,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
currentId = childId;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readColor(
|
|
160
|
+
chart: TreeChartBase,
|
|
161
|
+
colorCol: ColumnData | null | undefined,
|
|
162
|
+
rowIdx: number,
|
|
163
|
+
): { colorValue: number; colorLabel: string } {
|
|
164
|
+
let colorValue = NaN;
|
|
165
|
+
let colorLabel = "";
|
|
166
|
+
if (!colorCol) {
|
|
167
|
+
return { colorValue, colorLabel };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (chart._colorMode === "numeric" && colorCol.values) {
|
|
171
|
+
colorValue = colorCol.values[rowIdx] as number;
|
|
172
|
+
} else if (
|
|
173
|
+
chart._colorMode === "series" &&
|
|
174
|
+
colorCol.indices &&
|
|
175
|
+
colorCol.dictionary
|
|
176
|
+
) {
|
|
177
|
+
// Read the dictionary-decoded string for this row. The palette
|
|
178
|
+
// index that render uses is `_uniqueColorLabels.get(label)`,
|
|
179
|
+
// which `seedColorLabels` seeds in dictionary-index order so
|
|
180
|
+
// the end result is `palette[dictIdx % paletteSize]`.
|
|
181
|
+
colorLabel = colorCol.dictionary[colorCol.indices[rowIdx]];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { colorValue, colorLabel };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Seed `_uniqueColorLabels` with the color column's dictionary in
|
|
189
|
+
* index order. Using insertion-order-guarded `.set` means later
|
|
190
|
+
* chunks (or later splits in split_by mode) append new entries
|
|
191
|
+
* without disturbing already-assigned indices; for a single stable
|
|
192
|
+
* dictionary this yields `_uniqueColorLabels.get(dict[i]) === i`.
|
|
193
|
+
*
|
|
194
|
+
* No-op outside `"series"` mode or when the column lacks a
|
|
195
|
+
* dictionary.
|
|
196
|
+
*/
|
|
197
|
+
function seedColorLabels(
|
|
198
|
+
chart: TreeChartBase,
|
|
199
|
+
colorCol: ColumnData | null | undefined,
|
|
200
|
+
): void {
|
|
201
|
+
if (chart._colorMode !== "series") {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!colorCol?.dictionary) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const dict = colorCol.dictionary;
|
|
210
|
+
for (let i = 0; i < dict.length; i++) {
|
|
211
|
+
const s = dict[i];
|
|
212
|
+
if (!chart._uniqueColorLabels.has(s)) {
|
|
213
|
+
chart._uniqueColorLabels.set(s, chart._uniqueColorLabels.size);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Chunk processor
|
|
219
|
+
|
|
220
|
+
interface SplitSource {
|
|
221
|
+
prefix: string;
|
|
222
|
+
sizeCol: ColumnData | null;
|
|
223
|
+
colorCol: ColumnData | null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Resolve the per-split size / color columns. Returns `null` when
|
|
228
|
+
* `_splitBy` is empty — callers then take the non-split fast path.
|
|
229
|
+
*/
|
|
230
|
+
function resolveSplitSources(
|
|
231
|
+
chart: TreeChartBase,
|
|
232
|
+
columns: ColumnDataMap,
|
|
233
|
+
): SplitSource[] | null {
|
|
234
|
+
if (chart._splitBy.length === 0) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const required: string[] = chart._sizeName ? [chart._sizeName] : [];
|
|
239
|
+
const optional: string[] = chart._colorName ? [chart._colorName] : [];
|
|
240
|
+
const groups = buildSplitGroups(columns, required, optional);
|
|
241
|
+
if (groups.length === 0) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return groups.map((g) => ({
|
|
246
|
+
prefix: g.prefix,
|
|
247
|
+
sizeCol: chart._sizeName
|
|
248
|
+
? (columns.get(`${g.prefix}|${chart._sizeName}`) ?? null)
|
|
249
|
+
: null,
|
|
250
|
+
colorCol: chart._colorName
|
|
251
|
+
? (columns.get(`${g.prefix}|${chart._colorName}`) ?? null)
|
|
252
|
+
: null,
|
|
253
|
+
}));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Process one incoming chunk: grow row-data buffers, walk every row,
|
|
258
|
+
* capture column values, and insert into the tree.
|
|
259
|
+
*/
|
|
260
|
+
export function processTreeChunk(
|
|
261
|
+
chart: TreeChartBase,
|
|
262
|
+
columns: ColumnDataMap,
|
|
263
|
+
): void {
|
|
264
|
+
const rpCols: { indices: Int32Array; dictionary: string[] }[] = [];
|
|
265
|
+
for (let n = 0; ; n++) {
|
|
266
|
+
const rp = columns.get(`__ROW_PATH_${n}__`);
|
|
267
|
+
if (!rp) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (rp.type === "string" && rp.indices && rp.dictionary) {
|
|
272
|
+
rpCols.push({ indices: rp.indices, dictionary: rp.dictionary });
|
|
273
|
+
} else if (rp.values) {
|
|
274
|
+
// Non-string group_by (integer / float / date / datetime /
|
|
275
|
+
// boolean) — synthesize a string-indexed dictionary so the
|
|
276
|
+
// tree insertion loop can treat every level uniformly.
|
|
277
|
+
// Uses the same formatter as `resolveCategoryAxis`.
|
|
278
|
+
const levelName = chart._groupBy[n];
|
|
279
|
+
const levelType = chart._groupByTypes[levelName] ?? "string";
|
|
280
|
+
rpCols.push(synthesizeStringLevel(rp, rp.values.length, levelType));
|
|
281
|
+
} else {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const hasGroupBy = rpCols.length > 0;
|
|
287
|
+
const groupByLen = chart._groupBy.length;
|
|
288
|
+
const splitSources = resolveSplitSources(chart, columns);
|
|
289
|
+
const hasSplits = splitSources !== null;
|
|
290
|
+
|
|
291
|
+
const sizeCol = chart._sizeName ? columns.get(chart._sizeName) : null;
|
|
292
|
+
const colorCol = chart._colorName ? columns.get(chart._colorName) : null;
|
|
293
|
+
|
|
294
|
+
const firstSizeCol = hasSplits ? splitSources![0].sizeCol : sizeCol;
|
|
295
|
+
const numRows = hasGroupBy
|
|
296
|
+
? rpCols[0].indices.length
|
|
297
|
+
: (firstSizeCol?.values?.length ?? 0);
|
|
298
|
+
if (numRows === 0) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Seed palette label indices from the color column's dictionary
|
|
303
|
+
// BEFORE inserting rows, so the first row doesn't assign label 0
|
|
304
|
+
// to whichever dict value it happens to reference. For splits we
|
|
305
|
+
// seed once per split's own color column so every dict value is
|
|
306
|
+
// known to the shared legend.
|
|
307
|
+
if (hasSplits) {
|
|
308
|
+
for (const src of splitSources!) {
|
|
309
|
+
seedColorLabels(chart, src.colorCol);
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
seedColorLabels(chart, colorCol);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// `base` is the source-view row offset the tree should tag its
|
|
316
|
+
// leaves with. `_rowCount` tracks how many rows prior chunks
|
|
317
|
+
// occupied so `leafRowIdx[childId] = base + i` still points back
|
|
318
|
+
// to the correct view row after multiple chunk arrivals.
|
|
319
|
+
const base = chart._rowCount;
|
|
320
|
+
|
|
321
|
+
// The split expansion inserts every row under one extra leading
|
|
322
|
+
// path segment — the split prefix. When `group_by` is also active
|
|
323
|
+
// the path is `[prefix, ...groupByLevels]` (length `groupByLen + 1`)
|
|
324
|
+
// and the leaf store gate inside `insertRow` (`depth === groupByLen`)
|
|
325
|
+
// needs the `+1` to find the leaf. When `group_by` is empty we fall
|
|
326
|
+
// through the no-group-by branch below, which writes paths of the
|
|
327
|
+
// form `[prefix, label]`; that path's leaf is the row label, and
|
|
328
|
+
// sizing it relies on `insertRow`'s `groupByLen === 0`
|
|
329
|
+
// "every leaf gets sized" branch — passing `0` here lets that
|
|
330
|
+
// branch fire so each row's leaf actually receives a size. The
|
|
331
|
+
// historical `+1`-always form quietly produced an effective
|
|
332
|
+
// `groupByLen=1` against a depth-2 path, so the size store was
|
|
333
|
+
// skipped and treemap / sunburst rendered nothing.
|
|
334
|
+
const effectiveGroupLen =
|
|
335
|
+
hasSplits && hasGroupBy ? groupByLen + 1 : groupByLen;
|
|
336
|
+
|
|
337
|
+
if (!hasGroupBy) {
|
|
338
|
+
// Flat fallback: synthesize a single-segment path per row from
|
|
339
|
+
// the first string column (or a "Row N" sentinel).
|
|
340
|
+
//
|
|
341
|
+
// In `split_by` mode every column the view emits is pivoted as
|
|
342
|
+
// `${splitPrefix}|${baseName}`, so two correctness traps need
|
|
343
|
+
// to be avoided here:
|
|
344
|
+
//
|
|
345
|
+
// 1. The size / color column the user picked appears under
|
|
346
|
+
// its prefixed form (`First Class|Region`), so a literal
|
|
347
|
+
// `name === chart._sizeName/_colorName` skip would miss
|
|
348
|
+
// them and the loop would happily pick the color column
|
|
349
|
+
// itself as the label source.
|
|
350
|
+
// 2. Reading a pivoted column by row index returns values
|
|
351
|
+
// keyed to ONE specific split (the prefix that happened
|
|
352
|
+
// to win the iteration order), not to that row's actual
|
|
353
|
+
// split — so even a "different" pivoted dictionary column
|
|
354
|
+
// isn't a legitimate per-row label. And once any pivoted
|
|
355
|
+
// column is used as a label source, `childByName` collapses
|
|
356
|
+
// every row sharing a `(prefix, label)` pair onto a single
|
|
357
|
+
// node — last-write-wins for `size` — which truncates the
|
|
358
|
+
// visible cell count to `cardinality(label) × cardinality(splits)`.
|
|
359
|
+
//
|
|
360
|
+
// The two checks below address both: compare base names (post-`|`)
|
|
361
|
+
// for the size/color skip, and reject any pivoted column outright
|
|
362
|
+
// in split mode. If nothing remains, `labelCol` stays undefined
|
|
363
|
+
// and the per-row loop falls through to the `Row N` sentinel,
|
|
364
|
+
// which gives every row a unique key.
|
|
365
|
+
let labelCol: ColumnData | undefined;
|
|
366
|
+
for (const [name, col] of columns) {
|
|
367
|
+
if (name.startsWith("__")) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (hasSplits && name.includes("|")) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const pipeIdx = name.lastIndexOf("|");
|
|
376
|
+
const baseName =
|
|
377
|
+
pipeIdx === -1 ? name : name.substring(pipeIdx + 1);
|
|
378
|
+
if (baseName === chart._sizeName || baseName === chart._colorName) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (col.type === "string" && col.indices && col.dictionary) {
|
|
383
|
+
labelCol = col;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const sources = hasSplits ? splitSources! : null;
|
|
389
|
+
for (let i = 0; i < numRows; i++) {
|
|
390
|
+
const label =
|
|
391
|
+
labelCol?.indices && labelCol?.dictionary
|
|
392
|
+
? labelCol.dictionary[labelCol.indices[i]]
|
|
393
|
+
: `Row ${base + i}`;
|
|
394
|
+
|
|
395
|
+
if (sources) {
|
|
396
|
+
for (const src of sources) {
|
|
397
|
+
// Pass the signed value through; `insertRow` stores
|
|
398
|
+
// `|size|` and `sizeSign` separately so the
|
|
399
|
+
// render pass can dim negative leaves.
|
|
400
|
+
const sizeValue = src.sizeCol?.values
|
|
401
|
+
? (src.sizeCol.values[i] as number)
|
|
402
|
+
: 1;
|
|
403
|
+
const { colorValue, colorLabel } = readColor(
|
|
404
|
+
chart,
|
|
405
|
+
src.colorCol,
|
|
406
|
+
i,
|
|
407
|
+
);
|
|
408
|
+
insertRow(
|
|
409
|
+
chart,
|
|
410
|
+
[src.prefix, label],
|
|
411
|
+
sizeValue,
|
|
412
|
+
colorValue,
|
|
413
|
+
colorLabel,
|
|
414
|
+
base + i,
|
|
415
|
+
effectiveGroupLen,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
const sizeValue = sizeCol?.values
|
|
420
|
+
? (sizeCol.values[i] as number)
|
|
421
|
+
: 1;
|
|
422
|
+
const { colorValue, colorLabel } = readColor(
|
|
423
|
+
chart,
|
|
424
|
+
colorCol,
|
|
425
|
+
i,
|
|
426
|
+
);
|
|
427
|
+
insertRow(
|
|
428
|
+
chart,
|
|
429
|
+
[label],
|
|
430
|
+
sizeValue,
|
|
431
|
+
colorValue,
|
|
432
|
+
colorLabel,
|
|
433
|
+
base + i,
|
|
434
|
+
effectiveGroupLen,
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
chart._rowCount = base + numRows;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Hierarchical (group_by present): reuse a scratch path buffer
|
|
444
|
+
// across rows to avoid per-row array allocation. When splits are
|
|
445
|
+
// active the scratch is one slot longer to hold the leading prefix.
|
|
446
|
+
const extra = hasSplits ? 1 : 0;
|
|
447
|
+
const pathScratch: string[] = new Array(rpCols.length + extra);
|
|
448
|
+
for (let i = 0; i < numRows; i++) {
|
|
449
|
+
let pathLen = 0;
|
|
450
|
+
for (let d = 0; d < rpCols.length; d++) {
|
|
451
|
+
const rp = rpCols[d];
|
|
452
|
+
const label = rp.dictionary[rp.indices[i]];
|
|
453
|
+
if (!label && label !== "0") {
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
pathScratch[extra + pathLen++] = label;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (pathLen === 0) {
|
|
461
|
+
continue;
|
|
462
|
+
} // skip total row
|
|
463
|
+
|
|
464
|
+
if (hasSplits) {
|
|
465
|
+
for (const src of splitSources!) {
|
|
466
|
+
pathScratch[0] = src.prefix;
|
|
467
|
+
const rowPath = pathScratch.slice(0, pathLen + 1);
|
|
468
|
+
const sizeValue = src.sizeCol?.values
|
|
469
|
+
? (src.sizeCol.values[i] as number)
|
|
470
|
+
: 1;
|
|
471
|
+
const { colorValue, colorLabel } = readColor(
|
|
472
|
+
chart,
|
|
473
|
+
src.colorCol,
|
|
474
|
+
i,
|
|
475
|
+
);
|
|
476
|
+
insertRow(
|
|
477
|
+
chart,
|
|
478
|
+
rowPath,
|
|
479
|
+
sizeValue,
|
|
480
|
+
colorValue,
|
|
481
|
+
colorLabel,
|
|
482
|
+
base + i,
|
|
483
|
+
effectiveGroupLen,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
const rowPath = pathScratch.slice(0, pathLen);
|
|
488
|
+
const sizeValue = sizeCol?.values
|
|
489
|
+
? (sizeCol.values[i] as number)
|
|
490
|
+
: 1;
|
|
491
|
+
const { colorValue, colorLabel } = readColor(chart, colorCol, i);
|
|
492
|
+
insertRow(
|
|
493
|
+
chart,
|
|
494
|
+
rowPath,
|
|
495
|
+
sizeValue,
|
|
496
|
+
colorValue,
|
|
497
|
+
colorLabel,
|
|
498
|
+
base + i,
|
|
499
|
+
effectiveGroupLen,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
chart._rowCount = base + numRows;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Finalize
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Post-chunk finalization.
|
|
511
|
+
* 1. Recompute `value` bottom-up from `size` via an iterative
|
|
512
|
+
* post-order walk.
|
|
513
|
+
* 2. Re-resolve `_currentRootId` from the breadcrumb name-path so
|
|
514
|
+
* drill state survives incremental chunk arrivals.
|
|
515
|
+
*
|
|
516
|
+
* `colorLabel` is set at insert time (`readColor`) and needs no
|
|
517
|
+
* post-pass: in `"series"` mode it comes from the color column's
|
|
518
|
+
* dictionary, and in `"numeric"` / `"empty"` modes it's unused.
|
|
519
|
+
*/
|
|
520
|
+
export function finalizeTree(chart: TreeChartBase): void {
|
|
521
|
+
const store = chart._nodeStore;
|
|
522
|
+
const value = store.value;
|
|
523
|
+
const size = store.size;
|
|
524
|
+
const firstChild = store.firstChild;
|
|
525
|
+
const nextSibling = store.nextSibling;
|
|
526
|
+
|
|
527
|
+
// Iterative post-order. Stack holds `(id, state)` pairs; state
|
|
528
|
+
// 0 = pre-visit, 1 = post-visit.
|
|
529
|
+
let stack = new Int32Array(128);
|
|
530
|
+
stack[0] = chart._rootId;
|
|
531
|
+
stack[1] = 0;
|
|
532
|
+
let sp = 1;
|
|
533
|
+
|
|
534
|
+
// Reset value accumulators.
|
|
535
|
+
for (let i = 0; i < store.count; i++) {
|
|
536
|
+
value[i] = 0;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
while (sp > 0) {
|
|
540
|
+
sp--;
|
|
541
|
+
const id = stack[sp * 2];
|
|
542
|
+
const s = stack[sp * 2 + 1];
|
|
543
|
+
if (s === 0) {
|
|
544
|
+
stack[sp * 2 + 1] = 1;
|
|
545
|
+
sp++;
|
|
546
|
+
for (let c = firstChild[id]; c !== NULL_NODE; c = nextSibling[c]) {
|
|
547
|
+
if ((sp + 1) * 2 > stack.length) {
|
|
548
|
+
const bigger = new Int32Array(stack.length * 2);
|
|
549
|
+
bigger.set(stack);
|
|
550
|
+
stack = bigger;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
stack[sp * 2] = c;
|
|
554
|
+
stack[sp * 2 + 1] = 0;
|
|
555
|
+
sp++;
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
if (firstChild[id] === NULL_NODE) {
|
|
559
|
+
value[id] = Math.max(0, size[id]);
|
|
560
|
+
} else {
|
|
561
|
+
let sum = 0;
|
|
562
|
+
for (
|
|
563
|
+
let c = firstChild[id];
|
|
564
|
+
c !== NULL_NODE;
|
|
565
|
+
c = nextSibling[c]
|
|
566
|
+
) {
|
|
567
|
+
sum += value[c];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
value[id] = sum;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Preserve drill state across incremental chunk arrivals: walk the
|
|
576
|
+
// breadcrumb name-path from the root and re-resolve. If a segment
|
|
577
|
+
// is missing (shouldn't happen with incremental build, but
|
|
578
|
+
// defensively), fall back to the root.
|
|
579
|
+
if (chart._breadcrumbIds.length > 1) {
|
|
580
|
+
const breadcrumbNames: string[] = [];
|
|
581
|
+
for (let i = 1; i < chart._breadcrumbIds.length; i++) {
|
|
582
|
+
breadcrumbNames.push(store.name[chart._breadcrumbIds[i]]);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let node = chart._rootId;
|
|
586
|
+
let valid = true;
|
|
587
|
+
for (const seg of breadcrumbNames) {
|
|
588
|
+
const lookup = chart._childLookup.get(node);
|
|
589
|
+
const next = lookup?.get(seg);
|
|
590
|
+
if (next === undefined) {
|
|
591
|
+
valid = false;
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
node = next;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (valid && store.firstChild[node] !== NULL_NODE) {
|
|
599
|
+
chart._currentRootId = node;
|
|
600
|
+
rebuildBreadcrumbs(chart, node);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
chart._currentRootId = chart._rootId;
|
|
606
|
+
chart._breadcrumbIds = [chart._rootId];
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Breadcrumbs
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Rebuild `chart._breadcrumbIds` by walking up from `nodeId`.
|
|
613
|
+
*/
|
|
614
|
+
export function rebuildBreadcrumbs(chart: TreeChartBase, nodeId: number): void {
|
|
615
|
+
const ids: number[] = [];
|
|
616
|
+
let n = nodeId;
|
|
617
|
+
while (n !== NULL_NODE) {
|
|
618
|
+
ids.unshift(n);
|
|
619
|
+
n = chart._nodeStore.parent[n];
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
chart._breadcrumbIds = ids;
|
|
623
|
+
}
|