@hema-to/regl-scatterplot 1.14.1-hemato.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.
package/README.md ADDED
@@ -0,0 +1,1110 @@
1
+ # WebGl 2D Scatterplot with Regl
2
+
3
+ [![npm version](https://img.shields.io/npm/v/regl-scatterplot.svg?color=1a8cff&style=flat-square)](https://www.npmjs.com/package/regl-scatterplot)
4
+ [![build status](https://img.shields.io/github/actions/workflow/status/flekschas/regl-scatterplot/build.yml?branch=master&color=139ce9&style=flat-square)](https://github.com/flekschas/regl-scatterplot/actions?query=workflow%3Abuild)
5
+ [![file size](http://img.badgesize.io/https://unpkg.com/regl-scatterplot/dist/regl-scatterplot.min.js?compression=gzip&color=0dacd4&style=flat-square)](https://bundlephobia.com/result?p=regl-scatterplot)
6
+ [![DOI](https://img.shields.io/badge/JOSS-10.21105/joss.05275-06bcbe.svg?style=flat-square)](https://doi.org/10.21105/joss.05275)
7
+ [![regl-scatterplot demo](https://img.shields.io/badge/demo-online-00cca9.svg?style=flat-square)](https://flekschas.github.io/regl-scatterplot/)
8
+
9
+ A highly-scalable pan-and-zoomable scatter plot library that uses WebGL through [Regl](https://github.com/regl-project/regl). This library sacrifices feature richness for speed to allow rendering up to **20 million points** (depending on your hardware of course) including fast lasso selection. Further, the [footprint of regl-scatterplot](https://bundlephobia.com/result?p=regl-scatterplot) is kept small. **NEW:** Python lovers please see [jscatter](https://github.com/flekschas/jupyter-scatter): a Jupyter Notebook/Lab widget that uses regl-scatterplot.
10
+
11
+ <p>
12
+ <img src="https://user-images.githubusercontent.com/932103/62905669-7679f380-bd39-11e9-9528-86ee56d6dfba.gif" />
13
+ </p>
14
+
15
+ **Demo:** https://flekschas.github.io/regl-scatterplot/
16
+
17
+ **Live Playground:** https://observablehq.com/@flekschas/regl-scatterplot
18
+
19
+ **Default Interactions:**
20
+
21
+ - **Pan**: Click and drag your mouse.
22
+ - **Zoom**: Scroll vertically.
23
+ - **Rotate**: While pressing <kbd>ALT</kbd>, click and drag your mouse.
24
+ - **Select a dot**: Click on a dot with your mouse.
25
+ - **Select multiple dots**:
26
+
27
+ - While pressing <kbd>SHIFT</kbd>, click and drag your mouse. All items within the lasso will be selected.
28
+ - Upon activating the lasso on long press (i.e., `lassoOnLongPress: true`) you can click and hold anywhere on the plot, and a circle will appear at your mouse cursor. Wait until the circle is closed, then drag your mouse to start lassoing.
29
+ <details><summary>Click here to see how it works</summary>
30
+ <p>
31
+
32
+ ![Lassso on Long Press](https://github.com/user-attachments/assets/5e6a7c2a-5686-4711-9b3d-36d45a96ca69)
33
+
34
+ </p>
35
+ </details>
36
+
37
+ - Upon activating the lasso initiator (i.e., `lassoInitiator: true`) you can click into the background and a circle will appear under your mouse cursor. Click inside this circle and drag your mouse to start lassoing.
38
+ <details><summary>Click here to see how it works</summary>
39
+ <p>
40
+
41
+ ![Lasso Initiator](https://user-images.githubusercontent.com/932103/106489598-f42c4480-6482-11eb-8286-92a9956e1d20.gif)
42
+
43
+ </p>
44
+ </details>
45
+
46
+ - **Deselect**: Double-click onto an empty region.
47
+
48
+ Note, you can remap `rotate` and `lasso` to other modifier keys via the `keyMap` option!
49
+
50
+ **Supported Visual Encodings:**
51
+
52
+ - x/y point position (obviously)
53
+ - categorical and continuous color encoding (including opacity)
54
+ - categorical and continuous size encoding
55
+ - point connections (stemming, for example, from time series data)
56
+
57
+ ## Install
58
+
59
+ ```sh
60
+ npm i regl-scatterplot
61
+ ```
62
+
63
+ _FYI, if you're using `npm` version prior to 7, you have to install regl-scatterplot's peer dependencies (`regl` and `pub-sub-es`) manually._
64
+
65
+ ## Getting started
66
+
67
+ ### Basic Example
68
+
69
+ ```javascript
70
+ import createScatterplot from 'regl-scatterplot';
71
+
72
+ const canvas = document.querySelector('#canvas');
73
+
74
+ const { width, height } = canvas.getBoundingClientRect();
75
+
76
+ const scatterplot = createScatterplot({
77
+ canvas,
78
+ width,
79
+ height,
80
+ pointSize: 5,
81
+ });
82
+
83
+ const points = new Array(10000)
84
+ .fill()
85
+ .map(() => [-1 + Math.random() * 2, -1 + Math.random() * 2, color]);
86
+
87
+ scatterplot.draw(points);
88
+ ```
89
+
90
+ **IMPORTANT:** Your points positions need to be normalized to `[-1, 1]` (normalized device coordinates). Why? Regl-scatterplot is designed to be a lower-level library, whose primary purpose is speed. As such it expects you to normalize the data upfront.
91
+
92
+ ### Color, Opacity, and Size Encoding
93
+
94
+ In regl-scatterplot, points can be associated with two data values. These two values are defined as the third and forth component of the point quadruples (`[x, y, value, value]`). For instance:
95
+
96
+ ```javascript
97
+ scatterplot.draw([
98
+ [0.2, -0.1, 0, 0.1337],
99
+ [0.3, 0.1, 1, 0.3371],
100
+ [-0.9, 0.8, 2, 0.3713],
101
+ ]);
102
+ ```
103
+
104
+ These two values can be visually encoded as the color, opacity, or the size. Integers are treated as categorical data and floats that range between [0, 1] are treated as continuous values. In the example above, the first point value would be treated as categorical data and the second would be treated as continuous data.
105
+
106
+ In the edge case that you have continuous data but all data points are either `0` or `1` you can manually set the data type via the [`zDataType` and `wDatatype` draw options](#scatterplot.draw).
107
+
108
+ To encode the two point values use the `colorBy`, `opacityBy`, and `sizeBy` property as follows:
109
+
110
+ ```javascript
111
+ scatterplot.set({
112
+ opacityBy: 'valueA',
113
+ sizeBy: 'valueA',
114
+ colorBy: 'valueB',
115
+ });
116
+ ```
117
+
118
+ In this example we would encode the first categorical point values (`[0, 1, 2]`) as the point opacity and size. The second continuous point values (`[0.1337, 0.3317, 0.3713]`) would be encoded as the point color.
119
+
120
+ The last thing we need to tell regl-scatterplot is what those point values should be translated to. We do this by specifying a color, opacity, and size map as an array of colors, opacities, and sizes as follows:
121
+
122
+ ```javascript
123
+ scatterplot.set({
124
+ pointColor: ['#000000', '#111111', ..., '#eeeeee', '#ffffff'],
125
+ pointSize: [2, 4, 8],
126
+ opacity: [0.5, 0.75, 1],
127
+ });
128
+ ```
129
+
130
+ You can encode a point data value in multiple ways. For instance, as you can see in the example above, the categorical fist data value is encoded via the point size _and_ opacity.
131
+
132
+ **What if I have more than two values associated to a point?** Unfortunately, this isn't supported currently. In case you're wondering, this limitation is due to how we store the point data. The whole point state is encoded as an RGBA texture where the x and y coordinate are stored as the red and green color components and the first and second data value are stored in the blue and alpha component of the color. However, this limitation might be addressed in future versions so make sure to check back or, even better, start a pull request!
133
+
134
+ **Why can't I specify a range function instead of a map?** Until we have implemented enough scale functions in the shader it's easier to let _you_ pre-compute the map. For instance, if you wanted to encode a continuous values on a log scale of point size, you can simply do `pointSize: Array(100).fill().map((v, i) => Math.log(i + 1) + 1)`.
135
+
136
+ [Code Example](example/index.js) | [Demo](https://flekschas.github.io/regl-scatterplot/index.html)
137
+
138
+ ### Connecting points
139
+
140
+ You can connect points visually using spline curves by adding a 5th component to your point data and setting `showPointConnections: true`.
141
+
142
+ The 5th component is needed to identify which points should be connected. By default, the order of how the points are connected is defined by the order in which the points appear in your data.
143
+
144
+ ```javascript
145
+ const points = [
146
+ [1, 1, 0, 0, 0],
147
+ [2, 2, 0, 0, 0],
148
+ [3, 3, 0, 0, 1],
149
+ [4, 4, 0, 0, 1],
150
+ [5, 5, 0, 0, 0],
151
+ ];
152
+ ```
153
+
154
+ In the example above, the points would be connected as follows:
155
+
156
+ ```
157
+ 0 -> 1 -> 4
158
+ 2 -> 3
159
+ ```
160
+
161
+ **Line Ordering:**
162
+
163
+ To explicitely define or change the order of how points are connected, you can define a 6th component as follows:
164
+
165
+ ```javascript
166
+ const points = [
167
+ [1, 1, 0, 0, 0, 2],
168
+ [2, 2, 0, 0, 0, 0],
169
+ [3, 3, 0, 0, 1, 1],
170
+ [4, 4, 0, 0, 1, 0],
171
+ [5, 5, 0, 0, 0, 1],
172
+ ];
173
+ ```
174
+
175
+ would lead tp the following line segment ordering:
176
+
177
+ ```
178
+ 1 -> 4 -> 0
179
+ 3 -> 2
180
+ ```
181
+
182
+ Note, to visualize the point connections, make sure `scatterplot.set({ showPointConnection: true })` is set!
183
+
184
+ [Code Example](example/connected-points.js) | [Demo](https://flekschas.github.io/regl-scatterplot/connected-points.html)
185
+
186
+ ### Synchronize D3 x and y scales with the scatterplot view
187
+
188
+ Under the hood regl-scatterplot uses a [2D camera](https://github.com/flekschas/dom-2d-camera), which you can either get via `scatterplot.get('camera')` or `scatterplot.subscribe('view', ({ camera }) => {})`. You can use the camera's `view` matrix to compute the x and y scale domains. However, since this is tedious, regl-scatterplot allows you to specify D3 x and y scales that will automatically be synchronized. For example:
189
+
190
+ ```javascript
191
+ const xScale = scaleLinear().domain([0, 42]);
192
+ const yScale = scaleLinear().domain([-5, 5]);
193
+ const scatterplot = createScatterplot({
194
+ canvas,
195
+ width,
196
+ height,
197
+ xScale,
198
+ yScale,
199
+ });
200
+ ```
201
+
202
+ Now whenever you pan or zoom, the domains of `xScale` and `yScale` will be updated according to your current view. Note, the ranges are automatically set to the width and height of your `canvas` object.
203
+
204
+ [Code Example](example/axes.js) | [Demo](https://flekschas.github.io/regl-scatterplot/axes.html)
205
+
206
+ ### Translating Point Coordinates to Screen Coordinates
207
+
208
+ Imagine you want to render additional features on top of points points, for which you need to know where on the canvas points are drawn. To determine the screen coordinates of points you can use [D3 scales](#synchronize-d3-x-and-y-scales-with-the-scatterplot-view) and `scatterplot.get('pointsInView')` as follows:
209
+
210
+ ```javascript
211
+ const points = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);
212
+ const [xScale, yScale] = [scaleLinear().domain([0, 42]), scaleLinear().domain([0, 1])];
213
+
214
+ const scatterplot = createScatterplot({ ..., xScale, yScale });
215
+ scatterplot.draw(points);
216
+
217
+ scatterplot.subscribe('view', ({ xScale, yScale }) => {
218
+ console.log('pointsInScreenCoords', scatterplot.get('pointsInView').map((pointIndex) => [
219
+ xScale(points[pointIndex][0]),
220
+ yScale(points[pointIndex][1])
221
+ ]));
222
+ });
223
+ ```
224
+
225
+ [Code Example](example/text-labels.js) | [Demo](https://flekschas.github.io/regl-scatterplot/text-labels.html)
226
+
227
+ ### Transition Points
228
+
229
+ To make sense of two different states of points, it can help to show an animation by transitioning the points from their first to their second location. To do so, simple `draw()` the new points as follows:
230
+
231
+ ```javascript
232
+ const initialPoints = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);
233
+ const finalPoints = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);
234
+
235
+ const scatterplot = createScatterplot({ ... });
236
+ scatterplot.draw(initialPoints).then(() => {
237
+ scatterplot.draw(finalPoints, { transition: true });
238
+ })
239
+ ```
240
+
241
+ It's important that the number of points is the same for the two `draw()` calls. Also note that the point correspondence is determined by their index.
242
+
243
+ [Code Example](example/transition.js) | [Demo](https://flekschas.github.io/regl-scatterplot/transition.html)
244
+
245
+ ### Zoom to Points
246
+
247
+ Sometimes it can be useful to programmatically zoom to a set of points. In regl-scatterplot you can do this with the `zoomToPoints()` method as follows:
248
+
249
+ ```javascript
250
+ const points = Array.from({ length: 100 }, () => [Math.random() * 42, Math.random()]);
251
+
252
+ const scatterplot = createScatterplot({ ... });
253
+ scatterplot.draw(initialPoints).then(() => {
254
+ // We'll select the first five points...
255
+ scatterplot.select([0, 1, 2, 3, 4]);
256
+ // ...and zoom into them
257
+ scatterplot.zoomToPoints([0, 1, 2, 3, 4], { transition: true })
258
+ })
259
+ ```
260
+
261
+ Note that the zooming can be smoothly transitioned when `{ transition: true }` is passed to the function.
262
+
263
+ [Code Example](example/multiple-instances.js) | [Demo](https://flekschas.github.io/regl-scatterplot/multiple-instances.html)
264
+
265
+ ### Update only the Z/W point coordinates
266
+
267
+ If you only want to update the z/w points coordinates that can be used for encoding te point color, opacity, or size, you can improve the redrawing performance by reusing the existing spatial index, which is otherwise recomputed every time you draw new points.
268
+
269
+ ```javascript
270
+ const x = (length) => Array.from({ length }, () => -1 + Math.random() * 2);
271
+ const y = (length) => Array.from({ length }, () => -1 + Math.random() * 2);
272
+ const z = (length) => Array.from({ length }, () => Math.round(Math.random()));
273
+ const w = (length) => Array.from({ length }, () => Math.random());
274
+
275
+ const numPoints = 1000000;
276
+ const points = {
277
+ x: x(numPoints),
278
+ y: y(numPoints),
279
+ z: z(numPoints),
280
+ w: w(numPoints),
281
+ };
282
+
283
+ const scatterplot = createScatterplot({ ... });
284
+ scatterplot.draw(initialPoints).then(() => {
285
+ // After the initial draw, we retrieve and save the KDBush spatial index.
286
+ const spatialIndex = scatterplot.get('spatialIndex');
287
+ setInterval(() => {
288
+ // Update Z and W values
289
+ points.z = z(numPoints);
290
+ points.w = w(numPoints);
291
+
292
+ // We redraw the scatter plot with the updates points. Importantly, since
293
+ // the x/y coordinates remain unchanged we pass in the saved spatial index
294
+ // to avoid having to re-index the points.
295
+ scatterplot.draw(points, { spatialIndex });
296
+ }, 2000);
297
+ })
298
+ ```
299
+
300
+ ## API
301
+
302
+ ### Constructors
303
+
304
+ <a name="createScatterplot" href="#createScatterplot">#</a> <b>createScatterplot</b>(<i>options = {}</i>)
305
+
306
+ **Returns:** a new scatterplot instance.
307
+
308
+ **Options:** is an object that accepts any of the [properties](#properties).
309
+
310
+ <a name="createRenderer" href="#createRenderer">#</a> <b>createRenderer</b>(<i>options = {}</i>)
311
+
312
+ **Returns:** a new [Renderer](#renderer) instance with appropriate extensions being enabled.
313
+
314
+ **Options:** is an object that accepts any of the following optional properties:
315
+
316
+ - `regl`: a Regl instance to be used for rendering.
317
+ - `canvas`: background color of the scatterplot.
318
+ - `gamma`: the gamma value for alpha blending.
319
+
320
+ <a name="createRegl" href="#createRegl">#</a> <b>createRegl</b>(<i>canvas</i>)
321
+
322
+ **Returns:** a new [Regl](https://github.com/regl-project/regl) instance with appropriate extensions being enabled.
323
+
324
+ **Canvas:** the canvas object on which the scatterplot will be rendered on.
325
+
326
+ <a name="createTextureFromUrl" href="#createTextureFromUrl">#</a> <b>createTextureFromUrl</b>(<i>regl</i>, <i>url</i>)
327
+
328
+ _DEPRECATED! Use [`scatterplot.createTextureFromUrl()`](#scatterplot.createTextureFromUrl) instead._
329
+
330
+ ### Methods
331
+
332
+ <a name="scatterplot.draw" href="#scatterplot.draw">#</a> scatterplot.<b>draw</b>(<i>points</i>, <i>options</i>)
333
+
334
+ Sets and draws `points`. Importantly, the `points`' x and y coordinates need to have been normalized to `[-1, 1]` (normalized device coordinates). The two additional values (`valueA` and `valueB`) need to be normalized to `[0, 1]` (if they represent continuous data) or `[0, >1]` (if they represent categorical data).
335
+
336
+ Note that repeatedly calling this method without specifying `points` will not clear previously set points. To clear points use [`scatterplot.clear()`](#scatterplot.clear).
337
+
338
+ **Arguments:**
339
+
340
+ - `points` can either be an array of quadruples (row-oriented) or an object of arrays (column-oriented):
341
+ - For row-oriented data, each nested array defines a point data of the form `[x, y, ?valueA, ?valueB, ?line, ?lineOrder]`. `valueA` and `valueB` are optional and can be used for [color, opacity, or size encoding](#property-by). `line` and `lineOrder` are also optional and can be used to [visually connect points by lines](#connecting-points).
342
+ - For column-oriented data, the object must be of the form `{ x: [], y: [], ?valueA: [], ?valueB: [], ?line: [], ?lineOrder: [] }`.
343
+ - `options` is an object with the following properties:
344
+ - `showPointConnectionsOnce` [default: `false`]: if `true` and if points contain a `line` component/dimension the points will be visually conntected.
345
+ - `transition` [default: `false`]: if `true` and if the current number of points equals `points.length`, the current points will be transitioned to the new points
346
+ - `transitionDuration` [default: `500`]: the duration in milliseconds over which the transition should occur
347
+ - `transitionEasing` [default: `cubicInOut`]: the easing function, which determines how intermediate values of the transition are calculated
348
+ - `preventFilterReset` [default: `false`]: if `true` and if the number of new points is the same as the current number of points, the current point filter will not be reset
349
+ - `hover` [default: `undefined`]: a shortcut for [`hover()`](#scatterplot.hover). This option allows to programmatically hover a point by specifying a point index
350
+ - `select` [default: `undefined`]: a shortcut for [`select()`](#scatterplot.select). This option allows to programmatically select points by specifying a list of point indices
351
+ - `filter` [default: `undefined`]: a shortcut for [`filter()`](#scatterplot.filter). This option allows to programmatically filter points by specifying a list of point indices
352
+ - `zDataType` [default: `undefined`]: This option allows to manually set the data type of the z/valueA value to either `continuous` or `categorical`. By default the data type is [determined automatically](#color-opacity-and-size-encoding).
353
+ - `wDataType` [default: `undefined`]: This option allows to manually set the data type of the w/valyeB value to either `continuous` or `categorical`. By default the data type is [determined automatically](#color-opacity-and-size-encoding).
354
+ - `spatialIndex` [default: `undefined`]: This option allows to pass in the array buffer of [KDBush](https://github.com/mourner/kdbush) to skip the manual creation of the spatial index. Caution: only use this option if you know what you're doing! The point data is not validated against the spatial index.
355
+
356
+ **Returns:** a Promise object that resolves once the points have been drawn or transitioned.
357
+
358
+ **Examples:**
359
+
360
+ ```javascript
361
+ const points = [
362
+ [
363
+ // The relative X position in [-1,1] (normalized device coordinates)
364
+ 0.9,
365
+ // The relative Y position in [-1,1] (normalized device coordinates)
366
+ 0.3,
367
+ // The category, which defaults to `0` if `undefined`
368
+ 0,
369
+ // A continuous value between [0,1], which defaults to `0` if `undefined`
370
+ 0.5,
371
+ ],
372
+ ];
373
+
374
+ scatterplot.draw(points);
375
+
376
+ // You can now do something else like changing the point size etc.
377
+
378
+ // If we want to animate the transition of our point from above to another
379
+ // x,y position, we can also do this by drawing a new point while enableing
380
+ // transition via the `options` argument.
381
+ scatterplot.draw([[0.6, 0.6, 0, 0.6]], { transition: true });
382
+
383
+ // Let's unset the points. To do so, pass in an empty array to `draw()`.
384
+ // Or alternatively, call `scatterplot.clear()`
385
+ scatterplot.draw([]);
386
+
387
+ // You can also specify the point data in a column-oriented format. The
388
+ // following call will draw three points: (1,3), (2,2), and (3,1)
389
+ scatterplot.draw({
390
+ x: [1, 2, 3],
391
+ y: [3, 2, 1],
392
+ });
393
+
394
+ // Finally, you can also specify which point will be hovered, which points will
395
+ // be selected, and which points will be filtered. These options are useful to
396
+ // avoid a flicker which would occur if `hover()`, `select()`, and `filter()`
397
+ // are called after `draw()`.
398
+ scatterplot.draw(
399
+ { x: [1, 2, 3], y: [3, 2, 1] },
400
+ { hover: 0, selected: [0, 1], filter: [0, 2] }
401
+ );
402
+ ```
403
+
404
+ <a name="scatterplot.redraw" href="#scatterplot.redraw">#</a> scatterplot.<b>redraw</b>()
405
+
406
+ Redraw the scatter plot at the next animation frame.
407
+
408
+ Note, that regl-scatterlot automatically redraws the scatter plot whenever the
409
+ view changes in some ways. So theoretically, there should never be a need to
410
+ call this function!
411
+
412
+ <a name="scatterplot.clear" href="#scatterplot.clear">#</a> scatterplot.<b>clear</b>()
413
+
414
+ Clears previously drawn points, point connections, and annotations.
415
+
416
+ <a name="scatterplot.clearPoints" href="#scatterplot.clearPoints">#</a> scatterplot.<b>clearPoints</b>()
417
+
418
+ Clears previously drawn points and point connections.
419
+
420
+ <a name="scatterplot.clearPointConnections" href="#scatterplot.clearPointConnections">#</a> scatterplot.<b>clearPointConnections</b>()
421
+
422
+ Clears previously point connections.
423
+
424
+ <a name="scatterplot.drawAnnotations" href="#scatterplot.drawAnnotations">#</a> scatterplot.<b>drawAnnotations</b>(<i>annotations</i>)
425
+
426
+ Draw line-based annotations of the following kind in normalized device coordinates:
427
+
428
+ - Horizontal line
429
+ - Vertical line
430
+ - Rectangle
431
+ - Polygon
432
+
433
+ **Arguments:**
434
+
435
+ - `annotations` is expected to be a list of the following objects:
436
+ - For horizontal lines: `{ y: number, x1?: number, x2?: number, lineColor?: Color, lineWidth?: number }`
437
+ - For vertical lines: `{ x: number, y1?: number, y2?: number, lineColor?: Color, lineWidth?: number }`
438
+ - For rectangle : `{ x1: number, y1: number, x2: number, y2: number, lineColor?: Color, lineWidth?: number }` or `{ x: number, y: number, width: number, height: number, lineColor?: Color, lineWidth?: number }`
439
+ - For polygons or lines: `{ vertices: [number, number][], lineColor?: Color, lineWidth?: number }`
440
+
441
+ **Returns:** a Promise object that resolves once the annotations have been drawn or transitioned.
442
+
443
+ **Examples:**
444
+
445
+ ```javascript
446
+ const scatterplot = createScatterplot({
447
+ ...,
448
+ annotationLineColor: [1, 1, 1, 0.1], // Default line color
449
+ annotationLineWidth: 1, // Default line width
450
+ });
451
+
452
+ scatterplot.draw({
453
+ x: Array.from({ length: 10000 }, () => -1 + Math.random() * 2),
454
+ y: Array.from({ length: 10000 }, () => -1 + Math.random() * 2),
455
+ });
456
+
457
+ scatterplot.drawAnnotations([
458
+ // Horizontal line
459
+ { y: 0 },
460
+ // Vertical line
461
+ { x: 0 },
462
+ // Rectangle
463
+ {
464
+ x1: -0.5, y1: -0.5, x2: 0.5, y2: 0.5,
465
+ lineColor: [1, 0, 0, 0.33],
466
+ lineWidth: 2,
467
+ },
468
+ // Polygon
469
+ {
470
+ vertices: [[-1, 0], [0, 1], [1, 0], [0, -1], [-1, 0]],
471
+ lineColor: [1, 1, 0, 0.33],
472
+ lineWidth: 3,
473
+ },
474
+ ]);
475
+ ```
476
+
477
+ <a name="scatterplot.clearAnnotations" href="#scatterplot.clearAnnotations">#</a> scatterplot.<b>clearAnnotations</b>()
478
+
479
+ Clears previously drawn annotations.
480
+
481
+ <a name="scatterplot.get" href="#scatterplot.set">#</a> scatterplot.<b>get</b>(<i>property</i>)
482
+
483
+ **Arguments:**
484
+
485
+ - `property` is a string referencing a [property](#properties).
486
+
487
+ **Returns:** the property value.
488
+
489
+ <a name="scatterplot.set" href="#scatterplot.set">#</a> scatterplot.<b>set</b>(<i>properties = {}</i>)
490
+
491
+ **Arguments:**
492
+
493
+ - `properties` is an object of key-value pairs. [See below for a list of all properties.](#properties)
494
+
495
+ <a name="scatterplot.select" href="#scatterplot.select">#</a> scatterplot.<b>select</b>(<i>points</i>, <i>options = {}</i>)
496
+
497
+ Select some points, such that they get visually highlighted. This will trigger a `select` event unless `options.preventEvent === true`.
498
+
499
+ **Arguments:**
500
+
501
+ - `points` is an array of point indices referencing the points that you want to select.
502
+ - `options` [optional] is an object with the following properties:
503
+ - `preventEvent`: if `true` the `select` will not be published.
504
+
505
+ **Examples:**
506
+
507
+ ```javascript
508
+ // Let's say we have three points
509
+ scatterplot.draw([
510
+ [0.1, 0.1],
511
+ [0.2, 0.2],
512
+ [0.3, 0.3],
513
+ ]);
514
+
515
+ // To select the first and second point we have to do
516
+ scatterplot.select([0, 1]);
517
+ ```
518
+
519
+ <a name="scatterplot.deselect" href="#scatterplot.deselect">#</a> scatterplot.<b>deselect</b>(<i>options = {}</i>)
520
+
521
+ Deselect all selected points. This will trigger a `deselect` event unless `options.preventEvent === true`.
522
+
523
+ <a name="scatterplot.filter" href="#scatterplot.filter">#</a> scatterplot.<b>filter</b>(<i>points</i>, <i>options = {}</i>)
524
+
525
+ Filter down the currently drawn points, such that all points that are not included in the filter are visually and interactivelly hidden. This will trigger a `filter` event unless `options.preventEvent === true`.
526
+
527
+ Note: filtering down points can affect previously selected points. Selected points that are filtered out are also deselected.
528
+
529
+ **Arguments:**
530
+
531
+ - `points` is an array of indices referencing the points that you want to filter down to.
532
+ - `options` [optional] is an object with the following properties:
533
+ - `preventEvent`: if `true` the `select` will not be published.
534
+
535
+ **Examples:**
536
+
537
+ ```javascript
538
+ // Let's say we have three points
539
+ scatterplot.draw([
540
+ [0.1, 0.1],
541
+ [0.2, 0.2],
542
+ [0.3, 0.3],
543
+ ]);
544
+
545
+ // To only show the first and second point we have to do
546
+ scatterplot.filter([0, 1]);
547
+ ```
548
+
549
+ <a name="scatterplot.unfilter" href="#scatterplot.unfilter">#</a> scatterplot.<b>unfilter</b>(<i>options = {}</i>)
550
+
551
+ Reset previously filtered out points. This will trigger an `unfilter` event unless `options.preventEvent === true`.
552
+
553
+ <a name="scatterplot.hover" href="#scatterplot.hover">#</a> scatterplot.<b>hover</b>(<i>point</i>, <i>options = {}</i>)
554
+
555
+ Programmatically hover a point, such that it gets visually highlighted. This will trigger a `pointover` or `pointout` event unless `options.preventEvent === true`.
556
+
557
+ **Arguments:**
558
+
559
+ - `point` is the point index referring to the point you want to hover.
560
+ - `options` [optional] is an object with the following properties:
561
+ - `showReticleOnce`: if `true` the reticle will be shown once, even if `showReticle === false`.
562
+ - `preventEvent`: if `true` the `pointover` and `pointout` will not be published.
563
+
564
+ **Examples:**
565
+
566
+ ```javascript
567
+ scatterplot.draw([
568
+ [0.1, 0.1],
569
+ [0.2, 0.2],
570
+ [0.3, 0.3],
571
+ ]);
572
+
573
+ scatterplot.hover(1); // To hover the second point
574
+ ```
575
+
576
+ **Arguments:**
577
+
578
+ - `options` [optional] is an object with the following properties:
579
+ - `preventEvent`: if `true` the `deselect` will not be published.
580
+
581
+ <a name="scatterplot.zoomToPoints" href="#scatterplot.zoomToPoints">#</a> scatterplot.<b>zoomToPoints</b>(<i>points</i>, <i>options = {}</i>)
582
+
583
+ Zoom to a set of points
584
+
585
+ **Arguments:**
586
+
587
+ - `points` is an array of point indices.
588
+ - `options` [optional] is an object with the following properties:
589
+ - `padding`: [default: `0`]: relative padding around the bounding box of the points to zoom to
590
+ - `transition` [default: `false`]: if `true`, the camera will smoothly transition to its new position
591
+ - `transitionDuration` [default: `500`]: the duration in milliseconds over which the transition should occur
592
+ - `transitionEasing` [default: `cubicInOut`]: the easing function, which determines how intermediate values of the transition are calculated
593
+
594
+ **Examples:**
595
+
596
+ ```javascript
597
+ // Let's say we have three points
598
+ scatterplot.draw([
599
+ [0.1, 0.1],
600
+ [0.2, 0.2],
601
+ [0.3, 0.3],
602
+ ]);
603
+
604
+ // To zoom to the first and second point we have to do
605
+ scatterplot.zoomToPoints([0, 1]);
606
+ ```
607
+
608
+ <a name="scatterplot.zoomToOrigin" href="#scatterplot.zoomToOrigin">#</a> scatterplot.<b>zoomToOrigin</b>(<i>options = {}</i>)
609
+
610
+ Zoom to the original camera position. This is similar to resetting the view
611
+
612
+ **Arguments:**
613
+
614
+ - `options` [optional] is an object with the following properties:
615
+ - `transition` [default: `false`]: if `true`, the camera will smoothly transition to its new position
616
+ - `transitionDuration` [default: `500`]: the duration in milliseconds over which the transition should occur
617
+ - `transitionEasing` [default: `cubicInOut`]: the easing function, which determines how intermediate values of the transition are calculated
618
+
619
+ <a name="scatterplot.zoomToLocation" href="#scatterplot.zoomToLocation">#</a> scatterplot.<b>zoomToLocation</b>(<i>target</i>, <i>distance</i>, <i>options = {}</i>)
620
+
621
+ Zoom to a specific location, specified in normalized device coordinates. This function is similar to [`scatterplot.lookAt()`](#scatterplot.lookAt), however, it allows to smoothly transition the camera position.
622
+
623
+ **Arguments:**
624
+
625
+ - `target` the camera target given as a `[x, y]` tuple.
626
+ - `distance` the camera distance to the target given as a number between `]0, Infinity]`. The smaller the number the closer moves the camera, i.e., the more the view is zoomed in.
627
+ - `options` [optional] is an object with the following properties:
628
+ - `transition` [default: `false`]: if `true`, the camera will smoothly transition to its new position
629
+ - `transitionDuration` [default: `500`]: the duration in milliseconds over which the transition should occur
630
+ - `transitionEasing` [default: `cubicInOut`]: the easing function, which determines how intermediate values of the transition are calculated
631
+
632
+ **Examples:**
633
+
634
+ ```javascript
635
+ scatterplot.zoomToLocation([0.5, 0.5], 0.5, { transition: true });
636
+ // => This will make the camera zoom into the top-right corner of the scatter plot
637
+ ```
638
+
639
+ <a name="scatterplot.zoomToArea" href="#scatterplot.zoomToArea">#</a> scatterplot.<b>zoomToArea</b>(<i>rectangle</i>, <i>options = {}</i>)
640
+
641
+ Zoom to a specific area specified by a recangle in normalized device coordinates.
642
+
643
+ **Arguments:**
644
+
645
+ - `rectangle` the rectangle must come in the form of `{ x, y, width, height }`.
646
+ - `options` [optional] is an object with the following properties:
647
+ - `transition` [default: `false`]: if `true`, the camera will smoothly transition to its new position
648
+ - `transitionDuration` [default: `500`]: the duration in milliseconds over which the transition should occur
649
+ - `transitionEasing` [default: `cubicInOut`]: the easing function, which determines how intermediate values of the transition are calculated
650
+ - `preventFilterReset` [default: `false`]: if `true` and if the number of new points equals the number of already drawn points, the point filter set is not being reset.
651
+
652
+ **Examples:**
653
+
654
+ ```javascript
655
+ scatterplot.zoomToArea(
656
+ { x: 0, y: 0, width: 1, height: 1 },
657
+ { transition: true }
658
+ );
659
+ // => This will make the camera zoom into the top-right corner of the scatter plot
660
+ ```
661
+
662
+ <a name="scatterplot.getScreenPosition" href="#scatterplot.getScreenPosition">#</a> scatterplot.<b>getScreenPosition</b>(<i>pointIdx</i>)
663
+
664
+ Get the screen position of a point
665
+
666
+ **Arguments:**
667
+
668
+ - `pointIdx` is a point indix.
669
+
670
+ **Examples:**
671
+
672
+ ```javascript
673
+ // Let's say we have a 100x100 pixel scatter plot with three points
674
+ const scatterplot = createScatterplot({ width: 100, height: 100 });
675
+ scatterplot.draw([
676
+ [-1, -1],
677
+ [0, 0],
678
+ [1, 1],
679
+ ]);
680
+
681
+ // To retrieve the screen position of the second point you can call. If we
682
+ // haven't panned and zoomed, the returned position should be `50, 50`
683
+ scatterplot.getScreenPosition(1);
684
+ // => [50, 50]
685
+ ```
686
+
687
+ <a name="scatterplot.lookAt" href="#scatterplot.lookAt">#</a> scatterplot.<b>lookAt</b>(<i>view</i>, <i>options = {}</i>)
688
+
689
+ Update the camera's view matrix to change the viewport. This will trigger a `view` event unless `options.preventEvent === true`.
690
+
691
+ _Note, this API is a shorthand to `scatterplot.set({ 'cameraView': view })` with the additional features of allowing to prevent `view` events._
692
+
693
+ <a name="scatterplot.destroy" href="#scatterplot.destroy">#</a> scatterplot.<b>destroy</b>()
694
+
695
+ Destroys the scatterplot instance by disposing all event listeners, the pubSub
696
+ instance, regl, and the camera.
697
+
698
+ <a name="scatterplot.refresh" href="#scatterplot.refresh">#</a> scatterplot.<b>refresh</b>()
699
+
700
+ Refreshes the viewport of the scatterplot's regl instance.
701
+
702
+ <a name="scatterplot.reset" href="#scatterplot.reset">#</a> scatterplot.<b>reset</b>(<i>options</i>)
703
+
704
+ Sets the view back to the initially defined view. This will trigger a `view` event unless `options.preventEvent === true`.
705
+
706
+ <a name="scatterplot.export" href="#scatterplot.export">#</a> scatterplot.<b>export</b>(<i>options</i>)
707
+
708
+ **Arguments:**
709
+
710
+ - `options` is an object for customizing the render settings during the export:
711
+ - `scale`: is a float number allowning to adjust the exported image size
712
+ - `antiAliasing`: is a float allowing to adjust the anti-aliasing factor
713
+ - `pixelAligned`: is a Boolean allowing to adjust the point alignment with the pixel grid
714
+
715
+ **Returns:** an [`ImageData`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) object if `option` is `undefined`. Otherwise it returns a Promise resolving to an [`ImageData`](https://developer.mozilla.org/en-US/docs/Web/API/ImageData) object.
716
+
717
+ <a name="scatterplot.subscribe" href="#scatterplot.subscribe">#</a> scatterplot.<b>subscribe</b>(<i>eventName</i>, <i>eventHandler</i>)
718
+
719
+ Subscribe to an event.
720
+
721
+ **Arguments:**
722
+
723
+ - `eventName` needs to be [a valid event name](#events).
724
+ - `eventHandler` needs to be a callback function that can receive the payload.
725
+
726
+ **Returns:** an unsubscriber object that can be passed into [`unsubscribe()`](#scatterplot.unsubscribe).
727
+
728
+ <a name="scatterplot.unsubscribe" href="#scatterplot.unsubscribe">#</a> scatterplot.<b>unsubscribe</b>(<i>eventName</i>, <i>eventHandler</i>)
729
+
730
+ Unsubscribe from an event. See [`scatterplot.subscribe()`](#scatterplot.subscribe) for a list of all
731
+ events.
732
+
733
+ <a name="scatterplot.createTextureFromUrl" href="#scatterplot.createTextureFromUrl">#</a> scatterplot.<b>createTextureFromUrl</b>(<i>url</i>)
734
+
735
+ **Returns:** a Promise that resolves to a [Regl texture](https://github.com/regl-project/regl/blob/gh-pages/API.md#textures) that can be used, for example, as the [background image](#).
736
+
737
+ **url:** the URL to an image.
738
+
739
+ ### Properties
740
+
741
+ You can customize the scatter plot according to the following properties that
742
+ can be read and written via [`scatterplot.get()`](#scatterplot.get) and [`scatterplot.set()`](#scatterplot.set).
743
+
744
+ | Name | Type | Default | Constraints | Settable | Nullifiable |
745
+ | ------------------------------------- | -------------------------------------------- | ----------------------------------- | --------------------------------------------------------------- | -------- | ----------- |
746
+ | canvas | object | `document.createElement('canvas')` | | `false` | `false` |
747
+ | regl | [Regl](https://github.com/regl-project/regl) | `createRegl(canvas)` | | `false` | `false` |
748
+ | renderer | [Renderer](#renderer) | `createRenderer()` | | `false` | `false` |
749
+ | syncEvents | boolean | `false` | | `false` | `false` |
750
+ | version | string | | | `false` | `false` |
751
+ | spatialIndex | ArrayBuffer | | | `false` | `false` |
752
+ | spatialIndexUseWorker | undefined or boolean | `undefined` | | `true` | `false` |
753
+ | width | int or str | `'auto'` | `'auto'` or > 0 | `true` | `false` |
754
+ | height | int or str | `'auto'` | `'auto'` or > 0 | `true` | `false` |
755
+ | aspectRatio | float | `1.0` | > 0 | `true` | `false` |
756
+ | backgroundColor | string or array | rgba(0, 0, 0, 1) | hex, rgb, rgba | `true` | `false` |
757
+ | backgroundImage | function | `null` | Regl texture | `true` | `true` |
758
+ | camera | object | | See [dom-2d-camera](https://github.com/flekschas/dom-2d-camera) | `false` | `false` |
759
+ | cameraTarget | tuple | `[0, 0]` | | `true` | `false` |
760
+ | cameraDistance | float | `1` | > 0 | `true` | `false` |
761
+ | cameraRotation | float | `0` | | `true` | `false` |
762
+ | cameraView | Float32Array | `[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]` | | `true` | `false` |
763
+ | cameraIsFixed | boolean | `false` | | `true` | `false` |
764
+ | colorBy | string | `null` | See [data encoding](#property-by) | `true` | `true` |
765
+ | sizeBy | string | `null` | See [data encoding](#property-by) | `true` | `true` |
766
+ | opacityBy | string | `null` | See [data encoding](#property-by) | `true` | `true` |
767
+ | deselectOnDblClick | boolean | `true` | | `true` | `false` |
768
+ | deselectOnEscape | boolean | `true` | | `true` | `false` |
769
+ | opacity | float | `1` | Must be in ]0, 1] | `true` | `false` |
770
+ | opacityInactiveMax | float | `1` | Must be in [0, 1] | `true` | `false` |
771
+ | opacityInactiveScale | float | `1` | Must be in [0, 1] | `true` | `false` |
772
+ | points | tuple[] | `[[0.5, 2.3], ...]` | | `false` | `false` |
773
+ | selectedPoints | int[] | `[4, 2]` | | `false` | `false` |
774
+ | filteredPoints | int[] | `[4, 2]` | | `false` | `false` |
775
+ | pointsInView | int[] | `[1, 2, 12]` | | `false` | `false` |
776
+ | pointColor | quadruple | `[0.66, 0.66, 0.66, 1]` | single value or list of hex, rgb, rgba | `true` | `false` |
777
+ | pointColorActive | quadruple | `[0, 0.55, 1, 1]` | single value or list of hex, rgb, rgba | `true` | `false` |
778
+ | pointColorHover | quadruple | `[1, 1, 1, 1]` | single value or list of hex, rgb, rgba | `true` | `false` |
779
+ | pointOutlineWidth | int | `2` | >= 0 | `true` | `false` |
780
+ | pointSize | int | `6` | > 0 | `true` | `false` |
781
+ | pointSizeSelected | int | `2` | >= 0 | `true` | `false` |
782
+ | showPointConnection | boolean | `false` | | `true` | `false` |
783
+ | pointConnectionColor | quadruple | `[0.66, 0.66, 0.66, 0.2]` | | `true` | `false` |
784
+ | pointConnectionColorActive | quadruple | `[0, 0.55, 1, 1]` | | `true` | `false` |
785
+ | pointConnectionColorHover | quadruple | `[1, 1, 1, 1]` | | `true` | `false` |
786
+ | pointConnectionColorBy | string | `null` | See [data encoding](#property-point-conntection-by) | `true` | `false` |
787
+ | pointConnectionOpacity | float | `0.1` | | `true` | `false` |
788
+ | pointConnectionOpacityActive | float | `0.66` | | `true` | `false` |
789
+ | pointConnectionOpacityBy | string | `null` | See [data encoding](#property-point-conntection-by) | `true` | `false` |
790
+ | pointConnectionSize | float | `2` | | `true` | `false` |
791
+ | pointConnectionSizeActive | float | `2` | | `true` | `false` |
792
+ | pointConnectionSizeBy | string | `null` | See [data encoding](#property-point-conntection-by) | `true` | `false` |
793
+ | pointConnectionMaxIntPointsPerSegment | int | `100` | | `true` | `false` |
794
+ | pointConnectionTolerance | float | `0.002` | | `true` | `false` |
795
+ | pointScaleMode | string | `'asinh'` | `'asinh'`, `'linear'`, or `'constant'` | `true` | `false` |
796
+ | lassoMode | string | `null` | `null`, `'intersect'`, `'merge'`, or `'remove'` | `true` | `true` |
797
+ | lassoType | string | `'freeform'` | `'freeform'`, `'rectangle'`, or `'brush'` | `true` | `false` |
798
+ | lassoColor | quadruple | rgba(0, 0.667, 1, 1) | hex, rgb, rgba | `true` | `false` |
799
+ | lassoLineWidth | float | 2 | >= 1 | `true` | `false` |
800
+ | lassoMinDelay | int | 15 | >= 0 | `true` | `false` |
801
+ | lassoMinDist | int | 4 | >= 0 | `true` | `false` |
802
+ | lassoClearEvent | string | `'lassoEnd'` | `'lassoEnd'` or `'deselect'` | `true` | `false` |
803
+ | lassoInitiator | boolean | `false` | | `true` | `false` |
804
+ | lassoInitiatorElement | object | the lasso dom element | | `false` | `false` |
805
+ | lassoInitiatorParentElement | object | `document.body` | | `true` | `false` |
806
+ | lassoLongPressIndicatorParentElement | object | `document.body` | | `true` | `false` |
807
+ | lassoOnLongPress | boolean | `false` | | `true` | `false` |
808
+ | lassoLongPressTime | int | `750` | | `true` | `false` |
809
+ | lassoLongPressAfterEffectTime | int | `500` | | `true` | `false` |
810
+ | lassoLongPressEffectDelay | int | `100` | | `true` | `false` |
811
+ | lassoLongPressRevertEffectTime | int | `250` | | `true` | `false` |
812
+ | lassoBrushSize | int | `24` | | `true` | `false` |
813
+ | showReticle | boolean | `false` | `true` or `false` | `true` | `false` |
814
+ | reticleColor | quadruple | rgba(1, 1, 1, .5) | hex, rgb, rgba | `true` | `false` |
815
+ | xScale | function | `null` | must follow the D3 scale API | `true` | `true` |
816
+ | yScale | function | `null` | must follow the D3 scale API | `true` | `true` |
817
+ | actionKeyMap | object | `{ remove: 'alt': rotate: 'alt', merge: 'cmd', lasso: 'shift' }` | See the notes below | `true` | `false` |
818
+ | mouseMode | string | `'panZoom'` | `'panZoom'`, `'lasso'`, or `'rotate'` | `true` | `false` |
819
+ | performanceMode | boolean | `false` | can only be set during initialization! | `true` | `false` |
820
+ | gamma | float | `1` | to control the opacity blending | `true` | `false` |
821
+ | isDestroyed | boolean | `false` | | `false` | `false` |
822
+ | isPointsDrawn | boolean | `false` | | `false` | `false` |
823
+ | isPointsFiltered | boolean | `false` | | `false` | `false` |
824
+ | annotationLineColor | string or quadruple | `[1, 1, 1, 0.1]` | hex, rgb, rgba | `true` | `false` |
825
+ | annotationLineWidth | number | `1` | | `true` | `false` |
826
+ | annotationHVLineLimit | number | `1000` | the extent of horizontal or vertical lines | `true` | `false` |
827
+ | antiAliasing | number | `0.5` | higher values result in more blurry points | `true` | `false` |
828
+ | pixelAligned | number | `false` | if true, points are aligned with the pixel grid | `true` | `false` |
829
+ | renderPointsAsSquares | boolean | `false` | true of `performanceMode` is true. can only be set on init! | `true` | `false` |
830
+ | disableAlphaBlending | boolean | `false` | true of `performanceMode` is true. can only be set on init! | `true` | `false` |
831
+
832
+ <a name="property-notes" href="#property-notes">#</a> <b>Notes:</b>
833
+
834
+ - An attribute is considered _nullifiable_ if it can be unset. Attributes that
835
+ are **not nullifiable** will be ignored if you try to set them to a falsy
836
+ value. For example, if you call `scatterplot.attr({ width: 0 });` the width
837
+ will not be changed as `0` is interpreted as a falsy value.
838
+
839
+ - By default, the `width` and `height` are set to `'auto'`, which will make the
840
+ `canvas` stretch all the way to the bounds of its clostest parent element with
841
+ `position: relative`. When set to `'auto'` the library also takes care of
842
+ resizing the canvas on `resize` and `orientationchange` events.
843
+
844
+ - The background of the scatterplot is transparent, i.e., you have to control
845
+ the background with CSS! `background` is used when drawing the
846
+ outline of selected points to simulate the padded border only.
847
+
848
+ - The background image must be a Regl texture. To easily set a remote
849
+ image as the background please use [`createTextureFromUrl`](#const-texture--createTextureFromUrlregl-url-isCrossOrigin).
850
+
851
+ - The scatterplot understan 4 colors per color representing 4 states, representing:
852
+
853
+ - normal (`pointColor`): the normal color of points.
854
+ - active (`pointColorActive`): used for coloring selected points.
855
+ - hover (`pointColorHover`): used when mousing over a point.
856
+ - background (`backgroundColor`): used as the background color.
857
+
858
+ - Points can currently by colored by _category_ and _value_.
859
+
860
+ - The size of selected points is given by `pointSize + pointSizeSelected`
861
+
862
+ - By default, events are published asynchronously to decouple regl-scatterplot's
863
+ execution flow from the event consumer's process. However, you can enable
864
+ synchronous event broadcasting at your own risk via
865
+ `createScatterplot({ syncEvents: true })`. This property can't be changed
866
+ after initialization!
867
+
868
+ - If you need to draw more than 2 million points, you might want to set
869
+ `performanceMode` to `true` during the initialization to boost the
870
+ performance. In performance mode, points will be drawn as simple squares and
871
+ alpha blending is disabled. This should allow you to draw up to 20 million
872
+ points (or more depending on your hardware). Make sure to reduce the
873
+ `pointSize` as you render more and more points (e.g., `0.25` for 20 million
874
+ works for me) to ensure good performance. You can also enable squared points
875
+ and disable alpha blending individually via `renderPointsAsSquares` and
876
+ `disableAlphaBlending` respectively.
877
+
878
+ <a name="property-by" href="#property-by">#</a> <b>colorBy, opacityBy, sizeBy:</b>
879
+
880
+ To visual encode one of the two point values set `colorBy`, `opacityBy`, or `sizeBy`
881
+ to one of the following values referencing the third or forth component of your
882
+ points. To reference the third component you can use `category` (only for
883
+ backwards compatibility), `value1`, `valueA`, `valueZ`, or `z`. To reference
884
+ the forth component use `value` (only for backwards compatibility), `value2`,
885
+ `valueB`, `valueW`, or `w`.
886
+
887
+ **Density-based opacity encoding:** In addition, the opacity can dynamically be
888
+ set based on the point density and zoom level via `opacityBy: 'density'`. As an
889
+ example go to [dynamic-opacity.html](https://flekschas.github.io/regl-scatterplot/dynamic-opacity.html).
890
+ The implementation is an extension of [Ricky Reusser's awesome notebook](https://observablehq.com/@rreusser/selecting-the-right-opacity-for-2d-point-clouds).
891
+ Huuuge kudos Ricky! 🙇‍♂️
892
+
893
+ <a name="property-point-conntection-by" href="#property-point-conntection-by">#</a> <b>pointConnectionColorBy, pointConnectionOpacityBy, and pointConnectionSizeBy:</b>
894
+
895
+ In addition to the properties understood by [`colorBy`, etc.](#property-by),
896
+ `pointConnectionColorBy`, `pointConnectionOpacityBy`, and `pointConnectionSizeBy`
897
+ also understand `"inherit"` and `"segment"`. When set to `"inherit"`, the value
898
+ will be inherited from its point-specific counterpart. When set to `"segment"`,
899
+ each segment of a point connection will be encoded separately. This allows you
900
+ to, for instance, color connection by a gradient from the start to the end of
901
+ each line.
902
+
903
+ <a name="property-lassoInitiator" href="#property-lassoInitiator">#</a> <b>lassoInitiator:</b>
904
+
905
+ When setting `lassoInitiator` to `true` you can initiate the lasso selection
906
+ without the need to hold down a modifier key. Simply click somewhere into the
907
+ background and a circle will appear under your mouse cursor. Now click into the
908
+ circle and drag you mouse to start lassoing. You can additionally invoke the
909
+ lasso initiator circle by a long click on a dot.
910
+
911
+ ![Lasso Initiator](https://user-images.githubusercontent.com/932103/106489598-f42c4480-6482-11eb-8286-92a9956e1d20.gif)
912
+
913
+ You don't like the look of the lasso initiator? No problem. Simple get the DOM
914
+ element via `scatterplot.get('lassoInitiatorElement')` and adjust the style
915
+ via JavaScript. E.g.: `scatterplot.get('lassoInitiatorElement').style.background = 'green'`.
916
+
917
+ <a name="property-keymap" href="#property-keymap">#</a> <b>ActionKeyMap:</b>
918
+
919
+ The `actionKeyMap` property is an object defining which actions are enabled when
920
+ holding down which modifier key. E.g.: `{ lasso: 'shift' }`. Acceptable actions
921
+ are `lasso`, `rotate`, `merge` (for selecting multiple items by merging a series
922
+ of lasso or click selections), `intersect` (for selecting items that are a subset of selected points) and `remove` (for removing selected points).
923
+ Acceptable modifier keys are `alt`, `cmd`, `ctrl`, `meta`, `shift`.
924
+
925
+ You can also use the `actionKeyMap` option to disable the lasso selection and
926
+ rotation by setting `actionKeyMap` to an empty object.
927
+
928
+ The selection mode can be set via the property `lassoMode`, in which case the corresponding keyboard shortcuts will be ignored.
929
+
930
+ <a name="property-examples" href="#property-examples">#</a> <b>Examples:</b>
931
+
932
+ ```javascript
933
+ // Set width and height
934
+ scatterplot.set({ width: 300, height: 200 });
935
+
936
+ // get width
937
+ const width = scatterplot.get('width');
938
+
939
+ // Set the aspect ratio of the scatterplot. This aspect ratio is referring to
940
+ // your data source and **not** the aspect ratio of the canvas element! By
941
+ // default it is assumed that your data us following a 1:1 ratio and this ratio
942
+ // is preserved even if your canvas element has some other aspect ratio. But if
943
+ // you wanted you could provide data that's going from [0,2] in x and [0,1] in y
944
+ // in which case you'd have to set the aspect ratio as follows to `2`.
945
+ scatterplot.set({ aspectRatio: 2.0 });
946
+
947
+ // Set background color to red
948
+ scatterplot.set({ backgroundColor: '#00ff00' }); // hex string
949
+ scatterplot.set({ backgroundColor: [255, 0, 0] }); // rgb array
950
+ scatterplot.set({ backgroundColor: [255, 0, 0, 1.0] }); // rgba array
951
+ scatterplot.set({ backgroundColor: [1.0, 0, 0, 1.0] }); // normalized rgba
952
+
953
+ // Set background image to an image
954
+ scatterplot.set({ backgroundImage: 'https://server.com/my-image.png' });
955
+ // If you need to know when the image was loaded you have two options. First,
956
+ // you can listen to the following event
957
+ scatterplot.subscribe(
958
+ 'backgroundImageReady',
959
+ () => {
960
+ console.log('Background image is now loaded and rendered!');
961
+ },
962
+ 1
963
+ );
964
+ // or you load the image yourself as follows
965
+ const backgroundImage = await scatterplot.createTextureFromUrl(
966
+ 'https://server.com/my-image.png'
967
+ );
968
+ scatterplot.set({ backgroundImage });
969
+
970
+ // Color by
971
+ scatterplot.set({ colorBy: 'category' });
972
+
973
+ // Set color map
974
+ scatterplot.set({
975
+ pointColor: ['#ff0000', '#00ff00', '#0000ff'],
976
+ pointColorActive: ['#ff0000', '#00ff00', '#0000ff'], // optional
977
+ pointColorHover: ['#ff0000', '#00ff00', '#0000ff'], // optional
978
+ });
979
+
980
+ // Set base opacity
981
+ scatterplot.set({ opacity: 0.5 });
982
+
983
+ // If you want to deemphasize unselected points (when some points are selected)
984
+ // you can rescale the unselected points' opacity as follows
985
+ scatterplot.set({ opacityInactiveScale: 0.5 });
986
+
987
+ // Set the width of the outline of selected points
988
+ scatterplot.set({ pointOutlineWidth: 2 });
989
+
990
+ // Set the base point size
991
+ scatterplot.set({ pointSize: 10 });
992
+
993
+ // Set the additional point size of selected points
994
+ scatterplot.set({ pointSizeSelected: 2 });
995
+
996
+ // Change the lasso color and make it very smooth, i.e., do not wait before
997
+ // extending the lasso (i.e., `lassoMinDelay = 0`) and extend the lasso when
998
+ // the mouse moves at least 1 pixel
999
+ scatterplot.set({
1000
+ lassoColor: [1, 1, 1, 1],
1001
+ lassoMinDelay: 0,
1002
+ lassoMinDist: 1,
1003
+ // This will keep the drawn lasso until the selected points are deselected
1004
+ lassoClearEvent: 'deselect',
1005
+ });
1006
+
1007
+ // Activate reticle and set reticle color to red
1008
+ scatterplot.set({ showReticle: true, reticleColor: [1, 0, 0, 0.66] });
1009
+ ```
1010
+
1011
+ ### Renderer
1012
+
1013
+ The renderer class is responsible for rendering pixels onto the scatter plot's
1014
+ canvas using WebGL via Regl. It's created automatically internally but you can
1015
+ also create it yourself, which can be useful when you want to instantiate
1016
+ multiple scatter plot instances as they can share one renderer.
1017
+
1018
+ #### Renderer API
1019
+
1020
+ <a name="renderer.canvas" href="#renderer.canvas">#</a> renderer.<b>canvas</b>
1021
+
1022
+ The renderer's canvas instance. (Read-only)
1023
+
1024
+ <a name="renderer.gamma" href="#renderer.gamma">#</a> renderer.<b>gamma</b>
1025
+
1026
+ The renderer's gamma value. This value influences the alpha blending.
1027
+
1028
+ <a name="renderer.regl" href="#renderer.regl">#</a> renderer.<b>regl</b>
1029
+
1030
+ The renderer's regl instance. (Read-only)
1031
+
1032
+ <a name="renderer.onFrame" href="#renderer.onFrame">#</a> renderer.<b>onFrame</b>(<i>function</i>)
1033
+
1034
+ Add a function to be called on every animation frame.
1035
+
1036
+ **Arguments:**
1037
+
1038
+ - `function`: The function to be called on every animation frame.
1039
+
1040
+ **Returns:** A function to remove the added function from the animation frame cycle.
1041
+
1042
+ <a name="renderer.refresh" href="#renderer.refresh">#</a> renderer.<b>refresh</b>()
1043
+
1044
+ Updates Regl's viewport, drawingBufferWidth, and drawingBufferHeight.
1045
+
1046
+ <a name="renderer.render" href="#renderer.render">#</a> renderer.<b>render</b>(<i>drawFunction</i>, <i>targetCanvas</i>)
1047
+
1048
+ Render Regl draw instructions into a target canvas using the renderer.
1049
+
1050
+ **Arguments:**
1051
+
1052
+ - `drawFunction`: The draw function that triggers Regl draw instructions
1053
+ - `targetCanvas`: The canvas to rendering the final pixels into.
1054
+
1055
+ ### Events
1056
+
1057
+ | Name | Trigger | Payload |
1058
+ | -------------------- | ------------------------------------------ | ------------------------------------------------- |
1059
+ | init | when the scatter plot is initialized | `undefined` |
1060
+ | destroy | when the scatter plot is destroyed | `undefined` |
1061
+ | backgroundImageReady | when the background image was loaded | `undefined` |
1062
+ | pointOver | when the mouse cursor is over a point | pointIndex |
1063
+ | pointOut | when the mouse cursor moves out of a point | pointIndex |
1064
+ | select | when points are selected | `{ points }` |
1065
+ | deselect | when points are deselected | `undefined` |
1066
+ | filter | when points are filtered | `{ points }` |
1067
+ | unfilter | when the point filter is reset | `undefined` |
1068
+ | view | when the view has changes | `{ camera, view, isViewChanged, xScale, yScale }` |
1069
+ | draw | when the plot was drawn | `{ camera, view, isViewChanged, xScale, yScale }` |
1070
+ | drawing | when the plot is being drawn | `{ camera, view, isViewChanged, xScale, yScale }` |
1071
+ | lassoStart | when the lasso selection has started | `undefined` |
1072
+ | lassoExtend | when the lasso selection has extended | `{ coordinates }` |
1073
+ | lassoEnd | when the lasso selection has ended | `{ coordinates }` |
1074
+ | transitionStart | when points started to transition | `undefined` |
1075
+ | transitionEnd | when points ended to transition | `createRegl(canvas)` |
1076
+ | pointConnectionsDraw | when point connections were drawn | `undefined` |
1077
+
1078
+ ## Trouble Shooting
1079
+
1080
+ #### Resizing the scatterplot
1081
+
1082
+ The chances are high that you use the regl-scatterplot in a dynamically-resizable or interactive web-app. Please note that **regl-scatterplot doesn't not automatically resize** when the dimensions of its parent container change. It's your job to keep the size of regl-scatterplot and its parent element in sync. Hence, every time the size of the parent or `canvas` element changed, you have to call:
1083
+
1084
+ ```javascript
1085
+ const { width, height } = canvas.getBoundingClientRect();
1086
+ scatterplot.set({ width, height });
1087
+ ```
1088
+
1089
+ #### Using regl-scatterplot with Vue
1090
+
1091
+ Related to the resizing, when conditionally displaying regl-scatterplot in Vue you might have to update the `width` and `height` when the visibility is changed. See [issue #20](https://github.com/flekschas/regl-scatterplot/issues/20#issuecomment-639377810) for an example.
1092
+
1093
+ ## Citation
1094
+
1095
+ If you like `regl-scatterplot` and are using it in your research, we'd appreciate if you could cite our paper:
1096
+
1097
+ ```bibtex
1098
+ @article {lekschas2023reglscatterplot,
1099
+ author = {Lekschas, Fritz},
1100
+ title = {Regl-Scatterplot: A Scalable Interactive JavaScript-based Scatter Plot Library},
1101
+ journal = {Journal of Open Source Software},
1102
+ volume = {8},
1103
+ number = {84},
1104
+ pages = {5275},
1105
+ year = {2023},
1106
+ month = {4},
1107
+ doi = {10.21105/joss.05275},
1108
+ url = {https://doi.org/10.21105/joss.05275},
1109
+ }
1110
+ ```