@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chan Zuckerberg Biohub
|
|
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,501 @@
|
|
|
1
|
+
# @biohub/scatterplot
|
|
2
|
+
|
|
3
|
+
[](https://github.com/chanzuckerberg/scatterplot/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/chanzuckerberg/scatterplot/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/chanzuckerberg/scatterplot/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
High-performance WebGL scatterplot component for React with support for datasets up to 10M+ points.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **GPU-accelerated rendering** - WebGL2-based for smooth 60fps performance
|
|
12
|
+
- **Interactive pan & zoom** - Intuitive mouse/touch controls
|
|
13
|
+
- **Lasso selection** - Select multiple points with custom polygons
|
|
14
|
+
- **Customizable styling** - Point colors, sizes, and states
|
|
15
|
+
- **Responsive** - Auto-adapts to container size
|
|
16
|
+
- **React hooks** - Composable selection and interaction hooks
|
|
17
|
+
- **Zero heavy dependencies** - No D3, no regl, just React and WebGL
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @biohub/scatterplot
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Local Development
|
|
26
|
+
|
|
27
|
+
### Build the library
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This creates a `dist/` directory with:
|
|
34
|
+
- `scatterplot.js` (ESM)
|
|
35
|
+
- `scatterplot.umd.js` (UMD)
|
|
36
|
+
- `index.d.ts` (TypeScript types)
|
|
37
|
+
- Source maps
|
|
38
|
+
|
|
39
|
+
### Link for local development
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# In this directory
|
|
43
|
+
npm link
|
|
44
|
+
|
|
45
|
+
# In your consuming project directory
|
|
46
|
+
npm link @biohub/scatterplot
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Unlink when done
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# In your consuming project
|
|
53
|
+
npm unlink @biohub/scatterplot
|
|
54
|
+
|
|
55
|
+
# In this directory
|
|
56
|
+
npm unlink
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Basic Example
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
import { Scatterplot } from '@biohub/scatterplot';
|
|
65
|
+
|
|
66
|
+
function MyChart() {
|
|
67
|
+
const data = [
|
|
68
|
+
{ x: 10, y: 20, color: '#ff0000' },
|
|
69
|
+
{ x: 30, y: 40, color: '#00ff00' },
|
|
70
|
+
{ x: 50, y: 60, color: '#0000ff' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Scatterplot
|
|
75
|
+
data={data}
|
|
76
|
+
width={800}
|
|
77
|
+
height={600}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### With Selection Handling
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { Scatterplot } from '@biohub/scatterplot';
|
|
87
|
+
|
|
88
|
+
function InteractiveChart() {
|
|
89
|
+
const [selectedIndices, setSelectedIndices] = useState<number[]>([]);
|
|
90
|
+
|
|
91
|
+
const data = generateYourData(); // Array of {x, y, color?}
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<Scatterplot
|
|
95
|
+
data={data}
|
|
96
|
+
width={800}
|
|
97
|
+
height={600}
|
|
98
|
+
onSelectionChange={(indices) => {
|
|
99
|
+
console.log('Selected points:', indices);
|
|
100
|
+
setSelectedIndices(indices);
|
|
101
|
+
}}
|
|
102
|
+
highlightedIndices={selectedIndices}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### With Lasso Selection
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { Scatterplot } from '@biohub/scatterplot';
|
|
112
|
+
|
|
113
|
+
function LassoChart() {
|
|
114
|
+
const [isLassoMode, setIsLassoMode] = useState(false);
|
|
115
|
+
const [selectedIndices, setSelectedIndices] = useState<number[]>([]);
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div>
|
|
119
|
+
<button onClick={() => setIsLassoMode(!isLassoMode)}>
|
|
120
|
+
{isLassoMode ? 'Disable' : 'Enable'} Lasso
|
|
121
|
+
</button>
|
|
122
|
+
|
|
123
|
+
<Scatterplot
|
|
124
|
+
data={data}
|
|
125
|
+
width={800}
|
|
126
|
+
height={600}
|
|
127
|
+
lassoMode={isLassoMode}
|
|
128
|
+
onSelectionChange={setSelectedIndices}
|
|
129
|
+
selectedIndices={selectedIndices}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Responsive Sizing
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
import { Scatterplot } from '@biohub/scatterplot';
|
|
140
|
+
|
|
141
|
+
function ResponsiveChart() {
|
|
142
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
143
|
+
const [size, setSize] = useState({ width: 800, height: 600 });
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const observer = new ResizeObserver((entries) => {
|
|
147
|
+
const { width, height } = entries[0].contentRect;
|
|
148
|
+
setSize({ width, height });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (containerRef.current) {
|
|
152
|
+
observer.observe(containerRef.current);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return () => observer.disconnect();
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div ref={containerRef} style={{ width: '100%', height: '100vh' }}>
|
|
160
|
+
<Scatterplot
|
|
161
|
+
data={data}
|
|
162
|
+
width={size.width}
|
|
163
|
+
height={size.height}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or use the built-in `useContainerSize` hook:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { Scatterplot, useContainerSize } from '@biohub/scatterplot';
|
|
174
|
+
|
|
175
|
+
function ResponsiveChart() {
|
|
176
|
+
const { ref, width, height } = useContainerSize();
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<div ref={ref} style={{ width: '100%', height: '100vh' }}>
|
|
180
|
+
<Scatterplot
|
|
181
|
+
data={data}
|
|
182
|
+
width={width}
|
|
183
|
+
height={height}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## API Reference
|
|
191
|
+
|
|
192
|
+
### `<Scatterplot>`
|
|
193
|
+
|
|
194
|
+
Main component for rendering interactive scatterplots.
|
|
195
|
+
|
|
196
|
+
#### Props
|
|
197
|
+
|
|
198
|
+
| Prop | Type | Default | Description |
|
|
199
|
+
|------|------|---------|-------------|
|
|
200
|
+
| `data` | `Point[]` | **required** | Array of data points with `{x, y, color?}` |
|
|
201
|
+
| `width` | `number` | **required** | Canvas width in pixels |
|
|
202
|
+
| `height` | `number` | **required** | Canvas height in pixels |
|
|
203
|
+
| `selectedIndices` | `number[]` | `[]` | Indices of selected points |
|
|
204
|
+
| `highlightedIndices` | `number[]` | `[]` | Indices of highlighted points |
|
|
205
|
+
| `onSelectionChange` | `(indices: number[]) => void` | - | Callback when selection changes |
|
|
206
|
+
| `lassoMode` | `boolean` | `false` | Enable lasso selection mode |
|
|
207
|
+
| `enableZoom` | `boolean` | `true` | Enable zoom interaction |
|
|
208
|
+
| `enablePan` | `boolean` | `true` | Enable pan interaction |
|
|
209
|
+
|
|
210
|
+
### Types
|
|
211
|
+
|
|
212
|
+
#### `Point`
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface Point {
|
|
216
|
+
x: number;
|
|
217
|
+
y: number;
|
|
218
|
+
color?: string; // Hex color (e.g., '#ff0000'), defaults to '#999999'
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Hooks
|
|
223
|
+
|
|
224
|
+
#### `useSelection(initialSelection?: number[])`
|
|
225
|
+
|
|
226
|
+
Hook for managing point selection state.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const { selectedIndices, setSelectedIndices, clearSelection } = useSelection();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
- `selectedIndices: number[]` - Current selection
|
|
234
|
+
- `setSelectedIndices: (indices: number[]) => void` - Update selection
|
|
235
|
+
- `clearSelection: () => void` - Clear all selections
|
|
236
|
+
|
|
237
|
+
#### `useContainerSize()`
|
|
238
|
+
|
|
239
|
+
Hook for responsive container sizing.
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
const { ref, width, height } = useContainerSize();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
- `ref: RefObject<HTMLDivElement>` - Attach to container element
|
|
247
|
+
- `width: number` - Container width
|
|
248
|
+
- `height: number` - Container height
|
|
249
|
+
|
|
250
|
+
### Utilities
|
|
251
|
+
|
|
252
|
+
#### `findClosestPoint(mouseX, mouseY, data, bounds, width, height)`
|
|
253
|
+
|
|
254
|
+
Find the closest point to mouse coordinates.
|
|
255
|
+
|
|
256
|
+
#### `findPointsInLasso(points, lassoPath, bounds, width, height)`
|
|
257
|
+
|
|
258
|
+
Find all points within a lasso polygon.
|
|
259
|
+
|
|
260
|
+
#### `createFlagBuffer(data.length, selectedIndices, highlightedIndices)`
|
|
261
|
+
|
|
262
|
+
Create WebGL flag buffer for point states.
|
|
263
|
+
|
|
264
|
+
## Advanced Usage
|
|
265
|
+
|
|
266
|
+
### Custom Renderer
|
|
267
|
+
|
|
268
|
+
For advanced use cases, you can use the low-level `ScatterplotGL` component:
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
import { ScatterplotGL, useSelection } from '@biohub/scatterplot';
|
|
272
|
+
|
|
273
|
+
function CustomScatterplot() {
|
|
274
|
+
const { selectedIndices, setSelectedIndices } = useSelection();
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<ScatterplotGL
|
|
278
|
+
data={data}
|
|
279
|
+
width={800}
|
|
280
|
+
height={600}
|
|
281
|
+
selectedIndices={selectedIndices}
|
|
282
|
+
onPointClick={(index) => {
|
|
283
|
+
setSelectedIndices([index]);
|
|
284
|
+
}}
|
|
285
|
+
// More control over rendering...
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Theming
|
|
292
|
+
|
|
293
|
+
The scatterplot supports theming through a `theme` prop.
|
|
294
|
+
|
|
295
|
+
### Built-in Presets
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
import { Scatterplot, lightTheme, darkTheme, highContrastTheme } from '@biohub/scatterplot';
|
|
299
|
+
|
|
300
|
+
// Use a preset
|
|
301
|
+
<Scatterplot data={data} theme={darkTheme} />
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Available presets:
|
|
305
|
+
- `lightTheme` - Light background, blue points (default)
|
|
306
|
+
- `darkTheme` - Dark background, lighter blue points
|
|
307
|
+
- `highContrastTheme` - Black background, white points, yellow lasso
|
|
308
|
+
|
|
309
|
+
### Custom Themes
|
|
310
|
+
|
|
311
|
+
Use `createTheme()` to customize specific properties:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { Scatterplot, createTheme } from '@biohub/scatterplot';
|
|
315
|
+
|
|
316
|
+
// Override specific properties (inherits from lightTheme by default)
|
|
317
|
+
const myTheme = createTheme({
|
|
318
|
+
canvas: { background: '#1a1a2e' },
|
|
319
|
+
points: { size: 8, opacity: 0.8 },
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
<Scatterplot data={data} theme={myTheme} />
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Extend any base theme:
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
import { createTheme, darkTheme } from '@biohub/scatterplot';
|
|
329
|
+
|
|
330
|
+
// Extend darkTheme with custom point size
|
|
331
|
+
const myDarkTheme = createTheme({ points: { size: 10 } }, darkTheme);
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Theme Structure
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
interface ScatterplotTheme {
|
|
338
|
+
canvas: {
|
|
339
|
+
background: string; // Canvas background color (default: '#ffffff')
|
|
340
|
+
dataPadding: number; // Padding around data in pixels (default: 20)
|
|
341
|
+
};
|
|
342
|
+
points: {
|
|
343
|
+
defaultColor: string; // Fallback point color (default: '#3498db')
|
|
344
|
+
size: number; // Point diameter in pixels (default: 5)
|
|
345
|
+
opacity: number; // Base opacity 0-1 (default: 1.0)
|
|
346
|
+
backgroundOpacity: number; // Opacity for non-selected points (default: 0.5)
|
|
347
|
+
highlightBrightness: number; // Brightness multiplier for highlights (default: 1.4)
|
|
348
|
+
highlightSizeScale: number; // Size multiplier for highlights (default: 1.3)
|
|
349
|
+
unselectedSizeScale: number; // Size multiplier for unselected points (default: 0.2)
|
|
350
|
+
};
|
|
351
|
+
lasso: {
|
|
352
|
+
fill: string; // Lasso fill color (default: 'rgba(59, 130, 246, 0.1)')
|
|
353
|
+
stroke: string; // Lasso stroke color (default: 'rgb(59, 130, 246)')
|
|
354
|
+
strokeWidth: number; // Stroke width in pixels (default: 2)
|
|
355
|
+
strokeDasharray: string; // SVG dash pattern (default: '5,5')
|
|
356
|
+
};
|
|
357
|
+
debug: {
|
|
358
|
+
background: string; // Debug panel background (default: 'rgba(0, 0, 0, 0.8)')
|
|
359
|
+
color: string; // Debug panel text color (default: '#00ff00')
|
|
360
|
+
fontFamily: string; // Debug panel font (default: 'monospace')
|
|
361
|
+
fontSize: string; // Debug panel font size (default: '12px')
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### CSS Custom Properties
|
|
367
|
+
|
|
368
|
+
DOM elements (lasso overlay, debug panel) expose CSS custom properties for external styling.
|
|
369
|
+
|
|
370
|
+
**Naming convention:** `--scatterplot-{section}-{kebab-case-property}`
|
|
371
|
+
|
|
372
|
+
| Section | Properties |
|
|
373
|
+
|---------|------------|
|
|
374
|
+
| `lasso` | `--scatterplot-lasso-fill`, `--scatterplot-lasso-stroke`, `--scatterplot-lasso-stroke-width`, `--scatterplot-lasso-stroke-dasharray` |
|
|
375
|
+
| `debug` | `--scatterplot-debug-background`, `--scatterplot-debug-color`, `--scatterplot-debug-font-family`, `--scatterplot-debug-font-size` |
|
|
376
|
+
|
|
377
|
+
Example:
|
|
378
|
+
|
|
379
|
+
```css
|
|
380
|
+
.my-chart {
|
|
381
|
+
--scatterplot-lasso-stroke: red;
|
|
382
|
+
--scatterplot-lasso-fill: rgba(255, 0, 0, 0.1);
|
|
383
|
+
--scatterplot-debug-background: navy;
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
```tsx
|
|
388
|
+
<Scatterplot data={data} className="my-chart" />
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
> **Note:** Canvas background and point properties are WebGL-only and must be configured via the `theme` prop, not CSS.
|
|
392
|
+
|
|
393
|
+
### Using className
|
|
394
|
+
|
|
395
|
+
The `className` prop is applied to the wrapper div. You can use it to:
|
|
396
|
+
|
|
397
|
+
1. **Override CSS custom properties** (as shown above)
|
|
398
|
+
2. **Target internal elements** with CSS specificity
|
|
399
|
+
|
|
400
|
+
Internal class names:
|
|
401
|
+
- `.scatterplot-lasso-polygon` - The lasso SVG polygon
|
|
402
|
+
- `.scatterplot-debug-panel` - The debug panel container
|
|
403
|
+
- `.scatterplot-debug-panel-title` - The debug panel title
|
|
404
|
+
|
|
405
|
+
Example - custom lasso styling:
|
|
406
|
+
|
|
407
|
+
```css
|
|
408
|
+
.my-chart .scatterplot-lasso-polygon {
|
|
409
|
+
stroke: hotpink;
|
|
410
|
+
stroke-width: 3px;
|
|
411
|
+
stroke-dasharray: none;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
<Scatterplot data={data} className="my-chart" />
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Performance Tips
|
|
420
|
+
|
|
421
|
+
- **Large datasets (>100K points)**: Rendering is optimized for WebGL, should maintain 60fps
|
|
422
|
+
- **Colors**: Pre-calculate colors instead of computing on each render
|
|
423
|
+
- **Selection**: Use `useMemo` to avoid re-creating selection arrays
|
|
424
|
+
- **Responsive**: Debounce resize events for better performance
|
|
425
|
+
|
|
426
|
+
## Browser Requirements
|
|
427
|
+
|
|
428
|
+
- WebGL2 support required (all modern browsers since ~2017)
|
|
429
|
+
- Chrome 56+, Firefox 51+, Safari 15+, Edge 79+
|
|
430
|
+
|
|
431
|
+
## Troubleshooting
|
|
432
|
+
|
|
433
|
+
### "WebGL context lost" error
|
|
434
|
+
|
|
435
|
+
This can happen with very large datasets or after leaving tab inactive for long periods. The component will attempt to recover automatically.
|
|
436
|
+
|
|
437
|
+
### Selection not working
|
|
438
|
+
|
|
439
|
+
Make sure you're passing `onSelectionChange` callback and updating the `selectedIndices` prop with the new selection.
|
|
440
|
+
|
|
441
|
+
### Types not found
|
|
442
|
+
|
|
443
|
+
Ensure your `tsconfig.json` includes:
|
|
444
|
+
```json
|
|
445
|
+
{
|
|
446
|
+
"compilerOptions": {
|
|
447
|
+
"moduleResolution": "node",
|
|
448
|
+
"types": ["node"]
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Development
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
# Build library
|
|
457
|
+
npm run build
|
|
458
|
+
|
|
459
|
+
# Run tests
|
|
460
|
+
npm test
|
|
461
|
+
|
|
462
|
+
# Run tests in watch mode
|
|
463
|
+
npm run test:watch
|
|
464
|
+
|
|
465
|
+
# Run tests with coverage
|
|
466
|
+
npm run test:coverage
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Running the Demo
|
|
470
|
+
|
|
471
|
+
The demo app is in the `demo/` directory and links to the local library build.
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
# First, build and link the library
|
|
475
|
+
npm run build
|
|
476
|
+
npm link
|
|
477
|
+
|
|
478
|
+
# Then run the demo
|
|
479
|
+
cd demo
|
|
480
|
+
npm install
|
|
481
|
+
npm link @biohub/scatterplot
|
|
482
|
+
npm run dev # Development server (http://localhost:5173)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
#### Production Build (Recommended for Performance Testing)
|
|
486
|
+
|
|
487
|
+
For accurate performance testing, use the production build:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
cd demo
|
|
491
|
+
npm run build # Build production bundle
|
|
492
|
+
npm run preview # Serve at http://localhost:4173
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## License
|
|
496
|
+
|
|
497
|
+
MIT
|
|
498
|
+
|
|
499
|
+
## Support
|
|
500
|
+
|
|
501
|
+
For issues and questions, please open an issue on GitHub.
|