hyper-scatter 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +142 -0
  3. package/dist-lib/controller/interaction_controller.d.ts +29 -0
  4. package/dist-lib/controller/interaction_controller.d.ts.map +1 -0
  5. package/dist-lib/controller/interaction_controller.js +286 -0
  6. package/dist-lib/controller/interaction_controller.js.map +1 -0
  7. package/dist-lib/core/dataset.d.ts +62 -0
  8. package/dist-lib/core/dataset.d.ts.map +1 -0
  9. package/dist-lib/core/dataset.js +152 -0
  10. package/dist-lib/core/dataset.js.map +1 -0
  11. package/dist-lib/core/lasso_simplify.d.ts +16 -0
  12. package/dist-lib/core/lasso_simplify.d.ts.map +1 -0
  13. package/dist-lib/core/lasso_simplify.js +173 -0
  14. package/dist-lib/core/lasso_simplify.js.map +1 -0
  15. package/dist-lib/core/math/euclidean.d.ts +31 -0
  16. package/dist-lib/core/math/euclidean.d.ts.map +1 -0
  17. package/dist-lib/core/math/euclidean.js +64 -0
  18. package/dist-lib/core/math/euclidean.js.map +1 -0
  19. package/dist-lib/core/math/poincare.d.ts +117 -0
  20. package/dist-lib/core/math/poincare.d.ts.map +1 -0
  21. package/dist-lib/core/math/poincare.js +321 -0
  22. package/dist-lib/core/math/poincare.js.map +1 -0
  23. package/dist-lib/core/rng.d.ts +18 -0
  24. package/dist-lib/core/rng.d.ts.map +1 -0
  25. package/dist-lib/core/rng.js +52 -0
  26. package/dist-lib/core/rng.js.map +1 -0
  27. package/dist-lib/core/selection/point_in_polygon.d.ts +30 -0
  28. package/dist-lib/core/selection/point_in_polygon.d.ts.map +1 -0
  29. package/dist-lib/core/selection/point_in_polygon.js +112 -0
  30. package/dist-lib/core/selection/point_in_polygon.js.map +1 -0
  31. package/dist-lib/core/types.d.ts +185 -0
  32. package/dist-lib/core/types.d.ts.map +1 -0
  33. package/dist-lib/core/types.js +53 -0
  34. package/dist-lib/core/types.js.map +1 -0
  35. package/dist-lib/impl_candidate/spatial_index.d.ts +45 -0
  36. package/dist-lib/impl_candidate/spatial_index.d.ts.map +1 -0
  37. package/dist-lib/impl_candidate/spatial_index.js +186 -0
  38. package/dist-lib/impl_candidate/spatial_index.js.map +1 -0
  39. package/dist-lib/impl_candidate/webgl_candidate.d.ts +283 -0
  40. package/dist-lib/impl_candidate/webgl_candidate.d.ts.map +1 -0
  41. package/dist-lib/impl_candidate/webgl_candidate.js +2276 -0
  42. package/dist-lib/impl_candidate/webgl_candidate.js.map +1 -0
  43. package/dist-lib/index.d.ts +11 -0
  44. package/dist-lib/index.d.ts.map +1 -0
  45. package/dist-lib/index.js +52 -0
  46. package/dist-lib/index.js.map +1 -0
  47. package/package.json +63 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # hyper-scatter
