insomni-plot 0.1.0-alpha.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.
Files changed (242) hide show
  1. package/LICENSE.md +674 -0
  2. package/README.md +81 -0
  3. package/dist/core.d.mts +340 -0
  4. package/dist/core.mjs +1047 -0
  5. package/dist/index.d.mts +3426 -0
  6. package/dist/index.mjs +12762 -0
  7. package/dist/interactions-DEFL_F4E.mjs +5395 -0
  8. package/dist/range-presets-CzECsu3V.d.mts +1523 -0
  9. package/package.json +34 -0
  10. package/src/annotations.d.ts +121 -0
  11. package/src/annotations.ts +438 -0
  12. package/src/axis.d.ts +184 -0
  13. package/src/axis.test.ts +131 -0
  14. package/src/axis.ts +765 -0
  15. package/src/colorbar.d.ts +69 -0
  16. package/src/colorbar.ts +294 -0
  17. package/src/colors.d.ts +57 -0
  18. package/src/colors.test.ts +28 -0
  19. package/src/colors.ts +486 -0
  20. package/src/core.ts +299 -0
  21. package/src/format.d.ts +54 -0
  22. package/src/format.ts +138 -0
  23. package/src/grammar/accessibility.d.ts +147 -0
  24. package/src/grammar/accessibility.test.ts +199 -0
  25. package/src/grammar/accessibility.ts +443 -0
  26. package/src/grammar/aes.d.ts +35 -0
  27. package/src/grammar/aes.test.ts +75 -0
  28. package/src/grammar/aes.ts +120 -0
  29. package/src/grammar/annotations.d.ts +86 -0
  30. package/src/grammar/annotations.test.ts +68 -0
  31. package/src/grammar/annotations.ts +336 -0
  32. package/src/grammar/attach-brush.d.ts +44 -0
  33. package/src/grammar/attach-brush.test.ts +214 -0
  34. package/src/grammar/attach-brush.ts +111 -0
  35. package/src/grammar/attach-presets.d.ts +33 -0
  36. package/src/grammar/attach-presets.test.ts +106 -0
  37. package/src/grammar/attach-presets.ts +215 -0
  38. package/src/grammar/chart.d.ts +952 -0
  39. package/src/grammar/chart.test.ts +118 -0
  40. package/src/grammar/chart.ts +1172 -0
  41. package/src/grammar/color-utils.d.ts +29 -0
  42. package/src/grammar/color-utils.test.ts +53 -0
  43. package/src/grammar/color-utils.ts +66 -0
  44. package/src/grammar/constants.d.ts +45 -0
  45. package/src/grammar/constants.ts +61 -0
  46. package/src/grammar/coord.d.ts +183 -0
  47. package/src/grammar/coord.test.ts +355 -0
  48. package/src/grammar/coord.ts +619 -0
  49. package/src/grammar/data/pivot.d.ts +57 -0
  50. package/src/grammar/data/pivot.ts +107 -0
  51. package/src/grammar/emphasis-driver.d.ts +69 -0
  52. package/src/grammar/emphasis-driver.test.ts +199 -0
  53. package/src/grammar/emphasis-driver.ts +205 -0
  54. package/src/grammar/equality.d.ts +3 -0
  55. package/src/grammar/equality.ts +40 -0
  56. package/src/grammar/facet.d.ts +63 -0
  57. package/src/grammar/facet.test.ts +60 -0
  58. package/src/grammar/facet.ts +175 -0
  59. package/src/grammar/geoms/_categorical.d.ts +94 -0
  60. package/src/grammar/geoms/_categorical.ts +0 -0
  61. package/src/grammar/geoms/_distribution.d.ts +52 -0
  62. package/src/grammar/geoms/_distribution.ts +125 -0
  63. package/src/grammar/geoms/_mark.d.ts +69 -0
  64. package/src/grammar/geoms/_mark.ts +136 -0
  65. package/src/grammar/geoms/_shape.d.ts +41 -0
  66. package/src/grammar/geoms/_shape.ts +74 -0
  67. package/src/grammar/geoms/aggregate.d.ts +95 -0
  68. package/src/grammar/geoms/aggregate.test.ts +554 -0
  69. package/src/grammar/geoms/aggregate.ts +840 -0
  70. package/src/grammar/geoms/area.d.ts +32 -0
  71. package/src/grammar/geoms/area.test.ts +165 -0
  72. package/src/grammar/geoms/area.ts +578 -0
  73. package/src/grammar/geoms/band.d.ts +27 -0
  74. package/src/grammar/geoms/band.test.ts +57 -0
  75. package/src/grammar/geoms/band.ts +126 -0
  76. package/src/grammar/geoms/bar.d.ts +56 -0
  77. package/src/grammar/geoms/bar.test.ts +367 -0
  78. package/src/grammar/geoms/bar.ts +1054 -0
  79. package/src/grammar/geoms/boxplot.d.ts +129 -0
  80. package/src/grammar/geoms/boxplot.test.ts +299 -0
  81. package/src/grammar/geoms/boxplot.ts +834 -0
  82. package/src/grammar/geoms/connected-scatter.d.ts +27 -0
  83. package/src/grammar/geoms/connected-scatter.test.ts +157 -0
  84. package/src/grammar/geoms/connected-scatter.ts +63 -0
  85. package/src/grammar/geoms/emphasis.d.ts +76 -0
  86. package/src/grammar/geoms/emphasis.test.ts +135 -0
  87. package/src/grammar/geoms/emphasis.ts +162 -0
  88. package/src/grammar/geoms/histogram.d.ts +75 -0
  89. package/src/grammar/geoms/histogram.test.ts +262 -0
  90. package/src/grammar/geoms/histogram.ts +740 -0
  91. package/src/grammar/geoms/index.d.ts +20 -0
  92. package/src/grammar/geoms/index.ts +77 -0
  93. package/src/grammar/geoms/interval.d.ts +31 -0
  94. package/src/grammar/geoms/interval.test.ts +154 -0
  95. package/src/grammar/geoms/interval.ts +342 -0
  96. package/src/grammar/geoms/line.d.ts +38 -0
  97. package/src/grammar/geoms/line.test.ts +247 -0
  98. package/src/grammar/geoms/line.ts +659 -0
  99. package/src/grammar/geoms/point.d.ts +57 -0
  100. package/src/grammar/geoms/point.test.ts +163 -0
  101. package/src/grammar/geoms/point.ts +545 -0
  102. package/src/grammar/geoms/polar.test.ts +216 -0
  103. package/src/grammar/geoms/ribbon.d.ts +21 -0
  104. package/src/grammar/geoms/ribbon.test.ts +170 -0
  105. package/src/grammar/geoms/ribbon.ts +87 -0
  106. package/src/grammar/geoms/ridgeline.d.ts +89 -0
  107. package/src/grammar/geoms/ridgeline.test.ts +247 -0
  108. package/src/grammar/geoms/ridgeline.ts +1164 -0
  109. package/src/grammar/geoms/rolling.d.ts +43 -0
  110. package/src/grammar/geoms/rolling.test.ts +217 -0
  111. package/src/grammar/geoms/rolling.ts +387 -0
  112. package/src/grammar/geoms/rug.d.ts +28 -0
  113. package/src/grammar/geoms/rug.test.ts +126 -0
  114. package/src/grammar/geoms/rug.ts +214 -0
  115. package/src/grammar/geoms/rule.d.ts +23 -0
  116. package/src/grammar/geoms/rule.test.ts +69 -0
  117. package/src/grammar/geoms/rule.ts +212 -0
  118. package/src/grammar/geoms/smooth.d.ts +54 -0
  119. package/src/grammar/geoms/smooth.test.ts +78 -0
  120. package/src/grammar/geoms/smooth.ts +337 -0
  121. package/src/grammar/geoms/text.d.ts +29 -0
  122. package/src/grammar/geoms/text.test.ts +64 -0
  123. package/src/grammar/geoms/text.ts +234 -0
  124. package/src/grammar/geoms/tile.d.ts +61 -0
  125. package/src/grammar/geoms/tile.test.ts +157 -0
  126. package/src/grammar/geoms/tile.ts +621 -0
  127. package/src/grammar/geoms/types.d.ts +319 -0
  128. package/src/grammar/geoms/types.ts +362 -0
  129. package/src/grammar/geoms/violin.d.ts +85 -0
  130. package/src/grammar/geoms/violin.test.ts +187 -0
  131. package/src/grammar/geoms/violin.ts +672 -0
  132. package/src/grammar/index.d.ts +22 -0
  133. package/src/grammar/index.ts +269 -0
  134. package/src/grammar/interactions/_disposable.d.ts +5 -0
  135. package/src/grammar/interactions/_disposable.ts +23 -0
  136. package/src/grammar/interactions/_z.d.ts +4 -0
  137. package/src/grammar/interactions/_z.ts +16 -0
  138. package/src/grammar/interactions/brush-selection.test.ts +262 -0
  139. package/src/grammar/interactions/brush.d.ts +63 -0
  140. package/src/grammar/interactions/brush.test.ts +483 -0
  141. package/src/grammar/interactions/brush.ts +452 -0
  142. package/src/grammar/interactions/crosshair.d.ts +19 -0
  143. package/src/grammar/interactions/crosshair.test.ts +127 -0
  144. package/src/grammar/interactions/crosshair.ts +76 -0
  145. package/src/grammar/interactions/hit-layer.d.ts +64 -0
  146. package/src/grammar/interactions/hit-layer.ts +246 -0
  147. package/src/grammar/interactions/legend.d.ts +19 -0
  148. package/src/grammar/interactions/legend.ts +101 -0
  149. package/src/grammar/interactions/menu.d.ts +93 -0
  150. package/src/grammar/interactions/menu.test.ts +373 -0
  151. package/src/grammar/interactions/menu.ts +342 -0
  152. package/src/grammar/interactions/selection.d.ts +25 -0
  153. package/src/grammar/interactions/selection.test.ts +289 -0
  154. package/src/grammar/interactions/selection.ts +142 -0
  155. package/src/grammar/interactions/series-readout.d.ts +91 -0
  156. package/src/grammar/interactions/series-readout.test.ts +668 -0
  157. package/src/grammar/interactions/series-readout.ts +422 -0
  158. package/src/grammar/interactions/series-snap.d.ts +70 -0
  159. package/src/grammar/interactions/series-snap.test.ts +214 -0
  160. package/src/grammar/interactions/series-snap.ts +218 -0
  161. package/src/grammar/interactions/tooltip-axis.test.ts +176 -0
  162. package/src/grammar/interactions/tooltip-touch.browser.test.ts +49 -0
  163. package/src/grammar/interactions/tooltip-touch.test.ts +161 -0
  164. package/src/grammar/interactions/tooltip.d.ts +140 -0
  165. package/src/grammar/interactions/tooltip.test.ts +406 -0
  166. package/src/grammar/interactions/tooltip.ts +622 -0
  167. package/src/grammar/interactions/transitions.d.ts +34 -0
  168. package/src/grammar/interactions/transitions.test.ts +172 -0
  169. package/src/grammar/interactions/transitions.ts +160 -0
  170. package/src/grammar/layout.d.ts +68 -0
  171. package/src/grammar/layout.ts +186 -0
  172. package/src/grammar/legend-merge.test.ts +332 -0
  173. package/src/grammar/mount.d.ts +78 -0
  174. package/src/grammar/mount.test.ts +479 -0
  175. package/src/grammar/mount.ts +2112 -0
  176. package/src/grammar/palettes.d.ts +54 -0
  177. package/src/grammar/palettes.test.ts +80 -0
  178. package/src/grammar/palettes.ts +167 -0
  179. package/src/grammar/pan-zoom.test.ts +398 -0
  180. package/src/grammar/phylo.d.ts +65 -0
  181. package/src/grammar/phylo.test.ts +59 -0
  182. package/src/grammar/phylo.ts +112 -0
  183. package/src/grammar/pipeline.auto-ticks.test.ts +40 -0
  184. package/src/grammar/pipeline.d.ts +158 -0
  185. package/src/grammar/pipeline.test.ts +463 -0
  186. package/src/grammar/pipeline.ts +1233 -0
  187. package/src/grammar/profiling.d.ts +8 -0
  188. package/src/grammar/profiling.ts +24 -0
  189. package/src/grammar/scales.d.ts +188 -0
  190. package/src/grammar/scales.test.ts +181 -0
  191. package/src/grammar/scales.ts +800 -0
  192. package/src/grammar/svg.d.ts +3 -0
  193. package/src/grammar/svg.ts +39 -0
  194. package/src/grammar/theme.d.ts +261 -0
  195. package/src/grammar/theme.test.ts +105 -0
  196. package/src/grammar/theme.ts +490 -0
  197. package/src/heatmap/cpu.ts +109 -0
  198. package/src/heatmap/gpu.ts +565 -0
  199. package/src/heatmap/types.ts +177 -0
  200. package/src/heatmap.browser.test.ts +308 -0
  201. package/src/heatmap.test.ts +320 -0
  202. package/src/heatmap.ts +123 -0
  203. package/src/index.d.ts +1 -0
  204. package/src/index.ts +8 -0
  205. package/src/interactions.d.ts +48 -0
  206. package/src/interactions.test.ts +226 -0
  207. package/src/interactions.ts +394 -0
  208. package/src/layout/box.d.ts +48 -0
  209. package/src/layout/box.test.ts +107 -0
  210. package/src/layout/box.ts +143 -0
  211. package/src/legend.d.ts +115 -0
  212. package/src/legend.ts +422 -0
  213. package/src/marks/curve.d.ts +43 -0
  214. package/src/marks/curve.ts +244 -0
  215. package/src/marks/stack.d.ts +53 -0
  216. package/src/marks/stack.ts +184 -0
  217. package/src/marks.d.ts +273 -0
  218. package/src/marks.test.ts +541 -0
  219. package/src/marks.ts +1292 -0
  220. package/src/navigator.test.ts +174 -0
  221. package/src/navigator.ts +393 -0
  222. package/src/range-presets.d.ts +113 -0
  223. package/src/range-presets.test.ts +345 -0
  224. package/src/range-presets.ts +349 -0
  225. package/src/scales.d.ts +98 -0
  226. package/src/scales.test.ts +103 -0
  227. package/src/scales.ts +695 -0
  228. package/src/stats/index.d.ts +200 -0
  229. package/src/stats/index.test.ts +349 -0
  230. package/src/stats/index.ts +740 -0
  231. package/src/stats/regression.d.ts +38 -0
  232. package/src/stats/regression.test.ts +56 -0
  233. package/src/stats/regression.ts +396 -0
  234. package/src/stats/rolling-window.d.ts +55 -0
  235. package/src/stats/rolling-window.test.ts +237 -0
  236. package/src/stats/rolling-window.ts +256 -0
  237. package/src/test-setup.ts +19 -0
  238. package/src/viewport/axis-state.d.ts +72 -0
  239. package/src/viewport/axis-state.ts +476 -0
  240. package/src/viewport.d.ts +170 -0
  241. package/src/viewport.test.ts +363 -0
  242. package/src/viewport.ts +510 -0
