@biohub/scatterplot 0.1.4

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.
@@ -0,0 +1,354 @@
1
+ import { JSX } from 'react/jsx-runtime';
2
+ import { RefObject } from 'react';
3
+
4
+ /**
5
+ * Calculate data bounds from raw position buffer
6
+ * @param positions - Float32Array of [x, y, x, y, ...] coordinates
7
+ * @param count - Number of points
8
+ * @returns Data bounds
9
+ */
10
+ export declare function calculateBounds(positions: Float32Array, count: number): DataBounds;
11
+
12
+ /**
13
+ * Camera state for scatterplot visualization
14
+ */
15
+ export declare interface Camera {
16
+ /** Zoom level (1.0 = no zoom) */
17
+ zoom: number;
18
+ /** Pan offset in normalized device coordinates (-1 to 1) */
19
+ pan: {
20
+ x: number;
21
+ y: number;
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Theme types for the scatterplot component
27
+ */
28
+ export declare interface CanvasTheme {
29
+ background: string;
30
+ dataPadding: number;
31
+ }
32
+
33
+ export declare interface ContainerSize {
34
+ width: number;
35
+ height: number;
36
+ }
37
+
38
+ /**
39
+ * Create a flag buffer for a dataset
40
+ * @param count - Number of points
41
+ * @param selectedIndices - Set of selected point indices (optional)
42
+ * @param highlightedIndices - Set of highlighted point indices (optional)
43
+ * @param backgroundIndices - Set of background point indices (optional).
44
+ * If not provided and selectedIndices has items, non-selected/non-highlighted points are auto-marked as background.
45
+ * @returns Uint8Array of encoded flags (8 bits per point, 0-255)
46
+ */
47
+ export declare function createFlagBuffer(count: number, selectedIndices?: Set<number>, highlightedIndices?: Set<number>, backgroundIndices?: Set<number>): Uint8Array;
48
+
49
+ /**
50
+ * Create a theme by merging overrides with a base theme.
51
+ *
52
+ * Uses explicit property spreading instead of a generic deep merge because
53
+ * the theme structure is fixed at 2 levels. This keeps the implementation
54
+ * simple and avoids adding a dependency for something trivial.
55
+ */
56
+ export declare function createTheme(overrides: PartialTheme, base?: ScatterplotTheme): ScatterplotTheme;
57
+
58
+ export declare const darkTheme: ScatterplotTheme;
59
+
60
+ /**
61
+ * Geometry and normalization utilities for scatterplot
62
+ */
63
+ export declare interface DataBounds {
64
+ xMin: number;
65
+ xMax: number;
66
+ yMin: number;
67
+ yMax: number;
68
+ }
69
+
70
+ /**
71
+ * Transform a data point to screen (canvas pixel) coordinates
72
+ * This is the core projection pipeline used for hit-testing and interaction
73
+ * @param x - Data X coordinate
74
+ * @param y - Data Y coordinate
75
+ * @param xMin - Minimum X value in data space
76
+ * @param yMin - Minimum Y value in data space
77
+ * @param scale - Isotropic scale factor from getNormalizationScales
78
+ * @param xOffset - X centering offset from getNormalizationScales
79
+ * @param yOffset - Y centering offset from getNormalizationScales
80
+ * @param scaleX - Viewport X scale from getViewportScales
81
+ * @param scaleY - Viewport Y scale from getViewportScales
82
+ * @param panX - Camera pan X offset
83
+ * @param panY - Camera pan Y offset
84
+ * @param canvasWidth - Canvas width in pixels
85
+ * @param canvasHeight - Canvas height in pixels
86
+ * @returns Screen coordinates { screenX, screenY }
87
+ */
88
+ export declare function dataPointToScreen(x: number, y: number, xMin: number, yMin: number, scale: number, xOffset: number, yOffset: number, scaleX: number, scaleY: number, panX: number, panY: number, canvasWidth: number, canvasHeight: number): {
89
+ screenX: number;
90
+ screenY: number;
91
+ };
92
+
93
+ export declare interface DebugTheme {
94
+ background: string;
95
+ color: string;
96
+ fontFamily: string;
97
+ fontSize: string;
98
+ }
99
+
100
+ /**
101
+ * Utility type for partial theme overrides
102
+ */
103
+ declare type DeepPartial<T> = {
104
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
105
+ };
106
+
107
+ /**
108
+ * Default camera state: no zoom, no pan
109
+ */
110
+ export declare const DEFAULT_CAMERA: Camera;
111
+
112
+ export declare const defaultTheme: ScatterplotTheme;
113
+
114
+ /**
115
+ * Find the closest point to a click position
116
+ * @param clickX - Click X coordinate in canvas space (0 to width)
117
+ * @param clickY - Click Y coordinate in canvas space (0 to height)
118
+ * @param positions - Float32Array of [x, y, x, y, ...] coordinates
119
+ * @param count - Number of points
120
+ * @param canvasWidth - Canvas width in pixels
121
+ * @param canvasHeight - Canvas height in pixels
122
+ * @param camera - Current camera state (zoom and pan)
123
+ * @param maxDistance - Maximum distance in pixels to consider. For default, @see {@link DEFAULT_INTERACTION_DISTANCE_PX}
124
+ * @param bounds - Pre-computed data bounds
125
+ * @param dataPadding - Padding around data in pixels. For default, @see {@link defaultTheme.canvas.dataPadding}
126
+ * @returns Index of closest point, or null if no point within maxDistance
127
+ */
128
+ export declare function findClosestPointRaw(clickX: number, clickY: number, positions: Float32Array, count: number, canvasWidth: number, canvasHeight: number, camera: Camera, maxDistance: number | undefined, bounds: DataBounds, dataPadding?: number): number | null;
129
+
130
+ /**
131
+ * Find all points within a lasso polygon
132
+ * @param polygon - Array of [x, y] screen coordinates defining the lasso
133
+ * @param positions - Float32Array of [x, y, x, y, ...] coordinates
134
+ * @param count - Number of points
135
+ * @param canvasWidth - Canvas width in CSS pixels
136
+ * @param canvasHeight - Canvas height in CSS pixels
137
+ * @param camera - Current camera state (zoom and pan)
138
+ * @param bounds - Pre-computed data bounds
139
+ * @param dataPadding - Padding around data in pixels. For default, @see {@link defaultTheme.canvas.dataPadding}
140
+ * @returns Set of indices for points within the lasso
141
+ */
142
+ export declare function findPointsInLassoRaw(polygon: [number, number][], positions: Float32Array, count: number, canvasWidth: number, canvasHeight: number, camera: Camera, bounds: DataBounds, dataPadding?: number): Set<number>;
143
+
144
+ /**
145
+ * Converts hex color string to RGBA byte values (0-255 range)
146
+ * @param hex - Hex color string (e.g., "#ff0000" or "#f00")
147
+ * @param alpha - Alpha value (0-255). For default, @see {@link DEFAULT_ALPHA}
148
+ * @returns Array of [r, g, b, a] values in 0-255 range
149
+ */
150
+ export declare function hexToRgba(hex: string, alpha?: number): [number, number, number, number];
151
+
152
+ export declare const highContrastTheme: ScatterplotTheme;
153
+
154
+ export declare interface LassoTheme {
155
+ fill: string;
156
+ stroke: string;
157
+ strokeWidth: number;
158
+ strokeDasharray: string;
159
+ }
160
+
161
+ export declare const lightTheme: ScatterplotTheme;
162
+
163
+ /**
164
+ * Normalizes raw positions to WebGL space (-1 to 1) with isotropic scaling
165
+ * Preserves aspect ratio by using the same scale for both X and Y dimensions
166
+ * @param positions - Float32Array of [x, y, x, y, ...] in data space
167
+ * @param count - Number of points
168
+ * @param xMin - Minimum X value in data space
169
+ * @param xMax - Maximum X value in data space
170
+ * @param yMin - Minimum Y value in data space
171
+ * @param yMax - Maximum Y value in data space
172
+ * @returns New Float32Array with normalized coordinates
173
+ */
174
+ export declare function normalizePositionsIsotropic(positions: Float32Array, count: number, xMin: number, xMax: number, yMin: number, yMax: number): Float32Array;
175
+
176
+ export declare type PartialTheme = DeepPartial<ScatterplotTheme>;
177
+
178
+ /**
179
+ * Core types for the WebGL scatterplot
180
+ */
181
+ /**
182
+ * Represents a single point in the scatterplot
183
+ */
184
+ export declare interface Point {
185
+ x: number;
186
+ y: number;
187
+ color?: string;
188
+ }
189
+
190
+ export declare interface PointsTheme {
191
+ defaultColor: string;
192
+ size: number;
193
+ opacity: number;
194
+ backgroundOpacity: number;
195
+ highlightBrightness: number;
196
+ highlightSizeScale: number;
197
+ unselectedSizeScale: number;
198
+ }
199
+
200
+ /**
201
+ * Converts Point[] array to raw position and color buffers
202
+ * @param data - Array of points with x, y, and optional color properties
203
+ * @param defaultColor - Default color to use if point has no color. For default, @see {@link DEFAULT_COLOR}
204
+ * @returns Object containing positions (Float32Array) and colors (Uint8Array)
205
+ */
206
+ export declare function pointsToBuffers(data: Point[], defaultColor?: string): {
207
+ positions: Float32Array;
208
+ colors: Uint8Array;
209
+ };
210
+
211
+ export declare function Scatterplot({ points: data, width, height, initialCamera, theme, pixelRatio, enableLasso, enablePanZoom, debug, onSelectionChange, lassoRealtimeThreshold, controlled, }: ScatterplotProps): JSX.Element;
212
+
213
+ export declare function ScatterplotGL({ width, height, positions, colors, debug, pixelRatio, flags, onPointClick, lassoEnabled, onLassoComplete, onLassoUpdate, camera: controlledCamera, onCameraChange, panZoomEnabled, theme, className, }: ScatterplotGLProps): JSX.Element;
214
+
215
+ /**
216
+ * Props for the ScatterplotGL component
217
+ *
218
+ * @example
219
+ * ```tsx
220
+ * <ScatterplotGL
221
+ * positions={new Float32Array([0, 0, 1, 1, 2, 0])}
222
+ * colors={new Uint8Array([255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255])}
223
+ * width={800}
224
+ * height={600}
225
+ * onLassoComplete={(indices) => console.log('Selected:', indices)}
226
+ * />
227
+ * ```
228
+ */
229
+ declare interface ScatterplotGLProps {
230
+ /** Canvas width in CSS pixels (if not provided, fills container) */
231
+ width?: number;
232
+ /** Canvas height in CSS pixels (if not provided, fills container) */
233
+ height?: number;
234
+ /** Point positions as [x, y, x, y, ...] in data space. Automatically normalized with isotropic scaling. */
235
+ positions: Float32Array;
236
+ /** Point colors as [r, g, b, a, ...] in 0-255 range. Hardware-normalized to 0.0-1.0 in shader. */
237
+ colors: Uint8Array;
238
+ /** Show debug panel with performance metrics. */
239
+ debug?: boolean;
240
+ /** Device pixel ratio for crisp rendering on high-DPI displays. */
241
+ pixelRatio?: number;
242
+ /** Flag buffer for selection/highlight state (one byte per point). Use createFlagBuffer() to generate. */
243
+ flags?: Uint8Array;
244
+ /** Called when user clicks on or near a point. Receives point index or null if no point nearby. */
245
+ onPointClick?: (index: number | null) => void;
246
+ /** Enable lasso selection mode (disables pan/zoom and point click). */
247
+ lassoEnabled?: boolean;
248
+ /** Called when lasso selection completes with indices of selected points. */
249
+ onLassoComplete?: (indices: Set<number>) => void;
250
+ /** Called during lasso drawing for real-time highlight feedback. */
251
+ onLassoUpdate?: (indices: Set<number>) => void;
252
+ /** Current camera state (zoom and pan). If provided, component is controlled. */
253
+ camera?: Camera;
254
+ /** Called when camera changes (for controlled mode or to sync state). Supports functional updates. */
255
+ onCameraChange?: (camera: Camera | ((prev: Camera) => Camera)) => void;
256
+ /** Enable pan and zoom interactions. */
257
+ panZoomEnabled?: boolean;
258
+ /** Theme configuration for styling the scatterplot */
259
+ theme?: ScatterplotTheme;
260
+ /** Optional className for the wrapper div */
261
+ className?: string;
262
+ }
263
+
264
+ export declare interface ScatterplotProps {
265
+ /** Data points to visualize */
266
+ points: Point[];
267
+ /** Canvas width in pixels (if not provided, fills container). For default, @see {@link DEFAULT_FALLBACK_WIDTH} */
268
+ width?: number;
269
+ /** Canvas height in pixels (if not provided, fills container). For default, @see {@link DEFAULT_FALLBACK_HEIGHT} */
270
+ height?: number;
271
+ /** Initial camera state (zoom and pan) */
272
+ initialCamera?: Camera;
273
+ /** Theme configuration for styling */
274
+ theme?: ScatterplotTheme;
275
+ /** Device pixel ratio for high-DPI displays */
276
+ pixelRatio?: number;
277
+ /** Enable lasso selection mode */
278
+ enableLasso?: boolean;
279
+ /** Enable pan and zoom interactions */
280
+ enablePanZoom?: boolean;
281
+ /** Show debug panel with performance metrics */
282
+ debug?: boolean;
283
+ /** Callback when selection changes (receives Set of selected point indices) */
284
+ onSelectionChange?: (indices: Set<number>) => void;
285
+ /**
286
+ * Threshold for disabling real-time lasso highlighting.
287
+ * For default, @see {@link DEFAULT_LASSO_REALTIME_POINTS_THRESHOLD}
288
+ */
289
+ lassoRealtimeThreshold?: number;
290
+ /**
291
+ * Advanced: Control camera externally
292
+ * - If provided: fully controlled (parent manages state)
293
+ * - If omitted: component manages state internally
294
+ */
295
+ controlled?: {
296
+ camera: Camera;
297
+ onCameraChange?: (camera: Camera | ((prev: Camera) => Camera)) => void;
298
+ };
299
+ }
300
+
301
+ export declare interface ScatterplotTheme {
302
+ canvas: CanvasTheme;
303
+ points: PointsTheme;
304
+ lasso: LassoTheme;
305
+ debug: DebugTheme;
306
+ }
307
+
308
+ /**
309
+ * Hook that tracks the size of a container element using ResizeObserver
310
+ *
311
+ * Uses requestAnimationFrame to synchronize updates with the browser's
312
+ * paint cycle. Cancels pending updates when new resize events arrive
313
+ * to avoid unnecessary re-renders.
314
+ *
315
+ * @param containerRef - Ref to the container element to observe
316
+ * @returns Current width and height of the container
317
+ */
318
+ export declare function useContainerSize<T extends HTMLElement>(containerRef: RefObject<T | null>): ContainerSize;
319
+
320
+ /**
321
+ * Hook for managing point selection state
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * const { selectedIndices, handlePointClick } = useSelection();
326
+ *
327
+ * <ScatterplotGL
328
+ * data={data}
329
+ * flags={createFlagBuffer(data.length, selectedIndices)}
330
+ * onPointClick={handlePointClick}
331
+ * />
332
+ * ```
333
+ */
334
+ export declare function useSelection(): UseSelectionResult;
335
+
336
+ /**
337
+ * Hook for managing point selection state
338
+ *
339
+ * Supports both single-point (click) and multi-point (lasso) selection.
340
+ */
341
+ export declare interface UseSelectionResult {
342
+ /** Currently selected point indices (Set for efficient lookup) */
343
+ selectedIndices: Set<number>;
344
+ /** Handle point click - selects clicked point or clears if clicking empty space */
345
+ handlePointClick: (index: number | null) => void;
346
+ /** Set selection to specific indices (used by lasso tool) */
347
+ setSelection: (indices: Set<number>) => void;
348
+ /** Clear all selections */
349
+ clearSelection: () => void;
350
+ /** Check if a specific index is selected */
351
+ isSelected: (index: number) => boolean;
352
+ }
353
+
354
+ export { }
@@ -0,0 +1 @@
1
+ .scatterplot-lasso-polygon{fill:var(--scatterplot-lasso-fill);stroke:var(--scatterplot-lasso-stroke);stroke-width:var(--scatterplot-lasso-stroke-width);stroke-dasharray:var(--scatterplot-lasso-stroke-dasharray)}.scatterplot-debug-panel{position:absolute;top:10px;right:10px;padding:10px;border-radius:4px;min-width:180px;pointer-events:none;z-index:1000;line-height:1.5;background-color:var(--scatterplot-debug-background);color:var(--scatterplot-debug-color);font-family:var(--scatterplot-debug-font-family);font-size:var(--scatterplot-debug-font-size)}.scatterplot-debug-panel-title{margin-bottom:5px;font-weight:700;color:#fff}