2
+
3
+ <!-- badges -->
4
+ [![npm version](https://img.shields.io/npm/v/hyper-scatter.svg?style=flat-square)](https://www.npmjs.com/package/hyper-scatter)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
6
+
7
+ **Hyperbolic (Poincaré) embeddings at 60 FPS. 20,000,000 points. Pure WebGL2 (no `regl`, no `three.js`).**
8
+
9
+ A specialized scatterplot engine for [HyperView](https://github.com/HackerRoomAI/HyperView).
10
+
11
+ - Geometries: **Poincaré (hyperbolic)** + **Euclidean** today; **Spherical (S²)** is a good future contribution.
12
+ - Correctness: a slow CPU **Reference** defines semantics; the fast GPU **Candidate** must match.
13
+ - Implementation: **pure WebGL2** (no `regl`, no `three.js`, no runtime deps).
14
+
15
+ ---
16
+
17
+ ## Poincaré (Hyperbolic) semantics
18
+
19
+ This is the part most scatterplot libs don’t have.
20
+
21
+ - **View state:** Möbius isometry parameter $a=(a_x,a_y)$ with $|a|<1$, plus a separate `displayZoom` scalar.
22
+ - **Pan:** anchor-invariant (the point under the cursor stays under the cursor).
23
+ - **Zoom:** anchored zoom; `displayZoom` scales the visual disk without changing the underlying isometry.
24
+ - **Project / unproject:** stable round-trips, shared between Reference + Candidate math.
25
+ - **Hit-test:** hyperbolic-aware, disk-culls correctly, deterministic tie-breaking.
26
+ - **Lasso:** selection polygon is unprojected to data space; membership is verified against the Reference.
27
+
28
+ For the full invariants + how the harness selects candidate code paths, see [AGENTS.md](AGENTS.md).
29
+
30
+ ## Usage (copy/paste agent prompt)
31
+
32
+ ```text
33
+ You are a coding agent working in my repository.
34
+
35
+ Use these imports:
36
+
37
+ import {
38
+ EuclideanWebGLCandidate,
39
+ HyperbolicWebGLCandidate,
40
+ createDataset,
41
+ createInteractionController,
42
+ type SelectionResult,
43
+ } from 'hyper-scatter';
44
+
45
+ Goal:
46
+ - Integrate `hyper-scatter` to render my embedding scatterplot.
47
+
48
+ Requirements:
49
+ 1) Install:
50
+ - npm: `npm install hyper-scatter`
51
+
52
+ 2) Implement a small integration wrapper:
53
+ - Create `mountHyperScatter(canvas, params)` (or an idiomatic React hook).
54
+ - Pick renderer:
55
+ - if params.geometry === 'poincare' use `new HyperbolicWebGLCandidate()`
56
+ - else use `new EuclideanWebGLCandidate()`
57
+ - Ensure the canvas has a real CSS size (non-zero width/height).
58
+ - Init using CSS pixels:
59
+ - `const rect = canvas.getBoundingClientRect()`
60
+ - `renderer.init(canvas, { width: Math.max(1, Math.floor(rect.width)), height: Math.max(1, Math.floor(rect.height)), devicePixelRatio: window.devicePixelRatio })`
61
+ - Dataset:
62
+ - `renderer.setDataset(createDataset(params.geometry, params.positions, params.labels))`
63
+ - First frame:
64
+ - `renderer.render()`
65
+
66
+ 3) Wire interactions:
67
+ - Use `createInteractionController(canvas, renderer, { onHover, onLassoComplete })`.
68
+ - On lasso completion, keep the returned `SelectionResult` and (optionally) call:
69
+ - `await renderer.countSelection(result, { yieldEveryMs: 0 })` if you need an exact count without UI yielding.
70
+
71
+ 4) Cleanup:
72
+ - On unmount/destroy: `controller.destroy(); renderer.destroy();`
73
+
74
+ Deliverables:
75
+ - The concrete code changes + file paths.
76
+ - A minimal example showing how to pass `Float32Array positions` (flat [x,y,x,y,...]) and optional `Uint16Array labels`.
77
+ ```
78
+
79
+ ## Benchmarks
80
+
81
+ Main claim, measured via the browser harness (headed):
82
+
83
+ Config note: canvas `1125x400 @ 1x DPR` (Puppeteer).
84
+
85
+ | Geometry | Points | FPS (avg) |
86
+ |---|---:|---:|
87
+ | Euclidean | 20,000,000 | 59.9 |
88
+ | Poincaré | 20,000,000 | 59.9 |
89
+
90
+ Run the stress benchmark that reproduces the rows above:
91
+
92
+ ```bash
93
+ npm run bench -- --points=20000000
94
+ ```
95
+
96
+ Default sweep (smaller point counts): `npm run bench`
97
+
98
+ Note: for performance numbers, run headed (default). Headless runs can skew GPU timing.
99
+
100
+ ## How we built it
101
+
102
+ I (Matin) only knew Python. So we built this as a lab with a clear loop.
103
+
104
+ Roles:
105
+
106
+ - Matin: architect/product (Python-first).
107
+ - Claude: harness/environment engineer (benchmarks + correctness tests + reference semantics).
108
+ - Codex: implementation engineer (WebGL2 candidate).
109
+
110
+ ### 1) Reference first
111
+
112
+ - Write non-performant, readable Canvas2D renderers (`src/impl_reference/`).
113
+ - Treat them as semantics: projection, pan/zoom, hit-test, lasso.
114
+
115
+ ### 2) Harness as the reward function
116
+
117
+ - Accuracy compares Reference vs Candidate for: project/unproject, pan/zoom invariance, hit-test, lasso.
118
+ - Performance tracks: FPS, pan/hover FPS, hit-test time, lasso time.
119
+
120
+ ### 3) Candidate optimization
121
+
122
+ - Implement the WebGL2 candidate (`src/impl_candidate/`).
123
+ - Speed comes from GPU rendering + spatial indexing + adaptive quality.
124
+
125
+ ### 4) Reward hacking notes
126
+
127
+ If you give an agent a benchmark, it will try to win.
128
+
129
+ - Editing the harness/tolerances instead of fixing precision.
130
+ - Making lasso “async” so the timer looks better.
131
+
132
+ The harness tries to reduce these paths (example: lasso timing is end-to-end and includes the work required to get an exact selected-count).
133
+
134
+ ## Status & Roadmap
135
+
136
+ - [x] Euclidean Geometry
137
+ - [x] Poincaré Disk (Hyperbolic) Geometry
138
+ - [ ] **Spherical Geometry (S²)**: The architecture supports it (`GeometryMode` enum), but the Reference math is missing. Contributions welcome.
139
+
140
+ ## License
141
+
142
+ MIT © [Matin Mahmood](https://www.linkedin.com/in/matin-mahmood/) (X: [@MatinMnM](https://twitter.com/MatinMnM))
@@ -0,0 +1,29 @@
1
+ import type { HitResult, Renderer, SelectionResult } from '../core/types.js';
2
+ export interface InteractionController {
3
+ /** Detach all event listeners / observers. */
4
+ destroy(): void;
5
+ /** Force a size re-measure + renderer.resize() on next frame. */
6
+ requestResize(): void;
7
+ }
8
+ export interface InteractionControllerOptions {
9
+ /** If true, uses ResizeObserver (if available) to keep renderer size in sync. Default: true. */
10
+ observeResize?: boolean;
11
+ /** Controls how wheel deltas map to renderer.zoom(). Default matches the demo: -deltaY/100. */
12
+ wheelZoomScale?: number;
13
+ /** Trigger momentary lasso selection. Default: Shift + (Meta or Ctrl) (Embedding Atlas style). */
14
+ lassoPredicate?: (e: PointerEvent) => boolean;
15
+ /** Minimum screen-space sampling distance while lassoing (CSS px). Default: 2. */
16
+ lassoMinSampleDistPx?: number;
17
+ /** Vertex budget while dragging (for onLassoUpdate). Default: 24. */
18
+ lassoMaxVertsInteraction?: number;
19
+ /** Vertex budget on completion (for onLassoComplete). Default: 48. */
20
+ lassoMaxVertsFinal?: number;
21
+ /** Optional hook: hover updates (after hitTest). */
22
+ onHover?: (hit: HitResult | null) => void;
23
+ /** Optional hook: lasso polygon updates during drag. */
24
+ onLassoUpdate?: (dataPolygon: Float32Array, screenPolygon: Float32Array) => void;
25
+ /** Optional hook: lasso completion. */
26
+ onLassoComplete?: (result: SelectionResult, dataPolygon: Float32Array, screenPolygon: Float32Array) => void;
27
+ }
28
+ export declare function createInteractionController(canvas: HTMLCanvasElement, renderer: Renderer, opts?: InteractionControllerOptions): InteractionController;
29
+ //# sourceMappingURL=interaction_controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction_controller.d.ts","sourceRoot":"","sources":["../../src/controller/interaction_controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,QAAQ,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxF,MAAM,WAAW,qBAAqB;IACpC,8CAA8C;IAC9C,OAAO,IAAI,IAAI,CAAC;IAEhB,iEAAiE;IACjE,aAAa,IAAI,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,4BAA4B;IAC3C,gGAAgG;IAChG,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,kGAAkG;IAClG,cAAc,CAAC,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC;IAE9C,kFAAkF;IAClF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B,qEAAqE;IACrE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAElC,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,oDAAoD;IACpD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAE1C,wDAAwD;IACxD,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,KAAK,IAAI,CAAC;IAEjF,uCAAuC;IACvC,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,KAAK,IAAI,CAAC;CAC7G;AAgBD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,QAAQ,EAClB,IAAI,GAAE,4BAAiC,GACtC,qBAAqB,CAgUvB"}
@@ -0,0 +1,286 @@
1
+ import { simplifyPolygonData } from '../core/lasso_simplify.js';
2
+ function modifiersFromEvent(e) {
3
+ return {
4
+ shift: e.shiftKey,
5
+ ctrl: e.ctrlKey,
6
+ alt: e.altKey,
7
+ meta: e.metaKey,
8
+ };
9
+ }
10
+ function clampSizePx(v) {
11
+ if (!Number.isFinite(v))
12
+ return 1;
13
+ return Math.max(1, Math.floor(v));
14
+ }
15
+ export function createInteractionController(canvas, renderer, opts = {}) {
16
+ const wheelZoomScale = Number.isFinite(opts.wheelZoomScale) ? opts.wheelZoomScale : 1 / 100;
17
+ const lassoPredicate = opts.lassoPredicate ?? ((e) => e.shiftKey && (e.metaKey || e.ctrlKey));
18
+ const lassoMinSampleDistPx = Number.isFinite(opts.lassoMinSampleDistPx) ? opts.lassoMinSampleDistPx : 2;
19
+ const lassoMaxVertsInteraction = Number.isFinite(opts.lassoMaxVertsInteraction) ? opts.lassoMaxVertsInteraction : 24;
20
+ const lassoMaxVertsFinal = Number.isFinite(opts.lassoMaxVertsFinal) ? opts.lassoMaxVertsFinal : 48;
21
+ const observeResize = opts.observeResize ?? true;
22
+ let raf = 0;
23
+ let lastWidth = 0;
24
+ let lastHeight = 0;
25
+ let resizeDirty = true;
26
+ let mode = 'idle';
27
+ let activePointerId = null;
28
+ // Pan state (accumulate in CSS px; apply once per frame)
29
+ let lastX = 0;
30
+ let lastY = 0;
31
+ let pendingPanDx = 0;
32
+ let pendingPanDy = 0;
33
+ let pendingPanMods = { shift: false, ctrl: false, alt: false, meta: false };
34
+ // Wheel zoom (accumulate delta; apply once per frame)
35
+ let pendingZoom = 0;
36
+ let pendingZoomX = 0;
37
+ let pendingZoomY = 0;
38
+ let pendingZoomMods = { shift: false, ctrl: false, alt: false, meta: false };
39
+ let zoomDirty = false;
40
+ // Hover state (throttle to rAF)
41
+ let hoverDirty = false;
42
+ let pendingHoverX = 0;
43
+ let pendingHoverY = 0;
44
+ let lastHoverIndex = -2; // sentinel to ensure first update passes
45
+ // Lasso state (sample in DATA space; project back to screen when needed)
46
+ let lassoRawData = [];
47
+ let lassoActiveDataPolygon = null;
48
+ let lassoLastScreenX = 0;
49
+ let lassoLastScreenY = 0;
50
+ let lassoDirty = false;
51
+ const rectToLocal = (clientX, clientY) => {
52
+ const rect = canvas.getBoundingClientRect();
53
+ return { x: clientX - rect.left, y: clientY - rect.top };
54
+ };
55
+ const projectDataPolygonToScreen = (dataPoly) => {
56
+ const out = new Float32Array(dataPoly.length);
57
+ for (let i = 0; i < dataPoly.length / 2; i++) {
58
+ const dx = dataPoly[i * 2];
59
+ const dy = dataPoly[i * 2 + 1];
60
+ const s = renderer.projectToScreen(dx, dy);
61
+ out[i * 2] = s.x;
62
+ out[i * 2 + 1] = s.y;
63
+ }
64
+ return out;
65
+ };
66
+ const ensureSize = () => {
67
+ const rect = canvas.getBoundingClientRect();
68
+ const w = clampSizePx(rect.width);
69
+ const h = clampSizePx(rect.height);
70
+ if (w !== lastWidth || h !== lastHeight) {
71
+ lastWidth = w;
72
+ lastHeight = h;
73
+ renderer.resize(w, h);
74
+ return true;
75
+ }
76
+ return false;
77
+ };
78
+ const requestFrame = () => {
79
+ if (raf)
80
+ return;
81
+ raf = requestAnimationFrame(() => {
82
+ raf = 0;
83
+ let didChange = false;
84
+ if (resizeDirty) {
85
+ resizeDirty = false;
86
+ didChange = ensureSize() || didChange;
87
+ }
88
+ if (pendingPanDx !== 0 || pendingPanDy !== 0) {
89
+ renderer.pan(pendingPanDx, pendingPanDy, pendingPanMods);
90
+ pendingPanDx = 0;
91
+ pendingPanDy = 0;
92
+ didChange = true;
93
+ }
94
+ if (zoomDirty && pendingZoom !== 0) {
95
+ renderer.zoom(pendingZoomX, pendingZoomY, pendingZoom, pendingZoomMods);
96
+ pendingZoom = 0;
97
+ zoomDirty = false;
98
+ didChange = true;
99
+ }
100
+ if (mode === 'idle' && hoverDirty) {
101
+ hoverDirty = false;
102
+ const hit = renderer.hitTest(pendingHoverX, pendingHoverY);
103
+ const idx = hit ? hit.index : -1;
104
+ // Only update renderer + re-render if hover actually changed.
105
+ if (idx !== lastHoverIndex) {
106
+ lastHoverIndex = idx;
107
+ renderer.setHovered(idx);
108
+ didChange = true;
109
+ }
110
+ opts.onHover?.(hit);
111
+ }
112
+ if (mode === 'lasso' && lassoDirty && opts.onLassoUpdate && lassoRawData.length >= 6) {
113
+ lassoDirty = false;
114
+ const dataPoly = simplifyPolygonData(lassoRawData, lassoMaxVertsInteraction);
115
+ lassoActiveDataPolygon = dataPoly;
116
+ const screenPoly = projectDataPolygonToScreen(dataPoly);
117
+ opts.onLassoUpdate(dataPoly, screenPoly);
118
+ }
119
+ if (didChange) {
120
+ renderer.render();
121
+ }
122
+ });
123
+ };
124
+ const handlePointerDown = (e) => {
125
+ if (e.button !== 0)
126
+ return;
127
+ const { x, y } = rectToLocal(e.clientX, e.clientY);
128
+ activePointerId = e.pointerId;
129
+ lastX = x;
130
+ lastY = y;
131
+ // Clear hover while interacting.
132
+ if (lastHoverIndex !== -1) {
133
+ lastHoverIndex = -1;
134
+ renderer.setHovered(-1);
135
+ }
136
+ const wantsLasso = lassoPredicate(e);
137
+ if (wantsLasso) {
138
+ mode = 'lasso';
139
+ lassoRawData = [];
140
+ lassoActiveDataPolygon = null;
141
+ lassoLastScreenX = x;
142
+ lassoLastScreenY = y;
143
+ const d0 = renderer.unprojectFromScreen(x, y);
144
+ lassoRawData.push(d0.x, d0.y);
145
+ lassoDirty = true;
146
+ requestFrame();
147
+ }
148
+ else {
149
+ mode = 'pan';
150
+ pendingPanDx = 0;
151
+ pendingPanDy = 0;
152
+ pendingPanMods = modifiersFromEvent(e);
153
+ // For hyperbolic candidates: establish an anchor.
154
+ if ('startPan' in renderer) {
155
+ renderer.startPan(x, y);
156
+ }
157
+ requestFrame();
158
+ }
159
+ canvas.setPointerCapture(e.pointerId);
160
+ };
161
+ const handlePointerMove = (e) => {
162
+ if (activePointerId !== e.pointerId) {
163
+ // Still allow hover updates from other pointers.
164
+ if (mode === 'idle') {
165
+ const { x, y } = rectToLocal(e.clientX, e.clientY);
166
+ pendingHoverX = x;
167
+ pendingHoverY = y;
168
+ hoverDirty = true;
169
+ requestFrame();
170
+ }
171
+ return;
172
+ }
173
+ const { x, y } = rectToLocal(e.clientX, e.clientY);
174
+ if (mode === 'pan') {
175
+ pendingPanDx += x - lastX;
176
+ pendingPanDy += y - lastY;
177
+ pendingPanMods = modifiersFromEvent(e);
178
+ lastX = x;
179
+ lastY = y;
180
+ requestFrame();
181
+ return;
182
+ }
183
+ if (mode === 'lasso') {
184
+ const dx = x - lassoLastScreenX;
185
+ const dy = y - lassoLastScreenY;
186
+ const minD2 = lassoMinSampleDistPx * lassoMinSampleDistPx;
187
+ if (dx * dx + dy * dy >= minD2) {
188
+ const d = renderer.unprojectFromScreen(x, y);
189
+ lassoRawData.push(d.x, d.y);
190
+ lassoDirty = true;
191
+ lassoLastScreenX = x;
192
+ lassoLastScreenY = y;
193
+ requestFrame();
194
+ }
195
+ return;
196
+ }
197
+ // idle
198
+ pendingHoverX = x;
199
+ pendingHoverY = y;
200
+ hoverDirty = true;
201
+ requestFrame();
202
+ };
203
+ const handlePointerUp = (e) => {
204
+ if (activePointerId !== e.pointerId)
205
+ return;
206
+ // Flush any remaining pan before ending interaction.
207
+ if (mode === 'pan' && (pendingPanDx !== 0 || pendingPanDy !== 0)) {
208
+ renderer.pan(pendingPanDx, pendingPanDy, pendingPanMods);
209
+ pendingPanDx = 0;
210
+ pendingPanDy = 0;
211
+ renderer.render();
212
+ }
213
+ if (mode === 'lasso' && lassoRawData.length >= 6) {
214
+ const dataPoly = lassoActiveDataPolygon ?? simplifyPolygonData(lassoRawData, lassoMaxVertsFinal);
215
+ const screenPoly = projectDataPolygonToScreen(dataPoly);
216
+ const result = renderer.lassoSelect(screenPoly);
217
+ opts.onLassoComplete?.(result, dataPoly, screenPoly);
218
+ }
219
+ // End interaction policy (avoid LOD pop if implementation supports it).
220
+ if ('endInteraction' in renderer) {
221
+ renderer.endInteraction();
222
+ }
223
+ mode = 'idle';
224
+ activePointerId = null;
225
+ lassoRawData = [];
226
+ lassoActiveDataPolygon = null;
227
+ lassoDirty = false;
228
+ hoverDirty = false;
229
+ try {
230
+ canvas.releasePointerCapture(e.pointerId);
231
+ }
232
+ catch {
233
+ // ignore
234
+ }
235
+ };
236
+ const handleWheel = (e) => {
237
+ e.preventDefault();
238
+ const { x, y } = rectToLocal(e.clientX, e.clientY);
239
+ // Normalize wheel delta (matches demo)
240
+ const delta = -e.deltaY * wheelZoomScale;
241
+ pendingZoom += delta;
242
+ pendingZoomX = x;
243
+ pendingZoomY = y;
244
+ pendingZoomMods = modifiersFromEvent(e);
245
+ zoomDirty = true;
246
+ requestFrame();
247
+ };
248
+ // --- Attach listeners ---
249
+ canvas.addEventListener('pointerdown', handlePointerDown);
250
+ canvas.addEventListener('pointermove', handlePointerMove);
251
+ canvas.addEventListener('pointerup', handlePointerUp);
252
+ canvas.addEventListener('pointercancel', handlePointerUp);
253
+ canvas.addEventListener('wheel', handleWheel, { passive: false });
254
+ // Optional resize observation
255
+ let ro = null;
256
+ if (observeResize && typeof ResizeObserver !== 'undefined') {
257
+ ro = new ResizeObserver(() => {
258
+ resizeDirty = true;
259
+ requestFrame();
260
+ });
261
+ ro.observe(canvas);
262
+ }
263
+ // Initial size sync
264
+ resizeDirty = true;
265
+ requestFrame();
266
+ return {
267
+ destroy() {
268
+ if (raf) {
269
+ cancelAnimationFrame(raf);
270
+ raf = 0;
271
+ }
272
+ canvas.removeEventListener('pointerdown', handlePointerDown);
273
+ canvas.removeEventListener('pointermove', handlePointerMove);
274
+ canvas.removeEventListener('pointerup', handlePointerUp);
275
+ canvas.removeEventListener('pointercancel', handlePointerUp);
276
+ canvas.removeEventListener('wheel', handleWheel);
277
+ ro?.disconnect();
278
+ ro = null;
279
+ },
280
+ requestResize() {
281
+ resizeDirty = true;
282
+ requestFrame();
283
+ },
284
+ };
285
+ }
286
+ //# sourceMappingURL=interaction_controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interaction_controller.js","sourceRoot":"","sources":["../../src/controller/interaction_controller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAuChE,SAAS,kBAAkB,CAAC,CAA6E;IACvG,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,QAAQ;QACjB,IAAI,EAAE,CAAC,CAAC,OAAO;QACf,GAAG,EAAE,CAAC,CAAC,MAAM;QACb,IAAI,EAAE,CAAC,CAAC,OAAO;KAChB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,MAAyB,EACzB,QAAkB,EAClB,OAAqC,EAAE;IAEvC,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,cAAyB,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;IAExG,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,CAAe,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5G,MAAM,oBAAoB,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,oBAA+B,CAAC,CAAC,CAAC,CAAC,CAAC;IACpH,MAAM,wBAAwB,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,wBAAmC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjI,MAAM,kBAAkB,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,kBAA6B,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/G,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;IAEjD,IAAI,GAAG,GAAG,CAAC,CAAC;IAEZ,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,WAAW,GAAG,IAAI,CAAC;IAEvB,IAAI,IAAI,GAA6B,MAAM,CAAC;IAC5C,IAAI,eAAe,GAAkB,IAAI,CAAC;IAE1C,yDAAyD;IACzD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,cAAc,GAAc,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAEvF,sDAAsD;IACtD,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,eAAe,GAAc,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACxF,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,gCAAgC;IAChC,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAI,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,yCAAyC;IAElE,yEAAyE;IACzE,IAAI,YAAY,GAAa,EAAE,CAAC;IAChC,IAAI,sBAAsB,GAAwB,IAAI,CAAC;IACvD,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,OAAe,EAA4B,EAAE;QACjF,MAAM,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;QAC5C,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3D,CAAC,CAAC;IAEF,MAAM,0BAA0B,GAAG,CAAC,QAAsB,EAAgB,EAAE;QAC1E,MAAM,GAAG,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3B,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3C,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjB,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAY,EAAE;QAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,UAAU,EAAE,CAAC;YACxC,SAAS,GAAG,CAAC,CAAC;YACd,UAAU,GAAG,CAAC,CAAC;YACf,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,GAAS,EAAE;QAC9B,IAAI,GAAG;YAAE,OAAO;QAChB,GAAG,GAAG,qBAAqB,CAAC,GAAG,EAAE;YAC/B,GAAG,GAAG,CAAC,CAAC;YAER,IAAI,SAAS,GAAG,KAAK,CAAC;YAEtB,IAAI,WAAW,EAAE,CAAC;gBAChB,WAAW,GAAG,KAAK,CAAC;gBACpB,SAAS,GAAG,UAAU,EAAE,IAAI,SAAS,CAAC;YACxC,CAAC;YAED,IAAI,YAAY,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,EAAE,CAAC;gBAC7C,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;gBACzD,YAAY,GAAG,CAAC,CAAC;gBACjB,YAAY,GAAG,CAAC,CAAC;gBACjB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YAED,IAAI,SAAS,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;gBACnC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC;gBACxE,WAAW,GAAG,CAAC,CAAC;gBAChB,SAAS,GAAG,KAAK,CAAC;gBAClB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YAED,IAAI,IAAI,KAAK,MAAM,IAAI,UAAU,EAAE,CAAC;gBAClC,UAAU,GAAG,KAAK,CAAC;gBACnB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;gBAC3D,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAEjC,8DAA8D;gBAC9D,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;oBAC3B,cAAc,GAAG,GAAG,CAAC;oBACrB,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;oBACzB,SAAS,GAAG,IAAI,CAAC;gBACnB,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;YACtB,CAAC;YAED,IAAI,IAAI,KAAK,OAAO,IAAI,UAAU,IAAI,IAAI,CAAC,aAAa,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBACrF,UAAU,GAAG,KAAK,CAAC;gBACnB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC;gBAC7E,sBAAsB,GAAG,QAAQ,CAAC;gBAClC,MAAM,UAAU,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAC;gBACxD,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAC3C,CAAC;YAED,IAAI,SAAS,EAAE,CAAC;gBACd,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,CAAC,CAAe,EAAQ,EAAE;QAClD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAE3B,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QAEnD,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC;QAC9B,KAAK,GAAG,CAAC,CAAC;QACV,KAAK,GAAG,CAAC,CAAC;QAEV,iCAAiC;QACjC,IAAI,cAAc,KAAK,CAAC,CAAC,EAAE,CAAC;YAC1B,cAAc,GAAG,CAAC,CAAC,CAAC;YACpB,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,CAAC;QAED,MAAM,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,GAAG,OAAO,CAAC;YACf,YAAY,GAAG,EAAE,CAAC;YAClB,sBAAsB,GAAG,IAAI,CAAC;YAC9B,gBAAgB,GAAG,CAAC,CAAC;YACrB,gBAAgB,GAAG,CAAC,CAAC;YAErB,MAAM,EAAE,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9C,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9B,UAAU,GAAG,IAAI,CAAC;YAClB,YAAY,EAAE,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,KAAK,CAAC;YACb,YAAY,GAAG,CAAC,CAAC;YACjB,YAAY,GAAG,CAAC,CAAC;YACjB,cAAc,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YAEvC,kDAAkD;YAClD,IAAI,UAAU,IAAK,QAAgB,EAAE,CAAC;gBACnC,QAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACnC,CAAC;YAED,YAAY,EAAE,CAAC;QACjB,CAAC;QAED,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC,CAAC;IAEF,MAAM,iBAAiB,GAAG,CAAC,CAAe,EAAQ,EAAE;QAClD,IAAI,eAAe,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC;YACpC,iDAAiD;YACjD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpB,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;gBACnD,aAAa,GAAG,CAAC,CAAC;gBAClB,aAAa,GAAG,CAAC,CAAC;gBAClB,UAAU,GAAG,IAAI,CAAC;gBAClB,YAAY,EAAE,CAAC;YACjB,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QAEnD,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,YAAY,IAAI,CAAC,GAAG,KAAK,CAAC;YAC1B,YAAY,IAAI,CAAC,GAAG,KAAK,CAAC;YAC1B,cAAc,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;YACvC,KAAK,GAAG,CAAC,CAAC;YACV,KAAK,GAAG,CAAC,CAAC;YACV,YAAY,EAAE,CAAC;YACf,OAAO;QACT,CAAC;QAED,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAAC;YAChC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAAC;YAChC,MAAM,KAAK,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;YAC1D,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,KAAK,EAAE,CAAC;gBAC/B,MAAM,CAAC,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC7C,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC5B,UAAU,GAAG,IAAI,CAAC;gBAClB,gBAAgB,GAAG,CAAC,CAAC;gBACrB,gBAAgB,GAAG,CAAC,CAAC;gBACrB,YAAY,EAAE,CAAC;YACjB,CAAC;YACD,OAAO;QACT,CAAC;QAED,OAAO;QACP,aAAa,GAAG,CAAC,CAAC;QAClB,aAAa,GAAG,CAAC,CAAC;QAClB,UAAU,GAAG,IAAI,CAAC;QAClB,YAAY,EAAE,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,CAAC,CAAe,EAAQ,EAAE;QAChD,IAAI,eAAe,KAAK,CAAC,CAAC,SAAS;YAAE,OAAO;QAE5C,qDAAqD;QACrD,IAAI,IAAI,KAAK,KAAK,IAAI,CAAC,YAAY,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YACjE,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;YACzD,YAAY,GAAG,CAAC,CAAC;YACjB,YAAY,GAAG,CAAC,CAAC;YACjB,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,CAAC;QAED,IAAI,IAAI,KAAK,OAAO,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,QAAQ,GAAG,sBAAsB,IAAI,mBAAmB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;YACjG,MAAM,UAAU,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAC;YACxD,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;YAChD,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACvD,CAAC;QAED,wEAAwE;QACxE,IAAI,gBAAgB,IAAK,QAAgB,EAAE,CAAC;YACzC,QAAgB,CAAC,cAAc,EAAE,CAAC;QACrC,CAAC;QAED,IAAI,GAAG,MAAM,CAAC;QACd,eAAe,GAAG,IAAI,CAAC;QACvB,YAAY,GAAG,EAAE,CAAC;QAClB,sBAAsB,GAAG,IAAI,CAAC;QAC9B,UAAU,GAAG,KAAK,CAAC;QACnB,UAAU,GAAG,KAAK,CAAC;QAEnB,IAAI,CAAC;YACH,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,CAAa,EAAQ,EAAE;QAC1C,CAAC,CAAC,cAAc,EAAE,CAAC;QAEnB,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;QAEnD,uCAAuC;QACvC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,GAAG,cAAc,CAAC;QAEzC,WAAW,IAAI,KAAK,CAAC;QACrB,YAAY,GAAG,CAAC,CAAC;QACjB,YAAY,GAAG,CAAC,CAAC;QACjB,eAAe,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;QACxC,SAAS,GAAG,IAAI,CAAC;QAEjB,YAAY,EAAE,CAAC;IACjB,CAAC,CAAC;IAEF,2BAA2B;IAE3B,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAC1D,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;IAC1D,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IACtD,MAAM,CAAC,gBAAgB,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAC1D,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAElE,8BAA8B;IAC9B,IAAI,EAAE,GAA0B,IAAI,CAAC;IACrC,IAAI,aAAa,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE,CAAC;QAC3D,EAAE,GAAG,IAAI,cAAc,CAAC,GAAG,EAAE;YAC3B,WAAW,GAAG,IAAI,CAAC;YACnB,YAAY,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IAED,oBAAoB;IACpB,WAAW,GAAG,IAAI,CAAC;IACnB,YAAY,EAAE,CAAC;IAEf,OAAO;QACL,OAAO;YACL,IAAI,GAAG,EAAE,CAAC;gBACR,oBAAoB,CAAC,GAAG,CAAC,CAAC;gBAC1B,GAAG,GAAG,CAAC,CAAC;YACV,CAAC;YAED,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;YAC7D,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,iBAAiB,CAAC,CAAC;YAC7D,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;YACzD,MAAM,CAAC,mBAAmB,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;YAC7D,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAEjD,EAAE,EAAE,UAAU,EAAE,CAAC;YACjB,EAAE,GAAG,IAAI,CAAC;QACZ,CAAC;QAED,aAAa;YACX,WAAW,GAAG,IAAI,CAAC;YACnB,YAAY,EAAE,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Deterministic dataset generation for testing.
3
+ */
4
+ import { Dataset, GeometryMode } from './types.js';
5
+ export interface DatasetConfig {
6
+ seed: number;
7
+ n: number;
8
+ labelCount: number;
9
+ geometry: GeometryMode;
10
+ /** Controls the spatial distribution of generated points. */
11
+ distribution?: DatasetDistribution;
12
+ /** For hyperbolic: generate points near boundary (stress test) */
13
+ boundaryStress?: boolean;
14
+ }
15
+ export type DatasetDistribution = 'default' | 'clustered';
16
+ /**
17
+ * Generate a deterministic dataset.
18
+ */
19
+ export declare function generateDataset(config: DatasetConfig): Dataset;
20
+ /**
21
+ * Standard test datasets.
22
+ */
23
+ export declare const STANDARD_DATASETS: {
24
+ euclidean_10k: {
25
+ seed: number;
26
+ n: number;
27
+ labelCount: number;
28
+ geometry: "euclidean";
29
+ };
30
+ euclidean_100k: {
31
+ seed: number;
32
+ n: number;
33
+ labelCount: number;
34
+ geometry: "euclidean";
35
+ };
36
+ euclidean_1m: {
37
+ seed: number;
38
+ n: number;
39
+ labelCount: number;
40
+ geometry: "euclidean";
41
+ };
42
+ poincare_10k: {
43
+ seed: number;
44
+ n: number;
45
+ labelCount: number;
46
+ geometry: "poincare";
47
+ };
48
+ poincare_100k: {
49
+ seed: number;
50
+ n: number;
51
+ labelCount: number;
52
+ geometry: "poincare";
53
+ };
54
+ poincare_boundary_stress: {
55
+ seed: number;
56
+ n: number;
57
+ labelCount: number;
58
+ geometry: "poincare";
59
+ boundaryStress: boolean;
60
+ };
61
+ };
62
+ //# sourceMappingURL=dataset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset.d.ts","sourceRoot":"","sources":["../../src/core/dataset.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAGnD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;IACvB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,kEAAkE;IAClE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,WAAW,CAAC;AA2ChB;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAkH9D;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAO7B,CAAC"}