@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,794 @@
|
|
|
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 type { ZoomConfig } from "../../interaction/zoom-controller";
|
|
16
|
+
import { CategoricalYChart } from "../common/categorical-y-chart";
|
|
17
|
+
import { type PlotRect } from "../../layout/plot-layout";
|
|
18
|
+
import { type AxisDomain } from "../../axis/numeric-axis";
|
|
19
|
+
import {
|
|
20
|
+
buildSeriesPipeline,
|
|
21
|
+
readBarRecord,
|
|
22
|
+
type SeriesChartRecord,
|
|
23
|
+
type NumericCategoryDomain,
|
|
24
|
+
type SeriesInfo,
|
|
25
|
+
type BarColumns,
|
|
26
|
+
emptyBarColumns,
|
|
27
|
+
} from "./series-build";
|
|
28
|
+
import {
|
|
29
|
+
renderBarFrame,
|
|
30
|
+
uploadBarInstances,
|
|
31
|
+
invalidateGlyphBuffers,
|
|
32
|
+
rebuildGlyphBuffers,
|
|
33
|
+
} from "./series-render";
|
|
34
|
+
import {
|
|
35
|
+
handleBarHover,
|
|
36
|
+
handleBarLegendClick,
|
|
37
|
+
showBarPinnedTooltip,
|
|
38
|
+
showBarPinnedTooltipForSample,
|
|
39
|
+
} from "./series-interact";
|
|
40
|
+
import { resolvePalette } from "../../theme/palette";
|
|
41
|
+
import { LineGlyph } from "./glyphs/draw-lines";
|
|
42
|
+
import { ScatterGlyph } from "./glyphs/draw-scatter";
|
|
43
|
+
import { AreaGlyph } from "./glyphs/draw-areas";
|
|
44
|
+
import barVert from "../../shaders/bar.vert.glsl";
|
|
45
|
+
import barFrag from "../../shaders/bar.frag.glsl";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Per-frame memo of the auto-fit value extent for a {@link SeriesChart},
|
|
49
|
+
* keyed on the visible categorical window. Two axis slots (`left*` /
|
|
50
|
+
* `right*`) because dual-axis bar charts refit independently.
|
|
51
|
+
*/
|
|
52
|
+
export interface SeriesAutoFitCache {
|
|
53
|
+
catMin: number;
|
|
54
|
+
catMax: number;
|
|
55
|
+
hidden: Set<number>;
|
|
56
|
+
leftMin: number;
|
|
57
|
+
leftMax: number;
|
|
58
|
+
hasLeft: boolean;
|
|
59
|
+
rightMin: number;
|
|
60
|
+
rightMax: number;
|
|
61
|
+
hasRight: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CachedLocations {
|
|
65
|
+
u_proj_left: WebGLUniformLocation | null;
|
|
66
|
+
u_proj_right: WebGLUniformLocation | null;
|
|
67
|
+
u_hover_series: WebGLUniformLocation | null;
|
|
68
|
+
u_horizontal: WebGLUniformLocation | null;
|
|
69
|
+
a_corner: number;
|
|
70
|
+
a_x_center: number;
|
|
71
|
+
a_half_width: number;
|
|
72
|
+
a_y0: number;
|
|
73
|
+
a_y1: number;
|
|
74
|
+
a_color: number;
|
|
75
|
+
a_series_id: number;
|
|
76
|
+
a_axis: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Bar chart. Fields are package-internal (no `private`) so helper modules
|
|
81
|
+
* in this folder can read/write them.
|
|
82
|
+
*
|
|
83
|
+
* Orientation: vertical (Y Bar) is the default — categorical X, numeric
|
|
84
|
+
* Y. When `_isHorizontal` is true (X Bar) the roles swap: numeric X,
|
|
85
|
+
* categorical Y reading top-to-bottom. The data pipeline + instance
|
|
86
|
+
* attributes stay in *logical* coordinates (xCenter = category center,
|
|
87
|
+
* y0/y1 = value extent); the swap happens in three places only:
|
|
88
|
+
* 1. Projection matrix (`bar-render.ts`) — args reordered, Y flipped.
|
|
89
|
+
* 2. Vertex shader — `u_horizontal` uniform transposes position.
|
|
90
|
+
* 3. Chrome (`bar-axis.ts`) — categorical axis moves from bottom to
|
|
91
|
+
* left; numeric axis from left to bottom.
|
|
92
|
+
* Hit-testing reads the swapped pixel→data mapping via the projected
|
|
93
|
+
* `PlotLayout`, so its logical comparisons don't need changes.
|
|
94
|
+
*/
|
|
95
|
+
export class SeriesChart extends CategoricalYChart {
|
|
96
|
+
readonly _isHorizontal: boolean;
|
|
97
|
+
|
|
98
|
+
constructor(orientation: "vertical" | "horizontal" = "vertical") {
|
|
99
|
+
super();
|
|
100
|
+
this._isHorizontal = orientation === "horizontal";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Lock the categorical axis — scrolling through category indices
|
|
105
|
+
* isn't meaningful, and the layout code assumes all categories are
|
|
106
|
+
* always present. The value axis stays freely zoomable.
|
|
107
|
+
*/
|
|
108
|
+
protected override getZoomConfig(): ZoomConfig {
|
|
109
|
+
return { lockAxis: this._isHorizontal ? "x" : "y" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
_locations: CachedLocations | null = null;
|
|
113
|
+
|
|
114
|
+
// Series-specific categorical-axis bookkeeping. `_rowPaths`,
|
|
115
|
+
// `_numCategories`, `_rowOffset`, `_program`, `_cornerBuffer`,
|
|
116
|
+
// `_lastLayout`, `_lastXDomain`, `_lastYDomain`, `_lastYTicks`, and
|
|
117
|
+
// `_autoFitValue` all live on `CategoricalYChart`.
|
|
118
|
+
_aggregates: string[] = [];
|
|
119
|
+
_splitPrefixes: string[] = [];
|
|
120
|
+
_series: SeriesInfo[] = [];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Columnar bar/area record storage. Indexed by bar slot in
|
|
124
|
+
* `[0, _bars.count)`. Replaces the legacy `SeriesChartRecord[]` to
|
|
125
|
+
* avoid per-record POJO allocation on data load.
|
|
126
|
+
*/
|
|
127
|
+
_bars: BarColumns = emptyBarColumns();
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Pre-partitioned series indices by glyph type — populated at the end
|
|
131
|
+
* of `uploadAndRender` and reused across frames. Eliminates per-glyph
|
|
132
|
+
* `chart._series.filter(...)` allocations in the render loop. Each
|
|
133
|
+
* holds the full list of that type (including hidden series); the
|
|
134
|
+
* draw paths still skip hidden via `_hiddenSeries` lookup.
|
|
135
|
+
*/
|
|
136
|
+
_barSeries: SeriesInfo[] = [];
|
|
137
|
+
_lineSeries: SeriesInfo[] = [];
|
|
138
|
+
_scatterSeries: SeriesInfo[] = [];
|
|
139
|
+
_areaSeries: SeriesInfo[] = [];
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Cached primary / secondary axis labels — `_series.filter().map().
|
|
143
|
+
* dedupe().join()` per axis, recomputed only on series-set change.
|
|
144
|
+
*/
|
|
145
|
+
_primaryValueLabel = "";
|
|
146
|
+
_altValueLabel = "";
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* (seriesId * 1e9 + catIdx) → bar-record index in `_bars`. Built once
|
|
150
|
+
* per pipeline run for area-strip lookups; rebuilt on hidden-toggle
|
|
151
|
+
* is unnecessary because the index keys don't depend on hidden state.
|
|
152
|
+
*/
|
|
153
|
+
_areaBarIndex: Map<number, number> | null = null;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Cached Y-color buffer state for `uploadBarColors` short-circuit.
|
|
157
|
+
* `_lastUploadedColors` mirrors the bytes last shipped to the GPU;
|
|
158
|
+
* `uploadBarColors` skips when the new buffer matches byte-for-byte.
|
|
159
|
+
* Reset (set to `null`) on data load or palette change.
|
|
160
|
+
*/
|
|
161
|
+
_lastUploadedColors: Float32Array | null = null;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Cached palette + identity-keys for short-circuiting per-frame
|
|
165
|
+
* resolution. Inputs (`seriesPalette` ref, `gradientStops` ref,
|
|
166
|
+
* `series.length`) only change on data load or `restyle()`.
|
|
167
|
+
*/
|
|
168
|
+
_paletteCache: [number, number, number][] | null = null;
|
|
169
|
+
_paletteCacheKey: {
|
|
170
|
+
seriesPalette: [number, number, number][] | null;
|
|
171
|
+
gradientStops: unknown;
|
|
172
|
+
seriesLength: number;
|
|
173
|
+
} | null = null;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Reusable scratch for the build pipeline — keeps the stack ladder
|
|
177
|
+
* `Float64Array(N*M)` capacity hot across data reloads. The pipeline
|
|
178
|
+
* resizes if the new build's footprint exceeds capacity.
|
|
179
|
+
*/
|
|
180
|
+
_posStackScratch: Float64Array | null = null;
|
|
181
|
+
_negStackScratch: Float64Array | null = null;
|
|
182
|
+
_leftDomain: { min: number; max: number } = { min: 0, max: 1 };
|
|
183
|
+
_rightDomain: { min: number; max: number } | null = null;
|
|
184
|
+
_hasRightAxis = false;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* `domain_mode: "expand"` accumulators. Hold the running union of
|
|
188
|
+
* every prior build's value-axis (and, in numeric-category mode,
|
|
189
|
+
* category-axis) extent for as long as the option is active.
|
|
190
|
+
* Cleared in `resetExpandedDomain` — wired from the worker's
|
|
191
|
+
* `resetAllZooms` and from view-config mutations on the base
|
|
192
|
+
* class. `null` whenever the option is `"fit"` or the accumulator
|
|
193
|
+
* has just been cleared; the next build re-seeds.
|
|
194
|
+
*/
|
|
195
|
+
_expandedLeftDomain: { min: number; max: number } | null = null;
|
|
196
|
+
_expandedRightDomain: { min: number; max: number } | null = null;
|
|
197
|
+
_expandedCategoryDomain: { min: number; max: number } | null = null;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Numeric category-axis state. Populated only when `group_by` has
|
|
201
|
+
* exactly one level and that level is `date | datetime | integer |
|
|
202
|
+
* float` (boolean → category). When set, `_bars[].xCenter` lives in
|
|
203
|
+
* real data units (not logical category indices), and the
|
|
204
|
+
* categorical-side axis renders as a numeric axis instead of the
|
|
205
|
+
* stringified-category one.
|
|
206
|
+
*/
|
|
207
|
+
_categoryAxisMode: "category" | "numeric" = "category";
|
|
208
|
+
_numericCategoryDomain: NumericCategoryDomain | null = null;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Origin used to rebase category positions before f32 narrowing.
|
|
212
|
+
* Datetime numeric category axes carry ~1.7e12-magnitude values
|
|
213
|
+
* which f32 cannot represent below ~256ms; the GPU buffers store
|
|
214
|
+
* `(xCenter - _categoryOrigin)` and the projection matrix is built
|
|
215
|
+
* with the same origin so its `tx` term stays small. Leftover
|
|
216
|
+
* absolute coords are still available via `_numericCategoryDomain`
|
|
217
|
+
* for axis-tick formatting and `dataToPixel`. `0` in category mode
|
|
218
|
+
* (where positions are small integer indices) and in non-datetime
|
|
219
|
+
* numeric modes (integer / float categories also fit in f32).
|
|
220
|
+
*/
|
|
221
|
+
_categoryOrigin = 0;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Cached numeric category-axis ticks for the last frame.
|
|
225
|
+
*/
|
|
226
|
+
_lastCatTicks: number[] | null = null;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Per-category X coordinate in real data units (numeric axis mode
|
|
230
|
+
* only). `null` in category mode — line/scatter/area glyphs fall
|
|
231
|
+
* back to using `catIdx` directly as the X coordinate.
|
|
232
|
+
*/
|
|
233
|
+
_categoryPositions: Float64Array | null = null;
|
|
234
|
+
|
|
235
|
+
_hiddenSeries: Set<number> = new Set();
|
|
236
|
+
_hoveredBarIdx = -1;
|
|
237
|
+
_pinnedBarIdx = -1;
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Synthetic bar record for hover hits on line / scatter glyphs that
|
|
241
|
+
* don't have a real `BarRecord` in `_bars`. At most one of
|
|
242
|
+
* `_hoveredBarIdx` and `_hoveredSample` is populated per frame; see
|
|
243
|
+
* {@link ./bar-interact.getHoveredBar}.
|
|
244
|
+
*/
|
|
245
|
+
_hoveredSample: SeriesChartRecord | null = null;
|
|
246
|
+
|
|
247
|
+
// Unstacked sample grid produced by buildBarPipeline: samples[catI * S + seriesId].
|
|
248
|
+
_samples: Float32Array = new Float32Array(0);
|
|
249
|
+
_sampleValid: Uint8Array = new Uint8Array(0);
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Typed glyph composition. Each glyph (line / scatter / area) owns
|
|
253
|
+
* its program cache and persistent vertex buffers privately; the
|
|
254
|
+
* chart routes draw / rebuild / invalidate via `_glyphs`. Bar
|
|
255
|
+
* glyph state lives on the chart directly (shared bar program +
|
|
256
|
+
* `_locations` + buffer pool), so it's a free function rather than
|
|
257
|
+
* a class.
|
|
258
|
+
*/
|
|
259
|
+
readonly _glyphs = {
|
|
260
|
+
lines: new LineGlyph(),
|
|
261
|
+
scatter: new ScatterGlyph(),
|
|
262
|
+
areas: new AreaGlyph(),
|
|
263
|
+
} as const;
|
|
264
|
+
|
|
265
|
+
// Dual-axis bar charts keep a secondary Y-axis domain + ticks for
|
|
266
|
+
// the right-side axis chrome.
|
|
267
|
+
_lastAltYDomain: AxisDomain | null = null;
|
|
268
|
+
_lastAltYTicks: number[] | null = null;
|
|
269
|
+
|
|
270
|
+
_uploadedBars = 0;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Bar-record indices uploaded to the instance buffers, in dispatch
|
|
274
|
+
* order. `_uploadedBars` is the active prefix length; the trailing
|
|
275
|
+
* capacity is reused across data reloads / legend toggles.
|
|
276
|
+
*/
|
|
277
|
+
_visibleBarIndices: Int32Array = new Int32Array(0);
|
|
278
|
+
|
|
279
|
+
_legendRects: { seriesId: number; rect: PlotRect }[] = [];
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Cached legend layout — recomputed only on series-set / palette /
|
|
283
|
+
* hidden-set / theme change. Frame-rate redraws read from this
|
|
284
|
+
* directly; otherwise `ctx.measureText` would run per series each
|
|
285
|
+
* frame. `null` flags an invalidation; `_legendRects` is rebuilt
|
|
286
|
+
* lazily on the next chrome pass.
|
|
287
|
+
*/
|
|
288
|
+
_legendCacheValid = false;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Per-frame memo of the auto-fit value extent keyed on the visible
|
|
292
|
+
* categorical window. Two comparisons per hit → no walk. Reset to
|
|
293
|
+
* null on any mutation that would change the outcome (data reload,
|
|
294
|
+
* legend toggle).
|
|
295
|
+
*
|
|
296
|
+
* Two axis slots because dual-axis bar charts refit left and right
|
|
297
|
+
* independently.
|
|
298
|
+
*
|
|
299
|
+
* TODO(perf): when the visible window shrinks from a large N, the
|
|
300
|
+
* linear walk over `_bars` dominates for N > ~100K. `_bars` is
|
|
301
|
+
* already ordered by `catIdx`, so a binary-search pair to find the
|
|
302
|
+
* visible slice drops this to O(log N + K_visible). Deferred until
|
|
303
|
+
* profiling shows the walk in the hot path — current scale caps
|
|
304
|
+
* keep it below 1% of frame time.
|
|
305
|
+
*/
|
|
306
|
+
_autoFitCache: SeriesAutoFitCache | null = null;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Per-category extent buckets. Built once per data load (and
|
|
310
|
+
* rebuilt when `_hiddenSeries` changes), then read per-frame by
|
|
311
|
+
* `computeVisibleValueExtent` to compute the auto-fit window over
|
|
312
|
+
* the visible cat range in O(visibleCats) instead of
|
|
313
|
+
* O(`bars.count`). Capacity reused across builds via
|
|
314
|
+
* length-checked grow.
|
|
315
|
+
*
|
|
316
|
+
* Memory: 4 × Float64 + 2 × Uint8 = 34 bytes per category. For
|
|
317
|
+
* typical N (≤ 1000 cats) this is < 35 KB; for high-cardinality
|
|
318
|
+
* N = 100k it's 3.4 MB. Acceptable trade for eliminating the
|
|
319
|
+
* O(N×M×P) per-frame walk during pan/zoom animations.
|
|
320
|
+
*/
|
|
321
|
+
_catExtents: {
|
|
322
|
+
leftMin: Float64Array;
|
|
323
|
+
leftMax: Float64Array;
|
|
324
|
+
rightMin: Float64Array;
|
|
325
|
+
rightMax: Float64Array;
|
|
326
|
+
hasLeft: Uint8Array;
|
|
327
|
+
hasRight: Uint8Array;
|
|
328
|
+
n: number;
|
|
329
|
+
} | null = null;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Identity of the `_hiddenSeries` set baked into `_catExtents`.
|
|
333
|
+
* Pointer-compares to detect legend-toggle invalidations.
|
|
334
|
+
*/
|
|
335
|
+
_catExtentsHidden: Set<number> | null = null;
|
|
336
|
+
|
|
337
|
+
protected override tooltipCallbacks() {
|
|
338
|
+
return {
|
|
339
|
+
onHover: (mx: number, my: number) => handleBarHover(this, mx, my),
|
|
340
|
+
onLeave: () => {
|
|
341
|
+
if (this._hoveredBarIdx !== -1 || this._hoveredSample) {
|
|
342
|
+
this._hoveredBarIdx = -1;
|
|
343
|
+
this._hoveredSample = null;
|
|
344
|
+
if (this._glManager) {
|
|
345
|
+
renderBarFrame(this, this._glManager);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
onClickPre: (mx: number, my: number) =>
|
|
350
|
+
handleBarLegendClick(this, mx, my),
|
|
351
|
+
onPin: (mx: number, my: number) => {
|
|
352
|
+
// Refresh the hit-test at the click coords directly:
|
|
353
|
+
// `dispatchHover` is RAF-throttled in the worker, so a
|
|
354
|
+
// click that follows a mousemove in the same task may
|
|
355
|
+
// arrive at `onPin` before the prior hover RAF has
|
|
356
|
+
// updated `_hoveredBarIdx`. Re-running the hit-test
|
|
357
|
+
// here makes the pin path independent of hover timing.
|
|
358
|
+
handleBarHover(this, mx, my);
|
|
359
|
+
if (this._hoveredBarIdx >= 0) {
|
|
360
|
+
const barIdx = this._hoveredBarIdx;
|
|
361
|
+
showBarPinnedTooltip(this, barIdx);
|
|
362
|
+
const rec = readBarRecord(
|
|
363
|
+
this._bars,
|
|
364
|
+
barIdx,
|
|
365
|
+
this._splitPrefixes.length,
|
|
366
|
+
this._samples,
|
|
367
|
+
this._series.length,
|
|
368
|
+
);
|
|
369
|
+
void this._emitSeriesClickSelect(rec);
|
|
370
|
+
} else if (this._hoveredSample) {
|
|
371
|
+
const rec = this._hoveredSample;
|
|
372
|
+
showBarPinnedTooltipForSample(this, rec);
|
|
373
|
+
void this._emitSeriesClickSelect(rec);
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
onUnpin: () => {
|
|
377
|
+
this.emitUnselect();
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Resolve a clicked bar / point into a `PerspectiveClickDetail`
|
|
384
|
+
* (via `buildClickDetail`) and emit both
|
|
385
|
+
* `perspective-click` and `perspective-global-filter` to the host.
|
|
386
|
+
*
|
|
387
|
+
* `rowIdx` derivation: the series pipeline emits one record per
|
|
388
|
+
* (catIdx, agg, split) tuple, and a pivoted view has one view row
|
|
389
|
+
* per category — so `catIdx + _rowOffset` is the source-view row.
|
|
390
|
+
* `_aggregates[aggIdx]` is the *base* column name (no split
|
|
391
|
+
* prefix). Group-by values come from per-level `_rowPaths`, split-by
|
|
392
|
+
* values are recovered by splitting `_splitPrefixes[splitIdx]` on
|
|
393
|
+
* the `|` delimiter the engine uses for pivoted column names.
|
|
394
|
+
*/
|
|
395
|
+
private async _emitSeriesClickSelect(b: SeriesChartRecord): Promise<void> {
|
|
396
|
+
if (!this._aggregates[b.aggIdx]) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const groupByValues: (string | null)[] = this._rowPaths.map(
|
|
401
|
+
(level) => level.labels[b.catIdx] ?? null,
|
|
402
|
+
);
|
|
403
|
+
const splitKey = this._splitPrefixes[b.splitIdx] ?? "";
|
|
404
|
+
const splitByValues =
|
|
405
|
+
this._splitBy.length > 0 && splitKey !== ""
|
|
406
|
+
? splitKey.split("|")
|
|
407
|
+
: [];
|
|
408
|
+
|
|
409
|
+
await this.emitClickAndSelect({
|
|
410
|
+
rowIdx: b.catIdx + this._rowOffset,
|
|
411
|
+
columnName: this._aggregates[b.aggIdx],
|
|
412
|
+
groupByValues,
|
|
413
|
+
splitByValues,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async uploadAndRender(
|
|
418
|
+
glManager: WebGLContextManager,
|
|
419
|
+
columns: ColumnDataMap,
|
|
420
|
+
startRow: number,
|
|
421
|
+
endRow: number,
|
|
422
|
+
): Promise<void> {
|
|
423
|
+
this._glManager = glManager;
|
|
424
|
+
const gl = glManager.gl;
|
|
425
|
+
|
|
426
|
+
if (startRow !== 0) {
|
|
427
|
+
// Bar charts render a single consolidated pass — the viewer
|
|
428
|
+
// should not chunk this, but guard defensively.
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!this._program) {
|
|
433
|
+
this._program = glManager.shaders.getOrCreate(
|
|
434
|
+
"bar",
|
|
435
|
+
barVert,
|
|
436
|
+
barFrag,
|
|
437
|
+
);
|
|
438
|
+
const p = this._program;
|
|
439
|
+
this._locations = {
|
|
440
|
+
u_proj_left: gl.getUniformLocation(p, "u_proj_left"),
|
|
441
|
+
u_proj_right: gl.getUniformLocation(p, "u_proj_right"),
|
|
442
|
+
u_hover_series: gl.getUniformLocation(p, "u_hover_series"),
|
|
443
|
+
u_horizontal: gl.getUniformLocation(p, "u_horizontal"),
|
|
444
|
+
a_corner: gl.getAttribLocation(p, "a_corner"),
|
|
445
|
+
a_x_center: gl.getAttribLocation(p, "a_x_center"),
|
|
446
|
+
a_half_width: gl.getAttribLocation(p, "a_half_width"),
|
|
447
|
+
a_y0: gl.getAttribLocation(p, "a_y0"),
|
|
448
|
+
a_y1: gl.getAttribLocation(p, "a_y1"),
|
|
449
|
+
a_color: gl.getAttribLocation(p, "a_color"),
|
|
450
|
+
a_series_id: gl.getAttribLocation(p, "a_series_id"),
|
|
451
|
+
a_axis: gl.getAttribLocation(p, "a_axis"),
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
this._cornerBuffer = gl.createBuffer()!;
|
|
455
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this._cornerBuffer);
|
|
456
|
+
gl.bufferData(
|
|
457
|
+
gl.ARRAY_BUFFER,
|
|
458
|
+
new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
|
459
|
+
gl.STATIC_DRAW,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const result = buildSeriesPipeline({
|
|
464
|
+
columns,
|
|
465
|
+
numRows: endRow,
|
|
466
|
+
columnSlots: this._columnSlots,
|
|
467
|
+
groupBy: this._groupBy,
|
|
468
|
+
splitBy: this._splitBy,
|
|
469
|
+
groupByTypes: this._groupByTypes,
|
|
470
|
+
columnsConfig: this._columnsConfig,
|
|
471
|
+
defaultChartType: this._defaultChartType as
|
|
472
|
+
| "bar"
|
|
473
|
+
| "line"
|
|
474
|
+
| "scatter"
|
|
475
|
+
| "area"
|
|
476
|
+
| undefined,
|
|
477
|
+
autoAltYAxis: this._pluginConfig.auto_alt_y_axis,
|
|
478
|
+
bandInnerFrac: this._pluginConfig.band_inner_frac,
|
|
479
|
+
barInnerPad: this._pluginConfig.bar_inner_pad,
|
|
480
|
+
includeZero: this._pluginConfig.include_zero,
|
|
481
|
+
scratchBars: this._bars,
|
|
482
|
+
scratchPosStack: this._posStackScratch,
|
|
483
|
+
scratchNegStack: this._negStackScratch,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// `domain_mode: "expand"` post-build union. Mutate the pipeline
|
|
487
|
+
// result struct in place so every downstream assignment below
|
|
488
|
+
// (`_leftDomain`, `_rightDomain`, `_numericCategoryDomain`,
|
|
489
|
+
// `_categoryOrigin`) automatically picks up the grown extent.
|
|
490
|
+
// `"fit"` (or a fresh reset) leaves the result untouched and
|
|
491
|
+
// clears the accumulators so the next toggle starts fresh.
|
|
492
|
+
if (this._pluginConfig.domain_mode === "expand") {
|
|
493
|
+
if (this._expandedLeftDomain) {
|
|
494
|
+
result.leftDomain.min = Math.min(
|
|
495
|
+
this._expandedLeftDomain.min,
|
|
496
|
+
result.leftDomain.min,
|
|
497
|
+
);
|
|
498
|
+
result.leftDomain.max = Math.max(
|
|
499
|
+
this._expandedLeftDomain.max,
|
|
500
|
+
result.leftDomain.max,
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
this._expandedLeftDomain = { ...result.leftDomain };
|
|
505
|
+
|
|
506
|
+
if (result.rightDomain) {
|
|
507
|
+
if (this._expandedRightDomain) {
|
|
508
|
+
result.rightDomain.min = Math.min(
|
|
509
|
+
this._expandedRightDomain.min,
|
|
510
|
+
result.rightDomain.min,
|
|
511
|
+
);
|
|
512
|
+
result.rightDomain.max = Math.max(
|
|
513
|
+
this._expandedRightDomain.max,
|
|
514
|
+
result.rightDomain.max,
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
this._expandedRightDomain = { ...result.rightDomain };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (result.numericCategoryDomain) {
|
|
522
|
+
if (this._expandedCategoryDomain) {
|
|
523
|
+
result.numericCategoryDomain.min = Math.min(
|
|
524
|
+
this._expandedCategoryDomain.min,
|
|
525
|
+
result.numericCategoryDomain.min,
|
|
526
|
+
);
|
|
527
|
+
result.numericCategoryDomain.max = Math.max(
|
|
528
|
+
this._expandedCategoryDomain.max,
|
|
529
|
+
result.numericCategoryDomain.max,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
this._expandedCategoryDomain = {
|
|
534
|
+
min: result.numericCategoryDomain.min,
|
|
535
|
+
max: result.numericCategoryDomain.max,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
this._expandedLeftDomain = null;
|
|
540
|
+
this._expandedRightDomain = null;
|
|
541
|
+
this._expandedCategoryDomain = null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
this._aggregates = result.aggregates;
|
|
545
|
+
this._splitPrefixes = result.splitPrefixes;
|
|
546
|
+
this._rowPaths = result.rowPaths;
|
|
547
|
+
this._numCategories = result.numCategories;
|
|
548
|
+
this._rowOffset = result.rowOffset;
|
|
549
|
+
this._categoryAxisMode = result.axisMode.mode;
|
|
550
|
+
this._numericCategoryDomain = result.numericCategoryDomain;
|
|
551
|
+
this._categoryPositions = result.categoryPositions;
|
|
552
|
+
|
|
553
|
+
// Rebase origin for the category axis. Pin to the domain min so
|
|
554
|
+
// every bar/sample can be uploaded as `(xCenter - origin)` and
|
|
555
|
+
// the f32 GPU pipeline never sees the full ~1.7e12 timestamp.
|
|
556
|
+
// Non-numeric modes (categorical, no domain) leave origin at 0.
|
|
557
|
+
this._categoryOrigin = result.numericCategoryDomain?.min ?? 0;
|
|
558
|
+
this._series = result.series;
|
|
559
|
+
this._bars = result.bars;
|
|
560
|
+
this._posStackScratch = result.posStack;
|
|
561
|
+
this._negStackScratch = result.negStack;
|
|
562
|
+
this._samples = result.samples;
|
|
563
|
+
|
|
564
|
+
// Pre-partition `_series` by glyph type once per build. Frame
|
|
565
|
+
// paths read these directly instead of `_series.filter(...)`.
|
|
566
|
+
// Single bucket-push pass over the source array — replaces
|
|
567
|
+
// four `Array.filter` allocations with in-place `length = 0`
|
|
568
|
+
// resets on the chart-owned arrays. Same total memory in
|
|
569
|
+
// steady state, but skips three array-header allocations and
|
|
570
|
+
// one redundant pass over `result.series` per data load.
|
|
571
|
+
this._barSeries.length = 0;
|
|
572
|
+
this._lineSeries.length = 0;
|
|
573
|
+
this._scatterSeries.length = 0;
|
|
574
|
+
this._areaSeries.length = 0;
|
|
575
|
+
for (const s of result.series) {
|
|
576
|
+
switch (s.chartType) {
|
|
577
|
+
case "bar":
|
|
578
|
+
this._barSeries.push(s);
|
|
579
|
+
break;
|
|
580
|
+
case "line":
|
|
581
|
+
this._lineSeries.push(s);
|
|
582
|
+
break;
|
|
583
|
+
case "scatter":
|
|
584
|
+
this._scatterSeries.push(s);
|
|
585
|
+
break;
|
|
586
|
+
case "area":
|
|
587
|
+
this._areaSeries.push(s);
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Cache the per-axis label string. Recomputing the dedupe-and-
|
|
593
|
+
// join per frame allocated four arrays + a string, all stable
|
|
594
|
+
// between data loads.
|
|
595
|
+
this._primaryValueLabel = uniqueAggLabels(result.series, 0);
|
|
596
|
+
this._altValueLabel = uniqueAggLabels(result.series, 1);
|
|
597
|
+
|
|
598
|
+
// Pre-build the area-strip lookup index (seriesId * 1e9 + catIdx
|
|
599
|
+
// → bar slot). Legacy code rebuilt this every frame inside
|
|
600
|
+
// `drawAreas`. The index is derived purely from `_bars` and is
|
|
601
|
+
// valid for the lifetime of this build.
|
|
602
|
+
this._areaBarIndex = buildAreaBarIndex(this._bars);
|
|
603
|
+
|
|
604
|
+
// New bar records invalidate downstream caches — auto-fit extent,
|
|
605
|
+
// legend layout (text widths can shift on series-set change),
|
|
606
|
+
// palette + color upload (palette length changes), and persistent
|
|
607
|
+
// glyph buffers (vertex data is rebuilt below). Also drop the
|
|
608
|
+
// per-category extent identity so the bucket rebuilds on
|
|
609
|
+
// next read.
|
|
610
|
+
this._autoFitCache = null;
|
|
611
|
+
this._legendCacheValid = false;
|
|
612
|
+
this._paletteCache = null;
|
|
613
|
+
this._paletteCacheKey = null;
|
|
614
|
+
this._catExtentsHidden = null;
|
|
615
|
+
this._lastUploadedColors = null;
|
|
616
|
+
this._sampleValid = result.sampleValid;
|
|
617
|
+
this._leftDomain = result.leftDomain;
|
|
618
|
+
this._rightDomain = result.rightDomain;
|
|
619
|
+
this._hasRightAxis = result.hasRightAxis;
|
|
620
|
+
|
|
621
|
+
// Resolve the palette eagerly. Both `uploadBarInstances` (color
|
|
622
|
+
// attribute) and `rebuildGlyphBuffers` (per-series RGB capture)
|
|
623
|
+
// read `_series[i].color`, so the stamp has to happen first.
|
|
624
|
+
ensurePalette(this);
|
|
625
|
+
|
|
626
|
+
uploadBarInstances(this, glManager);
|
|
627
|
+
|
|
628
|
+
invalidateGlyphBuffers(this);
|
|
629
|
+
rebuildGlyphBuffers(this, glManager);
|
|
630
|
+
|
|
631
|
+
await this.requestRender(glManager);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
_fullRender(glManager: WebGLContextManager): void {
|
|
635
|
+
if (!this._program) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
this._glManager = glManager;
|
|
640
|
+
renderBarFrame(this, glManager);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
override resetExpandedDomain(): void {
|
|
644
|
+
this._expandedLeftDomain = null;
|
|
645
|
+
this._expandedRightDomain = null;
|
|
646
|
+
this._expandedCategoryDomain = null;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
protected destroyInternal(): void {
|
|
650
|
+
if (this._glManager) {
|
|
651
|
+
const gl = this._glManager.gl;
|
|
652
|
+
if (this._cornerBuffer) {
|
|
653
|
+
gl.deleteBuffer(this._cornerBuffer);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
destroyGlyphBuffers(this);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
this._program = null;
|
|
660
|
+
this._locations = null;
|
|
661
|
+
this._cornerBuffer = null;
|
|
662
|
+
this._bars = emptyBarColumns();
|
|
663
|
+
this._series = [];
|
|
664
|
+
this._barSeries = [];
|
|
665
|
+
this._lineSeries = [];
|
|
666
|
+
this._scatterSeries = [];
|
|
667
|
+
this._areaSeries = [];
|
|
668
|
+
this._areaBarIndex = null;
|
|
669
|
+
this._paletteCache = null;
|
|
670
|
+
this._paletteCacheKey = null;
|
|
671
|
+
this._lastUploadedColors = null;
|
|
672
|
+
this._posStackScratch = null;
|
|
673
|
+
this._negStackScratch = null;
|
|
674
|
+
this._rowPaths = [];
|
|
675
|
+
this._numCategories = 0;
|
|
676
|
+
this._hiddenSeries.clear();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Build the `(seriesId * 1e9 + catIdx) → bar-record-index` lookup for
|
|
682
|
+
* area glyphs. Areas read y0/y1 by (seriesId, catIdx) on every strip;
|
|
683
|
+
* legacy code rebuilt this map per frame from the bars list. Invariant:
|
|
684
|
+
* 1e9 is safe since category counts never approach it.
|
|
685
|
+
*/
|
|
686
|
+
function buildAreaBarIndex(bars: BarColumns): Map<number, number> {
|
|
687
|
+
const m = new Map<number, number>();
|
|
688
|
+
for (let i = 0; i < bars.count; i++) {
|
|
689
|
+
if (bars.chartType[i] !== 1 /* AREA */) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
m.set(bars.seriesId[i] * 1_000_000_000 + bars.catIdx[i], i);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return m;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Dedupe + join the aggregate names for series on a given axis. Stable
|
|
701
|
+
* across pan/zoom — caches on the chart so the legacy O(S²) `indexOf`-
|
|
702
|
+
* based dedupe doesn't run per frame.
|
|
703
|
+
*/
|
|
704
|
+
function uniqueAggLabels(series: SeriesInfo[], axis: 0 | 1): string {
|
|
705
|
+
const seen = new Set<string>();
|
|
706
|
+
const ordered: string[] = [];
|
|
707
|
+
for (const s of series) {
|
|
708
|
+
if (s.axis !== axis) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (seen.has(s.aggName)) {
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
seen.add(s.aggName);
|
|
717
|
+
ordered.push(s.aggName);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return ordered.join(", ");
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Resolve the per-series palette and stamp it onto `_series[i].color`.
|
|
725
|
+
* Cached on `_paletteCache` keyed by reference identity of the theme
|
|
726
|
+
* inputs + series count — only `restyle()` (which clears `_paletteCache`
|
|
727
|
+
* via `invalidateTheme`) or a data load (which clears it explicitly)
|
|
728
|
+
* forces re-resolution.
|
|
729
|
+
*
|
|
730
|
+
* Returns true when the cache changed (caller invalidates color upload).
|
|
731
|
+
*/
|
|
732
|
+
export function ensurePalette(chart: SeriesChart): boolean {
|
|
733
|
+
const theme = chart._resolveTheme();
|
|
734
|
+
const seriesPalette = theme.seriesPalette;
|
|
735
|
+
const gradientStops = theme.gradientStops;
|
|
736
|
+
const seriesLength = chart._series.length;
|
|
737
|
+
|
|
738
|
+
const key = chart._paletteCacheKey;
|
|
739
|
+
if (
|
|
740
|
+
chart._paletteCache &&
|
|
741
|
+
key &&
|
|
742
|
+
key.seriesPalette === seriesPalette &&
|
|
743
|
+
key.gradientStops === gradientStops &&
|
|
744
|
+
key.seriesLength === seriesLength
|
|
745
|
+
) {
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const palette = resolvePaletteCached(
|
|
750
|
+
seriesPalette,
|
|
751
|
+
gradientStops,
|
|
752
|
+
seriesLength,
|
|
753
|
+
);
|
|
754
|
+
chart._paletteCache = palette;
|
|
755
|
+
chart._paletteCacheKey = { seriesPalette, gradientStops, seriesLength };
|
|
756
|
+
|
|
757
|
+
for (let i = 0; i < chart._series.length; i++) {
|
|
758
|
+
chart._series[i].color = palette[i];
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Module-local indirection so `series.ts` can call into the palette
|
|
766
|
+
* resolver without pulling the entire `series-render.ts` import graph
|
|
767
|
+
* into its file scope. Re-exported through `series-render.ts`.
|
|
768
|
+
*/
|
|
769
|
+
function resolvePaletteCached(
|
|
770
|
+
seriesPalette: [number, number, number][],
|
|
771
|
+
gradientStops: import("../../theme/gradient").GradientStop[],
|
|
772
|
+
seriesLength: number,
|
|
773
|
+
): [number, number, number][] {
|
|
774
|
+
return resolvePalette(seriesPalette, gradientStops, seriesLength);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Tear down per-glyph GPU resources. Each glyph instance owns its own
|
|
779
|
+
* program cache + persistent buffers and frees both in `destroy`.
|
|
780
|
+
*/
|
|
781
|
+
function destroyGlyphBuffers(chart: SeriesChart): void {
|
|
782
|
+
chart._glyphs.lines.destroy(chart);
|
|
783
|
+
chart._glyphs.scatter.destroy(chart);
|
|
784
|
+
chart._glyphs.areas.destroy(chart);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Horizontal bar chart — numeric X, categorical Y.
|
|
789
|
+
*/
|
|
790
|
+
export class XBarChart extends SeriesChart {
|
|
791
|
+
constructor() {
|
|
792
|
+
super("horizontal");
|
|
793
|
+
}
|
|
794
|
+
}
|