@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,1109 @@
|
|
|
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 { Context2D } from "../canvas-types";
|
|
14
|
+
import type { WebGLContextManager } from "../../webgl/context-manager";
|
|
15
|
+
import {
|
|
16
|
+
ensurePalette,
|
|
17
|
+
type SeriesChart,
|
|
18
|
+
type SeriesAutoFitCache,
|
|
19
|
+
} from "./series";
|
|
20
|
+
import type { PlotRect } from "../../layout/plot-layout";
|
|
21
|
+
import { PlotLayout } from "../../layout/plot-layout";
|
|
22
|
+
import { renderInPlotFrame } from "../../webgl/plot-frame";
|
|
23
|
+
import { renderCanvasTooltip } from "../../interaction/tooltip-controller";
|
|
24
|
+
import { drawBars, BAR_TYPE_BAR_VAL as BAR_TYPE_BAR } from "./glyphs/draw-bars";
|
|
25
|
+
import { getHoveredBar } from "./series-interact";
|
|
26
|
+
import { computeNiceTicks } from "../../layout/ticks";
|
|
27
|
+
import { type AxisDomain } from "../../axis/numeric-axis";
|
|
28
|
+
import {
|
|
29
|
+
renderBarAxesChrome,
|
|
30
|
+
renderBarGridlines,
|
|
31
|
+
type BarCategoryAxis,
|
|
32
|
+
} from "../../axis/bar-axis";
|
|
33
|
+
import {
|
|
34
|
+
measureCategoricalAxisHeight,
|
|
35
|
+
measureCategoricalAxisWidth,
|
|
36
|
+
type CategoricalDomain,
|
|
37
|
+
} from "../../axis/categorical-axis";
|
|
38
|
+
import { buildBarTooltipLines } from "./series-interact";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reusable scratch for bar instance uploads. Sized lazily at the first
|
|
42
|
+
* use; grown on demand. Avoids `new Float32Array(n)` × 7 buffers per
|
|
43
|
+
* legend-toggle / data-load; size is bounded by the bar-typed subset
|
|
44
|
+
* of `_bars.count`.
|
|
45
|
+
*/
|
|
46
|
+
interface BarInstanceScratch {
|
|
47
|
+
xCenters: Float32Array;
|
|
48
|
+
halfWidths: Float32Array;
|
|
49
|
+
y0s: Float32Array;
|
|
50
|
+
y1s: Float32Array;
|
|
51
|
+
seriesIds: Float32Array;
|
|
52
|
+
axes: Float32Array;
|
|
53
|
+
colors: Float32Array;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let _barInstanceScratch: BarInstanceScratch | null = null;
|
|
57
|
+
|
|
58
|
+
function ensureBarInstanceScratch(n: number): BarInstanceScratch {
|
|
59
|
+
if (
|
|
60
|
+
_barInstanceScratch &&
|
|
61
|
+
_barInstanceScratch.xCenters.length >= n &&
|
|
62
|
+
_barInstanceScratch.colors.length >= n * 3
|
|
63
|
+
) {
|
|
64
|
+
return _barInstanceScratch;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cap = Math.max(n, _barInstanceScratch?.xCenters.length ?? 0);
|
|
68
|
+
_barInstanceScratch = {
|
|
69
|
+
xCenters: new Float32Array(cap),
|
|
70
|
+
halfWidths: new Float32Array(cap),
|
|
71
|
+
y0s: new Float32Array(cap),
|
|
72
|
+
y1s: new Float32Array(cap),
|
|
73
|
+
seriesIds: new Float32Array(cap),
|
|
74
|
+
axes: new Float32Array(cap),
|
|
75
|
+
colors: new Float32Array(cap * 3),
|
|
76
|
+
};
|
|
77
|
+
return _barInstanceScratch;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Upload bar instance buffers from the columnar `_bars` storage. Filters
|
|
82
|
+
* to bar-typed records only (areas draw as triangle strips). Skips
|
|
83
|
+
* hidden series. Re-called from data-load and legend-toggle paths; the
|
|
84
|
+
* scratch buffers and `_visibleBarIndices` are reused across calls.
|
|
85
|
+
*/
|
|
86
|
+
export function uploadBarInstances(
|
|
87
|
+
chart: SeriesChart,
|
|
88
|
+
glManager: WebGLContextManager,
|
|
89
|
+
): void {
|
|
90
|
+
const bars = chart._bars;
|
|
91
|
+
const total = bars.count;
|
|
92
|
+
let n = 0;
|
|
93
|
+
|
|
94
|
+
if (total > 0) {
|
|
95
|
+
const scratch = ensureBarInstanceScratch(total);
|
|
96
|
+
if (
|
|
97
|
+
!chart._visibleBarIndices ||
|
|
98
|
+
chart._visibleBarIndices.length < total
|
|
99
|
+
) {
|
|
100
|
+
chart._visibleBarIndices = new Int32Array(total);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const indices = chart._visibleBarIndices;
|
|
104
|
+
|
|
105
|
+
// Rebase each xCenter by `_categoryOrigin` before f32 narrowing.
|
|
106
|
+
// For datetime numeric category axes the absolute xCenter is
|
|
107
|
+
// ~1.7e12 and f32 narrowing collapses adjacent bars onto the
|
|
108
|
+
// same value; subtracting the origin brings every value into
|
|
109
|
+
// the seconds range where f32 has full precision. The matching
|
|
110
|
+
// projection matrix is built with the same origin so the shader
|
|
111
|
+
// math is consistent.
|
|
112
|
+
const xOrigin = chart._categoryOrigin;
|
|
113
|
+
const series = chart._series;
|
|
114
|
+
const hidden = chart._hiddenSeries;
|
|
115
|
+
const ct = bars.chartType;
|
|
116
|
+
const sid = bars.seriesId;
|
|
117
|
+
const xC = bars.xCenter;
|
|
118
|
+
const hw = bars.halfWidth;
|
|
119
|
+
const by0 = bars.y0;
|
|
120
|
+
const by1 = bars.y1;
|
|
121
|
+
const ax = bars.axis;
|
|
122
|
+
for (let i = 0; i < total; i++) {
|
|
123
|
+
if (ct[i] !== BAR_TYPE_BAR) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const seriesId = sid[i];
|
|
128
|
+
if (hidden.has(seriesId)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
scratch.xCenters[n] = xC[i] - xOrigin;
|
|
133
|
+
scratch.halfWidths[n] = hw[i];
|
|
134
|
+
scratch.y0s[n] = by0[i];
|
|
135
|
+
scratch.y1s[n] = by1[i];
|
|
136
|
+
scratch.seriesIds[n] = seriesId;
|
|
137
|
+
scratch.axes[n] = ax[i];
|
|
138
|
+
const color = series[seriesId].color;
|
|
139
|
+
scratch.colors[n * 3] = color[0];
|
|
140
|
+
scratch.colors[n * 3 + 1] = color[1];
|
|
141
|
+
scratch.colors[n * 3 + 2] = color[2];
|
|
142
|
+
indices[n] = i;
|
|
143
|
+
n++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
chart._uploadedBars = n;
|
|
148
|
+
if (n === 0) {
|
|
149
|
+
chart._lastUploadedColors = null;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const scratch = _barInstanceScratch!;
|
|
154
|
+
glManager.bufferPool.ensureCapacity(n);
|
|
155
|
+
// `subarray(0, n)` slices the scratch to the current frame's
|
|
156
|
+
// valid-data length. The scratch grows monotonically across
|
|
157
|
+
// frames (see `ensureBarInstanceScratch`) so its `.length` reflects
|
|
158
|
+
// historical peak, not current `n` — passing it whole would
|
|
159
|
+
// overflow the GPU buffer after any session reset.
|
|
160
|
+
glManager.bufferPool.upload("bar_x", scratch.xCenters.subarray(0, n), 0, 1);
|
|
161
|
+
glManager.bufferPool.upload(
|
|
162
|
+
"bar_hw",
|
|
163
|
+
scratch.halfWidths.subarray(0, n),
|
|
164
|
+
0,
|
|
165
|
+
1,
|
|
166
|
+
);
|
|
167
|
+
glManager.bufferPool.upload("bar_y0", scratch.y0s.subarray(0, n), 0, 1);
|
|
168
|
+
glManager.bufferPool.upload("bar_y1", scratch.y1s.subarray(0, n), 0, 1);
|
|
169
|
+
glManager.bufferPool.upload(
|
|
170
|
+
"bar_sid",
|
|
171
|
+
scratch.seriesIds.subarray(0, n),
|
|
172
|
+
0,
|
|
173
|
+
1,
|
|
174
|
+
);
|
|
175
|
+
glManager.bufferPool.upload("bar_axis", scratch.axes.subarray(0, n), 0, 1);
|
|
176
|
+
glManager.bufferPool.upload(
|
|
177
|
+
"bar_color",
|
|
178
|
+
scratch.colors.subarray(0, n * 3),
|
|
179
|
+
0,
|
|
180
|
+
3,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Snapshot the uploaded color bytes so subsequent palette-only
|
|
184
|
+
// changes can detect a no-op and skip the GPU write.
|
|
185
|
+
if (
|
|
186
|
+
!chart._lastUploadedColors ||
|
|
187
|
+
chart._lastUploadedColors.length < n * 3
|
|
188
|
+
) {
|
|
189
|
+
chart._lastUploadedColors = new Float32Array(
|
|
190
|
+
Math.max(n * 3, chart._lastUploadedColors?.length ?? 0),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
chart._lastUploadedColors.set(scratch.colors.subarray(0, n * 3));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Re-upload the per-bar color attribute. Short-circuits when the new
|
|
199
|
+
* colors match the last-uploaded snapshot byte-for-byte. Legacy code
|
|
200
|
+
* ran this every frame regardless; with the cached palette now stable
|
|
201
|
+
* across pan/zoom this becomes a no-op except after data load /
|
|
202
|
+
* `restyle()`.
|
|
203
|
+
*/
|
|
204
|
+
export function uploadBarColors(
|
|
205
|
+
chart: SeriesChart,
|
|
206
|
+
glManager: WebGLContextManager,
|
|
207
|
+
): void {
|
|
208
|
+
const n = chart._uploadedBars;
|
|
209
|
+
if (n === 0) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const indices = chart._visibleBarIndices;
|
|
214
|
+
const series = chart._series;
|
|
215
|
+
const sid = chart._bars.seriesId;
|
|
216
|
+
const scratch = ensureBarInstanceScratch(n);
|
|
217
|
+
for (let i = 0; i < n; i++) {
|
|
218
|
+
const color = series[sid[indices[i]]].color;
|
|
219
|
+
scratch.colors[i * 3] = color[0];
|
|
220
|
+
scratch.colors[i * 3 + 1] = color[1];
|
|
221
|
+
scratch.colors[i * 3 + 2] = color[2];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const last = chart._lastUploadedColors;
|
|
225
|
+
if (last && last.length >= n * 3) {
|
|
226
|
+
let same = true;
|
|
227
|
+
for (let i = 0; i < n * 3; i++) {
|
|
228
|
+
if (last[i] !== scratch.colors[i]) {
|
|
229
|
+
same = false;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (same) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
glManager.bufferPool.upload(
|
|
240
|
+
"bar_color",
|
|
241
|
+
scratch.colors.subarray(0, n * 3),
|
|
242
|
+
0,
|
|
243
|
+
3,
|
|
244
|
+
);
|
|
245
|
+
if (!last || last.length < n * 3) {
|
|
246
|
+
chart._lastUploadedColors = new Float32Array(n * 3);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
chart._lastUploadedColors!.set(scratch.colors.subarray(0, n * 3));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Drop persistent vertex buffers for line / scatter / area glyphs.
|
|
254
|
+
* Called from `uploadAndRender` before {@link rebuildGlyphBuffers}.
|
|
255
|
+
*/
|
|
256
|
+
export function invalidateGlyphBuffers(chart: SeriesChart): void {
|
|
257
|
+
chart._glyphs.lines.invalidateBuffers(chart);
|
|
258
|
+
chart._glyphs.scatter.invalidateBuffers(chart);
|
|
259
|
+
chart._glyphs.areas.invalidateBuffers(chart);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Build persistent vertex buffers for line / scatter / area glyphs.
|
|
264
|
+
* The legacy renderers rebuilt and re-uploaded these every frame inside
|
|
265
|
+
* the per-glyph draw functions; with stable post-build geometry the
|
|
266
|
+
* uploads now happen exactly once per data-load / palette change.
|
|
267
|
+
*/
|
|
268
|
+
export function rebuildGlyphBuffers(
|
|
269
|
+
chart: SeriesChart,
|
|
270
|
+
glManager: WebGLContextManager,
|
|
271
|
+
): void {
|
|
272
|
+
chart._glyphs.lines.rebuildBuffers(chart, glManager);
|
|
273
|
+
chart._glyphs.scatter.rebuildBuffers(chart, glManager);
|
|
274
|
+
chart._glyphs.areas.rebuildBuffers(chart, glManager);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Full-frame render: gridlines → WebGL bars (instanced) → chrome overlay.
|
|
279
|
+
*/
|
|
280
|
+
export function renderBarFrame(
|
|
281
|
+
chart: SeriesChart,
|
|
282
|
+
glManager: WebGLContextManager,
|
|
283
|
+
): void {
|
|
284
|
+
const gl = glManager.gl;
|
|
285
|
+
const dpr = glManager.dpr;
|
|
286
|
+
const cssWidth = gl.canvas.width / dpr;
|
|
287
|
+
const cssHeight = gl.canvas.height / dpr;
|
|
288
|
+
if (cssWidth <= 0 || cssHeight <= 0) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (chart._numCategories === 0) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Resolve the theme + palette. `ensurePalette` is a no-op when the
|
|
297
|
+
// palette inputs (theme refs + series count) are unchanged — under
|
|
298
|
+
// pan/zoom this short-circuits, leaving frame work to the GPU draw
|
|
299
|
+
// calls only. After data load / `restyle()` it stamps fresh RGB
|
|
300
|
+
// onto `_series[i].color`, and the color upload path detects the
|
|
301
|
+
// change and re-uploads the bar instance colors.
|
|
302
|
+
const theme = chart._resolveTheme();
|
|
303
|
+
if (ensurePalette(chart) && chart._uploadedBars > 0) {
|
|
304
|
+
uploadBarColors(chart, glManager);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const horizontal = chart._isHorizontal;
|
|
308
|
+
const numericCat = chart._categoryAxisMode === "numeric";
|
|
309
|
+
|
|
310
|
+
// Category axis bounds. Category mode runs [-0.5, N-0.5] in logical
|
|
311
|
+
// units; numeric mode reads min/max from the data-unit
|
|
312
|
+
// `_numericCategoryDomain`. Horizontal mode flips the Y domain so
|
|
313
|
+
// catIdx=0 sits at the top (handled below in the projection call).
|
|
314
|
+
const catMin = numericCat ? chart._numericCategoryDomain!.min : -0.5;
|
|
315
|
+
const catMax = numericCat
|
|
316
|
+
? chart._numericCategoryDomain!.max
|
|
317
|
+
: chart._numCategories - 0.5;
|
|
318
|
+
|
|
319
|
+
const valMin = chart._leftDomain.min;
|
|
320
|
+
const valMax = chart._leftDomain.max;
|
|
321
|
+
if (chart._zoomController) {
|
|
322
|
+
if (horizontal) {
|
|
323
|
+
chart._zoomController.setBaseDomain(valMin, valMax, catMin, catMax);
|
|
324
|
+
} else {
|
|
325
|
+
chart._zoomController.setBaseDomain(catMin, catMax, valMin, valMax);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// `visCat*` and `visVal*` always describe the currently-visible window
|
|
330
|
+
// in logical (category/value) coords regardless of orientation.
|
|
331
|
+
let visCatMin = catMin;
|
|
332
|
+
let visCatMax = catMax;
|
|
333
|
+
let visValMin = valMin;
|
|
334
|
+
let visValMax = valMax;
|
|
335
|
+
let visRightMin = chart._rightDomain?.min ?? 0;
|
|
336
|
+
let visRightMax = chart._rightDomain?.max ?? 1;
|
|
337
|
+
if (chart._zoomController) {
|
|
338
|
+
const vd = chart._zoomController.getVisibleDomain();
|
|
339
|
+
if (horizontal) {
|
|
340
|
+
visValMin = vd.xMin;
|
|
341
|
+
visValMax = vd.xMax;
|
|
342
|
+
visCatMin = vd.yMin;
|
|
343
|
+
visCatMax = vd.yMax;
|
|
344
|
+
} else {
|
|
345
|
+
visCatMin = vd.xMin;
|
|
346
|
+
visCatMax = vd.xMax;
|
|
347
|
+
visValMin = vd.yMin;
|
|
348
|
+
visValMax = vd.yMax;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Auto-fit the value axis to the visible categorical window. Gated
|
|
353
|
+
// on `_autoFitValue` + non-default zoom: at default zoom the refit
|
|
354
|
+
// result always equals `_leftDomain`/`_rightDomain`, so walking
|
|
355
|
+
// would be wasted work (and would shift test baselines).
|
|
356
|
+
if (
|
|
357
|
+
chart._autoFitValue &&
|
|
358
|
+
chart._zoomController &&
|
|
359
|
+
!chart._zoomController.isDefault()
|
|
360
|
+
) {
|
|
361
|
+
const fit = computeVisibleValueExtent(chart, visCatMin, visCatMax);
|
|
362
|
+
if (fit.hasLeft) {
|
|
363
|
+
visValMin = fit.leftMin;
|
|
364
|
+
visValMax = fit.leftMax;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (chart._rightDomain && fit.hasRight) {
|
|
368
|
+
visRightMin = fit.rightMin;
|
|
369
|
+
visRightMax = fit.rightMax;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// `include_zero` is absolute — zero must stay inside the rendered
|
|
374
|
+
// domain even after a dynamic-zoom refit (`computeVisibleValueExtent`
|
|
375
|
+
// returns the data-only extent, which can drop the baseline).
|
|
376
|
+
// Without this, tick computation sees the refit window while the
|
|
377
|
+
// projection's `requireZero` snap silently re-anchors to zero, so
|
|
378
|
+
// ticks crowd one edge of an otherwise zero-anchored plot.
|
|
379
|
+
if (chart._pluginConfig.include_zero) {
|
|
380
|
+
if (visValMin > 0) {
|
|
381
|
+
visValMin = 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (visValMax < 0) {
|
|
385
|
+
visValMax = 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (chart._rightDomain) {
|
|
389
|
+
if (visRightMin > 0) {
|
|
390
|
+
visRightMin = 0;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (visRightMax < 0) {
|
|
394
|
+
visRightMax = 0;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const hasLegend = chart._series.length > 1;
|
|
400
|
+
const hasCatLabel = chart._groupBy.length > 0;
|
|
401
|
+
|
|
402
|
+
const provisionalDomain: CategoricalDomain = {
|
|
403
|
+
levels: chart._rowPaths,
|
|
404
|
+
numRows: chart._numCategories,
|
|
405
|
+
levelLabels: chart._groupBy.slice(),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
let layout: PlotLayout;
|
|
409
|
+
if (horizontal) {
|
|
410
|
+
// Numeric category axis on the Y side: the gutter just needs
|
|
411
|
+
// standard numeric tick width (~55px), no per-row label
|
|
412
|
+
// measurement.
|
|
413
|
+
const leftExtra = numericCat
|
|
414
|
+
? 55
|
|
415
|
+
: measureCategoricalAxisWidth(provisionalDomain);
|
|
416
|
+
|
|
417
|
+
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
418
|
+
hasXLabel: true,
|
|
419
|
+
hasYLabel: hasCatLabel,
|
|
420
|
+
hasLegend,
|
|
421
|
+
leftExtra,
|
|
422
|
+
});
|
|
423
|
+
} else if (numericCat) {
|
|
424
|
+
// Numeric category axis on the X side: bottom gutter is a
|
|
425
|
+
// fixed numeric-axis row (~24px), no leaf-rotation measurement.
|
|
426
|
+
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
427
|
+
hasXLabel: hasCatLabel,
|
|
428
|
+
hasYLabel: true,
|
|
429
|
+
hasLegend,
|
|
430
|
+
bottomExtra: 24,
|
|
431
|
+
});
|
|
432
|
+
} else {
|
|
433
|
+
const estLeft = 55 + 16;
|
|
434
|
+
const estRight = hasLegend ? 80 : 16;
|
|
435
|
+
const estPlotWidth = Math.max(1, cssWidth - estLeft - estRight);
|
|
436
|
+
const bottomExtra = measureCategoricalAxisHeight(
|
|
437
|
+
provisionalDomain,
|
|
438
|
+
estPlotWidth,
|
|
439
|
+
);
|
|
440
|
+
layout = new PlotLayout(cssWidth, cssHeight, {
|
|
441
|
+
hasXLabel: hasCatLabel,
|
|
442
|
+
hasYLabel: true,
|
|
443
|
+
hasLegend,
|
|
444
|
+
bottomExtra,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
chart._lastLayout = layout;
|
|
449
|
+
if (chart._zoomController) {
|
|
450
|
+
chart._zoomController.updateLayout(layout);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Build the primary projection. `clamp` names the axis that carries
|
|
454
|
+
// the *value* data (Y for Y Bar, X for X Bar). `requireZero` pins
|
|
455
|
+
// the baseline at zero so bar / area glyphs grow from the axis
|
|
456
|
+
// line; it must track `include_zero` so the projection's padded
|
|
457
|
+
// domain matches the build pipeline's `leftDomain` (otherwise the
|
|
458
|
+
// tick computation and the WebGL geometry use different scales).
|
|
459
|
+
const requireZero = chart._pluginConfig.include_zero;
|
|
460
|
+
const projLeft = horizontal
|
|
461
|
+
? layout.buildProjectionMatrix(
|
|
462
|
+
visValMin,
|
|
463
|
+
visValMax,
|
|
464
|
+
|
|
465
|
+
// Flip so catIdx=0 renders at the top.
|
|
466
|
+
visCatMax,
|
|
467
|
+
visCatMin,
|
|
468
|
+
"x",
|
|
469
|
+
requireZero,
|
|
470
|
+
undefined,
|
|
471
|
+
0,
|
|
472
|
+
chart._categoryOrigin,
|
|
473
|
+
)
|
|
474
|
+
: layout.buildProjectionMatrix(
|
|
475
|
+
visCatMin,
|
|
476
|
+
visCatMax,
|
|
477
|
+
visValMin,
|
|
478
|
+
visValMax,
|
|
479
|
+
"y",
|
|
480
|
+
requireZero,
|
|
481
|
+
undefined,
|
|
482
|
+
chart._categoryOrigin,
|
|
483
|
+
0,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
let projRight: Float32Array;
|
|
487
|
+
if (chart._hasRightAxis && chart._rightDomain && !horizontal) {
|
|
488
|
+
const savedPadXMin = layout.paddedXMin;
|
|
489
|
+
const savedPadXMax = layout.paddedXMax;
|
|
490
|
+
const savedPadYMin = layout.paddedYMin;
|
|
491
|
+
const savedPadYMax = layout.paddedYMax;
|
|
492
|
+
projRight = layout.buildProjectionMatrix(
|
|
493
|
+
visCatMin,
|
|
494
|
+
visCatMax,
|
|
495
|
+
visRightMin,
|
|
496
|
+
visRightMax,
|
|
497
|
+
"y",
|
|
498
|
+
requireZero,
|
|
499
|
+
undefined,
|
|
500
|
+
chart._categoryOrigin,
|
|
501
|
+
0,
|
|
502
|
+
);
|
|
503
|
+
layout.paddedXMin = savedPadXMin;
|
|
504
|
+
layout.paddedXMax = savedPadXMax;
|
|
505
|
+
layout.paddedYMin = savedPadYMin;
|
|
506
|
+
layout.paddedYMax = savedPadYMax;
|
|
507
|
+
} else {
|
|
508
|
+
// Dual-axis horizontal is not supported in this iteration; fall
|
|
509
|
+
// through to a single axis when horizontal + _hasRightAxis.
|
|
510
|
+
projRight = projLeft;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const leftValueTicks = computeNiceTicks(visValMin, visValMax, 6);
|
|
514
|
+
const rightValueTicks =
|
|
515
|
+
chart._hasRightAxis && chart._rightDomain && !horizontal
|
|
516
|
+
? computeNiceTicks(visRightMin, visRightMax, 6)
|
|
517
|
+
: null;
|
|
518
|
+
|
|
519
|
+
const catDomain: CategoricalDomain = provisionalDomain;
|
|
520
|
+
const valueDomain: AxisDomain = {
|
|
521
|
+
min: visValMin,
|
|
522
|
+
max: visValMax,
|
|
523
|
+
label: chart._primaryValueLabel,
|
|
524
|
+
};
|
|
525
|
+
const altValueDomain: AxisDomain | null =
|
|
526
|
+
chart._rightDomain && !horizontal
|
|
527
|
+
? {
|
|
528
|
+
min: visRightMin,
|
|
529
|
+
max: visRightMax,
|
|
530
|
+
label: chart._altValueLabel,
|
|
531
|
+
}
|
|
532
|
+
: null;
|
|
533
|
+
|
|
534
|
+
if (chart._gridlineCanvas) {
|
|
535
|
+
renderBarGridlines(
|
|
536
|
+
chart._gridlineCanvas,
|
|
537
|
+
layout,
|
|
538
|
+
leftValueTicks,
|
|
539
|
+
theme,
|
|
540
|
+
glManager.dpr,
|
|
541
|
+
horizontal,
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
renderInPlotFrame(gl, layout, glManager.dpr, () => {
|
|
546
|
+
// Paint order: areas behind bars (so bar borders stay crisp),
|
|
547
|
+
// bars above, lines above those, scatter points on top. X Bar
|
|
548
|
+
// only paints bars — the other glyphs bake in vertical geometry
|
|
549
|
+
// and aren't supported for horizontal orientation.
|
|
550
|
+
if (!horizontal) {
|
|
551
|
+
chart._glyphs.areas.draw(
|
|
552
|
+
chart,
|
|
553
|
+
gl,
|
|
554
|
+
glManager,
|
|
555
|
+
projLeft,
|
|
556
|
+
projRight,
|
|
557
|
+
theme.areaOpacity,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
gl.useProgram(chart._program!);
|
|
562
|
+
const loc = chart._locations!;
|
|
563
|
+
gl.uniformMatrix4fv(loc.u_proj_left, false, projLeft);
|
|
564
|
+
gl.uniformMatrix4fv(loc.u_proj_right, false, projRight);
|
|
565
|
+
gl.uniform1f(loc.u_horizontal, horizontal ? 1.0 : 0.0);
|
|
566
|
+
const hovered = chart._series.length > 1 ? getHoveredBar(chart) : null;
|
|
567
|
+
gl.uniform1f(loc.u_hover_series, hovered ? hovered.seriesId : -1);
|
|
568
|
+
drawBars(chart, gl, glManager);
|
|
569
|
+
|
|
570
|
+
if (!horizontal) {
|
|
571
|
+
chart._glyphs.lines.draw(chart, gl, glManager, projLeft, projRight);
|
|
572
|
+
chart._glyphs.scatter.draw(
|
|
573
|
+
chart,
|
|
574
|
+
gl,
|
|
575
|
+
glManager,
|
|
576
|
+
projLeft,
|
|
577
|
+
projRight,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
chart._lastXDomain = catDomain;
|
|
583
|
+
chart._lastYDomain = valueDomain;
|
|
584
|
+
chart._lastYTicks = leftValueTicks;
|
|
585
|
+
chart._lastAltYDomain = altValueDomain;
|
|
586
|
+
chart._lastAltYTicks = rightValueTicks;
|
|
587
|
+
chart._lastCatTicks = numericCat
|
|
588
|
+
? computeNiceTicks(visCatMin, visCatMax, 6)
|
|
589
|
+
: null;
|
|
590
|
+
renderBarChromeOverlay(chart);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Draw axes chrome + legend + tooltip onto the overlay canvas.
|
|
595
|
+
*/
|
|
596
|
+
export function renderBarChromeOverlay(chart: SeriesChart): void {
|
|
597
|
+
if (
|
|
598
|
+
!chart._chromeCanvas ||
|
|
599
|
+
!chart._lastLayout ||
|
|
600
|
+
!chart._lastYDomain ||
|
|
601
|
+
!chart._lastYTicks
|
|
602
|
+
) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const theme = chart._resolveTheme();
|
|
607
|
+
let catAxis: BarCategoryAxis;
|
|
608
|
+
if (
|
|
609
|
+
chart._categoryAxisMode === "numeric" &&
|
|
610
|
+
chart._numericCategoryDomain &&
|
|
611
|
+
chart._lastCatTicks
|
|
612
|
+
) {
|
|
613
|
+
catAxis = {
|
|
614
|
+
mode: "numeric",
|
|
615
|
+
domain: {
|
|
616
|
+
min: chart._numericCategoryDomain.min,
|
|
617
|
+
max: chart._numericCategoryDomain.max,
|
|
618
|
+
isDate: chart._numericCategoryDomain.isDate,
|
|
619
|
+
label: chart._numericCategoryDomain.label,
|
|
620
|
+
},
|
|
621
|
+
ticks: chart._lastCatTicks,
|
|
622
|
+
};
|
|
623
|
+
} else if (chart._lastXDomain) {
|
|
624
|
+
catAxis = { mode: "category", domain: chart._lastXDomain };
|
|
625
|
+
} else {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Y axis columns: the primary axis aggregates the unique Y column
|
|
630
|
+
// shared by all series on it. With `auto_alt_y_axis`, series can
|
|
631
|
+
// split across primary/secondary by `_series[i].onAltAxis`; the
|
|
632
|
+
// primary formatter follows the first non-alt series, alt follows
|
|
633
|
+
// the first alt series (falls back to the formatter's own type-
|
|
634
|
+
// aware fallback if no such series exists).
|
|
635
|
+
const primarySeries = chart._series.find((s) => s.axis === 0);
|
|
636
|
+
const altSeries = chart._series.find((s) => s.axis === 1);
|
|
637
|
+
const xColumn = chart._groupBy[0];
|
|
638
|
+
renderBarAxesChrome(
|
|
639
|
+
chart._chromeCanvas,
|
|
640
|
+
catAxis,
|
|
641
|
+
chart._lastYDomain,
|
|
642
|
+
chart._lastYTicks,
|
|
643
|
+
chart._lastLayout,
|
|
644
|
+
theme,
|
|
645
|
+
chart._glManager?.dpr ?? 1,
|
|
646
|
+
chart._lastAltYDomain ?? undefined,
|
|
647
|
+
chart._lastAltYTicks ?? undefined,
|
|
648
|
+
chart._isHorizontal,
|
|
649
|
+
{
|
|
650
|
+
value: chart.getColumnFormatter(
|
|
651
|
+
primarySeries?.aggName ?? null,
|
|
652
|
+
"tick",
|
|
653
|
+
),
|
|
654
|
+
alt: chart.getColumnFormatter(altSeries?.aggName ?? null, "tick"),
|
|
655
|
+
category: chart.getColumnFormatter(xColumn, "tick"),
|
|
656
|
+
},
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
renderBarLegend(chart);
|
|
660
|
+
|
|
661
|
+
if (getHoveredBar(chart)) {
|
|
662
|
+
renderBarTooltipCanvas(chart);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Cached parallel array of measured legend text widths. The legend
|
|
668
|
+
* renderer reads from this each frame instead of re-running
|
|
669
|
+
* `ctx.measureText` per series; the widths only change on series-set
|
|
670
|
+
* or theme change. `_legendCacheValid` gates rebuild.
|
|
671
|
+
*/
|
|
672
|
+
let _legendTextWidths: Float64Array = new Float64Array(0);
|
|
673
|
+
|
|
674
|
+
function ensureLegendLayout(
|
|
675
|
+
chart: SeriesChart,
|
|
676
|
+
ctx: Context2D,
|
|
677
|
+
fontFamily: string,
|
|
678
|
+
): void {
|
|
679
|
+
if (chart._legendCacheValid) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const series = chart._series;
|
|
684
|
+
if (_legendTextWidths.length < series.length) {
|
|
685
|
+
_legendTextWidths = new Float64Array(series.length);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
ctx.save();
|
|
689
|
+
ctx.font = `11px ${fontFamily}`;
|
|
690
|
+
for (let i = 0; i < series.length; i++) {
|
|
691
|
+
_legendTextWidths[i] = ctx.measureText(series[i].label).width;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
ctx.restore();
|
|
695
|
+
chart._legendCacheValid = true;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function renderBarLegend(chart: SeriesChart): void {
|
|
699
|
+
chart._legendRects = [];
|
|
700
|
+
if (!chart._chromeCanvas || !chart._lastLayout) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (chart._series.length <= 1) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const ctx = chart._chromeCanvas.getContext("2d") as Context2D | null;
|
|
709
|
+
if (!ctx) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
ctx.save();
|
|
714
|
+
|
|
715
|
+
const theme = chart._resolveTheme();
|
|
716
|
+
const textColor = theme.legendText;
|
|
717
|
+
const fontFamily = theme.fontFamily;
|
|
718
|
+
|
|
719
|
+
ensureLegendLayout(chart, ctx, fontFamily);
|
|
720
|
+
|
|
721
|
+
const layout = chart._lastLayout;
|
|
722
|
+
const swatchSize = 10;
|
|
723
|
+
const lineHeight = 18;
|
|
724
|
+
const x = layout.plotRect.x + layout.plotRect.width + 12;
|
|
725
|
+
let y = layout.margins.top + 10;
|
|
726
|
+
|
|
727
|
+
ctx.font = `11px ${fontFamily}`;
|
|
728
|
+
ctx.textAlign = "left";
|
|
729
|
+
ctx.textBaseline = "middle";
|
|
730
|
+
|
|
731
|
+
const series = chart._series;
|
|
732
|
+
const widths = _legendTextWidths;
|
|
733
|
+
for (let i = 0; i < series.length; i++) {
|
|
734
|
+
const s = series[i];
|
|
735
|
+
const hidden = chart._hiddenSeries.has(s.seriesId);
|
|
736
|
+
const r = Math.round(s.color[0] * 255);
|
|
737
|
+
const g = Math.round(s.color[1] * 255);
|
|
738
|
+
const b = Math.round(s.color[2] * 255);
|
|
739
|
+
|
|
740
|
+
ctx.globalAlpha = hidden ? 0.3 : 1.0;
|
|
741
|
+
ctx.fillStyle = `rgb(${r},${g},${b})`;
|
|
742
|
+
ctx.fillRect(x, y - swatchSize / 2, swatchSize, swatchSize);
|
|
743
|
+
|
|
744
|
+
ctx.fillStyle = textColor;
|
|
745
|
+
ctx.fillText(s.label, x + swatchSize + 6, y);
|
|
746
|
+
|
|
747
|
+
const textW = widths[i];
|
|
748
|
+
if (hidden) {
|
|
749
|
+
ctx.strokeStyle = textColor;
|
|
750
|
+
ctx.lineWidth = 1;
|
|
751
|
+
ctx.beginPath();
|
|
752
|
+
ctx.moveTo(x + swatchSize + 6, y);
|
|
753
|
+
ctx.lineTo(x + swatchSize + 6 + textW, y);
|
|
754
|
+
ctx.stroke();
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
ctx.globalAlpha = 1.0;
|
|
758
|
+
|
|
759
|
+
const rect: PlotRect = {
|
|
760
|
+
x: x - 2,
|
|
761
|
+
y: y - lineHeight / 2,
|
|
762
|
+
width: swatchSize + 6 + textW + 4,
|
|
763
|
+
height: lineHeight,
|
|
764
|
+
};
|
|
765
|
+
chart._legendRects.push({ seriesId: s.seriesId, rect });
|
|
766
|
+
|
|
767
|
+
y += lineHeight;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
ctx.restore();
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function renderBarTooltipCanvas(chart: SeriesChart): void {
|
|
774
|
+
if (!chart._chromeCanvas || !chart._lastLayout) {
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const b = getHoveredBar(chart);
|
|
779
|
+
if (!b) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const layout = chart._lastLayout;
|
|
784
|
+
|
|
785
|
+
// Bar glyphs anchor the tooltip at the midpoint of the bar body so
|
|
786
|
+
// it reads against a solid swatch. Line / scatter / area glyphs
|
|
787
|
+
// have no body — the data point sits at `y1`, so anchor there
|
|
788
|
+
// (the tooltip visually hovers *over* the point). Hit records
|
|
789
|
+
// synthesized from line/scatter hover tag themselves as "bar" in
|
|
790
|
+
// `_hoveredSample` for rendering purposes, so we pull the true
|
|
791
|
+
// glyph from the series info instead.
|
|
792
|
+
const glyph = chart._series[b.seriesId]?.chartType ?? "bar";
|
|
793
|
+
const anchorV = glyph === "bar" ? (b.y0 + b.y1) / 2 : b.y1;
|
|
794
|
+
|
|
795
|
+
const pos =
|
|
796
|
+
b.axis === 0
|
|
797
|
+
? chart._isHorizontal
|
|
798
|
+
? layout.dataToPixel(anchorV, b.xCenter)
|
|
799
|
+
: layout.dataToPixel(b.xCenter, anchorV)
|
|
800
|
+
: rightAxisDataToPixel(chart, b.xCenter, anchorV);
|
|
801
|
+
|
|
802
|
+
const lines = buildBarTooltipLines(chart, b);
|
|
803
|
+
const theme = chart._resolveTheme();
|
|
804
|
+
renderCanvasTooltip(
|
|
805
|
+
chart._chromeCanvas,
|
|
806
|
+
pos,
|
|
807
|
+
lines,
|
|
808
|
+
layout,
|
|
809
|
+
theme,
|
|
810
|
+
chart._glManager?.dpr ?? 1,
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
export function rightAxisDataToPixel(
|
|
815
|
+
chart: SeriesChart,
|
|
816
|
+
x: number,
|
|
817
|
+
y: number,
|
|
818
|
+
): { px: number; py: number } {
|
|
819
|
+
const layout = chart._lastLayout!;
|
|
820
|
+
const { x: px, y: py, width, height } = layout.plotRect;
|
|
821
|
+
const tx =
|
|
822
|
+
(x - layout.paddedXMin) / (layout.paddedXMax - layout.paddedXMin);
|
|
823
|
+
const r = chart._rightDomain!;
|
|
824
|
+
const ty = (y - r.min) / (r.max - r.min);
|
|
825
|
+
return { px: px + tx * width, py: py + (1 - ty) * height };
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Compute per-axis value extent over bars whose `catIdx` falls inside
|
|
830
|
+
* `[visCatMin, visCatMax]`. Skips hidden series. Returns a cached
|
|
831
|
+
* result on `chart._autoFitCache` when `(visCatMin, visCatMax,
|
|
832
|
+
* _hiddenSeries)` match the previous call — hover-only redraws hit
|
|
833
|
+
* the cache every time.
|
|
834
|
+
*
|
|
835
|
+
* Value source is `min(y0, y1)`/`max(y0, y1)` per bar, which handles
|
|
836
|
+
* stacked + negative-value bars uniformly.
|
|
837
|
+
*
|
|
838
|
+
* TODO(perf): O(|_bars|) linear scan. `_bars` is already ordered by
|
|
839
|
+
* `catIdx`, so a binary-search pair to locate the visible slice would
|
|
840
|
+
* drop this to O(log N + K_visible). Deferred — under current
|
|
841
|
+
* `max_cells` ceilings the scan is <1% of frame time.
|
|
842
|
+
*
|
|
843
|
+
* Cache lifetime: reset on data upload ([bar.ts] `uploadAndRender`)
|
|
844
|
+
* and legend toggle ([bar-interact.ts] `handleBarLegendClick`). Any
|
|
845
|
+
* other mutation that affects the bar set must also null the cache.
|
|
846
|
+
*/
|
|
847
|
+
function computeVisibleValueExtent(
|
|
848
|
+
chart: SeriesChart,
|
|
849
|
+
visCatMin: number,
|
|
850
|
+
visCatMax: number,
|
|
851
|
+
): {
|
|
852
|
+
leftMin: number;
|
|
853
|
+
leftMax: number;
|
|
854
|
+
hasLeft: boolean;
|
|
855
|
+
rightMin: number;
|
|
856
|
+
rightMax: number;
|
|
857
|
+
hasRight: boolean;
|
|
858
|
+
} {
|
|
859
|
+
const cache = chart._autoFitCache;
|
|
860
|
+
if (
|
|
861
|
+
cache &&
|
|
862
|
+
cache.catMin === visCatMin &&
|
|
863
|
+
cache.catMax === visCatMax &&
|
|
864
|
+
cache.hidden === chart._hiddenSeries
|
|
865
|
+
) {
|
|
866
|
+
return cache;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Pre-bucketed extent table — built once per data load (and on
|
|
870
|
+
// hidden-series mutation) — turns the per-frame walk from
|
|
871
|
+
// O(`bars.count` = N×M×P) into O(visibleCats). The original
|
|
872
|
+
// O(`bars.count`) walk now runs only inside `ensureCatExtents`.
|
|
873
|
+
const buckets = ensureCatExtents(chart);
|
|
874
|
+
|
|
875
|
+
let leftMin = Infinity;
|
|
876
|
+
let leftMax = -Infinity;
|
|
877
|
+
let hasLeft = false;
|
|
878
|
+
let rightMin = Infinity;
|
|
879
|
+
let rightMax = -Infinity;
|
|
880
|
+
let hasRight = false;
|
|
881
|
+
|
|
882
|
+
if (buckets.n > 0) {
|
|
883
|
+
// Clamp to the populated [0, n-1] range. `visCat*` is in
|
|
884
|
+
// continuous coords (numeric or category index space), so
|
|
885
|
+
// floor/ceil to integer bucket indices.
|
|
886
|
+
const lo = Math.max(0, Math.floor(visCatMin));
|
|
887
|
+
const hi = Math.min(buckets.n - 1, Math.ceil(visCatMax));
|
|
888
|
+
const lMin = buckets.leftMin;
|
|
889
|
+
const lMax = buckets.leftMax;
|
|
890
|
+
const rMin = buckets.rightMin;
|
|
891
|
+
const rMax = buckets.rightMax;
|
|
892
|
+
const hL = buckets.hasLeft;
|
|
893
|
+
const hR = buckets.hasRight;
|
|
894
|
+
for (let i = lo; i <= hi; i++) {
|
|
895
|
+
if (hL[i]) {
|
|
896
|
+
if (lMin[i] < leftMin) {
|
|
897
|
+
leftMin = lMin[i];
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (lMax[i] > leftMax) {
|
|
901
|
+
leftMax = lMax[i];
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
hasLeft = true;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (hR[i]) {
|
|
908
|
+
if (rMin[i] < rightMin) {
|
|
909
|
+
rightMin = rMin[i];
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (rMax[i] > rightMax) {
|
|
913
|
+
rightMax = rMax[i];
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
hasRight = true;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Reuse the same cache object to avoid per-frame allocation.
|
|
922
|
+
// `hidden` stored by reference — identity comparison in the cache
|
|
923
|
+
// hit path catches set-content changes because the legend-click
|
|
924
|
+
// handler swaps / mutates the set in ways that invalidate the
|
|
925
|
+
// cache via the explicit null-out.
|
|
926
|
+
const next = cache ?? newSeriesAutoFitCache();
|
|
927
|
+
next.catMin = visCatMin;
|
|
928
|
+
next.catMax = visCatMax;
|
|
929
|
+
next.hidden = chart._hiddenSeries;
|
|
930
|
+
next.leftMin = leftMin;
|
|
931
|
+
next.leftMax = leftMax;
|
|
932
|
+
next.hasLeft = hasLeft;
|
|
933
|
+
next.rightMin = rightMin;
|
|
934
|
+
next.rightMax = rightMax;
|
|
935
|
+
next.hasRight = hasRight;
|
|
936
|
+
chart._autoFitCache = next;
|
|
937
|
+
return next;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function newSeriesAutoFitCache(): SeriesAutoFitCache {
|
|
941
|
+
return {
|
|
942
|
+
catMin: 0,
|
|
943
|
+
catMax: 0,
|
|
944
|
+
hidden: new Set(),
|
|
945
|
+
leftMin: 0,
|
|
946
|
+
leftMax: 0,
|
|
947
|
+
hasLeft: false,
|
|
948
|
+
rightMin: 0,
|
|
949
|
+
rightMax: 0,
|
|
950
|
+
hasRight: false,
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Build (or rebuild) the per-category extent buckets for the current
|
|
956
|
+
* `_bars` set plus the line / scatter sample grid, filtered by the
|
|
957
|
+
* current `_hiddenSeries` set. The buckets answer "what's the value
|
|
958
|
+
* range across this category?" in O(1) per category, replacing the
|
|
959
|
+
* O(`bars.count` + N × |line+scatter|) per-frame walk.
|
|
960
|
+
*
|
|
961
|
+
* Bar / area glyphs contribute via `_bars` (min/max of `y0`,`y1`, so
|
|
962
|
+
* stacking and negative values are handled uniformly). Line / scatter
|
|
963
|
+
* glyphs have no `_bars` records — they contribute the raw sample
|
|
964
|
+
* value `v` as the single-point extent `[v, v]`; without this pass
|
|
965
|
+
* `series_zoom_mode === "dynamic"` would silently behave as `"fixed"`
|
|
966
|
+
* on any pure line/scatter chart.
|
|
967
|
+
*
|
|
968
|
+
* Capacity-reused: typed arrays grown only when `_numCategories`
|
|
969
|
+
* exceeds prior capacity. Amortizes across pan/zoom frames — runs
|
|
970
|
+
* once per data load + once per legend toggle, not per frame.
|
|
971
|
+
*/
|
|
972
|
+
function ensureCatExtents(
|
|
973
|
+
chart: SeriesChart,
|
|
974
|
+
): NonNullable<SeriesChart["_catExtents"]> {
|
|
975
|
+
const N = chart._numCategories;
|
|
976
|
+
let buckets = chart._catExtents;
|
|
977
|
+
|
|
978
|
+
const sameCapacity = buckets && buckets.leftMin.length >= N;
|
|
979
|
+
if (
|
|
980
|
+
buckets &&
|
|
981
|
+
sameCapacity &&
|
|
982
|
+
chart._catExtentsHidden === chart._hiddenSeries
|
|
983
|
+
) {
|
|
984
|
+
return buckets;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (!buckets || !sameCapacity) {
|
|
988
|
+
buckets = {
|
|
989
|
+
leftMin: new Float64Array(N),
|
|
990
|
+
leftMax: new Float64Array(N),
|
|
991
|
+
rightMin: new Float64Array(N),
|
|
992
|
+
rightMax: new Float64Array(N),
|
|
993
|
+
hasLeft: new Uint8Array(N),
|
|
994
|
+
hasRight: new Uint8Array(N),
|
|
995
|
+
n: N,
|
|
996
|
+
};
|
|
997
|
+
chart._catExtents = buckets;
|
|
998
|
+
} else {
|
|
999
|
+
buckets.n = N;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Initialize every per-cat slot to the empty extent. `Infinity` /
|
|
1003
|
+
// `-Infinity` so that the first contributing bar wins on
|
|
1004
|
+
// min/max comparisons.
|
|
1005
|
+
for (let i = 0; i < N; i++) {
|
|
1006
|
+
buckets.leftMin[i] = Infinity;
|
|
1007
|
+
buckets.leftMax[i] = -Infinity;
|
|
1008
|
+
buckets.rightMin[i] = Infinity;
|
|
1009
|
+
buckets.rightMax[i] = -Infinity;
|
|
1010
|
+
buckets.hasLeft[i] = 0;
|
|
1011
|
+
buckets.hasRight[i] = 0;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const bars = chart._bars;
|
|
1015
|
+
const hidden = chart._hiddenSeries;
|
|
1016
|
+
const catIdxArr = bars.catIdx;
|
|
1017
|
+
const seriesIdArr = bars.seriesId;
|
|
1018
|
+
const y0Arr = bars.y0;
|
|
1019
|
+
const y1Arr = bars.y1;
|
|
1020
|
+
const axisArr = bars.axis;
|
|
1021
|
+
for (let i = 0; i < bars.count; i++) {
|
|
1022
|
+
if (hidden.has(seriesIdArr[i])) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const ci = catIdxArr[i];
|
|
1027
|
+
if (ci < 0 || ci >= N) {
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const y0 = y0Arr[i];
|
|
1032
|
+
const y1 = y1Arr[i];
|
|
1033
|
+
const lo = y0 < y1 ? y0 : y1;
|
|
1034
|
+
const hi = y0 < y1 ? y1 : y0;
|
|
1035
|
+
if (axisArr[i] === 1) {
|
|
1036
|
+
if (lo < buckets.rightMin[ci]) {
|
|
1037
|
+
buckets.rightMin[ci] = lo;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (hi > buckets.rightMax[ci]) {
|
|
1041
|
+
buckets.rightMax[ci] = hi;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
buckets.hasRight[ci] = 1;
|
|
1045
|
+
} else {
|
|
1046
|
+
if (lo < buckets.leftMin[ci]) {
|
|
1047
|
+
buckets.leftMin[ci] = lo;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (hi > buckets.leftMax[ci]) {
|
|
1051
|
+
buckets.leftMax[ci] = hi;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
buckets.hasLeft[ci] = 1;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Line / scatter glyphs route through `_samples`, not `_bars`, so
|
|
1059
|
+
// fold their per-cat values in here. Bar / area series are already
|
|
1060
|
+
// covered by the loop above (including non-stacking bar/area, which
|
|
1061
|
+
// emit `_bars` records with `y0=0`, `y1=v`); line / scatter never
|
|
1062
|
+
// stack, so the sample grid is their only contribution.
|
|
1063
|
+
const samplingSeries = [chart._lineSeries, chart._scatterSeries];
|
|
1064
|
+
const samples = chart._samples;
|
|
1065
|
+
const sampleValid = chart._sampleValid;
|
|
1066
|
+
const S = chart._series.length;
|
|
1067
|
+
for (const seriesArr of samplingSeries) {
|
|
1068
|
+
for (const s of seriesArr) {
|
|
1069
|
+
if (hidden.has(s.seriesId)) {
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const onRight = s.axis === 1;
|
|
1074
|
+
const sid = s.seriesId;
|
|
1075
|
+
for (let ci = 0; ci < N; ci++) {
|
|
1076
|
+
const sampleIdx = ci * S + sid;
|
|
1077
|
+
if (!((sampleValid[sampleIdx >> 3] >> (sampleIdx & 7)) & 1)) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const v = samples[sampleIdx];
|
|
1082
|
+
if (onRight) {
|
|
1083
|
+
if (v < buckets.rightMin[ci]) {
|
|
1084
|
+
buckets.rightMin[ci] = v;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (v > buckets.rightMax[ci]) {
|
|
1088
|
+
buckets.rightMax[ci] = v;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
buckets.hasRight[ci] = 1;
|
|
1092
|
+
} else {
|
|
1093
|
+
if (v < buckets.leftMin[ci]) {
|
|
1094
|
+
buckets.leftMin[ci] = v;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (v > buckets.leftMax[ci]) {
|
|
1098
|
+
buckets.leftMax[ci] = v;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
buckets.hasLeft[ci] = 1;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
chart._catExtentsHidden = hidden;
|
|
1108
|
+
return buckets;
|
|
1109
|
+
}
|