@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.
- package/LICENSE +21 -0
- package/README.md +501 -0
- package/dist/index.d.ts +354 -0
- package/dist/scatterplot.css +1 -0
- package/dist/scatterplot.js +985 -0
- package/dist/scatterplot.js.map +1 -0
- package/dist/scatterplot.umd.js +106 -0
- package/dist/scatterplot.umd.js.map +1 -0
- package/package.json +100 -0
package/dist/index.d.ts
ADDED
|
@@ -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}
|