@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,42 @@
|
|
|
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 { Theme } from "../theme/theme";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Paint a single facet's title strip — one line of centered text in the
|
|
18
|
+
* caller-supplied `rect`. Shared by every faceted chart family
|
|
19
|
+
* (cartesian, heatmap, …) so the title typography stays uniform.
|
|
20
|
+
*/
|
|
21
|
+
export function drawFacetTitle(
|
|
22
|
+
canvas: Canvas2D,
|
|
23
|
+
label: string,
|
|
24
|
+
rect: { x: number; y: number; width: number; height: number },
|
|
25
|
+
theme: Theme,
|
|
26
|
+
dpr: number,
|
|
27
|
+
): void {
|
|
28
|
+
const ctx = canvas.getContext("2d") as Context2D | null;
|
|
29
|
+
if (!ctx) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
ctx.save();
|
|
34
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
35
|
+
ctx.scale(dpr, dpr);
|
|
36
|
+
ctx.font = `11px ${theme.fontFamily}`;
|
|
37
|
+
ctx.fillStyle = theme.labelColor;
|
|
38
|
+
ctx.textAlign = "center";
|
|
39
|
+
ctx.textBaseline = "middle";
|
|
40
|
+
ctx.fillText(label, rect.x + rect.width / 2, rect.y + rect.height / 2);
|
|
41
|
+
ctx.restore();
|
|
42
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generic pixel-space label geometry helpers shared by the axis,
|
|
15
|
+
* legend, and tooltip overlays.
|
|
16
|
+
*
|
|
17
|
+
* All rectangles are in CSS pixels, origin top-left, Y-axis pointing down.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { Context2D } from "../charts/canvas-types";
|
|
21
|
+
|
|
22
|
+
export interface Rect {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type Rotation = 0 | 45 | 90;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Bounding rectangle of a text label anchored at `(cx, cy)`, accounting for
|
|
33
|
+
* rotation. Matches d3fc's approximation of rotated text bounds.
|
|
34
|
+
*/
|
|
35
|
+
export function labelRect(
|
|
36
|
+
cx: number,
|
|
37
|
+
cy: number,
|
|
38
|
+
textWidth: number,
|
|
39
|
+
textHeight: number,
|
|
40
|
+
rotation: Rotation,
|
|
41
|
+
): Rect {
|
|
42
|
+
if (rotation === 0) {
|
|
43
|
+
return {
|
|
44
|
+
x: cx - textWidth / 2,
|
|
45
|
+
y: cy,
|
|
46
|
+
width: textWidth,
|
|
47
|
+
height: textHeight,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (rotation === 90) {
|
|
52
|
+
return {
|
|
53
|
+
x: cx - textHeight / 2,
|
|
54
|
+
y: cy,
|
|
55
|
+
width: textHeight,
|
|
56
|
+
height: textWidth,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const w = (textWidth + textHeight) / Math.SQRT2;
|
|
61
|
+
return { x: cx - w, y: cy, width: w, height: w };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function rectsOverlap(a: Rect, b: Rect): boolean {
|
|
65
|
+
return (
|
|
66
|
+
a.x <= b.x + b.width &&
|
|
67
|
+
b.x <= a.x + a.width &&
|
|
68
|
+
a.y <= b.y + b.height &&
|
|
69
|
+
b.y <= a.y + a.height
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rotated-label overlap heuristic from d3fc: for steeply-rotated labels
|
|
75
|
+
* the right edge of the previous box must precede the right edge of the
|
|
76
|
+
* next (plus a small gap).
|
|
77
|
+
*/
|
|
78
|
+
export function rotatedLabelsOverlap(a: Rect, b: Rect): boolean {
|
|
79
|
+
return a.x + a.width + 14 > b.x + b.width;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function rectContained(inner: Rect, outer: Rect): boolean {
|
|
83
|
+
return (
|
|
84
|
+
inner.x >= outer.x &&
|
|
85
|
+
inner.x + inner.width <= outer.x + outer.width &&
|
|
86
|
+
inner.y >= outer.y &&
|
|
87
|
+
inner.y + inner.height <= outer.y + outer.height
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Truncate `label` with a trailing ellipsis so the rendered width fits
|
|
93
|
+
* within `maxWidth`. Returns "" when even one character would overflow.
|
|
94
|
+
*/
|
|
95
|
+
export function truncateLabel(
|
|
96
|
+
ctx: Context2D,
|
|
97
|
+
label: string,
|
|
98
|
+
maxWidth: number,
|
|
99
|
+
): string {
|
|
100
|
+
if (maxWidth <= 0) {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (ctx.measureText(label).width <= maxWidth) {
|
|
105
|
+
return label;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let lo = 0;
|
|
109
|
+
let hi = label.length;
|
|
110
|
+
while (lo < hi) {
|
|
111
|
+
const mid = (lo + hi + 1) >> 1;
|
|
112
|
+
const candidate = label.slice(0, mid) + "…";
|
|
113
|
+
if (ctx.measureText(candidate).width <= maxWidth) {
|
|
114
|
+
lo = mid;
|
|
115
|
+
} else {
|
|
116
|
+
hi = mid - 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lo === 0 ? "" : label.slice(0, lo) + "…";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Word-wrap `text` into at most `maxLines` lines, each fitting within
|
|
125
|
+
* `maxWidth`. Breaks at the last whitespace before the fit boundary
|
|
126
|
+
* when possible, falls back to a hard character break otherwise. The
|
|
127
|
+
* final line is ellipsis-truncated via {@link truncateLabel} if it
|
|
128
|
+
* still doesn't fit. Returns `[]` when nothing meaningful fits (only
|
|
129
|
+
* one line of ≤ 2 chars after wrapping).
|
|
130
|
+
*/
|
|
131
|
+
export function wrapLabel(
|
|
132
|
+
ctx: Context2D,
|
|
133
|
+
text: string,
|
|
134
|
+
maxWidth: number,
|
|
135
|
+
maxLines: number,
|
|
136
|
+
): string[] {
|
|
137
|
+
if (maxLines <= 0 || maxWidth <= 0) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (ctx.measureText(text).width <= maxWidth) {
|
|
142
|
+
return [text];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const lines: string[] = [];
|
|
146
|
+
let remaining = text;
|
|
147
|
+
|
|
148
|
+
while (remaining.length > 0 && lines.length < maxLines) {
|
|
149
|
+
const isLastLine = lines.length === maxLines - 1;
|
|
150
|
+
|
|
151
|
+
let fitLen = remaining.length;
|
|
152
|
+
while (
|
|
153
|
+
fitLen > 0 &&
|
|
154
|
+
ctx.measureText(remaining.slice(0, fitLen)).width > maxWidth
|
|
155
|
+
) {
|
|
156
|
+
fitLen--;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (fitLen === 0) {
|
|
160
|
+
fitLen = 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (fitLen === remaining.length) {
|
|
164
|
+
lines.push(remaining);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let breakAt = fitLen;
|
|
169
|
+
const spaceIdx = remaining.lastIndexOf(" ", fitLen);
|
|
170
|
+
if (spaceIdx > 0) {
|
|
171
|
+
breakAt = spaceIdx;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (isLastLine) {
|
|
175
|
+
lines.push(truncateLabel(ctx, remaining, maxWidth));
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
lines.push(remaining.slice(0, breakAt));
|
|
180
|
+
remaining = remaining.slice(breakAt).trimStart();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (lines.length === 1 && lines[0].length <= 2) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return lines;
|
|
188
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
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, PlotRect } from "../layout/plot-layout";
|
|
15
|
+
import { formatTickValue } from "../layout/ticks";
|
|
16
|
+
import {
|
|
17
|
+
colorValueToT,
|
|
18
|
+
sampleGradient,
|
|
19
|
+
type GradientStop,
|
|
20
|
+
} from "../theme/gradient";
|
|
21
|
+
import type { Theme } from "../theme/theme";
|
|
22
|
+
|
|
23
|
+
function rgbCss(c: [number, number, number, number]): string {
|
|
24
|
+
return `rgb(${Math.round(c[0] * 255)},${Math.round(c[1] * 255)},${Math.round(c[2] * 255)})`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Render a vertical color gradient legend on the Canvas2D overlay.
|
|
29
|
+
* Only call when a color column is active. When `colorDomain` crosses
|
|
30
|
+
* zero the 50% stop (sign pivot) is annotated with a tick + `0` label.
|
|
31
|
+
*
|
|
32
|
+
* Per-facet wrapper; computes the anchor from `layout` and delegates
|
|
33
|
+
* to {@link renderLegendAt}. Facet grids render one shared gradient
|
|
34
|
+
* legend and pass an explicit rect to `renderLegendAt` directly.
|
|
35
|
+
*/
|
|
36
|
+
export function renderLegend(
|
|
37
|
+
canvas: Canvas2D,
|
|
38
|
+
layout: PlotLayout,
|
|
39
|
+
colorDomain: { min: number; max: number; label: string },
|
|
40
|
+
stops: GradientStop[],
|
|
41
|
+
theme: Theme,
|
|
42
|
+
formatter?: (v: number) => string,
|
|
43
|
+
): void {
|
|
44
|
+
const rect: PlotRect = {
|
|
45
|
+
x: layout.plotRect.x + layout.plotRect.width + 12,
|
|
46
|
+
y: layout.margins.top + 20,
|
|
47
|
+
width: Math.max(
|
|
48
|
+
1,
|
|
49
|
+
layout.cssWidth - layout.plotRect.x - layout.plotRect.width - 12,
|
|
50
|
+
),
|
|
51
|
+
height: Math.max(1, layout.plotRect.height),
|
|
52
|
+
};
|
|
53
|
+
renderLegendAt(canvas, rect, colorDomain, stops, theme, formatter);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Render a gradient legend at an explicit canvas-absolute rect.
|
|
58
|
+
* Used by facet grids that paint one legend for the whole grid and
|
|
59
|
+
* by single-plot charts through {@link renderLegend}.
|
|
60
|
+
*/
|
|
61
|
+
export function renderLegendAt(
|
|
62
|
+
canvas: Canvas2D,
|
|
63
|
+
rect: PlotRect,
|
|
64
|
+
colorDomain: { min: number; max: number; label: string },
|
|
65
|
+
stops: GradientStop[],
|
|
66
|
+
theme: Theme,
|
|
67
|
+
formatter: (v: number) => string = formatTickValue,
|
|
68
|
+
): void {
|
|
69
|
+
const ctx = canvas.getContext("2d") as Context2D | null;
|
|
70
|
+
if (!ctx) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const textColor = theme.legendText;
|
|
75
|
+
const borderColor = theme.legendBorder;
|
|
76
|
+
const fontFamily = theme.fontFamily;
|
|
77
|
+
|
|
78
|
+
const barWidth = 16;
|
|
79
|
+
const barHeight = Math.min(120, rect.height * 0.4);
|
|
80
|
+
const x = rect.x;
|
|
81
|
+
const y = rect.y;
|
|
82
|
+
|
|
83
|
+
ctx.fillStyle = textColor;
|
|
84
|
+
ctx.font = `9px ${fontFamily}`;
|
|
85
|
+
ctx.textAlign = "left";
|
|
86
|
+
ctx.textBaseline = "bottom";
|
|
87
|
+
ctx.fillText(colorDomain.label, x, y - 4);
|
|
88
|
+
|
|
89
|
+
// Paint the gradient by walking `colorDomain.min..max` top→bottom and
|
|
90
|
+
// feeding each value through `colorValueToT` so the legend matches the
|
|
91
|
+
// sign-aware mapping used by the GPU / treemap paths.
|
|
92
|
+
const topVal = colorDomain.max;
|
|
93
|
+
const bottomVal = colorDomain.min;
|
|
94
|
+
const gradient = ctx.createLinearGradient(0, y, 0, y + barHeight);
|
|
95
|
+
const SAMPLES = 16;
|
|
96
|
+
for (let i = 0; i <= SAMPLES; i++) {
|
|
97
|
+
const offset = i / SAMPLES;
|
|
98
|
+
const v = topVal + offset * (bottomVal - topVal);
|
|
99
|
+
const t = colorValueToT(v, colorDomain.min, colorDomain.max);
|
|
100
|
+
const rgba = sampleGradient(stops, t);
|
|
101
|
+
gradient.addColorStop(offset, rgbCss(rgba));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
ctx.fillStyle = gradient;
|
|
105
|
+
ctx.fillRect(x, y, barWidth, barHeight);
|
|
106
|
+
|
|
107
|
+
ctx.strokeStyle = borderColor;
|
|
108
|
+
ctx.lineWidth = 1;
|
|
109
|
+
ctx.strokeRect(x, y, barWidth, barHeight);
|
|
110
|
+
|
|
111
|
+
ctx.fillStyle = textColor;
|
|
112
|
+
ctx.font = `10px ${fontFamily}`;
|
|
113
|
+
ctx.textAlign = "left";
|
|
114
|
+
ctx.textBaseline = "middle";
|
|
115
|
+
|
|
116
|
+
const labelX = x + barWidth + 5;
|
|
117
|
+
ctx.fillText(formatter(colorDomain.max), labelX, y + 2);
|
|
118
|
+
ctx.fillText(
|
|
119
|
+
formatter((colorDomain.min + colorDomain.max) / 2),
|
|
120
|
+
labelX,
|
|
121
|
+
y + barHeight / 2,
|
|
122
|
+
);
|
|
123
|
+
ctx.fillText(formatter(colorDomain.min), labelX, y + barHeight - 2);
|
|
124
|
+
|
|
125
|
+
// Sign-pivot marker when the data crosses zero: a small tick on the
|
|
126
|
+
// right edge of the bar + a "0" label.
|
|
127
|
+
if (colorDomain.min < 0 && colorDomain.max > 0) {
|
|
128
|
+
const zeroOffset =
|
|
129
|
+
(colorDomain.max - 0) / (colorDomain.max - colorDomain.min);
|
|
130
|
+
const zeroY = y + zeroOffset * barHeight;
|
|
131
|
+
ctx.strokeStyle = textColor;
|
|
132
|
+
ctx.lineWidth = 1;
|
|
133
|
+
ctx.beginPath();
|
|
134
|
+
ctx.moveTo(x + barWidth, zeroY);
|
|
135
|
+
ctx.lineTo(x + barWidth + 4, zeroY);
|
|
136
|
+
ctx.stroke();
|
|
137
|
+
ctx.fillStyle = textColor;
|
|
138
|
+
ctx.fillText("0", labelX, zeroY);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Render a categorical legend with discrete colored swatches.
|
|
144
|
+
* Used when split_by or string color columns produce distinct categories.
|
|
145
|
+
*
|
|
146
|
+
* The per-facet wrapper; computes the anchor from `layout` and delegates
|
|
147
|
+
* to {@link renderCategoricalLegendAt}. Facet grids that render one
|
|
148
|
+
* shared legend pass an explicit rect to `renderCategoricalLegendAt`
|
|
149
|
+
* directly.
|
|
150
|
+
*/
|
|
151
|
+
export function renderCategoricalLegend(
|
|
152
|
+
canvas: Canvas2D,
|
|
153
|
+
layout: PlotLayout,
|
|
154
|
+
labels: Map<string, number>,
|
|
155
|
+
palette: [number, number, number][],
|
|
156
|
+
theme: Theme,
|
|
157
|
+
): void {
|
|
158
|
+
const rect: PlotRect = {
|
|
159
|
+
x: layout.plotRect.x + layout.plotRect.width + 12,
|
|
160
|
+
y: layout.margins.top + 10,
|
|
161
|
+
width: Math.max(
|
|
162
|
+
1,
|
|
163
|
+
layout.cssWidth - layout.plotRect.x - layout.plotRect.width - 12,
|
|
164
|
+
),
|
|
165
|
+
height: Math.max(1, layout.plotRect.height),
|
|
166
|
+
};
|
|
167
|
+
renderCategoricalLegendAt(canvas, rect, labels, palette, theme);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Render a categorical legend at an explicit canvas-absolute rect.
|
|
172
|
+
* Used by facet grids that paint one legend for the whole grid and by
|
|
173
|
+
* single-plot charts through {@link renderCategoricalLegend}.
|
|
174
|
+
*/
|
|
175
|
+
export function renderCategoricalLegendAt(
|
|
176
|
+
canvas: Canvas2D,
|
|
177
|
+
rect: PlotRect,
|
|
178
|
+
labels: Map<string, number>,
|
|
179
|
+
palette: [number, number, number][],
|
|
180
|
+
theme: Theme,
|
|
181
|
+
): void {
|
|
182
|
+
const ctx = canvas.getContext("2d") as Context2D | null;
|
|
183
|
+
if (!ctx) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (labels.size === 0) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const textColor = theme.legendText;
|
|
192
|
+
const fontFamily = theme.fontFamily;
|
|
193
|
+
|
|
194
|
+
const swatchSize = 10;
|
|
195
|
+
const lineHeight = 18;
|
|
196
|
+
const x = rect.x;
|
|
197
|
+
let y = rect.y + lineHeight / 2;
|
|
198
|
+
|
|
199
|
+
ctx.font = `11px ${fontFamily}`;
|
|
200
|
+
ctx.textAlign = "left";
|
|
201
|
+
ctx.textBaseline = "middle";
|
|
202
|
+
|
|
203
|
+
for (const [label, idx] of labels) {
|
|
204
|
+
if (y + swatchSize / 2 > rect.y + rect.height) {
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const color = palette[idx] ??
|
|
209
|
+
palette[idx % palette.length] ?? [0, 0, 0];
|
|
210
|
+
ctx.fillStyle = `rgb(${Math.round(color[0] * 255)},${Math.round(color[1] * 255)},${Math.round(color[2] * 255)})`;
|
|
211
|
+
ctx.fillRect(x, y - swatchSize / 2, swatchSize, swatchSize);
|
|
212
|
+
|
|
213
|
+
ctx.fillStyle = textColor;
|
|
214
|
+
ctx.fillText(label, x + swatchSize + 6, y);
|
|
215
|
+
|
|
216
|
+
y += lineHeight;
|
|
217
|
+
}
|
|
218
|
+
}
|