@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,100 @@
|
|
|
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
|
+
export class SpatialGrid {
|
|
14
|
+
private _cells: Map<number, number[]> = new Map();
|
|
15
|
+
private _xMin: number;
|
|
16
|
+
private _yMin: number;
|
|
17
|
+
private _cellSize: number;
|
|
18
|
+
private _cols: number;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
xMin: number,
|
|
22
|
+
xMax: number,
|
|
23
|
+
yMin: number,
|
|
24
|
+
yMax: number,
|
|
25
|
+
cellSize: number,
|
|
26
|
+
) {
|
|
27
|
+
this._xMin = xMin;
|
|
28
|
+
this._yMin = yMin;
|
|
29
|
+
this._cellSize = cellSize;
|
|
30
|
+
this._cols = Math.max(1, Math.ceil((xMax - xMin) / cellSize));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private _cellKey(cx: number, cy: number): number {
|
|
34
|
+
return cy * this._cols + cx;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
insert(index: number, x: number, y: number): void {
|
|
38
|
+
const cx = Math.floor((x - this._xMin) / this._cellSize);
|
|
39
|
+
const cy = Math.floor((y - this._yMin) / this._cellSize);
|
|
40
|
+
const key = this._cellKey(cx, cy);
|
|
41
|
+
let cell = this._cells.get(key);
|
|
42
|
+
if (!cell) {
|
|
43
|
+
cell = [];
|
|
44
|
+
this._cells.set(key, cell);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
cell.push(index);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Find the nearest point to (dataX, dataY) within the given radius,
|
|
52
|
+
* measured in pixel distance using the provided scale factors.
|
|
53
|
+
*/
|
|
54
|
+
query(
|
|
55
|
+
dataX: number,
|
|
56
|
+
dataY: number,
|
|
57
|
+
radiusPx: number,
|
|
58
|
+
pxPerDataX: number,
|
|
59
|
+
pxPerDataY: number,
|
|
60
|
+
xData: Float32Array,
|
|
61
|
+
yData: Float32Array,
|
|
62
|
+
): number {
|
|
63
|
+
const cellRadiusX = Math.ceil(radiusPx / pxPerDataX / this._cellSize);
|
|
64
|
+
const cellRadiusY = Math.ceil(radiusPx / pxPerDataY / this._cellSize);
|
|
65
|
+
const centerCX = Math.floor((dataX - this._xMin) / this._cellSize);
|
|
66
|
+
const centerCY = Math.floor((dataY - this._yMin) / this._cellSize);
|
|
67
|
+
|
|
68
|
+
let bestIdx = -1;
|
|
69
|
+
let bestDistSq = radiusPx * radiusPx;
|
|
70
|
+
|
|
71
|
+
for (
|
|
72
|
+
let cy = centerCY - cellRadiusY;
|
|
73
|
+
cy <= centerCY + cellRadiusY;
|
|
74
|
+
cy++
|
|
75
|
+
) {
|
|
76
|
+
for (
|
|
77
|
+
let cx = centerCX - cellRadiusX;
|
|
78
|
+
cx <= centerCX + cellRadiusX;
|
|
79
|
+
cx++
|
|
80
|
+
) {
|
|
81
|
+
const cell = this._cells.get(this._cellKey(cx, cy));
|
|
82
|
+
if (!cell) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const i of cell) {
|
|
87
|
+
const dx = (xData[i] - dataX) * pxPerDataX;
|
|
88
|
+
const dy = (yData[i] - dataY) * pxPerDataY;
|
|
89
|
+
const distSq = dx * dx + dy * dy;
|
|
90
|
+
if (distSq < bestDistSq) {
|
|
91
|
+
bestDistSq = distSq;
|
|
92
|
+
bestIdx = i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return bestIdx;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
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 { Canvas2D, Context2D } from "../charts/canvas-types";
|
|
14
|
+
import type { PlotLayout } from "../layout/plot-layout";
|
|
15
|
+
import type { Theme } from "../theme/theme";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Minimal positioning input — PlotLayout satisfies this.
|
|
19
|
+
*/
|
|
20
|
+
export interface CssBounds {
|
|
21
|
+
cssWidth: number;
|
|
22
|
+
cssHeight: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface TooltipCallbacks {
|
|
26
|
+
/**
|
|
27
|
+
* RAF-throttled mouse position in CSS pixels, relative to the GL
|
|
28
|
+
* canvas (host already subtracted `getBoundingClientRect`).
|
|
29
|
+
*/
|
|
30
|
+
onHover(mx: number, my: number): void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fires on mouseleave; skipped while a pinned tooltip is active.
|
|
34
|
+
*/
|
|
35
|
+
onLeave(): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Fires on click with mouse position. Return true to consume the click
|
|
39
|
+
* (skipping the default pin/dismiss flow — used for legend clicks).
|
|
40
|
+
*/
|
|
41
|
+
onClickPre?(mx: number, my: number): boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fires when a click should pin the current hover target.
|
|
45
|
+
*/
|
|
46
|
+
onPin?(mx: number, my: number): void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Fires on dblclick (treemap drill-up gesture). Optional — charts
|
|
50
|
+
* that don't bind a handler simply ignore the event.
|
|
51
|
+
*/
|
|
52
|
+
onDblClick?(mx: number, my: number): void;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fires when an active pin is dismissed by a click on the
|
|
56
|
+
* already-pinned target (the "click again to unpin" gesture in
|
|
57
|
+
* `dispatchClick`). Chart impls hook this to emit a
|
|
58
|
+
* `perspective-global-filter` with `selected: false`. Does *not*
|
|
59
|
+
* fire on the implicit dismiss inside `pin()` that replaces an
|
|
60
|
+
* existing pin — that path is followed by a fresh `onPin` which
|
|
61
|
+
* emits its own `selected: true`.
|
|
62
|
+
*/
|
|
63
|
+
onUnpin?(): void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RenderTooltipOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Draw a dashed crosshair at `pos`. Used by scatter/line.
|
|
69
|
+
*/
|
|
70
|
+
crosshair?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Draw a ring of `radius` CSS pixels at `pos`. Used to highlight a
|
|
74
|
+
* hovered point. Omit for bars (where the bar itself highlights).
|
|
75
|
+
*/
|
|
76
|
+
highlightRadius?: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Side-channel from the chart back to the host's DOM. The chart calls
|
|
81
|
+
* into a sink rather than touching the DOM itself; the host
|
|
82
|
+
* materializes the actual visual (`<div>` for pinned tooltip, cursor
|
|
83
|
+
* mutation on the GL canvas).
|
|
84
|
+
*
|
|
85
|
+
* - `MessageHostSink` (in worker/) — forwards calls over a
|
|
86
|
+
* `postMessage`-shaped channel back to
|
|
87
|
+
* the host.
|
|
88
|
+
* - `DomHostSink` (in host transport) — receives the matching
|
|
89
|
+
* envelopes and applies them to the DOM.
|
|
90
|
+
*
|
|
91
|
+
* The controller's `_pinned` flag is the source of truth for whether
|
|
92
|
+
* hover updates are gated; the sink only owns the visual artifact.
|
|
93
|
+
*/
|
|
94
|
+
export interface HostSink {
|
|
95
|
+
pin(
|
|
96
|
+
lines: string[],
|
|
97
|
+
pos: { px: number; py: number },
|
|
98
|
+
bounds: CssBounds,
|
|
99
|
+
): void;
|
|
100
|
+
dismiss(): void;
|
|
101
|
+
setCursor(cursor: string): void;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Forward a `perspective-click` to the host. Optional — only the
|
|
105
|
+
* worker-bound `MessageHostSink` implements it; `DomHostSink` (the
|
|
106
|
+
* host-side consumer of pin/dismiss) never sees user-event calls,
|
|
107
|
+
* so omits the implementation.
|
|
108
|
+
*/
|
|
109
|
+
emitUserClick?(detail: UserClickPayload): void;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Forward a `perspective-global-filter` to the host with the
|
|
113
|
+
* `selected: true` / `selected: false` semantics. The host owns the
|
|
114
|
+
* `removeConfigs` history (mirrors datagrid's
|
|
115
|
+
* `model._last_insert_configs`); the sink only ships the new state.
|
|
116
|
+
*/
|
|
117
|
+
emitUserSelect?(payload: UserSelectPayload): void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Plain-object payload for `HostSink.emitUserClick`. Matches
|
|
122
|
+
* `PerspectiveClickDetail` byte-for-byte; defined locally to avoid a
|
|
123
|
+
* cycle through `event-detail.ts`.
|
|
124
|
+
*/
|
|
125
|
+
export interface UserClickPayload {
|
|
126
|
+
row: Record<string, unknown>;
|
|
127
|
+
column_names: string[];
|
|
128
|
+
config: { filter?: unknown[] };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Plain-object payload for `HostSink.emitUserSelect`. The host
|
|
133
|
+
* transport reconstructs a `PerspectiveSelectDetail` class instance
|
|
134
|
+
* from this plus its cached `_lastInsertConfig`.
|
|
135
|
+
*/
|
|
136
|
+
export interface UserSelectPayload {
|
|
137
|
+
selected: boolean;
|
|
138
|
+
row: Record<string, unknown>;
|
|
139
|
+
column_names: string[];
|
|
140
|
+
insertConfig: { filter?: unknown[] };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Owns the hover/click/dblclick state machine and the pinned-tooltip
|
|
145
|
+
* lifecycle. The renderer drives this purely through
|
|
146
|
+
* `dispatchHover` / `dispatchLeave` / `dispatchClick` /
|
|
147
|
+
* `dispatchDblClick` — the host's `RawEventForwarder` captures DOM
|
|
148
|
+
* events on the GL canvas and posts them as `InteractionEvent`s.
|
|
149
|
+
*
|
|
150
|
+
* Pinning + cursor changes go through a {@link HostSink} so the actual
|
|
151
|
+
* DOM mutations happen host-side regardless of where the chart runs.
|
|
152
|
+
*/
|
|
153
|
+
export class TooltipController {
|
|
154
|
+
private _callbacks: TooltipCallbacks | null = null;
|
|
155
|
+
private _hoverRAFId = 0;
|
|
156
|
+
private _hoverTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
157
|
+
private _host: HostSink | null = null;
|
|
158
|
+
private _pinned = false;
|
|
159
|
+
|
|
160
|
+
get isPinned(): boolean {
|
|
161
|
+
return this._pinned;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Replace the active host sink. Dismisses any existing pin via the
|
|
166
|
+
* prior sink so we never leak a pinned artifact across resets —
|
|
167
|
+
* though in practice each chart instance uses one sink for its
|
|
168
|
+
* lifetime.
|
|
169
|
+
*/
|
|
170
|
+
setHost(sink: HostSink): void {
|
|
171
|
+
if (this._pinned) {
|
|
172
|
+
this._host?.dismiss();
|
|
173
|
+
this._pinned = false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
this._host = sink;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Forward a cursor change to the host. No-op when no host sink is
|
|
181
|
+
* installed (chart constructed without a transport).
|
|
182
|
+
*/
|
|
183
|
+
setCursor(cursor: string): void {
|
|
184
|
+
this._host?.setCursor(cursor);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Install the chart's tooltip callbacks. The renderer drives the
|
|
189
|
+
* controller via `dispatchHover` / `dispatchLeave` /
|
|
190
|
+
* `dispatchClick` / `dispatchDblClick`; this controller never
|
|
191
|
+
* touches the DOM directly.
|
|
192
|
+
*/
|
|
193
|
+
attach(callbacks: TooltipCallbacks): void {
|
|
194
|
+
this.detach();
|
|
195
|
+
this._callbacks = callbacks;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
detach(): void {
|
|
199
|
+
if (this._hoverRAFId) {
|
|
200
|
+
cancelAnimationFrame(this._hoverRAFId);
|
|
201
|
+
this._hoverRAFId = 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (this._hoverTimeoutId !== null) {
|
|
205
|
+
clearTimeout(this._hoverTimeoutId);
|
|
206
|
+
this._hoverTimeoutId = null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this._callbacks = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Schedule an `onHover` callback for the given canvas-relative
|
|
214
|
+
* coords. Coalesces multiple calls within one animation frame so
|
|
215
|
+
* pointer streams don't backlog the chart's hit-test path.
|
|
216
|
+
*
|
|
217
|
+
* Workers ship with `requestAnimationFrame` (DedicatedWorkerGlobalScope
|
|
218
|
+
* exposes it for OffscreenCanvas painting), so the same coalescer
|
|
219
|
+
* works in both modes. We fall back to setTimeout if RAF is missing
|
|
220
|
+
* (e.g. node tests without a polyfill).
|
|
221
|
+
*/
|
|
222
|
+
dispatchHover(mx: number, my: number): void {
|
|
223
|
+
if (this._pinned || !this._callbacks) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (this._hoverRAFId || this._hoverTimeoutId !== null) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const fire = () => {
|
|
232
|
+
this._hoverRAFId = 0;
|
|
233
|
+
this._hoverTimeoutId = null;
|
|
234
|
+
this._callbacks?.onHover(mx, my);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (typeof requestAnimationFrame === "function") {
|
|
238
|
+
this._hoverRAFId = requestAnimationFrame(fire);
|
|
239
|
+
} else {
|
|
240
|
+
this._hoverTimeoutId = setTimeout(fire, 16);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
dispatchLeave(): void {
|
|
245
|
+
if (this._pinned || !this._callbacks) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this._callbacks.onLeave();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
dispatchClick(mx: number, my: number): void {
|
|
253
|
+
if (!this._callbacks) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this._callbacks.onClickPre?.(mx, my)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (this._pinned) {
|
|
262
|
+
const cb = this._callbacks;
|
|
263
|
+
this.dismiss();
|
|
264
|
+
cb.onUnpin?.();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this._callbacks.onPin?.(mx, my);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
dispatchDblClick(mx: number, my: number): void {
|
|
272
|
+
this._callbacks?.onDblClick?.(mx, my);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Pin a tooltip (or replace an active one). Forwards through the
|
|
277
|
+
* configured sink and flips the controller's pinned flag so hover
|
|
278
|
+
* dispatch is suppressed until dismissal.
|
|
279
|
+
*/
|
|
280
|
+
pin(
|
|
281
|
+
lines: string[],
|
|
282
|
+
pos: { px: number; py: number },
|
|
283
|
+
bounds: CssBounds,
|
|
284
|
+
): void {
|
|
285
|
+
if (lines.length === 0) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this._host?.pin(lines, pos, bounds);
|
|
290
|
+
this._pinned = true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
dismiss(): void {
|
|
294
|
+
this._host?.dismiss();
|
|
295
|
+
this._pinned = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Paint a canvas tooltip (crosshair, highlight ring, box + text) onto
|
|
301
|
+
* `canvas`. The helper normalizes the 2D context to a DPR-scaled
|
|
302
|
+
* identity transform on entry and restores prior state on exit, so it
|
|
303
|
+
* composes cleanly with other chrome painters that may have already
|
|
304
|
+
* called `initCanvas` on the same canvas — re-applying `scale(dpr,dpr)`
|
|
305
|
+
* blind would double-scale in that case, misplacing the tooltip
|
|
306
|
+
* proportionally to its distance from the origin.
|
|
307
|
+
*/
|
|
308
|
+
export function renderCanvasTooltip(
|
|
309
|
+
canvas: Canvas2D | null,
|
|
310
|
+
pos: { px: number; py: number },
|
|
311
|
+
lines: string[],
|
|
312
|
+
layout: PlotLayout,
|
|
313
|
+
theme: Theme,
|
|
314
|
+
dpr: number,
|
|
315
|
+
options: RenderTooltipOptions = {},
|
|
316
|
+
): void {
|
|
317
|
+
if (!canvas) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const ctx = canvas.getContext("2d") as Context2D | null;
|
|
322
|
+
if (!ctx) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
ctx.save();
|
|
327
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
328
|
+
ctx.scale(dpr, dpr);
|
|
329
|
+
ctx.font = `11px ${theme.fontFamily}`;
|
|
330
|
+
const lineHeight = 16;
|
|
331
|
+
const padding = 8;
|
|
332
|
+
let maxWidth = 0;
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
const w = ctx.measureText(line).width;
|
|
335
|
+
if (w > maxWidth) {
|
|
336
|
+
maxWidth = w;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const boxW = maxWidth + padding * 2;
|
|
341
|
+
const boxH = lines.length * lineHeight + padding * 2 - 4;
|
|
342
|
+
let tx = pos.px + 12;
|
|
343
|
+
let ty = pos.py - boxH - 8;
|
|
344
|
+
if (tx + boxW > layout.cssWidth) {
|
|
345
|
+
tx = pos.px - boxW - 12;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (ty < 0) {
|
|
349
|
+
ty = pos.py + 12;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (ty + boxH > layout.cssHeight) {
|
|
353
|
+
ty = layout.cssHeight - boxH - 4;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const hasLines = lines.length > 0;
|
|
357
|
+
|
|
358
|
+
// Crosshair
|
|
359
|
+
if (options.crosshair) {
|
|
360
|
+
ctx.strokeStyle = theme.tickColor;
|
|
361
|
+
ctx.globalAlpha = 0.3;
|
|
362
|
+
ctx.lineWidth = 1;
|
|
363
|
+
ctx.setLineDash([4, 4]);
|
|
364
|
+
ctx.beginPath();
|
|
365
|
+
ctx.moveTo(pos.px, layout.plotRect.y);
|
|
366
|
+
ctx.lineTo(pos.px, layout.plotRect.y + layout.plotRect.height);
|
|
367
|
+
ctx.moveTo(layout.plotRect.x, pos.py);
|
|
368
|
+
ctx.lineTo(layout.plotRect.x + layout.plotRect.width, pos.py);
|
|
369
|
+
ctx.stroke();
|
|
370
|
+
ctx.setLineDash([]);
|
|
371
|
+
ctx.globalAlpha = 1.0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Highlight ring
|
|
375
|
+
if (options.highlightRadius && options.highlightRadius > 0) {
|
|
376
|
+
ctx.strokeStyle = theme.tickColor;
|
|
377
|
+
ctx.globalAlpha = 0.8;
|
|
378
|
+
ctx.lineWidth = 2;
|
|
379
|
+
ctx.beginPath();
|
|
380
|
+
ctx.arc(pos.px, pos.py, options.highlightRadius, 0, Math.PI * 2);
|
|
381
|
+
ctx.stroke();
|
|
382
|
+
ctx.globalAlpha = 1.0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Box + text are only drawn when we have content. Callers pass an
|
|
386
|
+
// empty `lines` array while a lazy row fetch is still in flight —
|
|
387
|
+
// the crosshair / highlight ring above paint immediately so the
|
|
388
|
+
// hover remains visible, but the tooltip chrome waits for data.
|
|
389
|
+
if (hasLines) {
|
|
390
|
+
ctx.fillStyle = theme.tooltipBg;
|
|
391
|
+
ctx.strokeStyle = theme.tooltipBorder;
|
|
392
|
+
ctx.lineWidth = 1;
|
|
393
|
+
ctx.beginPath();
|
|
394
|
+
ctx.roundRect(tx, ty, boxW, boxH, 4);
|
|
395
|
+
ctx.fill();
|
|
396
|
+
ctx.stroke();
|
|
397
|
+
|
|
398
|
+
ctx.fillStyle = theme.tooltipText;
|
|
399
|
+
ctx.textAlign = "left";
|
|
400
|
+
ctx.textBaseline = "top";
|
|
401
|
+
for (let i = 0; i < lines.length; i++) {
|
|
402
|
+
ctx.fillText(lines[i], tx + padding, ty + padding + i * lineHeight);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
ctx.restore();
|
|
407
|
+
}
|