package/dist/core.mjs ADDED
@@ -0,0 +1,1047 @@
1
+ import { $ as percent, A as valueLabelMark, At as viridis, C as areaSwatch, Ct as rdbu, D as pointSwatch, Dt as set3, E as lineSwatch, Et as set2, F as barMark, Ft as truncateToWidth, G as defineCurve, H as stackedBarMark, Ht as timeScale, I as categoricalBarMark, It as bandScale, K as resamplePoints, L as groupedBarMark, Lt as groupedBandScale, M as DOTTED_PATTERN, Mt as leftAxis, Nt as rightAxis, O as bandMark, Ot as spectral, P as areaMark, Pt as topAxis, Q as fixed, R as lineMark, Rt as linearScale, S as polyFit, St as purples, T as legend, Tt as set1, U as stack, Ut as timeTickFormat, V as stackedAreaMark, Vt as sqrtScale, W as cardinalCurve, Wt as timeTicks, Z as currency, _ as quantile, _t as pastel, a as linearPreset, at as category10, b as linearFit, bt as prgn, c as bin, ct as continuousPalette, d as binWithBreaks, dt as greens, et as siNumber, f as boxStats, ft as greys, g as kde, gt as paired, h as jitter, ht as oranges, i as createRangePresets, it as categoricalPalette, j as DASHED_PATTERN, jt as bottomAxis, k as ruleMark, kt as tableau10, l as binBreaks, lt as coolwarm, m as histogramMeasureValue, mt as magma, n as createDataViewport, nt as blues, o as logPreset, ot as cividis, p as groupBy, pt as inferno, q as colorBar, r as linkViewports, rt as brbg, s as timePreset, st as colorScale, t as bindDataViewport, tt as accent, u as binBy, ut as dark2, v as rollingWindow, vt as piyg, w as barSwatch, wt as reds, x as loessFit, xt as puor, y as confidenceBand, yt as plasma, z as pointMark, zt as logScale } from "./interactions-DEFL_F4E.mjs";
2
+ import { Texture, createLayer, rgba } from "insomni";
3
+ import { resolveRoot } from "insomni/internal";
4
+ //#region src/heatmap/types.ts
5
+ function resolveSpec(spec) {
6
+ const [nx, ny] = spec.bins;
7
+ if (!Number.isInteger(nx) || !Number.isInteger(ny) || nx < 1 || ny < 1) throw new Error("heatmapLayer: `bins` entries must be positive integers.");
8
+ const weightScale = spec.weightScale ?? 1e6;
9
+ if (!Number.isFinite(weightScale) || weightScale <= 0) throw new Error("heatmapLayer: `weightScale` must be a positive finite number.");
10
+ const viewport = spec.viewport ?? null;
11
+ const xDomain = spec.xDomain ?? (viewport ? viewport.visibleXDomain : null);
12
+ const yDomain = spec.yDomain ?? (viewport ? viewport.visibleYDomain : null);
13
+ const frame = spec.frame ?? viewport?.frame ?? null;
14
+ if (!xDomain || !yDomain) throw new Error("heatmapLayer: `xDomain` and `yDomain` are required when no `viewport` is provided.");
15
+ if (!frame) throw new Error("heatmapLayer: `frame` is required when no `viewport` is provided.");
16
+ return {
17
+ x: spec.x,
18
+ y: spec.y,
19
+ weight: spec.weight ?? (() => 1),
20
+ nx,
21
+ ny,
22
+ x0: xDomain[0],
23
+ x1: xDomain[1],
24
+ y0: yDomain[0],
25
+ y1: yDomain[1],
26
+ frame,
27
+ viewport,
28
+ svgExport: spec.svgExport ?? "auto",
29
+ colorMap: spec.colorMap,
30
+ weightScale,
31
+ interpolation: spec.interpolation ?? "nearest",
32
+ normalize: spec.normalize ?? "max"
33
+ };
34
+ }
35
+ /**
36
+ * Apply the configured normalization to a raw bin value, given the grid's
37
+ * `max`. Returns a `[0, 1]` parameter for palette sampling.
38
+ */
39
+ function normalizeValue(value, max, mode) {
40
+ if (max <= 0 || value <= 0) return 0;
41
+ if (mode === "max") return value / max;
42
+ if (mode === "sqrt") return Math.sqrt(value / max);
43
+ return Math.log1p(value) / Math.log1p(max);
44
+ }
45
+ function clamp01(v) {
46
+ if (v < 0) return 0;
47
+ if (v > 1) return 1;
48
+ return v;
49
+ }
50
+ function nextPow2(n) {
51
+ let p = 1;
52
+ while (p < n) p <<= 1;
53
+ return p;
54
+ }
55
+ //#endregion
56
+ //#region src/heatmap/cpu.ts
57
+ var HeatmapCpuRenderer = class {
58
+ spec;
59
+ data;
60
+ bins;
61
+ max = 0;
62
+ dirty = true;
63
+ constructor(spec, data) {
64
+ this.spec = spec;
65
+ this.data = data;
66
+ this.bins = new Float64Array(spec.nx * spec.ny);
67
+ }
68
+ setData(data) {
69
+ this.data = data;
70
+ this.dirty = true;
71
+ }
72
+ markDirty() {
73
+ this.dirty = true;
74
+ }
75
+ /** Largest bin weight after the most recent rebin. Useful for legend domains. */
76
+ getMax() {
77
+ if (this.dirty) this.rebin();
78
+ return this.max;
79
+ }
80
+ build() {
81
+ if (this.dirty) this.rebin();
82
+ const { nx, ny, frame, colorMap, normalize } = this.spec;
83
+ const max = this.max;
84
+ const layer = createLayer({ space: "ui" });
85
+ if (max <= 0) return [layer];
86
+ const cellW = frame.width / nx;
87
+ const cellH = frame.height / ny;
88
+ for (let iy = 0; iy < ny; iy++) for (let ix = 0; ix < nx; ix++) {
89
+ const v = this.bins[iy * nx + ix];
90
+ if (v <= 0) continue;
91
+ layer.pushRect({
92
+ x: frame.x + ix * cellW,
93
+ y: frame.y + iy * cellH,
94
+ width: cellW,
95
+ height: cellH,
96
+ fill: colorMap(normalizeValue(v, max, normalize))
97
+ });
98
+ }
99
+ return [layer];
100
+ }
101
+ rebin() {
102
+ const spec = this.spec;
103
+ const { nx, ny, x0, x1, y0, y1 } = spec;
104
+ const dx = x1 - x0;
105
+ const dy = y1 - y0;
106
+ const bins = this.bins;
107
+ bins.fill(0);
108
+ if (dx === 0 || dy === 0) {
109
+ this.max = 0;
110
+ this.dirty = false;
111
+ return;
112
+ }
113
+ let max = 0;
114
+ for (const datum of this.data) {
115
+ const xv = spec.x(datum);
116
+ const yv = spec.y(datum);
117
+ const tx = (xv - x0) / dx;
118
+ const ty = (yv - y0) / dy;
119
+ if (tx < 0 || tx >= 1 || ty < 0 || ty >= 1) continue;
120
+ const ix = Math.min(nx - 1, Math.floor(tx * nx));
121
+ const idx = (ny - 1 - Math.min(ny - 1, Math.floor(ty * ny))) * nx + ix;
122
+ const w = spec.weight(datum);
123
+ const next = bins[idx] + w;
124
+ bins[idx] = next;
125
+ if (next > max) max = next;
126
+ }
127
+ this.max = max;
128
+ this.dirty = false;
129
+ }
130
+ };
131
+ //#endregion
132
+ //#region src/heatmap/gpu.ts
133
+ var HeatmapGpuRenderer = class {
134
+ spec;
135
+ data;
136
+ state = null;
137
+ dirty = true;
138
+ constructor(spec, data) {
139
+ this.spec = spec;
140
+ this.data = data;
141
+ }
142
+ setData(data) {
143
+ this.data = data;
144
+ this.dirty = true;
145
+ }
146
+ markDirty() {
147
+ this.dirty = true;
148
+ }
149
+ /**
150
+ * Build (or refresh) the GPU heatmap and return the drawable that renders it.
151
+ *
152
+ * `root` owns the GPU buffers/textures/pipelines; `renderer` is the v3
153
+ * {@link Renderer2D} whose `compute(cb)` seam the binning passes are recorded
154
+ * onto. The returned {@link Layer} is a `space:"ui"` layer with a single sprite
155
+ * pointing at the compute-written output texture, drawn in submission order
156
+ * (the same post-main slot as before). The sprite layer is cleared and
157
+ * re-pushed each frame so the pack content naturally changes when data is
158
+ * dirty — no separate `bumpVersion` needed.
159
+ *
160
+ * The compute callback is queued (not submitted) here; it runs when the
161
+ * caller invokes `renderer.render(...)`, immediately before the render pass,
162
+ * so the colormap output is visible to this layer's draw in the same frame.
163
+ */
164
+ build(root, renderer) {
165
+ const state = this.ensureState(root);
166
+ this.updateSprite(state);
167
+ if (this.dirty) {
168
+ this.uploadDataBuffer(state);
169
+ renderer.compute((encoder) => {
170
+ const pass = encoder.beginComputePass({ label: "heatmap-pass" });
171
+ this.runPasses(state, pass);
172
+ pass.end();
173
+ });
174
+ this.dirty = false;
175
+ }
176
+ return state.spriteLayer;
177
+ }
178
+ destroy() {
179
+ if (!this.state) return;
180
+ this.state.dataBuffer.destroy();
181
+ this.state.binBuffer.destroy();
182
+ this.state.maxBuffer.destroy();
183
+ this.state.paramsBuffer.destroy();
184
+ this.state.lutTexture.destroy();
185
+ this.state.outputWrapper.destroy();
186
+ this.state = null;
187
+ }
188
+ ensureState(root) {
189
+ if (this.state) return this.state;
190
+ const spec = this.spec;
191
+ const device = root.device;
192
+ const nx = spec.nx;
193
+ const ny = spec.ny;
194
+ const binBuffer = device.createBuffer({
195
+ label: "heatmap-bins",
196
+ size: Math.max(nx * ny * 4, 16),
197
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
198
+ });
199
+ const maxBuffer = device.createBuffer({
200
+ label: "heatmap-max",
201
+ size: 16,
202
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
203
+ });
204
+ const paramsBuffer = device.createBuffer({
205
+ label: "heatmap-params",
206
+ size: 48,
207
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
208
+ });
209
+ const initialCapacity = Math.max(1, this.data.length);
210
+ const dataBuffer = device.createBuffer({
211
+ label: "heatmap-data",
212
+ size: initialCapacity * 16,
213
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
214
+ });
215
+ const lutTexture = device.createTexture({
216
+ label: "heatmap-lut",
217
+ size: [
218
+ 256,
219
+ 1,
220
+ 1
221
+ ],
222
+ format: "rgba8unorm",
223
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST
224
+ });
225
+ const lutView = lutTexture.createView();
226
+ const outputTexture = device.createTexture({
227
+ label: "heatmap-output",
228
+ size: [
229
+ nx,
230
+ ny,
231
+ 1
232
+ ],
233
+ format: "rgba8unorm",
234
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST
235
+ });
236
+ const outputView = outputTexture.createView();
237
+ const outputWrapper = new Texture(outputTexture, nx, ny);
238
+ const clearModule = device.createShaderModule({
239
+ label: "heatmap-clear",
240
+ code: CLEAR_WGSL
241
+ });
242
+ const splatModule = device.createShaderModule({
243
+ label: "heatmap-splat",
244
+ code: SPLAT_WGSL
245
+ });
246
+ const reduceModule = device.createShaderModule({
247
+ label: "heatmap-reduce",
248
+ code: REDUCE_WGSL
249
+ });
250
+ const colormapModule = device.createShaderModule({
251
+ label: "heatmap-colormap",
252
+ code: COLORMAP_WGSL
253
+ });
254
+ const clearLayout = device.createBindGroupLayout({
255
+ label: "heatmap-clear",
256
+ entries: [
257
+ {
258
+ binding: 0,
259
+ visibility: GPUShaderStage.COMPUTE,
260
+ buffer: { type: "storage" }
261
+ },
262
+ {
263
+ binding: 1,
264
+ visibility: GPUShaderStage.COMPUTE,
265
+ buffer: { type: "storage" }
266
+ },
267
+ {
268
+ binding: 2,
269
+ visibility: GPUShaderStage.COMPUTE,
270
+ buffer: { type: "uniform" }
271
+ }
272
+ ]
273
+ });
274
+ const splatLayout = device.createBindGroupLayout({
275
+ label: "heatmap-splat",
276
+ entries: [
277
+ {
278
+ binding: 0,
279
+ visibility: GPUShaderStage.COMPUTE,
280
+ buffer: { type: "read-only-storage" }
281
+ },
282
+ {
283
+ binding: 1,
284
+ visibility: GPUShaderStage.COMPUTE,
285
+ buffer: { type: "storage" }
286
+ },
287
+ {
288
+ binding: 2,
289
+ visibility: GPUShaderStage.COMPUTE,
290
+ buffer: { type: "uniform" }
291
+ }
292
+ ]
293
+ });
294
+ const reduceLayout = device.createBindGroupLayout({
295
+ label: "heatmap-reduce",
296
+ entries: [
297
+ {
298
+ binding: 0,
299
+ visibility: GPUShaderStage.COMPUTE,
300
+ buffer: { type: "storage" }
301
+ },
302
+ {
303
+ binding: 1,
304
+ visibility: GPUShaderStage.COMPUTE,
305
+ buffer: { type: "storage" }
306
+ },
307
+ {
308
+ binding: 2,
309
+ visibility: GPUShaderStage.COMPUTE,
310
+ buffer: { type: "uniform" }
311
+ }
312
+ ]
313
+ });
314
+ const colormapLayout = device.createBindGroupLayout({
315
+ label: "heatmap-colormap",
316
+ entries: [
317
+ {
318
+ binding: 0,
319
+ visibility: GPUShaderStage.COMPUTE,
320
+ buffer: { type: "storage" }
321
+ },
322
+ {
323
+ binding: 1,
324
+ visibility: GPUShaderStage.COMPUTE,
325
+ buffer: { type: "storage" }
326
+ },
327
+ {
328
+ binding: 2,
329
+ visibility: GPUShaderStage.COMPUTE,
330
+ buffer: { type: "uniform" }
331
+ },
332
+ {
333
+ binding: 3,
334
+ visibility: GPUShaderStage.COMPUTE,
335
+ texture: {
336
+ sampleType: "float",
337
+ viewDimension: "2d"
338
+ }
339
+ },
340
+ {
341
+ binding: 4,
342
+ visibility: GPUShaderStage.COMPUTE,
343
+ storageTexture: {
344
+ access: "write-only",
345
+ format: "rgba8unorm",
346
+ viewDimension: "2d"
347
+ }
348
+ }
349
+ ]
350
+ });
351
+ const clearPipeline = device.createComputePipeline({
352
+ label: "heatmap-clear-pipeline",
353
+ layout: device.createPipelineLayout({ bindGroupLayouts: [clearLayout] }),
354
+ compute: {
355
+ module: clearModule,
356
+ entryPoint: "main"
357
+ }
358
+ });
359
+ const splatPipeline = device.createComputePipeline({
360
+ label: "heatmap-splat-pipeline",
361
+ layout: device.createPipelineLayout({ bindGroupLayouts: [splatLayout] }),
362
+ compute: {
363
+ module: splatModule,
364
+ entryPoint: "main"
365
+ }
366
+ });
367
+ const reducePipeline = device.createComputePipeline({
368
+ label: "heatmap-reduce-pipeline",
369
+ layout: device.createPipelineLayout({ bindGroupLayouts: [reduceLayout] }),
370
+ compute: {
371
+ module: reduceModule,
372
+ entryPoint: "main"
373
+ }
374
+ });
375
+ const colormapPipeline = device.createComputePipeline({
376
+ label: "heatmap-colormap-pipeline",
377
+ layout: device.createPipelineLayout({ bindGroupLayouts: [colormapLayout] }),
378
+ compute: {
379
+ module: colormapModule,
380
+ entryPoint: "main"
381
+ }
382
+ });
383
+ const clearBindGroup = device.createBindGroup({
384
+ layout: clearLayout,
385
+ entries: [
386
+ {
387
+ binding: 0,
388
+ resource: { buffer: binBuffer }
389
+ },
390
+ {
391
+ binding: 1,
392
+ resource: { buffer: maxBuffer }
393
+ },
394
+ {
395
+ binding: 2,
396
+ resource: { buffer: paramsBuffer }
397
+ }
398
+ ]
399
+ });
400
+ const splatBindGroup = device.createBindGroup({
401
+ layout: splatLayout,
402
+ entries: [
403
+ {
404
+ binding: 0,
405
+ resource: { buffer: dataBuffer }
406
+ },
407
+ {
408
+ binding: 1,
409
+ resource: { buffer: binBuffer }
410
+ },
411
+ {
412
+ binding: 2,
413
+ resource: { buffer: paramsBuffer }
414
+ }
415
+ ]
416
+ });
417
+ const reduceBindGroup = device.createBindGroup({
418
+ layout: reduceLayout,
419
+ entries: [
420
+ {
421
+ binding: 0,
422
+ resource: { buffer: binBuffer }
423
+ },
424
+ {
425
+ binding: 1,
426
+ resource: { buffer: maxBuffer }
427
+ },
428
+ {
429
+ binding: 2,
430
+ resource: { buffer: paramsBuffer }
431
+ }
432
+ ]
433
+ });
434
+ const colormapBindGroup = device.createBindGroup({
435
+ layout: colormapLayout,
436
+ entries: [
437
+ {
438
+ binding: 0,
439
+ resource: { buffer: binBuffer }
440
+ },
441
+ {
442
+ binding: 1,
443
+ resource: { buffer: maxBuffer }
444
+ },
445
+ {
446
+ binding: 2,
447
+ resource: { buffer: paramsBuffer }
448
+ },
449
+ {
450
+ binding: 3,
451
+ resource: lutView
452
+ },
453
+ {
454
+ binding: 4,
455
+ resource: outputView
456
+ }
457
+ ]
458
+ });
459
+ writeLut(device, lutTexture, spec.colorMap);
460
+ writeParams(device, paramsBuffer, spec, this.data.length);
461
+ this.state = {
462
+ root,
463
+ device,
464
+ dataBuffer,
465
+ dataCapacity: initialCapacity,
466
+ binBuffer,
467
+ maxBuffer,
468
+ paramsBuffer,
469
+ lutTexture,
470
+ lutView,
471
+ outputTexture,
472
+ outputView,
473
+ outputWrapper,
474
+ clearPipeline,
475
+ splatPipeline,
476
+ reducePipeline,
477
+ colormapPipeline,
478
+ splatLayout,
479
+ clearBindGroup,
480
+ splatBindGroup,
481
+ reduceBindGroup,
482
+ colormapBindGroup,
483
+ spriteLayer: createLayer({ space: "ui" }),
484
+ dataCount: this.data.length
485
+ };
486
+ return this.state;
487
+ }
488
+ uploadDataBuffer(state) {
489
+ const spec = this.spec;
490
+ const n = this.data.length;
491
+ state.dataCount = n;
492
+ if (n > state.dataCapacity) {
493
+ state.dataBuffer.destroy();
494
+ const next = nextPow2(n);
495
+ state.dataBuffer = state.device.createBuffer({
496
+ label: "heatmap-data",
497
+ size: next * 16,
498
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
499
+ });
500
+ state.dataCapacity = next;
501
+ state.splatBindGroup = state.device.createBindGroup({
502
+ layout: state.splatLayout,
503
+ entries: [
504
+ {
505
+ binding: 0,
506
+ resource: { buffer: state.dataBuffer }
507
+ },
508
+ {
509
+ binding: 1,
510
+ resource: { buffer: state.binBuffer }
511
+ },
512
+ {
513
+ binding: 2,
514
+ resource: { buffer: state.paramsBuffer }
515
+ }
516
+ ]
517
+ });
518
+ }
519
+ if (n > 0) {
520
+ const packed = new Float32Array(n * 4);
521
+ for (let i = 0; i < n; i++) {
522
+ const datum = this.data[i];
523
+ packed[i * 4 + 0] = spec.x(datum);
524
+ packed[i * 4 + 1] = spec.y(datum);
525
+ packed[i * 4 + 2] = spec.weight(datum);
526
+ packed[i * 4 + 3] = 0;
527
+ }
528
+ state.device.queue.writeBuffer(state.dataBuffer, 0, packed);
529
+ }
530
+ writeParams(state.device, state.paramsBuffer, spec, n);
531
+ }
532
+ updateSprite(state) {
533
+ const spec = this.spec;
534
+ state.spriteLayer.clear();
535
+ state.spriteLayer.pushSprite({
536
+ pos: {
537
+ x: spec.frame.x + spec.frame.width * .5,
538
+ y: spec.frame.y + spec.frame.height * .5
539
+ },
540
+ size: {
541
+ x: spec.frame.width,
542
+ y: spec.frame.height
543
+ },
544
+ texture: state.outputWrapper,
545
+ anchor: {
546
+ x: .5,
547
+ y: .5
548
+ }
549
+ });
550
+ }
551
+ runPasses(state, pass) {
552
+ const spec = this.spec;
553
+ const cellCount = spec.nx * spec.ny;
554
+ pass.setPipeline(state.clearPipeline);
555
+ pass.setBindGroup(0, state.clearBindGroup);
556
+ pass.dispatchWorkgroups(Math.ceil(cellCount / 256));
557
+ if (state.dataCount > 0) {
558
+ pass.setPipeline(state.splatPipeline);
559
+ pass.setBindGroup(0, state.splatBindGroup);
560
+ pass.dispatchWorkgroups(Math.ceil(state.dataCount / 256));
561
+ }
562
+ pass.setPipeline(state.reducePipeline);
563
+ pass.setBindGroup(0, state.reduceBindGroup);
564
+ pass.dispatchWorkgroups(1);
565
+ pass.setPipeline(state.colormapPipeline);
566
+ pass.setBindGroup(0, state.colormapBindGroup);
567
+ pass.dispatchWorkgroups(Math.ceil(spec.nx / 8), Math.ceil(spec.ny / 8));
568
+ }
569
+ };
570
+ function writeParams(device, buffer, spec, count) {
571
+ const bytes = /* @__PURE__ */ new ArrayBuffer(48);
572
+ const u32 = new Uint32Array(bytes);
573
+ const f32 = new Float32Array(bytes);
574
+ u32[0] = spec.nx;
575
+ u32[1] = spec.ny;
576
+ u32[2] = count;
577
+ f32[3] = spec.weightScale;
578
+ f32[4] = spec.x0;
579
+ f32[5] = spec.x1;
580
+ f32[6] = spec.y0;
581
+ f32[7] = spec.y1;
582
+ u32[8] = spec.nx * spec.ny;
583
+ u32[9] = spec.normalize === "log" ? 1 : spec.normalize === "sqrt" ? 2 : 0;
584
+ device.queue.writeBuffer(buffer, 0, bytes);
585
+ }
586
+ function writeLut(device, texture, palette) {
587
+ const bytes = new Uint8Array(256 * 4);
588
+ for (let i = 0; i < 256; i++) {
589
+ const c = palette(i / 255);
590
+ const a = clamp01(c.a);
591
+ bytes[i * 4 + 0] = Math.round(clamp01(c.r) * a * 255);
592
+ bytes[i * 4 + 1] = Math.round(clamp01(c.g) * a * 255);
593
+ bytes[i * 4 + 2] = Math.round(clamp01(c.b) * a * 255);
594
+ bytes[i * 4 + 3] = Math.round(a * 255);
595
+ }
596
+ device.queue.writeTexture({ texture }, bytes, {
597
+ bytesPerRow: 256 * 4,
598
+ rowsPerImage: 1
599
+ }, [
600
+ 256,
601
+ 1,
602
+ 1
603
+ ]);
604
+ }
605
+ const PARAMS_WGSL = `
606
+ struct Params {
607
+ bins: vec2u,
608
+ count: u32,
609
+ weightScale: f32,
610
+ xDomain: vec2f,
611
+ yDomain: vec2f,
612
+ cellCount: u32,
613
+ normalize: u32, // 0=max, 1=log, 2=sqrt
614
+ };
615
+ `;
616
+ const CLEAR_WGSL = `${PARAMS_WGSL}
617
+ @group(0) @binding(0) var<storage, read_write> bins: array<atomic<i32>>;
618
+ @group(0) @binding(1) var<storage, read_write> maxCell: array<atomic<i32>, 1>;
619
+ @group(0) @binding(2) var<uniform> params: Params;
620
+
621
+ @compute @workgroup_size(256)
622
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
623
+ let i = gid.x;
624
+ if (i >= params.cellCount) {
625
+ return;
626
+ }
627
+ atomicStore(&bins[i], 0);
628
+ if (i == 0u) {
629
+ atomicStore(&maxCell[0], 0);
630
+ }
631
+ }
632
+ `;
633
+ const SPLAT_WGSL = `${PARAMS_WGSL}
634
+ @group(0) @binding(0) var<storage, read> data: array<vec4f>;
635
+ @group(0) @binding(1) var<storage, read_write> bins: array<atomic<i32>>;
636
+ @group(0) @binding(2) var<uniform> params: Params;
637
+
638
+ @compute @workgroup_size(256)
639
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
640
+ let i = gid.x;
641
+ if (i >= params.count) {
642
+ return;
643
+ }
644
+ let datum = data[i];
645
+ let xv = datum.x;
646
+ let yv = datum.y;
647
+ let w = datum.z;
648
+
649
+ let dx = params.xDomain.y - params.xDomain.x;
650
+ let dy = params.yDomain.y - params.yDomain.x;
651
+ if (dx == 0.0 || dy == 0.0) {
652
+ return;
653
+ }
654
+ let tx = (xv - params.xDomain.x) / dx;
655
+ let ty = (yv - params.yDomain.x) / dy;
656
+ if (tx < 0.0 || tx >= 1.0 || ty < 0.0 || ty >= 1.0) {
657
+ return;
658
+ }
659
+ let nx = params.bins.x;
660
+ let ny = params.bins.y;
661
+ let ix = min(nx - 1u, u32(tx * f32(nx)));
662
+ // Flip iy: row 0 represents the *top* of the frame (highest data-y).
663
+ let iy = ny - 1u - min(ny - 1u, u32(ty * f32(ny)));
664
+ let idx = iy * nx + ix;
665
+ let quant = i32(round(w * params.weightScale));
666
+ atomicAdd(&bins[idx], quant);
667
+ }
668
+ `;
669
+ const REDUCE_WGSL = `${PARAMS_WGSL}
670
+ @group(0) @binding(0) var<storage, read_write> bins: array<atomic<i32>>;
671
+ @group(0) @binding(1) var<storage, read_write> maxCell: array<atomic<i32>, 1>;
672
+ @group(0) @binding(2) var<uniform> params: Params;
673
+
674
+ @compute @workgroup_size(256)
675
+ fn main(@builtin(local_invocation_id) lid: vec3u) {
676
+ let threads = 256u;
677
+ let total = params.cellCount;
678
+ var localMax: i32 = 0;
679
+ var i = lid.x;
680
+ loop {
681
+ if (i >= total) {
682
+ break;
683
+ }
684
+ let v = atomicLoad(&bins[i]);
685
+ if (v > localMax) {
686
+ localMax = v;
687
+ }
688
+ i = i + threads;
689
+ }
690
+ atomicMax(&maxCell[0], localMax);
691
+ }
692
+ `;
693
+ const COLORMAP_WGSL = `${PARAMS_WGSL}
694
+ @group(0) @binding(0) var<storage, read_write> bins: array<atomic<i32>>;
695
+ @group(0) @binding(1) var<storage, read_write> maxCell: array<atomic<i32>, 1>;
696
+ @group(0) @binding(2) var<uniform> params: Params;
697
+ @group(0) @binding(3) var lut: texture_2d<f32>;
698
+ @group(0) @binding(4) var outputTex: texture_storage_2d<rgba8unorm, write>;
699
+
700
+ @compute @workgroup_size(8, 8)
701
+ fn main(@builtin(global_invocation_id) gid: vec3u) {
702
+ let nx = params.bins.x;
703
+ let ny = params.bins.y;
704
+ if (gid.x >= nx || gid.y >= ny) {
705
+ return;
706
+ }
707
+ let idx = gid.y * nx + gid.x;
708
+ let raw = atomicLoad(&bins[idx]);
709
+ let maxRaw = atomicLoad(&maxCell[0]);
710
+ var t: f32 = 0.0;
711
+ if (maxRaw > 0) {
712
+ let fraw = f32(raw);
713
+ let fmax = f32(maxRaw);
714
+ if (params.normalize == 1u) {
715
+ // log: log(1+v) / log(1+max)
716
+ t = clamp(log(1.0 + fraw) / log(1.0 + fmax), 0.0, 1.0);
717
+ } else if (params.normalize == 2u) {
718
+ // sqrt
719
+ t = clamp(sqrt(fraw / fmax), 0.0, 1.0);
720
+ } else {
721
+ // max (default)
722
+ t = clamp(fraw / fmax, 0.0, 1.0);
723
+ }
724
+ }
725
+ let lutWidth = 256;
726
+ let u = i32(round(t * f32(lutWidth - 1)));
727
+ let color = textureLoad(lut, vec2i(u, 0), 0);
728
+ textureStore(outputTex, vec2i(i32(gid.x), i32(gid.y)), color);
729
+ }
730
+ `;
731
+ //#endregion
732
+ //#region src/heatmap.ts
733
+ function heatmapLayer(data, spec) {
734
+ return new HeatmapProducerImpl(data, spec);
735
+ }
736
+ var HeatmapProducerImpl = class {
737
+ resolved;
738
+ cpu;
739
+ gpu;
740
+ unsubscribeViewport = null;
741
+ constructor(data, spec) {
742
+ this.resolved = resolveSpec(spec);
743
+ this.cpu = new HeatmapCpuRenderer(this.resolved, data);
744
+ this.gpu = new HeatmapGpuRenderer(this.resolved, data);
745
+ if (this.resolved.viewport) this.unsubscribeViewport = this.resolved.viewport.onChange(() => {
746
+ this.cpu.markDirty();
747
+ this.gpu.markDirty();
748
+ });
749
+ }
750
+ setData(data) {
751
+ this.cpu.setData(data);
752
+ this.gpu.setData(data);
753
+ }
754
+ getMax() {
755
+ this.syncFromViewport();
756
+ return this.cpu.getMax();
757
+ }
758
+ buildCPU() {
759
+ this.syncFromViewport();
760
+ return this.cpu.build();
761
+ }
762
+ buildGPU(opts) {
763
+ this.syncFromViewport();
764
+ const root = resolveRoot(opts.renderer.device);
765
+ return this.gpu.build(root, opts.renderer);
766
+ }
767
+ destroy() {
768
+ if (this.unsubscribeViewport) {
769
+ this.unsubscribeViewport();
770
+ this.unsubscribeViewport = null;
771
+ }
772
+ this.gpu.destroy();
773
+ }
774
+ /** Pull current visible domain + frame off the viewport, if one is attached. */
775
+ syncFromViewport() {
776
+ const vp = this.resolved.viewport;
777
+ if (!vp) return;
778
+ const xd = vp.visibleXDomain;
779
+ const yd = vp.visibleYDomain;
780
+ this.resolved.x0 = xd[0];
781
+ this.resolved.x1 = xd[1];
782
+ this.resolved.y0 = yd[0];
783
+ this.resolved.y1 = yd[1];
784
+ this.resolved.frame = vp.frame;
785
+ }
786
+ };
787
+ //#endregion
788
+ //#region src/navigator.ts
789
+ const DEFAULT_INDICATOR_FILL = rgba(1, 1, 1, .08);
790
+ const DEFAULT_INDICATOR_STROKE = rgba(1, 1, 1, .85);
791
+ /**
792
+ * Build a Navigator for a data viewport: a secondary view of the same data
793
+ * with a rect that tracks the source's visible domain. Drag inside the
794
+ * overview pans/reframes the source.
795
+ */
796
+ function createDataNavigator(opts) {
797
+ const source = opts.source;
798
+ const overview = opts.overview;
799
+ const axes = opts.axes ?? "xy";
800
+ const renderFn = opts.render;
801
+ const cache = opts.cache;
802
+ const interactionState = { active: false };
803
+ const target = createLayer({
804
+ space: "ui",
805
+ ...cache ? { cache: "always" } : {}
806
+ });
807
+ const indicator = createLayer({ space: "ui" });
808
+ const style = resolveIndicatorStyle(opts.indicator);
809
+ const refreshContent = () => {
810
+ target.clear();
811
+ renderFn(target);
812
+ if (cache) cache.renderer.cacheLayer(target);
813
+ };
814
+ const refreshIndicator = () => {
815
+ indicator.clear();
816
+ const rect = computeDataIndicatorRect(source, overview, axes);
817
+ if (!rect) return;
818
+ indicator.pushRect({
819
+ x: rect.x,
820
+ y: rect.y,
821
+ width: rect.width,
822
+ height: rect.height,
823
+ fill: style.fill,
824
+ stroke: style.stroke,
825
+ strokeWidth: style.strokeWidth,
826
+ cornerRadius: style.cornerRadius
827
+ });
828
+ };
829
+ const refresh = () => {
830
+ refreshContent();
831
+ refreshIndicator();
832
+ };
833
+ refresh();
834
+ const unsubSource = source.onChange(refreshIndicator);
835
+ const unsubOverview = overview.onChange(refresh);
836
+ const node = opts.interaction ? bindDataInteraction(opts.interaction, source, overview, axes, interactionState) : null;
837
+ let disposed = false;
838
+ return {
839
+ layers: [target, indicator],
840
+ get interacting() {
841
+ return interactionState.active;
842
+ },
843
+ refresh,
844
+ dispose() {
845
+ if (disposed) return;
846
+ disposed = true;
847
+ unsubSource();
848
+ unsubOverview();
849
+ node?.destroy();
850
+ target.destroy();
851
+ indicator.destroy();
852
+ }
853
+ };
854
+ }
855
+ function bindDataInteraction(opts, source, overview, axes, interactionState) {
856
+ const dragEnabled = opts.drag ?? true;
857
+ const clickMode = opts.click ?? "center";
858
+ const useX = axes === "xy" || axes === "x";
859
+ const useY = axes === "xy" || axes === "y";
860
+ const resizeEnabled = opts.resizeEdges ?? axes !== "xy";
861
+ const edgeHitPx = opts.edgeHitPx ?? 6;
862
+ let mode = "pan";
863
+ let startDataX = 0;
864
+ let startDataY = 0;
865
+ let startSrcX0 = 0;
866
+ let startSrcX1 = 0;
867
+ let startSrcY0 = 0;
868
+ let startSrcY1 = 0;
869
+ let dragHasX = false;
870
+ let dragHasY = false;
871
+ let dragging = false;
872
+ const detectEdge = (evt) => {
873
+ if (!resizeEnabled) return null;
874
+ const r = computeDataIndicatorRect(source, overview, axes);
875
+ if (!r) return null;
876
+ if (axes === "x") {
877
+ if (Math.abs(evt.x - r.x) <= edgeHitPx) return "resize-x0";
878
+ if (Math.abs(evt.x - (r.x + r.width)) <= edgeHitPx) return "resize-x1";
879
+ return null;
880
+ }
881
+ if (axes === "y") {
882
+ if (Math.abs(evt.y - r.y) <= edgeHitPx) return "resize-y0";
883
+ if (Math.abs(evt.y - (r.y + r.height)) <= edgeHitPx) return "resize-y1";
884
+ return null;
885
+ }
886
+ return null;
887
+ };
888
+ return opts.manager.add({
889
+ zIndex: opts.zIndex ?? 50,
890
+ space: "ui",
891
+ bounds: opts.bounds ?? (() => overview.absoluteFrame),
892
+ cursor: dragEnabled ? "grab" : "pointer",
893
+ dragCursor: "grabbing",
894
+ onPress: (evt) => {
895
+ const edge = detectEdge(evt);
896
+ if (edge) {
897
+ mode = edge;
898
+ return;
899
+ }
900
+ const indRect = computeDataIndicatorRect(source, overview, axes);
901
+ if (indRect) {
902
+ if (evt.x >= indRect.x && evt.x <= indRect.x + indRect.width && evt.y >= indRect.y && evt.y <= indRect.y + indRect.height) {
903
+ mode = "pan";
904
+ return;
905
+ }
906
+ }
907
+ mode = "pan";
908
+ if (clickMode !== "center") return;
909
+ const data = overview.screenToData(evt.x, evt.y);
910
+ if (!data) return;
911
+ const update = {};
912
+ if (useX) {
913
+ const span = visibleNumericSpan(source, "x");
914
+ if (span !== null) {
915
+ const c = toNumber(data.x);
916
+ update.x = [c - span / 2, c + span / 2];
917
+ }
918
+ }
919
+ if (useY) {
920
+ const span = visibleNumericSpan(source, "y");
921
+ if (span !== null) {
922
+ const c = toNumber(data.y);
923
+ update.y = [c - span / 2, c + span / 2];
924
+ }
925
+ }
926
+ source.setVisibleDomain(update);
927
+ },
928
+ onDragStart: dragEnabled ? (evt) => {
929
+ const data = overview.screenToData(evt.x, evt.y);
930
+ if (!data) return;
931
+ startDataX = toNumber(data.x);
932
+ startDataY = toNumber(data.y);
933
+ dragHasX = false;
934
+ dragHasY = false;
935
+ if (useX) {
936
+ const dom = source.visibleXDomain;
937
+ if (Array.isArray(dom) && dom.length === 2) {
938
+ startSrcX0 = toNumber(dom[0]);
939
+ startSrcX1 = toNumber(dom[1]);
940
+ dragHasX = Number.isFinite(startSrcX0) && Number.isFinite(startSrcX1);
941
+ }
942
+ }
943
+ if (useY) {
944
+ const dom = source.visibleYDomain;
945
+ if (Array.isArray(dom) && dom.length === 2) {
946
+ startSrcY0 = toNumber(dom[0]);
947
+ startSrcY1 = toNumber(dom[1]);
948
+ dragHasY = Number.isFinite(startSrcY0) && Number.isFinite(startSrcY1);
949
+ }
950
+ }
951
+ dragging = dragHasX || dragHasY;
952
+ interactionState.active = dragging;
953
+ } : void 0,
954
+ onDragMove: dragEnabled ? (evt) => {
955
+ if (!dragging) return;
956
+ const data = overview.screenToData(evt.x, evt.y);
957
+ if (!data) return;
958
+ if (mode === "resize-x0" && dragHasX) {
959
+ source.setVisibleDomain({ x: [toNumber(data.x), startSrcX1] });
960
+ return;
961
+ }
962
+ if (mode === "resize-x1" && dragHasX) {
963
+ source.setVisibleDomain({ x: [startSrcX0, toNumber(data.x)] });
964
+ return;
965
+ }
966
+ if (mode === "resize-y0" && dragHasY) {
967
+ source.setVisibleDomain({ y: [toNumber(data.y), startSrcY1] });
968
+ return;
969
+ }
970
+ if (mode === "resize-y1" && dragHasY) {
971
+ source.setVisibleDomain({ y: [startSrcY0, toNumber(data.y)] });
972
+ return;
973
+ }
974
+ const update = {};
975
+ if (dragHasX) {
976
+ const dx = toNumber(data.x) - startDataX;
977
+ update.x = [startSrcX0 + dx, startSrcX1 + dx];
978
+ }
979
+ if (dragHasY) {
980
+ const dy = toNumber(data.y) - startDataY;
981
+ update.y = [startSrcY0 + dy, startSrcY1 + dy];
982
+ }
983
+ source.setVisibleDomain(update);
984
+ } : void 0,
985
+ onDragEnd: dragEnabled ? () => {
986
+ dragging = false;
987
+ interactionState.active = false;
988
+ mode = "pan";
989
+ } : void 0
990
+ });
991
+ }
992
+ function resolveIndicatorStyle(opts) {
993
+ return {
994
+ fill: opts?.fill ?? DEFAULT_INDICATOR_FILL,
995
+ stroke: opts?.stroke ?? DEFAULT_INDICATOR_STROKE,
996
+ strokeWidth: opts?.strokeWidth ?? 1,
997
+ cornerRadius: opts?.cornerRadius ?? 0
998
+ };
999
+ }
1000
+ function toNumber(v) {
1001
+ if (typeof v === "number") return v;
1002
+ if (v instanceof Date) return v.getTime();
1003
+ return Number(v);
1004
+ }
1005
+ function visibleNumericSpan(vp, axis) {
1006
+ const dom = axis === "x" ? vp.visibleXDomain : vp.visibleYDomain;
1007
+ if (!Array.isArray(dom) || dom.length !== 2) return null;
1008
+ const a = toNumber(dom[0]);
1009
+ const b = toNumber(dom[1]);
1010
+ if (!Number.isFinite(a) || !Number.isFinite(b)) return null;
1011
+ return b - a;
1012
+ }
1013
+ function computeDataIndicatorRect(source, overview, axes) {
1014
+ const useX = axes === "xy" || axes === "x";
1015
+ const useY = axes === "xy" || axes === "y";
1016
+ const f = overview.absoluteFrame;
1017
+ let x = f.x;
1018
+ let width = f.width;
1019
+ let y = f.y;
1020
+ let height = f.height;
1021
+ const srcX = source.visibleXDomain;
1022
+ const srcY = source.visibleYDomain;
1023
+ const yAnchor = Array.isArray(srcY) && srcY.length > 0 ? srcY[0] : null;
1024
+ const xAnchor = Array.isArray(srcX) && srcX.length > 0 ? srcX[0] : null;
1025
+ if (useX) {
1026
+ if (!Array.isArray(srcX) || srcX.length !== 2 || yAnchor === null) return null;
1027
+ const left = overview.dataToScreen(srcX[0], yAnchor);
1028
+ const right = overview.dataToScreen(srcX[1], yAnchor);
1029
+ x = Math.min(left.x, right.x);
1030
+ width = Math.abs(right.x - left.x);
1031
+ }
1032
+ if (useY) {
1033
+ if (!Array.isArray(srcY) || srcY.length !== 2 || xAnchor === null) return null;
1034
+ const top = overview.dataToScreen(xAnchor, srcY[0]);
1035
+ const bottom = overview.dataToScreen(xAnchor, srcY[1]);
1036
+ y = Math.min(top.y, bottom.y);
1037
+ height = Math.abs(bottom.y - top.y);
1038
+ }
1039
+ return {
1040
+ x,
1041
+ y,
1042
+ width,
1043
+ height
1044
+ };
1045
+ }
1046
+ //#endregion
1047
+ export { DASHED_PATTERN, DOTTED_PATTERN, accent, areaMark, areaSwatch, bandMark, bandScale, barMark, barSwatch, bin, binBreaks, binBy, binWithBreaks, bindDataViewport, blues, bottomAxis, boxStats, brbg, cardinalCurve, categoricalBarMark, categoricalPalette, category10, cividis, colorBar, colorScale, confidenceBand, continuousPalette, coolwarm, createDataNavigator, createDataViewport, createRangePresets, currency, dark2, defineCurve, fixed, greens, greys, groupBy, groupedBandScale, groupedBarMark, heatmapLayer, histogramMeasureValue, inferno, jitter, kde, leftAxis, legend, lineMark, lineSwatch, linearFit, linearPreset, linearScale, linkViewports, loessFit, logPreset, logScale, magma, oranges, paired, pastel, percent, piyg, plasma, pointMark, pointSwatch, polyFit, prgn, puor, purples, quantile, rdbu, reds, resamplePoints, rightAxis, rollingWindow, ruleMark, set1, set2, set3, siNumber, spectral, sqrtScale, stack, stackedAreaMark, stackedBarMark, tableau10, timePreset, timeScale, timeTickFormat, timeTicks, topAxis, truncateToWidth, valueLabelMark, viridis };